Skip to content
Open
Show file tree
Hide file tree
Changes from 3 commits
Commits
Show all changes
165 commits
Select commit Hold shift + click to select a range
c190e28
feat: support async startup promise across formats
ScriptedAlchemy Oct 16, 2025
40c571a
Merge remote-tracking branch 'origin/main' into feature/async-startup…
ScriptedAlchemy Oct 16, 2025
241a46e
fix(runtime): avoid duplicate export for mf async startup
ScriptedAlchemy Oct 16, 2025
b22849c
fix(runtime): gate async startup and reuse default export
ScriptedAlchemy Oct 16, 2025
2ef62db
chore(binding): sync wasi generated exports
ScriptedAlchemy Oct 16, 2025
8b99fdd
chore: drop webpack hot-case bundles
ScriptedAlchemy Oct 17, 2025
36cd349
fix(runtime): adjust async startup promise flow
ScriptedAlchemy Oct 17, 2025
3a3b9c1
fix(runtime): prevent async startup wrapper for simple entries withou…
ScriptedAlchemy Oct 17, 2025
8ecf6e4
test: fix container tests and sync WASI bindings
ScriptedAlchemy Oct 18, 2025
be3302e
fix(runtime): preserve async chunk loading for non-MF projects
ScriptedAlchemy Oct 19, 2025
38b4c77
fix(runtime): await async chunk loaders
ScriptedAlchemy Oct 19, 2025
a8ce32c
chore: merge origin main
ScriptedAlchemy Oct 19, 2025
463e19b
fix(runtime): ensure async loaders await dependencies
ScriptedAlchemy Oct 19, 2025
d1c8a51
refactor(runtime): tidy async startup hash update
ScriptedAlchemy Oct 19, 2025
4cc4f4e
fix(runtime): gate mf async startup behind experiment
ScriptedAlchemy Oct 20, 2025
30b5d94
fix: honor explicit mfAsyncStartup for containers
ScriptedAlchemy Oct 20, 2025
a70265d
fix(mf): include runtime handlers in enhanced mode
ScriptedAlchemy Oct 20, 2025
23c719c
fix(mf): remove async startup wrapping from containers
ScriptedAlchemy Oct 20, 2025
d67e097
refactor(mf): improve HoistContainerReferencesPlugin runtime chunk ma…
ScriptedAlchemy Oct 20, 2025
5b23c61
fix(mf): restore runtime chunk detection in HoistContainerReferencesP…
ScriptedAlchemy Oct 21, 2025
0a9103f
fix(mf): keep bundler runtime helper in runtime chunk
ScriptedAlchemy Oct 21, 2025
7946cb0
test: update mf fixtures and watch expectations
ScriptedAlchemy Oct 21, 2025
d9882dd
Merge branch 'main' into feature/async-startup-runtime-promise
ScriptedAlchemy Oct 21, 2025
6d29968
fix: re-add mfAsyncStartup experiment after main merge
ScriptedAlchemy Oct 21, 2025
8990fac
fix: ignore ustr-fxhash transitive dependency in cargo-shear
ScriptedAlchemy Oct 21, 2025
1a9e85e
fix(mf): gate async wrapper by chunk_needs_mf_async_startup
ScriptedAlchemy Oct 21, 2025
d57e766
fix: update API extractor output to remove mfAsyncStartup references
ScriptedAlchemy Oct 21, 2025
6093ae3
test: update test snapshots and expectations after mf async startup fix
ScriptedAlchemy Oct 21, 2025
708b67c
fix: suppress dead code warnings for plugin hook structs and methods
ScriptedAlchemy Oct 21, 2025
dab2dc1
test: update Rust defaults snapshot for mf_async_startup field
ScriptedAlchemy Oct 21, 2025
f5a1dbf
fix: use module-level allow(dead_code) for plugin hook system
ScriptedAlchemy Oct 21, 2025
94711b2
fix(mf): re-enable hoist container hoisting
ScriptedAlchemy Oct 21, 2025
1e50a14
fix(binding): add mf async startup bindings
ScriptedAlchemy Oct 22, 2025
7796253
Merge remote-tracking branch 'origin/main' into feature/async-startup…
ScriptedAlchemy Oct 22, 2025
7fcf21e
perf(mf): skip hoist when federation deps missing
ScriptedAlchemy Oct 22, 2025
aee5d59
Remove library test directory (doesn't exist on main)
ScriptedAlchemy Oct 22, 2025
1582cbe
Remove split-chunks snapshot to match main branch
ScriptedAlchemy Oct 22, 2025
36da492
fix(tests): restore test files from main to fix defaults tests
ScriptedAlchemy Oct 22, 2025
3b6d207
fix(tests): update base.js snapshot to include mfAsyncStartup
ScriptedAlchemy Oct 22, 2025
fb2d020
refactor(mf): improve HoistContainerReferencesPlugin code quality
ScriptedAlchemy Oct 22, 2025
df7151b
revert: unrelated CSS test package.json formatting changes
ScriptedAlchemy Oct 22, 2025
fd00613
fix(mf): remove unused ustr dependency from Cargo.toml
ScriptedAlchemy Oct 22, 2025
0623ed8
chore: update Cargo.lock after removing ustr dependency
ScriptedAlchemy Oct 22, 2025
8994f30
fix: gate all MF async startup logic on experiment flag
ScriptedAlchemy Oct 22, 2025
28eba04
fix: properly gate MF async startup to preserve main branch behavior
ScriptedAlchemy Oct 22, 2025
29ac7d4
fix: update hot snapshot for MF share-plugin test
ScriptedAlchemy Oct 22, 2025
b3b3c5d
fix: suppress false positive 'this' unused warning in BuildInfo
ScriptedAlchemy Oct 22, 2025
8c3fcc4
chore: increase lint warning threshold to accommodate CI environment
ScriptedAlchemy Oct 22, 2025
4d77e07
Revert "chore: increase lint warning threshold to accommodate CI envi…
ScriptedAlchemy Oct 22, 2025
00cbb22
Merge remote-tracking branch 'origin/main' into feature/async-startup…
ScriptedAlchemy Oct 22, 2025
52fb979
fix(rspack): make build info inspect serializable
ScriptedAlchemy Oct 22, 2025
31da1bb
fix(incremental): avoid repeat full-hash warnings
ScriptedAlchemy Oct 22, 2025
3ba4253
test(container): align async startup expectations
ScriptedAlchemy Oct 22, 2025
fbd55b0
test(container): handle remote share versions
ScriptedAlchemy Oct 22, 2025
b5c3d9c
test(container): normalize async startup expectations
ScriptedAlchemy Oct 23, 2025
beddcde
fix(container): gate EntryFederationRuntimeModule by async startup flag
ScriptedAlchemy Oct 23, 2025
e030641
fix(mf): skip federation startup call in async mode
ScriptedAlchemy Oct 23, 2025
b7118da
Merge remote-tracking branch 'origin/main' into feature/async-startup…
ScriptedAlchemy Oct 23, 2025
420b7f6
fix(mf): guard embed runtime plugin behind async startup
ScriptedAlchemy Oct 23, 2025
3ebd6f3
fix(mf): ensure backwards compatibility for async startup feature
ScriptedAlchemy Oct 23, 2025
b395434
fix(tests): silence hot css logging and align externals
ScriptedAlchemy Oct 23, 2025
a34b4aa
fix(mf): gate all async startup changes behind experiment flag
ScriptedAlchemy Oct 24, 2025
1315c0a
fix(mf): restrict async startup to MF-participating chunks only (P0)
ScriptedAlchemy Oct 24, 2025
c3e1054
fix(tests): align library test configs with main branch
ScriptedAlchemy Oct 24, 2025
ed1c31a
Merge remote-tracking branch 'origin/main' into feature/async-startup…
ScriptedAlchemy Oct 24, 2025
3112141
Merge branch 'main' into feature/async-startup-runtime-promise
ScriptedAlchemy Oct 24, 2025
deca562
fix(ir): minimal async startup gating
ScriptedAlchemy Oct 25, 2025
5b238ab
fix(runtime): restore sync startup when mf async disabled
ScriptedAlchemy Oct 27, 2025
df01379
test(stats): restore snapshot baselines
ScriptedAlchemy Oct 27, 2025
7c7e021
fix(runtime): restore startup plugin wiring for async
ScriptedAlchemy Oct 27, 2025
e8a04f9
fix(resolver): avoid sharing alias caches across compilers
ScriptedAlchemy Oct 27, 2025
57c5905
Revert unrelated changes
ScriptedAlchemy Oct 27, 2025
9094974
Merge remote-tracking branch 'origin/main' into feature/async-startup…
ScriptedAlchemy Oct 27, 2025
ba884df
Merge branch 'main' into feature/async-startup-runtime-promise
ScriptedAlchemy Oct 27, 2025
bcde5e7
chore: revert HotSnapshot hottest test
ScriptedAlchemy Oct 27, 2025
ef5fe7f
chore: revert BuildInfo overrides
ScriptedAlchemy Oct 27, 2025
780c4ed
chore: revert module federation default runtime
ScriptedAlchemy Oct 27, 2025
c0e6cfa
refactor: parse startup exports with swc
ScriptedAlchemy Oct 27, 2025
7aded25
revert: drop async startup changes
ScriptedAlchemy Oct 27, 2025
d767978
chore: remove mf entry runtime shim
ScriptedAlchemy Oct 27, 2025
b452da3
chore: sync runtime cargo manifest
ScriptedAlchemy Oct 27, 2025
f8ec4f3
chore: snapshot current work
ScriptedAlchemy Oct 28, 2025
0f2d5f4
chore: snapshot current work
ScriptedAlchemy Oct 28, 2025
c7f5d5c
test: update default options snapshot
ScriptedAlchemy Oct 28, 2025
ab27f3a
chore: remove investigation markdown files
ScriptedAlchemy Oct 28, 2025
762e0ef
Merge branch 'main' into feature/async-startup-runtime-promise
ScriptedAlchemy Oct 28, 2025
a0926e8
chore: remove comprehensive research and analysis files related to ge…
ScriptedAlchemy Oct 28, 2025
689dab5
refactor(tests): simplify container test cases and update rspack conf…
ScriptedAlchemy Oct 28, 2025
5686be5
Merge remote-tracking branch 'origin/main' into feature/async-startup…
ScriptedAlchemy Oct 28, 2025
8016dd8
fix: correct container entry chunk handling for non-async startup
ScriptedAlchemy Oct 28, 2025
8ff2703
fix: resolve test failures for async startup and ESM output
ScriptedAlchemy Oct 28, 2025
023c30b
wip: add Promise-based wrapper for async federation startup (CJS work…
ScriptedAlchemy Oct 28, 2025
8a5a4b2
feat: implement async federation startup for entry chunks (1635/1636 …
ScriptedAlchemy Oct 28, 2025
b1f5b57
feat: implement ESM async startup with Promise.resolve().then() pattern
ScriptedAlchemy Oct 28, 2025
fb3b268
refine container entry detection for async startup
ScriptedAlchemy Oct 29, 2025
d30b7d9
Merge remote-tracking branch 'origin/main' into feature/async-startup…
ScriptedAlchemy Oct 29, 2025
44e0920
fix: align mf async startup defaults
ScriptedAlchemy Oct 29, 2025
9e622ce
Merge branch 'main' into feature/async-startup-runtime-promise
ScriptedAlchemy Oct 29, 2025
776c5a5
fix: avoid string conversion on source value
ScriptedAlchemy Oct 29, 2025
0ab50b4
test: update esm output snapshots
ScriptedAlchemy Oct 29, 2025
f3b0a0f
feat: support mf async startup in esm runtime
ScriptedAlchemy Oct 29, 2025
a6b8659
Revert "test: update esm output snapshots"
ScriptedAlchemy Oct 30, 2025
bb1f22a
fix: gate mf async startup runtime by experiment
ScriptedAlchemy Oct 30, 2025
1190bd9
chore: restore esm snapshot baselines
ScriptedAlchemy Oct 30, 2025
8aadb28
fix: gate mf async bootstrap and preserve imports
ScriptedAlchemy Oct 30, 2025
aacf919
fix: export EmbedFederationRuntimeModule to fix cacheable deserializa…
ScriptedAlchemy Oct 31, 2025
0be6e50
fix: restore accidentally removed exports from WASI binding files
ScriptedAlchemy Oct 31, 2025
9914305
Merge remote-tracking branch 'origin/main' into feature/async-startup…
ScriptedAlchemy Nov 1, 2025
2c08ac8
test: update stats exports snapshot
ScriptedAlchemy Nov 1, 2025
553915a
test: update stats exports snapshot for macOS build
ScriptedAlchemy Nov 2, 2025
ba6e99b
Merge remote-tracking branch 'origin/main' into feature/async-startup…
ScriptedAlchemy Nov 2, 2025
45b3afc
Merge remote-tracking branch 'origin/main' into feature/async-startup…
ScriptedAlchemy Nov 5, 2025
17ec896
Merge branch 'main' into feature/async-startup-runtime-promise
ScriptedAlchemy Nov 5, 2025
5037973
chore: format examples/basic config
ScriptedAlchemy Nov 5, 2025
5641d9c
Merge remote-tracking branch 'origin/main' into feature/async-startup…
ScriptedAlchemy Nov 10, 2025
66991e1
chore: format code to satisfy Prettier CI checks
ScriptedAlchemy Nov 10, 2025
8d53ac1
test(wasm): relax chunk id expectation in mini-css-extract-plugin cas…
ScriptedAlchemy Nov 10, 2025
9a1a65c
Merge remote-tracking branch 'origin/main' into feature/async-startup…
ScriptedAlchemy Nov 10, 2025
4e6be08
Merge remote-tracking branch 'origin/main' into feature/async-startup…
ScriptedAlchemy Nov 10, 2025
da1cc5a
Merge branch 'main' into feature/async-startup-runtime-promise
ScriptedAlchemy Nov 12, 2025
83ce4d2
fix: restore mf_async_startup parameter in enable_chunk_loading_plugin
ScriptedAlchemy Nov 12, 2025
c9d8a4f
fix: update runtime module export name
ScriptedAlchemy Nov 12, 2025
a7bfd9a
Merge branch 'main' into feature/async-startup-runtime-promise
ScriptedAlchemy Nov 12, 2025
4e82ca7
Merge branch 'main' into feature/async-startup-runtime-promise
ScriptedAlchemy Nov 12, 2025
57c1479
feat: prevent duplicate async startup wrappers in Module Federation (…
ScriptedAlchemy Nov 13, 2025
c695be6
Merge branch 'main' into feature/async-startup-runtime-promise
ScriptedAlchemy Nov 13, 2025
8a39e92
Merge remote-tracking branch 'origin/main' into feature/async-startup…
ScriptedAlchemy Nov 14, 2025
75b217d
fix: restore startup chunk dependencies
ScriptedAlchemy Nov 15, 2025
a000c61
Merge remote-tracking branch 'origin/main' into feature/async-startup…
ScriptedAlchemy Nov 15, 2025
2523e43
chore: update pnpm lock after merge
ScriptedAlchemy Nov 15, 2025
2b131a9
ci: use node 22 for pnpm setup
ScriptedAlchemy Nov 15, 2025
c42dbe3
chore: exclude comprehensive demo from workspace
ScriptedAlchemy Nov 15, 2025
68be7f3
chore: regenerate pnpm lock
ScriptedAlchemy Nov 15, 2025
c1a170d
chore: remove comprehensive demo packages from workspace
ScriptedAlchemy Nov 15, 2025
1b7c2b8
chore: align markdown-to-jsx version
ScriptedAlchemy Nov 15, 2025
b51162b
Merge remote-tracking branch 'origin/main' into feature/async-startup…
ScriptedAlchemy Nov 16, 2025
75265bb
Merge remote-tracking branch 'origin/main' into feature/async-startup…
ScriptedAlchemy Nov 17, 2025
48b814c
feat: add mf async startup runtime option
ScriptedAlchemy Nov 17, 2025
d6e18f8
Merge branch 'origin/main' into feature/async-startup-runtime-promise
ScriptedAlchemy Nov 19, 2025
0a16f5f
chore: fix workspace dependency targets
ScriptedAlchemy Nov 19, 2025
5b12bd2
fix(runtime): guard startup entrypoint fallback
ScriptedAlchemy Nov 19, 2025
e3529e6
Merge remote-tracking branch 'origin/main' into feature/async-startup…
ScriptedAlchemy Nov 20, 2025
ee39816
fix: align async mf startup globals
ScriptedAlchemy Nov 20, 2025
b40174a
fix: run on_chunks_loaded during async startup
ScriptedAlchemy Nov 21, 2025
bd2a921
chore: align demo deps for lint check
ScriptedAlchemy Nov 21, 2025
0e6e787
chore: fmt after async startup change
ScriptedAlchemy Nov 21, 2025
12b044e
fix: run all entry modules in async MF startup
ScriptedAlchemy Nov 21, 2025
ea55acc
fix: use async startup entrypoint for delegated mf chunks
ScriptedAlchemy Nov 21, 2025
09cd387
fix: restore startup for mf entry chunks
ScriptedAlchemy Nov 21, 2025
e3388bf
chore: revert esm library change
ScriptedAlchemy Nov 21, 2025
c7dde4d
test: cover asyncStartup commonjs share
ScriptedAlchemy Nov 21, 2025
993eedc
fix: await share init in async MF bootstrap
ScriptedAlchemy Nov 21, 2025
8d6cbf7
Revert "fix: await share init in async MF bootstrap"
ScriptedAlchemy Nov 21, 2025
8cb5b35
Revert "test: cover asyncStartup commonjs share"
ScriptedAlchemy Nov 21, 2025
51f2c54
fix(mf): skip sync initial consumes under async startup
ScriptedAlchemy Nov 21, 2025
c573019
fix: restore WASI named exports
ScriptedAlchemy Nov 22, 2025
cbe8c5f
Merge remote-tracking branch 'origin/main' into feature/async-startup…
ScriptedAlchemy Nov 22, 2025
8fb9e27
fix(binding): regenerate WASI named exports
ScriptedAlchemy Nov 22, 2025
40c3572
fix(mf): keep consumes runtime state in async startup
ScriptedAlchemy Nov 23, 2025
44483cb
fix(mf): enable async startup runtime for v1 plugin
ScriptedAlchemy Nov 23, 2025
4b7f8b8
Revert "fix(mf): enable async startup runtime for v1 plugin"
ScriptedAlchemy Nov 23, 2025
e818d48
chore: pin module-federation runtime to 0.21.6
ScriptedAlchemy Nov 23, 2025
90e8950
Revert "chore: pin module-federation runtime to 0.21.6"
ScriptedAlchemy Nov 23, 2025
4d7a5ff
chore: lock mf bundler runtime to 0.21.6
ScriptedAlchemy Nov 23, 2025
c24eeae
fix(mf): declare exports in async ESM bootstrap
ScriptedAlchemy Nov 23, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
116 changes: 116 additions & 0 deletions PROPOSAL-async-federation-startup.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
# Proposal: Native Async Startup Support for Module Federation in Rspack

## 1. Background and Motivation
- Module Federation runtimes increasingly assume an *async startup contract*: the host must delay executing entry modules until remote containers, shared scopes, and any startup hooks finish resolving.
- The `@module-federation/enhanced` project already exposes an `experiments.asyncStartup` toggle. When enabled it:
- Forces webpack’s `StartupChunkDependenciesPlugin` into async mode even for sync loaders like `require`.
- Replaces the generated startup bootstrap so it awaits `Promise.all` on `__webpack_require__.f.remotes` / `.__consumes` handlers before running entry modules.
- Tracks async work via `promiseTrack` and integrates with `RuntimeGlobals.startupEntrypoint`.
- Rspack’s Module Federation implementation currently mirrors the synchronous webpack bootstrap:
- `StartupChunkDependenciesPlugin` only emits async code when the **chunk loading type** itself is async (`async-node`, `import()`, `importScripts`). The default `require` loader stays synchronous.
- `EmbedFederationRuntimePlugin` wraps `RuntimeGlobals::STARTUP` to execute federation runtime dependencies, but it forwards whatever `prevStartup()` returns without forcing a promise.
- `RuntimeGlobals::ENSURE_CHUNK` already aggregates promises from `__webpack_require__.f.*` handlers (e.g. `remotes`, `consumes`), yet the surrounding startup path may ignore those promises.
- Result: when a Node/SSR app enables Module Federation, remote containers can begin loading asynchronously but the entry chunk continues executing immediately, violating the federation runtime contract.

## 2. Goals
- Provide first-class async startup behavior whenever federation requests it, regardless of chunk loading strategy.
- Align Rspack’s runtime semantics with the `@module-federation/enhanced` experiment so the same application code works on both bundlers.
- Preserve backwards compatibility: synchronous startup remains the default unless federation explicitly opts in.
- Keep the change localized (no breaking change to unrelated runtime paths) and covered by integration tests.

## 3. Current Runtime Analysis
### 3.1 Enhanced async startup experiment
- Hooks `ModuleFederationPlugin` to install a custom `StartupChunkDependenciesPlugin` wrapper that always sets `{ asyncChunkLoading: true }`.
- Generates a startup fragment that:
- Pushes async operations into `promiseTrack`.
- Calls `__webpack_require__.x()` (startup extensions) and both `__webpack_require__.f.remotes` and `.consumes`.
- Wraps entry execution in `Promise.all(promiseTrack).then(...)` or `await` (TLA).

### 3.2 Rspack runtime today
- `StartupChunkDependenciesPlugin` ([`crates/rspack_plugin_runtime/src/startup_chunk_dependencies.rs`](crates/rspack_plugin_runtime/src/startup_chunk_dependencies.rs)) toggles async behavior via its `async_chunk_loading` flag; `ChunkLoadingType::Require` passes `false`.
- The factory that wires runtime plugins (`enable_chunk_loading_plugin` in [`crates/rspack_plugin_runtime/src/lib.rs`](crates/rspack_plugin_runtime/src/lib.rs)) always instantiates `StartupChunkDependenciesPlugin::new(..., false)` for `ChunkLoadingType::Require`, so Node/CommonJS builds never emit the async templates today.
- `StartupChunkDependenciesRuntimeModule` and `StartupEntrypointRuntimeModule` already contain *both* async and sync templates, chosen by that flag.
- `EmbedFederationRuntimePlugin` ([`crates/rspack_plugin_mf/src/container/embed_federation_runtime_plugin.rs`](crates/rspack_plugin_mf/src/container/embed_federation_runtime_plugin.rs)) injects the federation runtime and wraps `__webpack_require__.x`, but does not ensure async resolution of remote handlers.
- Its `render_startup` hook inserts a plain `__webpack_require__.x();` call for entry chunks that proxy to runtime chunks, so any promise returned from `STARTUP` is dropped on the floor.
- JS bootstrap (`crates/rspack_plugin_javascript/src/plugin/mod.rs`) assigns `var __webpack_exports__ = __webpack_require__.x();` and proceeds synchronously; consumers of `__webpack_exports__` may still work if the variable is a promise, but cross-runtime helpers (e.g. CommonJS runner) often expect the exports object immediately.
- No config plumbing exists for `experiments.asyncStartup`, so the binding cannot pass the requirement through.

## 4. Proposed Native Implementation
### 4.1 Async startup gate and detection
### 4.1 Async startup flag lifecycle
1. **JavaScript API** – primary path is to extend `ModuleFederationPluginOptions` with `experiments?: { asyncStartup?: boolean; ... }` and forward the boolean into `ModuleFederationRuntimePlugin` during `apply`.
- If pushing a new property onto the federation options is not feasible, fall back to a `mfAsyncStartup?: boolean` entry on the top-level `experiments` object. In that mode, `ModuleFederationPlugin` reads `compiler.options.experiments.mfAsyncStartup === true` and sets the runtime flag automatically so consumers have a universal switch.
2. **Bindings** – add `async_startup: Option<bool>` to `RawModuleFederationRuntimePluginOptions` and to `ModuleFederationRuntimePluginOptions` so Rust receives the signal. Regenerate the napi typings.
3. **Registry** – inside `ModuleFederationRuntimePlugin`, record the flag against the current `CompilationId` (e.g. using a `LazyLock<FxDashMap<CompilationId, bool>>`). When using the global `mfAsyncStartup` fallback, treat the federation flag as `federationFlag || globalFlag`. Register a small hook on `compilation.finish` (or similar) to remove the entry once we’re done.

### 4.2 Chunk-level detection and caching
1. Augment `StartupChunkDependenciesPlugin` with a `requires_async_startup(compilation, chunk_ukey)` helper that:
- Returns `true` immediately when the chunk loading strategy is already async (`Import`, `Jsonp`, `ImportScripts`, `AsyncNode`).
- Otherwise checks the registry; if the project requested async startup, continue to federation heuristics:
* runtime requirements include `INITIALIZE_SHARING`, `SHARE_SCOPE_MAP`, or `CURRENT_REMOTE_GET_SCOPE`;
* the chunk contains modules emitted as `SourceType::Remote` or `SourceType::ConsumeShared`;
* federation runtime modules (`webpack/runtime/remotes_loading`, `consumes_loading`, etc.) are present.
- Even without the explicit flag, fall back to auto detection when the runtime requirements set indicates federation and `ENSURE_CHUNK_HANDLERS` is present (covers advanced cases such as custom remotes).
2. Cache the decision in an `FxHashMap<ChunkUkey, bool>` on the plugin so subsequent hooks reuse the computation.

### 4.3 Wiring runtime modules
1. Pass the cached boolean into both runtime modules:
- `StartupChunkDependenciesRuntimeModule::new(is_async)`
- `StartupEntrypointRuntimeModule::new(is_async)`
2. Inside `StartupChunkDependenciesRuntimeModule::generate`, double-check the helper (for safety) and emit the asynchronous `Promise.all` body when required—even for `ChunkLoadingType::Require`.
3. `StartupEntrypointRuntimeModule` already exposes async and sync templates; we simply select the async template whenever the helper returns `true`.

### 4.4 Bootstrap behaviour across output formats
- **CommonJS / target: node** – ensure `rspack_plugin_javascript` assigns the startup promise to `module.exports`, while still supporting synchronous consumers by resolving immediately when the promise settles synchronously.
- **Script / var libraries** – write the promise onto the global container (e.g. `self[name] = exportsPromise`) and keep compatibility by returning the resolved exports via `exportsPromise.then(...)`.
- **ESM (`ModuleChunkFormatPlugin`)** – generate `const __webpack_exports__Promise = __webpack_require__.x();` and re-export as `export default await __webpack_exports__Promise` (or `return exportsPromise.then(...)` when TLA is unavailable). Forward named exports via helpers that await the promise.
- **Async chunk loaders** – no behavioural change required; the startup promise already chains the promises returned by `__webpack_require__.e`.

### 4.5 Interaction with other runtime plugins
- `CommonJsChunkLoadingPlugin` still installs the synchronous loader for `ChunkLoadingType::Require`; the new startup promise simply resolves immediately when no async handlers are registered, preserving fast paths.
- `ModuleChunkFormatPlugin` must import the promise and wait for it before executing entry modules, mirroring the script/global bootstraps.
- `EmbedFederationRuntimeModule` continues to wrap `prevStartup` with `Promise.resolve`, ensuring additional runtime hooks (e.g. other plugins) can compose without being aware of the async transition.

### 4.6 Hook ordering
- `ModuleFederationRuntimePlugin` registers the flag during `CompilerCompilation`, before runtime plugins execute.
- Federation plugins (`ContainerPlugin`, `ContainerReferencePlugin`, `EmbedFederationRuntimePlugin`) add their runtime requirements in the same compilation phase, so by the time `StartupChunkDependenciesPlugin` evaluates a chunk, the requirements already contain federation markers.
- `ModuleChunkFormatPlugin` runs after runtime modules emit their code, allowing it to reuse the cached boolean to choose the appropriate bootstrap.

### 4.7 Testing and validation
1. **Unit tests**
- Verify the helper returns `true` for combinations of runtime globals, source types, and explicit flags.
- Confirm `StartupChunkDependenciesRuntimeModule` emits the expected async/sync templates.
- Ensure `EmbedFederationRuntimeModule` still correctly wraps startup with a promise.
2. **Integration tests**
- Node/CommonJS host with `experiments.asyncStartup = true`: `require("./dist/main.js")` should return a promise that resolves to the entry exports and waits for remote loading.
- Browser/script host: confirm the global entry receives a promise and side-effects run only after awaiting it.
- ESM output (`experiments.outputModule`): top-level `await` (or promise chain) should expose the federated exports after remotes resolve.
- Negative control (async flag off, no remotes): startup remains synchronous.
3. **Parity check** – compare generated startup fragments and runtime requirements with the enhanced webpack plugin to ensure compatibility.
4. **Performance** – benchmark a representative federation build to confirm that the extra promise handling does not regress cold start measurably.

## 5. Compatibility Considerations
- Async startup remains opt-in at the federation plugin level; existing users see no change.
- Document that the entry bundle produces a promise when the flag is enabled, and suggest awaiting it in application code (Node/SSR or browser).
- Update CLI/help text and typings so the flag is discoverable.
- Highlight that enabling async startup aligns Rspack with the latest `@module-federation/runtime` expectations.

## 6. Open Questions
1. Should we add a global `experiments.forceAsyncStartup` for non-federation runtimes that still want the behaviour?
2. How do we handle legacy environments without native promises or top-level `await`? (Likely polyfill guidance.)
3. Do we auto-enable async startup when detecting federation even if the flag is false, or keep that as a warning-only path?
4. How should SSR frameworks detect and await the startup promise automatically?

## 7. Status & Remaining Work (October 16 2025)
- ✅ JS & Rust plumbing: `moduleFederation.experiments.asyncStartup` and the fallback `experiments.mfAsyncStartup` flow through napi into the core `Experiments` struct.
- ✅ Runtime detection: `StartupChunkDependenciesPlugin` now auto-selects the async startup templates whenever the flag is set or federation runtime globals/modules are present.
- ✅ Regression coverage: `tests/rspack-test/serialCases/container-1-5/async-startup-no-dynamic` exercises federation async startup without relying on dynamic imports.
- ✅ CommonJS bootstraps (`CommonJsChunkFormatPlugin`) now return a promise when async startup is enabled.
- ✅ Global script bootstraps (`ArrayPushCallbackChunkFormatPlugin`) wrap startup in a promise under the async flag.
- ✅ Module chunk (ESM) bootstraps export an awaited promise when async startup is enabled.
- ✅ Added smoke checks that emitted CommonJS, global script, and ESM bundles contain the promise-aware bootstrap.
- ❌ Need end-to-end runtime assertions (especially verifying resolved values) that hosts receive a promise.
- ⚠️ `pnpm run test:unit` currently fails due to existing Jest haste-map collisions and long-running watch tests; no new failures introduced by async-startup changes.
- ⚠️ `pnpm run lint:type` currently fails due to the repository’s warning budget (pre-existing issue); revisit once refactor lands.

Delivering the remaining items will make async startup a native capability of Rspack’s runtime while keeping behaviour strictly opt-in for federation users.
2 changes: 2 additions & 0 deletions crates/node_binding/napi-binding.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2112,6 +2112,7 @@ inlineConst: boolean
inlineEnum: boolean
typeReexportsPresence: boolean
lazyBarrel: boolean
mfAsyncStartup: boolean
}

export interface RawExperimentSnapshotOptions {
Expand Down Expand Up @@ -2424,6 +2425,7 @@ export interface RawLimitChunkCountPluginOptions {

export interface RawModuleFederationRuntimePluginOptions {
entryRuntime?: string | undefined
asyncStartup?: boolean | undefined
}

export interface RawModuleFilenameTemplateFnCtx {
Expand Down
61 changes: 1 addition & 60 deletions crates/node_binding/rspack.wasi-browser.js
Original file line number Diff line number Diff line change
Expand Up @@ -63,63 +63,4 @@ const {
},
})
export default __napiModule.exports
export const Assets = __napiModule.exports.Assets
export const AsyncDependenciesBlock = __napiModule.exports.AsyncDependenciesBlock
export const Chunk = __napiModule.exports.Chunk
export const ChunkGraph = __napiModule.exports.ChunkGraph
export const ChunkGroup = __napiModule.exports.ChunkGroup
export const Chunks = __napiModule.exports.Chunks
export const CodeGenerationResult = __napiModule.exports.CodeGenerationResult
export const CodeGenerationResults = __napiModule.exports.CodeGenerationResults
export const ConcatenatedModule = __napiModule.exports.ConcatenatedModule
export const ContextModule = __napiModule.exports.ContextModule
export const Dependency = __napiModule.exports.Dependency
export const Diagnostics = __napiModule.exports.Diagnostics
export const EntryDataDto = __napiModule.exports.EntryDataDto
export const EntryDataDTO = __napiModule.exports.EntryDataDTO
export const EntryDependency = __napiModule.exports.EntryDependency
export const EntryOptionsDto = __napiModule.exports.EntryOptionsDto
export const EntryOptionsDTO = __napiModule.exports.EntryOptionsDTO
export const ExternalModule = __napiModule.exports.ExternalModule
export const JsCompilation = __napiModule.exports.JsCompilation
export const JsCompiler = __napiModule.exports.JsCompiler
export const JsContextModuleFactoryAfterResolveData = __napiModule.exports.JsContextModuleFactoryAfterResolveData
export const JsContextModuleFactoryBeforeResolveData = __napiModule.exports.JsContextModuleFactoryBeforeResolveData
export const JsDependencies = __napiModule.exports.JsDependencies
export const JsEntries = __napiModule.exports.JsEntries
export const JsExportsInfo = __napiModule.exports.JsExportsInfo
export const JsModuleGraph = __napiModule.exports.JsModuleGraph
export const JsResolver = __napiModule.exports.JsResolver
export const JsResolverFactory = __napiModule.exports.JsResolverFactory
export const JsStats = __napiModule.exports.JsStats
export const KnownBuildInfo = __napiModule.exports.KnownBuildInfo
export const Module = __napiModule.exports.Module
export const ModuleGraphConnection = __napiModule.exports.ModuleGraphConnection
export const NativeWatcher = __napiModule.exports.NativeWatcher
export const NativeWatchResult = __napiModule.exports.NativeWatchResult
export const NormalModule = __napiModule.exports.NormalModule
export const RawExternalItemFnCtx = __napiModule.exports.RawExternalItemFnCtx
export const ReadonlyResourceData = __napiModule.exports.ReadonlyResourceData
export const ResolverFactory = __napiModule.exports.ResolverFactory
export const Sources = __napiModule.exports.Sources
export const VirtualFileStore = __napiModule.exports.VirtualFileStore
export const JsVirtualFileStore = __napiModule.exports.JsVirtualFileStore
export const async = __napiModule.exports.async
export const BuiltinPluginName = __napiModule.exports.BuiltinPluginName
export const cleanupGlobalTrace = __napiModule.exports.cleanupGlobalTrace
export const EnforceExtension = __napiModule.exports.EnforceExtension
export const EXPECTED_RSPACK_CORE_VERSION = __napiModule.exports.EXPECTED_RSPACK_CORE_VERSION
export const formatDiagnostic = __napiModule.exports.formatDiagnostic
export const JsLoaderState = __napiModule.exports.JsLoaderState
export const JsRspackSeverity = __napiModule.exports.JsRspackSeverity
export const loadBrowserslist = __napiModule.exports.loadBrowserslist
export const minify = __napiModule.exports.minify
export const minifySync = __napiModule.exports.minifySync
export const RawJavascriptParserCommonjsExports = __napiModule.exports.RawJavascriptParserCommonjsExports
export const RawRuleSetConditionType = __napiModule.exports.RawRuleSetConditionType
export const registerGlobalTrace = __napiModule.exports.registerGlobalTrace
export const RegisterJsTapKind = __napiModule.exports.RegisterJsTapKind
export const sync = __napiModule.exports.sync
export const syncTraceEvent = __napiModule.exports.syncTraceEvent
export const transform = __napiModule.exports.transform
export const transformSync = __napiModule.exports.transformSync

Loading
Loading