Skip to content

application builder writes ngsw.json entries for files only present in server dist (SSR + i18n + JS-import CSS) #33922

Description

@ftfeiteng

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

bug is present on every version we tested, 19.2.27 through 22.1.3

Description

Description

The @angular/build:application builder emits a browser ngsw.json whose hashTable and assetGroups[*].urls list hashed CSS files that only exist in the server output directory, not the browser one.

Root cause

Defect is in packages/angular/build/src/builders/application/build-action.js, around lines 231–238:

for (const file of outputFiles) {
    result.files[file.path] = {          // ← keyed by file.path only; file.type ignored
        type: file.type,
        contents: file.contents,
        origin: 'memory',
        hash: file.hash,
    };
}

Step 1 — Two esbuild passes emit the same relative path

The application builder runs esbuild twice from a single build invocation:

  • Browser pass — produces the client bundle. Outputs a chunk chunk-4JJCZF36.css and tags every file with type: Browser.
  • Server pass — produces the SSR bundle. Outputs its own chunk-4JJCZF36.css (identical minified content, because both variants came from the same import 'normalize.css' in the same lazy component) and tags every file with type: ServerApplication.

Step 2 — result.files[path] collision

Both files get pushed into a single JS object:

result.files[file.path] = { type, contents, hash, origin };

Because the key is file.path — and paths are identical between the two passes — the second write silently overwrites the first, so ServerApplication wins and Browser is discarded.

Minimal Reproduction

https://github.com/ftfeiteng/ngsw-css-repro

git clone https://github.com/ftfeiteng/ngsw-css-repro.git && cd ngsw-css-repro
npm install
npx ng build --configuration=production

Then inspect the mismatch:

# CSS URLs the browser manifest thinks exist:
jq -r '.hashTable | keys[] | select(endswith(".css"))' \
  dist/ngsw-phantom-css-repro/browser/en/ngsw.json
#   /en/chunk-4JJCZF36.css      ← phantom
#   /en/main-ICO76ARU.css
#   /en/styles-5INURTSO.css

# CSS files actually in the browser dist:
ls dist/ngsw-phantom-css-repro/browser/en/*.css
#   dist/…/browser/en/main-ICO76ARU.css
#   dist/…/browser/en/styles-5INURTSO.css

# Where the missing file went:
ls dist/ngsw-phantom-css-repro/server/en/chunk-*.css
#   dist/…/server/en/chunk-4JJCZF36.css

Exception or Error


Your Environment

Reproduces on:


  Angular CLI:                    19.2.27
  @angular-devkit/build-angular:  19.2.27
  @angular/build:                 19.2.27
  @angular/core:                  19.2.25
  @angular/service-worker:        19.2.25
  @angular/ssr:                   19.2.27
  Node:                           20.19.4
  TypeScript:                     5.7.3


Also confirmed on 22.1.3

Anything else relevant?

Expected behaviour

Every URL in ngsw.json.hashTable resolves to a file under the browser output directory.

Impact

  • Service worker install fails → PWA offline caching broken for new clients.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions