Skip to content

Commit cc42aca

Browse files
committed
feat(mf): support lazy compilation
1 parent f72a069 commit cc42aca

File tree

15 files changed

+265
-15
lines changed

15 files changed

+265
-15
lines changed

crates/rspack_plugin_mf/src/sharing/consume_shared_runtime_module.rs

Lines changed: 25 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -104,21 +104,35 @@ impl RuntimeModule for ConsumeSharedRuntimeModule {
104104
add_module(mid, chunk, &mut initial_consumes);
105105
}
106106
}
107-
if module_id_to_consume_data_mapping.is_empty() {
108-
return Ok("".to_string());
109-
}
110-
let module_id_to_consume_data_mapping = module_id_to_consume_data_mapping
111-
.into_iter()
112-
.map(|(k, v)| format!("{}: {}", json_stringify(&k), v))
113-
.collect::<Vec<_>>()
114-
.join(", ");
107+
let module_id_to_consume_data_mapping = if module_id_to_consume_data_mapping.is_empty() {
108+
"{}".to_string()
109+
} else {
110+
format!(
111+
"{{{}}}",
112+
module_id_to_consume_data_mapping
113+
.into_iter()
114+
.map(|(k, v)| format!("{}: {}", json_stringify(&k), v))
115+
.collect::<Vec<_>>()
116+
.join(", ")
117+
)
118+
};
119+
let chunk_mapping = if chunk_to_module_mapping.is_empty() {
120+
"{}".to_string()
121+
} else {
122+
json_stringify(&chunk_to_module_mapping)
123+
};
124+
let initial_consumes_json = if initial_consumes.is_empty() {
125+
"[]".to_string()
126+
} else {
127+
json_stringify(&initial_consumes)
128+
};
115129
let mut source = format!(
116130
r#"
117-
__webpack_require__.consumesLoadingData = {{ chunkMapping: {chunk_mapping}, moduleIdToConsumeDataMapping: {{ {module_to_consume_data_mapping} }}, initialConsumes: {initial_consumes} }};
131+
__webpack_require__.consumesLoadingData = {{ chunkMapping: {chunk_mapping}, moduleIdToConsumeDataMapping: {module_to_consume_data_mapping}, initialConsumes: {initial_consumes_json} }};
118132
"#,
119-
chunk_mapping = json_stringify(&chunk_to_module_mapping),
133+
chunk_mapping = chunk_mapping,
120134
module_to_consume_data_mapping = module_id_to_consume_data_mapping,
121-
initial_consumes = json_stringify(&initial_consumes),
135+
initial_consumes_json = initial_consumes_json,
122136
);
123137
if self.enhanced {
124138
if ChunkGraph::get_chunk_runtime_requirements(compilation, &chunk_ukey)

packages/rspack/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@
6767
"zod-validation-error": "3.5.3"
6868
},
6969
"dependencies": {
70-
"@module-federation/runtime-tools": "0.18.0",
70+
"@module-federation/runtime-tools": "0.0.0-fix-lazy-comile-20250930092757",
7171
"@rspack/binding": "workspace:*",
7272
"@rspack/lite-tapable": "1.0.1"
7373
},

packages/rspack/src/container/ModuleFederationPlugin.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import type { Compiler } from "../Compiler";
22
import type { ExternalsType } from "../config";
3+
import { RuntimeGlobals } from "../RuntimeGlobals";
34
import { getExternalsTypeSchema } from "../schema/config";
45
import { isValidate } from "../schema/validate";
56
import type { ModuleFederationPluginV1Options } from "./ModuleFederationPluginV1";
@@ -40,6 +41,24 @@ export class ModuleFederationPlugin {
4041
...this._options,
4142
enhanced: true
4243
}).apply(compiler);
44+
45+
// inject ensureChunkHandlers if enable lazy compilation
46+
if (compiler.options.lazyCompilation && this._options.remotes) {
47+
compiler.hooks.thisCompilation.tap(
48+
"CoreModuleFederationPlugin",
49+
compilation => {
50+
compilation.hooks.additionalTreeRuntimeRequirements.tap(
51+
"CoreModuleFederationPlugin",
52+
(chunk, set) => {
53+
if (chunk.hasRuntime()) {
54+
set.add(RuntimeGlobals.ensureChunk);
55+
set.add(RuntimeGlobals.ensureChunkHandlers);
56+
}
57+
}
58+
);
59+
}
60+
);
61+
}
4362
}
4463
}
4564

