Skip to content

Instantly share code, notes, and snippets.

@jacob-ebey
Last active August 27, 2023 01:47
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jacob-ebey/cd32da68d622aa356dbd1cedfa3b5ab6 to your computer and use it in GitHub Desktop.
Save jacob-ebey/cd32da68d622aa356dbd1cedfa3b5ab6 to your computer and use it in GitHub Desktop.
Webpack Federation Expose Remotes Plugin
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,
});
});
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