feat(node): opt-in native ESM HTTP loader for bundler-free runtime#4862
feat(node): opt-in native ESM HTTP loader for bundler-free runtime#4862ScriptedAlchemy wants to merge 7 commits into
Conversation
🦋 Changeset detectedLatest commit: f613100 The changes in this PR will be included in the next version bump. This PR includes changesets to release 47 packages
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
@module-federation/devtools
@module-federation/cli
create-module-federation
@module-federation/dts-plugin
@module-federation/enhanced
@module-federation/error-codes
@module-federation/esbuild
@module-federation/managers
@module-federation/manifest
@module-federation/metro
@module-federation/metro-plugin-rnc-cli
@module-federation/metro-plugin-rnef
@module-federation/metro-plugin-rock
@module-federation/modern-js
@module-federation/modern-js-v3
@module-federation/native-federation-tests
@module-federation/native-federation-typescript
@module-federation/nextjs-mf
@module-federation/node
@module-federation/observability-plugin
@module-federation/playground
@module-federation/retry-plugin
@module-federation/rsbuild-plugin
@module-federation/rspack
@module-federation/rspress-plugin
@module-federation/runtime
@module-federation/runtime-core
@module-federation/runtime-tools
@module-federation/sdk
@module-federation/storybook-addon
@module-federation/third-party-dts-extractor
@module-federation/treeshake-frontend
@module-federation/treeshake-server
@module-federation/typescript
@module-federation/utilities
@module-federation/webpack-bundler-runtime
@module-federation/bridge-react
@module-federation/bridge-react-webpack-plugin
@module-federation/bridge-shared
@module-federation/bridge-vue3
@module-federation/inject-external-runtime-core-plugin
commit: |
Add Node module-customization hooks (module.register) that import ESM remote entries and their chunks directly over HTTP(S) with a per-origin allowlist, wired into the MF runtime loadEntry hook so loadRemote works in a plain Node process. Registered via @module-federation/node/register or registerNativeHttpLoader(); the default vm-based path is unchanged.
Add the four research documents (Node platform hooks status, runtime-core entry-loading audit, ESM chunk-loading options, prior art) and update the native HTTP loader design doc accordingly: module.registerHooks() becomes the primary API in the roadmap (module.register() is DEP0205-deprecated, runtime-deprecated in Node 26) with out-of-band prefetch + disk cache, the no-hooks f.j/externalInstallChunk chunk fallback, upstream runtime-core PR candidates (ssrRemoteEntry preference, RUNTIME_008 normalization), and the manifest-SRI + lockfile integrity roadmap.
Add apps/node-esm-remote (webpack ESM output: chunkFormat 'module', chunkLoading 'import', module-type remote entry with a nested async chunk, plus a tiny static server) and apps/node-esm-host, a bundler-free-loading host that runs with `node --import @module-federation/node/register` and loads the remote entry and its chunks over http through Node's native ESM loader via the node runtime plugin's loadEntry bridge.
35f6a7d to
ce100b3
Compare
…urface Security fixes from consolidated code review of the native HTTP ESM loader: - gate registration itself on the MF_NODE_NATIVE_LOADER kill switch so module.register() is never called when disabled - share one in-flight ack per origin in allowOrigin() so concurrent callers cannot resolve before the hooks thread applied the allowlist update - reject HTTP redirects in the hooks-thread fetch so an allowlisted origin cannot redirect module source to an un-allowlisted target - bound the hooks fetch (30s abort) and allowlist acks (10s rejection) so a broken port fails loudly instead of hanging the process Cleanup: reuse RemoteEntryExports from @module-federation/runtime/types, drop the always-true state.enabled flag, fold state.ts into protocol.ts, type the hooks with @types/node module hook types, stop re-exporting test-only helpers, resolve the CJS register hooksUrl via defaultHooksUrl(), share the log prefix constant, warn when smoke tests skip on missing dist, and fix a stale comment in the node-esm-host example.
✅ Deploy Preview for module-federation-docs ready!
To edit notification comments on pull requests, go to your Netlify project configuration. |
Bundle Size Report11 package(s) changed, 31 unchanged. Package dist + ESM entry
Bundle targets
Consumer scenarios
Total dist (raw): 35.75 MB (+51.2 kB (+0.1%)) Bundle sizes are generated with rslib (Rspack). Package-root metrics preserve the historical report. Tracked subpath exports such as |
…-loader-main # Conflicts: # pnpm-lock.yaml
…-loader # Conflicts: # pnpm-lock.yaml
Description
Adds an opt-in native ESM HTTP loader for
@module-federation/node, enabling a bundler-free Module Federation runtime in Node. Withnode --import @module-federation/node/register, remote entries and their chunks are loaded through nativemodule.register()loader hooks (realimport()overhttp(s)) instead offetch+vm.runInThisContext.What changed:
packages/node/src/loader-hooks/module:module.register()-based HTTP loader hooks with an origin allowlist, a resolve/load protocol between the main thread and the hooks thread, and an entry loader for federation remote entries. Includes a fix to keep the allowlist ackMessagePortreferenced during the round-trip so the process cannot exit mid-handshake.@module-federation/node/registerentry (register.mjs/register.cjs+ type declarations) so consumers can enable the loader with a single--importflag.runtimePlugin.tsbridge: when the native loader is active,loadEntrydelegates remote-entry loading to the loader hooks path; default behavior is unchanged when the loader is not registered.apps/node-esm-host,apps/node-esm-remote) demonstrating a webpack-built ESM remote consumed by a bundler-free Node host over HTTP.packages/node/docs/), and a changeset (@module-federation/nodeminor).Why: today the Node runtime executes remote code via
fetch+vm.runInThisContext, which bypasses the native ESM module graph (no realimport.meta, no native async chunk resolution, awkward interop). Native loader hooks let Node itself resolve and load federated ESM modules over HTTP.Impact: fully opt-in. Nothing changes unless the process is started with the
/registerentry (or hooks are registered explicitly). Changeset included:@module-federation/nodeminor.Validation:
@module-federation/nodepackage build green; 71 jest tests passing (loader hooks, register entry, smoke tests).http://vianode --import @module-federation/node/register, verified on Node 18.19 and Node 22.Roadmap (phase 2): migrate from
module.register()(hooks thread) to the synchronous in-threadmodule.registerHooks()API as it stabilizes, which removes the worker round-trip.Related Issue
No linked issue; this implements the native ESM HTTP loader design discussed in
packages/node/docs/native-http-loader-design.md.Types of changes
Checklist