webpack/lib/css/CssModulesPlugin.js

214 lines
6.1 KiB
JavaScript
Raw Normal View History

2021-11-30 19:55:51 +08:00
/*
MIT License http://www.opensource.org/licenses/mit-license.php
Author Tobias Koppers @sokra
*/
"use strict";
const { ConcatSource } = require("webpack-sources");
const HotUpdateChunk = require("../HotUpdateChunk");
const RuntimeGlobals = require("../RuntimeGlobals");
2021-11-30 20:46:42 +08:00
const CssUrlDependency = require("../dependencies/CssUrlDependency");
const StaticExportsDependency = require("../dependencies/StaticExportsDependency");
2021-11-30 19:55:51 +08:00
const {
compareModulesByPostOrderIndexOrIdentifier
} = require("../util/comparators");
const createSchemaValidation = require("../util/create-schema-validation");
const createHash = require("../util/createHash");
const CssGenerator = require("./CssGenerator");
const CssLoadingRuntimeModule = require("./CssLoadingRuntimeModule");
const CssParser = require("./CssParser");
/** @typedef {import("webpack-sources").Source} Source */
/** @typedef {import("../Chunk")} Chunk */
/** @typedef {import("../Compiler")} Compiler */
/** @typedef {import("../Module")} Module */
const getSchema = name => {
const { definitions } = require("../../schemas/WebpackOptions.json");
return {
definitions,
oneOf: [{ $ref: `#/definitions/${name}` }]
};
};
const validateGeneratorOptions = createSchemaValidation(
require("../../schemas/plugins/css/CssGeneratorOptions.check.js"),
() => getSchema("CssGeneratorOptions"),
{
name: "Css Modules Plugin",
baseDataPath: "parser"
}
);
const validateParserOptions = createSchemaValidation(
require("../../schemas/plugins/css/CssParserOptions.check.js"),
() => getSchema("CssParserOptions"),
{
name: "Css Modules Plugin",
baseDataPath: "parser"
}
);
const escapeCssIdentifierPart = str => {
// cspell:word uffff
return `${str}`.replace(/[^a-zA-Z0-9_\u0081-\uffff-]/g, s => `\\${s}`);
};
const plugin = "CssModulesPlugin";
class CssModulesPlugin {
/**
* Apply the plugin
* @param {Compiler} compiler the compiler instance
* @returns {void}
*/
apply(compiler) {
compiler.hooks.compilation.tap(
plugin,
(compilation, { normalModuleFactory }) => {
2021-11-30 20:46:42 +08:00
compilation.dependencyFactories.set(
CssUrlDependency,
normalModuleFactory
);
compilation.dependencyTemplates.set(
CssUrlDependency,
new CssUrlDependency.Template()
);
compilation.dependencyTemplates.set(
StaticExportsDependency,
new StaticExportsDependency.Template()
);
2021-11-30 19:55:51 +08:00
normalModuleFactory.hooks.createParser
.for("css")
.tap(plugin, parserOptions => {
validateParserOptions(parserOptions);
return new CssParser();
});
normalModuleFactory.hooks.createGenerator
.for("css")
.tap(plugin, generatorOptions => {
validateGeneratorOptions(generatorOptions);
return new CssGenerator();
});
compilation.hooks.contentHash.tap("JavascriptModulesPlugin", chunk => {
const {
chunkGraph,
outputOptions: {
hashSalt,
hashDigest,
hashDigestLength,
hashFunction
}
} = compilation;
if (!CssModulesPlugin.chunkHasCss(chunk, chunkGraph)) return;
const hash = createHash(hashFunction);
if (hashSalt) hash.update(hashSalt);
const modules = this.getOrderedChunkCssModules(chunk, chunkGraph);
for (const module of modules) {
hash.update(chunkGraph.getModuleHash(module, chunk.runtime));
}
const digest = /** @type {string} */ (hash.digest(hashDigest));
chunk.contentHash.css = digest.substr(0, hashDigestLength);
});
compilation.hooks.renderManifest.tap(plugin, (result, options) => {
const { chunkGraph } = compilation;
const { hash, chunk, codeGenerationResults } = options;
if (chunk instanceof HotUpdateChunk) return result;
if (CssModulesPlugin.chunkHasCss(chunk, chunkGraph)) {
result.push({
render: () =>
this.renderChunk({
chunk,
chunkGraph,
codeGenerationResults
}),
filenameTemplate: CssModulesPlugin.getChunkFilenameTemplate(
chunk,
compilation.outputOptions
),
pathOptions: {
hash,
runtime: chunk.runtime,
chunk,
contentHashType: "css"
},
identifier: `css${chunk.id}`,
hash: chunk.contentHash.css
});
}
return result;
});
const enabledChunks = new WeakSet();
const handler = (chunk, set) => {
if (enabledChunks.has(chunk)) {
return;
}
enabledChunks.add(chunk);
set.add(RuntimeGlobals.publicPath);
set.add(RuntimeGlobals.getChunkCssFilename);
set.add(RuntimeGlobals.hasOwnProperty);
set.add(RuntimeGlobals.moduleFactoriesAddOnly);
compilation.addRuntimeModule(chunk, new CssLoadingRuntimeModule(set));
};
compilation.hooks.runtimeRequirementInTree
.for(RuntimeGlobals.ensureChunkHandlers)
.tap(plugin, handler);
compilation.hooks.runtimeRequirementInTree
.for(RuntimeGlobals.hmrDownloadUpdateHandlers)
.tap(plugin, handler);
}
);
}
getOrderedChunkCssModules(chunk, chunkGraph) {
return chunkGraph.getOrderedChunkModulesIterableBySourceType(
chunk,
"css",
// TODO improve order function
compareModulesByPostOrderIndexOrIdentifier(chunkGraph.moduleGraph)
);
}
renderChunk({ chunk, chunkGraph, codeGenerationResults }) {
const modules = this.getOrderedChunkCssModules(chunk, chunkGraph);
const source = new ConcatSource();
for (const module of modules) {
try {
const s = codeGenerationResults.getSource(module, chunk.runtime, "css");
2021-11-30 20:46:42 +08:00
if (s) {
source.add(s);
source.add("\n");
}
2021-11-30 19:55:51 +08:00
} catch (e) {
e.message += `\nduring rendering of css ${module.identifier()}`;
throw e;
}
}
source.add(
`head{--webpack-${escapeCssIdentifierPart(chunk.id)}:${Array.from(
modules,
m => `_${escapeCssIdentifierPart(m.id)}`
).join(" ")};}`
);
return source;
}
static getChunkFilenameTemplate(chunk, outputOptions) {
if (chunk.cssFilenameTemplate) {
return chunk.cssFilenameTemplate;
} else {
return outputOptions.cssFilename;
}
}
static chunkHasCss(chunk, chunkGraph) {
return !!chunkGraph.getChunkModulesIterableBySourceType(chunk, "css");
}
}
module.exports = CssModulesPlugin;