packages/rspack/src/runtime/moduleFederationDefaultRuntime.js

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,11 @@ module.exports = function () {
159159
"chunkMapping",
160160
() => remotesLoadingChunkMapping
161161
);
162+
early(
163+
__webpack_require__.federation.bundlerRuntimeOptions.remotes,
164+
"remoteInfos",
165+
() => __module_federation_remote_infos__
166+
);
162167
early(
163168
__webpack_require__.federation.bundlerRuntimeOptions.remotes,
164169
"idToExternalAndNameMapping",
@@ -207,6 +212,12 @@ module.exports = function () {
207212
__webpack_require__.federation.attachShareScopeMap(__webpack_require__);
208213
}
209214

215+
if (!__webpack_require__.f) {
216+
__webpack_require__.f = {};
217+
}
218+
if (!__webpack_require__.f.remotes) {
219+
__webpack_require__.f.remotes = function () {};
220+
}
210221
override(__webpack_require__.f, "remotes", (chunkId, promises) =>
211222
__webpack_require__.federation.bundlerRuntime.remotes({
212223
chunkId,
@@ -221,6 +232,9 @@ module.exports = function () {
221232
webpackRequire: __webpack_require__
222233
})
223234
);
235+
if (!__webpack_require__.f.consumes) {
236+
__webpack_require__.f.consumes = function () {};
237+
}
224238
override(__webpack_require__.f, "consumes", (chunkId, promises) =>
225239
__webpack_require__.federation.bundlerRuntime.consumes({
226240
chunkId,

pnpm-lock.yaml

Lines changed: 45 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import { expect, test } from "@/fixtures";
2+
3+
test("should load remote and shared success", async ({ page }) => {
4+
// Click the button that triggers dynamic import
5+
await page.waitForSelector('button:has-text("Click me")');
6+
await page.getByText("Click me").click();
7+
8+
// Wait for the component to appear with a more reliable wait
9+
await page.waitForSelector('div:has-text("RemoteComponent")');
10+
11+
// Check that the component was loaded and displayed
12+
const RemoteComponentCount = await page.getByText("RemoteComponent").count();
13+
expect(RemoteComponentCount).toBe(1);
14+
15+
16+
// Check that the shared component was loaded and displayed
17+
const SharedReactCount = await page.getByText("SharedReact").count();
18+
expect(SharedReactCount).toBe(1);
19+
});
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
const { rspack } = require("@rspack/core");
2+
const path = require('path');
3+
const ReactRefreshPlugin = require("@rspack/plugin-react-refresh");
4+
5+
/** @type { import('@rspack/core').RspackOptions } */
6+
module.exports = {
7+
context: __dirname,
8+
entry: "./src/index.jsx",
9+
mode: "development",
10+
devtool:false,
11+
resolve: {
12+
extensions: ["...", ".jsx"]
13+
},
14+
module: {
15+
rules: [
16+
{
17+
test: /\.(jsx?|tsx?)$/,
18+
use: [
19+
{
20+
loader: "builtin:swc-loader",
21+
options: {
22+
jsc: {
23+
parser: {
24+
syntax: "typescript",
25+
tsx: true
26+
},
27+
transform: {
28+
react: {
29+
runtime: "automatic",
30+
development: true,
31+
refresh: true,
32+
}
33+
},
34+
},
35+
}
36+
},
37+
]
38+
}
39+
]
40+
},
41+
plugins: [new rspack.HtmlRspackPlugin({ template: "./src/index.html" }), new rspack.container.ModuleFederationPlugin({
42+
name:"host",
43+
remotes: {
44+
remote: "remote@http://localhost:5679/remoteEntry.js"
45+
},
46+
shared: {
47+
react: {},
48+
'react-dom': {}
49+
},
50+
runtimePlugins: [require.resolve('./runtimePlugin.js')]
51+
}),
52+
new ReactRefreshPlugin(),
53+
54+
],
55+
lazyCompilation:true,
56+
devServer: {
57+
hot: true,
58+
port: 5678,
59+
devMiddleware: {
60+
writeToDisk: true
61+
}
62+
}
63+
};
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
module.exports = function () {
2+
let component;
3+
return {
4+
name: 'proxy-remote',
5+
async errorLoadRemote() {
6+
if(!component){
7+
component = document.createElement("div");
8+
component.textContent = "RemoteComponent";
9+
document.body.appendChild(component);
10+
}
11+
12+
return ()=>component;
13+
},
14+
};
15+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import React from "react";
2+
import ReactDOM from "react-dom/client";
3+
4+
ReactDOM.createRoot(document.getElementById("root")).render(
5+
<React.StrictMode>
6+
<button type="button" onClick={() => {
7+
import("./remote-entry.js");
8+
import("./share-entry.js");
9+
}}>
10+
Click me
11+
</button>
12+
</React.StrictMode>
13+
);
14+
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="UTF-8" />
5+
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
6+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
7+
<title>Document</title>
8+
</head>
9+
<body>
10+
<div id="root"></div>
11+
</body>
12+
</html>

0 commit comments

Comments
 (0)