add support for import chunk loading with runtime chunk

This commit is contained in:
Tobias Koppers 2021-07-08 09:13:42 +02:00
parent 1bb0db3a86
commit 77ed50425d
7 changed files with 213 additions and 48 deletions

View File

@ -379,6 +379,7 @@ class Template {
source.add(Template.toNormalComment(module.identifier()) + "\n");
if (!module.shouldIsolate()) {
source.add(runtimeSource);
source.add("\n\n");
} else if (renderContext.runtimeTemplate.supportsArrowFunction()) {
source.add("(() => {\n");
if (renderContext.useStrict) source.add('\t"use strict";\n');

View File

@ -0,0 +1,29 @@
/*
MIT License http://www.opensource.org/licenses/mit-license.php
*/
"use strict";
const RuntimeModule = require("../RuntimeModule");
class ExportWebpackRequireRuntimeModule extends RuntimeModule {
constructor() {
super("export webpack runtime", RuntimeModule.STAGE_ATTACH);
}
/**
* @returns {boolean} true, if the runtime module should get it's own scope
*/
shouldIsolate() {
return false;
}
/**
* @returns {string} runtime code
*/
generate() {
return "export default __webpack_require__;";
}
}
module.exports = ExportWebpackRequireRuntimeModule;

View File

@ -5,13 +5,18 @@
"use strict";
const { ConcatSource } = require("webpack-sources");
const { ConcatSource, RawSource } = require("webpack-sources");
const { RuntimeGlobals } = require("..");
const HotUpdateChunk = require("../HotUpdateChunk");
const Template = require("../Template");
const {
getCompilationHooks
getCompilationHooks,
getChunkFilenameTemplate
} = require("../javascript/JavascriptModulesPlugin");
const {
generateEntryStartup,
updateHashForEntryStartup
} = require("../javascript/StartupHelpers");
/** @typedef {import("../Compiler")} Compiler */
@ -30,8 +35,9 @@ class ModuleChunkFormatPlugin {
(chunk, set) => {
if (chunk.hasRuntime()) return;
if (compilation.chunkGraph.getNumberOfEntryModules(chunk) > 0) {
set.add(RuntimeGlobals.onChunksLoaded);
set.add(RuntimeGlobals.require);
set.add(RuntimeGlobals.startupEntrypoint);
set.add(RuntimeGlobals.externalInstallChunk);
}
}
);
@ -39,7 +45,7 @@ class ModuleChunkFormatPlugin {
hooks.renderChunk.tap(
"ModuleChunkFormatPlugin",
(modules, renderContext) => {
const { chunk, chunkGraph } = renderContext;
const { chunk, chunkGraph, runtimeTemplate } = renderContext;
const hotUpdateChunk =
chunk instanceof HotUpdateChunk ? chunk : null;
const source = new ConcatSource();
@ -68,9 +74,84 @@ class ModuleChunkFormatPlugin {
chunkGraph.getChunkEntryModulesWithChunkGroupIterable(chunk)
);
if (entries.length > 0) {
throw new Error(
"Entry modules in chunk is not implemented for module chunk format yet"
const runtimeChunk = entries[0][1].getRuntimeChunk();
const currentOutputName = compilation
.getPath(
getChunkFilenameTemplate(chunk, compilation.outputOptions),
{
chunk,
contentHashType: "javascript"
}
)
.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();
// remove common parts
while (
currentOutputName.length > 0 &&
runtimeOutputName.length > 0 &&
currentOutputName[0] === runtimeOutputName[0]
) {
currentOutputName.shift();
runtimeOutputName.shift();
}
// create final path
const runtimePath =
(currentOutputName.length > 0
? "../".repeat(currentOutputName.length)
: "./") + runtimeOutputName.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
)};\n`
);
entrySource.add(
`${RuntimeGlobals.externalInstallChunk}(__webpack_self_exports__);\n`
);
const startupSource = new RawSource(
generateEntryStartup(
chunkGraph,
runtimeTemplate,
entries,
chunk,
false
)
);
entrySource.add(
hooks.renderStartup.call(
startupSource,
entries[entries.length - 1][0],
{
...renderContext,
inlined: false
}
)
);
return entrySource;
}
}
return source;
@ -82,11 +163,10 @@ class ModuleChunkFormatPlugin {
if (chunk.hasRuntime()) return;
hash.update("ModuleChunkFormatPlugin");
hash.update("1");
// TODO
// const entries = Array.from(
// chunkGraph.getChunkEntryModulesWithChunkGroupIterable(chunk)
// );
// updateHashForEntryStartup(hash, chunkGraph, entries, chunk);
const entries = Array.from(
chunkGraph.getChunkEntryModulesWithChunkGroupIterable(chunk)
);
updateHashForEntryStartup(hash, chunkGraph, entries, chunk);
}
);
}

View File

@ -6,6 +6,7 @@
"use strict";
const RuntimeGlobals = require("../RuntimeGlobals");
const ExportWebpackRequireRuntimeModule = require("./ExportWebpackRequireRuntimeModule");
const ModuleChunkLoadingRuntimeModule = require("./ModuleChunkLoadingRuntimeModule");
/** @typedef {import("../Compiler")} Compiler */
@ -45,9 +46,21 @@ class ModuleChunkLoadingPlugin {
compilation.hooks.runtimeRequirementInTree
.for(RuntimeGlobals.baseURI)
.tap("ModuleChunkLoadingPlugin", handler);
compilation.hooks.runtimeRequirementInTree
.for(RuntimeGlobals.externalInstallChunk)
.tap("ModuleChunkLoadingPlugin", handler);
compilation.hooks.runtimeRequirementInTree
.for(RuntimeGlobals.onChunksLoaded)
.tap("ModuleChunkLoadingPlugin", handler);
compilation.hooks.runtimeRequirementInTree
.for(RuntimeGlobals.externalInstallChunk)
.tap("ModuleChunkLoadingPlugin", (chunk, set) => {
if (!isEnabledForChunk(chunk)) return;
compilation.addRuntimeModule(
chunk,
new ExportWebpackRequireRuntimeModule()
);
});
compilation.hooks.runtimeRequirementInTree
.for(RuntimeGlobals.ensureChunkHandlers)

View File

@ -67,6 +67,9 @@ class ModuleChunkLoadingRuntimeModule extends RuntimeModule {
} = compilation;
const fn = RuntimeGlobals.ensureChunkHandlers;
const withBaseURI = this._runtimeRequirements.has(RuntimeGlobals.baseURI);
const withExternalInstallChunk = this._runtimeRequirements.has(
RuntimeGlobals.externalInstallChunk
);
const withLoading = this._runtimeRequirements.has(
RuntimeGlobals.ensureChunkHandlers
);
@ -110,6 +113,38 @@ class ModuleChunkLoadingRuntimeModule extends RuntimeModule {
),
"};",
"",
withLoading || withExternalInstallChunk
? `var installChunk = ${runtimeTemplate.basicFunction("data", [
runtimeTemplate.destructureObject(
["ids", "modules", "runtime"],
"data"
),
'// add "modules" to the modules object,',
'// then flag all "ids" as loaded and fire callback',
"var moduleId, chunkId, i = 0;",
"for(moduleId in modules) {",
Template.indent([
`if(${RuntimeGlobals.hasOwnProperty}(modules, moduleId)) {`,
Template.indent(
`${RuntimeGlobals.moduleFactories}[moduleId] = modules[moduleId];`
),
"}"
]),
"}",
"if(runtime) runtime(__webpack_require__);",
"for(;i < ids.length; i++) {",
Template.indent([
"chunkId = ids[i];",
`if(${RuntimeGlobals.hasOwnProperty}(installedChunks, chunkId) && installedChunks[chunkId]) {`,
Template.indent("installedChunks[chunkId][0]();"),
"}",
"installedChunks[ids[i]] = 0;"
]),
"}",
withOnChunkLoad ? `${RuntimeGlobals.onChunksLoaded}();` : ""
])}`
: "// no install chunk",
"",
withLoading
? Template.asString([
`${fn}.j = ${runtimeTemplate.basicFunction(
@ -137,45 +172,13 @@ class ModuleChunkLoadingRuntimeModule extends RuntimeModule {
rootOutputDir
)} + ${
RuntimeGlobals.getChunkScriptFilename
}(chunkId)).then(${runtimeTemplate.basicFunction(
"data",
}(chunkId)).then(installChunk, ${runtimeTemplate.basicFunction(
"e",
[
runtimeTemplate.destructureObject(
["ids", "modules", "runtime"],
"data"
),
'// add "modules" to the modules object,',
'// then flag all "ids" as loaded and fire callback',
"var moduleId, chunkId, i = 0;",
"for(moduleId in modules) {",
Template.indent([
`if(${RuntimeGlobals.hasOwnProperty}(modules, moduleId)) {`,
Template.indent(
`${RuntimeGlobals.moduleFactories}[moduleId] = modules[moduleId];`
),
"}"
]),
"}",
"if(runtime) runtime(__webpack_require__);",
"for(;i < ids.length; i++) {",
Template.indent([
"chunkId = ids[i];",
`if(${RuntimeGlobals.hasOwnProperty}(installedChunks, chunkId) && installedChunks[chunkId]) {`,
Template.indent(
"installedChunks[chunkId][0]();"
),
"}",
"installedChunks[ids[i]] = 0;"
]),
"}",
withOnChunkLoad
? `${RuntimeGlobals.onChunksLoaded}();`
: ""
"if(installedChunks[chunkId] !== 0) installedChunks[chunkId] = undefined;",
"throw e;"
]
)}, ${runtimeTemplate.basicFunction("e", [
"if(installedChunks[chunkId] !== 0) installedChunks[chunkId] = undefined;",
"throw e;"
])});`,
)});`,
`var promise = Promise.race([promise, new Promise(${runtimeTemplate.expressionFunction(
`installedChunkData = installedChunks[chunkId] = [resolve]`,
"resolve"
@ -193,6 +196,12 @@ class ModuleChunkLoadingRuntimeModule extends RuntimeModule {
])
: "// no chunk on demand loading",
"",
withExternalInstallChunk
? Template.asString([
`${RuntimeGlobals.externalInstallChunk} = installChunk;`
])
: "// no external install chunk",
"",
withOnChunkLoad
? `${
RuntimeGlobals.onChunksLoaded

View File

@ -17,6 +17,24 @@ module.exports = (env, { testPath }) => [
outputModule: true
}
},
{
output: {
filename: "esm-runtimeChunk/[name].js",
libraryTarget: "module"
},
target: "node14",
resolve: {
alias: {
external: "./non-external"
}
},
optimization: {
runtimeChunk: "single"
},
experiments: {
outputModule: true
}
},
{
output: {
filename: "commonjs.js",

View File

@ -14,6 +14,21 @@ module.exports = (env, { testPath }) => [
})
]
},
{
resolve: {
alias: {
library: path.resolve(
testPath,
"../0-create-library/esm-runtimeChunk/main.js"
)
}
},
plugins: [
new webpack.DefinePlugin({
NAME: JSON.stringify("esm-runtimeChunk")
})
]
},
{
resolve: {
alias: {