diff --git a/.changeset/bridge-ssr-v1.md b/.changeset/bridge-ssr-v1.md
new file mode 100644
index 00000000000..a03c0a77087
--- /dev/null
+++ b/.changeset/bridge-ssr-v1.md
@@ -0,0 +1,7 @@
+---
+'@module-federation/bridge-shared': minor
+'@module-federation/bridge-react': minor
+'@module-federation/bridge-vue3': minor
+---
+
+add a thin buffered Bridge SSR contract with remote-owned React 18 and Vue 3 rendering, document-slot hydration, identity-only host references, instance-scoped mount markers, and contextual server-render failures
diff --git a/.changeset/bright-pots-vanish.md b/.changeset/bright-pots-vanish.md
deleted file mode 100644
index bb3d5a7d8be..00000000000
--- a/.changeset/bright-pots-vanish.md
+++ /dev/null
@@ -1,5 +0,0 @@
----
-'@module-federation/retry-plugin': patch
----
-
-fix: externalize @module-federation/runtime and runtime-core from DTS bundle to prevent type incompatibility in consumers
diff --git a/.changeset/clean-devtools-graph.md b/.changeset/clean-devtools-graph.md
deleted file mode 100644
index db21705a990..00000000000
--- a/.changeset/clean-devtools-graph.md
+++ /dev/null
@@ -1,5 +0,0 @@
----
-"@module-federation/devtools": patch
----
-
-Fix dependency graph cards for remotes that are only known by their manifest URL.
diff --git a/.changeset/drop-ansi-colors-dts-plugin.md b/.changeset/drop-ansi-colors-dts-plugin.md
deleted file mode 100644
index e292e061f8e..00000000000
--- a/.changeset/drop-ansi-colors-dts-plugin.md
+++ /dev/null
@@ -1,5 +0,0 @@
----
-'@module-federation/dts-plugin': patch
----
-
-chore(dts-plugin): drop `ansi-colors` dependency in favor of inline ANSI escape for the single red error message.
diff --git a/.changeset/drop-lodash-get.md b/.changeset/drop-lodash-get.md
deleted file mode 100644
index 2d630d65a64..00000000000
--- a/.changeset/drop-lodash-get.md
+++ /dev/null
@@ -1,5 +0,0 @@
----
-'@module-federation/typescript': patch
----
-
-chore(typescript): drop `lodash.get` dependency in favor of optional chaining in `normalizeOptions`.
diff --git a/.changeset/drop-resolve-extractor.md b/.changeset/drop-resolve-extractor.md
deleted file mode 100644
index 857b7545c2d..00000000000
--- a/.changeset/drop-resolve-extractor.md
+++ /dev/null
@@ -1,5 +0,0 @@
----
-'@module-federation/third-party-dts-extractor': patch
----
-
-chore(third-party-dts-extractor): replace `resolve` with `exsolve` (0 transitive deps, ~100KB smaller) for the single `package.json` lookup.
diff --git a/.changeset/fast-manifests-flush.md b/.changeset/fast-manifests-flush.md
new file mode 100644
index 00000000000..605ba312bfe
--- /dev/null
+++ b/.changeset/fast-manifests-flush.md
@@ -0,0 +1,7 @@
+---
+'@module-federation/nextjs-mf': patch
+'@module-federation/node': patch
+'@module-federation/sdk': patch
+---
+
+Use manifest expose assets for Next.js chunk flushing, including aliased remotes, and derive legacy federated stats compatibility assets without an extra Webpack stats traversal.
diff --git a/.changeset/fix-runtime-esm-preload.md b/.changeset/fix-runtime-esm-preload.md
new file mode 100644
index 00000000000..3041d317f8a
--- /dev/null
+++ b/.changeset/fix-runtime-esm-preload.md
@@ -0,0 +1,7 @@
+---
+'@module-federation/runtime-core': patch
+---
+
+fix(runtime): preload ESM remote chunks with `modulepreload` links instead of
+executable module scripts before container initialization. Loader plugins now
+observe these implicit ESM preloads through `createLink`.
diff --git a/.changeset/fuzzy-pears-play.md b/.changeset/fuzzy-pears-play.md
deleted file mode 100644
index 8f7a441bdc6..00000000000
--- a/.changeset/fuzzy-pears-play.md
+++ /dev/null
@@ -1,5 +0,0 @@
----
-"@module-federation/playground": patch
----
-
-Add a publishable Module Federation playground package for the docs site.
diff --git a/.changeset/modern-v3-lazy-compilation.md b/.changeset/modern-v3-lazy-compilation.md
deleted file mode 100644
index 9394c54afce..00000000000
--- a/.changeset/modern-v3-lazy-compilation.md
+++ /dev/null
@@ -1,5 +0,0 @@
----
-"@module-federation/modern-js-v3": patch
----
-
-Disable lazy compilation for Modern.js v3 producer apps.
diff --git a/.changeset/node-esm-builtin-loader.md b/.changeset/node-esm-builtin-loader.md
deleted file mode 100644
index 808ae572569..00000000000
--- a/.changeset/node-esm-builtin-loader.md
+++ /dev/null
@@ -1,5 +0,0 @@
----
-"@module-federation/sdk": patch
----
-
-Handle Node.js built-in imports in the Node ESM remote loader.
diff --git a/.changeset/replace-find-pkg-with-empathic.md b/.changeset/replace-find-pkg-with-empathic.md
deleted file mode 100644
index fc5fd8c037c..00000000000
--- a/.changeset/replace-find-pkg-with-empathic.md
+++ /dev/null
@@ -1,7 +0,0 @@
----
-'@module-federation/managers': patch
-'@module-federation/third-party-dts-extractor': patch
-'@module-federation/manifest': patch
----
-
-chore: replace `find-pkg` with `empathic` (per [e18e replacements](https://e18e.dev/docs/replacements/find-pkg.html)). Removes unused `find-pkg` dependency from `@module-federation/manifest`.
diff --git a/.changeset/small-deps-consolidated.md b/.changeset/small-deps-consolidated.md
deleted file mode 100644
index a72b346080e..00000000000
--- a/.changeset/small-deps-consolidated.md
+++ /dev/null
@@ -1,9 +0,0 @@
----
-'@module-federation/devtools': patch
-'@module-federation/treeshake-server': patch
-'@module-federation/rspress-plugin': patch
-'create-module-federation': patch
-'@module-federation/third-party-dts-extractor': patch
----
-
-Bump runtime dependencies: echarts 6.1.0 (devtools), hono 4.12.26 and @hono/node-server 1.19.13 (treeshake-server), lodash-es 4.18.1 (rspress-plugin), handlebars 4.7.9 (create-module-federation), resolve 1.22.12 (third-party-dts-extractor).
diff --git a/.changeset/tricky-pants-rescue.md b/.changeset/tricky-pants-rescue.md
deleted file mode 100644
index e109c611c66..00000000000
--- a/.changeset/tricky-pants-rescue.md
+++ /dev/null
@@ -1,5 +0,0 @@
----
-'@module-federation/treeshake-server': patch
----
-
-Bump undici to 7.28.0 to resolve known vulnerabilities (CVE-2026-12151 and related advisories).
diff --git a/.codex/skills/local-ci/SKILL.md b/.codex/skills/local-ci/SKILL.md
index 556a34b8f2a..fce1704e0b1 100644
--- a/.codex/skills/local-ci/SKILL.md
+++ b/.codex/skills/local-ci/SKILL.md
@@ -99,5 +99,5 @@ Use the local CI job if one exists for the workflow's behavior. For workflow syn
- `ci:local` sets `CI=true` and mirrors the repo workflow order.
- Some jobs intentionally skip GitHub-only actions such as `actionlint` and `bundle-size-comment`.
-- The repo uses `pnpm`, Node 20, and Turbo as the standard execution path.
+- The repo uses `pnpm`, Node 24, and Turbo as the standard execution path.
- In worktrees, still use Turbo/package scripts directly as documented by the repo instructions.
diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml
index a55a76c3a91..a0793b07b2a 100644
--- a/.github/workflows/build-and-test.yml
+++ b/.github/workflows/build-and-test.yml
@@ -18,6 +18,8 @@ jobs:
checkout-install:
runs-on: ubuntu-latest
timeout-minutes: 30
+ outputs:
+ e2e_suites: ${{ steps.e2e-affected.outputs.e2e_suites }}
steps:
- name: Checkout Repository
uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5
@@ -27,16 +29,20 @@ jobs:
- name: Setup pnpm
uses: pnpm/action-setup@b906affcce14559ad1aafd4ab0e942779e9f58b1 # v4
- - name: Setup Node.js 20
+ - name: Setup Node.js 24
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6
with:
- node-version: '20'
+ node-version: '24'
cache: 'pnpm'
cache-dependency-path: '**/pnpm-lock.yaml'
- name: Install dependencies
run: pnpm install --frozen-lockfile
+ - name: Evaluate E2E affected suites
+ id: e2e-affected
+ run: node tools/scripts/ci-is-affected.mjs --githubOutputAll=e2e_suites
+
- name: Restore Turborepo cache
id: turbo-cache
uses: actions/cache/restore@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5
@@ -53,6 +59,9 @@ jobs:
- name: Check code format
run: node tools/scripts/check-format-changed.mjs
+ - name: Test CI affected policy
+ run: node --test tools/scripts/ci-is-affected.test.mjs
+
- name: Verify Rslib Template Publint Wiring
run: node packages/create-module-federation/scripts/verify-rslib-templates.mjs
@@ -85,6 +94,19 @@ jobs:
- name: Run affected package tests
run: node tools/scripts/run-affected-package-tests.mjs
+ - name: Pack Node.js 20 compatibility artifacts
+ if: ${{ !startsWith(github.head_ref, 'release-v') }}
+ run: node tools/scripts/prepare-node20-compat-packages.mjs
+
+ - name: Upload Node.js 20 compatibility artifacts
+ if: ${{ !startsWith(github.head_ref, 'release-v') }}
+ uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
+ with:
+ name: node20-compat-packages
+ path: artifacts/node20-compat/*.tgz
+ if-no-files-found: error
+ retention-days: 1
+
- name: Save Turborepo cache
if: ${{ success() && steps.turbo-cache.outputs.cache-hit != 'true' }}
uses: actions/cache/save@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5
@@ -94,52 +116,85 @@ jobs:
e2e-modern:
needs: checkout-install
+ if: ${{ fromJSON(needs.checkout-install.outputs.e2e_suites).modern }}
uses: ./.github/workflows/e2e-modern.yml
secrets: inherit
+ node20-compat:
+ if: ${{ !startsWith(github.head_ref, 'release-v') }}
+ needs: checkout-install
+ runs-on: ubuntu-latest
+ timeout-minutes: 15
+ steps:
+ - name: Checkout Repository
+ uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5
+
+ - name: Setup Node.js 20.19.5
+ uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6
+ with:
+ node-version: '20.19.5'
+
+ - name: Download Node.js 20 compatibility artifacts
+ uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4
+ with:
+ name: node20-compat-packages
+ path: artifacts/node20-compat
+
+ - name: Verify published packages on Node.js 20.19.5
+ run: node tools/scripts/verify-node20-compat.mjs
+
e2e-runtime:
needs: checkout-install
+ if: ${{ fromJSON(needs.checkout-install.outputs.e2e_suites).runtime }}
uses: ./.github/workflows/e2e-runtime.yml
secrets: inherit
e2e-manifest:
needs: checkout-install
+ if: ${{ fromJSON(needs.checkout-install.outputs.e2e_suites).manifest }}
uses: ./.github/workflows/e2e-manifest.yml
secrets: inherit
e2e-node:
needs: checkout-install
+ if: ${{ fromJSON(needs.checkout-install.outputs.e2e_suites).node }}
uses: ./.github/workflows/e2e-node.yml
secrets: inherit
e2e-next-dev:
needs: checkout-install
+ if: ${{ fromJSON(needs.checkout-install.outputs.e2e_suites).next }}
uses: ./.github/workflows/e2e-next-dev.yml
secrets: inherit
e2e-next-prod:
needs: checkout-install
+ if: ${{ fromJSON(needs.checkout-install.outputs.e2e_suites).next }}
uses: ./.github/workflows/e2e-next-prod.yml
secrets: inherit
e2e-treeshake:
needs: checkout-install
+ if: ${{ fromJSON(needs.checkout-install.outputs.e2e_suites).treeshake }}
uses: ./.github/workflows/e2e-treeshake.yml
secrets: inherit
e2e-modern-ssr:
needs: checkout-install
+ if: ${{ fromJSON(needs.checkout-install.outputs.e2e_suites).modernSsr }}
uses: ./.github/workflows/e2e-modern-ssr.yml
secrets: inherit
e2e-router:
needs: checkout-install
+ if: ${{ fromJSON(needs.checkout-install.outputs.e2e_suites).router }}
uses: ./.github/workflows/e2e-router.yml
secrets: inherit
e2e-metro:
needs:
- checkout-install
+ if: ${{ fromJSON(needs.checkout-install.outputs.e2e_suites).metro }}
uses: ./.github/workflows/e2e-metro.yml
secrets: inherit
diff --git a/.github/workflows/bundle-size.yml b/.github/workflows/bundle-size.yml
index 25341fc241d..f2b0f4d17f6 100644
--- a/.github/workflows/bundle-size.yml
+++ b/.github/workflows/bundle-size.yml
@@ -29,7 +29,7 @@ jobs:
- name: Setup Node.js
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6
with:
- node-version: 20
+ node-version: 24
cache: pnpm
cache-dependency-path: '**/pnpm-lock.yaml'
diff --git a/.github/workflows/devtools.yml b/.github/workflows/devtools.yml
index f389fabfa7a..232eb914fb3 100644
--- a/.github/workflows/devtools.yml
+++ b/.github/workflows/devtools.yml
@@ -20,10 +20,10 @@ jobs:
- name: Setup pnpm
uses: pnpm/action-setup@b906affcce14559ad1aafd4ab0e942779e9f58b1 # v4
- - name: Setup Node.js 20
+ - name: Setup Node.js 24
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6
with:
- node-version: '20'
+ node-version: '24'
cache: 'pnpm'
cache-dependency-path: '**/pnpm-lock.yaml'
diff --git a/.github/workflows/e2e-bridge-ssr.yml b/.github/workflows/e2e-bridge-ssr.yml
new file mode 100644
index 00000000000..9cb1a7a5595
--- /dev/null
+++ b/.github/workflows/e2e-bridge-ssr.yml
@@ -0,0 +1,45 @@
+name: E2E Test for Bridge SSR
+
+on:
+ pull_request:
+ paths:
+ - 'packages/bridge/**'
+ - 'apps/bridge-ssr-demo/**'
+ - 'tools/scripts/*bridge-ssr*'
+ - '.github/workflows/e2e-bridge-ssr.yml'
+ - 'pnpm-lock.yaml'
+
+concurrency:
+ group: e2e-bridge-ssr-${{ github.ref }}
+ cancel-in-progress: true
+
+jobs:
+ rsbuild:
+ runs-on: ubuntu-latest
+ timeout-minutes: 45
+ steps:
+ - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5
+ - uses: pnpm/action-setup@b906affcce14559ad1aafd4ab0e942779e9f58b1 # v4
+ - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6
+ with:
+ node-version: '20'
+ cache: pnpm
+ - run: pnpm install --frozen-lockfile
+ - run: pnpm run build:packages
+ - run: pnpm run e2e:bridge:ssr
+ - run: pnpm run e2e:bridge:ssr:production
+
+ vite-smoke:
+ runs-on: ubuntu-latest
+ timeout-minutes: 30
+ continue-on-error: true
+ steps:
+ - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5
+ - uses: pnpm/action-setup@b906affcce14559ad1aafd4ab0e942779e9f58b1 # v4
+ - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6
+ with:
+ node-version: '20'
+ cache: pnpm
+ - run: pnpm install --frozen-lockfile
+ - run: pnpm run build:packages
+ - run: pnpm run e2e:bridge:ssr:vite-smoke
diff --git a/.github/workflows/e2e-manifest.yml b/.github/workflows/e2e-manifest.yml
index 1aba2e9a5a5..f3796437556 100644
--- a/.github/workflows/e2e-manifest.yml
+++ b/.github/workflows/e2e-manifest.yml
@@ -1,4 +1,4 @@
-# .github/workflows/e2e-webpack-host.yml
+# .github/workflows/e2e-manifest.yml
name: E2E Test for Manifest
on:
@@ -17,10 +17,10 @@ jobs:
- name: Setup pnpm
uses: pnpm/action-setup@b906affcce14559ad1aafd4ab0e942779e9f58b1 # v4
- - name: Setup Node.js 20
+ - name: Setup Node.js 24
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6
with:
- node-version: '20'
+ node-version: '24'
cache: 'pnpm'
cache-dependency-path: '**/pnpm-lock.yaml'
@@ -45,24 +45,8 @@ jobs:
- name: Build shared packages
run: pnpm run build:packages
- - name: Check CI conditions
- id: check-ci
- run: |
- ci_status=0
- node tools/scripts/ci-is-affected.mjs --appName=3008-webpack-host,3009-webpack-provider,3010-rspack-provider,3011-rspack-manifest-provider,3012-rspack-js-entry-provider || ci_status=$?
- if [ "$ci_status" -eq 0 ]; then
- echo "run-e2e=true" >> "$GITHUB_OUTPUT"
- elif [ "$ci_status" -eq 1 ]; then
- echo "run-e2e=false" >> "$GITHUB_OUTPUT"
- else
- echo "ci-is-affected failed with unexpected exit code: $ci_status" >&2
- exit "$ci_status"
- fi
-
- name: E2E Test for Manifest Demo (dev)
- if: steps.check-ci.outputs.run-e2e == 'true'
run: pnpm exec turbo run test:e2e --filter=3008-webpack-host
- name: E2E Test for Manifest Demo (prod)
- if: steps.check-ci.outputs.run-e2e == 'true'
run: pnpm exec turbo run test:e2e:production --filter=3008-webpack-host
diff --git a/.github/workflows/e2e-metro.yml b/.github/workflows/e2e-metro.yml
index 9560cac3639..01b33b19692 100644
--- a/.github/workflows/e2e-metro.yml
+++ b/.github/workflows/e2e-metro.yml
@@ -15,51 +15,7 @@ env:
METRO_APP_NAME: example-host
jobs:
- e2e-metro-check-affected:
- runs-on: ubuntu-latest
- timeout-minutes: 30
- outputs:
- should_run_e2e: ${{ steps.affected.outputs.should-run-e2e }}
- steps:
- - name: Check out repository
- uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5
- with:
- fetch-depth: 0
-
- - name: Set up pnpm
- uses: pnpm/action-setup@b906affcce14559ad1aafd4ab0e942779e9f58b1 # v4
-
- - name: Set up Node.js 20
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6
- with:
- node-version: '20'
- cache: 'pnpm'
- cache-dependency-path: '**/pnpm-lock.yaml'
-
- - name: Export SKIP_DEVTOOLS_POSTINSTALL
- run: echo "SKIP_DEVTOOLS_POSTINSTALL=true" >> "$GITHUB_ENV"
- shell: bash
-
- - name: Install pnpm dependencies
- run: pnpm install --frozen-lockfile
-
- - name: Evaluate Metro affected scope
- id: affected
- run: |
- ci_status=0
- node tools/scripts/ci-is-affected.mjs --appName=${{ env.METRO_APP_NAME }} || ci_status=$?
- if [ "$ci_status" -eq 0 ]; then
- echo "should-run-e2e=true" >> "$GITHUB_OUTPUT"
- elif [ "$ci_status" -eq 1 ]; then
- echo "should-run-e2e=false" >> "$GITHUB_OUTPUT"
- else
- echo "ci-is-affected failed with unexpected exit code: $ci_status" >&2
- exit "$ci_status"
- fi
-
e2e-metro-android:
- needs: e2e-metro-check-affected
- if: needs.e2e-metro-check-affected.outputs.should_run_e2e == 'true'
runs-on: ubuntu-latest
timeout-minutes: 60
env:
@@ -82,10 +38,10 @@ jobs:
- name: Set up pnpm
uses: pnpm/action-setup@b906affcce14559ad1aafd4ab0e942779e9f58b1 # v4
- - name: Set up Node.js 20
+ - name: Set up Node.js 24
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6
with:
- node-version: '20'
+ node-version: '24'
cache: 'pnpm'
cache-dependency-path: '**/pnpm-lock.yaml'
@@ -170,9 +126,8 @@ jobs:
path: ~/.maestro/tests/
e2e-metro-ios:
- needs: e2e-metro-check-affected
- if: needs.e2e-metro-check-affected.outputs.should_run_e2e == 'true'
- runs-on: macos-latest
+ # Xcode 26.5 fails to compile React Native 0.80's fmt pod for the simulator.
+ runs-on: macos-15
timeout-minutes: 60
env:
NO_COLOR: 1
@@ -190,10 +145,10 @@ jobs:
- name: Set up pnpm
uses: pnpm/action-setup@b906affcce14559ad1aafd4ab0e942779e9f58b1 # v4
- - name: Set up Node.js 20
+ - name: Set up Node.js 24
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6
with:
- node-version: '20'
+ node-version: '24'
cache: 'pnpm'
cache-dependency-path: '**/pnpm-lock.yaml'
diff --git a/.github/workflows/e2e-modern-ssr.yml b/.github/workflows/e2e-modern-ssr.yml
index bbfb6a89a78..247957a4e54 100644
--- a/.github/workflows/e2e-modern-ssr.yml
+++ b/.github/workflows/e2e-modern-ssr.yml
@@ -16,10 +16,10 @@ jobs:
- name: Setup pnpm
uses: pnpm/action-setup@b906affcce14559ad1aafd4ab0e942779e9f58b1 # v4
- - name: Setup Node.js 20
+ - name: Setup Node.js 24
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6
with:
- node-version: '20'
+ node-version: '24'
cache: 'pnpm'
cache-dependency-path: '**/pnpm-lock.yaml'
@@ -44,20 +44,5 @@ jobs:
- name: Build shared packages
run: pnpm run build:packages
- - name: Check CI conditions
- id: check-ci
- run: |
- ci_status=0
- node tools/scripts/ci-is-affected.mjs --appName=@module-federation/modern-js,@module-federation/modern-js-v3,modernjs-ssr-host,modernjs-ssr-remote,modernjs-ssr-remote-new-version,modernjs-ssr-nested-remote,modernjs-ssr-dynamic-remote,modernjs-ssr-dynamic-remote-new-version,modernjs-ssr-dynamic-nested-remote,modernjs-ssr-data-fetch-host,modernjs-ssr-data-fetch-provider,modernjs-ssr-data-fetch-provider-csr || ci_status=$?
- if [ "$ci_status" -eq 0 ]; then
- echo "run-e2e=true" >> "$GITHUB_OUTPUT"
- elif [ "$ci_status" -eq 1 ]; then
- echo "run-e2e=false" >> "$GITHUB_OUTPUT"
- else
- echo "ci-is-affected failed with unexpected exit code: $ci_status" >&2
- exit "$ci_status"
- fi
-
- name: Manifest verification for ModernJS SSR
- if: steps.check-ci.outputs.run-e2e == 'true'
run: node tools/scripts/run-modern-e2e.mjs --mode=manifest
diff --git a/.github/workflows/e2e-modern.yml b/.github/workflows/e2e-modern.yml
index 7ed2a7c757c..b5cd1b15301 100644
--- a/.github/workflows/e2e-modern.yml
+++ b/.github/workflows/e2e-modern.yml
@@ -1,4 +1,4 @@
-# .github/workflows/e2e-modernjs.yml
+# .github/workflows/e2e-modern.yml
name: E2E Test for ModernJS
on:
@@ -17,10 +17,10 @@ jobs:
- name: Setup pnpm
uses: pnpm/action-setup@b906affcce14559ad1aafd4ab0e942779e9f58b1 # v4
- - name: Setup Node.js 20
+ - name: Setup Node.js 24
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6
with:
- node-version: '20'
+ node-version: '24'
cache: 'pnpm'
cache-dependency-path: '**/pnpm-lock.yaml'
@@ -45,20 +45,5 @@ jobs:
- name: Build shared packages
run: pnpm run build:packages
- - name: Check CI conditions
- id: check-ci
- run: |
- ci_status=0
- node tools/scripts/ci-is-affected.mjs --appName=@module-federation/modern-js,@module-federation/modern-js-v3 || ci_status=$?
- if [ "$ci_status" -eq 0 ]; then
- echo "run-e2e=true" >> "$GITHUB_OUTPUT"
- elif [ "$ci_status" -eq 1 ]; then
- echo "run-e2e=false" >> "$GITHUB_OUTPUT"
- else
- echo "ci-is-affected failed with unexpected exit code: $ci_status" >&2
- exit "$ci_status"
- fi
-
- name: Run Modern package test suite
- if: steps.check-ci.outputs.run-e2e == 'true'
run: pnpm exec turbo run test --filter=@module-federation/modern-js --filter=@module-federation/modern-js-v3 --concurrency=20
diff --git a/.github/workflows/e2e-next-dev.yml b/.github/workflows/e2e-next-dev.yml
index e8524459313..0a2df603814 100644
--- a/.github/workflows/e2e-next-dev.yml
+++ b/.github/workflows/e2e-next-dev.yml
@@ -18,10 +18,10 @@ jobs:
- name: Setup pnpm
uses: pnpm/action-setup@b906affcce14559ad1aafd4ab0e942779e9f58b1 # v4
- - name: Setup Node.js 20
+ - name: Setup Node.js 24
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6
with:
- node-version: '20'
+ node-version: '24'
cache: 'pnpm'
cache-dependency-path: '**/pnpm-lock.yaml'
@@ -46,20 +46,5 @@ jobs:
- name: Build shared packages
run: pnpm run build:packages
- - name: Check CI conditions
- id: check-ci
- run: |
- ci_status=0
- node tools/scripts/ci-is-affected.mjs --appName=@module-federation/3000-home,@module-federation/3001-shop,@module-federation/3002-checkout || ci_status=$?
- if [ "$ci_status" -eq 0 ]; then
- echo "run-e2e=true" >> "$GITHUB_OUTPUT"
- elif [ "$ci_status" -eq 1 ]; then
- echo "run-e2e=false" >> "$GITHUB_OUTPUT"
- else
- echo "ci-is-affected failed with unexpected exit code: $ci_status" >&2
- exit "$ci_status"
- fi
-
- name: E2E Test for Next.js Dev
- if: steps.check-ci.outputs.run-e2e == 'true'
run: node tools/scripts/run-next-e2e.mjs --mode=dev
diff --git a/.github/workflows/e2e-next-prod.yml b/.github/workflows/e2e-next-prod.yml
index e1e7a6fa8c3..2b8f6fcdce8 100644
--- a/.github/workflows/e2e-next-prod.yml
+++ b/.github/workflows/e2e-next-prod.yml
@@ -16,10 +16,10 @@ jobs:
- name: Setup pnpm
uses: pnpm/action-setup@b906affcce14559ad1aafd4ab0e942779e9f58b1 # v4
- - name: Setup Node.js 20
+ - name: Setup Node.js 24
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6
with:
- node-version: '20'
+ node-version: '24'
cache: 'pnpm'
cache-dependency-path: '**/pnpm-lock.yaml'
@@ -44,20 +44,5 @@ jobs:
- name: Build shared packages
run: pnpm run build:packages
- - name: Check CI conditions
- id: check-ci
- run: |
- ci_status=0
- node tools/scripts/ci-is-affected.mjs --appName=@module-federation/3000-home,@module-federation/3001-shop,@module-federation/3002-checkout || ci_status=$?
- if [ "$ci_status" -eq 0 ]; then
- echo "run-e2e=true" >> "$GITHUB_OUTPUT"
- elif [ "$ci_status" -eq 1 ]; then
- echo "run-e2e=false" >> "$GITHUB_OUTPUT"
- else
- echo "ci-is-affected failed with unexpected exit code: $ci_status" >&2
- exit "$ci_status"
- fi
-
- name: E2E Test for Next.js Prod
- if: steps.check-ci.outputs.run-e2e == 'true'
run: node tools/scripts/run-next-e2e.mjs --mode=prod
diff --git a/.github/workflows/e2e-node.yml b/.github/workflows/e2e-node.yml
index aa2033e9a48..0e3843d7f09 100644
--- a/.github/workflows/e2e-node.yml
+++ b/.github/workflows/e2e-node.yml
@@ -1,4 +1,4 @@
-# .github/workflows/e2e-node-federation.yml
+# .github/workflows/e2e-node.yml
name: E2E Node Federation
on:
@@ -17,10 +17,10 @@ jobs:
- name: Setup pnpm
uses: pnpm/action-setup@b906affcce14559ad1aafd4ab0e942779e9f58b1 # v4
- - name: Setup Node.js 20
+ - name: Setup Node.js 24
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6
with:
- node-version: '20'
+ node-version: '24'
cache: 'pnpm'
cache-dependency-path: '**/pnpm-lock.yaml'
@@ -45,20 +45,5 @@ jobs:
- name: Build shared packages
run: pnpm run build:packages
- - name: Check CI conditions
- id: check-ci
- run: |
- ci_status=0
- node tools/scripts/ci-is-affected.mjs --appName=node-host,node-local-remote,node-remote,node-dynamic-remote-new-version,node-dynamic-remote,node-host-e2e || ci_status=$?
- if [ "$ci_status" -eq 0 ]; then
- echo "run-e2e=true" >> "$GITHUB_OUTPUT"
- elif [ "$ci_status" -eq 1 ]; then
- echo "run-e2e=false" >> "$GITHUB_OUTPUT"
- else
- echo "ci-is-affected failed with unexpected exit code: $ci_status" >&2
- exit "$ci_status"
- fi
-
- name: E2E Node Federation
- if: steps.check-ci.outputs.run-e2e == 'true'
run: pnpm run e2e:node
diff --git a/.github/workflows/e2e-router.yml b/.github/workflows/e2e-router.yml
index 8f46d8ccc66..c0f22539465 100644
--- a/.github/workflows/e2e-router.yml
+++ b/.github/workflows/e2e-router.yml
@@ -1,4 +1,4 @@
-# .github/workflows/e2e-router-host-2000.yml
+# .github/workflows/e2e-router.yml
name: E2E Test for Router
on:
@@ -17,10 +17,10 @@ jobs:
- name: Setup pnpm
uses: pnpm/action-setup@b906affcce14559ad1aafd4ab0e942779e9f58b1 # v4
- - name: Setup Node.js 20
+ - name: Setup Node.js 24
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6
with:
- node-version: '20'
+ node-version: '24'
cache: 'pnpm'
cache-dependency-path: '**/pnpm-lock.yaml'
@@ -45,20 +45,5 @@ jobs:
- name: Build shared packages
run: pnpm run build:packages
- - name: Check CI conditions
- id: check-ci
- run: |
- ci_status=0
- node tools/scripts/ci-is-affected.mjs --appName=host,host-v5,host-vue3,remote1,remote2,remote3,remote4,remote5,remote6 || ci_status=$?
- if [ "$ci_status" -eq 0 ]; then
- echo "run-e2e=true" >> "$GITHUB_OUTPUT"
- elif [ "$ci_status" -eq 1 ]; then
- echo "run-e2e=false" >> "$GITHUB_OUTPUT"
- else
- echo "ci-is-affected failed with unexpected exit code: $ci_status" >&2
- exit "$ci_status"
- fi
-
- name: E2E Test for Router
- if: steps.check-ci.outputs.run-e2e == 'true'
run: node tools/scripts/run-router-e2e.mjs --mode=dev
diff --git a/.github/workflows/e2e-runtime.yml b/.github/workflows/e2e-runtime.yml
index d2c7adb9247..4bab493185d 100644
--- a/.github/workflows/e2e-runtime.yml
+++ b/.github/workflows/e2e-runtime.yml
@@ -1,4 +1,4 @@
-# .github/workflows/e2e-runtime-host.yml
+# .github/workflows/e2e-runtime.yml
name: E2E Test for Runtime
on:
@@ -17,10 +17,10 @@ jobs:
- name: Setup pnpm
uses: pnpm/action-setup@b906affcce14559ad1aafd4ab0e942779e9f58b1 # v4
- - name: Setup Node.js 20
+ - name: Setup Node.js 24
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6
with:
- node-version: '20'
+ node-version: '24'
cache: 'pnpm'
cache-dependency-path: '**/pnpm-lock.yaml'
@@ -45,20 +45,5 @@ jobs:
- name: Build shared packages
run: pnpm run build:packages
- - name: Check CI conditions
- id: check-ci
- run: |
- ci_status=0
- node tools/scripts/ci-is-affected.mjs --appName=runtime-host,runtime-remote1,runtime-remote2 || ci_status=$?
- if [ "$ci_status" -eq 0 ]; then
- echo "run-e2e=true" >> "$GITHUB_OUTPUT"
- elif [ "$ci_status" -eq 1 ]; then
- echo "run-e2e=false" >> "$GITHUB_OUTPUT"
- else
- echo "ci-is-affected failed with unexpected exit code: $ci_status" >&2
- exit "$ci_status"
- fi
-
- name: E2E Test for Runtime Demo
- if: steps.check-ci.outputs.run-e2e == 'true'
run: pnpm exec turbo run test:e2e --filter=runtime-host
diff --git a/.github/workflows/e2e-shared-tree-shaking.yml b/.github/workflows/e2e-shared-tree-shaking.yml
index 38a27ab4ac5..e3e63a3bb1b 100644
--- a/.github/workflows/e2e-shared-tree-shaking.yml
+++ b/.github/workflows/e2e-shared-tree-shaking.yml
@@ -16,10 +16,10 @@ jobs:
- name: Setup pnpm
uses: pnpm/action-setup@b906affcce14559ad1aafd4ab0e942779e9f58b1 # v4
- - name: Setup Node.js 20
+ - name: Setup Node.js 24
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6
with:
- node-version: '20'
+ node-version: '24'
cache: 'pnpm'
cache-dependency-path: '**/pnpm-lock.yaml'
diff --git a/.github/workflows/e2e-treeshake.yml b/.github/workflows/e2e-treeshake.yml
index 54e67044fa6..17b3b653bc6 100644
--- a/.github/workflows/e2e-treeshake.yml
+++ b/.github/workflows/e2e-treeshake.yml
@@ -19,10 +19,10 @@ jobs:
- name: Setup pnpm
uses: pnpm/action-setup@b906affcce14559ad1aafd4ab0e942779e9f58b1 # v4
- - name: Setup Node.js 20
+ - name: Setup Node.js 24
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6
with:
- node-version: '20'
+ node-version: '24'
cache: 'pnpm'
cache-dependency-path: '**/pnpm-lock.yaml'
@@ -44,24 +44,8 @@ jobs:
- name: Build shared packages
run: pnpm run build:packages
- - name: Check CI conditions
- id: check-ci
- run: |
- ci_status=0
- node tools/scripts/ci-is-affected.mjs --appName=@module-federation/treeshake-server,@module-federation/treeshake-frontend || ci_status=$?
- if [ "$ci_status" -eq 0 ]; then
- echo "run-e2e=true" >> "$GITHUB_OUTPUT"
- elif [ "$ci_status" -eq 1 ]; then
- echo "run-e2e=false" >> "$GITHUB_OUTPUT"
- else
- echo "ci-is-affected failed with unexpected exit code: $ci_status" >&2
- exit "$ci_status"
- fi
-
- name: E2E Treeshake Server
- if: steps.check-ci.outputs.run-e2e == 'true'
run: pnpm exec turbo run test --filter=@module-federation/treeshake-server --only
- name: E2E Treeshake Frontend
- if: steps.check-ci.outputs.run-e2e == 'true'
run: pnpm exec turbo run e2e --filter=@module-federation/treeshake-frontend --only
diff --git a/.github/workflows/lint-deps.yml b/.github/workflows/lint-deps.yml
index 1e443e1543f..28677ff1b04 100644
--- a/.github/workflows/lint-deps.yml
+++ b/.github/workflows/lint-deps.yml
@@ -26,10 +26,10 @@ jobs:
- name: Setup pnpm
uses: pnpm/action-setup@b906affcce14559ad1aafd4ab0e942779e9f58b1 # v4
- - name: Setup Node.js 20
+ - name: Setup Node.js 24
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6
with:
- node-version: '20'
+ node-version: '24'
cache: 'pnpm'
cache-dependency-path: '**/pnpm-lock.yaml'
diff --git a/.github/workflows/pkg-pr-new.yml b/.github/workflows/pkg-pr-new.yml
index 2709cf9865e..54c6e0cf4e3 100644
--- a/.github/workflows/pkg-pr-new.yml
+++ b/.github/workflows/pkg-pr-new.yml
@@ -28,10 +28,10 @@ jobs:
- name: Setup pnpm
uses: pnpm/action-setup@b906affcce14559ad1aafd4ab0e942779e9f58b1 # v4
- - name: Setup Node.js 20
+ - name: Setup Node.js 24
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6
with:
- node-version: '20'
+ node-version: '24'
cache: 'pnpm'
cache-dependency-path: '**/pnpm-lock.yaml'
diff --git a/.github/workflows/release-pull-request.yml b/.github/workflows/release-pull-request.yml
index fdf867b28e8..2d1b0b374ee 100644
--- a/.github/workflows/release-pull-request.yml
+++ b/.github/workflows/release-pull-request.yml
@@ -36,10 +36,10 @@ jobs:
- name: Setup pnpm
uses: pnpm/action-setup@b906affcce14559ad1aafd4ab0e942779e9f58b1 # v4
- - name: Setup Node.js 20
+ - name: Setup Node.js 24
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6
with:
- node-version: '20'
+ node-version: '24'
cache: 'pnpm'
cache-dependency-path: '**/pnpm-lock.yaml'
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index 2d1fa3204b7..9c54e0e261e 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -1,4 +1,5 @@
name: Release
+run-name: Release ${{ inputs.version }} from ${{ inputs.branch }}
on:
workflow_dispatch:
inputs:
@@ -20,7 +21,7 @@ permissions:
jobs:
release:
- name: Release
+ name: Release ${{ inputs.version }} from ${{ inputs.branch }}
if: ${{ github.event_name == 'workflow_dispatch' || github.event_name == 'push' }}
runs-on: ubuntu-latest
environment: npm
@@ -45,14 +46,16 @@ jobs:
- name: Setup Node.js
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6
with:
- node-version: 20
+ node-version: 24
cache: 'pnpm'
cache-dependency-path: '**/pnpm-lock.yaml'
- # Update npm to the latest version to enable OIDC
+ # npm 12.0.0 omits its bundled sigstore dependency and breaks OIDC
+ # provenance publishing. Keep npm 11 until the upstream fix ships.
+ # https://github.com/npm/cli/issues/9722
- name: Update npm
run: |
- npm install -g npm@latest
+ npm install -g npm@^11
npm --version
- name: Install deps
diff --git a/.github/workflows/website-zephyr.yml b/.github/workflows/website-zephyr.yml
new file mode 100644
index 00000000000..0ab770a06f5
--- /dev/null
+++ b/.github/workflows/website-zephyr.yml
@@ -0,0 +1,79 @@
+name: Website Zephyr Deploy
+
+on:
+ pull_request:
+ types: [opened, synchronize, reopened, closed]
+ branches: [main]
+ paths:
+ - 'apps/website-new/**'
+ - 'packages/rspress-plugin/**'
+ - 'package.json'
+ - 'pnpm-lock.yaml'
+ - 'turbo.json'
+ - '.github/workflows/website-zephyr.yml'
+ release:
+ types: [released]
+ workflow_dispatch:
+
+permissions:
+ contents: read
+ pull-requests: write
+
+concurrency:
+ group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
+ cancel-in-progress: true
+
+env:
+ SITE_ORIGIN: https://module-federation.io
+ ZE_SECRET_TOKEN: ${{ secrets.ZE_SECRET_TOKEN }}
+ ACTIONS_ALLOW_USE_UNSECURE_NODE_VERSION: true
+
+jobs:
+ deploy:
+ runs-on: ubuntu-latest
+ timeout-minutes: 30
+ steps:
+ - name: Checkout Repository
+ uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5
+ with:
+ fetch-depth: 0
+
+ - name: Setup pnpm
+ uses: pnpm/action-setup@b906affcce14559ad1aafd4ab0e942779e9f58b1 # v4
+
+ - name: Setup Node.js 24
+ uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6
+ with:
+ node-version: '24'
+ cache: 'pnpm'
+ cache-dependency-path: '**/pnpm-lock.yaml'
+
+ - name: Install dependencies
+ run: pnpm install --frozen-lockfile
+
+ - name: Restore Turborepo cache
+ uses: actions/cache/restore@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5
+ with:
+ path: .turbo/cache
+ key: ${{ runner.os }}-turbo-${{ github.ref_name }}-${{ hashFiles('pnpm-lock.yaml') }}
+ restore-keys: |
+ ${{ runner.os }}-turbo-${{ github.ref_name }}-
+ ${{ runner.os }}-turbo-
+
+ - name: Use production Zephyr environment
+ if: ${{ github.event_name == 'release' }}
+ run: echo "ZE_ENV=production" >> "$GITHUB_ENV"
+
+ - name: Build and deploy website with Zephyr
+ if: ${{ env.ZE_SECRET_TOKEN != '' && github.event.action != 'closed' }}
+ run: pnpm run build:website
+
+ - name: Update Zephyr preview environment
+ if: ${{ env.ZE_SECRET_TOKEN != '' && github.event_name == 'pull_request' }}
+ uses: ZephyrCloudIO/zephyr-preview-environment-action@b3ada4d42eb404c8af6da6299c1b0585d238fdd0 # main, pending Node 24 release
+ with:
+ github_token: ${{ secrets.GITHUB_TOKEN }}
+
+ - name: Skip Zephyr deploy without token
+ if: ${{ env.ZE_SECRET_TOKEN == '' }}
+ run: echo "ZE_SECRET_TOKEN is not available for this workflow run; skipping Zephyr deploy."
diff --git a/.husky/commit-msg b/.husky/commit-msg
index 693dbea0ca6..087d40d63ce 100755
--- a/.husky/commit-msg
+++ b/.husky/commit-msg
@@ -1,5 +1,4 @@
#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"
-
-pnpm run commitlint ${1}
+pnpm run commitlint --edit "${1}"
diff --git a/.nvmrc b/.nvmrc
index 209e3ef4b62..a45fd52cc58 100644
--- a/.nvmrc
+++ b/.nvmrc
@@ -1 +1 @@
-20
+24
diff --git a/.prettierignore b/.prettierignore
index a9e2bf4e1d4..70683db1d6b 100644
--- a/.prettierignore
+++ b/.prettierignore
@@ -8,6 +8,9 @@
pnpm-lock.yaml
**/dist/**
**/dist-rslib/**
+**/dist-vite/**
+**/playwright-report/**
+**/test-results/**
apps/website-new/docs
packages/treeshake-server/template/**
diff --git a/.tool-versions b/.tool-versions
index 77730a63469..fcf8a11aa82 100644
--- a/.tool-versions
+++ b/.tool-versions
@@ -1 +1 @@
-nodejs 20.19.0
+nodejs 24.18.0
diff --git a/.vscode/tasks.json b/.vscode/tasks.json
index 79ba01f3db5..6f8c838b4d3 100644
--- a/.vscode/tasks.json
+++ b/.vscode/tasks.json
@@ -4,7 +4,7 @@
{
"label": "pnpm-build-enhanced",
"type": "shell",
- "command": "source $HOME/.nvm/nvm.sh && nvm use 20 && pnpm --filter @module-federation/enhanced run build",
+ "command": "source $HOME/.nvm/nvm.sh && nvm use 24 && pnpm --filter @module-federation/enhanced run build",
"problemMatcher": [],
"options": {
"shell": {
@@ -17,7 +17,7 @@
"panel": "dedicated",
"clear": true
},
- "detail": "Sources NVM, uses Node v20, then builds the enhanced package."
+ "detail": "Sources NVM, uses Node v24, then builds the enhanced package."
}
]
}
diff --git a/AGENTS.md b/AGENTS.md
index 6308311818c..ec49d591537 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -21,7 +21,7 @@ If prose docs conflict with workflows (for example `README.md` or `CONTRIBUTING.
## Environment Parity
-- Node.js: `20` (from `.nvmrc` and workflow setup)
+- Node.js: `24` (from `.nvmrc` and workflow setup)
- pnpm: `10.28.0` (from `package.json` `packageManager`)
- Package manager: pnpm only
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index e89cc8555d9..f92d287df70 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -23,15 +23,15 @@ Thank you for your interest in contributing to Module Federation! Before startin
### Install Node.js
-We recommend using Node.js 20 LTS. Check your Node.js version with `node -v`.
+We recommend using Node.js 24 LTS. Check your Node.js version with `node -v`.
To install Node.js, use [nvm](https://github.com/nvm-sh/nvm) or [fnm](https://github.com/Schniz/fnm):
```bash
-# Install Node.js 20 LTS
-nvm install 20 --lts
-nvm alias default 20
-nvm use 20
+# Install Node.js 24 LTS
+nvm install 24 --lts
+nvm alias default 24
+nvm use 24
```
### Install Dependencies
diff --git a/README.md b/README.md
index 4d43d56612d..7b8e7e1b71d 100644
--- a/README.md
+++ b/README.md
@@ -48,6 +48,24 @@ Come and chat with us on [Discussions](https://github.com/module-federation/univ
Please read the [Contributing Guide](https://github.com/module-federation/core/blob/main/CONTRIBUTING.md).
+### Node.js support
+
+Working on this repository requires Node.js 24 and pnpm 10.28.0. The repository's
+development, test, build, and release workflows run on Node.js 24.
+
+Published packages that execute in Node.js, including build plugins, command-line
+tools, type generation, workers, and server-side runtime code, remain compatible
+with Node.js 20.19.5. CI builds the packages on Node.js 24, installs the resulting
+package tarballs in a clean Node.js 20.19.5 project, and verifies their CommonJS,
+ES module, command-line, and type entry points. The clean TypeScript project is
+also built with both Webpack and Rspack, and the resulting Node.js bundles are
+executed on Node.js 20.19.5.
+
+Browser runtime compatibility is defined by the browser build targets rather than
+the Node.js version used to maintain this repository. Node.js 20 compatibility is
+provided for legacy consumers even though Node.js 20 no longer receives upstream
+maintenance.
+

## 🙌 Code of Conduct
diff --git a/apps/3000-home/cypress/tsconfig.json b/apps/3000-home/cypress/tsconfig.json
index 0285a6e4c66..2a308b20964 100644
--- a/apps/3000-home/cypress/tsconfig.json
+++ b/apps/3000-home/cypress/tsconfig.json
@@ -2,7 +2,6 @@
"extends": "../tsconfig.json",
"compilerOptions": {
"allowJs": true,
- "outDir": "../../dist/out-tsc",
"module": "commonjs",
"types": ["cypress", "node"],
"sourceMap": false
diff --git a/apps/3001-shop/cypress/tsconfig.json b/apps/3001-shop/cypress/tsconfig.json
index 0285a6e4c66..2a308b20964 100644
--- a/apps/3001-shop/cypress/tsconfig.json
+++ b/apps/3001-shop/cypress/tsconfig.json
@@ -2,7 +2,6 @@
"extends": "../tsconfig.json",
"compilerOptions": {
"allowJs": true,
- "outDir": "../../dist/out-tsc",
"module": "commonjs",
"types": ["cypress", "node"],
"sourceMap": false
diff --git a/apps/3002-checkout/cypress/tsconfig.json b/apps/3002-checkout/cypress/tsconfig.json
index 0285a6e4c66..2a308b20964 100644
--- a/apps/3002-checkout/cypress/tsconfig.json
+++ b/apps/3002-checkout/cypress/tsconfig.json
@@ -2,7 +2,6 @@
"extends": "../tsconfig.json",
"compilerOptions": {
"allowJs": true,
- "outDir": "../../dist/out-tsc",
"module": "commonjs",
"types": ["cypress", "node"],
"sourceMap": false
diff --git a/apps/bridge-ssr-demo/.gitignore b/apps/bridge-ssr-demo/.gitignore
new file mode 100644
index 00000000000..a7fe3c62b30
--- /dev/null
+++ b/apps/bridge-ssr-demo/.gitignore
@@ -0,0 +1,3 @@
+**/dist-vite/
+**/test-results/
+**/playwright-report/
diff --git a/apps/bridge-ssr-demo/README.md b/apps/bridge-ssr-demo/README.md
new file mode 100644
index 00000000000..0cade160d2e
--- /dev/null
+++ b/apps/bridge-ssr-demo/README.md
@@ -0,0 +1,24 @@
+# Bridge SSR V1 acceptance demo
+
+This focused demo proves buffered, application-level Bridge SSR in both cross-framework directions:
+
+- React 18 host → Vue 3 remote
+- Vue 3 host → React 18 remote
+
+The server host calls `renderRemoteBridge` and passes the complete result to its remote component. That component emits a single instance-scoped document slot which owns the remote HTML and dehydrated state. The host hydration JSON contains only a versioned identity reference, so the remote HTML is transferred once.
+
+Before React `hydrateRoot` or the Vue host mount, the client creates a hydration registry from the document and installs it through the framework adapter. The reference resolves a validated snapshot from that registry. Successful hydration consumes the snapshot once; a later SPA revisit uses CSR. A missing slot also uses CSR, while malformed, duplicate, or identity-conflicting slots surface contextual errors.
+
+Client and Node builds expose the same `./export-app` key from different source files. Preparation and the framework server renderer stay in the Node graph; hydration mapping stays browser-safe.
+
+Run from the repository root:
+
+```bash
+pnpm run e2e:bridge:ssr
+pnpm run e2e:bridge:ssr:production
+pnpm run e2e:bridge:ssr:vite-smoke
+```
+
+Rsbuild development and production are required. The Vite command is a non-blocking React-host → Vue-remote smoke proof. The suites cover raw HTML, single-copy transport, hydration, deep routes, host and remote navigation, CSR revisits after consumption, multiple independent instances, browser/server error monitoring, and exclusion of server-only renderers and preparation from browser bundles.
+
+React Router v6 remotes use the strict `router-v6` alias in browser and Node builds; the browser bundle checks are release gates, not documentation-only guidance. Assets and head management, response status/headers, shared fallbacks and timeouts, data routers, streaming, and additional framework adapters are intentionally outside V1. Without a host-owned wrapper, a slow or broken remote can delay or fail the host response.
diff --git a/apps/bridge-ssr-demo/host-vue/package.json b/apps/bridge-ssr-demo/host-vue/package.json
new file mode 100644
index 00000000000..50dd19c626d
--- /dev/null
+++ b/apps/bridge-ssr-demo/host-vue/package.json
@@ -0,0 +1,31 @@
+{
+ "name": "bridge-ssr-host-vue",
+ "private": true,
+ "version": "0.0.1",
+ "scripts": {
+ "dev": "NODE_OPTIONS=--experimental-vm-modules node ./server.mjs",
+ "build": "rsbuild build",
+ "serve:production": "NODE_OPTIONS=--experimental-vm-modules BRIDGE_SSR_MODE=production node ./server.mjs",
+ "e2e:ssr:ci": "node ../../../scripts/ensure-playwright.js && playwright test --config playwright.config.ts --reporter=line"
+ },
+ "dependencies": {
+ "@module-federation/bridge-react": "workspace:*",
+ "@module-federation/bridge-shared": "workspace:*",
+ "@module-federation/bridge-vue3": "workspace:*",
+ "@module-federation/enhanced": "workspace:*",
+ "@module-federation/runtime": "workspace:*",
+ "@vue/server-renderer": "^3.5.30",
+ "express": "^4.21.2",
+ "react": "18.3.1",
+ "react-dom": "18.3.1",
+ "vue": "^3.5.30",
+ "vue-router": "4.4.5"
+ },
+ "devDependencies": {
+ "@module-federation/rsbuild-plugin": "workspace:*",
+ "@playwright/test": "1.57.0",
+ "@rsbuild/core": "1.7.3",
+ "@rsbuild/plugin-vue": "^1.2.6",
+ "typescript": "~5.0.4"
+ }
+}
diff --git a/apps/bridge-ssr-demo/host-vue/playwright.config.ts b/apps/bridge-ssr-demo/host-vue/playwright.config.ts
new file mode 100644
index 00000000000..db2de20c99a
--- /dev/null
+++ b/apps/bridge-ssr-demo/host-vue/playwright.config.ts
@@ -0,0 +1,25 @@
+import { defineConfig, devices } from '@playwright/test';
+
+export default defineConfig({
+ testDir: '../host/e2e',
+ timeout: 60_000,
+ fullyParallel: false,
+ retries: process.env.CI ? 2 : 0,
+ workers: 1,
+ use: {
+ baseURL: process.env.BRIDGE_SSR_VUE_BASE_URL ?? 'http://localhost:2303',
+ trace: 'on-first-retry',
+ ...devices['Desktop Chrome'],
+ },
+ projects: [
+ {
+ name: 'vue-host-nojs',
+ testMatch: /bridge-ssr-nojs\.spec\.ts/,
+ use: { javaScriptEnabled: false },
+ },
+ {
+ name: 'vue-host-browser',
+ testMatch: /bridge-ssr-browser\.spec\.ts/,
+ },
+ ],
+});
diff --git a/apps/bridge-ssr-demo/host-vue/rsbuild.config.ts b/apps/bridge-ssr-demo/host-vue/rsbuild.config.ts
new file mode 100644
index 00000000000..318329e9621
--- /dev/null
+++ b/apps/bridge-ssr-demo/host-vue/rsbuild.config.ts
@@ -0,0 +1,49 @@
+import { defineConfig } from '@rsbuild/core';
+import { pluginVue } from '@rsbuild/plugin-vue';
+import { pluginModuleFederation } from '@module-federation/rsbuild-plugin';
+import {
+ bridgeSsrHostUrl,
+ bridgeSsrRemotePort,
+ bridgeSsrServerManifestPath,
+} from '../shared/devHost';
+
+const reactOrigin = bridgeSsrHostUrl(bridgeSsrRemotePort('react'));
+const webRemotes = {
+ bridge_ssr_react: `bridge_ssr_react@${reactOrigin}/mf-manifest.json`,
+};
+const ssrRemotes = {
+ bridge_ssr_react: `bridge_ssr_react@${reactOrigin}/${bridgeSsrServerManifestPath}`,
+};
+const shared = {
+ vue: { singleton: true, eager: true },
+ 'vue-router': { singleton: true, eager: true },
+ react: { singleton: true, eager: true },
+ 'react-dom': { singleton: true, eager: true },
+};
+
+export default defineConfig({
+ performance: { buildCache: false },
+ server: { port: 2303, cors: true, middlewareMode: true },
+ environments: {
+ client: {
+ source: { entry: { index: './src/entry.client.ts' } },
+ html: { template: './src/index.html' },
+ output: { distPath: { root: 'dist' } },
+ },
+ ssr: {
+ source: { entry: { index: './src/entry.server.ts' } },
+ output: { target: 'node', distPath: { root: 'dist/ssr' } },
+ },
+ },
+ plugins: [
+ pluginVue(),
+ pluginModuleFederation(
+ { name: 'bridge_ssr_vue_host', remotes: webRemotes, shared },
+ { environment: 'client' },
+ ),
+ pluginModuleFederation(
+ { name: 'bridge_ssr_vue_host', remotes: ssrRemotes, shared },
+ { target: 'node', environment: 'ssr' },
+ ),
+ ],
+});
diff --git a/apps/bridge-ssr-demo/host-vue/server.mjs b/apps/bridge-ssr-demo/host-vue/server.mjs
new file mode 100644
index 00000000000..69bf42e65b0
--- /dev/null
+++ b/apps/bridge-ssr-demo/host-vue/server.mjs
@@ -0,0 +1,11 @@
+import path from 'node:path';
+import { fileURLToPath } from 'node:url';
+import { startBridgeHost } from '../shared/createHostServer.mjs';
+
+const rootDir = path.dirname(fileURLToPath(import.meta.url));
+startBridgeHost({ rootDir, port: 2303, label: 'bridge-ssr-vue-host' }).catch(
+ (error) => {
+ console.error('[bridge-ssr-vue-host] failed:', error);
+ process.exitCode = 1;
+ },
+);
diff --git a/apps/bridge-ssr-demo/host-vue/src/App.vue b/apps/bridge-ssr-demo/host-vue/src/App.vue
new file mode 100644
index 00000000000..7df3e1cc223
--- /dev/null
+++ b/apps/bridge-ssr-demo/host-vue/src/App.vue
@@ -0,0 +1,76 @@
+
+
+
+ Vue outer host consuming a React application remote.React Remote Pair
+ React Remote
+ Bridge SSR Host
+
SSR detail page content
+test prop: {{ test }}
+ +