Debug ID changes the bytes of an optimized chunk but not its file name, so cached files fail the SRI check

クローズ
#34,574 コメント 0 件 リアクション 4 件 担当者 0 名 GitHub で見る

メンテナーはふだん 1 日以内に返信

関連するプルリクエストがすでにマージされています。

  • #34583 @alan-agius4 による — マージ済み

評価

難易度
4/5
見積もり時間
3〜5日
初心者へのやさしさ
55/100
issue の種類
バグ
明瞭さ
明確に書かれている
活発さ
活発
技術スタック
angular, typescript
領域
build-system

調査の方向性

Start in execute-post-bundle.ts, where injectDebugIds changes output bytes, and chunk-optimizer.ts, where Rolldown assigns hashed chunk names. Trace how the chunk hash is computed and how debug IDs are injected, then check the build tests for optimized chunks and source maps. Done means a changed chunk byte sequence gets a different filename, including when only its source map changes.

索引モデルが issue の本文から書いたものです。

説明

area: @angular/build
Command

build

Is this a regression?
  • Yes, this behavior used to work in the previous version
The previous version in which this bug was not present was

22.0.x

Description

Since 22.1.0 (#33110), the application builder adds a //# debugId=<uuid> line to each browser JS file when script source maps are enabled. The ID comes from the bytes of the source map.

When the chunk optimizer runs (by default when the build has 3 or more lazy chunks), Rolldown gives the chunks their final file names. The [hash] in these names covers the code of the chunk only. It does not cover the source map. The debugId line is added after that.

A change that keeps the JS code of a chunk the same but changes its source map (for example, a change to a TypeScript type or to a comment) gives this result for a chunk that does not import other chunks:

  1. The file name stays the same.
  2. The debugId changes, because the source map changed.
  3. The file bytes change, because the debugId line changed.
  4. The SRI hash in index.html changes to match the new bytes.

In one build, the HTML and the files match. Across two builds, the rule "same hashed file name means same content" is no longer true.

Hosting setups depend on this rule: they cache hashed files as immutable. Any cache can keep the file from the previous build: a CDN such as Cloudflare, the browser cache, or a proxy. That cache then serves the old bytes together with the new index.html. The integrity check fails, the browser blocks the file, and the modules in it do not load.

Before 22.1.0 there was no debugId line, so the bytes of a hashed file changed only when its name changed.

Expected behavior: if the bytes of a hashed output file change, its file name changes too.

Minimal Reproduction

Repository: https://github.com/virgiliojr/angular-debugid-sri-repro

The app is a plain ng new app (Angular CLI 22.2.2) with 3 lazy routes, so the chunk optimizer runs. lazy-a and lazy-b use src/app/shared/label.ts. This module has no imports, so it becomes its own chunk.

  1. Run npm ci.
  2. Run npx ng build --source-map --subresource-integrity.
  3. In dist/repro/browser/, find the chunk that contains label:. Write down its file name and its SHA-384.
  4. In src/app/shared/label.ts, add extra?: number; to the LabelInput interface. This is a type-only change: the JS output of label.ts stays the same.
  5. Run npx ng build --source-map --subresource-integrity again.
  6. Find the chunk that contains label: again.

bash compare-builds.sh in the repository does steps 2 to 6 and prints the result below.

Actual result:

Build 1
  file name:          chunk-z-ytF2G0.js
  sha384 of bytes:    sha384-Y7f5K4V0FTCrXc6JQ2tGE+VllRWg501EixeFEPMjNMboDgwfeRc9E6l8oHlqTbX5
  debugId line:       //# debugId=105cedd6-2603-584f-8e8d-7801d6020b96
  sha in index.html:  yes
Build 2
  file name:          chunk-z-ytF2G0.js
  sha384 of bytes:    sha384-bgCbCJmJexbhKCHeS4kpofe2lKBPWJ1f3JpK9668+/jD2mfiEYADUy5xD1Usr1dZ
  debugId line:       //# debugId=9c547775-c029-5af8-ba7e-25c70949f1b6
  sha in index.html:  yes

Diff of the chunk between the two builds:
2c2
< //# debugId=105cedd6-2603-584f-8e8d-7801d6020b96
---
> //# debugId=9c547775-c029-5af8-ba7e-25c70949f1b6

The file name is the same, but the bytes and the SRI hash in index.html are different.

Expected result: if the bytes are different, the file name is different too.

To see the failure in a browser, serve the output of build 2, but serve chunk-z-ytF2G0.js from build 1. This is what a cache does after a deployment.

Exception or Error
Failed to find a valid digest in the 'integrity' attribute for resource 'https://<host>/chunk-z-ytF2G0.js' with computed SHA-384 integrity '<hash>'. The resource has been blocked.
Your Environment
Angular CLI       : 22.2.5
Angular           : 22.2.3
Node.js           : 24.18.0
Package Manager   : npm 11.16.0
Operating System  : darwin arm64

┌───────────────────────────┬───────────────────┬───────────────────┐
│ Package                   │ Installed Version │ Requested Version │
├───────────────────────────┼───────────────────┼───────────────────┤
│ @angular/build            │ 22.2.5            │ ^22.2.2           │
│ @angular/cli              │ 22.2.5            │ ^22.2.2           │
│ @angular/common           │ 22.2.3            │ ^22.2.0           │
│ @angular/compiler         │ 22.2.3            │ ^22.2.0           │
│ @angular/compiler-cli     │ 22.2.3            │ ^22.2.0           │
│ @angular/core             │ 22.2.3            │ ^22.2.0           │
│ @angular/forms            │ 22.2.3            │ ^22.2.0           │
│ @angular/platform-browser │ 22.2.3            │ ^22.2.0           │
│ @angular/router           │ 22.2.3            │ ^22.2.0           │
│ rxjs                      │ 7.8.2             │ ~7.8.0            │
│ typescript                │ 6.0.3             │ ~6.0.2            │
└───────────────────────────┴───────────────────┴───────────────────┘
Anything else relevant?

This does not depend on the browser. Any cache in front of the hashed files can cause it.

The defect needs the chunk optimizer. With NG_BUILD_OPTIMIZE_CHUNKS=false, the same type change gives a new esbuild file name for the chunk (for example chunk-P54LQIKA.js to chunk-5HS55H7R.js), so the name and the bytes change together.

A chunk that imports other chunks usually gets a new name anyway, because the names of the chunks it imports change. A chunk without such imports keeps its name.

Related: #33108, #33110, #33572. The current main still has this behavior: injectDebugIds (called from execute-post-bundle.ts when sourcemapOptions.scripts is true) changes the file contents but does not change the file name. The Rolldown pass in chunk-optimizer.ts uses chunkFileNames: '<name>-[hash].js'.

主要言語
TypeScript
スター
27k
フォーク
11.8k
平均マージ
1日 52分
マージ済み PR(30日)
268

環境構築

はじめの一歩

  1. issue を最後まで読み、次にプロジェクトのコントリビューションガイドを読みます。
  2. 着手することを issue にコメントします — 二人が同じ作業をするのを防げます。
  3. リポジトリをフォークし、ブランチを切って変更します。
  4. issue 番号を参照したプルリクエストを送ります。

angular/angular-cli のほかの issue

angular/angular-cli の issue をすべて見る

似ている issue

TypeScript の issue をもっと見る

新しい issue をメールで受け取る

初心者向けの GitHub issue を短くまとめたダイジェスト。