Skip to content

feat(native-federation): honor esmsInitOptions.shimMode:false — native import map injection - #1127

Merged
Aukevanoost merged 1 commit into
angular-architects:21.x.xfrom
sparlampe:feat/native-import-map-injection
Sep 11, 2026
Merged

Aukevanoost merged 1 commit into
angular-architects:21.x.xfrom
sparlampe:feat/native-import-map-injection

Conversation

@sparlampe

Copy link
Copy Markdown

Motivation

Native Federation always drives the module graph through es-module-shims'
shim mode: the builder hardcodes the main bundle as
<script type="module-shim"> and the runtime always injects
<script type="importmap-shim">. In shim mode es-module-shims fetches every
module and re-executes it from blob: URLs — stack traces point at blobs,
DevTools source-map resolution suffers, and every module is fetched and
rewritten twice.

Import maps themselves are baseline in all evergreen browsers today. The one
thing Native Federation additionally needs — appending a further import map
after module loading has begun (when a remote is loaded at runtime) — is
supported natively in Chrome 133+ and Safari 18.4+ (Firefox gates multiple
import maps behind a pref at the time of writing). On those platforms a page
can run its whole federated module graph natively: real URLs in stack traces,
exact source maps, no double fetch, no blob rewriting.

esmsInitOptions already accepts shimMode: false and the builder already
serializes it into the esms-options tag — but the option was dead end to
end: the runtime still injected importmap-shim (which only es-module-shims
reads), and the hardcoded module-shim script type both prevented native
execution and made es-module-shims' auto-detection force shim mode back on.

Change

  1. native-federation-runtime: appendImportMap honors the page's shim
    mode.
    When the esms-options tag explicitly contains
    "shimMode": false, inject a NATIVE <script type="importmap"> instead
    of importmap-shim. The native map is an inline script the browser
    parses, so under a nonce-based script-src it must carry the page's CSP
    nonce — the runtime propagates the nonce from any nonced script on the
    page (with 'strict-dynamic' the nonce is redundant but harmless; the
    .nonce IDL property is read because browsers hide the content
    attribute).

  2. native-federation builder: the index transform follows. With
    esmsInitOptions.shimMode === false, the main bundle is emitted as
    <script type="module"> instead of the hardcoded module-shim, so the
    browser executes it natively. Injection order is safe by Native
    Federation's own bootstrap pattern: initFederation() appends the import
    map before import('./bootstrap') pulls in anything that uses shared
    packages.

Default unchanged in both places: without an esms-options tag, or with
shimMode true or unset, behavior is byte-for-byte as before
(module-shim + importmap-shim).

Usage

// angular.json → the nf builder target
"esmsInitOptions": {
  "shimMode": false
}

Nothing else changes; removing the option (or setting true) restores shim
mode. Users who need older-browser coverage simply keep the default.

@Aukevanoost

Aukevanoost commented Sep 9, 2026

Copy link
Copy Markdown
Collaborator

Hi thanks for this!

However I'd recommend against using this runtime in favor of the orchestrator. It's fragile and right now it's EOL. If you want to know more about this, have a look at our docs!
https://native-federation.com/docs/v3/orchestrator/

That orchestrator does allow you to opt-out of shimMode as well.

… main script tag

The builder serializes esmsInitOptions into the esms-options tag but
hardcodes the main bundle as <script type="module-shim">, so
shimMode: false is dead on arrival: the browser skips module-shim (an
unknown type), and es-module-shims force-enables shim mode when it sees
any -shim script. A host that wants to run its module graph natively -
for example through @softarc/native-federation-orchestrator with a
native import map - never can.

Emit <script type="module"> when esmsInitOptions.shimMode === false.
With shimMode true or unset the output is byte-for-byte unchanged.
@sparlampe
sparlampe force-pushed the feat/native-import-map-injection branch from f87e39d to af7166b Compare September 10, 2026 21:02
@sparlampe

Copy link
Copy Markdown
Author

Thanks a lot for the feedback! I tried the orchestrator as suggested — it
covers the runtime side nicely, but one thing can't be done without a builder
change: the builder hardcodes the main bundle as
<script type="module-shim">, and es-module-shims auto-enables shim mode as
soon as it sees any -shim tag, overriding "shimMode": false in the
esms-options tag. In shim mode it only reads importmap-shim, so the native
import map the orchestrator injects is never picked up — an orchestrator host
can never actually run native.

I have re-scoped this PR down to exactly that part: one commit, +5/−1 in
updateIndexHtml.ts — emit type="module" when
esmsInitOptions.shimMode === false; with shimMode true or unset the
output is byte-for-byte unchanged. The runtime commit is dropped.

Here is a reproducible demo on your examples workspace (nf 21.2.3 +
orchestrator 4.0.0, host fully native, shimMode driven from angular.json):
sparlampe/native-federation-examples-ng#1 — the
stock builder with shimMode: false configured still emits module-shim;
with this patch it emits module and the page runs natively.

Is there an easier approach that doesn't require touching
updateIndexHtml.ts?

@Aukevanoost

Copy link
Copy Markdown
Collaborator

Fair, and acceptable. I don't think there is another way.

@Aukevanoost
Aukevanoost merged commit 3664881 into angular-architects:21.x.x Sep 11, 2026
1 check 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