Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix and test module federation with ESM #14827

Merged
merged 3 commits into from Nov 25, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
3 changes: 3 additions & 0 deletions lib/container/ContainerEntryModule.js
Expand Up @@ -10,6 +10,7 @@ const AsyncDependenciesBlock = require("../AsyncDependenciesBlock");
const Module = require("../Module");
const RuntimeGlobals = require("../RuntimeGlobals");
const Template = require("../Template");
const StaticExportsDependency = require("../dependencies/StaticExportsDependency");
const makeSerializable = require("../util/makeSerializable");
const ContainerExposedDependency = require("./ContainerExposedDependency");

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
147 changes: 80 additions & 67 deletions test/ConfigTestCases.template.js
Expand Up @@ -325,6 +325,8 @@ const describeCases = config => {
};

const requireCache = Object.create(null);
const esmCache = new Map();
const esmIdentifier = `${category.name}-${testName}-${i}`;
// eslint-disable-next-line no-loop-func
const _require = (
currentDirectory,
Expand All @@ -335,7 +337,7 @@ const describeCases = config => {
) => {
if (testConfig === undefined) {
throw new Error(
`_require(${module}) called after all tests have completed`
`_require(${module}) called after all tests from ${category.name} ${testName} have completed`
);
}
if (Array.isArray(module) || /^\.\.?\//.test(module)) {
Expand Down Expand Up @@ -373,16 +375,15 @@ 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 = {
console: console,
it: _it,
beforeEach: _beforeEach,
afterEach: _afterEach,
Expand All @@ -396,36 +397,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,48 +411,55 @@ 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: esmIdentifier + "-" + p,
url: pathToFileURL(p).href + "?" + esmIdentifier,
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(
async (specifier, referencingModule) => {
return await asModule(
await _require(
path.dirname(
referencingModule.identifier ||
fileURLToPath(referencingModule.url)
referencingModule.identifier
? referencingModule.identifier.slice(
esmIdentifier.length + 1
)
: fileURLToPath(referencingModule.url)
),
options,
specifier,
Expand All @@ -502,6 +481,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 +530,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";
}
};
83 changes: 58 additions & 25 deletions test/configCases/container/0-container-full/webpack.config.js
@@ -1,28 +1,61 @@
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
}
}
})
]
/** @type {ConstructorParameters<typeof ModuleFederationPlugin>[0]} */
const common = {
name: "container",
exposes: {
"./ComponentA": {
import: "./ComponentA"
}
},
shared: {
react: {
version: false,
requiredVersion: false
}
}
};

/** @type {import("../../../../").Configuration[]} */
module.exports = [
{
output: {
filename: "[name].js",
uniqueName: "0-container-full"
},
plugins: [
new ModuleFederationPlugin({
library: { type: "commonjs-module" },
filename: "container.js",
remotes: {
containerA: {
external: "./container.js"
}
},
...common
})
]
},
{
experiments: {
outputModule: true
},
output: {
filename: "module/[name].mjs",
uniqueName: "0-container-full-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";
}
};