Skip to content

Commit deb6003

Browse files
committed
fix: fix HMR for concatenated CSS modules with style exportType
- Use stable per-module identifiers ([moduleId, style] tuples) instead of positional indices for injected style elements in concatenated CSS modules - Track inner module IDs of ConcatenatedModules in HMR records so removed sub-modules appear in hotUpdateMainJson.m - Add buildMeta.needIdWithoutChunk flag to ensure style-type CSS modules retain module IDs when disconnected from chunks during concatenation
1 parent 3e6d0ea commit deb6003

12 files changed

Lines changed: 48 additions & 22 deletions

File tree

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"webpack": patch
3+
---
4+
5+
Fix HMR for concatenated CSS modules with `style` exportType by using stable per-module identifiers for injected style elements and tracking inner module IDs of concatenated modules in HMR records

lib/HotModuleReplacementPlugin.js

Lines changed: 25 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ const JavascriptParser = require("./javascript/JavascriptParser");
3030
const {
3131
evaluateToIdentifier
3232
} = require("./javascript/JavascriptParserHelpers");
33+
const ConcatenatedModule = require("./optimize/ConcatenatedModule");
3334
const { find, isSubset } = require("./util/SetHelpers");
3435
const TupleSet = require("./util/TupleSet");
3536
const { compareModulesById } = require("./util/comparators");
@@ -389,13 +390,30 @@ class HotModuleReplacementPlugin {
389390
for (const chunk of compilation.chunks) {
390391
const chunkId = /** @type {ChunkId} */ (chunk.id);
391392

392-
records.chunkModuleIds[chunkId] = Array.from(
393-
chunkGraph.getOrderedChunkModulesIterable(
394-
chunk,
395-
compareModulesById(chunkGraph)
396-
),
397-
(m) => /** @type {ModuleId} */ (chunkGraph.getModuleId(m))
398-
);
393+
/** @type {ModuleId[]} */
394+
const moduleIds = [];
395+
for (const m of chunkGraph.getOrderedChunkModulesIterable(
396+
chunk,
397+
compareModulesById(chunkGraph)
398+
)) {
399+
moduleIds.push(
400+
/** @type {ModuleId} */ (chunkGraph.getModuleId(m))
401+
);
402+
if (m instanceof ConcatenatedModule && m.modules) {
403+
for (const innerModule of m.modules) {
404+
if (
405+
innerModule.buildMeta &&
406+
innerModule.buildMeta.needIdInConcatenation
407+
) {
408+
const innerId = chunkGraph.getModuleId(innerModule);
409+
if (innerId !== null) {
410+
moduleIds.push(innerId);
411+
}
412+
}
413+
}
414+
}
415+
}
416+
records.chunkModuleIds[chunkId] = moduleIds;
399417
}
400418
});
401419
/** @type {TupleSet<Module, Chunk>} */

lib/Module.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,7 @@ const makeSerializable = require("./util/makeSerializable");
174174
* @property {boolean=} async
175175
* @property {boolean=} sideEffectFree
176176
* @property {boolean=} isCssModule
177+
* @property {boolean=} needIdInConcatenation
177178
* @property {Record<string, string>=} jsIncompatibleExports
178179
* @property {Map<string, Record<string, string>>=} exportsFinalNameByRuntime
179180
* @property {Map<string, string>=} exportsSourceByRuntime

lib/css/CssGenerator.js

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -474,18 +474,18 @@ class CssGenerator extends Generator {
474474
RuntimeGlobals.cssInjectStyle
475475
);
476476

477+
const moduleId = generateContext.chunkGraph.getModuleId(module);
478+
477479
if (generateContext.concatenationScope) {
478480
return new ConcatSource(
479-
"__webpack_css_styles__.push(",
481+
`__webpack_css_styles__.push([${JSON.stringify(moduleId)}, `,
480482
this._cssToJsLiteral(cssSource, devtool),
481-
");"
483+
"]);"
482484
);
483485
}
484486

485-
const moduleId = generateContext.chunkGraph.getModuleId(module);
486-
487487
return new ConcatSource(
488-
`${RuntimeGlobals.cssInjectStyle}(${JSON.stringify(moduleId || "")}, `,
488+
`${RuntimeGlobals.cssInjectStyle}(${JSON.stringify(moduleId)}, `,
489489
this._cssToJsLiteral(cssSource, devtool),
490490
");"
491491
);

