Skip to content

fix(client): static import map fixes Firefox 150 bundle load failures#175

Merged
jackmusick merged 2 commits into
jackmusick:mainfrom
Cory-Covi:firefox-importmap-fix
May 4, 2026
Merged

fix(client): static import map fixes Firefox 150 bundle load failures#175
jackmusick merged 2 commits into
jackmusick:mainfrom
Cory-Covi:firefox-importmap-fix

Conversation

@Cory-Covi
Copy link
Copy Markdown
Contributor

Production Firefox 150 fails to load every Bifrost app with "bare specifier was not remapped" because Firefox 150 ships multiple-import-maps support gated behind dom.multiple_import_maps.enabled (default off), enforcing the legacy single-map rule strictly. Any runtime <script type="importmap"> registered after the module loader has started is rejected. main.tsx's top-level imports run before initReactShim(), so the runtime map (post-#165 single map; pre-#165 two maps) is always too late in Firefox 150. Chrome 133+ ships the same multi-map feature on, which is why it merged the late map silently.

Move the platform import map from runtime JS injection to a static <script type="importmap"> in client/index.html. The map URLs point to small ESM stub files generated by a Vite plugin
(client/src/build-plugins/bifrost-module-stubs.ts) that re-export from globalThis._bifrost* keys populated by initReactShim() at boot.

User-declared deps continue to be resolved at runtime, but only when an app actually has them. In that case we lazy-load es-module-shims in shim mode (where late importmap-shim registration is allowed). Apps with no user deps load with zero polyfill cost.

A drift test (client/src/lib/platform-modules.test.ts) pins the contract between the registry, the shim, the static HTML map, and the stub-generator so future additions can't drift silently.

App bundler is unchanged. Existing S3 bundles still load. No SCHEMA_VERSION bump, no cache invalidation, no risk to deployed apps.

Production Firefox 150 fails to load every Bifrost app with "bare
specifier was not remapped" because Firefox 150 ships
multiple-import-maps support gated behind dom.multiple_import_maps.enabled
(default off), enforcing the legacy single-map rule strictly. Any
runtime <script type="importmap"> registered after the module loader
has started is rejected. main.tsx's top-level imports run before
initReactShim(), so the runtime map (post-jackmusick#165 single map; pre-jackmusick#165
two maps) is always too late in Firefox 150. Chrome 133+ ships the
same multi-map feature on, which is why it merged the late map
silently.

Move the platform import map from runtime JS injection to a static
<script type="importmap"> in client/index.html. The map URLs point to
small ESM stub files generated by a Vite plugin
(client/src/build-plugins/bifrost-module-stubs.ts) that re-export from
globalThis.__bifrost_* keys populated by initReactShim() at boot.

User-declared deps continue to be resolved at runtime, but only when an
app actually has them. In that case we lazy-load es-module-shims in
shim mode (where late importmap-shim registration is allowed). Apps
with no user deps load with zero polyfill cost.

A drift test (client/src/lib/platform-modules.test.ts) pins the
contract between the registry, the shim, the static HTML map, and the
stub-generator so future additions can't drift silently.

App bundler is unchanged. Existing S3 bundles still load. No
SCHEMA_VERSION bump, no cache invalidation, no risk to deployed apps.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@Cory-Covi Cory-Covi requested a review from jackmusick as a code owner May 1, 2026 19:22
Resolved conflict in BundledAppShell.tsx by keeping main's
`setDefaultAppScope` import (added by jackmusick#184) and dropping the
no-longer-used `platformScope` import (this branch removed
the `setPlatformScope()` call entirely in favor of populating
globalThis.__bifrost_* via initReactShim() at startup).

No semantic changes — just import-list reconciliation.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown
Owner

@jackmusick jackmusick left a comment

Choose a reason for hiding this comment

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

APPROVE — static import map for Firefox 150.

What I checked

  • Drift test (platform-modules.test.ts) — 15/15 pass. The test pins all four corners of the contract: registry validity, stubUrlFor filename stability, static importmap completeness + ordering before any module script, and the shim assigning every globalKey. This is the right safety net for a fix whose failure mode is silent (specifier resolution falling back).
  • Production build — green; 7 stub assets emitted under dist/__bifrost_modules/ (one per platform module). Stub bodies look correct: globalThis.__bifrost_reactexport default m.default ?? m; export const X = m["X"]; with a clear error if the global is unpopulated.
  • Full vitest — 943/943 pass on the merged tip.
  • Conflict resolution on merge with mainBundledAppShell.tsx had an import-list conflict with #184's setDefaultAppScope addition. Resolved by keeping #184's import and dropping the no-longer-used platformScope import (this PR removed setPlatformScope() entirely in favor of populating globalThis.__bifrost_* at boot via initReactShim()). Pushed as commit 313e03e on your branch.
  • Architectural fit — moving from runtime blob-URL maps to static-HTML map is the right answer here. Apps with no user deps now pay zero polyfill cost. User-dep apps lazy-load es-module-shims and use shim mode for late importmap registration; the user-dep map duplicates the platform keys so shim-mode modules can resolve them too. Same import-map behavior cross-browser (Firefox 150 strict mode and Chrome multi-map both work).

Notes

  • The REACT_EXTERNALS constant continues to keep esm.sh from bundling its own React, preserving the single-instance invariant.
  • registeredUserDepMaps Set guards against re-registering the same shim-mode map for the same dep set.

Merging.

@jackmusick jackmusick enabled auto-merge (squash) May 4, 2026 20:00
@jackmusick jackmusick merged commit d97f5e8 into jackmusick:main May 4, 2026
13 checks passed
jackmusick added a commit that referenced this pull request May 4, 2026
…tag) (#190)

## Summary
- `https://ga.jspm.io/npm:es-module-shims@2/dist/es-module-shims.js`
returns **HTTP 404** — JSPM's CDN doesn't resolve floating-tag
specifiers, only exact versions.
- Every app with user-declared deps (the `hasUserDeps` branch added in
#175) failed to load with `Failed to load es-module-shims from ...`.
- Switched to jsDelivr — the CDN the es-module-shims docs themselves use
— pinned to `2.8.0` for reproducible loads.

## Test plan
- [x] `npm run tsc` clean
- [x] `eslint` clean on changed file
- [ ] In Firefox, open a Bifrost app with at least one user-declared
dependency and confirm the bundle loads without the prior error
- [ ] In Chrome, confirm apps with no user deps still take the
native-import path (no shim fetch)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
jackmusick added a commit that referenced this pull request May 6, 2026
Pulls in the post-#177 main commits since the first merge: external MCP
client (#177), pagination tiebreaker (#189), CLI --json flag position
(#189), Firefox bundle load fix (#175), browser table SDK polish (#184),
es-module-shims jsDelivr fix (#190), drop app.yaml (#191).

Conflicts resolved (all parallel-add — keep both sides):
- api/src/main.py — both branches added new routers; concatenated
- api/src/routers/__init__.py — same parallel router exports
- client/src/lib/v1.d.ts — regenerated from merged OpenAPI surface
- client/tsconfig.app.tsbuildinfo — build artifact, kept ours

Alembic merge migration updated: 20260504_merge_main_chat_v2 now points
to (20260503_agent_mcp_grants, 20260428_chat_v2_m2) since main's tip
moved past 20260504_backfill_table_access via 20260502_external_mcp.
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