From cf04e7e2448f330dbca6f337002027c82f18177f Mon Sep 17 00:00:00 2001 From: ScriptedAlchemy Date: Wed, 5 Feb 2025 18:57:25 -0800 Subject: [PATCH 01/11] fix(enhanced): refactor hoisting plugins --- .../3009-webpack-provider/webpack.config.js | 6 + .../webpack-host/webpack.config.js | 1 + apps/modernjs/modern.config.ts | 1 + apps/modernjs/project.json | 24 ++-- package.json | 2 +- .../src/lib/container/ContainerPlugin.ts | 4 +- .../runtime/EmbedFederationRuntimePlugin.ts | 108 ++++++++++++----- .../MfStartupChunkDependenciesPlugin.ts | 111 +++++++++++++++++- .../test/ConfigTestCases.basictest.js | 2 + 9 files changed, 215 insertions(+), 44 deletions(-) diff --git a/apps/manifest-demo/3009-webpack-provider/webpack.config.js b/apps/manifest-demo/3009-webpack-provider/webpack.config.js index 6df53bd0864..0a0c221eb0e 100644 --- a/apps/manifest-demo/3009-webpack-provider/webpack.config.js +++ b/apps/manifest-demo/3009-webpack-provider/webpack.config.js @@ -15,6 +15,12 @@ module.exports = composePlugins( config.watchOptions = { ignored: ['**/node_modules/**', '**/@mf-types/**'], }; + config.devServer = { + ...config.devServer, + devMiddleware: { + writeToDisk: true, + }, + }; // publicPath must be specific url config.output.publicPath = 'auto'; config.plugins.push( diff --git a/apps/manifest-demo/webpack-host/webpack.config.js b/apps/manifest-demo/webpack-host/webpack.config.js index 90ee7a73e78..825d4649285 100644 --- a/apps/manifest-demo/webpack-host/webpack.config.js +++ b/apps/manifest-demo/webpack-host/webpack.config.js @@ -13,6 +13,7 @@ module.exports = composePlugins(withNx(), withReact(), (config, context) => { }; config.plugins.push( new ModuleFederationPlugin({ + runtime: false, name: 'manifest_host', remotes: { remote1: 'webpack_provider@http://localhost:3009/mf-manifest.json', diff --git a/apps/modernjs/modern.config.ts b/apps/modernjs/modern.config.ts index 6d4c62fc090..66aaf2d73a3 100644 --- a/apps/modernjs/modern.config.ts +++ b/apps/modernjs/modern.config.ts @@ -35,6 +35,7 @@ export default defineConfig({ appendPlugins([ new ModuleFederationPlugin({ + runtime: false, name: 'app1', exposes: { './thing': './src/test.ts', diff --git a/apps/modernjs/project.json b/apps/modernjs/project.json index d9433da6bf2..953912b3513 100644 --- a/apps/modernjs/project.json +++ b/apps/modernjs/project.json @@ -8,13 +8,13 @@ "targets": { "build": { "executor": "nx:run-commands", + "dependsOn": [ + { + "target": "build", + "dependencies": true + } + ], "options": { - "dependsOn": [ - { - "target": "build", - "dependencies": true - } - ], "commands": [ { "command": "cd apps/modernjs; pnpm run build", @@ -25,13 +25,13 @@ }, "serve": { "executor": "nx:run-commands", + "dependsOn": [ + { + "target": "build", + "dependencies": true + } + ], "options": { - "dependsOn": [ - { - "target": "build", - "dependencies": true - } - ], "commands": [ { "command": "cd apps/modernjs; pnpm run dev", diff --git a/package.json b/package.json index 9579b6f7f2b..818bdafabbe 100644 --- a/package.json +++ b/package.json @@ -41,7 +41,7 @@ "app:node:dev": "nx run-many --target=serve --parallel=10 --configuration=development -p node-host,node-local-remote,node-remote,node-dynamic-remote-new-version,node-dynamic-remote", "app:runtime:dev": "nx run-many --target=serve -p 3005-runtime-host,3006-runtime-remote,3007-runtime-remote", "app:router:dev": "nx run-many --target=serve --parallel=10 --projects='router-*'", - "app:manifest:dev": "nx run-many --target=serve --configuration=development --parallel=100 -p modernjs,manifest-webpack-host,3009-webpack-provider,3010-rspack-provider,3011-rspack-manifest-provider,3012-rspack-js-entry-provider", + "app:manifest:dev": "nx run-many --target=serve --configuration=development --parallel=100 -p modernjs,manifest-webpack-host,3009-webpack-provider,3010-rspack-provider,3011-rspack-manifest-provider,3012-rspack-js-entry-provider --skip-nx-cache", "app:manifest:prod": "nx run-many --target=serve --configuration=production --parallel=100 -p modernjs,manifest-webpack-host,3009-webpack-provider,3010-rspack-provider,3011-rspack-manifest-provider,3012-rspack-js-entry-provider", "app:ts:dev": "nx run-many --target=serve -p react_ts_host,react_ts_nested_remote,react_ts_remote", "app:modern:dev": "nx run-many --target=serve --parallel=10 --configuration=development -p modernjs-ssr-dynamic-nested-remote,modernjs-ssr-dynamic-remote,modernjs-ssr-dynamic-remote-new-version,modernjs-ssr-host,modernjs-ssr-nested-remote,modernjs-ssr-remote,modernjs-ssr-remote-new-version", diff --git a/packages/enhanced/src/lib/container/ContainerPlugin.ts b/packages/enhanced/src/lib/container/ContainerPlugin.ts index 25a3db206aa..4fa8ab3f79e 100644 --- a/packages/enhanced/src/lib/container/ContainerPlugin.ts +++ b/packages/enhanced/src/lib/container/ContainerPlugin.ts @@ -193,8 +193,6 @@ class ContainerPlugin { compilation: Compilation, callback: (error?: WebpackError | null | undefined) => void, ) => { - const hasSingleRuntimeChunk = - compilation.options?.optimization?.runtimeChunk; const hooks = FederationModulesPlugin.getCompilationHooks(compilation); const federationRuntimeDependency = federationRuntimePluginInstance.getDependency(compiler); @@ -215,7 +213,7 @@ class ContainerPlugin { { name, filename, - runtime: hasSingleRuntimeChunk ? false : runtime, + runtime, library, }, (error: WebpackError | null | undefined) => { diff --git a/packages/enhanced/src/lib/container/runtime/EmbedFederationRuntimePlugin.ts b/packages/enhanced/src/lib/container/runtime/EmbedFederationRuntimePlugin.ts index ac05409b446..c8ab84e77e6 100644 --- a/packages/enhanced/src/lib/container/runtime/EmbedFederationRuntimePlugin.ts +++ b/packages/enhanced/src/lib/container/runtime/EmbedFederationRuntimePlugin.ts @@ -66,52 +66,41 @@ class EmbedFederationRuntimePlugin { compiler.hooks.thisCompilation.tap( PLUGIN_NAME, - (compilation: Compilation) => { - debugger; + (compilation /*: Compilation */) => { const { renderStartup } = compiler.webpack.javascript.JavascriptModulesPlugin.getCompilationHooks( compilation, ); - // Prevent double tapping of renderStartup hook - const startupTaps = renderStartup.taps || []; - if (this.isHookAlreadyTapped(startupTaps, PLUGIN_NAME)) { - return; - } - renderStartup.tap( PLUGIN_NAME, (startupSource, lastInlinedModule, renderContext) => { const { chunk, chunkGraph } = renderContext; - if (!this.isEnabledForChunk(chunk)) { + if (this.isEnabledForChunk(chunk)) { return startupSource; } - const treeRuntimeRequirements = + const runtimeRequirements = chunkGraph.getTreeRuntimeRequirements(chunk); - const chunkRuntimeRequirements = - chunkGraph.getChunkRuntimeRequirements(chunk); - - const federation = - chunkRuntimeRequirements.has(federationGlobal) || - treeRuntimeRequirements.has(federationGlobal); - - if (!federation) { + const entryModuleCount = chunkGraph.getNumberOfEntryModules(chunk); + + // The original renderBootstrap pushes a startup call automatically when either: + // - There is at least one entry module, OR + // - runtimeRequirements.has(RuntimeGlobals.startupNoDefault) is true. + // (In all other cases—even if one of the startup keys is set—the startup function is defined + // but not called.) + if ( + entryModuleCount > 0 || + runtimeRequirements.has(RuntimeGlobals.startupNoDefault) + ) { return startupSource; } - // Skip if chunk was already processed - if (this.processedChunks.get(chunk)) { - return startupSource; - } - - // Mark chunk as processed - this.processedChunks.set(chunk, true); - - // Add basic startup call + // Otherwise, append a startup call. return new ConcatSource( startupSource, + '\n// Custom hook: appended startup call because none was added automatically\n', `${RuntimeGlobals.startup}();\n`, ); }, @@ -119,6 +108,61 @@ class EmbedFederationRuntimePlugin { }, ); + // compiler.hooks.thisCompilation.tap( + // PLUGIN_NAME, + // (compilation: Compilation) => { + // const { renderStartup } = + // compiler.webpack.javascript.JavascriptModulesPlugin.getCompilationHooks( + // compilation, + // ); + // + // // Prevent double tapping of renderStartup hook + // const startupTaps = renderStartup.taps || []; + // if (this.isHookAlreadyTapped(startupTaps, PLUGIN_NAME)) { + // return; + // } + // + // renderStartup.tap( + // PLUGIN_NAME, + // (startupSource, lastInlinedModule, renderContext) => { + // const { chunk, chunkGraph } = renderContext; + // + // if (!this.isEnabledForChunk(chunk)) { + // return startupSource; + // } + // + // const treeRuntimeRequirements = + // chunkGraph.getTreeRuntimeRequirements(chunk); + // const chunkRuntimeRequirements = + // chunkGraph.getChunkRuntimeRequirements(chunk); + // + // const federation = + // chunkRuntimeRequirements.has(federationGlobal) || + // treeRuntimeRequirements.has(federationGlobal); + // + // if (!federation) { + // return startupSource; + // } + // + // // Skip if chunk was already processed + // if (this.processedChunks.get(chunk)) { + // return startupSource; + // } + // + // // Mark chunk as processed + // this.processedChunks.set(chunk, true); + // debugger; + // // Add basic startup call + // return new ConcatSource( + // startupSource, + // // add only when not added already + // `${RuntimeGlobals.startup}();\n`, + // ); + // }, + // ); + // }, + // ); + compiler.hooks.thisCompilation.tap( PLUGIN_NAME, (compilation: Compilation) => { @@ -127,6 +171,14 @@ class EmbedFederationRuntimePlugin { ContainerEntryDependency | FederationRuntimeDependency > = new Set(); + // Proactively add startupOnlyBefore to all chunks + compilation.hooks.additionalChunkRuntimeRequirements.tap( + PLUGIN_NAME, + (chunk: Chunk, runtimeRequirements: Set) => { + runtimeRequirements.add(RuntimeGlobals.startupOnlyBefore); + }, + ); + hooks.addFederationRuntimeModule.tap( PLUGIN_NAME, (dependency: FederationRuntimeDependency) => { @@ -138,6 +190,8 @@ class EmbedFederationRuntimePlugin { chunk: Chunk, runtimeRequirements: Set, ) => { + runtimeRequirements.add(RuntimeGlobals.startupOnlyBefore); + if (!this.isEnabledForChunk(chunk)) { return; } diff --git a/packages/enhanced/src/lib/startup/MfStartupChunkDependenciesPlugin.ts b/packages/enhanced/src/lib/startup/MfStartupChunkDependenciesPlugin.ts index 31ae0959a16..0dc567070d6 100644 --- a/packages/enhanced/src/lib/startup/MfStartupChunkDependenciesPlugin.ts +++ b/packages/enhanced/src/lib/startup/MfStartupChunkDependenciesPlugin.ts @@ -43,6 +43,7 @@ class StartupChunkDependenciesPlugin { compiler.hooks.thisCompilation.tap( 'MfStartupChunkDependenciesPlugin', (compilation) => { + // Add additional runtime requirements on the tree level. compilation.hooks.additionalTreeRuntimeRequirements.tap( 'StartupChunkDependenciesPlugin', (chunk, set, { chunkGraph }) => { @@ -55,6 +56,7 @@ class StartupChunkDependenciesPlugin { }, ); + // Add additional runtime requirements on the chunk level if there are entry modules. compilation.hooks.additionalChunkRuntimeRequirements.tap( 'MfStartupChunkDependenciesPlugin', (chunk, set, { chunkGraph }) => { @@ -64,6 +66,7 @@ class StartupChunkDependenciesPlugin { }, ); + // When the startupEntrypoint runtime requirement is in the tree, add additional keys and runtime module. compilation.hooks.runtimeRequirementInTree .for(RuntimeGlobals.startupEntrypoint) .tap( @@ -80,6 +83,7 @@ class StartupChunkDependenciesPlugin { }, ); + // Replace the generated startup with our custom version when there are entry modules. const { renderStartup } = compiler.webpack.javascript.JavascriptModulesPlugin.getCompilationHooks( compilation, @@ -90,13 +94,17 @@ class StartupChunkDependenciesPlugin { (startupSource, lastInlinedModule, renderContext) => { const { chunk, chunkGraph, runtimeTemplate } = renderContext; + // Only modify chunks that are enabled. if (!this.isEnabledForChunk(chunk, compilation)) { return startupSource; } - if (chunkGraph.getNumberOfEntryModules(chunk) === 0) + // If no entry modules, do nothing. + if (chunkGraph.getNumberOfEntryModules(chunk) === 0) { return startupSource; + } + // Check the runtime requirements for federation. const treeRuntimeRequirements = chunkGraph.getTreeRuntimeRequirements(chunk); const chunkRuntimeRequirements = @@ -106,18 +114,22 @@ class StartupChunkDependenciesPlugin { chunkRuntimeRequirements.has(federationStartup) || treeRuntimeRequirements.has(federationStartup); + // If the federation requirement is not present, leave the startup as-is. if (!federation) { return startupSource; } + // Otherwise, get the entry modules and generate a custom startup. const entryModules = Array.from( chunkGraph.getChunkEntryModulesWithChunkGroupIterable(chunk), ); + // Choose between ESM and non-ESM startup generation based on the runtimeTemplate. const entryGeneration = runtimeTemplate.outputOptions.module ? generateESMEntryStartup : generateEntryStartup; + // Replace the startup by returning a new ConcatSource. return new compiler.webpack.sources.ConcatSource( entryGeneration( compilation, @@ -133,6 +145,103 @@ class StartupChunkDependenciesPlugin { }, ); } + + // apply(compiler: Compiler): void { + // compiler.hooks.thisCompilation.tap( + // 'MfStartupChunkDependenciesPlugin', + // (compilation) => { + // compilation.hooks.additionalTreeRuntimeRequirements.tap( + // 'StartupChunkDependenciesPlugin', + // (chunk, set, { chunkGraph }) => { + // if (!this.isEnabledForChunk(chunk, compilation)) return; + // if (chunk.hasRuntime()) { + // set.add(RuntimeGlobals.startupEntrypoint); + // set.add(RuntimeGlobals.ensureChunk); + // set.add(RuntimeGlobals.ensureChunkIncludeEntries); + // } + // }, + // ); + // + // compilation.hooks.additionalChunkRuntimeRequirements.tap( + // 'MfStartupChunkDependenciesPlugin', + // (chunk, set, { chunkGraph }) => { + // if (!this.isEnabledForChunk(chunk, compilation)) return; + // if (chunkGraph.getNumberOfEntryModules(chunk) === 0) return; + // set.add(federationStartup); + // }, + // ); + // + // compilation.hooks.runtimeRequirementInTree + // .for(RuntimeGlobals.startupEntrypoint) + // .tap( + // 'StartupChunkDependenciesPlugin', + // (chunk, set, { chunkGraph }) => { + // if (!this.isEnabledForChunk(chunk, compilation)) return; + // set.add(RuntimeGlobals.require); + // set.add(RuntimeGlobals.ensureChunk); + // set.add(RuntimeGlobals.ensureChunkIncludeEntries); + // compilation.addRuntimeModule( + // chunk, + // new StartupEntrypointRuntimeModule(this.asyncChunkLoading), + // ); + // }, + // ); + // + // const { renderStartup } = + // compiler.webpack.javascript.JavascriptModulesPlugin.getCompilationHooks( + // compilation, + // ); + // + // renderStartup.tap( + // 'MfStartupChunkDependenciesPlugin', + // (startupSource, lastInlinedModule, renderContext) => { + // const { chunk, chunkGraph, runtimeTemplate } = renderContext; + // + // + // if (!this.isEnabledForChunk(chunk, compilation)) { + // return startupSource; + // } + // + // if (chunkGraph.getNumberOfEntryModules(chunk) === 0) { + // return startupSource; + // } + // + // const treeRuntimeRequirements = + // chunkGraph.getTreeRuntimeRequirements(chunk); + // const chunkRuntimeRequirements = + // chunkGraph.getChunkRuntimeRequirements(chunk); + // + // const federation = + // chunkRuntimeRequirements.has(federationStartup) || + // treeRuntimeRequirements.has(federationStartup); + // + // if (!federation) { + // return startupSource; + // } + // + // const entryModules = Array.from( + // chunkGraph.getChunkEntryModulesWithChunkGroupIterable(chunk), + // ); + // + // const entryGeneration = runtimeTemplate.outputOptions.module + // ? generateESMEntryStartup + // : generateEntryStartup; + // + // return new compiler.webpack.sources.ConcatSource( + // entryGeneration( + // compilation, + // chunkGraph, + // runtimeTemplate, + // entryModules, + // chunk, + // false, + // ), + // ); + // }, + // ); + // }, + // ); + // } } export default StartupChunkDependenciesPlugin; diff --git a/packages/enhanced/test/ConfigTestCases.basictest.js b/packages/enhanced/test/ConfigTestCases.basictest.js index ce08fc3df27..b57936c2c6e 100644 --- a/packages/enhanced/test/ConfigTestCases.basictest.js +++ b/packages/enhanced/test/ConfigTestCases.basictest.js @@ -14,3 +14,5 @@ const { describeCases } = require('./ConfigTestCases.template'); describeCases({ name: 'ConfigTestCases', }); + +describe('ConfigTestCases', () => {}); From 1610570643b9d922091bb7b1d0388b9c2bdec769 Mon Sep 17 00:00:00 2001 From: ScriptedAlchemy Date: Wed, 5 Feb 2025 19:17:42 -0800 Subject: [PATCH 02/11] fix(enhanced): reverse enabled chunk check logic for runtime embed --- .../src/lib/container/runtime/EmbedFederationRuntimePlugin.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/enhanced/src/lib/container/runtime/EmbedFederationRuntimePlugin.ts b/packages/enhanced/src/lib/container/runtime/EmbedFederationRuntimePlugin.ts index c8ab84e77e6..6c0d9b529f5 100644 --- a/packages/enhanced/src/lib/container/runtime/EmbedFederationRuntimePlugin.ts +++ b/packages/enhanced/src/lib/container/runtime/EmbedFederationRuntimePlugin.ts @@ -77,7 +77,7 @@ class EmbedFederationRuntimePlugin { (startupSource, lastInlinedModule, renderContext) => { const { chunk, chunkGraph } = renderContext; - if (this.isEnabledForChunk(chunk)) { + if (!this.isEnabledForChunk(chunk)) { return startupSource; } From c241fd13127d6619447515aa1150fee28b21799f Mon Sep 17 00:00:00 2001 From: ScriptedAlchemy Date: Wed, 5 Feb 2025 19:29:15 -0800 Subject: [PATCH 03/11] chore: update test options correctly --- packages/enhanced/test/ConfigTestCases.template.js | 4 ++-- .../container/exposed-overridables/webpack.config.js | 2 +- .../configCases/container/module-federation/webpack.config.js | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/enhanced/test/ConfigTestCases.template.js b/packages/enhanced/test/ConfigTestCases.template.js index 0e70c968312..30c6dd68946 100644 --- a/packages/enhanced/test/ConfigTestCases.template.js +++ b/packages/enhanced/test/ConfigTestCases.template.js @@ -95,9 +95,9 @@ const describeCases = (config) => { if (!mfp._options.experiments) { mfp._options.experiments = {}; } - if (config.federation?.federationRuntime) { + if (config.federation?.asyncStartup) { // dont override if explicitly set - if ('federationRuntime' in mfp._options.experiments) { + if ('asyncStartup' in mfp._options.experiments) { } else { Object.assign( mfp._options.experiments, diff --git a/packages/enhanced/test/configCases/container/exposed-overridables/webpack.config.js b/packages/enhanced/test/configCases/container/exposed-overridables/webpack.config.js index 4e71aca7e88..c1ea5c79f39 100644 --- a/packages/enhanced/test/configCases/container/exposed-overridables/webpack.config.js +++ b/packages/enhanced/test/configCases/container/exposed-overridables/webpack.config.js @@ -8,7 +8,7 @@ module.exports = { exposes: { './Button': './Button', }, - experiments: { federationRuntime: false }, + experiments: { asyncStartup: false }, shared: { react: { eager: true, diff --git a/packages/enhanced/test/configCases/container/module-federation/webpack.config.js b/packages/enhanced/test/configCases/container/module-federation/webpack.config.js index 313470f8eda..b1670b4db6c 100644 --- a/packages/enhanced/test/configCases/container/module-federation/webpack.config.js +++ b/packages/enhanced/test/configCases/container/module-federation/webpack.config.js @@ -11,7 +11,7 @@ function createConfig() { filename: 'container.js', library: { type: 'system' }, exposes: ['./other', './self', './dep'], - experiments: { federationRuntime: false }, + experiments: { asyncStartup: false }, remotes: { abc: 'ABC', def: 'DEF', @@ -24,7 +24,7 @@ function createConfig() { filename: 'container2.js', library: { type: 'system' }, exposes: ['./other', './self', './dep'], - experiments: { federationRuntime: false }, + experiments: { asyncStartup: false }, remotes: { abc: 'ABC', def: 'DEF', From 7f1cb0549a18f65233bf3ad777e19a640935cdf1 Mon Sep 17 00:00:00 2001 From: ScriptedAlchemy Date: Wed, 5 Feb 2025 19:40:28 -0800 Subject: [PATCH 04/11] chore: fix node tests --- apps/node-host/src/bootstrap.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/node-host/src/bootstrap.js b/apps/node-host/src/bootstrap.js index 02b82f673fc..39af3d1da44 100644 --- a/apps/node-host/src/bootstrap.js +++ b/apps/node-host/src/bootstrap.js @@ -38,8 +38,8 @@ app.get('/api', async (req, res) => { res.send({ message: 'Welcome to node-host!', remotes: { - // node_remote: await remoteMsg, - // node_local_remote, + node_remote: await remoteMsg, + node_local_remote, }, }); }); From 061203912d3a00f275d26492984d8ab44c4d07f1 Mon Sep 17 00:00:00 2001 From: ScriptedAlchemy Date: Wed, 5 Feb 2025 19:57:58 -0800 Subject: [PATCH 05/11] chore: use async startup on node --- apps/node-host/src/bootstrap.js | 93 ------------------------------- apps/node-host/src/main.js | 94 +++++++++++++++++++++++++++++++- apps/node-host/webpack.config.js | 6 +- 3 files changed, 96 insertions(+), 97 deletions(-) diff --git a/apps/node-host/src/bootstrap.js b/apps/node-host/src/bootstrap.js index 39af3d1da44..e69de29bb2d 100644 --- a/apps/node-host/src/bootstrap.js +++ b/apps/node-host/src/bootstrap.js @@ -1,93 +0,0 @@ -/** - * This is not a production server yet! - * This is only a minimal backend to get started. - */ - -import express from 'express'; -import * as path from 'path'; -import node_local_remote from 'node_local_remote/test'; -import { registerRemotes, loadRemote } from '@module-federation/runtime'; - -registerRemotes([ - { - name: 'node_dynamic_remote', - entry: 'http://localhost:3026/remoteEntry.js', - }, -]); - -const getMemoryUsage = () => { - const formatSize = (bytes) => `${(bytes / 1024 / 1024).toFixed(2)} MB`; - - const memory = process.memoryUsage(); - return `Time: ${new Date()}\nheap total: ${formatSize( - memory.heapTotal, - )} heapUsed: ${formatSize(memory.heapUsed)} rss: ${formatSize(memory.rss)}`; -}; - -const remoteMsg = import('node_remote/test').then((m) => { - console.log('\x1b[32m%s\x1b[0m', m.default || m); - return m.default || m; -}); -console.log('\x1b[32m%s\x1b[0m', node_local_remote); - -const app = express(); - -app.use('/assets', express.static(path.join(__dirname, 'assets'))); - -app.get('/api', async (req, res) => { - res.send({ - message: 'Welcome to node-host!', - remotes: { - node_remote: await remoteMsg, - node_local_remote, - }, - }); -}); - -app.get('/dynamic-remote', async (req, res) => { - const dynamicRemote = await loadRemote('node_dynamic_remote/test-with-axios'); - - res.send({ - message: 'dynamic remote', - dynamicRemote: dynamicRemote(), - }); -}); - -app.get('/upgrade-remote', async (req, res) => { - registerRemotes( - [ - { - name: 'node_dynamic_remote', - entry: 'http://localhost:3027/remoteEntry.js', - }, - ], - { force: true }, - ); - - res.send({ - message: 'Upgrade success!', - }); -}); - -app.get('/memory-cache', async (req, res) => { - const memoryUsage = getMemoryUsage(); - console.log(memoryUsage); - res.send({ - message: 'memory-cache', - memoryUsage: memoryUsage, - }); -}); - -app.get('/federation-info', async (req, res) => { - console.log(global.__FEDERATION__); - console.log(global.__FEDERATION__.__INSTANCES__[0].moduleCache); - res.send({ - message: 'federation info will be output in terminal !', - }); -}); - -const port = process.env.PORT || 3333; -const server = app.listen(port, () => { - console.log(`Listening at http://localhost:${port}/api`); -}); -server.on('error', console.error); diff --git a/apps/node-host/src/main.js b/apps/node-host/src/main.js index b93c7a0268a..39af3d1da44 100644 --- a/apps/node-host/src/main.js +++ b/apps/node-host/src/main.js @@ -1 +1,93 @@ -import('./bootstrap'); +/** + * This is not a production server yet! + * This is only a minimal backend to get started. + */ + +import express from 'express'; +import * as path from 'path'; +import node_local_remote from 'node_local_remote/test'; +import { registerRemotes, loadRemote } from '@module-federation/runtime'; + +registerRemotes([ + { + name: 'node_dynamic_remote', + entry: 'http://localhost:3026/remoteEntry.js', + }, +]); + +const getMemoryUsage = () => { + const formatSize = (bytes) => `${(bytes / 1024 / 1024).toFixed(2)} MB`; + + const memory = process.memoryUsage(); + return `Time: ${new Date()}\nheap total: ${formatSize( + memory.heapTotal, + )} heapUsed: ${formatSize(memory.heapUsed)} rss: ${formatSize(memory.rss)}`; +}; + +const remoteMsg = import('node_remote/test').then((m) => { + console.log('\x1b[32m%s\x1b[0m', m.default || m); + return m.default || m; +}); +console.log('\x1b[32m%s\x1b[0m', node_local_remote); + +const app = express(); + +app.use('/assets', express.static(path.join(__dirname, 'assets'))); + +app.get('/api', async (req, res) => { + res.send({ + message: 'Welcome to node-host!', + remotes: { + node_remote: await remoteMsg, + node_local_remote, + }, + }); +}); + +app.get('/dynamic-remote', async (req, res) => { + const dynamicRemote = await loadRemote('node_dynamic_remote/test-with-axios'); + + res.send({ + message: 'dynamic remote', + dynamicRemote: dynamicRemote(), + }); +}); + +app.get('/upgrade-remote', async (req, res) => { + registerRemotes( + [ + { + name: 'node_dynamic_remote', + entry: 'http://localhost:3027/remoteEntry.js', + }, + ], + { force: true }, + ); + + res.send({ + message: 'Upgrade success!', + }); +}); + +app.get('/memory-cache', async (req, res) => { + const memoryUsage = getMemoryUsage(); + console.log(memoryUsage); + res.send({ + message: 'memory-cache', + memoryUsage: memoryUsage, + }); +}); + +app.get('/federation-info', async (req, res) => { + console.log(global.__FEDERATION__); + console.log(global.__FEDERATION__.__INSTANCES__[0].moduleCache); + res.send({ + message: 'federation info will be output in terminal !', + }); +}); + +const port = process.env.PORT || 3333; +const server = app.listen(port, () => { + console.log(`Listening at http://localhost:${port}/api`); +}); +server.on('error', console.error); diff --git a/apps/node-host/webpack.config.js b/apps/node-host/webpack.config.js index 53f5f4e452e..77626757c55 100644 --- a/apps/node-host/webpack.config.js +++ b/apps/node-host/webpack.config.js @@ -20,12 +20,12 @@ module.exports = composePlugins(withNx(), async (config) => { runtimePlugins: [ require.resolve('@module-federation/node/runtimePlugin'), ], + experiments: { + asyncStartup: true, + }, remotes: { node_local_remote: 'commonjs ../../node-local-remote/dist/remoteEntry.js', - // node_local_remote: '__webpack_require__.federation.instance.moduleCache.get("node_local_remote")', - // node_remote: - // '__webpack_require__.federation.instance.moduleCache.get("node_remote")@http://localhost:3002/remoteEntry.js', node_remote: 'node_remote@http://localhost:3022/remoteEntry.js', }, }), From 60297ede96b911390e13f0d626a68e38c2f70847 Mon Sep 17 00:00:00 2001 From: ScriptedAlchemy Date: Wed, 5 Feb 2025 20:02:17 -0800 Subject: [PATCH 06/11] refactor(enhanced): streamline EmbedFederationRuntimePlugin logic --- .../runtime/EmbedFederationRuntimePlugin.ts | 105 +++++------------- 1 file changed, 25 insertions(+), 80 deletions(-) diff --git a/packages/enhanced/src/lib/container/runtime/EmbedFederationRuntimePlugin.ts b/packages/enhanced/src/lib/container/runtime/EmbedFederationRuntimePlugin.ts index 6c0d9b529f5..8ed75bb2698 100644 --- a/packages/enhanced/src/lib/container/runtime/EmbedFederationRuntimePlugin.ts +++ b/packages/enhanced/src/lib/container/runtime/EmbedFederationRuntimePlugin.ts @@ -17,14 +17,14 @@ const federationGlobal = getFederationGlobalScope(RuntimeGlobals); interface EmbedFederationRuntimePluginOptions { /** - * Whether to enable runtime module embedding for all chunks - * If false, will only embed for chunks that explicitly require it + * Whether to enable runtime module embedding for all chunks. + * If false, only chunks that explicitly require it will be embedded. */ enableForAllChunks?: boolean; } /** - * Plugin that handles embedding of Module Federation runtime code into chunks. + * Plugin that embeds Module Federation runtime code into chunks. * It ensures proper initialization of federated modules and manages runtime requirements. */ class EmbedFederationRuntimePlugin { @@ -39,16 +39,16 @@ class EmbedFederationRuntimePlugin { } /** - * Determines if runtime embedding should be enabled for a given chunk + * Determines if runtime embedding should be enabled for a given chunk. */ private isEnabledForChunk(chunk: Chunk): boolean { - if (this.options.enableForAllChunks) return true; + // Disable for our special "build time chunk" if (chunk.id === 'build time chunk') return false; - return chunk.hasRuntime(); + return this.options.enableForAllChunks || chunk.hasRuntime(); } /** - * Checks if a compilation hook has already been tapped by this plugin + * Checks if a hook has already been tapped by this plugin. */ private isHookAlreadyTapped( taps: Array<{ name: string }>, @@ -58,15 +58,17 @@ class EmbedFederationRuntimePlugin { } apply(compiler: Compiler): void { - // Prevent double application of plugin + // Prevent double application of the plugin. const compilationTaps = compiler.hooks.thisCompilation.taps || []; if (this.isHookAlreadyTapped(compilationTaps, PLUGIN_NAME)) { return; } + // Tap into the compilation to modify renderStartup and runtime requirements. compiler.hooks.thisCompilation.tap( PLUGIN_NAME, - (compilation /*: Compilation */) => { + (compilation: Compilation) => { + // --- Part 1: Modify renderStartup to append a startup call when none is added automatically --- const { renderStartup } = compiler.webpack.javascript.JavascriptModulesPlugin.getCompilationHooks( compilation, @@ -74,7 +76,7 @@ class EmbedFederationRuntimePlugin { renderStartup.tap( PLUGIN_NAME, - (startupSource, lastInlinedModule, renderContext) => { + (startupSource, _lastInlinedModule, renderContext) => { const { chunk, chunkGraph } = renderContext; if (!this.isEnabledForChunk(chunk)) { @@ -85,11 +87,9 @@ class EmbedFederationRuntimePlugin { chunkGraph.getTreeRuntimeRequirements(chunk); const entryModuleCount = chunkGraph.getNumberOfEntryModules(chunk); - // The original renderBootstrap pushes a startup call automatically when either: + // The default renderBootstrap automatically pushes a startup call when either: // - There is at least one entry module, OR // - runtimeRequirements.has(RuntimeGlobals.startupNoDefault) is true. - // (In all other cases—even if one of the startup keys is set—the startup function is defined - // but not called.) if ( entryModuleCount > 0 || runtimeRequirements.has(RuntimeGlobals.startupNoDefault) @@ -105,104 +105,49 @@ class EmbedFederationRuntimePlugin { ); }, ); - }, - ); - // compiler.hooks.thisCompilation.tap( - // PLUGIN_NAME, - // (compilation: Compilation) => { - // const { renderStartup } = - // compiler.webpack.javascript.JavascriptModulesPlugin.getCompilationHooks( - // compilation, - // ); - // - // // Prevent double tapping of renderStartup hook - // const startupTaps = renderStartup.taps || []; - // if (this.isHookAlreadyTapped(startupTaps, PLUGIN_NAME)) { - // return; - // } - // - // renderStartup.tap( - // PLUGIN_NAME, - // (startupSource, lastInlinedModule, renderContext) => { - // const { chunk, chunkGraph } = renderContext; - // - // if (!this.isEnabledForChunk(chunk)) { - // return startupSource; - // } - // - // const treeRuntimeRequirements = - // chunkGraph.getTreeRuntimeRequirements(chunk); - // const chunkRuntimeRequirements = - // chunkGraph.getChunkRuntimeRequirements(chunk); - // - // const federation = - // chunkRuntimeRequirements.has(federationGlobal) || - // treeRuntimeRequirements.has(federationGlobal); - // - // if (!federation) { - // return startupSource; - // } - // - // // Skip if chunk was already processed - // if (this.processedChunks.get(chunk)) { - // return startupSource; - // } - // - // // Mark chunk as processed - // this.processedChunks.set(chunk, true); - // debugger; - // // Add basic startup call - // return new ConcatSource( - // startupSource, - // // add only when not added already - // `${RuntimeGlobals.startup}();\n`, - // ); - // }, - // ); - // }, - // ); - - compiler.hooks.thisCompilation.tap( - PLUGIN_NAME, - (compilation: Compilation) => { - const hooks = FederationModulesPlugin.getCompilationHooks(compilation); + // --- Part 2: Embed Federation Runtime Module and adjust runtime requirements --- + const federationHooks = + FederationModulesPlugin.getCompilationHooks(compilation); const containerEntrySet: Set< ContainerEntryDependency | FederationRuntimeDependency > = new Set(); - // Proactively add startupOnlyBefore to all chunks + // Proactively add startupOnlyBefore target chunks. compilation.hooks.additionalChunkRuntimeRequirements.tap( PLUGIN_NAME, (chunk: Chunk, runtimeRequirements: Set) => { + if (!this.isEnabledForChunk(chunk)) { + return; + } runtimeRequirements.add(RuntimeGlobals.startupOnlyBefore); }, ); - hooks.addFederationRuntimeModule.tap( + // Collect federation runtime dependencies. + federationHooks.addFederationRuntimeModule.tap( PLUGIN_NAME, (dependency: FederationRuntimeDependency) => { containerEntrySet.add(dependency); }, ); + // Handle additional runtime requirements when federation is enabled. const handleRuntimeRequirements = ( chunk: Chunk, runtimeRequirements: Set, ) => { - runtimeRequirements.add(RuntimeGlobals.startupOnlyBefore); - if (!this.isEnabledForChunk(chunk)) { return; } + // Skip if already processed or if not a federation chunk. if (runtimeRequirements.has('embeddedFederationRuntime')) return; if (!runtimeRequirements.has(federationGlobal)) { return; } - runtimeRequirements.add(RuntimeGlobals.startupOnlyBefore); + // Mark as embedded and add the runtime module. runtimeRequirements.add('embeddedFederationRuntime'); - const runtimeModule = new EmbedFederationRuntimeModule( containerEntrySet, ); From e019364d07be332724cb4a6bc54087b0a1c3ec2f Mon Sep 17 00:00:00 2001 From: ScriptedAlchemy Date: Wed, 5 Feb 2025 20:15:26 -0800 Subject: [PATCH 07/11] chore: use async startup on node --- .github/workflows/e2e-next-dev.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/e2e-next-dev.yml b/.github/workflows/e2e-next-dev.yml index 00f4d9e69fe..5bfb31f25e4 100644 --- a/.github/workflows/e2e-next-dev.yml +++ b/.github/workflows/e2e-next-dev.yml @@ -48,6 +48,7 @@ jobs: - name: E2E Test for Next.js Dev if: steps.check-ci.outcome == 'success' run: | + npx kill-port 3000,3001,3002 && pnpm run app:next:dev & sleep 1 && npx wait-on tcp:3001 && From 4d1a6740847c74dd67bbfe5be3d3072f10f06c50 Mon Sep 17 00:00:00 2001 From: ScriptedAlchemy Date: Fri, 7 Feb 2025 11:20:10 -0800 Subject: [PATCH 08/11] fix(enhanced): add runtime startup to entry initialization --- .../runtime/EmbedFederationRuntimeModule.ts | 9 ++++- .../src/lib/startup/StartupHelpers.ts | 1 + .../InvertedContainerRuntimeModule.ts | 33 +++++++++++++------ 3 files changed, 32 insertions(+), 11 deletions(-) diff --git a/packages/enhanced/src/lib/container/runtime/EmbedFederationRuntimeModule.ts b/packages/enhanced/src/lib/container/runtime/EmbedFederationRuntimeModule.ts index 93a5ae96d4f..ddbf900d619 100644 --- a/packages/enhanced/src/lib/container/runtime/EmbedFederationRuntimeModule.ts +++ b/packages/enhanced/src/lib/container/runtime/EmbedFederationRuntimeModule.ts @@ -63,9 +63,16 @@ class EmbedFederationRuntimeModule extends RuntimeModule { const result = Template.asString([ `var oldStartup = ${RuntimeGlobals.startup};`, + `var hasRun = false;`, `${RuntimeGlobals.startup} = ${compilation.runtimeTemplate.basicFunction( '', - [`${initRuntimeModuleGetter};`, `return oldStartup();`], + [ + `if (!hasRun) {`, + ` hasRun = true;`, + ` ${initRuntimeModuleGetter};`, + `}`, + `return oldStartup();`, + ], )};`, ]); this._cachedGeneratedCode = result; diff --git a/packages/enhanced/src/lib/startup/StartupHelpers.ts b/packages/enhanced/src/lib/startup/StartupHelpers.ts index e43b28dd3a2..223671e4b6b 100644 --- a/packages/enhanced/src/lib/startup/StartupHelpers.ts +++ b/packages/enhanced/src/lib/startup/StartupHelpers.ts @@ -49,6 +49,7 @@ export const generateEntryStartup = ( '', '\n', 'var promises = [];', + '__webpack_require__.x();', ]; const treeRuntimeRequirements = chunkGraph.getTreeRuntimeRequirements(chunk); diff --git a/packages/nextjs-mf/src/plugins/container/InvertedContainerRuntimeModule.ts b/packages/nextjs-mf/src/plugins/container/InvertedContainerRuntimeModule.ts index b87d149bd55..f171bfc060b 100644 --- a/packages/nextjs-mf/src/plugins/container/InvertedContainerRuntimeModule.ts +++ b/packages/nextjs-mf/src/plugins/container/InvertedContainerRuntimeModule.ts @@ -55,16 +55,29 @@ class InvertedContainerRuntimeModule extends RuntimeModule { const nameJSON = JSON.stringify(containerEntryModule._name); return Template.asString([ - `var innerRemote;`, - `function attachRemote () {`, - Template.indent([ - `innerRemote = ${initRuntimeModuleGetter};`, - `var gs = ${RuntimeGlobals.global} || globalThis`, - `gs[${nameJSON}] = innerRemote`, - `return innerRemote;`, - ]), - `};`, - `attachRemote();`, + `var prevStartup = ${RuntimeGlobals.startup};`, + `var hasRun = false;`, + `var cachedRemote;`, + `${RuntimeGlobals.startup} = ${compilation.runtimeTemplate.basicFunction( + '', + Template.asString([ + `if (!hasRun) {`, + ` hasRun = true;`, + ` console.log("startup");`, + ` if (typeof prevStartup === 'function') {`, + ` console.log('running prevStartup');`, + ` prevStartup();`, + ` }`, + ` cachedRemote = ${initRuntimeModuleGetter};`, + ` var gs = ${RuntimeGlobals.global} || globalThis;`, + ` gs[${nameJSON}] = cachedRemote;`, + ` console.log(cachedRemote);`, + `} else if (typeof prevStartup === 'function') {`, + ` prevStartup();`, + `}`, + `return cachedRemote;`, + ]), + )};`, ]); } } From f7b669f442a27494a87b81b485f3af7da5b830e0 Mon Sep 17 00:00:00 2001 From: ScriptedAlchemy Date: Fri, 7 Feb 2025 11:40:41 -0800 Subject: [PATCH 09/11] chore: clean up old code --- package.json | 2 +- .../MfStartupChunkDependenciesPlugin.ts | 113 +----------------- 2 files changed, 5 insertions(+), 110 deletions(-) diff --git a/package.json b/package.json index 08c8159c3b0..11d9d8d63ea 100644 --- a/package.json +++ b/package.json @@ -41,7 +41,7 @@ "app:node:dev": "nx run-many --target=serve --parallel=10 --configuration=development -p node-host,node-local-remote,node-remote,node-dynamic-remote-new-version,node-dynamic-remote", "app:runtime:dev": "nx run-many --target=serve -p 3005-runtime-host,3006-runtime-remote,3007-runtime-remote", "app:router:dev": "nx run-many --target=serve --parallel=10 --projects='router-*'", - "app:manifest:dev": "nx run-many --target=serve --configuration=development --parallel=100 -p modernjs,manifest-webpack-host,3009-webpack-provider,3010-rspack-provider,3011-rspack-manifest-provider,3012-rspack-js-entry-provider --skip-nx-cache", + "app:manifest:dev": "nx run-many --target=serve --configuration=development --parallel=100 -p modernjs,manifest-webpack-host,3009-webpack-provider,3010-rspack-provider,3011-rspack-manifest-provider,3012-rspack-js-entry-provider", "app:manifest:prod": "nx run-many --target=serve --configuration=production --parallel=100 -p modernjs,manifest-webpack-host,3009-webpack-provider,3010-rspack-provider,3011-rspack-manifest-provider,3012-rspack-js-entry-provider", "app:ts:dev": "nx run-many --target=serve -p react_ts_host,react_ts_nested_remote,react_ts_remote", "app:modern:dev": "nx run-many --target=serve --parallel=10 --configuration=development -p modernjs-ssr-dynamic-nested-remote,modernjs-ssr-dynamic-remote,modernjs-ssr-dynamic-remote-new-version,modernjs-ssr-host,modernjs-ssr-nested-remote,modernjs-ssr-remote,modernjs-ssr-remote-new-version", diff --git a/packages/enhanced/src/lib/startup/MfStartupChunkDependenciesPlugin.ts b/packages/enhanced/src/lib/startup/MfStartupChunkDependenciesPlugin.ts index 0dc567070d6..46b8b563d81 100644 --- a/packages/enhanced/src/lib/startup/MfStartupChunkDependenciesPlugin.ts +++ b/packages/enhanced/src/lib/startup/MfStartupChunkDependenciesPlugin.ts @@ -12,7 +12,6 @@ import ContainerEntryModule from '../container/ContainerEntryModule'; const { RuntimeGlobals } = require( normalizeWebpackPath('webpack'), ) as typeof import('webpack'); - const StartupEntrypointRuntimeModule = require( normalizeWebpackPath('webpack/lib/runtime/StartupEntrypointRuntimeModule'), ) as typeof import('webpack/lib/runtime/StartupEntrypointRuntimeModule'); @@ -43,7 +42,7 @@ class StartupChunkDependenciesPlugin { compiler.hooks.thisCompilation.tap( 'MfStartupChunkDependenciesPlugin', (compilation) => { - // Add additional runtime requirements on the tree level. + // Add additional runtime requirements at the tree level. compilation.hooks.additionalTreeRuntimeRequirements.tap( 'StartupChunkDependenciesPlugin', (chunk, set, { chunkGraph }) => { @@ -56,7 +55,7 @@ class StartupChunkDependenciesPlugin { }, ); - // Add additional runtime requirements on the chunk level if there are entry modules. + // Add additional runtime requirements at the chunk level if there are entry modules. compilation.hooks.additionalChunkRuntimeRequirements.tap( 'MfStartupChunkDependenciesPlugin', (chunk, set, { chunkGraph }) => { @@ -66,7 +65,7 @@ class StartupChunkDependenciesPlugin { }, ); - // When the startupEntrypoint runtime requirement is in the tree, add additional keys and runtime module. + // When the startupEntrypoint requirement is present, add extra keys and a runtime module. compilation.hooks.runtimeRequirementInTree .for(RuntimeGlobals.startupEntrypoint) .tap( @@ -83,7 +82,7 @@ class StartupChunkDependenciesPlugin { }, ); - // Replace the generated startup with our custom version when there are entry modules. + // Replace the generated startup with a custom version if entry modules exist. const { renderStartup } = compiler.webpack.javascript.JavascriptModulesPlugin.getCompilationHooks( compilation, @@ -94,17 +93,14 @@ class StartupChunkDependenciesPlugin { (startupSource, lastInlinedModule, renderContext) => { const { chunk, chunkGraph, runtimeTemplate } = renderContext; - // Only modify chunks that are enabled. if (!this.isEnabledForChunk(chunk, compilation)) { return startupSource; } - // If no entry modules, do nothing. if (chunkGraph.getNumberOfEntryModules(chunk) === 0) { return startupSource; } - // Check the runtime requirements for federation. const treeRuntimeRequirements = chunkGraph.getTreeRuntimeRequirements(chunk); const chunkRuntimeRequirements = @@ -114,22 +110,18 @@ class StartupChunkDependenciesPlugin { chunkRuntimeRequirements.has(federationStartup) || treeRuntimeRequirements.has(federationStartup); - // If the federation requirement is not present, leave the startup as-is. if (!federation) { return startupSource; } - // Otherwise, get the entry modules and generate a custom startup. const entryModules = Array.from( chunkGraph.getChunkEntryModulesWithChunkGroupIterable(chunk), ); - // Choose between ESM and non-ESM startup generation based on the runtimeTemplate. const entryGeneration = runtimeTemplate.outputOptions.module ? generateESMEntryStartup : generateEntryStartup; - // Replace the startup by returning a new ConcatSource. return new compiler.webpack.sources.ConcatSource( entryGeneration( compilation, @@ -145,103 +137,6 @@ class StartupChunkDependenciesPlugin { }, ); } - - // apply(compiler: Compiler): void { - // compiler.hooks.thisCompilation.tap( - // 'MfStartupChunkDependenciesPlugin', - // (compilation) => { - // compilation.hooks.additionalTreeRuntimeRequirements.tap( - // 'StartupChunkDependenciesPlugin', - // (chunk, set, { chunkGraph }) => { - // if (!this.isEnabledForChunk(chunk, compilation)) return; - // if (chunk.hasRuntime()) { - // set.add(RuntimeGlobals.startupEntrypoint); - // set.add(RuntimeGlobals.ensureChunk); - // set.add(RuntimeGlobals.ensureChunkIncludeEntries); - // } - // }, - // ); - // - // compilation.hooks.additionalChunkRuntimeRequirements.tap( - // 'MfStartupChunkDependenciesPlugin', - // (chunk, set, { chunkGraph }) => { - // if (!this.isEnabledForChunk(chunk, compilation)) return; - // if (chunkGraph.getNumberOfEntryModules(chunk) === 0) return; - // set.add(federationStartup); - // }, - // ); - // - // compilation.hooks.runtimeRequirementInTree - // .for(RuntimeGlobals.startupEntrypoint) - // .tap( - // 'StartupChunkDependenciesPlugin', - // (chunk, set, { chunkGraph }) => { - // if (!this.isEnabledForChunk(chunk, compilation)) return; - // set.add(RuntimeGlobals.require); - // set.add(RuntimeGlobals.ensureChunk); - // set.add(RuntimeGlobals.ensureChunkIncludeEntries); - // compilation.addRuntimeModule( - // chunk, - // new StartupEntrypointRuntimeModule(this.asyncChunkLoading), - // ); - // }, - // ); - // - // const { renderStartup } = - // compiler.webpack.javascript.JavascriptModulesPlugin.getCompilationHooks( - // compilation, - // ); - // - // renderStartup.tap( - // 'MfStartupChunkDependenciesPlugin', - // (startupSource, lastInlinedModule, renderContext) => { - // const { chunk, chunkGraph, runtimeTemplate } = renderContext; - // - // - // if (!this.isEnabledForChunk(chunk, compilation)) { - // return startupSource; - // } - // - // if (chunkGraph.getNumberOfEntryModules(chunk) === 0) { - // return startupSource; - // } - // - // const treeRuntimeRequirements = - // chunkGraph.getTreeRuntimeRequirements(chunk); - // const chunkRuntimeRequirements = - // chunkGraph.getChunkRuntimeRequirements(chunk); - // - // const federation = - // chunkRuntimeRequirements.has(federationStartup) || - // treeRuntimeRequirements.has(federationStartup); - // - // if (!federation) { - // return startupSource; - // } - // - // const entryModules = Array.from( - // chunkGraph.getChunkEntryModulesWithChunkGroupIterable(chunk), - // ); - // - // const entryGeneration = runtimeTemplate.outputOptions.module - // ? generateESMEntryStartup - // : generateEntryStartup; - // - // return new compiler.webpack.sources.ConcatSource( - // entryGeneration( - // compilation, - // chunkGraph, - // runtimeTemplate, - // entryModules, - // chunk, - // false, - // ), - // ); - // }, - // ); - // }, - // ); - // } } export default StartupChunkDependenciesPlugin; From 9bbacc5665ccffd1cc244926ac8f031f1a067d65 Mon Sep 17 00:00:00 2001 From: ScriptedAlchemy Date: Mon, 10 Feb 2025 13:13:42 -0800 Subject: [PATCH 10/11] chore: clean up old code --- .../InvertedContainerRuntimeModule.ts | 26 +++++++++---------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/packages/nextjs-mf/src/plugins/container/InvertedContainerRuntimeModule.ts b/packages/nextjs-mf/src/plugins/container/InvertedContainerRuntimeModule.ts index f171bfc060b..8d2dffe85e7 100644 --- a/packages/nextjs-mf/src/plugins/container/InvertedContainerRuntimeModule.ts +++ b/packages/nextjs-mf/src/plugins/container/InvertedContainerRuntimeModule.ts @@ -41,7 +41,7 @@ class InvertedContainerRuntimeModule extends RuntimeModule { if ( compilation.chunkGraph.isEntryModuleInChunk(containerEntryModule, chunk) ) { - // dont apply to remote entry itself + // Don't apply to the remote entry itself return ''; } const initRuntimeModuleGetter = compilation.runtimeTemplate.moduleRaw({ @@ -62,20 +62,20 @@ class InvertedContainerRuntimeModule extends RuntimeModule { '', Template.asString([ `if (!hasRun) {`, - ` hasRun = true;`, - ` console.log("startup");`, - ` if (typeof prevStartup === 'function') {`, - ` console.log('running prevStartup');`, - ` prevStartup();`, - ` }`, - ` cachedRemote = ${initRuntimeModuleGetter};`, - ` var gs = ${RuntimeGlobals.global} || globalThis;`, - ` gs[${nameJSON}] = cachedRemote;`, - ` console.log(cachedRemote);`, + Template.indent( + Template.asString([ + `hasRun = true;`, + `if (typeof prevStartup === 'function') {`, + Template.indent(Template.asString([`prevStartup();`])), + `}`, + `cachedRemote = ${initRuntimeModuleGetter};`, + `var gs = ${RuntimeGlobals.global} || globalThis;`, + `gs[${nameJSON}] = cachedRemote;`, + ]), + ), `} else if (typeof prevStartup === 'function') {`, - ` prevStartup();`, + Template.indent(`prevStartup();`), `}`, - `return cachedRemote;`, ]), )};`, ]); From b40d6ea66882d9d62e308f9c12fa10070b0b9ec6 Mon Sep 17 00:00:00 2001 From: ScriptedAlchemy Date: Mon, 10 Feb 2025 15:28:20 -0800 Subject: [PATCH 11/11] chore: add logger about single runtime --- .../container/InvertedContainerRuntimeModule.ts | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/packages/nextjs-mf/src/plugins/container/InvertedContainerRuntimeModule.ts b/packages/nextjs-mf/src/plugins/container/InvertedContainerRuntimeModule.ts index 8d2dffe85e7..f10204fa43b 100644 --- a/packages/nextjs-mf/src/plugins/container/InvertedContainerRuntimeModule.ts +++ b/packages/nextjs-mf/src/plugins/container/InvertedContainerRuntimeModule.ts @@ -1,6 +1,4 @@ import { normalizeWebpackPath } from '@module-federation/sdk/normalize-webpack-path'; -import type { Module } from 'webpack'; -import { container } from '@module-federation/enhanced'; import type ContainerEntryModule from '@module-federation/enhanced/src/lib/container/ContainerEntryModule'; const { RuntimeModule, Template, RuntimeGlobals } = require( normalizeWebpackPath('webpack'), @@ -27,6 +25,15 @@ class InvertedContainerRuntimeModule extends RuntimeModule { if (chunk.runtime === 'webpack-api-runtime') { return ''; } + + const runtimeChunk = compilation.options.optimization?.runtimeChunk; + if (runtimeChunk === 'single' || typeof runtimeChunk === 'object') { + const logger = compilation.getLogger('InvertedContainerRuntimeModule'); + logger.info( + 'Runtime chunk is set to single. Consider adding runtime: false to your ModuleFederationPlugin configuration to prevent runtime conflicts.', + ); + } + let containerEntryModule; for (const containerDep of this.options.containers) { const mod = compilation.moduleGraph.getModule(containerDep);