Skip to content

Commit

Permalink
Merge pull request #14829 from webpack/bugfix/split-chunks-esm
Browse files Browse the repository at this point in the history
fix outputModule with initial splitChunks
  • Loading branch information
sokra committed Nov 25, 2021
2 parents 093eadf + c69e37c commit 4f9fafc
Show file tree
Hide file tree
Showing 10 changed files with 207 additions and 120 deletions.
123 changes: 74 additions & 49 deletions lib/esm/ModuleChunkFormatPlugin.js
Expand Up @@ -5,18 +5,16 @@

"use strict";

const { ConcatSource, RawSource } = require("webpack-sources");
const { ConcatSource } = require("webpack-sources");
const { RuntimeGlobals } = require("..");
const HotUpdateChunk = require("../HotUpdateChunk");
const Template = require("../Template");
const { getAllChunks } = require("../javascript/ChunkHelpers");
const {
getCompilationHooks,
getChunkFilenameTemplate
} = require("../javascript/JavascriptModulesPlugin");
const {
generateEntryStartup,
updateHashForEntryStartup
} = require("../javascript/StartupHelpers");
const { updateHashForEntryStartup } = require("../javascript/StartupHelpers");

/** @typedef {import("../Compiler")} Compiler */

Expand Down Expand Up @@ -84,63 +82,90 @@ class ModuleChunkFormatPlugin {
}
)
.split("/");
const runtimeOutputName = compilation
.getPath(
getChunkFilenameTemplate(
runtimeChunk,
compilation.outputOptions
),
{
chunk: runtimeChunk,
contentHashType: "javascript"
}
)
.split("/");

// remove filename, we only need the directory
const outputFilename = currentOutputName.pop();
currentOutputName.pop();

// remove common parts
while (
currentOutputName.length > 0 &&
runtimeOutputName.length > 0 &&
currentOutputName[0] === runtimeOutputName[0]
) {
currentOutputName.shift();
runtimeOutputName.shift();
}
const getRelativePath = chunk => {
const baseOutputName = currentOutputName.slice();
const chunkOutputName = compilation
.getPath(
getChunkFilenameTemplate(
chunk,
compilation.outputOptions
),
{
chunk: chunk,
contentHashType: "javascript"
}
)
.split("/");

// create final path
const runtimePath =
(currentOutputName.length > 0
? "../".repeat(currentOutputName.length)
: "./") + runtimeOutputName.join("/");
// remove common parts
while (
baseOutputName.length > 0 &&
chunkOutputName.length > 0 &&
baseOutputName[0] === chunkOutputName[0]
) {
baseOutputName.shift();
chunkOutputName.shift();
}
// create final path
return (
(baseOutputName.length > 0
? "../".repeat(baseOutputName.length)
: "./") + chunkOutputName.join("/")
);
};

