Skip to content

Commit

Permalink
fix and test module federation with ESM
Browse files Browse the repository at this point in the history
  • Loading branch information
sokra committed Nov 25, 2021
1 parent 68c4a2a commit b6fa3b2
Show file tree
Hide file tree
Showing 7 changed files with 198 additions and 112 deletions.
3 changes: 3 additions & 0 deletions lib/container/ContainerEntryModule.js
Expand Up @@ -7,6 +7,7 @@

const { OriginalSource, RawSource } = require("webpack-sources");
const AsyncDependenciesBlock = require("../AsyncDependenciesBlock");
const StaticExportsDependency = require("../dependencies/StaticExportsDependency");
const Module = require("../Module");
const RuntimeGlobals = require("../RuntimeGlobals");
const Template = require("../Template");
Expand Down Expand Up @@ -104,6 +105,7 @@ class ContainerEntryModule extends Module {
strict: true,
topLevelDeclarations: new Set(["moduleMap", "get", "init"])
};
this.buildMeta.exportsType = "namespace";

this.clearDependenciesAndBlocks();

Expand All @@ -127,6 +129,7 @@ class ContainerEntryModule extends Module {
}
this.addBlock(block);
}
this.addDependency(new StaticExportsDependency(["get", "init"], false));

callback();
}
Expand Down
136 changes: 72 additions & 64 deletions test/ConfigTestCases.template.js
Expand Up @@ -325,6 +325,7 @@ const describeCases = config => {
};

const requireCache = Object.create(null);
const esmCache = new Map();
// eslint-disable-next-line no-loop-func
const _require = (
currentDirectory,
Expand Down Expand Up @@ -373,13 +374,11 @@ const describeCases = config => {
);
}
}
if (p in requireCache) {
return requireCache[p].exports;
}
const m = {
exports: {}
};
requireCache[p] = m;
const isModule =
p.endsWith(".mjs") &&
options.experiments &&
options.experiments.outputModule;

let runInNewContext = false;

const moduleScope = {
Expand All @@ -396,36 +395,7 @@ const describeCases = config => {
return m;
}
};
const isModule =
p.endsWith(".mjs") &&
options.experiments &&
options.experiments.outputModule;
if (!isModule) {
Object.assign(moduleScope, {
require: _require.bind(
null,
path.dirname(p),
options
),
importScripts: url => {
expect(url).toMatch(
/^https:\/\/test\.cases\/path\//
);
_require(
outputDirectory,
options,
`.${url.slice(
"https://test.cases/path".length
)}`
);
},
module: m,
exports: m.exports,
__dirname: path.dirname(p),
__filename: p,
_globalAssign: { expect }
});
}

