From 77ed50425d1a3850da790709275a06a08ad2654b Mon Sep 17 00:00:00 2001 From: Tobias Koppers Date: Thu, 8 Jul 2021 09:13:42 +0200 Subject: [PATCH] add support for import chunk loading with runtime chunk --- lib/Template.js | 1 + lib/esm/ExportWebpackRequireRuntimeModule.js | 29 +++++ lib/esm/ModuleChunkFormatPlugin.js | 102 ++++++++++++++++-- lib/esm/ModuleChunkLoadingPlugin.js | 13 +++ lib/esm/ModuleChunkLoadingRuntimeModule.js | 83 +++++++------- .../0-create-library/webpack.config.js | 18 ++++ .../library/1-use-library/webpack.config.js | 15 +++ 7 files changed, 213 insertions(+), 48 deletions(-) create mode 100644 lib/esm/ExportWebpackRequireRuntimeModule.js diff --git a/lib/Template.js b/lib/Template.js index 891b74bc584..93551d5dd93 100644 --- a/lib/Template.js +++ b/lib/Template.js @@ -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'); diff --git a/lib/esm/ExportWebpackRequireRuntimeModule.js b/lib/esm/ExportWebpackRequireRuntimeModule.js new file mode 100644 index 00000000000..42d97cbd46c --- /dev/null +++ b/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; diff --git a/lib/esm/ModuleChunkFormatPlugin.js b/lib/esm/ModuleChunkFormatPlugin.js index 0602ec6be08..8045fe404b6 100644 --- a/lib/esm/ModuleChunkFormatPlugin.js +++ b/lib/esm/ModuleChunkFormatPlugin.js @@ -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); } ); } diff --git a/lib/esm/ModuleChunkLoadingPlugin.js b/lib/esm/ModuleChunkLoadingPlugin.js index 35fdd726d3d..c72180a430a 100644 --- a/lib/esm/ModuleChunkLoadingPlugin.js +++ b/lib/esm/ModuleChunkLoadingPlugin.js @@ -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) diff --git a/lib/esm/ModuleChunkLoadingRuntimeModule.js b/lib/esm/ModuleChunkLoadingRuntimeModule.js index 76af7d15753..d4eba186bc3 100644 --- a/lib/esm/ModuleChunkLoadingRuntimeModule.js +++ b/lib/esm/ModuleChunkLoadingRuntimeModule.js @@ -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 diff --git a/test/configCases/library/0-create-library/webpack.config.js b/test/configCases/library/0-create-library/webpack.config.js index f77134bc150..807fc9de9d3 100644 --- a/test/configCases/library/0-create-library/webpack.config.js +++ b/test/configCases/library/0-create-library/webpack.config.js @@ -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", diff --git a/test/configCases/library/1-use-library/webpack.config.js b/test/configCases/library/1-use-library/webpack.config.js index 4fc8cdb1941..d74992f29b2 100644 --- a/test/configCases/library/1-use-library/webpack.config.js +++ b/test/configCases/library/1-use-library/webpack.config.js @@ -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: {