Last active
August 27, 2023 01:47
-
-
Save jacob-ebey/cd32da68d622aa356dbd1cedfa3b5ab6 to your computer and use it in GitHub Desktop.
Webpack Federation Expose Remotes Plugin
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
const remote = () => "@app/" + "other"; | |
__webpack_chunk_load__(remote()).then(async () => { | |
const container = __webpack_require__(remote()); | |
const factory = await container.get("./federated"); | |
const mod = factory(); | |
console.log({ | |
APP_NAME, | |
REMOTE: mod.name, | |
}); | |
}); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class ExposeRemotesPlugin { | |
static NAME = "ExposeRemotesPlugin"; | |
/** | |
* @param {import('webpack').Compiler} compiler | |
*/ | |
apply(compiler) { | |
const webpack = compiler.webpack; | |
const RuntimeGlobals = webpack.RuntimeGlobals; | |
class ExposeRemotesRuntimeModule extends webpack.RuntimeModule { | |
/** | |
* @param {Record<string, webpack.Module>} remotes | |
*/ | |
constructor() { | |
super("remote container loading"); | |
} | |
generate() { | |
const compilation = this.compilation; | |
const runtimeTemplate = compilation.runtimeTemplate; | |
const RuntimeGlobals = webpack.RuntimeGlobals; | |
const Template = webpack.Template; | |
const chunkMapping = {}; | |
const idToExternalMapping = {}; | |
/** @type {Array<webpack.Chunk & { name: string }>} */ | |
const remoteModules = []; | |
const otherChunks = []; | |
for ( | |
const chunk of compilation.chunks | |
) { | |
if (!chunk.name || !chunk.name.startsWith("__webpack_remote__:")) { | |
otherChunks.push(chunk); | |
continue; | |
} | |
const name = chunk.name.slice("__webpack_remote__:".length); | |
chunkMapping[name] = [chunk.name]; | |
chunkMapping[name] = []; | |
let remoteMod; | |
for (const mod of compilation.chunkGraph.getChunkModules(chunk)) { | |
if (mod.type === "remote-module") { | |
continue; | |
} | |
if (remoteMod) { | |
throw new Error("Multiple remote modules in chunk"); | |
} | |
remoteMod = mod; | |
} | |
idToExternalMapping[name] = compilation.chunkGraph.getModuleId( | |
remoteMod, | |
); | |
} | |
return Template.asString([ | |
"// @module-federation/utils/expose remotes", | |
"var chunkMapping = {", | |
Template.indent( | |
Object.entries(chunkMapping).map(([key, value]) => | |
`${JSON.stringify(key)}: ${JSON.stringify(value)},` | |
), | |
), | |
"};", | |
"var idToExternalMapping = {", | |
Template.indent( | |
Object.entries(idToExternalMapping).map(([key, value]) => | |
`${JSON.stringify(key)}: ${JSON.stringify(value)},` | |
), | |
), | |
"};", | |
`${RuntimeGlobals.ensureChunkHandlers}.exposedRemotes = ${ | |
runtimeTemplate.basicFunction(["chunkId, promises"], [ | |
`if (!${RuntimeGlobals.hasOwnProperty}(chunkMapping, chunkId)) return;`, | |
"var data = chunkMapping[chunkId];", | |
"if (!data.p) {", | |
Template.indent([ | |
`${RuntimeGlobals.externalInstallChunk}({ ids: [chunkId] });`, | |
"data.p = Promise.all(promises).then(", | |
Template.indent(runtimeTemplate.returningFunction( | |
Template.asString([ | |
"new Promise(", | |
Template.indent( | |
runtimeTemplate.basicFunction(["resolve", "reject"], [ | |
`Promise.all(data.map(${RuntimeGlobals.ensureChunk}))`, | |
".then(", | |
Template.indent( | |
runtimeTemplate.basicFunction(["res"], [ | |
`${RuntimeGlobals.moduleFactories}[chunkId] = ${RuntimeGlobals.moduleFactories}[idToExternalMapping[chunkId]];`, | |
`${RuntimeGlobals.moduleFactories}[chunkId]`, | |
"resolve();", | |
]), | |
), | |
")", | |
]), | |
"reject", | |
), | |
")", | |
]), | |
)), | |
");", | |
]), | |
"}", | |
"promises.push(data.p);", | |
]) | |
}`, | |
]); | |
} | |
} | |
const federationPlugins = compiler.options.plugins.filter((p) => | |
p && typeof p === "object" && | |
p instanceof webpack.container.ModuleFederationPlugin | |
); | |
const allRemotes = new Set(); | |
compiler.options.resolve.alias = compiler.options.resolve.alias || {}; | |
for (const plugin of federationPlugins) { | |
const remotes = plugin._options.remotes; | |
if (!remotes) continue; | |
const remoteNames = Object.keys(remotes); | |
for (const remote of remoteNames) { | |
allRemotes.add(remote); | |
compiler.options.entry[`__webpack_remote__:${remote}`] = { | |
import: [remote], | |
}; | |
} | |
} | |
compiler.hooks.thisCompilation.tap( | |
ExposeRemotesPlugin.NAME, | |
(compilation) => { | |
compilation.hooks.optimizeModuleIds.tap( | |
ExposeRemotesPlugin.NAME, | |
() => { | |
/** @type {Array<webpack.Chunk & { name: string }>} */ | |
const remoteModules = []; | |
const otherChunks = []; | |
for ( | |
const chunk of compilation.chunks | |
) { | |
if ( | |
!chunk.name || !chunk.name.startsWith("__webpack_remote__:") | |
) { | |
otherChunks.push(chunk); | |
continue; | |
} | |
for (const mod of compilation.chunkGraph.getChunkModules(chunk)) { | |
remoteModules.push(mod); | |
} | |
} | |
for (const chunk of otherChunks) { | |
compilation.chunkGraph.addChunkRuntimeRequirements( | |
chunk, | |
new Set([ | |
RuntimeGlobals.hasOwnProperty, | |
RuntimeGlobals.externalInstallChunk, | |
RuntimeGlobals.moduleFactories, | |
]), | |
); | |
compilation.addRuntimeModule( | |
chunk, | |
new ExposeRemotesRuntimeModule(), | |
); | |
compilation.chunkGraph.attachModules( | |
chunk, | |
remoteModules, | |
); | |
} | |
}, | |
); | |
}, | |
); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment