Skip to content

feat(web): emit precache_manifest.json when --web-content-hash is enabled - #192953

Merged
auto-submit[bot] merged 2 commits into
flutter:masterfrom
kevmoo:web-content-hash-phase-3
Sep 21, 2026
Merged

auto-submit[bot] merged 2 commits into
flutter:masterfrom
kevmoo:web-content-hash-phase-3

Conversation

@kevmoo

@kevmoo kevmoo commented Sep 17, 2026 •

Copy link
Copy Markdown
Contributor

Description

Implements Phase 3 of Flutter Web Content Hashing (#191916):

  • Emit build/web/precache_manifest.json when --web-content-hash is enabled:
    • Generated inside WebServiceWorker.build (packages/flutter_tools/lib/src/web/content_hash.dart) after hashWebAssets rewrites hashed assets.
    • Emits { "version": 1, "entries": [...] } sorted lexicographically by "url" for deterministic output.
    • Each entry includes:
      • "url": POSIX path relative to build/web/
      • "hash": 8-character lowercase hex SHA-256 prefix of the file's bytes
      • "size": File byte length (int)
      • "urlHashed": bool (true if the filename already contains .$hash. or ends with .$hash so custom service workers/PWAs can skip redundant ?v=<hash> cache-busting query parameters; false for fixed-URL files such as index.html, flutter_bootstrap.js, assets/AssetManifest.bin, assets/FontManifest.json, assets/NOTICES, and version.json).
  • Non-Runtime Bloat Exclusion:
    • Excludes source maps (*.map), symbols (*.symbols), compiler info (*.info.json), dotfiles (. prefix), flutter_service_worker.js, precache_manifest.json itself, and canvaskit/** (unless kUseLocalCanvasKitFlag == 'true' / --no-web-resources-cdn).
  • Stale Manifest Cleanup & Depfile Safety:
    • Deletes any stale precache_manifest.json in build/web/ when --web-content-hash is false.
    • Excludes precache_manifest.json from WebServiceWorker input discovery while registering it as a Depfile output to prevent input/output cycles in service_worker.d.
  • Preserve hide: true on --web-content-hash:
    • Keeps hide: true on WebOptions.webContentHash while runtime raw-key asset resolution (rootBundle.load, ui_web.AssetManager.getAssetUrl) is completed in companion tasks, and updates the --help description to clarify that assets/AssetManifest.bin.json and assets/FontManifest.json must be served with revalidation while they remain unhashed.

Note

On master, passing the hidden --web-content-hash flag currently causes raw-key asset loads (rootBundle.load(...), vector_graphics, DOM media plugins) to 404 on cold cache because physical files are content-hashed on disk but runtime resolution has not yet landed. Phase 2.5 will add runtime key-to-hashed asset resolution in the web engine (ui_web.AssetManager) before --web-content-hash is un-hidden for general use.

Related Issues

Tests

  • Added 3 unit tests in packages/flutter_tools/test/general.shard/build_system/targets/web_test.dart verifying precache_manifest.json schema/sorting/filtering, kUseLocalCanvasKitFlag == 'true' inclusion of canvaskit/, and stale manifest deletion when webContentHash: false.

@github-actions github-actions Bot added the tool Affects the "flutter" command-line tool. See also t: labels. label Sep 17, 2026
…bled

- Emit build/web/precache_manifest.json in WebServiceWorker when --web-content-hash is enabled, providing deterministic URL, SHA-256 prefix hash, byte size, and urlHashed metadata for custom service workers and PWAs.

- Filter non-runtime build artifacts (*.map, *.symbols, *.info.json, dotfiles, flutter_service_worker.js, precache_manifest.json, and canvaskit/** when --web-resources-cdn is active).

- Clean up stale precache_manifest.json when --web-content-hash is disabled and prevent Depfile input/output cycles.

- Keep --web-content-hash hidden while runtime raw-key asset resolution (rootBundle.load) is completed.

Fixes flutter#191916

Part of flutter#149031
@kevmoo
kevmoo force-pushed the web-content-hash-phase-3 branch from e8d540c to f2d9b59 Compare September 18, 2026 21:54
@kevmoo kevmoo changed the title feat(web): emit precache_manifest.json and un-hide --web-content-hash feat(web): emit precache_manifest.json when --web-content-hash is enabled Sep 18, 2026
@kevmoo
kevmoo marked this pull request as ready for review September 18, 2026 22:38
@kevmoo kevmoo added the CICD Run CI/CD label Sep 18, 2026

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces content hashing for static web assets and adds a 'precache_manifest.json' file to improve browser caching after deployments. It includes logic to generate this manifest, exclude non-runtime files, and handle stale manifest deletion. Additionally, it updates the 'web-content-hash' flag documentation and adds comprehensive tests for the new functionality. A syntax error regarding the conditional list element in 'web.dart' was identified, and a missing documentation comment for a public constant was noted.

Comment thread packages/flutter_tools/lib/src/build_system/targets/web.dart
Comment thread packages/flutter_tools/lib/src/web/content_hash.dart
@kevmoo
kevmoo requested a review from mdebbar September 18, 2026 22:51
@kevmoo kevmoo added the autosubmit Merge PR when tree becomes green via auto submit App label Sep 21, 2026
@auto-submit
auto-submit Bot added this pull request to the merge queue Sep 21, 2026
Merged via the queue into flutter:master with commit 985e184 Sep 21, 2026
23 checks passed
@flutter-dashboard flutter-dashboard Bot removed the autosubmit Merge PR when tree becomes green via auto submit App label Sep 21, 2026
@kevmoo
kevmoo deleted the web-content-hash-phase-3 branch September 21, 2026 17:55
pull Bot pushed a commit to Mu-L/flutter that referenced this pull request Sep 23, 2026
…hide --web-content-hash (flutter#193040)

## Description

Completes the `--web-content-hash` architecture by adding runtime
raw-key asset resolution in `web_ui`, achieving 100% content-hashing
coverage across all files in `build/web/assets/` (eliminating manifest
and shader exclusions), preserving `AssetManifest.listAssets()` and
`AssetMetadata.main`, validating custom `web/flutter_bootstrap.js` /
`web/index.html` templates, and un-hiding `--web-content-hash`
(`--no-web-content-hash`) in `flutter build web --help`.

### Why this is needed

Phase 2 (flutter#191919) and Phase 3
(flutter#192953) renamed physical asset
files on disk (`<name>.<hash>.<ext>`), rewrote target values inside
`AssetManifest.bin`, `AssetManifest.bin.json`, `AssetManifest.json`, and
`FontManifest.json`, and emitted `precache_manifest.json`. Two critical
gaps remained before exposing `--web-content-hash` to users:

1. **Cold-cache 404s on raw-key asset requests**:
- While `Image.asset` resolves variant paths via `AssetManifest` before
calling `rootBundle.load(variant.key)`, direct callers of
`rootBundle.load(key)` / `rootBundle.loadString(key)`
(`PlatformAssetBundle`) and third-party web plugins (`vector_graphics` /
`flutter_svg` `AssetBytesLoader`, `lottie`, `rive`, `video_player_web`,
`audioplayers_web`) pass the raw declared key
(`assets/data/config.json`) directly to
`ui_web.AssetManager.getAssetUrl(String asset)`.
- Without runtime key translation in
`ui_web.AssetManager.getAssetUrl(asset)`, raw-key asset fetches
requested un-hashed paths (`assets/assets/data/config.json`) and 404'd
on cold caches.
2. **Un-hashed manifest and shader files under `build/web/assets/`**:
- `AssetManifest.*`, `FontManifest.json`, `NOTICES`, `NOTICES.Z`, and
`shaders/**` were previously excluded from hashing, which meant a single
`"source": "assets/**" -> Cache-Control: public, max-age=31536000,
immutable` CDN rule would freeze `AssetManifest.bin.json` and
`FontManifest.json` across deployments (`v1 -> v2`).

### What this PR changes

1. **100% `build/web/assets/` Immutability & Clean
`AssetManifest.listAssets()`
(`packages/flutter_tools/lib/src/web/content_hash.dart`)**:
- Removes `_excludedFilenames` and `_excludedDirPrefixes` so every file
under `build/web/assets/` (including `shaders/**`, `NOTICES`,
`NOTICES.Z`, `FontManifest.json`, `AssetManifest.json`,
`AssetManifest.bin`, and `AssetManifest.bin.json`) is content-hashed on
disk.
- Keeps `AssetManifest.bin` and `AssetManifest.json` strictly limited to
user/package manifest entries (storing `Uri.decodeFull` variant paths so
`Image.asset` on spaced filenames encodes cleanly to `%2520` rather than
triple-encoding to `%252520`), and passes non-manifest SDK files
(`NOTICES`, `shaders/ink_sparkle.frag`, `shaders/stretch_effect.frag`,
`AssetManifest.bin`, `AssetManifest.json`) via
`_flutter.buildConfig.extraAssets` (~200 bytes) alongside
`"assetManifest"` and `"fontManifest"`.
- Sorts discovered asset paths deterministically before manifest
assembly so incremental and clean builds emit identical manifest hashes.
- Fails fast (`throwToolExit`) if custom `web/flutter_bootstrap.js` or
`web/index.html` templates omit `{{flutter_build_config}}` or contain
malformed `_flutter.buildConfig` assignments.
2. **Preserve `AssetMetadata.main`
(`packages/flutter/lib/src/services/asset_manifest.dart`)**:
- Updates `_AssetManifestBin` (`asset_manifest.dart`) so
`AssetMetadata.main` remains `true` for the primary 1.0x asset variant
when content hashes are embedded in variant filenames.
3. **Incremental Build `.filecache` Tracking
(`packages/flutter_tools/lib/src/build_system/targets/web.dart`)**:
- Declares `{OUTPUT_DIR}/*/index.html` and
`{OUTPUT_DIR}/flutter_bootstrap.js` in `WebReleaseBundle.inputs` and
`WebReleaseBundle.outputs` when `webContentHash` is enabled so
`.filecache` records post-injection file hashes.
4. **Synchronous Engine-Private Runtime Key Translation (`web_ui`)**:
- Extends `FlutterJS` with `JSBuildConfig` (`assetManifest`,
`fontManifest`, `extraAssets`) in `js_loader.dart`.
- Updates `fetchFontManifest` in `fonts.dart` to fetch
`flutter?.buildConfig?.fontManifest?.toDart ?? 'FontManifest.json'`.
- Adds `_loadContentHashedAssetManifest(ui_web.assetManager)` inside
`initializeEngineServices()` `Future.wait` in `initialization.dart` to
register `assetManifest`, `fontManifest`, `extraAssets`, and decoded
`AssetManifest.bin.<hash>.json` mappings in engine-private
`setContentHashedAssetMap` / `resolveContentHashedAsset` (keeping the
public `dart:ui_web` `AssetManager` API unchanged).
- Resolves logical keys and variant paths synchronously inside
`ui_web.AssetManager.getAssetUrl(String asset)`.
5. **Un-hide `--web-content-hash`
(`packages/flutter_tools/lib/src/web/web_options.dart` &
`build_web.dart`)**:
- Removes `hide: true` from `WebOptions.webContentHash` and notes that
local `canvaskit/**` (`--no-web-resources-cdn`) remains un-hashed
(`urlHashed: false` in `precache_manifest.json`).

## Related Issues

- Fixes flutter#191915
- Fixes flutter#193031
- Part of flutter#149031

## Tests

-
`packages/flutter_tools/test/general.shard/build_system/targets/web_test.dart`
-
`packages/flutter_tools/test/commands.shard/hermetic/build_web_test.dart`
& `args_test.dart`
- `packages/flutter/test/services/asset_manifest_test.dart`
- `engine/src/flutter/lib/web_ui/test/engine/assets_test.dart`

## Checklist

- [x] I read the [Contributor Guide] and followed the process outlined
there for submitting PRs.
- [x] I signed the [CLA].
- [x] I listed at least one issue that this PR fixes in the description
above.
- [x] I updated/added relevant documentation (doc comments with `///`).
- [x] I added new tests to check the change I am making, or this PR is
[test-exempt].
- [x] All existing and new tests are passing.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CICD Run CI/CD tool Affects the "flutter" command-line tool. See also t: labels.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[web] Emit build precache manifest for custom service workers and PWAs (--web-content-hash)

2 participants