Skip to content

fix: honour --no-web-resources-cdn instead of probing the CDN - #24

Merged
mike-doctorina merged 2 commits into
DoctorinaAI:masterfrom
NeoriseTech:fix/use-local-canvaskit
Oct 29, 2026
Merged

mike-doctorina merged 2 commits into
DoctorinaAI:masterfrom
NeoriseTech:fix/use-local-canvaskit

Conversation

@pdcruze

@pdcruze pdcruze commented Sep 27, 2026

Copy link
Copy Markdown
Contributor

What happened

An app built with flutter build web --no-web-resources-cdn still loaded its engine from Google's CDN. Every load, returning visits included, began with a cache: "no-store" Range: bytes=0-0 request to www.gstatic.com/flutter-canvaskit/<engineRevision>/<variant>.js before the engine download could start (175 ms in a production trace), and the engine then came from gstatic anyway. The canvaskit/ files the build deploys were never requested, so the service worker never cached them.

Root cause

Flutter records the flag as "useLocalCanvasKit": true in _flutter.buildConfig. extractFlutterBuildInfo only read engineRevision and builds, so the flag never reached bootstrap.js, and loadCanvasKit always probes the CDN when an engine revision is present. Because the probe is no-store, it is a network round trip on every load, and where gstatic.com is blocked or slow it can hold the loading screen for up to two 8 s attempts before falling back. The repo's own example is affected: CONTRIBUTING.md builds it with --no-web-resources-cdn.

Serving the engine locally exposed a second, latent problem. For the canvaskit renderer, cleanup's keep-set kept canvaskit/chromium/canvaskit.wasm but pruned canvaskit/chromium/canvaskit.js, the pair getCanvasKitVariant loads on Chromium-class browsers. While the engine came from the CDN the missing half went unnoticed; loaded locally, the script is gone, and on hosts with an SPA rewrite the request is answered with index.html and the app never starts.

The fix

  • Generator (Dart): FlutterBuildInfo.useLocalCanvasKit, parsed from buildConfig (false when absent). injectBootstrapConfig emits "useLocalCanvasKit": true only when set, so output for CDN builds is byte-identical. The generator's summary prints which source the bootstrap will use.
  • Bootstrap (TS): BuildConfig.useLocalCanvasKit?; loadCanvasKit returns canvaskit/ straight away when it is set, and the pipeline passes it in. CDN-first behaviour is unchanged for every other build.
  • Cleanup: the canvaskit renderer's keep-set lists both files of the Chromium variant.
  • Categorisation is unchanged: CanvasKit variants stay Optional, so in a local build the one variant the browser picks is cached on first load.
  • bootstrap_template.dart regenerated with npm run build:all; README, docs/architecture.md, docs/bootstrap-pipeline.md, CLAUDE.md and the categoriser comment describe the local path; CHANGELOG entry under Unreleased.

Verification

  • Vitest: 276 pass. The 3 new tests fail with the fix reverted.
  • Dart: format and analyzer clean, 108 tests pass including integration. A new generate() test over a synthetic CanvasKit build fails without the keep-set fix; the new integration test compares bootstrap.js with whatever the example's flutter_bootstrap.js recorded, so it holds for builds with and without the flag.
  • Playwright: 17/17 in Chromium, against the example built both with and without --no-web-resources-cdn. With the local build and the keep-set unfixed, "page loads without errors" and the wedged-queue boot test fail on the missing chromium/canvaskit.js.
  • npm run verify: committed Dart assets match the build output.
  • Chromium 152, example built with --no-web-resources-cdn: before, the one-byte probe and then canvaskit.js and canvaskit.wasm from gstatic; after, no gstatic request, both from canvaskit/.
  • A Firebase Hosting deployment with sw pinned to this branch: the bootstrap logs Using local: canvaskit/ (built with --no-web-resources-cdn), no request goes to gstatic, and the engine loads from the origin and then from the service worker's cache on later loads.

`flutter build web --no-web-resources-cdn` records its choice as
"useLocalCanvasKit": true in _flutter.buildConfig, but the generator
dropped it and the bootstrap always tried Google's CDN first. On every
load it sent a cache: "no-store" Range request for the variant's .js to
gstatic.com, a network round trip that returning visitors paid too and
that ran before the engine download could start, then loaded the engine
from the CDN whenever that answered. Where gstatic.com is blocked or
slow the probe could hold the loading screen for up to two 8 s
timeouts. The canvaskit/ files the app chose to serve were never used,
so the service worker never cached them either.

- extractFlutterBuildInfo reads useLocalCanvasKit (false when absent),
  and injectBootstrapConfig passes it to the bootstrap config only when
  it is true, so output for other builds is unchanged. The generator's
  summary reports which source the bootstrap will use.
- loadCanvasKit returns the local canvaskit/ path straight away when the
  flag is set, and the pipeline passes it in. Builds without the flag
  keep the CDN-first behaviour. The variant in use is cached lazily on
  first load, like any other Optional file.
- bootstrap_template.dart is regenerated with npm run build:all.
- README, docs/architecture.md, docs/bootstrap-pipeline.md, CLAUDE.md
  and the categoriser's comment describe the local path; CHANGELOG has
  an Unreleased entry.

Tests: Vitest covers loadCanvasKit making no request with the flag and
the pipeline passing true, and false when the config omits it; all
three fail without the fix. Dart covers parsing, the default, emitting
the flag only when true, and an integration check that bootstrap.js
carries whatever the example's flutter_bootstrap.js recorded, so it
holds for builds with and without the flag.

Checked in Chromium 152 against the same --no-web-resources-cdn example
build: 0.2.0 sent the one-byte gstatic.com probe and loaded canvaskit.js
and canvaskit.wasm from the CDN; with the fix there is no gstatic.com
request and both load from canvaskit/.
For the canvaskit renderer, cleanup's keep-set kept
canvaskit/chromium/canvaskit.wasm but pruned
canvaskit/chromium/canvaskit.js. getCanvasKitVariant loads that pair on
Chromium-class browsers. While the engine always came from the CDN the
missing half went unnoticed, but with useLocalCanvasKit the bootstrap
loads it from canvaskit/, where the script is gone; on hosts with an SPA
rewrite the request is answered with index.html and the app never
starts.

The keep-set now lists both files of the Chromium variant.

Tests: a generate() test over a synthetic canvaskit build checks that
both files of the standard and Chromium variants survive cleanup and a
file no variant uses is pruned; it fails without the fix. Upstream's
Playwright suite passes (17) against the example built with and without
--no-web-resources-cdn; with the local build and the keep-set unfixed,
"page loads without errors" and the wedged-queue boot test failed on the
missing script.
@mike-doctorina
mike-doctorina merged commit 0adc6e7 into DoctorinaAI:master Oct 29, 2026
4 of 6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants