refactor: use runtime module for optimized deferred module (#20057)

This commit is contained in:
Gengkun 2025-10-29 22:32:23 +08:00 committed by GitHub
parent a576d0f318
commit 9a2e984b76
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 108 additions and 45 deletions

View File

@ -267,6 +267,11 @@ module.exports.makeDeferredNamespaceObjectSymbol = "__webpack_require__.zS";
*/
module.exports.makeNamespaceObject = "__webpack_require__.r";
/**
* make a optimized deferred namespace object
*/
module.exports.makeOptimizedDeferredNamespaceObject = "__webpack_require__.zO";
/**
* the internal module object
*/

View File

@ -24,7 +24,10 @@ const GetTrustedTypesPolicyRuntimeModule = require("./runtime/GetTrustedTypesPol
const GlobalRuntimeModule = require("./runtime/GlobalRuntimeModule");
const HasOwnPropertyRuntimeModule = require("./runtime/HasOwnPropertyRuntimeModule");
const LoadScriptRuntimeModule = require("./runtime/LoadScriptRuntimeModule");
const MakeDeferredNamespaceObjectRuntime = require("./runtime/MakeDeferredNamespaceObjectRuntime");
const {
MakeDeferredNamespaceObjectRuntimeModule,
MakeOptimizedDeferredNamespaceObjectRuntimeModule
} = require("./runtime/MakeDeferredNamespaceObjectRuntime");
const MakeNamespaceObjectRuntimeModule = require("./runtime/MakeNamespaceObjectRuntimeModule");
const NonceRuntimeModule = require("./runtime/NonceRuntimeModule");
const OnChunksLoadedRuntimeModule = require("./runtime/OnChunksLoadedRuntimeModule");
@ -78,6 +81,7 @@ const GLOBALS_ON_REQUIRE = [
RuntimeGlobals.loadScript,
RuntimeGlobals.systemContext,
RuntimeGlobals.onChunksLoaded,
RuntimeGlobals.makeOptimizedDeferredNamespaceObject,
RuntimeGlobals.makeDeferredNamespaceObject
];
@ -96,11 +100,11 @@ const TREE_DEPENDENCIES = {
RuntimeGlobals.makeNamespaceObject,
RuntimeGlobals.require
],
[RuntimeGlobals.makeOptimizedDeferredNamespaceObject]: [
RuntimeGlobals.require
],
[RuntimeGlobals.makeDeferredNamespaceObject]: [
RuntimeGlobals.definePropertyGetters,
RuntimeGlobals.makeNamespaceObject,
RuntimeGlobals.createFakeNamespaceObject,
RuntimeGlobals.hasOwnProperty,
RuntimeGlobals.require
],
[RuntimeGlobals.initializeSharing]: [RuntimeGlobals.shareScopeMap],
@ -190,12 +194,23 @@ class RuntimePlugin {
);
return true;
});
compilation.hooks.runtimeRequirementInTree
.for(RuntimeGlobals.makeOptimizedDeferredNamespaceObject)
.tap("RuntimePlugin", (chunk, runtimeRequirement) => {
compilation.addRuntimeModule(
chunk,
new MakeOptimizedDeferredNamespaceObjectRuntimeModule(
runtimeRequirement.has(RuntimeGlobals.asyncModule)
)
);
return true;
});
compilation.hooks.runtimeRequirementInTree
.for(RuntimeGlobals.makeDeferredNamespaceObject)
.tap("RuntimePlugin", (chunk, runtimeRequirement) => {
compilation.addRuntimeModule(
chunk,
new MakeDeferredNamespaceObjectRuntime(
new MakeDeferredNamespaceObjectRuntimeModule(
runtimeRequirement.has(RuntimeGlobals.asyncModule)
)
);

View File

@ -871,10 +871,10 @@ class RuntimeTemplate {
const outgoingAsyncModules = getOutgoingAsyncModules(moduleGraph, module);
importContent = `/* deferred harmony import */ ${optDeclaration}${importVar} = ${getOptimizedDeferredModule(
this,
exportsType,
moduleId,
Array.from(outgoingAsyncModules, (mod) => chunkGraph.getModuleId(mod))
exportsType,
Array.from(outgoingAsyncModules, (mod) => chunkGraph.getModuleId(mod)),
runtimeRequirements
)};\n`;
return [importContent, ""];

View File

@ -1837,15 +1837,15 @@ ${defineGetters}`
if (info.type === "external" && info.deferred) {
const moduleId = JSON.stringify(chunkGraph.getModuleId(info.module));
const loader = getOptimizedDeferredModule(
runtimeTemplate,
moduleId,
info.module.getExportsType(
moduleGraph,
/** @type {BuildMeta} */
(this.rootModule.buildMeta).strictHarmonyModule
),
moduleId,
// an async module will opt-out of the concat module optimization.
[]
[],
runtimeRequirements
);
runtimeRequirements.add(RuntimeGlobals.require);
result.add(

View File

@ -8,8 +8,12 @@ const RuntimeGlobals = require("../RuntimeGlobals");
const Template = require("../Template");
const HelperRuntimeModule = require("./HelperRuntimeModule");
/** @typedef {import("../Module").RuntimeRequirements} RuntimeRequirements */
/** @typedef {import("../Module").ExportsType} ExportsType */
/** @typedef {import("../ChunkGraph").ModuleId} ModuleId */
/**
* @param {import("../Module").ExportsType} exportsType exports type
* @param {ExportsType} exportsType exports type
* @returns {string} mode
*/
function getMakeDeferredNamespaceModeFromExportsType(exportsType) {
@ -19,45 +23,80 @@ function getMakeDeferredNamespaceModeFromExportsType(exportsType) {
if (exportsType === "dynamic") return `/* ${exportsType} */ 3`;
return "";
}
/**
* @param {import("../ModuleTemplate").RuntimeTemplate} _runtimeTemplate runtimeTemplate
* @param {import("../Module").ExportsType} exportsType exportsType
* @param {string} moduleId moduleId
* @param {(import("../ChunkGraph").ModuleId | null)[]} asyncDepsIds asyncDepsIds
* @returns {string} function
* @param {ExportsType} exportsType exportsType
* @param {(ModuleId | null)[]} asyncDepsIds asyncDepsIds
* @param {RuntimeRequirements} runtimeRequirements runtime requirements
* @returns {string} call make optimized deferred namespace object
*/
function getOptimizedDeferredModule(
_runtimeTemplate,
exportsType,
moduleId,
asyncDepsIds
exportsType,
asyncDepsIds,
runtimeRequirements
) {
const isAsync = asyncDepsIds && asyncDepsIds.length;
const init = `${RuntimeGlobals.require}(${moduleId})${
isAsync ? `[${RuntimeGlobals.asyncModuleExportSymbol}]` : ""
}`;
const props = [
`/* ${exportsType} */ get a() {`,
// if exportsType is "namespace" we can generate the most optimized code,
// on the second access, we can avoid trigger the getter.
// we can also do this if exportsType is "dynamic" and there is a "__esModule" property on it.
exportsType === "namespace" || exportsType === "dynamic"
? Template.indent([
`var exports = ${init};`,
`${
exportsType === "dynamic" ? "if (exports.__esModule) " : ""
}Object.defineProperty(this, "a", { value: exports });`,
"return exports;"
])
: Template.indent([`return ${init};`]),
isAsync ? "}," : "}",
isAsync
? `[${
RuntimeGlobals.makeDeferredNamespaceObjectSymbol
}]: ${JSON.stringify(asyncDepsIds.filter((x) => x !== null))}`
runtimeRequirements.add(RuntimeGlobals.makeOptimizedDeferredNamespaceObject);
const mode = getMakeDeferredNamespaceModeFromExportsType(exportsType);
return `${RuntimeGlobals.makeOptimizedDeferredNamespaceObject}(${moduleId}, ${mode}${
asyncDepsIds.length > 0
? `, ${JSON.stringify(asyncDepsIds.filter((x) => x !== null))}`
: ""
];
return Template.asString(["{", Template.indent(props), "}"]);
})`;
}
class MakeOptimizedDeferredNamespaceObjectRuntimeModule extends HelperRuntimeModule {
/**
* @param {boolean} hasAsyncRuntime if async module is used.
*/
constructor(hasAsyncRuntime) {
super("make optimized deferred namespace object");
this.hasAsyncRuntime = hasAsyncRuntime;
}
/**
* @returns {string | null} runtime code
*/
generate() {
if (!this.compilation) return null;
const fn = RuntimeGlobals.makeOptimizedDeferredNamespaceObject;
const hasAsync = this.hasAsyncRuntime;
return Template.asString([
// Note: must be a function (not arrow), because this is used in body!
`${fn} = function(moduleId, mode${hasAsync ? ", asyncDeps" : ""}) {`,
Template.indent([
"// mode: 0 => namespace (esm)",
"// mode: 1 => default-only (esm strict cjs)",
"// mode: 2 => default-with-named (esm-cjs compat)",
"// mode: 3 => dynamic (if exports has __esModule, then esm, otherwise default-with-named)",
"var r = this;",
hasAsync ? "var isAsync = asyncDeps && asyncDeps.length;" : "",
"var obj = {",
Template.indent([
"get a() {",
Template.indent([
"var exports = r(moduleId);",
hasAsync
? `if(isAsync) exports = exports[${RuntimeGlobals.asyncModuleExportSymbol}];`
: "",
// if exportsType is "namespace" we can generate the most optimized code,
// on the second access, we can avoid trigger the getter.
// we can also do this if exportsType is "dynamic" and there is a "__esModule" property on it.
'if(mode == 0 || (mode == 3 && exports.__esModule)) Object.defineProperty(this, "a", { value: exports });',
"return exports;"
]),
"}"
]),
"};",
hasAsync
? `if(isAsync) obj[${RuntimeGlobals.makeDeferredNamespaceObjectSymbol}] = asyncDeps;`
: "",
"return obj;"
]),
"};"
]);
}
}
class MakeDeferredNamespaceObjectRuntimeModule extends HelperRuntimeModule {
@ -200,7 +239,10 @@ class MakeDeferredNamespaceObjectRuntimeModule extends HelperRuntimeModule {
}
}
module.exports = MakeDeferredNamespaceObjectRuntimeModule;
module.exports.MakeDeferredNamespaceObjectRuntimeModule =
MakeDeferredNamespaceObjectRuntimeModule;
module.exports.MakeOptimizedDeferredNamespaceObjectRuntimeModule =
MakeOptimizedDeferredNamespaceObjectRuntimeModule;
module.exports.getMakeDeferredNamespaceModeFromExportsType =
getMakeDeferredNamespaceModeFromExportsType;
module.exports.getOptimizedDeferredModule = getOptimizedDeferredModule;

1
types.d.ts vendored
View File

@ -19042,6 +19042,7 @@ declare namespace exports {
export let makeDeferredNamespaceObject: "__webpack_require__.z";
export let makeDeferredNamespaceObjectSymbol: "__webpack_require__.zS";
export let makeNamespaceObject: "__webpack_require__.r";
export let makeOptimizedDeferredNamespaceObject: "__webpack_require__.zO";
export let module: "module";
export let moduleCache: "__webpack_require__.c";
export let moduleFactories: "__webpack_require__.m";