fix: deferred modules define order and transitive deferred module not emitted correctly in concatenated modules

This commit is contained in:
Jack Works 2025-09-24 04:33:02 +08:00 committed by GitHub
parent 73cecf5e78
commit c647cf193c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
17 changed files with 119 additions and 37 deletions

View File

@ -23,6 +23,7 @@ const Template = require("../Template");
const { DEFAULTS } = require("../config/defaults");
const HarmonyImportDependency = require("../dependencies/HarmonyImportDependency");
const HarmonyImportSideEffectDependency = require("../dependencies/HarmonyImportSideEffectDependency");
const HarmonyImportSpecifierDependency = require("../dependencies/HarmonyImportSpecifierDependency");
const JavascriptParser = require("../javascript/JavascriptParser");
const {
getMakeDeferredNamespaceModeFromExportsType,
@ -1832,6 +1833,25 @@ ${defineGetters}`
if (!source) continue;
result.add(source);
}
if (info.type === "external" && info.deferred) {
const moduleId = JSON.stringify(chunkGraph.getModuleId(info.module));
const loader = getOptimizedDeferredModule(
runtimeTemplate,
info.module.getExportsType(
moduleGraph,
/** @type {BuildMeta} */
(this.rootModule.buildMeta).strictHarmonyModule
),
moduleId,
// an async module will opt-out of the concat module optimization.
[]
);
runtimeRequirements.add(RuntimeGlobals.require);
result.add(
`\n// DEFERRED EXTERNAL MODULE: ${info.module.readableIdentifier(requestShortener)}\nvar ${info.deferredName} = ${loader};`
);
}
}
/** @type {InitFragment<ChunkRenderContext>[]} */
@ -1855,10 +1875,19 @@ ${defineGetters}`
for (const dep of info.module.dependencies) {
if (
!(/** @type {HarmonyImportDependency} */ (dep).defer) &&
dep instanceof HarmonyImportSideEffectDependency
(dep instanceof HarmonyImportSideEffectDependency ||
dep instanceof HarmonyImportSpecifierDependency)
) {
const referredModule = moduleGraph.getModule(dep);
if (!referredModule) continue;
if (!referredModule) {
if (dep instanceof HarmonyImportSideEffectDependency) {
continue;
} else {
throw new Error(
"Deferred module used, but no module in the graph."
);
}
}
if (moduleGraph.isDeferred(referredModule)) {
const deferredModuleInfo = /** @type {ExternalModuleInfo} */ (
modulesWithInfo.find(
@ -1887,6 +1916,8 @@ ${defineGetters}`
break;
}
case "external": {
// deferred case is handled in the "const info of modulesWithInfo" loop above
if (!info.deferred) {
result.add(
`\n// EXTERNAL MODULE: ${info.module.readableIdentifier(
requestShortener
@ -1906,22 +1937,9 @@ ${defineGetters}`
isConditional = true;
result.add(`if (${condition}) {\n`);
}
const moduleId = JSON.stringify(chunkGraph.getModuleId(info.module));
if (info.deferred) {
const loader = getOptimizedDeferredModule(
runtimeTemplate,
info.module.getExportsType(
moduleGraph,
/** @type {BuildMeta} */
(this.rootModule.buildMeta).strictHarmonyModule
),
moduleId,
// an async module will opt-out of the concat module optimization.
[]
const moduleId = JSON.stringify(
chunkGraph.getModuleId(info.module)
);
result.add(`var ${info.deferredName} = ${loader};`);
name = info.deferredName;
} else {
result.add(`var ${info.name} = __webpack_require__(${moduleId});`);
name = info.name;
}

View File

@ -174,7 +174,7 @@ class MakeDeferredNamespaceObjectRuntimeModule extends HelperRuntimeModule {
])},`,
`ownKeys: ${runtimeTemplate.basicFunction("", [
init,
`var keys = Reflect.ownKeys(ns).filter(${runtimeTemplate.expressionFunction('x !== "then"', "x")}).concat([Symbol.toStringTag]);`,
`var keys = Reflect.ownKeys(ns).filter(${runtimeTemplate.expressionFunction('x !== "then" && x !== Symbol.toStringTag', "x")}).concat([Symbol.toStringTag]);`,
"return keys;"
])},`,
`getOwnPropertyDescriptor: ${runtimeTemplate.basicFunction("_, name", [

View File

@ -0,0 +1,3 @@
import { a } from 'mod'
Object.keys(a).forEach(() => { })

View File

@ -0,0 +1,3 @@
import defer * as m from 'mod'
Object.keys(m).forEach(() => {})

View File

@ -0,0 +1,3 @@
it('should compile and load', () => {
require('./main.js');
})

View File

@ -0,0 +1,2 @@
import './a'
import './b'

View File

@ -0,0 +1 @@
export { a } from './re-export'

View File

@ -0,0 +1,5 @@
{
"sideEffects": false,
"main": "./index.js",
"name": "mod"
}

View File

@ -0,0 +1,3 @@
export function a() {
}

View File

@ -0,0 +1,12 @@
"use strict";
/** @type {import("../../../../types").Configuration} */
module.exports = {
target: [`async-node${process.versions.node.split(".").map(Number)[0]}`],
optimization: {
concatenateModules: true
},
experiments: {
deferImport: true
}
};

View File

@ -0,0 +1,5 @@
import defer * as mod from 'mod'
export function b() {
mod.fn()
}

View File

@ -0,0 +1,3 @@
it('should compile and load', () => {
require('./main.js');
})

View File

@ -0,0 +1,5 @@
import { fn } from 'mod'
import { b } from './a'
fn()
b()

View File

@ -0,0 +1 @@
export { fn } from './re-export'

View File

@ -0,0 +1,5 @@
{
"sideEffects": false,
"main": "./index.js",
"name": "mod"
}

View File

@ -0,0 +1 @@
export function fn() {}

View File

@ -0,0 +1,12 @@
"use strict";
/** @type {import("../../../../types").Configuration} */
module.exports = {
target: [`async-node${process.versions.node.split(".").map(Number)[0]}`],
optimization: {
concatenateModules: true
},
experiments: {
deferImport: true
}
};