if (
options.target === "web" ||
options.target === "webworker"
Expand All @@ -439,39 +409,43 @@ const describeCases = config => {
});
runInNewContext = true;
}
if (testConfig.moduleScope) {
testConfig.moduleScope(moduleScope);
}
if (isModule) {
if (testConfig.moduleScope) {
testConfig.moduleScope(moduleScope);
}
if (!vm.SourceTextModule)
throw new Error(
"Running this test requires '--experimental-vm-modules'.\nRun with 'node --experimental-vm-modules node_modules/jest-cli/bin/jest'."
);
const esm = new vm.SourceTextModule(content, {
identifier: p,
url: pathToFileURL(p).href,
context:
(parentModule && parentModule.context) ||
vm.createContext(moduleScope, {
name: `context for ${p}`
}),
initializeImportMeta: (meta, module) => {
meta.url = pathToFileURL(p).href;
},
importModuleDynamically: async (
specifier,
module
) => {
const result = await _require(
path.dirname(p),
options,
let esm = esmCache.get(p);
if (!esm) {
esm = new vm.SourceTextModule(content, {
identifier: p,
url: pathToFileURL(p).href,
context:
(parentModule && parentModule.context) ||
vm.createContext(moduleScope, {
name: `context for ${p}`
}),
initializeImportMeta: (meta, module) => {
meta.url = pathToFileURL(p).href;
},
importModuleDynamically: async (
specifier,
"evaluated",
module
);
return await asModule(result, module.context);
}
});
) => {
const result = await _require(
path.dirname(p),
options,
specifier,
"evaluated",
module
);
return await asModule(result, module.context);
}
});
esmCache.set(p, esm);
}
if (esmMode === "unlinked") return esm;
return (async () => {
await esm.link(
Expand Down Expand Up @@ -502,6 +476,40 @@ const describeCases = config => {
: ns;
})();
} else {
if (p in requireCache) {
return requireCache[p].exports;
}
const m = {
exports: {}
};
requireCache[p] = m;
Object.assign(moduleScope, {
require: _require.bind(
null,
path.dirname(p),
options
),
importScripts: url => {
expect(url).toMatch(
/^https:\/\/test\.cases\/path\//
);
_require(
outputDirectory,
options,
`.${url.slice(
"https://test.cases/path".length
)}`
);
},
module: m,
exports: m.exports,
__dirname: path.dirname(p),
__filename: p,
_globalAssign: { expect }
});
if (testConfig.moduleScope) {
testConfig.moduleScope(moduleScope);
}
if (!runInNewContext)
content = `Object.assign(global, _globalAssign); ${content}`;
const args = Object.keys(moduleScope);
Expand All @@ -517,8 +525,8 @@ const describeCases = config => {
: vm.runInThisContext(code, p);
fn.call(m.exports, ...argValues);
document.currentScript = oldCurrentScript;
return m.exports;
}
return m.exports;
} else if (
testConfig.modules &&
module in testConfig.modules
Expand Down
5 changes: 5 additions & 0 deletions test/configCases/container/0-container-full/test.config.js
@@ -0,0 +1,5 @@
module.exports = {
findBundle: function (i, options) {
return i === 0 ? "./main.js" : "./module/main.mjs";
}
};
80 changes: 55 additions & 25 deletions test/configCases/container/0-container-full/webpack.config.js
@@ -1,28 +1,58 @@
const { ModuleFederationPlugin } = require("../../../../").container;

/** @type {import("../../../../").Configuration} */
module.exports = {
plugins: [
new ModuleFederationPlugin({
name: "container",
library: { type: "commonjs-module" },
filename: "container.js",
exposes: {
"./ComponentA": {
import: "./ComponentA"
}
},
remotes: {
containerA: {
external: "./container.js"
}
},
shared: {
react: {
version: false,
requiredVersion: false
}
}
})
]
const common = {
name: "container",
exposes: {
"./ComponentA": {
import: "./ComponentA"
}
},
shared: {
react: {
version: false,
requiredVersion: false
}
}
};

/** @type {import("../../../../").Configuration[]} */
module.exports = [
{
output: {
filename: "[name].js"
},
plugins: [
new ModuleFederationPlugin({
library: { type: "commonjs-module" },
filename: "container.js",
remotes: {
containerA: {
external: "./container.js"
}
},
...common
})
]
},
{
experiments: {
outputModule: true
},
output: {
filename: "module/[name].mjs"
},
plugins: [
new ModuleFederationPlugin({
library: { type: "module" },
filename: "module/container.mjs",
remotes: {
containerA: {
external: "./container.mjs"
}
},
...common
})
],
target: "node14"
}
];
4 changes: 4 additions & 0 deletions test/configCases/container/1-container-full/package.json
@@ -1,4 +1,8 @@
{
"private": true,
"engines": {
"node": ">=10.13.0"
},
"dependencies": {
"react": "*"
}
Expand Down
5 changes: 5 additions & 0 deletions test/configCases/container/1-container-full/test.config.js
@@ -0,0 +1,5 @@
module.exports = {
findBundle: function (i, options) {
return i === 0 ? "./main.js" : "./module/main.mjs";
}
};

0 comments on commit b6fa3b2

Please sign in to comment.