diff --git a/lib/container/ContainerEntryModule.js b/lib/container/ContainerEntryModule.js index e592ce480e5..8611f1f4378 100644 --- a/lib/container/ContainerEntryModule.js +++ b/lib/container/ContainerEntryModule.js @@ -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"); @@ -104,6 +105,7 @@ class ContainerEntryModule extends Module { strict: true, topLevelDeclarations: new Set(["moduleMap", "get", "init"]) }; + this.buildMeta.exportsType = "namespace"; this.clearDependenciesAndBlocks(); @@ -127,6 +129,7 @@ class ContainerEntryModule extends Module { } this.addBlock(block); } + this.addDependency(new StaticExportsDependency(["get", "init"], false)); callback(); } diff --git a/test/ConfigTestCases.template.js b/test/ConfigTestCases.template.js index c511745abb2..3dcdbecb89c 100644 --- a/test/ConfigTestCases.template.js +++ b/test/ConfigTestCases.template.js @@ -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, @@ -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)) { @@ -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, @@ -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" @@ -439,39 +411,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: 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( @@ -479,8 +455,11 @@ const describeCases = config => { 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, @@ -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); @@ -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 diff --git a/test/configCases/container/0-container-full/test.config.js b/test/configCases/container/0-container-full/test.config.js new file mode 100644 index 00000000000..2d0d66fd4c0 --- /dev/null +++ b/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"; + } +}; diff --git a/test/configCases/container/0-container-full/webpack.config.js b/test/configCases/container/0-container-full/webpack.config.js index 96cd2424ce8..3fe8d8bab2c 100644 --- a/test/configCases/container/0-container-full/webpack.config.js +++ b/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[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" + } +]; diff --git a/test/configCases/container/1-container-full/package.json b/test/configCases/container/1-container-full/package.json index e04e63e83ad..be6238fec84 100644 --- a/test/configCases/container/1-container-full/package.json +++ b/test/configCases/container/1-container-full/package.json @@ -1,4 +1,8 @@ { + "private": true, + "engines": { + "node": ">=10.13.0" + }, "dependencies": { "react": "*" } diff --git a/test/configCases/container/1-container-full/test.config.js b/test/configCases/container/1-container-full/test.config.js new file mode 100644 index 00000000000..2d0d66fd4c0 --- /dev/null +++ b/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"; + } +}; diff --git a/test/configCases/container/1-container-full/webpack.config.js b/test/configCases/container/1-container-full/webpack.config.js index 0717682bc7c..049f843e7eb 100644 --- a/test/configCases/container/1-container-full/webpack.config.js +++ b/test/configCases/container/1-container-full/webpack.config.js @@ -1,33 +1,67 @@ // eslint-disable-next-line node/no-unpublished-require const { ModuleFederationPlugin } = require("../../../../").container; -/** @type {import("../../../../").Configuration} */ -module.exports = { +const common = { entry: { - bundle0: "./index.js" + main: "./index.js" }, - output: { - filename: "[name].js", - uniqueName: "1-container-full" - }, - plugins: [ - new ModuleFederationPlugin({ - name: "container", - library: { type: "commonjs-module" }, - filename: "container.js", - runtime: false, - exposes: { - "./ComponentB": "./ComponentB", - "./ComponentC": "./ComponentC" - }, - remotes: { - containerA: "../0-container-full/container.js", - containerB: "./container.js" - }, - shared: ["react"] - }) - ], optimization: { runtimeChunk: "single" } }; + +/** @type {ConstructorParameters[0]} */ +const commonMF = { + runtime: false, + exposes: { + "./ComponentB": "./ComponentB", + "./ComponentC": "./ComponentC" + }, + shared: ["react"] +}; + +/** @type {import("../../../../").Configuration[]} */ +module.exports = [ + { + ...common, + output: { + filename: "[name].js", + uniqueName: "1-container-full" + }, + plugins: [ + new ModuleFederationPlugin({ + name: "container", + library: { type: "commonjs-module" }, + filename: "container.js", + remotes: { + containerA: "../0-container-full/container.js", + containerB: "./container.js" + }, + ...commonMF + }) + ] + }, + { + ...common, + experiments: { + outputModule: true + }, + output: { + filename: "module/[name].mjs", + uniqueName: "1-container-full-mjs" + }, + plugins: [ + new ModuleFederationPlugin({ + name: "container", + library: { type: "module" }, + filename: "module/container.mjs", + remotes: { + containerA: "../../0-container-full/module/container.mjs", + containerB: "./container.mjs" + }, + ...commonMF + }) + ], + target: "node14" + } +];