From 96e922820f43bb760c864fd62245b6b8d02ea51d Mon Sep 17 00:00:00 2001 From: ScriptedAlchemy Date: Wed, 1 May 2024 19:37:47 -0700 Subject: [PATCH 1/5] fix(rspack_plugin_mf): add chunk js matcher runtime to federation runtime --- .../module_federation_runtime_plugin.rs | 20 ++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/crates/rspack_plugin_mf/src/container/module_federation_runtime_plugin.rs b/crates/rspack_plugin_mf/src/container/module_federation_runtime_plugin.rs index 37c38e233656..c96b466e8a3d 100644 --- a/crates/rspack_plugin_mf/src/container/module_federation_runtime_plugin.rs +++ b/crates/rspack_plugin_mf/src/container/module_federation_runtime_plugin.rs @@ -1,6 +1,6 @@ use async_trait::async_trait; use rspack_core::{ - impl_runtime_module, + compile_boolean_matcher, impl_runtime_module, rspack_sources::{BoxSource, RawSource, SourceExt}, ApplyContext, ChunkUkey, Compilation, CompilationAdditionalTreeRuntimeRequirements, CompilerOptions, Plugin, PluginContext, RuntimeGlobals, RuntimeModule, RuntimeModuleStage, @@ -9,6 +9,7 @@ use rspack_error::Result; use rspack_hook::{plugin, plugin_hook}; use rspack_identifier::Identifier; use rspack_util::source_map::SourceMapKind; +use runtime_module::utils::chunk_has_js; #[impl_runtime_module] #[derive(Debug, Eq)] @@ -41,20 +42,29 @@ impl RuntimeModule for FederationRuntimeModule { RuntimeModuleStage::Normal } - fn generate(&self, _: &Compilation) -> rspack_error::Result { - Ok(RawSource::from(federation_runtime_template()).boxed()) + fn generate(&self, compilation: &Compilation) -> rspack_error::Result { + Ok(RawSource::from(federation_runtime_template(compilation)).boxed()) } } -fn federation_runtime_template() -> String { +fn federation_runtime_template(compilation: &Compilation) -> String { let federation_global = format!("{}.federation", RuntimeGlobals::REQUIRE); + let condition_map = + compilation + .chunk_graph + .get_chunk_condition_map(&chunk.ukey, compilation, chunk_has_js); + let has_js_matcher = compile_boolean_matcher(&condition_map); + format!( r#" if(!{federation_global}){{ - {federation_global} = {{}}; + {federation_global} = {{ + chunkMatcher: {has_js_matcher} + }}; }} "#, federation_global = federation_global, + has_js_matcher = &has_js_matcher.render("chunkId") ) } From f2a501704ceac73ab8430525d623f31639144d60 Mon Sep 17 00:00:00 2001 From: ScriptedAlchemy Date: Wed, 1 May 2024 20:24:35 -0700 Subject: [PATCH 2/5] fix(rspack_plugin_mf): add chunk js matcher runtime to federation runtime --- Cargo.lock | 1 + crates/rspack_plugin_mf/Cargo.toml | 15 +++++---- .../module_federation_runtime_plugin.rs | 33 ++++++++++++++----- crates/rspack_plugin_runtime/src/lib.rs | 3 +- 4 files changed, 36 insertions(+), 16 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 08415992a13a..7c9e7b643dd6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3425,6 +3425,7 @@ dependencies = [ "rspack_hook", "rspack_identifier", "rspack_loader_runner", + "rspack_plugin_runtime", "rspack_util", "rustc-hash", "serde", diff --git a/crates/rspack_plugin_mf/Cargo.toml b/crates/rspack_plugin_mf/Cargo.toml index f16affe82331..0d45c019f486 100644 --- a/crates/rspack_plugin_mf/Cargo.toml +++ b/crates/rspack_plugin_mf/Cargo.toml @@ -8,13 +8,14 @@ version = "0.1.0" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -rspack_core = { path = "../rspack_core" } -rspack_error = { path = "../rspack_error" } -rspack_hash = { path = "../rspack_hash" } -rspack_hook = { path = "../rspack_hook" } -rspack_identifier = { path = "../rspack_identifier" } -rspack_loader_runner = { path = "../rspack_loader_runner" } -rspack_util = { path = "../rspack_util" } +rspack_core = { path = "../rspack_core" } +rspack_error = { path = "../rspack_error" } +rspack_hash = { path = "../rspack_hash" } +rspack_hook = { path = "../rspack_hook" } +rspack_identifier = { path = "../rspack_identifier" } +rspack_loader_runner = { path = "../rspack_loader_runner" } +rspack_plugin_runtime = { path = "../rspack_plugin_runtime" } +rspack_util = { path = "../rspack_util" } async-trait = { workspace = true } hashlink = { workspace = true } diff --git a/crates/rspack_plugin_mf/src/container/module_federation_runtime_plugin.rs b/crates/rspack_plugin_mf/src/container/module_federation_runtime_plugin.rs index c96b466e8a3d..78d66452bbeb 100644 --- a/crates/rspack_plugin_mf/src/container/module_federation_runtime_plugin.rs +++ b/crates/rspack_plugin_mf/src/container/module_federation_runtime_plugin.rs @@ -2,15 +2,15 @@ use async_trait::async_trait; use rspack_core::{ compile_boolean_matcher, impl_runtime_module, rspack_sources::{BoxSource, RawSource, SourceExt}, - ApplyContext, ChunkUkey, Compilation, CompilationAdditionalTreeRuntimeRequirements, - CompilerOptions, Plugin, PluginContext, RuntimeGlobals, RuntimeModule, RuntimeModuleStage, + ApplyContext, BooleanMatcher, Chunk, ChunkUkey, Compilation, + CompilationAdditionalTreeRuntimeRequirements, CompilerOptions, Plugin, PluginContext, + RuntimeGlobals, RuntimeModule, RuntimeModuleStage, }; use rspack_error::Result; use rspack_hook::{plugin, plugin_hook}; use rspack_identifier::Identifier; +use rspack_plugin_runtime::chunk_has_js; use rspack_util::source_map::SourceMapKind; -use runtime_module::utils::chunk_has_js; - #[impl_runtime_module] #[derive(Debug, Eq)] pub struct FederationRuntimeModule { @@ -43,28 +43,45 @@ impl RuntimeModule for FederationRuntimeModule { } fn generate(&self, compilation: &Compilation) -> rspack_error::Result { - Ok(RawSource::from(federation_runtime_template(compilation)).boxed()) + let chunk = compilation + .chunk_by_ukey + .expect_get(&self.chunk.expect("The chunk should be attached.")); + Ok(RawSource::from(federation_runtime_template(chunk, compilation)).boxed()) } } -fn federation_runtime_template(compilation: &Compilation) -> String { +fn federation_runtime_template(chunk: &Chunk, compilation: &Compilation) -> String { let federation_global = format!("{}.federation", RuntimeGlobals::REQUIRE); + let condition_map = compilation .chunk_graph .get_chunk_condition_map(&chunk.ukey, compilation, chunk_has_js); let has_js_matcher = compile_boolean_matcher(&condition_map); + let chunk_matcher = if matches!(has_js_matcher, BooleanMatcher::Condition(true)) { + format!( + r#" +chunkMatcher: function(chunkId) {{ + return {has_js_matcher}; +}} +"#, + has_js_matcher = &has_js_matcher.render("chunkId") + ) + } else { + String::from("") + }; + format!( r#" if(!{federation_global}){{ {federation_global} = {{ - chunkMatcher: {has_js_matcher} + {chunk_matcher} }}; }} "#, federation_global = federation_global, - has_js_matcher = &has_js_matcher.render("chunkId") + chunk_matcher = chunk_matcher ) } diff --git a/crates/rspack_plugin_runtime/src/lib.rs b/crates/rspack_plugin_runtime/src/lib.rs index 65f265f38e30..59f113b32021 100644 --- a/crates/rspack_plugin_runtime/src/lib.rs +++ b/crates/rspack_plugin_runtime/src/lib.rs @@ -22,7 +22,8 @@ mod import_scripts_chunk_loading; pub use import_scripts_chunk_loading::ImportScriptsChunkLoadingPlugin; mod runtime_module; pub use runtime_module::{ - chunk_has_css, is_enabled_for_chunk, stringify_chunks, GetChunkFilenameRuntimeModule, + chunk_has_css, chunk_has_js, is_enabled_for_chunk, stringify_chunks, + GetChunkFilenameRuntimeModule, }; mod startup_chunk_dependencies; pub use startup_chunk_dependencies::StartupChunkDependenciesPlugin; From a295a1c53ede4e0e7f9b4b43b8b761acae2a2d15 Mon Sep 17 00:00:00 2001 From: ScriptedAlchemy Date: Wed, 8 May 2024 19:05:34 -0700 Subject: [PATCH 3/5] tests(rspack_plugin_mf): add chunk matcher test --- .../container-1-5/chunk-matcher/App.js | 5 + .../container-1-5/chunk-matcher/ComponentB.js | 5 + .../container-1-5/chunk-matcher/ComponentC.js | 7 ++ .../container-1-5/chunk-matcher/index.js | 9 ++ .../container-1-5/chunk-matcher/package.json | 9 ++ .../chunk-matcher/runtimePlugin.js | 95 +++++++++++++++++++ .../chunk-matcher/test.config.js | 5 + .../chunk-matcher/webpack.config.js | 46 +++++++++ 8 files changed, 181 insertions(+) create mode 100644 packages/rspack-test-tools/tests/configCases/container-1-5/chunk-matcher/App.js create mode 100644 packages/rspack-test-tools/tests/configCases/container-1-5/chunk-matcher/ComponentB.js create mode 100644 packages/rspack-test-tools/tests/configCases/container-1-5/chunk-matcher/ComponentC.js create mode 100644 packages/rspack-test-tools/tests/configCases/container-1-5/chunk-matcher/index.js create mode 100644 packages/rspack-test-tools/tests/configCases/container-1-5/chunk-matcher/package.json create mode 100644 packages/rspack-test-tools/tests/configCases/container-1-5/chunk-matcher/runtimePlugin.js create mode 100644 packages/rspack-test-tools/tests/configCases/container-1-5/chunk-matcher/test.config.js create mode 100644 packages/rspack-test-tools/tests/configCases/container-1-5/chunk-matcher/webpack.config.js diff --git a/packages/rspack-test-tools/tests/configCases/container-1-5/chunk-matcher/App.js b/packages/rspack-test-tools/tests/configCases/container-1-5/chunk-matcher/App.js new file mode 100644 index 000000000000..25bb494cab20 --- /dev/null +++ b/packages/rspack-test-tools/tests/configCases/container-1-5/chunk-matcher/App.js @@ -0,0 +1,5 @@ +import React from "react"; + +export default () => { + return `App fetched with Chunk Handler __HANDLER__`; +}; diff --git a/packages/rspack-test-tools/tests/configCases/container-1-5/chunk-matcher/ComponentB.js b/packages/rspack-test-tools/tests/configCases/container-1-5/chunk-matcher/ComponentB.js new file mode 100644 index 000000000000..1943469c7461 --- /dev/null +++ b/packages/rspack-test-tools/tests/configCases/container-1-5/chunk-matcher/ComponentB.js @@ -0,0 +1,5 @@ +import React from "react"; + +export default () => { + return `ComponentB rendered with [${React()}]`; +}; diff --git a/packages/rspack-test-tools/tests/configCases/container-1-5/chunk-matcher/ComponentC.js b/packages/rspack-test-tools/tests/configCases/container-1-5/chunk-matcher/ComponentC.js new file mode 100644 index 000000000000..3ff3832c7180 --- /dev/null +++ b/packages/rspack-test-tools/tests/configCases/container-1-5/chunk-matcher/ComponentC.js @@ -0,0 +1,7 @@ +import React from "react"; +import ComponentA from "containerA/ComponentA"; +import ComponentB from "containerB/ComponentB"; + +export default () => { + return `ComponentC rendered with [${React()}] and [${ComponentA()}] and [${ComponentB()}]`; +}; diff --git a/packages/rspack-test-tools/tests/configCases/container-1-5/chunk-matcher/index.js b/packages/rspack-test-tools/tests/configCases/container-1-5/chunk-matcher/index.js new file mode 100644 index 000000000000..40d0e664221d --- /dev/null +++ b/packages/rspack-test-tools/tests/configCases/container-1-5/chunk-matcher/index.js @@ -0,0 +1,9 @@ +it("should load chunk with patched chunk handler", () => { + return import("./App").then(({ default: App }) => { + const rendered = App(); + console.log(rendered) + expect(rendered).toBe( + "App fetched with Chunk Handler PASS" + ); + }); +}); diff --git a/packages/rspack-test-tools/tests/configCases/container-1-5/chunk-matcher/package.json b/packages/rspack-test-tools/tests/configCases/container-1-5/chunk-matcher/package.json new file mode 100644 index 000000000000..be6238fec84d --- /dev/null +++ b/packages/rspack-test-tools/tests/configCases/container-1-5/chunk-matcher/package.json @@ -0,0 +1,9 @@ +{ + "private": true, + "engines": { + "node": ">=10.13.0" + }, + "dependencies": { + "react": "*" + } +} diff --git a/packages/rspack-test-tools/tests/configCases/container-1-5/chunk-matcher/runtimePlugin.js b/packages/rspack-test-tools/tests/configCases/container-1-5/chunk-matcher/runtimePlugin.js new file mode 100644 index 000000000000..0a51f06188f8 --- /dev/null +++ b/packages/rspack-test-tools/tests/configCases/container-1-5/chunk-matcher/runtimePlugin.js @@ -0,0 +1,95 @@ +const runtimePlugin = function () { + return { + name: 'my-runtime-plugin', + beforeInit(args) { + + const federationWarehouse = __webpack_require__.federation + debugger; + !function() { + var installedChunks = {"115": 0,}; + var installChunk = function (chunk) { + var moreModules = chunk.modules, chunkIds = chunk.ids, + runtime = chunk.runtime; + for (var moduleId in moreModules) { + if (__webpack_require__.o(moreModules, moduleId)) { + __webpack_require__.m[moduleId] = moreModules[moduleId]; + } + } + if (runtime) runtime(__webpack_require__); + for (var i = 0; i < chunkIds.length; i++) { + if (installedChunks[chunkIds[i]]) { + installedChunks[chunkIds[i]][0](); + } + installedChunks[chunkIds[i]] = 0; + } + + }; + + // ReadFile + VM.run chunk loading for javascript" + const handler = function (chunkId, promises) { + var installedChunkData = installedChunks[chunkId]; + if (installedChunkData !== 0) { // 0 means "already installed". + // array of [resolve, reject, promise] means "currently loading" + if (installedChunkData) { + promises.push(installedChunkData[2]); + } else { + if (__webpack_require__.federation.chunkMatcher(chunkId)) { // all chunks have JS + // load the chunk and return promise to it + var promise + + promise = new Promise(function (resolve, reject) { + installedChunkData = installedChunks[chunkId] = [resolve, reject]; + var filename = require('path').join( + __dirname, "" + __webpack_require__.u(chunkId)); + require('fs').readFile(filename, 'utf-8', function (err, content) { + if (err) return reject(err); + var chunk = {}; + content = content.replace('__HANDLER__', 'PASS') + require('vm').runInThisContext( + '(function(exports, require, __dirname, __filename) {' + + content + '\n})', + filename)( + chunk, require, require('path').dirname(filename), filename); + installChunk(chunk); + }); + }); + + promises.push(installedChunkData[2] = promise); + } else installedChunks[chunkId] = 0; + + } + } + + }; + if(!__webpack_require__.f.j) { + __webpack_require__.f.j = handler + } else { + __webpack_require__.f.readfileVm = handler + } + + }(); + console.log('beforeInit: ', args); + return args; + }, + beforeRequest(args) { + console.log('beforeRequest: ', args); + return args; + }, + afterResolve(args) { + console.log('afterResolve', args); + return args; + }, + onLoad(args) { + console.log('onLoad: ', args); + return args; + }, + async loadShare(args) { + console.log('loadShare:', args); + }, + async beforeLoadShare(args) { + console.log('beforeloadShare:', args); + return args; + }, + }; +}; +export default runtimePlugin; diff --git a/packages/rspack-test-tools/tests/configCases/container-1-5/chunk-matcher/test.config.js b/packages/rspack-test-tools/tests/configCases/container-1-5/chunk-matcher/test.config.js new file mode 100644 index 000000000000..2d0d66fd4c0d --- /dev/null +++ b/packages/rspack-test-tools/tests/configCases/container-1-5/chunk-matcher/test.config.js @@ -0,0 +1,5 @@ +module.exports = { + findBundle: function (i, options) { + return i === 0 ? "./main.js" : "./module/main.mjs"; + } +}; diff --git a/packages/rspack-test-tools/tests/configCases/container-1-5/chunk-matcher/webpack.config.js b/packages/rspack-test-tools/tests/configCases/container-1-5/chunk-matcher/webpack.config.js new file mode 100644 index 000000000000..d0b403b92898 --- /dev/null +++ b/packages/rspack-test-tools/tests/configCases/container-1-5/chunk-matcher/webpack.config.js @@ -0,0 +1,46 @@ +// eslint-disable-next-line node/no-unpublished-require +const { ModuleFederationPlugin } = require("@rspack/core").container; + +const common = { + entry: { + main: "./index.js" + }, + target: 'async-node', + optimization: { + runtimeChunk: "single" + } +}; + +/** @type {ConstructorParameters[0]} */ +const commonMF = { + runtime: false, + exposes: { + "./ComponentB": "./ComponentB", + "./ComponentC": "./ComponentC" + }, + shared: ["react"] +}; + +/** @type {import("@rspack/core").Configuration[]} */ +module.exports = [ + { + ...common, + output: { + filename: "[name].js", + uniqueName: "1-container-full" + }, + plugins: [ + new ModuleFederationPlugin({ + name: "container", + library: { type: "commonjs-module" }, + runtimePlugins: [require.resolve('./runtimePlugin.js')], + filename: "container.js", + remotes: { + containerA: "../0-container-full/container.js", + containerB: "./container.js" + }, + ...commonMF + }) + ] + } +]; From e27a4270582ba0e35d8bef254bb671e1ba156a3c Mon Sep 17 00:00:00 2001 From: ScriptedAlchemy Date: Wed, 8 May 2024 19:06:11 -0700 Subject: [PATCH 4/5] fix(rspack_plugin_mf): has_js_matcher boolean check incorrect --- .../src/container/module_federation_runtime_plugin.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/rspack_plugin_mf/src/container/module_federation_runtime_plugin.rs b/crates/rspack_plugin_mf/src/container/module_federation_runtime_plugin.rs index 7eaedca81f2a..98eef1e74d95 100644 --- a/crates/rspack_plugin_mf/src/container/module_federation_runtime_plugin.rs +++ b/crates/rspack_plugin_mf/src/container/module_federation_runtime_plugin.rs @@ -59,7 +59,9 @@ fn federation_runtime_template(chunk: &Chunk, compilation: &Compilation) -> Stri .get_chunk_condition_map(&chunk.ukey, compilation, chunk_has_js); let has_js_matcher = compile_boolean_matcher(&condition_map); - let chunk_matcher = if matches!(has_js_matcher, BooleanMatcher::Condition(true)) { + let chunk_matcher = if matches!(has_js_matcher, BooleanMatcher::Condition(false)) { + String::from("") + } else { format!( r#" chunkMatcher: function(chunkId) {{ @@ -68,8 +70,6 @@ chunkMatcher: function(chunkId) {{ "#, has_js_matcher = &has_js_matcher.render("chunkId") ) - } else { - String::from("") }; format!( From bd69e4421074a92c7d2ce174c14157599443dda0 Mon Sep 17 00:00:00 2001 From: ScriptedAlchemy Date: Wed, 8 May 2024 19:22:25 -0700 Subject: [PATCH 5/5] tests(rspack_plugin_mf): add chunk matcher test --- .../container-1-5/chunk-matcher/ComponentC.js | 3 +-- .../container-1-5/chunk-matcher/runtimePlugin.js | 9 ++++----- .../container-1-5/chunk-matcher/test.config.js | 5 ----- .../container-1-5/chunk-matcher/webpack.config.js | 1 - 4 files changed, 5 insertions(+), 13 deletions(-) delete mode 100644 packages/rspack-test-tools/tests/configCases/container-1-5/chunk-matcher/test.config.js diff --git a/packages/rspack-test-tools/tests/configCases/container-1-5/chunk-matcher/ComponentC.js b/packages/rspack-test-tools/tests/configCases/container-1-5/chunk-matcher/ComponentC.js index 3ff3832c7180..fedce96a4264 100644 --- a/packages/rspack-test-tools/tests/configCases/container-1-5/chunk-matcher/ComponentC.js +++ b/packages/rspack-test-tools/tests/configCases/container-1-5/chunk-matcher/ComponentC.js @@ -1,7 +1,6 @@ import React from "react"; import ComponentA from "containerA/ComponentA"; -import ComponentB from "containerB/ComponentB"; export default () => { - return `ComponentC rendered with [${React()}] and [${ComponentA()}] and [${ComponentB()}]`; + return `ComponentC rendered with [${React()}] and [${ComponentA()}]`; }; diff --git a/packages/rspack-test-tools/tests/configCases/container-1-5/chunk-matcher/runtimePlugin.js b/packages/rspack-test-tools/tests/configCases/container-1-5/chunk-matcher/runtimePlugin.js index 0a51f06188f8..67402e7c3f45 100644 --- a/packages/rspack-test-tools/tests/configCases/container-1-5/chunk-matcher/runtimePlugin.js +++ b/packages/rspack-test-tools/tests/configCases/container-1-5/chunk-matcher/runtimePlugin.js @@ -4,7 +4,6 @@ const runtimePlugin = function () { beforeInit(args) { const federationWarehouse = __webpack_require__.federation - debugger; !function() { var installedChunks = {"115": 0,}; var installChunk = function (chunk) { @@ -39,17 +38,17 @@ const runtimePlugin = function () { promise = new Promise(function (resolve, reject) { installedChunkData = installedChunks[chunkId] = [resolve, reject]; - var filename = require('path').join( + var filename = __non_webpack_require__('path').join( __dirname, "" + __webpack_require__.u(chunkId)); - require('fs').readFile(filename, 'utf-8', function (err, content) { + __non_webpack_require__('fs').readFile(filename, 'utf-8', function (err, content) { if (err) return reject(err); var chunk = {}; content = content.replace('__HANDLER__', 'PASS') - require('vm').runInThisContext( + __non_webpack_require__('vm').runInThisContext( '(function(exports, require, __dirname, __filename) {' + content + '\n})', filename)( - chunk, require, require('path').dirname(filename), filename); + chunk, __non_webpack_require__, __non_webpack_require__('path').dirname(filename), filename); installChunk(chunk); }); }); diff --git a/packages/rspack-test-tools/tests/configCases/container-1-5/chunk-matcher/test.config.js b/packages/rspack-test-tools/tests/configCases/container-1-5/chunk-matcher/test.config.js deleted file mode 100644 index 2d0d66fd4c0d..000000000000 --- a/packages/rspack-test-tools/tests/configCases/container-1-5/chunk-matcher/test.config.js +++ /dev/null @@ -1,5 +0,0 @@ -module.exports = { - findBundle: function (i, options) { - return i === 0 ? "./main.js" : "./module/main.mjs"; - } -}; diff --git a/packages/rspack-test-tools/tests/configCases/container-1-5/chunk-matcher/webpack.config.js b/packages/rspack-test-tools/tests/configCases/container-1-5/chunk-matcher/webpack.config.js index d0b403b92898..0d353bca9404 100644 --- a/packages/rspack-test-tools/tests/configCases/container-1-5/chunk-matcher/webpack.config.js +++ b/packages/rspack-test-tools/tests/configCases/container-1-5/chunk-matcher/webpack.config.js @@ -37,7 +37,6 @@ module.exports = [ filename: "container.js", remotes: { containerA: "../0-container-full/container.js", - containerB: "./container.js" }, ...commonMF })