123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462 |
- /*
- 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");
- const SelfModuleFactory = require("../SelfModuleFactory");
- const CssExportDependency = require("../dependencies/CssExportDependency");
- const CssImportDependency = require("../dependencies/CssImportDependency");
- const CssLocalIdentifierDependency = require("../dependencies/CssLocalIdentifierDependency");
- const CssSelfLocalIdentifierDependency = require("../dependencies/CssSelfLocalIdentifierDependency");
- const CssUrlDependency = require("../dependencies/CssUrlDependency");
- const StaticExportsDependency = require("../dependencies/StaticExportsDependency");
- const { compareModulesByIdentifier } = require("../util/comparators");
- const createSchemaValidation = require("../util/create-schema-validation");
- const createHash = require("../util/createHash");
- const memoize = require("../util/memoize");
- const nonNumericOnlyHash = require("../util/nonNumericOnlyHash");
- const CssExportsGenerator = require("./CssExportsGenerator");
- const CssGenerator = require("./CssGenerator");
- const CssParser = require("./CssParser");
- /** @typedef {import("webpack-sources").Source} Source */
- /** @typedef {import("../../declarations/WebpackOptions").CssExperimentOptions} CssExperimentOptions */
- /** @typedef {import("../Chunk")} Chunk */
- /** @typedef {import("../Compiler")} Compiler */
- /** @typedef {import("../Module")} Module */
- const getCssLoadingRuntimeModule = memoize(() =>
- require("./CssLoadingRuntimeModule")
- );
- 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 escapeCss = (str, omitOptionalUnderscore) => {
- const escaped = `${str}`.replace(
- // cspell:word uffff
- /[^a-zA-Z0-9_\u0081-\uffff-]/g,
- s => `\\${s}`
- );
- return !omitOptionalUnderscore && /^(?!--)[0-9_-]/.test(escaped)
- ? `_${escaped}`
- : escaped;
- };
- const plugin = "CssModulesPlugin";
- class CssModulesPlugin {
- /**
- * @param {CssExperimentOptions} options options
- */
- constructor({ exportsOnly = false }) {
- this._exportsOnly = exportsOnly;
- }
- /**
- * Apply the plugin
- * @param {Compiler} compiler the compiler instance
- * @returns {void}
- */
- apply(compiler) {
- compiler.hooks.compilation.tap(
- plugin,
- (compilation, { normalModuleFactory }) => {
- const selfFactory = new SelfModuleFactory(compilation.moduleGraph);
- compilation.dependencyFactories.set(
- CssUrlDependency,
- normalModuleFactory
- );
- compilation.dependencyTemplates.set(
- CssUrlDependency,
- new CssUrlDependency.Template()
- );
- compilation.dependencyTemplates.set(
- CssLocalIdentifierDependency,
- new CssLocalIdentifierDependency.Template()
- );
- compilation.dependencyFactories.set(
- CssSelfLocalIdentifierDependency,
- selfFactory
- );
- compilation.dependencyTemplates.set(
- CssSelfLocalIdentifierDependency,
- new CssSelfLocalIdentifierDependency.Template()
- );
- compilation.dependencyTemplates.set(
- CssExportDependency,
- new CssExportDependency.Template()
- );
- compilation.dependencyFactories.set(
- CssImportDependency,
- normalModuleFactory
- );
- compilation.dependencyTemplates.set(
- CssImportDependency,
- new CssImportDependency.Template()
- );
- compilation.dependencyTemplates.set(
- StaticExportsDependency,
- new StaticExportsDependency.Template()
- );
- normalModuleFactory.hooks.createParser
- .for("css")
- .tap(plugin, parserOptions => {
- validateParserOptions(parserOptions);
- return new CssParser();
- });
- normalModuleFactory.hooks.createParser
- .for("css/global")
- .tap(plugin, parserOptions => {
- validateParserOptions(parserOptions);
- return new CssParser({
- allowPseudoBlocks: false,
- allowModeSwitch: false
- });
- });
- normalModuleFactory.hooks.createParser
- .for("css/module")
- .tap(plugin, parserOptions => {
- validateParserOptions(parserOptions);
- return new CssParser({
- defaultMode: "local"
- });
- });
- normalModuleFactory.hooks.createGenerator
- .for("css")
- .tap(plugin, generatorOptions => {
- validateGeneratorOptions(generatorOptions);
- return this._exportsOnly
- ? new CssExportsGenerator()
- : new CssGenerator();
- });
- normalModuleFactory.hooks.createGenerator
- .for("css/global")
- .tap(plugin, generatorOptions => {
- validateGeneratorOptions(generatorOptions);
- return this._exportsOnly
- ? new CssExportsGenerator()
- : new CssGenerator();
- });
- normalModuleFactory.hooks.createGenerator
- .for("css/module")
- .tap(plugin, generatorOptions => {
- validateGeneratorOptions(generatorOptions);
- return this._exportsOnly
- ? new CssExportsGenerator()
- : new CssGenerator();
- });
- const orderedCssModulesPerChunk = new WeakMap();
- compilation.hooks.afterCodeGeneration.tap("CssModulesPlugin", () => {
- const { chunkGraph } = compilation;
- for (const chunk of compilation.chunks) {
- if (CssModulesPlugin.chunkHasCss(chunk, chunkGraph)) {
- orderedCssModulesPerChunk.set(
- chunk,
- this.getOrderedChunkCssModules(chunk, chunkGraph, compilation)
- );
- }
- }
- });
- compilation.hooks.contentHash.tap("CssModulesPlugin", chunk => {
- const {
- chunkGraph,
- outputOptions: {
- hashSalt,
- hashDigest,
- hashDigestLength,
- hashFunction
- }
- } = compilation;
- const modules = orderedCssModulesPerChunk.get(chunk);
- if (modules === undefined) return;
- const hash = createHash(hashFunction);
- if (hashSalt) hash.update(hashSalt);
- for (const module of modules) {
- hash.update(chunkGraph.getModuleHash(module, chunk.runtime));
- }
- const digest = /** @type {string} */ (hash.digest(hashDigest));
- chunk.contentHash.css = nonNumericOnlyHash(digest, hashDigestLength);
- });
- compilation.hooks.renderManifest.tap(plugin, (result, options) => {
- const { chunkGraph } = compilation;
- const { hash, chunk, codeGenerationResults } = options;
- if (chunk instanceof HotUpdateChunk) return result;
- const modules = orderedCssModulesPerChunk.get(chunk);
- if (modules !== undefined) {
- result.push({
- render: () =>
- this.renderChunk({
- chunk,
- chunkGraph,
- codeGenerationResults,
- uniqueName: compilation.outputOptions.uniqueName,
- modules
- }),
- 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);
- set.add(RuntimeGlobals.makeNamespaceObject);
- const CssLoadingRuntimeModule = getCssLoadingRuntimeModule();
- compilation.addRuntimeModule(chunk, new CssLoadingRuntimeModule(set));
- };
- compilation.hooks.runtimeRequirementInTree
- .for(RuntimeGlobals.hasCssModules)
- .tap(plugin, handler);
- compilation.hooks.runtimeRequirementInTree
- .for(RuntimeGlobals.ensureChunkHandlers)
- .tap(plugin, handler);
- compilation.hooks.runtimeRequirementInTree
- .for(RuntimeGlobals.hmrDownloadUpdateHandlers)
- .tap(plugin, handler);
- }
- );
- }
- getModulesInOrder(chunk, modules, compilation) {
- if (!modules) return [];
- const modulesList = [...modules];
- // Get ordered list of modules per chunk group
- // Lists are in reverse order to allow to use Array.pop()
- const modulesByChunkGroup = Array.from(chunk.groupsIterable, chunkGroup => {
- const sortedModules = modulesList
- .map(module => {
- return {
- module,
- index: chunkGroup.getModulePostOrderIndex(module)
- };
- })
- .filter(item => item.index !== undefined)
- .sort((a, b) => b.index - a.index)
- .map(item => item.module);
- return { list: sortedModules, set: new Set(sortedModules) };
- });
- if (modulesByChunkGroup.length === 1)
- return modulesByChunkGroup[0].list.reverse();
- const compareModuleLists = ({ list: a }, { list: b }) => {
- if (a.length === 0) {
- return b.length === 0 ? 0 : 1;
- } else {
- if (b.length === 0) return -1;
- return compareModulesByIdentifier(a[a.length - 1], b[b.length - 1]);
- }
- };
- modulesByChunkGroup.sort(compareModuleLists);
- const finalModules = [];
- for (;;) {
- const failedModules = new Set();
- const list = modulesByChunkGroup[0].list;
- if (list.length === 0) {
- // done, everything empty
- break;
- }
- let selectedModule = list[list.length - 1];
- let hasFailed = undefined;
- outer: for (;;) {
- for (const { list, set } of modulesByChunkGroup) {
- if (list.length === 0) continue;
- const lastModule = list[list.length - 1];
- if (lastModule === selectedModule) continue;
- if (!set.has(selectedModule)) continue;
- failedModules.add(selectedModule);
- if (failedModules.has(lastModule)) {
- // There is a conflict, try other alternatives
- hasFailed = lastModule;
- continue;
- }
- selectedModule = lastModule;
- hasFailed = false;
- continue outer; // restart
- }
- break;
- }
- if (hasFailed) {
- // There is a not resolve-able conflict with the selectedModule
- if (compilation) {
- // TODO print better warning
- compilation.warnings.push(
- new Error(
- `chunk ${
- chunk.name || chunk.id
- }\nConflicting order between ${hasFailed.readableIdentifier(
- compilation.requestShortener
- )} and ${selectedModule.readableIdentifier(
- compilation.requestShortener
- )}`
- )
- );
- }
- selectedModule = hasFailed;
- }
- // Insert the selected module into the final modules list
- finalModules.push(selectedModule);
- // Remove the selected module from all lists
- for (const { list, set } of modulesByChunkGroup) {
- const lastModule = list[list.length - 1];
- if (lastModule === selectedModule) list.pop();
- else if (hasFailed && set.has(selectedModule)) {
- const idx = list.indexOf(selectedModule);
- if (idx >= 0) list.splice(idx, 1);
- }
- }
- modulesByChunkGroup.sort(compareModuleLists);
- }
- return finalModules;
- }
- getOrderedChunkCssModules(chunk, chunkGraph, compilation) {
- return [
- ...this.getModulesInOrder(
- chunk,
- chunkGraph.getOrderedChunkModulesIterableBySourceType(
- chunk,
- "css-import",
- compareModulesByIdentifier
- ),
- compilation
- ),
- ...this.getModulesInOrder(
- chunk,
- chunkGraph.getOrderedChunkModulesIterableBySourceType(
- chunk,
- "css",
- compareModulesByIdentifier
- ),
- compilation
- )
- ];
- }
- renderChunk({
- uniqueName,
- chunk,
- chunkGraph,
- codeGenerationResults,
- modules
- }) {
- const source = new ConcatSource();
- const metaData = [];
- for (const module of modules) {
- try {
- const codeGenResult = codeGenerationResults.get(module, chunk.runtime);
- const s =
- codeGenResult.sources.get("css") ||
- codeGenResult.sources.get("css-import");
- if (s) {
- source.add(s);
- source.add("\n");
- }
- const exports =
- codeGenResult.data && codeGenResult.data.get("css-exports");
- const moduleId = chunkGraph.getModuleId(module) + "";
- metaData.push(
- `${
- exports
- ? Array.from(exports, ([n, v]) => {
- const shortcutValue = `${
- uniqueName ? uniqueName + "-" : ""
- }${moduleId}-${n}`;
- return v === shortcutValue
- ? `${escapeCss(n)}/`
- : v === "--" + shortcutValue
- ? `${escapeCss(n)}%`
- : `${escapeCss(n)}(${escapeCss(v)})`;
- }).join("")
- : ""
- }${escapeCss(moduleId)}`
- );
- } catch (e) {
- e.message += `\nduring rendering of css ${module.identifier()}`;
- throw e;
- }
- }
- source.add(
- `head{--webpack-${escapeCss(
- (uniqueName ? uniqueName + "-" : "") + chunk.id,
- true
- )}:${metaData.join(",")};}`
- );
- return source;
- }
- static getChunkFilenameTemplate(chunk, outputOptions) {
- if (chunk.cssFilenameTemplate) {
- return chunk.cssFilenameTemplate;
- } else if (chunk.canBeInitial()) {
- return outputOptions.cssFilename;
- } else {
- return outputOptions.cssChunkFilename;
- }
- }
- static chunkHasCss(chunk, chunkGraph) {
- return (
- !!chunkGraph.getChunkModulesIterableBySourceType(chunk, "css") ||
- !!chunkGraph.getChunkModulesIterableBySourceType(chunk, "css-import")
- );
- }
- }
- module.exports = CssModulesPlugin;
|