Skip to content

Commit

Permalink
Merge pull request #13757 from webpack/feature/module-runtime-chunk
Browse files Browse the repository at this point in the history
add support for import chunk loading with runtime chunk
  • Loading branch information
sokra committed Jul 8, 2021
2 parents 1bb0db3 + 77ed504 commit 740cdc4
Show file tree
Hide file tree
Showing 7 changed files with 213 additions and 48 deletions.
1 change: 1 addition & 0 deletions lib/Template.js
Expand Up @@ -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');
Expand Down
29 changes: 29 additions & 0 deletions lib/esm/ExportWebpackRequireRuntimeModule.js
@@ -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;
102 changes: 91 additions & 11 deletions lib/esm/ModuleChunkFormatPlugin.js
Expand Up @@ -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 */

Expand All @@ -30,16 +35,17 @@ 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);
}
}
);
const hooks = getCompilationHooks(compilation);
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();
Expand Down Expand Up @@ -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;
Expand All @@ -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);
}
);
}
Expand Down
13 changes: 13 additions & 0 deletions lib/esm/ModuleChunkLoadingPlugin.js
Expand Up @@ -6,6 +6,7 @@
"use strict";

const RuntimeGlobals = require("../RuntimeGlobals");
const ExportWebpackRequireRuntimeModule = require("./ExportWebpackRequireRuntimeModule");
const ModuleChunkLoadingRuntimeModule = require("./ModuleChunkLoadingRuntimeModule");

/** @typedef {import("../Compiler")} Compiler */
Expand Down Expand Up @@ -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)
Expand Down
83 changes: 46 additions & 37 deletions lib/esm/ModuleChunkLoadingRuntimeModule.js
Expand Up @@ -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
);
Expand Down Expand Up @@ -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(
Expand Down Expand Up @@ -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"
Expand All @@ -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
Expand Down
18 changes: 18 additions & 0 deletions test/configCases/library/0-create-library/webpack.config.js
Expand Up @@ -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",
Expand Down
15 changes: 15 additions & 0 deletions test/configCases/library/1-use-library/webpack.config.js
Expand Up @@ -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: {
Expand Down

0 comments on commit 740cdc4

Please sign in to comment.