fix: HMR failure in defer module (#19759)

This commit is contained in:
Xiao 2025-08-04 22:41:30 +08:00 committed by GitHub
parent e8e9f15b82
commit 07a9d20a4c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 111 additions and 2 deletions

View File

@ -18,6 +18,8 @@ const NullDependency = require("./NullDependency");
/** @typedef {import("../serialization/ObjectMiddleware").ObjectDeserializerContext} ObjectDeserializerContext */
/** @typedef {import("../serialization/ObjectMiddleware").ObjectSerializerContext} ObjectSerializerContext */
/** @typedef {import("./HarmonyAcceptImportDependency")} HarmonyAcceptImportDependency */
/** @typedef {import("../Module")} Module */
/** @typedef {import("../Module").ModuleId} ModuleId */
class HarmonyAcceptDependency extends NullDependency {
/**
@ -84,7 +86,57 @@ HarmonyAcceptDependency.Template = class HarmonyAcceptDependencyTemplate extends
chunkGraph
} = templateContext;
/** @type {HarmonyAcceptImportDependency[]} */
/**
* @param {Dependency} dependency the dependency to get module id for
* @returns {ModuleId | null} the module id or null if not found
*/
const getDependencyModuleId = (dependency) =>
chunkGraph.getModuleId(
/** @type {Module} */ (moduleGraph.getModule(dependency))
);
/**
* @param {Dependency} a the first dependency
* @param {Dependency} b the second dependency
* @returns {boolean} true if the dependencies are related
*/
const isRelatedHarmonyImportDependency = (a, b) =>
a !== b &&
b instanceof HarmonyImportDependency &&
getDependencyModuleId(a) === getDependencyModuleId(b);
/**
* HarmonyAcceptImportDependency lacks a lot of information, such as the defer property.
* One HarmonyAcceptImportDependency may need to generate multiple ImportStatements.
* Therefore, we find its original HarmonyImportDependency for code generation.
* @param {HarmonyAcceptImportDependency} dependency the dependency to get harmony import dependencies for
* @returns {HarmonyImportDependency[]} array of related harmony import dependencies
*/
const getHarmonyImportDependencies = (dependency) => {
const result = [];
let deferDependency = null;
let noDeferredDependency = null;
for (const d of module.dependencies) {
if (deferDependency && noDeferredDependency) break;
if (isRelatedHarmonyImportDependency(dependency, d)) {
if (d.defer) {
deferDependency = /** @type {HarmonyImportDependency} */ (d);
} else {
noDeferredDependency = /** @type {HarmonyImportDependency} */ (d);
}
}
}
if (deferDependency) result.push(deferDependency);
if (noDeferredDependency) result.push(noDeferredDependency);
if (result.length === 0) {
// fallback to the original dependency
result.push(dependency);
}
return result;
};
/** @type {HarmonyImportDependency[]} */
const syncDeps = [];
/** @type {HarmonyAcceptImportDependency[]} */
@ -96,7 +148,7 @@ HarmonyAcceptDependency.Template = class HarmonyAcceptDependencyTemplate extends
if (connection && moduleGraph.isAsync(connection.module)) {
asyncDeps.push(dependency);
} else {
syncDeps.push(dependency);
syncDeps.push(...getHarmonyImportDependencies(dependency));
}
}

View File

@ -0,0 +1,3 @@
export const a = "1";
---
export const a = "2";

View File

@ -0,0 +1,14 @@
import * as a /* webpackDefer: true */ from "./a.js";
import * as a2 from "./a.js";
it("should handle defer import", (done) => {
expect(a.a).toBe("1");
expect(a2.a).toBe("1");
module.hot.accept("./a", function() {
expect(a.a).toBe("2");
expect(a2.a).toBe("2");
done();
});
NEXT(require("../../update.js")(done));
});

View File

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

View File

@ -0,0 +1,3 @@
export const a = "1";
---
export const a = "2";

View File

@ -0,0 +1,11 @@
import * as a /* webpackDefer: true */ from "./a.js";
it("should handle defer import", (done) => {
expect(a.a).toBe("1");
module.hot.accept("./a", function() {
expect(a.a).toBe("2");
done();
});
NEXT(require("../../update")(done));
});

View File

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