From 9f21893f4234f43d7aaa44c563a8b6e40f9bd90b Mon Sep 17 00:00:00 2001 From: ScriptedAlchemy Date: Tue, 2 Apr 2024 17:49:26 -0700 Subject: [PATCH] Revert "chore(enhanced): adjust add federation init process (#2035)" This reverts commit df3ef24c --- .../src/lib/container/ContainerEntryModule.ts | 1 + .../src/lib/container/ContainerPlugin.ts | 66 +---- .../HoistContainerReferencesPlugin.ts | 278 +++--------------- .../lib/container/ModuleFederationPlugin.ts | 4 +- .../container/runtime/FederationInitModule.ts | 137 --------- .../runtime/FederationRuntimePlugin.ts | 161 +++++----- .../apply-client-plugins.ts | 2 + .../apply-server-plugins.ts | 2 + .../container/HoistPseudoEagerModules.ts | 94 ++++++ .../RemoveEagerModulesFromRuntimePlugin.ts | 97 ++++++ pnpm-lock.yaml | 53 ++-- 11 files changed, 343 insertions(+), 552 deletions(-) delete mode 100644 packages/enhanced/src/lib/container/runtime/FederationInitModule.ts create mode 100644 packages/nextjs-mf/src/plugins/container/HoistPseudoEagerModules.ts create mode 100644 packages/nextjs-mf/src/plugins/container/RemoveEagerModulesFromRuntimePlugin.ts diff --git a/packages/enhanced/src/lib/container/ContainerEntryModule.ts b/packages/enhanced/src/lib/container/ContainerEntryModule.ts index 7e3d063b285..363f7b692e0 100644 --- a/packages/enhanced/src/lib/container/ContainerEntryModule.ts +++ b/packages/enhanced/src/lib/container/ContainerEntryModule.ts @@ -189,6 +189,7 @@ class ContainerEntryModule extends Module { // @ts-ignore new EntryDependency(this._injectRuntimeEntry), ); + callback(); } diff --git a/packages/enhanced/src/lib/container/ContainerPlugin.ts b/packages/enhanced/src/lib/container/ContainerPlugin.ts index 5c2bc4af6e7..2bf0c22ecf5 100644 --- a/packages/enhanced/src/lib/container/ContainerPlugin.ts +++ b/packages/enhanced/src/lib/container/ContainerPlugin.ts @@ -149,8 +149,6 @@ class ContainerPlugin { if (!useModuleFederationPlugin) { ContainerPlugin.patchChunkSplit(compiler, this._options.name); - ContainerPlugin.patchChunkSplit(compiler, 'federation-runtime'); - ContainerPlugin.patchChunkSplit(compiler, 'mfp-runtime-plugins'); } const federationRuntimePluginInstance = new FederationRuntimePlugin(); federationRuntimePluginInstance.apply(compiler); @@ -166,16 +164,6 @@ class ContainerPlugin { ) { compiler.options.output.enabledLibraryTypes.push(library.type); } - const hasSingleRuntimeChunk = compiler.options?.optimization?.runtimeChunk; - - new compiler.webpack.EntryPlugin( - compiler.options.context || '', - federationRuntimePluginInstance.entryFilePath, - { - name, - runtime: hasSingleRuntimeChunk ? false : runtime, - }, - ).apply(compiler); compiler.hooks.make.tapAsync(PLUGIN_NAME, (compilation, callback) => { const dep = new ContainerEntryDependency( @@ -211,45 +199,21 @@ class ContainerPlugin { ); // Function to add entry for undefined runtime - const addEntryToSingleRuntimeChunk = async () => { - const entries = - typeof compiler.options.entry === 'function' - ? await compiler.options.entry() - : compiler.options.entry; - const runtimes: Set = new Set(); - - Object.keys(entries).forEach((key) => { - if (entries[key].runtime) { - runtimes.add(entries[key].runtime); - } else if (entries[key].runtime === undefined) { - runtimes.add(undefined); - } - }); - - //Add container entry for each runtime that exists - for (const runtime of runtimes) { - const name = runtime - ? 'federation-runtime-' + runtime - : 'federation-runtime'; - await new Promise((resolve, reject) => { - compilation.addEntry( - compilation.options.context || '', - //@ts-ignore - dep, - { - name: name, // merge container into federation entrypoint added to compilation - runtime: runtime, - library, - }, - (error: WebpackError | null | undefined) => { - if (error) return reject(error); - resolve(true); - }, - ); - }).catch(callback); - } - - callback(); + const addEntryToSingleRuntimeChunk = () => { + compilation.addEntry( + compilation.options.context || '', + //@ts-ignore + dep, + { + name: name ? name + '_partial' : undefined, // give unique name name + runtime: undefined, + library, + }, + (error: WebpackError | null | undefined) => { + if (error) return callback(error); + callback(); + }, + ); }; }); diff --git a/packages/enhanced/src/lib/container/HoistContainerReferencesPlugin.ts b/packages/enhanced/src/lib/container/HoistContainerReferencesPlugin.ts index fd6b28f7c95..bb4bc008cee 100644 --- a/packages/enhanced/src/lib/container/HoistContainerReferencesPlugin.ts +++ b/packages/enhanced/src/lib/container/HoistContainerReferencesPlugin.ts @@ -4,116 +4,24 @@ import type { Chunk, WebpackPluginInstance, } from 'webpack'; -import { normalizeWebpackPath } from '@module-federation/sdk/normalize-webpack-path'; import ContainerEntryModule from './ContainerEntryModule'; -const runtime = require( - normalizeWebpackPath('webpack/lib/util/runtime'), -) as typeof import('webpack/lib/util/runtime'); - -export class HoistContainerReferencesPlugin implements WebpackPluginInstance { - private integratedChunks: Set = new Set(); - integrateChunks( - chunkA: Chunk, - chunkB: Chunk, - compilation: Compilation, - ): void { - const { chunkGraph, compiler } = compilation; - // Merge id name hints - for (const hint of chunkB.idNameHints) { - chunkA.idNameHints.add(hint); - } - this.integratedChunks.add(chunkB); - // Merge runtime - //@ts-ignore - chunkA.runtime = runtime.mergeRuntime(chunkA.runtime, chunkB.runtime); - - // getChunkModules is used here to create a clone, because disconnectChunkAndModule modifies - for (const module of chunkGraph.getChunkModules(chunkB)) { - // chunkGraph.disconnectChunkAndModule(chunkB, module); - chunkGraph.connectChunkAndModule(chunkA, module); - } - - for (const [module, chunkGroup] of Array.from( - chunkGraph.getChunkEntryModulesWithChunkGroupIterable(chunkB), - )) { - // dont disconnect as module may need to be copied into multiple chunks - // chunkGraph.disconnectChunkAndEntryModule(chunkB, module); - //connect as normal module not entry module to preserve existing entrypoint modules - chunkGraph.connectChunkAndModule(chunkA, module); - // chunkGraph.connectChunkAndEntryModule(chunkA, module,chunkGroup); - } - - for (const chunkGroup of chunkB.groupsIterable) { - chunkGroup.replaceChunk(chunkB, chunkA); - chunkA.addGroup(chunkGroup); - chunkB.removeGroup(chunkGroup); - } - compiler.webpack.ChunkGraph.clearChunkGraphForChunk(chunkB); - } - +/** + * This class is used to hoist container references in the code. + * @constructor + */ +export class HoistContainerReferences implements WebpackPluginInstance { apply(compiler: Compiler): void { - let hasMultipleRuntime = 0; - compiler.hooks.make.tapPromise( - this.constructor.name, - async (compilation) => { - let entry: any; - if (typeof compilation.options.entry === 'function') { - entry = await compilation.options.entry(); - } else { - entry = compilation.options.entry; - } - - Object.keys(entry).forEach((entryItem) => { - if (entry[entryItem].runtime) { - hasMultipleRuntime++; - } - }); - }, - ); - compiler.hooks.compilation.tap( - 'HoistContainerReferencesPlugin', + compiler.hooks.thisCompilation.tap( + 'HoistContainerReferences', (compilation: Compilation) => { - const runtimes: Set = new Set(); - compilation.hooks.afterOptimizeChunks.tap( - { - name: 'HoistContainerReferencesPlugin', - stage: 10, // Advanced stage chunk optimization. - }, - (chunks: Iterable) => this.processChunks(chunks, compilation), - ); - - compilation.hooks.beforeChunkAssets.tap( - 'HoistContainerReferencesPlugin', - () => { - // the federation-runtime chunk is integrated into multiple other runtime chunks, like main, or runtime.js - // because this entrypoint is integrated using chunk group updates - this chunk cannot be emitted without causing multiple writes to same runtime - // the federation-runtime serves no output process, it is used as a reference to hoist federation runtime once into all runtime chunks for eager consumption - // this plugin serves - const federationRuntimeChunk = - compilation.namedChunks.get('federation-runtime'); - - const federationRuntimePluginsChunk = compilation.namedChunks.get( - 'mfp-runtime-plugins', - ); - - if (federationRuntimeChunk) { - compilation.chunks.delete(federationRuntimeChunk); - this.integratedChunks.delete(federationRuntimeChunk); - } - - if (federationRuntimePluginsChunk) { - compilation.chunks.delete(federationRuntimePluginsChunk); - this.integratedChunks.delete(federationRuntimePluginsChunk); - } - - compilation.namedChunks.delete('federation-runtime'); - compilation.namedChunks.delete('mfp-runtime-plugins'); - - for (const chunk of this.integratedChunks) { - compilation.chunks.delete(chunk); - if (chunk.name) compilation.namedChunks.delete(chunk.name); + 'HoistContainerReferences', + (chunks: Iterable) => { + for (const chunk of chunks) { + if (this.chunkContainsContainerEntryModule(chunk, compilation)) { + this.hoistModulesInChunk(chunk, compilation); + } } }, ); @@ -121,154 +29,40 @@ export class HoistContainerReferencesPlugin implements WebpackPluginInstance { ); } - processChunks(chunks: Iterable, compilation: Compilation) { - const { chunkGraph, compiler } = compilation; - const runtimes = new Set(); - this.collectRuntimes(chunks, runtimes); - - if (!compiler.options.optimization.runtimeChunk) { - this.optimizeWithoutRuntimeChunk(chunks, compilation); - } else { - this.optimizeWithRuntimeChunk(compilation, runtimes); - } - } - - collectRuntimes(chunks: Iterable, runtimes: Set) { - for (const chunk of chunks) { - if (!chunk.runtime) continue; - if (typeof chunk.runtime === 'string') { - runtimes.add(chunk.runtime); - } else { - for (const runtime of chunk.runtime) { - runtimes.add(runtime); - } - } - } - } - - optimizeWithoutRuntimeChunk( - chunks: Iterable, - compilation: Compilation, - ) { - const federationRuntimeChunk = - compilation.namedChunks.get('federation-runtime'); - const federationRuntimePlugins = compilation.namedChunks.get( - 'mfp-runtime-plugins', - ); - - if (federationRuntimeChunk && federationRuntimePlugins) { - this.integrateRuntimeChunks( - chunks, - federationRuntimeChunk, - federationRuntimePlugins, - compilation, - ); - this.disconnectModulesFromChunk(compilation, federationRuntimeChunk); - this.disconnectModulesFromChunk(compilation, federationRuntimePlugins); - } - } - - integrateRuntimeChunks( - chunks: Iterable, - federationRuntimeChunk: Chunk, - federationRuntimePlugins: Chunk, + private chunkContainsContainerEntryModule( + chunk: Chunk, compilation: Compilation, - ) { - for (const chunk of chunks) { - if ( - chunk.hasRuntime() && - !this.chunkContainsContainerEntryModule(chunk, compilation) - ) { - if (chunk !== federationRuntimeChunk) { - this.integrateChunks(chunk, federationRuntimeChunk, compilation); - } - if (chunk !== federationRuntimePlugins) { - this.integrateChunks(chunk, federationRuntimePlugins, compilation); - } + ): boolean { + for (const module of compilation.chunkGraph.getChunkModulesIterable( + chunk, + )) { + if (module instanceof ContainerEntryModule) { + return true; } } + return false; } - disconnectModulesFromChunk(compilation: Compilation, chunk: Chunk) { - const { chunkGraph } = compilation; - for (const module of chunkGraph.getChunkModules(chunk)) { - chunkGraph.disconnectChunkAndModule(chunk, module); - } - } - - optimizeWithRuntimeChunk(compilation: Compilation, runtimes: Set) { - const baseRuntimeName = 'federation-runtime'; - const basePluginsName = 'mfp-runtime-plugins'; - - for (const runtime of runtimes) { - this.handleRuntimeChunks( - compilation, - runtime, - baseRuntimeName, - basePluginsName, - ); - } - } - - handleRuntimeChunks( - compilation: Compilation, - runtime: string, - baseRuntimeName: string, - basePluginsName: string, - ) { - const runtimeChunk = compilation.namedChunks.get(runtime); - if ( - !runtimeChunk || - this.chunkContainsContainerEntryModule(runtimeChunk, compilation) - ) - return; + private hoistModulesInChunk(chunk: Chunk, compilation: Compilation): void { + const chunkGraph = compilation.chunkGraph; + const runtimeChunks = this.getRuntimeChunks(chunk, compilation); - const federationRuntimeChunk = this.getNamedChunk( - compilation, - `${baseRuntimeName}-${runtime}`, - baseRuntimeName, - ); - const pluginsRuntimeChunk = this.getNamedChunk( - compilation, - `${basePluginsName}-${runtime}`, - basePluginsName, - ); - - if (federationRuntimeChunk) { - this.integrateChunks(runtimeChunk, federationRuntimeChunk, compilation); - } - if (pluginsRuntimeChunk) { - this.integrateChunks(runtimeChunk, pluginsRuntimeChunk, compilation); + for (const module of chunkGraph.getChunkModulesIterable(chunk)) { + for (const runtimeChunk of runtimeChunks) { + chunkGraph.connectChunkAndModule(runtimeChunk, module); + } } } - getNamedChunk( - compilation: Compilation, - newChunkName: string, - defaultChunkName: string, - ): Chunk | undefined { - return ( - // search for runtime allocated entry chunk, fall back to default runtime chunk (undefined, false, etc) - compilation.namedChunks.get(newChunkName) || - compilation.namedChunks.get(defaultChunkName) - ); - } - - private chunkContainsContainerEntryModule( - chunk: Chunk, - compilation: Compilation, - ): boolean { - let hasContainerEntryModule = false; - for (const module of compilation.chunkGraph.getChunkModulesIterable( - chunk, - )) { - if (module instanceof ContainerEntryModule) { - hasContainerEntryModule = true; - break; + private getRuntimeChunks(chunk: Chunk, compilation: Compilation): Chunk[] { + const runtimeChunks = []; + for (const c of compilation.chunks) { + if (c.hasRuntime() && c !== chunk) { + runtimeChunks.push(c); } } - return hasContainerEntryModule; + return runtimeChunks; } } -export default HoistContainerReferencesPlugin; +export default HoistContainerReferences; diff --git a/packages/enhanced/src/lib/container/ModuleFederationPlugin.ts b/packages/enhanced/src/lib/container/ModuleFederationPlugin.ts index feeca0a1401..265b78511cd 100644 --- a/packages/enhanced/src/lib/container/ModuleFederationPlugin.ts +++ b/packages/enhanced/src/lib/container/ModuleFederationPlugin.ts @@ -52,6 +52,7 @@ class ModuleFederationPlugin implements WebpackPluginInstance { */ apply(compiler: Compiler): void { const { _options: options } = this; + // @ts-ignore new FederationRuntimePlugin(options).apply(compiler); const library = options.library || { type: 'var', name: options.name }; const remoteType = @@ -70,8 +71,6 @@ class ModuleFederationPlugin implements WebpackPluginInstance { if (useContainerPlugin) { // @ts-ignore ContainerPlugin.patchChunkSplit(compiler, this._options.name); - ContainerPlugin.patchChunkSplit(compiler, 'federation-runtime'); - ContainerPlugin.patchChunkSplit(compiler, 'mfp-runtime-plugins'); } if (!disableManifest && useContainerPlugin) { @@ -108,6 +107,7 @@ class ModuleFederationPlugin implements WebpackPluginInstance { //@ts-ignore exposes: options.exposes, runtimePlugins: options.runtimePlugins, + //@ts-ignore }).apply(compiler); } if ( diff --git a/packages/enhanced/src/lib/container/runtime/FederationInitModule.ts b/packages/enhanced/src/lib/container/runtime/FederationInitModule.ts deleted file mode 100644 index d4b760c8d4b..00000000000 --- a/packages/enhanced/src/lib/container/runtime/FederationInitModule.ts +++ /dev/null @@ -1,137 +0,0 @@ -import { normalizeWebpackPath } from '@module-federation/sdk/normalize-webpack-path'; -import type { Chunk, Compilation, Module } from 'webpack'; - -const { RuntimeModule, Template } = require( - normalizeWebpackPath('webpack'), -) as typeof import('webpack'); - -class FederationInitModule extends RuntimeModule { - constructor( - public containerName: string, - public entryFilePath: string, - public chunksRuntimePluginsDependsOn: Set | undefined, - ) { - super('federation runtime init', RuntimeModule.STAGE_ATTACH); - } - - private chunkContainsFederationRuntime( - chunk: Chunk, - compilation: Compilation, - ): { - federationRuntimeModule: Module | null; - federationRuntimePluginModule: Module | null; - } { - let federationRuntimeModule: Module | null = null; - let federationRuntimePluginModule: Module | null = null; - for (const module of compilation.chunkGraph.getChunkModulesIterable( - chunk, - )) { - if ( - !federationRuntimeModule && - module.identifier?.()?.includes('.federation/federation') - ) { - federationRuntimeModule = module; - } else if ( - !federationRuntimePluginModule && - module.identifier?.()?.includes('.federation/plugin') - ) { - federationRuntimePluginModule = module; - } - if (federationRuntimeModule && federationRuntimePluginModule) break; - } - return { federationRuntimeModule, federationRuntimePluginModule }; - } - - getModuleByInstance(): { - federationRuntimeModuleId: string | number | undefined; - runtimePluginModuleId: string | number | undefined; - chunk: Chunk; - } | null { - if ( - !this.compilation || - !this.chunk || - !this.compilation.chunkGraph || - !this.chunk.hasRuntime() - ) - return null; - - const { federationRuntimeModule, federationRuntimePluginModule } = - this.chunkContainsFederationRuntime(this.chunk, this.compilation); - let runtimePluginModuleId: string | number | undefined; - let federationRuntimeModuleId: string | number | undefined; - - if (federationRuntimeModule) { - federationRuntimeModuleId = this.compilation.chunkGraph.getModuleId( - federationRuntimeModule, - ); - } - - if (federationRuntimePluginModule) { - runtimePluginModuleId = this.compilation.chunkGraph.getModuleId( - federationRuntimePluginModule, - ); - } - - return { - federationRuntimeModuleId, - runtimePluginModuleId, - chunk: this.chunk, - }; - } - - override generate(): string | null { - if (!this.compilation || !this.chunk) return ''; - - const moduleInstance = this.getModuleByInstance(); - // Early return if no moduleInstance is found - if (!moduleInstance) return ''; - - const { federationRuntimeModuleId, runtimePluginModuleId } = moduleInstance; - const requireStatements: string[] = []; - - // Directly push federationRuntimeModuleId require statement if it exists - if (federationRuntimeModuleId) { - requireStatements.push( - `__webpack_require__(${JSON.stringify(federationRuntimeModuleId)});`, - ); - } - - if (runtimePluginModuleId) { - // check if needs async boundary - const chunkConsumesStatements = this.chunksRuntimePluginsDependsOn - ? Array.from(this.chunksRuntimePluginsDependsOn) - .map( - (chunk) => - `__webpack_require__.f.consumes(${JSON.stringify( - chunk.id || chunk.name, - )}, consumes);`, - ) - .join('\n') - : ''; - - if (chunkConsumesStatements) { - requireStatements.push( - Template.asString([ - `var consumes = [];`, - `if(__webpack_require__.f && __webpack_require__.f.consumes){`, - Template.indent(chunkConsumesStatements), - `}`, - `Promise.all(consumes).then(function() {`, - Template.indent( - `__webpack_require__(${JSON.stringify(runtimePluginModuleId)});`, - ), - `});`, - ]), - ); - } else { - requireStatements.push( - `__webpack_require__(${JSON.stringify(runtimePluginModuleId)});`, - ); - } - } - - return Template.asString(requireStatements); - } -} - -export default FederationInitModule; diff --git a/packages/enhanced/src/lib/container/runtime/FederationRuntimePlugin.ts b/packages/enhanced/src/lib/container/runtime/FederationRuntimePlugin.ts index 20dd76e9105..1218b2155d5 100644 --- a/packages/enhanced/src/lib/container/runtime/FederationRuntimePlugin.ts +++ b/packages/enhanced/src/lib/container/runtime/FederationRuntimePlugin.ts @@ -1,7 +1,6 @@ -import type { Compiler, Chunk } from 'webpack'; +import type { Compiler, sources } from 'webpack'; import { normalizeWebpackPath } from '@module-federation/sdk/normalize-webpack-path'; import FederationRuntimeModule from './FederationRuntimeModule'; -import FederationInitModule from './FederationInitModule'; import { getFederationGlobalScope, normalizeRuntimeInitOptionsWithOutShared, @@ -13,7 +12,6 @@ import fs from 'fs'; import path from 'path'; import { TEMP_DIR } from '../constant'; import type { moduleFederationPlugin } from '@module-federation/sdk'; -import HoistContainerReferences from '../HoistContainerReferencesPlugin'; const { RuntimeGlobals, Template } = require( normalizeWebpackPath('webpack'), @@ -39,43 +37,22 @@ const federationGlobal = getFederationGlobalScope(RuntimeGlobals); class FederationRuntimePlugin { options?: moduleFederationPlugin.ModuleFederationPluginOptions; entryFilePath: string; - pluginsFilePath: string; // New path for plugins file bundlerRuntimePath: string; constructor(options?: moduleFederationPlugin.ModuleFederationPluginOptions) { this.options = options ? { ...options } : undefined; this.entryFilePath = ''; - this.pluginsFilePath = ''; // Initialize plugins file path this.bundlerRuntimePath = BundlerRuntimePath; } - static getTemplate(bundlerRuntimePath: string) { + static getTemplate(runtimePlugins: string[], bundlerRuntimePath?: string) { // internal runtime plugin const normalizedBundlerRuntimePath = normalizeToPosixPath( bundlerRuntimePath || BundlerRuntimePath, ); - return Template.asString([ - `import federation from '${normalizedBundlerRuntimePath}';`, - `${federationGlobal} = {...federation,...${federationGlobal}};`, - `if(!${federationGlobal}.instance){`, - Template.indent([ - `${federationGlobal}.instance = ${federationGlobal}.runtime.init(${federationGlobal}.initOptions);`, - `if(${federationGlobal}.attachShareScopeMap){`, - Template.indent([ - `${federationGlobal}.attachShareScopeMap(${RuntimeGlobals.require})`, - ]), - '}', - `if(${federationGlobal}.installInitialConsumes){`, - Template.indent([`${federationGlobal}.installInitialConsumes()`]), - '}', - ]), - '}', - ]); - } - static getPluginsTemplate(runtimePlugins: string[]) { let runtimePluginTemplates = ''; - const runtimePluginNames: string[] = []; + const runtimePLuginNames: string[] = []; if (Array.isArray(runtimePlugins)) { runtimePlugins.forEach((runtimePlugin, index) => { @@ -87,80 +64,103 @@ class FederationRuntimePlugin { ); runtimePluginTemplates += `import ${runtimePluginName} from '${runtimePluginPath}';\n`; - runtimePluginNames.push(runtimePluginName); + runtimePLuginNames.push(runtimePluginName); }); } return Template.asString([ + `import federation from '${normalizedBundlerRuntimePath}';`, runtimePluginTemplates, - `if(${federationGlobal}.instance){`, + `${federationGlobal} = {...federation,...${federationGlobal}};`, + `if(!${federationGlobal}.instance){`, Template.indent([ - runtimePluginNames.length + runtimePLuginNames.length ? Template.asString([ - `${federationGlobal}.initOptions.plugins = ${federationGlobal}.initOptions.plugins ? ${federationGlobal}.initOptions.plugins.concat([`, - Template.indent(runtimePluginNames.map((item) => `${item}(),`)), - ']) : [', - Template.indent(runtimePluginNames.map((item) => `${item}(),`)), - '];', + `${federationGlobal}.initOptions.plugins = ([`, + Template.indent(runtimePLuginNames.map((item) => `${item}(),`)), + '])', ]) : '', - `${federationGlobal}.runtime.init(${federationGlobal}.initOptions);`, //init again with plugins attached. + `${federationGlobal}.instance = ${federationGlobal}.runtime.init(${federationGlobal}.initOptions);`, + `if(${federationGlobal}.attachShareScopeMap){`, + Template.indent([ + `${federationGlobal}.attachShareScopeMap(${RuntimeGlobals.require})`, + ]), + '}', + `if(${federationGlobal}.installInitialConsumes){`, + Template.indent([`${federationGlobal}.installInitialConsumes()`]), + '}', ]), '}', ]); } - ensureFiles() { + static getFilePath( + containerName: string, + runtimePlugins: string[], + bundlerRuntimePath?: string, + ) { + const hash = createHash( + `${containerName} ${FederationRuntimePlugin.getTemplate( + runtimePlugins, + bundlerRuntimePath, + )}`, + ); + return path.join(TEMP_DIR, `entry.${hash}.js`); + } + + getFilePath() { + if (this.entryFilePath) { + return this.entryFilePath; + } + if (!this.options) { - return; + return ''; } - const federationTemplate = FederationRuntimePlugin.getTemplate( + this.entryFilePath = FederationRuntimePlugin.getFilePath( + this.options.name!, + this.options.runtimePlugins!, this.bundlerRuntimePath, ); - const pluginsTemplate = FederationRuntimePlugin.getPluginsTemplate( - this.options.runtimePlugins || [], - ); - - const federationHash = createHash(federationTemplate); - const pluginsHash = createHash(pluginsTemplate); - - this.entryFilePath = path.join(TEMP_DIR, `federation.${federationHash}.js`); - this.pluginsFilePath = path.join(TEMP_DIR, `plugins.${pluginsHash}.js`); - - this.writeFile(this.entryFilePath, federationTemplate); - this.writeFile(this.pluginsFilePath, pluginsTemplate); + return this.entryFilePath; } - writeFile(filePath: string, content: string) { + ensureFile() { + if (!this.options) { + return; + } + const filePath = this.getFilePath(); try { fs.readFileSync(filePath); } catch (err) { - mkdirpSync(fs, path.dirname(filePath)); - fs.writeFileSync(filePath, content); + mkdirpSync(fs, TEMP_DIR); + fs.writeFileSync( + filePath, + FederationRuntimePlugin.getTemplate( + this.options.runtimePlugins!, + this.bundlerRuntimePath, + ), + ); } } prependEntry(compiler: Compiler) { - this.ensureFiles(); + this.ensureFile(); + const entryFilePath = this.getFilePath(); + modifyEntry({ compiler, prependEntry: (entry) => { - Object.keys(entry).forEach((key) => { - const entryItem = entry[key]; - const prefix = entryItem.runtime ? `-${entryItem.runtime}` : ''; - const runtimePluginKey = `mfp-runtime-plugins${prefix}`; - const federationRuntimeKey = `federation-runtime${prefix}`; - - entry[runtimePluginKey] = { - import: [this.pluginsFilePath], - runtime: entryItem.runtime, - }; - - entry[federationRuntimeKey] = { - import: [this.entryFilePath], - runtime: entryItem.runtime, - }; + Object.keys(entry).forEach((entryName) => { + const entryItem = entry[entryName]; + if (!entryItem.import) { + // TODO: maybe set this variable as constant is better https://github.com/webpack/webpack/blob/main/lib/config/defaults.js#L176 + entryItem.import = ['./src']; + } + if (!entryItem.import.includes(entryFilePath)) { + entryItem.import.unshift(entryFilePath); + } }); }, }); @@ -181,19 +181,6 @@ class FederationRuntimePlugin { compiler.hooks.thisCompilation.tap( this.constructor.name, (compilation, { normalModuleFactory }) => { - let chunksRuntimePluginsDependsOn: Set | undefined = undefined; - compilation.hooks.afterOptimizeChunks.tap( - this.constructor.name, - (chunk) => { - const runtimePluginEntry = compilation.namedChunks.get( - 'mfp-runtime-plugins', - ); - if (runtimePluginEntry) { - chunksRuntimePluginsDependsOn = - runtimePluginEntry.getAllInitialChunks(); - } - }, - ); compilation.hooks.additionalTreeRuntimeRequirements.tap( this.constructor.name, (chunk, runtimeRequirements) => { @@ -212,14 +199,6 @@ class FederationRuntimePlugin { initOptionsWithoutShared, ), ); - compilation.addRuntimeModule( - chunk, - new FederationInitModule( - name, - this.entryFilePath, - chunksRuntimePluginsDependsOn, - ), - ); }, ); }, @@ -301,8 +280,6 @@ class FederationRuntimePlugin { this.prependEntry(compiler); this.injectRuntime(compiler); this.setRuntimeAlias(compiler); - - new HoistContainerReferences().apply(compiler); } } diff --git a/packages/nextjs-mf/src/plugins/NextFederationPlugin/apply-client-plugins.ts b/packages/nextjs-mf/src/plugins/NextFederationPlugin/apply-client-plugins.ts index 2a23fa30f2c..db13a675631 100644 --- a/packages/nextjs-mf/src/plugins/NextFederationPlugin/apply-client-plugins.ts +++ b/packages/nextjs-mf/src/plugins/NextFederationPlugin/apply-client-plugins.ts @@ -5,6 +5,7 @@ import { } from '@module-federation/utilities'; import { ChunkCorrelationPlugin } from '@module-federation/node'; import InvertedContainerPlugin from '../container/InvertedContainerPlugin'; +import { HoistContainerReferencesPlugin } from '@module-federation/enhanced'; /** * Applies client-specific plugins. @@ -37,6 +38,7 @@ export function applyClientPlugins( compiler.options.output.publicPath = 'auto'; // Build will hang without this. Likely something in my plugin compiler.options.optimization.splitChunks = undefined; + new HoistContainerReferencesPlugin().apply(compiler); // If automatic page stitching is enabled, add a new rule to the compiler's module rules if (extraOptions.automaticPageStitching) { diff --git a/packages/nextjs-mf/src/plugins/NextFederationPlugin/apply-server-plugins.ts b/packages/nextjs-mf/src/plugins/NextFederationPlugin/apply-server-plugins.ts index 96e20d092c5..2eb3d3b0f7d 100644 --- a/packages/nextjs-mf/src/plugins/NextFederationPlugin/apply-server-plugins.ts +++ b/packages/nextjs-mf/src/plugins/NextFederationPlugin/apply-server-plugins.ts @@ -1,5 +1,6 @@ import type { Compiler } from 'webpack'; import { ModuleFederationPluginOptions } from '@module-federation/utilities'; +import { HoistContainerReferencesPlugin } from '@module-federation/enhanced'; import path from 'path'; import InvertedContainerPlugin from '../container/InvertedContainerPlugin'; import { ModuleFederationPlugin } from '@module-federation/enhanced'; @@ -32,6 +33,7 @@ export function applyServerPlugins( } // Hoist container references into runtime chunks //@ts-ignore + new HoistContainerReferencesPlugin().apply(compiler); // Add the StreamingTargetPlugin with the ModuleFederationPlugin from the webpack container new StreamingTargetPlugin(options, { diff --git a/packages/nextjs-mf/src/plugins/container/HoistPseudoEagerModules.ts b/packages/nextjs-mf/src/plugins/container/HoistPseudoEagerModules.ts new file mode 100644 index 00000000000..39c4d1f864c --- /dev/null +++ b/packages/nextjs-mf/src/plugins/container/HoistPseudoEagerModules.ts @@ -0,0 +1,94 @@ +import type { Compiler, Chunk, Module, Compilation, ChunkGroup } from 'webpack'; + +/** + * @typedef {import("webpack").Compiler} Compiler + * @typedef {import("webpack").Compilation} Compilation + * @typedef {import("webpack").Chunk} Chunk + * @typedef {import("webpack").Module} Module + */ + +/** + * This class is responsible for hoisting container references in the code. + * @constructor + */ +export class HoistPseudoEager { + /** + * @function apply + * @param {Compiler} compiler The webpack compiler object + */ + apply(compiler: Compiler): void { + // Hook into the compilation process + compiler.hooks.thisCompilation.tap( + 'HoistPseudoEager', + (compilation: Compilation) => { + // Perform the hoisting after chunks are optimized + compilation.hooks.afterOptimizeChunks.tap( + 'HoistPseudoEager', + (chunks: Iterable, chunkGroups: ChunkGroup[]) => { + // Create a map to store chunks by their id or name + /** @type {Map<(string|number), Chunk>} */ + const chunkSet = new Map(); + // Create a set to store external module requests + /** @type {Set} */ + const externalRequests = new Set(); + // Populate the chunkSet with chunks + for (const chunk of chunks) { + const ident = chunk.id || chunk.name; + if (ident) { + chunkSet.set(ident, chunk); + } + } + + // Iterate over chunks again to handle remote modules + for (const chunk of chunks) { + // Get iterable of remote modules for the chunk + const remoteModules = + compilation.chunkGraph.getChunkModulesIterableBySourceType( + chunk, + 'remote', + ); + if (!remoteModules) continue; + const runtime = + chunkSet.get('webpack-runtime') || chunkSet.get('webpack'); + const runtimeRoots = runtime + ? compilation.chunkGraph.getChunkRootModules(runtime) + : null; + const refChunks = runtime + ? Array.from(runtime.getAllReferencedChunks()) + : null; + if (refChunks) { + for (const refChunk of refChunks) { + const consumeSharedModules = + compilation.chunkGraph.getChunkModulesIterableBySourceType( + refChunk, + 'consume-shared', + ); + if (!consumeSharedModules) continue; + //loop through consume-shared modules + for (const module of consumeSharedModules) { + // Get the module associated with the dependency + for (const block of module.blocks) { + for (const dep of block.dependencies) { + const mod = compilation.moduleGraph.getModule(dep); + // If the module exists and the chunk has a runtime, add the module to externalRequests + if (mod !== null && runtime) { + // Get the runtime chunk from the chunkSet + // If the runtime chunk exists, connect it with the module in the chunk graph + compilation.chunkGraph.connectChunkAndModule( + runtime, + mod, + ); + } + } + } + } + } + } + } + }, + ); + }, + ); + } +} +export default HoistPseudoEager; diff --git a/packages/nextjs-mf/src/plugins/container/RemoveEagerModulesFromRuntimePlugin.ts b/packages/nextjs-mf/src/plugins/container/RemoveEagerModulesFromRuntimePlugin.ts new file mode 100644 index 00000000000..cbada7fdf78 --- /dev/null +++ b/packages/nextjs-mf/src/plugins/container/RemoveEagerModulesFromRuntimePlugin.ts @@ -0,0 +1,97 @@ +import type { Compiler, Compilation, Chunk, Module } from 'webpack'; + +/** + * This plugin removes eager modules from the runtime. + * @class RemoveEagerModulesFromRuntimePlugin + */ +class RemoveEagerModulesFromRuntimePlugin { + private container: string | undefined; + private debug: boolean; + private modulesToProcess: Set; + + /** + * Creates an instance of RemoveEagerModulesFromRuntimePlugin. + * @param {Object} options - The options for the plugin. + * @param {string} options.container - The container to remove modules from. + * @param {boolean} options.debug - Whether to log debug information. + */ + constructor(options: { container?: string; debug?: boolean }) { + this.container = options.container; + this.debug = options.debug || false; + this.modulesToProcess = new Set(); + } + + /** + * Applies the plugin to the compiler. + * @param {Compiler} compiler - The webpack compiler. + */ + apply(compiler: Compiler) { + if (!this.container) { + console.warn( + '[nextjs-mf]:', + 'RemoveEagerModulesFromRuntimePlugin container is not defined:', + this.container, + ); + return; + } + + compiler.hooks.thisCompilation.tap( + 'RemoveEagerModulesFromRuntimePlugin', + (compilation: Compilation) => { + compilation.hooks.optimizeChunkModules.tap( + 'RemoveEagerModulesFromRuntimePlugin', + (chunks: Iterable, modules: Iterable) => { + for (const chunk of chunks) { + if (chunk.hasRuntime() && chunk.name === this.container) { + this.processModules(compilation, chunk, modules); + } + } + }, + ); + }, + ); + } + + /** + * Processes the modules in the chunk. + * @param {Compilation} compilation - The webpack compilation. + * @param {Chunk} chunk - The chunk to process. + * @param {Iterable} modules - The modules in the chunk. + */ + private processModules( + compilation: Compilation, + chunk: Chunk, + modules: Iterable, + ) { + for (const module of modules) { + if (!compilation.chunkGraph.isModuleInChunk(module, chunk)) { + continue; + } + + if (module.constructor.name === 'NormalModule') { + this.modulesToProcess.add(module); + } + } + + this.removeModules(compilation, chunk); + } + + /** + * Removes the modules from the chunk. + * @param {Compilation} compilation - The webpack compilation. + * @param {Chunk} chunk - The chunk to remove modules from. + */ + private removeModules(compilation: Compilation, chunk: Chunk) { + for (const moduleToRemove of this.modulesToProcess) { + if (this.debug) { + console.log('removing', moduleToRemove.constructor.name); + } + + if (compilation.chunkGraph.isModuleInChunk(moduleToRemove, chunk)) { + compilation.chunkGraph.disconnectChunkAndModule(chunk, moduleToRemove); + } + } + } +} + +export default RemoveEagerModulesFromRuntimePlugin; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b3595e20261..2c53f03f372 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1147,7 +1147,7 @@ importers: dependencies: '@arco-design/web-react': specifier: ^2.59.1 - version: 2.61.1(@types/react@18.2.62)(react-dom@18.2.0)(react@18.2.0) + version: 2.61.2(@types/react@18.2.62)(react-dom@18.2.0)(react@18.2.0) '@modern-js/runtime': specifier: 2.46.1 version: 2.46.1(@types/react-dom@18.2.19)(@types/react@18.2.62)(react-dom@18.2.0)(react@18.2.0)(webpack@5.91.0) @@ -1159,7 +1159,7 @@ importers: version: 0.7.52 ahooks: specifier: ^3.7.10 - version: 3.7.10(react@18.2.0) + version: 3.7.11(react@18.2.0) axios: specifier: ^1.6.7 version: 1.6.7 @@ -1884,8 +1884,8 @@ packages: color: 3.2.1 dev: false - /@arco-design/web-react@2.61.1(@types/react@18.2.62)(react-dom@18.2.0)(react@18.2.0): - resolution: {integrity: sha512-JbrNCLZzpSCkri6319Wq75sZJRFAThqBzKpRiJbUqtLdKljlRR3IYxxfTgEJdWS6hTRu7A0PA6dbiNaVn/89yA==} + /@arco-design/web-react@2.61.2(@types/react@18.2.62)(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-YroNHRESaffGwkJ5jCgVCAsLeKhIB+Nky4tmHr1/FruTdGEjERNaxagvj8yCgBTpunwJdbxU6OXlzYwW1IfBeQ==} peerDependencies: react: '>=16' react-dom: '>=16' @@ -6297,7 +6297,7 @@ packages: resolution: {integrity: sha512-kV4N3JMfyl4pYJIPhtMTby7EOxid9Adq298Z9b2TbAb1EgzyiuDviOakzcks8jRAiesuI9sh7TFjLPniHdSQUA==} dependencies: '@swc/helpers': 0.5.3 - caniuse-lite: 1.0.30001600 + caniuse-lite: 1.0.30001599 lodash: 4.17.21 rslog: 1.2.1 @@ -9865,7 +9865,7 @@ packages: react-dom: '>=17' dependencies: '@reactflow/core': 11.10.4(@types/react@18.2.62)(react-dom@18.2.0)(react@18.2.0) - classcat: 5.0.4 + classcat: 5.0.5 react: 18.2.0 react-dom: 18.2.0(react@18.2.0) zustand: 4.5.2(@types/react@18.2.62)(react@18.2.0) @@ -9881,7 +9881,7 @@ packages: react-dom: '>=17' dependencies: '@reactflow/core': 11.10.4(@types/react@18.2.62)(react-dom@18.2.0)(react@18.2.0) - classcat: 5.0.4 + classcat: 5.0.5 react: 18.2.0 react-dom: 18.2.0(react@18.2.0) zustand: 4.5.2(@types/react@18.2.62)(react@18.2.0) @@ -9900,7 +9900,7 @@ packages: '@types/d3-drag': 3.0.7 '@types/d3-selection': 3.0.10 '@types/d3-zoom': 3.0.8 - classcat: 5.0.4 + classcat: 5.0.5 d3-drag: 3.0.0 d3-selection: 3.0.0 d3-zoom: 3.0.0 @@ -9921,7 +9921,7 @@ packages: '@reactflow/core': 11.10.4(@types/react@18.2.62)(react-dom@18.2.0)(react@18.2.0) '@types/d3-selection': 3.0.10 '@types/d3-zoom': 3.0.8 - classcat: 5.0.4 + classcat: 5.0.5 d3-selection: 3.0.0 d3-zoom: 3.0.0 react: 18.2.0 @@ -9939,7 +9939,7 @@ packages: react-dom: '>=17' dependencies: '@reactflow/core': 11.10.4(@types/react@18.2.62)(react-dom@18.2.0)(react@18.2.0) - classcat: 5.0.4 + classcat: 5.0.5 d3-drag: 3.0.0 d3-selection: 3.0.0 react: 18.2.0 @@ -9957,7 +9957,7 @@ packages: react-dom: '>=17' dependencies: '@reactflow/core': 11.10.4(@types/react@18.2.62)(react-dom@18.2.0)(react@18.2.0) - classcat: 5.0.4 + classcat: 5.0.5 react: 18.2.0 react-dom: 18.2.0(react@18.2.0) zustand: 4.5.2(@types/react@18.2.62)(react@18.2.0) @@ -14808,8 +14808,8 @@ packages: indent-string: 5.0.0 dev: true - /ahooks@3.7.10(react@18.2.0): - resolution: {integrity: sha512-/HLYif7sFA/5qSuWKrwvjDbf3bq+sdaMrUWS7XGCDRWdC2FrG/i+u5LZdakMYc6UIgJTMQ7tGiJCV7sdU4kSIw==} + /ahooks@3.7.11(react@18.2.0): + resolution: {integrity: sha512-BfSq7HJ9wk/7a2vX7WbLdwzHyQHmbNe21ipX1PfIzssXIzQfAl79WVJ9GjZaqNl4PFPsJusj/Xjg2OF+gIgGaQ==} engines: {node: '>=8.0.0'} peerDependencies: react: ^16.8.0 || ^17.0.0 || ^18.0.0 @@ -14820,6 +14820,7 @@ packages: js-cookie: 2.2.1 lodash: 4.17.21 react: 18.2.0 + react-fast-compare: 3.2.2 resize-observer-polyfill: 1.5.1 screenfull: 5.2.0 tslib: 2.6.2 @@ -16345,7 +16346,7 @@ packages: engines: {node: '>= 6.0.0'} dependencies: mime-types: 2.1.35 - ylru: 1.3.2 + ylru: 1.4.0 dev: false /cache-directory@2.0.0: @@ -16449,6 +16450,7 @@ packages: /caniuse-lite@1.0.30001600: resolution: {integrity: sha512-+2S9/2JFhYmYaDpZvo0lKkfvuKIglrx68MwOBqMGHhQsNkLjB5xtc/TGoEPs+MxjSyN/72qer2g97nzR641mOQ==} + dev: true /case-sensitive-paths-webpack-plugin@2.4.0: resolution: {integrity: sha512-roIFONhcxog0JSSWbvVAh3OocukmSgpqOH6YpMkCvav/ySIV3JKg4Dc8vYtQjYi/UxpNE36r/9v+VqTQqgkYmw==} @@ -16677,8 +16679,8 @@ packages: static-extend: 0.1.2 dev: true - /classcat@5.0.4: - resolution: {integrity: sha512-sbpkOw6z413p+HDGcBENe498WM9woqWHiJxCq7nvmxe9WmrUmqfAcxpIwAiMtM5Q3AhYkzXcNQHqsWq0mND51g==} + /classcat@5.0.5: + resolution: {integrity: sha512-JhZUT7JFcQy/EzW605k/ktHtncoo9vnyW/2GspNYwFlN1C/WmjuV/xtS04e9SOkL2sTdw0VAZ2UGCcQ9lR6p6w==} dev: false /classnames@2.5.0: @@ -17925,7 +17927,7 @@ packages: engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0, npm: '>=7.0.0'} dependencies: mdn-data: 2.0.28 - source-map-js: 1.2.0 + source-map-js: 1.0.2 dev: true /css-tree@2.3.1: @@ -22000,7 +22002,6 @@ packages: inherits: 2.0.3 setprototypeof: 1.1.0 statuses: 1.5.0 - dev: true /http-errors@1.8.1: resolution: {integrity: sha512-Kpk9Sm7NmI+RHhnj6OIWDI1d6fIoFAtFt9RLaTMRlg/8w49juAStsrBgp0Dp4OdxdVbRIeKhtCUvoi/RuAhO4g==} @@ -22330,7 +22331,6 @@ packages: /inherits@2.0.3: resolution: {integrity: sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw==} - dev: true /inherits@2.0.4: resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} @@ -23999,7 +23999,7 @@ packages: escape-html: 1.0.3 fresh: 0.5.2 http-assert: 1.5.0 - http-errors: 1.8.1 + http-errors: 1.6.3 is-generator-function: 1.0.10 koa-compose: 4.1.0 koa-convert: 1.2.0 @@ -24164,8 +24164,6 @@ packages: peerDependenciesMeta: webpack: optional: true - webpack-sources: - optional: true dependencies: webpack: 5.90.3(@swc/core@1.3.102)(esbuild@0.18.20) webpack-sources: 3.2.3 @@ -30878,7 +30876,6 @@ packages: /setprototypeof@1.1.0: resolution: {integrity: sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==} - dev: true /setprototypeof@1.2.0: resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==} @@ -34576,7 +34573,7 @@ packages: '@types/ws': 8.5.3 ansi-html-community: 0.0.8 bonjour-service: 1.2.1 - chokidar: 3.6.0 + chokidar: 3.5.3 colorette: 2.0.20 compression: 1.7.4 connect-history-api-fallback: 2.0.0 @@ -34929,7 +34926,7 @@ packages: browserslist: 4.23.0 chrome-trace-event: 1.0.3 enhanced-resolve: 5.15.0 - es-module-lexer: 1.4.1 + es-module-lexer: 1.4.2 eslint-scope: 5.1.1 events: 3.3.0 glob-to-regexp: 0.4.1 @@ -34969,7 +34966,7 @@ packages: browserslist: 4.23.0 chrome-trace-event: 1.0.3 enhanced-resolve: 5.15.0 - es-module-lexer: 1.4.1 + es-module-lexer: 1.4.2 eslint-scope: 5.1.1 events: 3.3.0 glob-to-regexp: 0.4.1 @@ -35452,8 +35449,8 @@ packages: buffer-crc32: 0.2.13 dev: true - /ylru@1.3.2: - resolution: {integrity: sha512-RXRJzMiK6U2ye0BlGGZnmpwJDPgakn6aNQ0A7gHRbD4I0uvK4TW6UqkK1V0pp9jskjJBAXd3dRrbzWkqJ+6cxA==} + /ylru@1.4.0: + resolution: {integrity: sha512-2OQsPNEmBCvXuFlIni/a+Rn+R2pHW9INm0BxXJ4hVDA8TirqMj+J/Rp9ItLatT/5pZqWwefVrTQcHpixsxnVlA==} engines: {node: '>= 4.0.0'} dev: false