refactor: CSS HMR

This commit is contained in:
alexander.akait 2024-11-28 23:35:59 +03:00
parent f4092a6059
commit 193b712734
11 changed files with 148 additions and 128 deletions

View File

@ -65,6 +65,11 @@ class CssModule extends NormalModule {
identifier += `|${inheritance.join("|")}`;
}
// We generate extra code for HMR, so we need to invalidate the module
if (this.hot) {
identifier += `|${this.hot}`;
}
return identifier;
}

View File

@ -40,11 +40,11 @@
/**
* @typedef {object} CssDependencyTemplateContextExtras
* @property {CssExportsData} cssExportsData the css exports data
* @property {CssData} cssData the css exports data
*/
/**
* @typedef {object} CssExportsData
* @typedef {object} CssData
* @property {boolean} esModule whether export __esModule
* @property {Map<string, string>} exports the css exports
*/

View File

@ -856,6 +856,10 @@ To fix this, make sure to include [runtime] in the output.hotUpdateMainFilename
.tap(PLUGIN_NAME, parser => {
applyImportMetaHot(parser);
});
normalModuleFactory.hooks.module.tap(PLUGIN_NAME, module => {
module.hot = true;
return module;
});
NormalModule.getCompilationHooks(compilation).loader.tap(
PLUGIN_NAME,

View File

@ -200,6 +200,9 @@ class Module extends DependenciesBlock {
/** @type {boolean} */
this.useSimpleSourceMap = false;
// Is hot context
/** @type {boolean} */
this.hot = false;
// Info from Build
/** @type {WebpackError[] | undefined} */
this._warnings = undefined;
@ -1075,6 +1078,7 @@ class Module extends DependenciesBlock {
write(this.factoryMeta);
write(this.useSourceMap);
write(this.useSimpleSourceMap);
write(this.hot);
write(
this._warnings !== undefined && this._warnings.length === 0
? undefined
@ -1104,6 +1108,7 @@ class Module extends DependenciesBlock {
this.factoryMeta = read();
this.useSourceMap = read();
this.useSimpleSourceMap = read();
this.hot = read();
this._warnings = read();
this._errors = read();
this.buildMeta = read();

View File

@ -22,8 +22,8 @@ const Template = require("../Template");
/** @typedef {import("../../declarations/WebpackOptions").CssModuleGeneratorOptions} CssModuleGeneratorOptions */
/** @typedef {import("../CodeGenerationResults")} CodeGenerationResults */
/** @typedef {import("../Dependency")} Dependency */
/** @typedef {import("../DependencyTemplate").CssData} CssData */
/** @typedef {import("../DependencyTemplate").CssDependencyTemplateContext} DependencyTemplateContext */
/** @typedef {import("../DependencyTemplate").CssExportsData} CssExportsData */
/** @typedef {import("../Generator").GenerateContext} GenerateContext */
/** @typedef {import("../Generator").UpdateHashContext} UpdateHashContext */
/** @typedef {import("../Module").ConcatenationBailoutReasonContext} ConcatenationBailoutReasonContext */
@ -69,8 +69,8 @@ class CssGenerator extends Generator {
/** @type {InitFragment<GenerateContext>[]} */
const initFragments = [];
/** @type {CssExportsData} */
const cssExportsData = {
/** @type {CssData} */
const cssData = {
esModule: this.esModule,
exports: new Map()
};
@ -91,7 +91,7 @@ class CssGenerator extends Generator {
/** @type {CodeGenerationResults} */
(generateContext.codeGenerationResults),
initFragments,
cssExportsData,
cssData,
get chunkInitFragments() {
if (!chunkInitFragments) {
const data =
@ -131,12 +131,14 @@ class CssGenerator extends Generator {
switch (generateContext.type) {
case "javascript": {
module.buildInfo.cssData = cssData;
generateContext.runtimeRequirements.add(RuntimeGlobals.module);
if (generateContext.concatenationScope) {
const source = new ConcatSource();
const usedIdentifiers = new Set();
for (const [name, v] of cssExportsData.exports) {
for (const [name, v] of cssData.exports) {
const usedName = generateContext.moduleGraph
.getExportInfo(module, name)
.getUsedName(name, generateContext.runtime);
@ -180,7 +182,7 @@ class CssGenerator extends Generator {
const exports = [];
for (const [name, v] of cssExportsData.exports) {
for (const [name, v] of cssData.exports) {
exports.push(`\t${JSON.stringify(name)}: ${JSON.stringify(v)}`);
}
@ -199,11 +201,6 @@ class CssGenerator extends Generator {
generateContext.runtimeRequirements.add(RuntimeGlobals.hasCssModules);
const data =
/** @type {NonNullable<GenerateContext["getData"]>} */
(generateContext.getData)();
data.set("css-exports", cssExportsData);
return InitFragment.addToSource(source, initFragments, generateContext);
}
}
@ -226,7 +223,16 @@ class CssGenerator extends Generator {
getSize(module, type) {
switch (type) {
case "javascript": {
return 42;
/** @type {undefined} */
const exports = module.buildInfo.cssData.exports;
const stringifiedExports = JSON.stringify(
Array.from(exports).reduce((obj, [key, value]) => {
obj[key] = value;
return obj;
}, {})
);
return stringifiedExports.length + 42;
}
case "css": {
const originalSource = module.originalSource();

View File

@ -190,21 +190,13 @@ class CssLoadingRuntimeModule extends RuntimeModule {
runtimeTemplate.outputOptions.uniqueName
)};`
: "// data-webpack is not used as build has no uniqueName",
`var loadCssChunkData = ${runtimeTemplate.basicFunction(
"target, chunkId",
[
`${withHmr ? "var moduleIds = [];" : ""}`,
`${
withHmr ? `if(target == ${RuntimeGlobals.moduleFactories}) ` : ""
}installedChunks[chunkId] = 0;`,
withHmr ? "return moduleIds;" : ""
]
)}`,
withLoading || withHmr
? Template.asString([
'var loadingAttribute = "data-webpack-loading";',
`var loadStylesheet = ${runtimeTemplate.basicFunction(
`chunkId, url, done${withHmr ? ", hmr" : ""}${
`chunkId, url, done${
withFetchPriority ? ", fetchPriority" : ""
}`,
}${withHmr ? ", hmr" : ""}`,
[
'var link, needAttach, key = "chunk-" + chunkId;',
withHmr ? "if(!hmr) {" : "",
@ -249,12 +241,16 @@ class CssLoadingRuntimeModule extends RuntimeModule {
"link.onload = onLinkComplete.bind(null, link.onload);"
]),
"} else onLinkComplete(undefined, { type: 'load', target: link });", // We assume any existing stylesheet is render blocking
withFetchPriority
? 'if (hmr && hmr.getAttribute("fetchpriority")) link.setAttribute("fetchpriority", hmr.getAttribute("fetchpriority"));'
: "",
withHmr ? "hmr ? document.head.insertBefore(link, hmr) :" : "",
"needAttach && document.head.appendChild(link);",
"return link;"
]
)};`,
"",
)};`
])
: "",
withLoading
? Template.asString([
`${fn}.css = ${runtimeTemplate.basicFunction(
@ -306,7 +302,7 @@ class CssLoadingRuntimeModule extends RuntimeModule {
]),
"} else {",
Template.indent([
`loadCssChunkData(${RuntimeGlobals.moduleFactories}, chunkId);`,
"installedChunks[chunkId] = 0;",
"installedChunkData[0]();"
]),
"}"
@ -429,32 +425,20 @@ class CssLoadingRuntimeModule extends RuntimeModule {
"var oldTags = [];",
"var newTags = [];",
`var applyHandler = ${runtimeTemplate.basicFunction("options", [
`return { dispose: ${runtimeTemplate.basicFunction(
"",
[]
)}, apply: ${runtimeTemplate.basicFunction("", [
"var moduleIds = [];",
`newTags.forEach(${runtimeTemplate.expressionFunction(
"info[1].sheet.disabled = false",
"info"
)});`,
`return { dispose: ${runtimeTemplate.basicFunction("", [
"while(oldTags.length) {",
Template.indent([
"var oldTag = oldTags.pop();",
"if(oldTag.parentNode) oldTag.parentNode.removeChild(oldTag);"
]),
"}",
"}"
])}, apply: ${runtimeTemplate.basicFunction("", [
"while(newTags.length) {",
Template.indent([
"var info = newTags.pop();",
`var chunkModuleIds = loadCssChunkData(${RuntimeGlobals.moduleFactories}, info[0]);`,
`chunkModuleIds.forEach(${runtimeTemplate.expressionFunction(
"moduleIds.push(id)",
"id"
)});`
"var newTag = newTags.pop();",
"newTag.sheet.disabled = false"
]),
"}",
"return moduleIds;"
"}"
])} };`
])}`,
`var cssTextKey = ${runtimeTemplate.returningFunction(
@ -494,20 +478,14 @@ class CssLoadingRuntimeModule extends RuntimeModule {
"} else {",
Template.indent([
"try { if(cssTextKey(oldTag) == cssTextKey(link)) { if(link.parentNode) link.parentNode.removeChild(link); return resolve(); } } catch(e) {}",
"var factories = {};",
"loadCssChunkData(factories, chunkId);",
`Object.keys(factories).forEach(${runtimeTemplate.expressionFunction(
"updatedModulesList.push(id)",
"id"
)})`,
"link.sheet.disabled = true;",
"oldTags.push(oldTag);",
"newTags.push([chunkId, link]);",
"newTags.push(link);",
"resolve();"
]),
"}"
]
)}, oldTag);`
)}, ${withFetchPriority ? "undefined," : ""} oldTag);`
]
)}));`
])});`

View File

@ -10,7 +10,8 @@ const {
ConcatSource,
PrefixSource,
ReplaceSource,
CachedSource
CachedSource,
RawSource
} = require("webpack-sources");
const Compilation = require("../Compilation");
const CssModule = require("../CssModule");
@ -24,6 +25,7 @@ const {
} = require("../ModuleTypeConstants");
const RuntimeGlobals = require("../RuntimeGlobals");
const SelfModuleFactory = require("../SelfModuleFactory");
const Template = require("../Template");
const WebpackError = require("../WebpackError");
const CssIcssExportDependency = require("../dependencies/CssIcssExportDependency");
const CssIcssImportDependency = require("../dependencies/CssIcssImportDependency");
@ -33,6 +35,7 @@ const CssLocalIdentifierDependency = require("../dependencies/CssLocalIdentifier
const CssSelfLocalIdentifierDependency = require("../dependencies/CssSelfLocalIdentifierDependency");
const CssUrlDependency = require("../dependencies/CssUrlDependency");
const StaticExportsDependency = require("../dependencies/StaticExportsDependency");
const JavascriptModulesPlugin = require("../javascript/JavascriptModulesPlugin");
const { compareModulesByIdentifier } = require("../util/comparators");
const createSchemaValidation = require("../util/create-schema-validation");
const createHash = require("../util/createHash");
@ -358,8 +361,36 @@ class CssModulesPlugin {
return new CssModule(createData);
});
}
JavascriptModulesPlugin.getCompilationHooks(
compilation
).renderModuleContent.tap(PLUGIN_NAME, (source, module) => {
if (module instanceof CssModule && module.hot) {
const exports = module.buildInfo.cssData.exports;
const stringifiedExports = JSON.stringify(
Array.from(exports).reduce((obj, [key, value]) => {
obj[key] = value;
return obj;
}, {})
);
const hmrCode = Template.asString([
"",
`var exports = ${stringifiedExports};`,
"// only invalidate when locals change",
"if (module.hot.data && module.hot.data.exports && module.hot.data.exports != exports) {",
Template.indent("module.hot.invalidate();"),
"} else {",
Template.indent("module.hot.accept();"),
"}",
"module.hot.dispose(function(data) { data.exports = exports; });"
]);
return new ConcatSource(source, "\n", new RawSource(hmrCode));
}
});
const orderedCssModulesPerChunk = new WeakMap();
compilation.hooks.afterCodeGeneration.tap("CssModulesPlugin", () => {
compilation.hooks.afterCodeGeneration.tap(PLUGIN_NAME, () => {
const { chunkGraph } = compilation;
for (const chunk of compilation.chunks) {
if (CssModulesPlugin.chunkHasCss(chunk, chunkGraph)) {
@ -370,13 +401,10 @@ class CssModulesPlugin {
}
}
});
compilation.hooks.chunkHash.tap(
"CssModulesPlugin",
(chunk, hash, context) => {
compilation.hooks.chunkHash.tap(PLUGIN_NAME, (chunk, hash, context) => {
hooks.chunkHash.call(chunk, hash, context);
}
);
compilation.hooks.contentHash.tap("CssModulesPlugin", chunk => {
});
compilation.hooks.contentHash.tap(PLUGIN_NAME, chunk => {
const {
chunkGraph,
codeGenerationResults,
@ -483,7 +511,6 @@ class CssModulesPlugin {
onceForChunkSet.add(chunk);
if (!isEnabledForChunk(chunk)) return;
set.add(RuntimeGlobals.moduleFactoriesAddOnly);
set.add(RuntimeGlobals.makeNamespaceObject);
const CssLoadingRuntimeModule = getCssLoadingRuntimeModule();
@ -531,7 +558,6 @@ class CssModulesPlugin {
}
set.add(RuntimeGlobals.publicPath);
set.add(RuntimeGlobals.getChunkCssFilename);
set.add(RuntimeGlobals.moduleFactoriesAddOnly);
});
}
);
@ -801,7 +827,6 @@ class CssModulesPlugin {
*/
renderChunk(
{
uniqueName,
undoPath,
chunk,
chunkGraph,

View File

@ -125,11 +125,7 @@ CssIcssExportDependency.Template = class CssIcssExportDependencyTemplate extends
* @param {DependencyTemplateContext} templateContext the context object
* @returns {void}
*/
apply(
dependency,
source,
{ cssExportsData, module: m, runtime, moduleGraph }
) {
apply(dependency, source, { cssData, module: m, runtime, moduleGraph }) {
const dep = /** @type {CssIcssExportDependency} */ (dependency);
const module = /** @type {CssModule} */ (m);
const convention =
@ -147,7 +143,7 @@ CssIcssExportDependency.Template = class CssIcssExportDependencyTemplate extends
);
for (const used of usedNames.concat(names)) {
cssExportsData.exports.set(used, dep.value);
cssData.exports.set(used, dep.value);
}
}
};

View File

@ -115,12 +115,12 @@ CssIcssSymbolDependency.Template = class CssValueAtRuleDependencyTemplate extend
* @param {DependencyTemplateContext} templateContext the context object
* @returns {void}
*/
apply(dependency, source, { cssExportsData }) {
apply(dependency, source, { cssData }) {
const dep = /** @type {CssIcssSymbolDependency} */ (dependency);
source.replace(dep.range[0], dep.range[1] - 1, dep.value);
cssExportsData.exports.set(dep.name, dep.value);
cssData.exports.set(dep.name, dep.value);
}
};

View File

@ -338,7 +338,7 @@ CssLocalIdentifierDependency.Template = class CssLocalIdentifierDependencyTempla
* @returns {void}
*/
apply(dependency, source, templateContext) {
const { module: m, moduleGraph, runtime, cssExportsData } = templateContext;
const { module: m, moduleGraph, runtime, cssData } = templateContext;
const dep = /** @type {CssLocalIdentifierDependency} */ (dependency);
const module = /** @type {CssModule} */ (m);
const convention =
@ -364,7 +364,7 @@ CssLocalIdentifierDependency.Template = class CssLocalIdentifierDependencyTempla
source.replace(dep.range[0], dep.range[1] - 1, identifier);
for (const used of usedNames.concat(names)) {
cssExportsData.exports.set(used, identifier);
cssData.exports.set(used, identifier);
}
}
};

1
types.d.ts vendored
View File

@ -8730,6 +8730,7 @@ declare class Module extends DependenciesBlock {
factoryMeta?: FactoryMeta;
useSourceMap: boolean;
useSimpleSourceMap: boolean;
hot: boolean;
buildMeta?: BuildMeta;
buildInfo?: BuildInfo;
presentationalDependencies?: Dependency[];