Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
db13cf6
fix(retry-plugin): fix ESM remote entry retry (#4877)
gioboa Aug 3, 2026
45b01e1
docs: add ESM retry cache-busting defaults (#4951)
gioboa Aug 3, 2026
179caf4
fix(bridge-react): dedupe SSR stylesheet assets (#4852)
BleedingDev Aug 4, 2026
53d7e43
chore(deps): bump undici from 7.28.0 to 7.29.0 (#4958)
dependabot[bot] Aug 6, 2026
b022032
Release v2.8.2 (#4967)
2heal1 Aug 6, 2026
60e6b82
feat(rstest): add federation integration plugin (#4920)
ScriptedAlchemy Aug 10, 2026
d113174
chore(deps-dev): bump js-yaml from 4.3.0 to 4.3.1 (#4979)
dependabot[bot] Aug 10, 2026
0ebae56
fix(rstest): coordinate Node configuration ownership (#4980)
ScriptedAlchemy Aug 11, 2026
b171954
chore(deps): bump nanoid from 5.1.6 to 5.1.16 (#4977)
dependabot[bot] Aug 11, 2026
0e948e3
chore(deps): bump hono from 4.12.26 to 4.12.34 (#4957)
dependabot[bot] Aug 11, 2026
1710313
chore(deps-dev): bump postcss from 8.5.10 to 8.5.23 (#4964)
dependabot[bot] Aug 11, 2026
219315c
chore(deps): bump nanoid from 5.1.6 to 5.1.16 in /packages/enhanced/t…
dependabot[bot] Aug 11, 2026
a1109da
chore(deps): bump @remix-run/router from 1.23.2 to 1.23.3 in /package…
dependabot[bot] Aug 11, 2026
2a1c63f
chore(deps): bump json from 2.19.4 to 2.19.9 in /apps/metro-example-h…
dependabot[bot] Aug 11, 2026
6eb5bcd
chore(deps): bump react-router from 7.16.0 to 7.18.2 (#4936)
dependabot[bot] Aug 11, 2026
f06303a
chore(deps): bump sharp from 0.34.5 to 0.35.0 (#4932)
dependabot[bot] Aug 11, 2026
f11afc0
feat(bridge): add server-rendered bridge islands
tonoizer Jul 18, 2026
6bc0087
fix(bridge): harden SSR hydration boundaries
tonoizer Jul 18, 2026
c264ea6
fix(bridge): hydrate SSR islands from document slots
tonoizer Jul 18, 2026
d7abbd0
fix(bridge): harden remote application lifecycles
tonoizer Jul 18, 2026
8fc3661
docs(bridge): finalize SSR V1 contract and release metadata
tonoizer Jul 18, 2026
1aa90d9
fix(bridge): harden SSR V1 identity and lifecycle boundaries
cursoragent Jul 18, 2026
90c2a5f
fix(bridge): invalidate unconsumed SSR hydration snapshots
tonoizer Jul 18, 2026
7fee860
fix(bridge): destroy remote root once when render settles after unmount
tonoizer Aug 11, 2026
dc506b9
chore(bridge): trim nonessential SSR V1 demo and surface noise
tonoizer Aug 11, 2026
f887cac
fix(bridge-vue3): derive SSR identity reactively in RemoteApp
tonoizer Aug 11, 2026
aeff317
fix(bridge): flush SSR teardown before remount and CSR fallback
tonoizer Aug 11, 2026
e3dbd4c
fix(bridge): restore prettierignore lines and invalidate Vue SSR clears
tonoizer Aug 11, 2026
419182c
fix(bridge-react): abort stale SSR jobs on in-place CSR mount swap
tonoizer Aug 11, 2026
d1567be
fix(bridge-react): ignore stale abort destroy after providerInfo remount
tonoizer Aug 11, 2026
088b3a4
fix(bridge-react): refresh forwarded mount refs
tonoizer Aug 11, 2026
2e8c9a5
fix(bridge): handle SSR identity lifecycle transitions
tonoizer Aug 16, 2026
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
  •  
  •  
  •  
7 changes: 7 additions & 0 deletions .changeset/bridge-ssr-v1.md
Original file line number Diff line number Diff line change
@@ -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
5 changes: 5 additions & 0 deletions .changeset/calm-cats-test.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@module-federation/rstest': patch
---

Add an Rstest integration that enables federation-aware Node test builds and can reuse an existing Rsbuild federation configuration.
1 change: 1 addition & 0 deletions .changeset/config.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
"@module-federation/modern-js-v3",
"@module-federation/retry-plugin",
"@module-federation/rsbuild-plugin",
"@module-federation/rstest",
"@module-federation/error-codes",
"@module-federation/inject-external-runtime-core-plugin",
"@module-federation/runtime-core",
Expand Down
6 changes: 6 additions & 0 deletions .changeset/rstest-legacy-node-defaults.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'@module-federation/rsbuild-plugin': patch
'@module-federation/rstest': patch
---

Coordinate Node configuration ownership between the Rsbuild and Rstest federation plugins while preserving Rstest's runtime chunk and split-chunk behavior.
5 changes: 5 additions & 0 deletions .changeset/tidy-ssr-library-name.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@module-federation/rsbuild-plugin': patch
---

Expose typed federation options to integrations, preserve Rstest-owned Node compiler settings, and keep bundler-derived library names in SSR builds.
2 changes: 1 addition & 1 deletion .eslintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"root": true,
"overrides": [
{
"files": ["*.ts", "*.tsx"],
"files": ["*.ts", "*.tsx", "*.mts", "*.cts"],
"extends": ["plugin:@typescript-eslint/recommended"],
"parser": "@typescript-eslint/parser",
"parserOptions": { "ecmaVersion": 2020, "sourceType": "module" },
Expand Down
6 changes: 6 additions & 0 deletions .github/workflows/build-and-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,12 @@ jobs:
uses: ./.github/workflows/e2e-node.yml
secrets: inherit

e2e-rstest:
needs: checkout-install
if: ${{ fromJSON(needs.checkout-install.outputs.e2e_suites).rstest }}
uses: ./.github/workflows/e2e-rstest.yml
secrets: inherit

e2e-next-dev:
needs: checkout-install
if: ${{ fromJSON(needs.checkout-install.outputs.e2e_suites).next }}
Expand Down
30 changes: 30 additions & 0 deletions .github/workflows/e2e-bridge-ssr.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
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: '24'
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
46 changes: 46 additions & 0 deletions .github/workflows/e2e-rstest.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
# .github/workflows/e2e-rstest.yml
name: E2E Rstest Federation

on:
workflow_call:

jobs:
e2e-rstest:
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: Export SKIP_DEVTOOLS_POSTINSTALL
run: echo "SKIP_DEVTOOLS_POSTINSTALL=true" >> "$GITHUB_ENV"

- 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: Build shared packages
run: pnpm run build:packages

- name: E2E Rstest Federation
run: pnpm exec turbo run e2e --filter=rstest-federation-host
3 changes: 3 additions & 0 deletions .prettierignore
Original file line number Diff line number Diff line change
Expand Up @@ -25,3 +25,6 @@ packages/assemble-release-plan/**

# Generated Chrome DevTools proxy asset
packages/chrome-devtools/src/vendor/basic-proxy-core.js

**/playwright-report/**
**/test-results/**
1 change: 1 addition & 0 deletions apps/bridge-ssr-demo/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
**/dist/
17 changes: 17 additions & 0 deletions apps/bridge-ssr-demo/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# Bridge SSR V1 acceptance demo

Rsbuild demo for application-level Bridge SSR in both directions:

- React 18 host → Vue 3 remote
- Vue 3 host → React 18 remote

Contract details: [Bridge SSR guide](../website-new/docs/en/guide/bridge/ssr.mdx).

Run from the repository root:

```bash
pnpm run e2e:bridge:ssr
pnpm run e2e:bridge:ssr:production
```

These suites cover no-JS HTML, single-copy transport, hydration, deep routes, navigation, CSR revisits after consumption, multiple instances, and browser-bundle exclusion of server-only renderers.
31 changes: 31 additions & 0 deletions apps/bridge-ssr-demo/host-vue/package.json
Original file line number Diff line number Diff line change
@@ -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"
}
}
25 changes: 25 additions & 0 deletions apps/bridge-ssr-demo/host-vue/playwright.config.ts
Original file line number Diff line number Diff line change
@@ -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/,
},
],
});
49 changes: 49 additions & 0 deletions apps/bridge-ssr-demo/host-vue/rsbuild.config.ts
Original file line number Diff line number Diff line change
@@ -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' },
),
],
});
11 changes: 11 additions & 0 deletions apps/bridge-ssr-demo/host-vue/server.mjs
Original file line number Diff line number Diff line change
@@ -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;
},
);
76 changes: 76 additions & 0 deletions apps/bridge-ssr-demo/host-vue/src/App.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
<script setup lang="ts">
import { computed, ref, watch } from 'vue';
import { RouterLink, useRoute } from 'vue-router';
import { RemoteReactApp } from './remoteApps';
import type { HostSSRContext } from './ssrContext';

const props = defineProps<{ ssrContext?: HostSSRContext }>();
const route = useRoute();
const locationUrl = computed(
() =>
`${route.path}${route.fullPath.includes('?') ? `?${route.fullPath.split('?')[1]}` : ''}`,
);
const seedPath = props.ssrContext?.url.split('?')[0].split('#')[0];
const seedConsumed = ref(false);

watch(
() => route.path,
(path) => {
if (seedPath && path !== seedPath) seedConsumed.value = true;
},
{ immediate: true },
);

const activeContext = computed(() => {
if (seedConsumed.value) return undefined;
return seedPath === route.path ? props.ssrContext : undefined;
});
</script>

<template>
<div class="bridge-ssr-host bridge-ssr-vue-host" :data-location="locationUrl">
<nav>
<RouterLink to="/">Home</RouterLink> |
<RouterLink class="host-react-remote-link" to="/react-remote"
>React Remote</RouterLink
>
|
<RouterLink to="/react-pair">React Pair</RouterLink>
</nav>

<main v-if="route.path.startsWith('/react-pair')">
<h2>React Remote Pair</h2>
<RemoteReactApp
module-name="bridge_ssr_react"
basename="/react-pair"
name="Left"
:age="1"
:ssr="activeContext?.reactPair?.[0]"
:instance-id="activeContext?.reactPair?.[0]?.instanceId"
/>
<RemoteReactApp
module-name="bridge_ssr_react"
basename="/react-pair"
name="Right"
:age="2"
:ssr="activeContext?.reactPair?.[1]"
:instance-id="activeContext?.reactPair?.[1]?.instanceId"
/>
</main>
<main v-else-if="route.path.startsWith('/react-remote')">
<h2>React Remote</h2>
<RemoteReactApp
module-name="bridge_ssr_react"
basename="/react-remote"
name="Ming"
:age="12"
:ssr="activeContext?.reactRemote"
:instance-id="activeContext?.reactRemote?.instanceId"
/>
</main>
<main v-else>
<h1>Bridge SSR Host</h1>
<p>Vue outer host consuming a React application remote.</p>
</main>
</div>
</template>
16 changes: 16 additions & 0 deletions apps/bridge-ssr-demo/host-vue/src/entry.client.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { createSSRApp } from 'vue';
import { createRouter, createWebHistory } from 'vue-router';
import { createBridgeHydrationRegistry } from '@module-federation/bridge-shared';
import { provideBridgeHydrationRegistry } from '@module-federation/bridge-vue3';
import App from './App.vue';
import { readHostSSRContext } from './ssrContext';

const router = createRouter({
history: createWebHistory(),
routes: [{ path: '/:pathMatch(.*)*', component: App }],
});
const app = createSSRApp(App, { ssrContext: readHostSSRContext() });
app.use(router);
provideBridgeHydrationRegistry(app, createBridgeHydrationRegistry(document));
await router.isReady();
app.mount('#root');
Loading
Loading