From cc42aca9852ec558e999b5655896555b36df29e3 Mon Sep 17 00:00:00 2001 From: 2heal1 Date: Tue, 30 Sep 2025 17:44:45 +0800 Subject: [PATCH 1/2] feat(mf): support lazy compilation --- .../sharing/consume_shared_runtime_module.rs | 36 +++++++---- packages/rspack/package.json | 2 +- .../src/container/ModuleFederationPlugin.ts | 19 ++++++ .../runtime/moduleFederationDefaultRuntime.js | 14 +++++ pnpm-lock.yaml | 47 +++++++++++++- .../module-federation/index.test.ts | 19 ++++++ .../module-federation/rspack.config.js | 63 +++++++++++++++++++ .../module-federation/runtimePlugin.js | 15 +++++ .../module-federation/src/bootstrap.jsx | 14 +++++ .../module-federation/src/index.html | 12 ++++ .../module-federation/src/index.jsx | 1 + .../module-federation/src/lazy-entry.js | 3 + .../module-federation/src/remote-entry.js | 3 + .../module-federation/src/share-entry.js | 28 +++++++++ tests/e2e/playwright.config.ts | 4 +- 15 files changed, 265 insertions(+), 15 deletions(-) create mode 100644 tests/e2e/cases/lazy-compilation/module-federation/index.test.ts create mode 100644 tests/e2e/cases/lazy-compilation/module-federation/rspack.config.js create mode 100644 tests/e2e/cases/lazy-compilation/module-federation/runtimePlugin.js create mode 100644 tests/e2e/cases/lazy-compilation/module-federation/src/bootstrap.jsx create mode 100644 tests/e2e/cases/lazy-compilation/module-federation/src/index.html create mode 100644 tests/e2e/cases/lazy-compilation/module-federation/src/index.jsx create mode 100644 tests/e2e/cases/lazy-compilation/module-federation/src/lazy-entry.js create mode 100644 tests/e2e/cases/lazy-compilation/module-federation/src/remote-entry.js create mode 100644 tests/e2e/cases/lazy-compilation/module-federation/src/share-entry.js diff --git a/crates/rspack_plugin_mf/src/sharing/consume_shared_runtime_module.rs b/crates/rspack_plugin_mf/src/sharing/consume_shared_runtime_module.rs index a3ee23e9e0cb..081004d6f7ec 100644 --- a/crates/rspack_plugin_mf/src/sharing/consume_shared_runtime_module.rs +++ b/crates/rspack_plugin_mf/src/sharing/consume_shared_runtime_module.rs @@ -104,21 +104,35 @@ impl RuntimeModule for ConsumeSharedRuntimeModule { add_module(mid, chunk, &mut initial_consumes); } } - if module_id_to_consume_data_mapping.is_empty() { - return Ok("".to_string()); - } - let module_id_to_consume_data_mapping = module_id_to_consume_data_mapping - .into_iter() - .map(|(k, v)| format!("{}: {}", json_stringify(&k), v)) - .collect::>() - .join(", "); + let module_id_to_consume_data_mapping = if module_id_to_consume_data_mapping.is_empty() { + "{}".to_string() + } else { + format!( + "{{{}}}", + module_id_to_consume_data_mapping + .into_iter() + .map(|(k, v)| format!("{}: {}", json_stringify(&k), v)) + .collect::>() + .join(", ") + ) + }; + let chunk_mapping = if chunk_to_module_mapping.is_empty() { + "{}".to_string() + } else { + json_stringify(&chunk_to_module_mapping) + }; + let initial_consumes_json = if initial_consumes.is_empty() { + "[]".to_string() + } else { + json_stringify(&initial_consumes) + }; let mut source = format!( r#" -__webpack_require__.consumesLoadingData = {{ chunkMapping: {chunk_mapping}, moduleIdToConsumeDataMapping: {{ {module_to_consume_data_mapping} }}, initialConsumes: {initial_consumes} }}; +__webpack_require__.consumesLoadingData = {{ chunkMapping: {chunk_mapping}, moduleIdToConsumeDataMapping: {module_to_consume_data_mapping}, initialConsumes: {initial_consumes_json} }}; "#, - chunk_mapping = json_stringify(&chunk_to_module_mapping), + chunk_mapping = chunk_mapping, module_to_consume_data_mapping = module_id_to_consume_data_mapping, - initial_consumes = json_stringify(&initial_consumes), + initial_consumes_json = initial_consumes_json, ); if self.enhanced { if ChunkGraph::get_chunk_runtime_requirements(compilation, &chunk_ukey) diff --git a/packages/rspack/package.json b/packages/rspack/package.json index 2bc46a1382d7..228a137bcef7 100644 --- a/packages/rspack/package.json +++ b/packages/rspack/package.json @@ -67,7 +67,7 @@ "zod-validation-error": "3.5.3" }, "dependencies": { - "@module-federation/runtime-tools": "0.18.0", + "@module-federation/runtime-tools": "0.0.0-fix-lazy-comile-20250930092757", "@rspack/binding": "workspace:*", "@rspack/lite-tapable": "1.0.1" }, diff --git a/packages/rspack/src/container/ModuleFederationPlugin.ts b/packages/rspack/src/container/ModuleFederationPlugin.ts index ece2a0e68ac1..3aea9aa3aaee 100644 --- a/packages/rspack/src/container/ModuleFederationPlugin.ts +++ b/packages/rspack/src/container/ModuleFederationPlugin.ts @@ -1,5 +1,6 @@ import type { Compiler } from "../Compiler"; import type { ExternalsType } from "../config"; +import { RuntimeGlobals } from "../RuntimeGlobals"; import { getExternalsTypeSchema } from "../schema/config"; import { isValidate } from "../schema/validate"; import type { ModuleFederationPluginV1Options } from "./ModuleFederationPluginV1"; @@ -40,6 +41,24 @@ export class ModuleFederationPlugin { ...this._options, enhanced: true }).apply(compiler); + + // inject ensureChunkHandlers if enable lazy compilation + if (compiler.options.lazyCompilation && this._options.remotes) { + compiler.hooks.thisCompilation.tap( + "CoreModuleFederationPlugin", + compilation => { + compilation.hooks.additionalTreeRuntimeRequirements.tap( + "CoreModuleFederationPlugin", + (chunk, set) => { + if (chunk.hasRuntime()) { + set.add(RuntimeGlobals.ensureChunk); + set.add(RuntimeGlobals.ensureChunkHandlers); + } + } + ); + } + ); + } } } diff --git a/packages/rspack/src/runtime/moduleFederationDefaultRuntime.js b/packages/rspack/src/runtime/moduleFederationDefaultRuntime.js index d01823e98a50..8977b644453b 100644 --- a/packages/rspack/src/runtime/moduleFederationDefaultRuntime.js +++ b/packages/rspack/src/runtime/moduleFederationDefaultRuntime.js @@ -159,6 +159,11 @@ module.exports = function () { "chunkMapping", () => remotesLoadingChunkMapping ); + early( + __webpack_require__.federation.bundlerRuntimeOptions.remotes, + "remoteInfos", + () => __module_federation_remote_infos__ + ); early( __webpack_require__.federation.bundlerRuntimeOptions.remotes, "idToExternalAndNameMapping", @@ -207,6 +212,12 @@ module.exports = function () { __webpack_require__.federation.attachShareScopeMap(__webpack_require__); } + if (!__webpack_require__.f) { + __webpack_require__.f = {}; + } + if (!__webpack_require__.f.remotes) { + __webpack_require__.f.remotes = function () {}; + } override(__webpack_require__.f, "remotes", (chunkId, promises) => __webpack_require__.federation.bundlerRuntime.remotes({ chunkId, @@ -221,6 +232,9 @@ module.exports = function () { webpackRequire: __webpack_require__ }) ); + if (!__webpack_require__.f.consumes) { + __webpack_require__.f.consumes = function () {}; + } override(__webpack_require__.f, "consumes", (chunkId, promises) => __webpack_require__.federation.bundlerRuntime.consumes({ chunkId, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 348af64cb245..982df5488413 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -314,8 +314,8 @@ importers: packages/rspack: dependencies: '@module-federation/runtime-tools': - specifier: 0.18.0 - version: 0.18.0 + specifier: 0.0.0-fix-lazy-comile-20250930092757 + version: 0.0.0-fix-lazy-comile-20250930092757 '@rspack/binding': specifier: workspace:* version: link:../../crates/node_binding @@ -2525,21 +2525,39 @@ packages: '@microsoft/tsdoc@0.15.1': resolution: {integrity: sha512-4aErSrCR/On/e5G2hDP0wjooqDdauzEbIq8hIkIe5pXV0rtWJZvdCEKL0ykZxex+IxIwBp0eGeV48hQN07dXtw==} + '@module-federation/error-codes@0.0.0-fix-lazy-comile-20250930092757': + resolution: {integrity: sha512-LH64OXr9XQQvVgH08PtK0mhd22qXP6dxhyeaFy0C/r1DiJntz/sJeOIaq4q8SbX0Vbs4ocd2qRmmQn36dmEYOg==} + '@module-federation/error-codes@0.18.0': resolution: {integrity: sha512-Woonm8ehyVIUPXChmbu80Zj6uJkC0dD9SJUZ/wOPtO8iiz/m+dkrOugAuKgoiR6qH4F+yorWila954tBz4uKsQ==} + '@module-federation/runtime-core@0.0.0-fix-lazy-comile-20250930092757': + resolution: {integrity: sha512-eAeSPrwbbq4FNQYcb2yRMBMSDGuY++2G0LWKSiyCZLn1v84Tu056YACZWXM4NOqvwLeEJCYsE1auhCllHfaNJA==} + '@module-federation/runtime-core@0.18.0': resolution: {integrity: sha512-ZyYhrDyVAhUzriOsVfgL6vwd+5ebYm595Y13KeMf6TKDRoUHBMTLGQ8WM4TDj8JNsy7LigncK8C03fn97of0QQ==} + '@module-federation/runtime-tools@0.0.0-fix-lazy-comile-20250930092757': + resolution: {integrity: sha512-BYaabAPlQ0IJl4iQ+lSFUCzaXM+zvWR0UsraW5OeJZNkT6LgdIOYpeqWsAmbUkpMFNQ25ay0OCUfFhGsbInGRA==} + '@module-federation/runtime-tools@0.18.0': resolution: {integrity: sha512-fSga9o4t1UfXNV/Kh6qFvRyZpPp3EHSPRISNeyT8ZoTpzDNiYzhtw0BPUSSD8m6C6XQh2s/11rI4g80UY+d+hA==} + '@module-federation/runtime@0.0.0-fix-lazy-comile-20250930092757': + resolution: {integrity: sha512-qmejl5ext2RhejMXxSg8KMRY2dCy9wXnM/bOBMMG6Qatc2Ws2bAWFiYO0/FOJGpWMPcHuHJG0DNh3Afg+T+CdQ==} + '@module-federation/runtime@0.18.0': resolution: {integrity: sha512-+C4YtoSztM7nHwNyZl6dQKGUVJdsPrUdaf3HIKReg/GQbrt9uvOlUWo2NXMZ8vDAnf/QRrpSYAwXHmWDn9Obaw==} + '@module-federation/sdk@0.0.0-fix-lazy-comile-20250930092757': + resolution: {integrity: sha512-9Ak3jYZQb3sj5di5XNLmQm7B+yr16gWanqDpReeMgw0b63hYCc/6A/1kOvIsPSECthRPA2qB4pFUYWBigzhn5w==} + '@module-federation/sdk@0.18.0': resolution: {integrity: sha512-Lo/Feq73tO2unjmpRfyyoUkTVoejhItXOk/h5C+4cistnHbTV8XHrW/13fD5e1Iu60heVdAhhelJd6F898Ve9A==} + '@module-federation/webpack-bundler-runtime@0.0.0-fix-lazy-comile-20250930092757': + resolution: {integrity: sha512-x/4qRE+lnl2PO58zyQ9Q8CabAQyXWxioF+7dK/ebvtalf1nL1kgE7dHZn7rBfyVt4wQVbcrh1uS6rkTc4kyvGA==} + '@module-federation/webpack-bundler-runtime@0.18.0': resolution: {integrity: sha512-TEvErbF+YQ+6IFimhUYKK3a5wapD90d90sLsNpcu2kB3QGT7t4nIluE25duXuZDVUKLz86tEPrza/oaaCWTpvQ==} @@ -10397,26 +10415,51 @@ snapshots: '@microsoft/tsdoc@0.15.1': {} + '@module-federation/error-codes@0.0.0-fix-lazy-comile-20250930092757': {} + '@module-federation/error-codes@0.18.0': {} + '@module-federation/runtime-core@0.0.0-fix-lazy-comile-20250930092757': + dependencies: + '@module-federation/error-codes': 0.0.0-fix-lazy-comile-20250930092757 + '@module-federation/sdk': 0.0.0-fix-lazy-comile-20250930092757 + '@module-federation/runtime-core@0.18.0': dependencies: '@module-federation/error-codes': 0.18.0 '@module-federation/sdk': 0.18.0 + '@module-federation/runtime-tools@0.0.0-fix-lazy-comile-20250930092757': + dependencies: + '@module-federation/runtime': 0.0.0-fix-lazy-comile-20250930092757 + '@module-federation/webpack-bundler-runtime': 0.0.0-fix-lazy-comile-20250930092757 + '@module-federation/runtime-tools@0.18.0': dependencies: '@module-federation/runtime': 0.18.0 '@module-federation/webpack-bundler-runtime': 0.18.0 + '@module-federation/runtime@0.0.0-fix-lazy-comile-20250930092757': + dependencies: + '@module-federation/error-codes': 0.0.0-fix-lazy-comile-20250930092757 + '@module-federation/runtime-core': 0.0.0-fix-lazy-comile-20250930092757 + '@module-federation/sdk': 0.0.0-fix-lazy-comile-20250930092757 + '@module-federation/runtime@0.18.0': dependencies: '@module-federation/error-codes': 0.18.0 '@module-federation/runtime-core': 0.18.0 '@module-federation/sdk': 0.18.0 + '@module-federation/sdk@0.0.0-fix-lazy-comile-20250930092757': {} + '@module-federation/sdk@0.18.0': {} + '@module-federation/webpack-bundler-runtime@0.0.0-fix-lazy-comile-20250930092757': + dependencies: + '@module-federation/runtime': 0.0.0-fix-lazy-comile-20250930092757 + '@module-federation/sdk': 0.0.0-fix-lazy-comile-20250930092757 + '@module-federation/webpack-bundler-runtime@0.18.0': dependencies: '@module-federation/runtime': 0.18.0 diff --git a/tests/e2e/cases/lazy-compilation/module-federation/index.test.ts b/tests/e2e/cases/lazy-compilation/module-federation/index.test.ts new file mode 100644 index 000000000000..447ef0a3b86b --- /dev/null +++ b/tests/e2e/cases/lazy-compilation/module-federation/index.test.ts @@ -0,0 +1,19 @@ +import { expect, test } from "@/fixtures"; + +test("should load remote and shared success", async ({ page }) => { + // Click the button that triggers dynamic import + await page.waitForSelector('button:has-text("Click me")'); + await page.getByText("Click me").click(); + + // Wait for the component to appear with a more reliable wait + await page.waitForSelector('div:has-text("RemoteComponent")'); + + // Check that the component was loaded and displayed + const RemoteComponentCount = await page.getByText("RemoteComponent").count(); + expect(RemoteComponentCount).toBe(1); + + + // Check that the shared component was loaded and displayed + const SharedReactCount = await page.getByText("SharedReact").count(); + expect(SharedReactCount).toBe(1); +}); diff --git a/tests/e2e/cases/lazy-compilation/module-federation/rspack.config.js b/tests/e2e/cases/lazy-compilation/module-federation/rspack.config.js new file mode 100644 index 000000000000..e5e9036bb97f --- /dev/null +++ b/tests/e2e/cases/lazy-compilation/module-federation/rspack.config.js @@ -0,0 +1,63 @@ +const { rspack } = require("@rspack/core"); +const path = require('path'); +const ReactRefreshPlugin = require("@rspack/plugin-react-refresh"); + +/** @type { import('@rspack/core').RspackOptions } */ +module.exports = { + context: __dirname, + entry: "./src/index.jsx", + mode: "development", + devtool:false, + resolve: { + extensions: ["...", ".jsx"] + }, + module: { + rules: [ + { + test: /\.(jsx?|tsx?)$/, + use: [ + { + loader: "builtin:swc-loader", + options: { + jsc: { + parser: { + syntax: "typescript", + tsx: true + }, + transform: { + react: { + runtime: "automatic", + development: true, + refresh: true, + } + }, + }, + } + }, + ] + } + ] + }, + plugins: [new rspack.HtmlRspackPlugin({ template: "./src/index.html" }), new rspack.container.ModuleFederationPlugin({ + name:"host", + remotes: { + remote: "remote@http://localhost:5679/remoteEntry.js" + }, + shared: { + react: {}, + 'react-dom': {} + }, + runtimePlugins: [require.resolve('./runtimePlugin.js')] + }), + new ReactRefreshPlugin(), + +], + lazyCompilation:true, + devServer: { + hot: true, + port: 5678, + devMiddleware: { + writeToDisk: true + } + } +}; diff --git a/tests/e2e/cases/lazy-compilation/module-federation/runtimePlugin.js b/tests/e2e/cases/lazy-compilation/module-federation/runtimePlugin.js new file mode 100644 index 000000000000..82cc262c3d67 --- /dev/null +++ b/tests/e2e/cases/lazy-compilation/module-federation/runtimePlugin.js @@ -0,0 +1,15 @@ +module.exports = function () { + let component; + return { + name: 'proxy-remote', + async errorLoadRemote() { + if(!component){ + component = document.createElement("div"); + component.textContent = "RemoteComponent"; + document.body.appendChild(component); + } + + return ()=>component; + }, + }; +} diff --git a/tests/e2e/cases/lazy-compilation/module-federation/src/bootstrap.jsx b/tests/e2e/cases/lazy-compilation/module-federation/src/bootstrap.jsx new file mode 100644 index 000000000000..825ffda24a18 --- /dev/null +++ b/tests/e2e/cases/lazy-compilation/module-federation/src/bootstrap.jsx @@ -0,0 +1,14 @@ +import React from "react"; +import ReactDOM from "react-dom/client"; + +ReactDOM.createRoot(document.getElementById("root")).render( + + + +); + diff --git a/tests/e2e/cases/lazy-compilation/module-federation/src/index.html b/tests/e2e/cases/lazy-compilation/module-federation/src/index.html new file mode 100644 index 000000000000..127a457455b3 --- /dev/null +++ b/tests/e2e/cases/lazy-compilation/module-federation/src/index.html @@ -0,0 +1,12 @@ + + + + + + + Document + + +
+ + diff --git a/tests/e2e/cases/lazy-compilation/module-federation/src/index.jsx b/tests/e2e/cases/lazy-compilation/module-federation/src/index.jsx new file mode 100644 index 000000000000..a9d4f0f546f4 --- /dev/null +++ b/tests/e2e/cases/lazy-compilation/module-federation/src/index.jsx @@ -0,0 +1 @@ +import('./bootstrap.jsx') \ No newline at end of file diff --git a/tests/e2e/cases/lazy-compilation/module-federation/src/lazy-entry.js b/tests/e2e/cases/lazy-compilation/module-federation/src/lazy-entry.js new file mode 100644 index 000000000000..649a7571d706 --- /dev/null +++ b/tests/e2e/cases/lazy-compilation/module-federation/src/lazy-entry.js @@ -0,0 +1,3 @@ +const component = document.createElement("div"); +component.textContent = "LazyEntryComponent"; +document.body.appendChild(component); diff --git a/tests/e2e/cases/lazy-compilation/module-federation/src/remote-entry.js b/tests/e2e/cases/lazy-compilation/module-federation/src/remote-entry.js new file mode 100644 index 000000000000..382e600a24ea --- /dev/null +++ b/tests/e2e/cases/lazy-compilation/module-federation/src/remote-entry.js @@ -0,0 +1,3 @@ + import("remote").then(() => { + console.log("remote loaded"); + }); diff --git a/tests/e2e/cases/lazy-compilation/module-federation/src/share-entry.js b/tests/e2e/cases/lazy-compilation/module-federation/src/share-entry.js new file mode 100644 index 000000000000..a7e337968bf7 --- /dev/null +++ b/tests/e2e/cases/lazy-compilation/module-federation/src/share-entry.js @@ -0,0 +1,28 @@ + +// 挂载 React 组件的函数 +function mountReactComponent() { + Promise.all([ + import('react'), + import('react-dom/client') + ]).then(([React, ReactDOM]) => { + // 创建 React 组件 + const App = () => { + return React.createElement('div', { + style: { + padding: '20px', + border: '1px solid #ccc', + borderRadius: '8px', + margin: '10px 0' + } + }, [ + React.createElement('h2', { key: 'title' }, 'SharedReact Component'), + ]); + }; + + // 获取挂载点并渲染组件 + const root = ReactDOM.createRoot(document.getElementById('root')); + root.render(React.createElement(App)); + }); +} + +mountReactComponent() diff --git a/tests/e2e/playwright.config.ts b/tests/e2e/playwright.config.ts index f5146435b134..edb3e385c7f6 100644 --- a/tests/e2e/playwright.config.ts +++ b/tests/e2e/playwright.config.ts @@ -14,7 +14,9 @@ export default defineConfig({ // Fail the build on CI if you accidentally left test.only in the source code. forbidOnly: !!process.env.CI, - + build: { + external: ['**/moduleFederationDefaultRuntime.js'] + }, retries: 0, // timeout 30s From 5606ecdf9b6c02768e7bae1071ce202de0db1af7 Mon Sep 17 00:00:00 2001 From: 2heal1 Date: Tue, 30 Sep 2025 17:50:40 +0800 Subject: [PATCH 2/2] chore: no need to create fallback --- .../rspack/src/runtime/moduleFederationDefaultRuntime.js | 9 --------- 1 file changed, 9 deletions(-) diff --git a/packages/rspack/src/runtime/moduleFederationDefaultRuntime.js b/packages/rspack/src/runtime/moduleFederationDefaultRuntime.js index 8977b644453b..a19796cf01ab 100644 --- a/packages/rspack/src/runtime/moduleFederationDefaultRuntime.js +++ b/packages/rspack/src/runtime/moduleFederationDefaultRuntime.js @@ -212,12 +212,6 @@ module.exports = function () { __webpack_require__.federation.attachShareScopeMap(__webpack_require__); } - if (!__webpack_require__.f) { - __webpack_require__.f = {}; - } - if (!__webpack_require__.f.remotes) { - __webpack_require__.f.remotes = function () {}; - } override(__webpack_require__.f, "remotes", (chunkId, promises) => __webpack_require__.federation.bundlerRuntime.remotes({ chunkId, @@ -232,9 +226,6 @@ module.exports = function () { webpackRequire: __webpack_require__ }) ); - if (!__webpack_require__.f.consumes) { - __webpack_require__.f.consumes = function () {}; - } override(__webpack_require__.f, "consumes", (chunkId, promises) => __webpack_require__.federation.bundlerRuntime.consumes({ chunkId,