lib/css/CssModulesPlugin.js

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -517,12 +517,10 @@ class CssModulesPlugin {
517517
}
518518
}
519519
if (injectCssStylesVar) {
520-
const moduleId = `${compilation.chunkGraph.getModuleId(module)}|`;
521-
522520
/** @type {ConcatSource} */
523521
(source).add(
524522
"for (let i = 0; i < __webpack_css_styles__.length; i++) {\n" +
525-
`${RuntimeGlobals.cssInjectStyle}(${`${JSON.stringify(moduleId)} + i`}, __webpack_css_styles__[i]);\n` +
523+
`${RuntimeGlobals.cssInjectStyle}(__webpack_css_styles__[i][0], __webpack_css_styles__[i][1]);\n` +
526524
"}"
527525
);
528526
}

lib/css/CssParser.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -699,6 +699,10 @@ class CssParser extends Parser {
699699

700700
/** @type {BuildMeta} */
701701
(module.buildMeta).isCssModule = isModules;
702+
if (/** @type {CssModule} */ (module).exportType === "style") {
703+
/** @type {BuildMeta} */
704+
(module.buildMeta).needIdInConcatenation = true;
705+
}
702706

703707
const locConverter = new LocConverter(source);
704708

lib/ids/IdHelpers.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -263,7 +263,8 @@ const getUsedModuleIdsAndModules = (compilation, filter) => {
263263
(!filter || filter(module)) &&
264264
(chunkGraph.getNumberOfModuleChunks(module) !== 0 ||
265265
// CSS modules need IDs even when not in chunks, for generating CSS class names(i.e. [id]-[local])
266-
/** @type {BuildMeta} */ (module.buildMeta).isCssModule)
266+
/** @type {BuildMeta} */ (module.buildMeta).isCssModule ||
267+
/** @type {BuildMeta} */ (module.buildMeta).needIdInConcatenation)
267268
) {
268269
modules.push(module);
269270
}

test/hotCases/css/export-type-concatenation/index.js

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { textA, textB, sheetA, sheetB, linkAClass, linkBClass, styleA, styleB } from "./lib.js";
1+
import { textA, textB, sheetA, sheetB, linkAClass, linkBClass, styleB } from "./lib.js";
22

33
it("should handle HMR for all exportTypes with concatenation", function (done) {
44
// Verify modules are concatenated: only index.js and lib.js (+ update helper)
@@ -22,7 +22,6 @@ it("should handle HMR for all exportTypes with concatenation", function (done) {
2222
expect(sheetB._cssText).toContain("color: purple");
2323

2424
// Initial state: style (style-a has @import of style-a-dep)
25-
expect(typeof styleA).toBe("string");
2625
expect(typeof styleB).toBe("string");
2726
const allStyles = () => Array.from(window.document.getElementsByTagName("style")).map(s => s.textContent);
2827
expect(allStyles().some(c => c.includes("color: red"))).toBe(true);
@@ -52,8 +51,8 @@ it("should handle HMR for all exportTypes with concatenation", function (done) {
5251
expect(sheetB._cssText).toContain("color: violet");
5352

5453
// After HMR: style (style-a no longer has @import after update)
55-
expect(typeof styleA).toBe("string");
5654
expect(typeof styleB).toBe("string");
55+
expect(allStyles().length).toBe(2);
5756
expect(allStyles().some(c => c.includes("font-size: 12px"))).toBe(false);
5857
expect(allStyles().some(c => c.includes("color: blue"))).toBe(true);
5958

test/hotCases/css/export-type-concatenation/lib.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,5 +4,5 @@ export { default as sheetA } from "./sheet-a.css";
44
export { default as sheetB } from "./sheet-b.css";
55
export { "link-a-class" as linkAClass } from "./link-a.module.css";
66
export { "link-b-class" as linkBClass } from "./link-b.module.css";
7-
export { "style-a" as styleA } from "./style-a.css";
8-
export { "style-b" as styleB } from "./style-b.css";
7+
import "./style-a.css";
8+
export { "style-b" as styleB } from "./style-b.module.css";

test/hotCases/css/export-type-concatenation/style-b.css renamed to test/hotCases/css/export-type-concatenation/style-b.module.css

File renamed without changes.

0 commit comments

Comments
 (0)