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

add support for import chunk loading with runtime chunk #13757

Merged
merged 1 commit into from Jul 8, 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
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