const entrySource = new ConcatSource();
entrySource.add(source);
entrySource.add(";\n\n// load runtime\n");
entrySource.add(
`import __webpack_require__ from ${JSON.stringify(
runtimePath
)};\n`
);
entrySource.add(
`import * as __webpack_self_exports__ from ${JSON.stringify(
"./" + outputFilename
getRelativePath(runtimeChunk)
)};\n`
);
entrySource.add(
`${RuntimeGlobals.externalInstallChunk}(__webpack_self_exports__);\n`
);
const startupSource = new RawSource(
generateEntryStartup(
chunkGraph,
runtimeTemplate,
entries,
chunk,
false
)

const startupSource = new ConcatSource();
startupSource.add(
`var __webpack_exec__ = ${runtimeTemplate.returningFunction(
`__webpack_require__(${RuntimeGlobals.entryModuleId} = moduleId)`,
"moduleId"
)}\n`
);

const loadedChunks = new Set();
let index = 0;
for (let i = 0; i < entries.length; i++) {
const [module, entrypoint] = entries[i];
const final = i + 1 === entries.length;
const moduleId = chunkGraph.getModuleId(module);
const chunks = getAllChunks(
entrypoint,
runtimeChunk,
undefined
);
for (const chunk of chunks) {
if (loadedChunks.has(chunk)) continue;
loadedChunks.add(chunk);
startupSource.add(
`import * as __webpack_chunk_${index}__ from ${JSON.stringify(
getRelativePath(chunk)
)};\n`
);
startupSource.add(
`${RuntimeGlobals.externalInstallChunk}(__webpack_chunk_${index}__);\n`
);
index++;
}
startupSource.add(
`${
final ? "var __webpack_exports__ = " : ""
}__webpack_exec__(${JSON.stringify(moduleId)});\n`
);
}

entrySource.add(
hooks.renderStartup.call(
startupSource,
Expand Down
33 changes: 33 additions & 0 deletions lib/javascript/ChunkHelpers.js
@@ -0,0 +1,33 @@
/*
MIT License http://www.opensource.org/licenses/mit-license.php
Author Tobias Koppers @sokra
*/

"use strict";

const Entrypoint = require("../Entrypoint");

/** @typedef {import("../Chunk")} Chunk */

/**
* @param {Entrypoint} entrypoint a chunk group
* @param {Chunk} excludedChunk1 current chunk which is excluded
* @param {Chunk} excludedChunk2 runtime chunk which is excluded
* @returns {Set<Chunk>} chunks
*/
const getAllChunks = (entrypoint, excludedChunk1, excludedChunk2) => {
const queue = new Set([entrypoint]);
const chunks = new Set();
for (const entrypoint of queue) {
for (const chunk of entrypoint.chunks) {
if (chunk === excludedChunk1) continue;
if (chunk === excludedChunk2) continue;
chunks.add(chunk);
}
for (const parent of entrypoint.parentsIterable) {
if (parent instanceof Entrypoint) queue.add(parent);
}
}
return chunks;
};
exports.getAllChunks = getAllChunks;
26 changes: 1 addition & 25 deletions lib/javascript/StartupHelpers.js
Expand Up @@ -5,10 +5,10 @@

"use strict";

const Entrypoint = require("../Entrypoint");
const RuntimeGlobals = require("../RuntimeGlobals");
const Template = require("../Template");
const { isSubset } = require("../util/SetHelpers");
const { getAllChunks } = require("./ChunkHelpers");
const { chunkHasJs } = require("./JavascriptModulesPlugin");

/** @typedef {import("../util/Hash")} Hash */
Expand All @@ -19,30 +19,6 @@ const { chunkHasJs } = require("./JavascriptModulesPlugin");
/** @typedef {import("../RuntimeTemplate")} RuntimeTemplate */
/** @typedef {(string|number)[]} EntryItem */

// TODO move to this file to ../javascript/ChunkHelpers.js

/**
* @param {Entrypoint} entrypoint a chunk group
* @param {Chunk} excludedChunk1 current chunk which is excluded
* @param {Chunk} excludedChunk2 runtime chunk which is excluded
* @returns {Set<Chunk>} chunks
*/
const getAllChunks = (entrypoint, excludedChunk1, excludedChunk2) => {
const queue = new Set([entrypoint]);
const chunks = new Set();
for (const entrypoint of queue) {
for (const chunk of entrypoint.chunks) {
if (chunk === excludedChunk1) continue;
if (chunk === excludedChunk2) continue;
chunks.add(chunk);
}
for (const parent of entrypoint.parentsIterable) {
if (parent instanceof Entrypoint) queue.add(parent);
}
}
return chunks;
};

const EXPORT_PREFIX = "var __webpack_exports__ = ";

/**
Expand Down
87 changes: 44 additions & 43 deletions test/ConfigTestCases.template.js
Expand Up @@ -305,6 +305,7 @@ const describeCases = config => {
if (testConfig.beforeExecute) testConfig.beforeExecute();
const results = [];
for (let i = 0; i < optionsArr.length; i++) {
const options = optionsArr[i];
const bundlePath = testConfig.findBundle(i, optionsArr[i]);
if (bundlePath) {
filesCount++;
Expand All @@ -327,6 +328,43 @@ const describeCases = config => {
const requireCache = Object.create(null);
const esmCache = new Map();
const esmIdentifier = `${category.name}-${testName}-${i}`;
const baseModuleScope = {
console: console,
it: _it,
beforeEach: _beforeEach,
afterEach: _afterEach,
expect,
jest,
__STATS__: jsonStats,
nsObj: m => {
Object.defineProperty(m, Symbol.toStringTag, {
value: "Module"
});
return m;
}
};

let runInNewContext = false;
if (
options.target === "web" ||
options.target === "webworker"
) {
baseModuleScope.window = globalContext;
baseModuleScope.self = globalContext;
baseModuleScope.URL = URL;
baseModuleScope.Worker =
require("./helpers/createFakeWorker")({
outputDirectory
});
runInNewContext = true;
}
if (testConfig.moduleScope) {
testConfig.moduleScope(baseModuleScope);
}
const esmContext = vm.createContext(baseModuleScope, {
name: "context for esm"
});

// eslint-disable-next-line no-loop-func
const _require = (
currentDirectory,
Expand Down Expand Up @@ -380,41 +418,7 @@ const describeCases = config => {
options.experiments &&
options.experiments.outputModule;

let runInNewContext = false;

const moduleScope = {
console: console,
it: _it,
beforeEach: _beforeEach,
afterEach: _afterEach,
expect,
jest,
__STATS__: jsonStats,
nsObj: m => {
Object.defineProperty(m, Symbol.toStringTag, {
value: "Module"
});
return m;
}
};

if (
options.target === "web" ||
options.target === "webworker"
) {
moduleScope.window = globalContext;
moduleScope.self = globalContext;
moduleScope.URL = URL;
moduleScope.Worker =
require("./helpers/createFakeWorker")({
outputDirectory
});
runInNewContext = true;
}
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'."
Expand All @@ -424,11 +428,7 @@ const describeCases = config => {
esm = new vm.SourceTextModule(content, {
identifier: esmIdentifier + "-" + p,
url: pathToFileURL(p).href + "?" + esmIdentifier,
context:
(parentModule && parentModule.context) ||
vm.createContext(moduleScope, {
name: `context for ${p}`
}),
context: esmContext,
initializeImportMeta: (meta, module) => {
meta.url = pathToFileURL(p).href;
},
Expand Down Expand Up @@ -488,7 +488,8 @@ const describeCases = config => {
exports: {}
};
requireCache[p] = m;
Object.assign(moduleScope, {
const moduleScope = {
...baseModuleScope,
require: _require.bind(
null,
path.dirname(p),
Expand All @@ -511,7 +512,7 @@ const describeCases = config => {
__dirname: path.dirname(p),
__filename: p,
_globalAssign: { expect }
});
};
if (testConfig.moduleScope) {
testConfig.moduleScope(moduleScope);
}
Expand Down Expand Up @@ -549,14 +550,14 @@ const describeCases = config => {
results.push(
_require(
outputDirectory,
optionsArr[i],
options,
"./" + bundlePathItem
)
);
}
} else {
results.push(
_require(outputDirectory, optionsArr[i], bundlePath)
_require(outputDirectory, options, bundlePath)
);
}
}
Expand Down
2 changes: 1 addition & 1 deletion test/configCases/module/runtime-chunk/test.config.js
@@ -1,5 +1,5 @@
module.exports = {
findBundle: function () {
return ["./runtime.js", "./main.js"];
return ["./runtime.mjs", "./main.mjs"];
}
};
4 changes: 2 additions & 2 deletions test/configCases/module/runtime-chunk/webpack.config.js
@@ -1,9 +1,9 @@
/** @type {import("../../../../").Configuration} */
module.exports = {
output: {
filename: "[name].js"
filename: "[name].mjs"
},
target: "web",
target: ["web", "es2020"],
experiments: {
outputModule: true
},
Expand Down

0 comments on commit 4f9fafc

Please sign in to comment.