| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281 |
- /*
- MIT License http://www.opensource.org/licenses/mit-license.php
- Author Tobias Koppers @sokra
- */
- "use strict";
- const { SyncBailHook, SyncHook, SyncWaterfallHook } = require("tapable");
- const {
- CachedSource,
- ConcatSource,
- PrefixSource,
- RawSource,
- ReplaceSource
- } = require("webpack-sources");
- /** @import Compilation, { ChunkHashContext } from "../Compilation" */
- const HotUpdateChunk = require("../HotUpdateChunk");
- const { CSS_IMPORT_TYPE, CSS_TYPE } = require("../ModuleSourceTypeConstants");
- const {
- CSS_MODULE_TYPE,
- CSS_MODULE_TYPE_AUTO,
- CSS_MODULE_TYPE_GLOBAL,
- CSS_MODULE_TYPE_MODULE
- } = require("../ModuleTypeConstants");
- const NormalModule = require("../NormalModule");
- const RuntimeGlobals = require("../RuntimeGlobals");
- const Template = require("../Template");
- const StaticExportsDependency = require("../dependencies/StaticExportsDependency");
- const { tryRunOrWebpackError } = require("../errors/HookWebpackError");
- const WebpackError = require("../errors/WebpackError");
- const JavascriptModulesPlugin = require("../javascript/JavascriptModulesPlugin");
- const ConcatenatedModule = require("../optimize/ConcatenatedModule");
- const { compareModulesByFullName } = require("../util/comparators");
- const createHash = require("../util/createHash");
- const createHooksRegistry = require("../util/createHooksRegistry");
- const { getUndoPath } = require("../util/identifier");
- const implicitTypeLoaderFallback = require("../util/implicitTypeLoaderFallback");
- const lazyModule = require("../util/lazyModule");
- const memoize = require("../util/memoize");
- const { digestNonNumericOnlyWithFull } = require("../util/nonNumericOnlyHash");
- const preloadModuleType = require("../util/preloadModuleType");
- const {
- PUBLIC_PATH_AUTO,
- walkFullHashPlaceholders
- } = require("../util/publicPathPlaceholder");
- const removeBOM = require("../util/removeBOM");
- const CssModule = require("./CssModule");
- /** @import { Source } from "webpack-sources" */
- /**
- * @import {
- * OutputNormalizedWithDefaults as OutputOptions
- * } from "../config/defaults"
- */
- /** @import Chunk, { ChunkFilenameTemplate } from "../Chunk" */
- /** @import ChunkGraph from "../ChunkGraph" */
- /** @import CodeGenerationResults from "../CodeGenerationResults" */
- /** @import Compiler from "../Compiler" */
- /** @import RuntimeTemplate from "../RuntimeTemplate" */
- /** @import CssGenerator from "./CssGenerator" */
- /** @import CssParser from "./CssParser" */
- /**
- * @import {
- * Inheritance,
- * CssModuleCreateData,
- * CssModuleBuildInfo
- * } from "./CssModule"
- */
- /** @import Module, { RuntimeRequirements } from "../Module" */
- /** @import Hash from "../util/Hash" */
- /**
- * Defines the render context type used by this module.
- * @typedef {object} RenderContext
- * @property {Chunk} chunk the chunk
- * @property {ChunkGraph} chunkGraph the chunk graph
- * @property {CodeGenerationResults} codeGenerationResults results of code generation
- * @property {RuntimeTemplate} runtimeTemplate the runtime template
- * @property {string} uniqueName the unique name
- * @property {string} undoPath undo path to css file
- * @property {string=} hash compilation hash
- * @property {CssModule[]} modules modules
- */
- /**
- * Defines the chunk render context type used by this module.
- * @typedef {object} ChunkRenderContext
- * @property {Chunk=} chunk the chunk
- * @property {ChunkGraph=} chunkGraph the chunk graph
- * @property {CodeGenerationResults=} codeGenerationResults results of code generation
- * @property {RuntimeTemplate} runtimeTemplate the runtime template
- * @property {string} undoPath undo path to css file
- * @property {string=} hash compilation hash
- * @property {WeakMap<Source, ModuleFactoryCacheEntry>} moduleFactoryCache moduleFactoryCache
- * @property {Source} moduleSourceContent content
- */
- /**
- * Defines the module factory cache entry type used by this module.
- * @typedef {object} ModuleFactoryCacheEntry
- * @property {string} undoPath - The undo path to the CSS file
- * @property {string | undefined} hash - The compilation hash
- * @property {Inheritance} inheritance - The inheritance chain
- * @property {CachedSource} source - The cached source
- */
- // Lazy: this plugin is applied on every build (`experiments.css` defaults on),
- // but the parser and generator are only reached once CSS is in the graph — and
- // the parser pulls in the CSS tokenizer.
- const getCssParser = lazyModule(() => require("./CssParser"));
- const getCssGenerator = lazyModule(() => require("./CssGenerator"));
- const CSS_MODULE_TYPES = [
- CSS_MODULE_TYPE,
- CSS_MODULE_TYPE_GLOBAL,
- CSS_MODULE_TYPE_MODULE,
- CSS_MODULE_TYPE_AUTO
- ];
- const getCssImportDependency = memoize(() =>
- require("../dependencies/CssImportDependency")
- );
- const getCssLoadingRuntimeModule = lazyModule(() =>
- require("./CssLoadingRuntimeModule")
- );
- const getCssInjectStyleRuntimeModule = lazyModule(() =>
- require("./CssInjectStyleRuntimeModule")
- );
- /**
- * @typedef {object} PublicPathPlaceholderPlan
- * @property {number[]} autos start offsets of each `PUBLIC_PATH_AUTO` placeholder
- * @property {{ start: number, end: number, length: number }[]} hashes `[start, end)` ranges of each `PUBLIC_PATH_FULL_HASH` placeholder and its requested hash length
- */
- /**
- * Public-path placeholder offsets are invariant for a given module source —
- * they don't depend on `undoPath`/`hash`/`inheritance`/runtime — so scan once
- * and reuse across renders instead of re-materializing and re-scanning the
- * source on every cache miss. Weakly keyed so entries release with the source.
- * @type {WeakMap<Source, PublicPathPlaceholderPlan>}
- */
- const publicPathPlaceholderPlans = new WeakMap();
- /**
- * Memoized inheritance chain per module, revalidated against its source
- * fields — `updateCacheModule` reassigns them on watch rebuilds.
- * @type {WeakMap<CssModule, { cssLayer: CssModule["cssLayer"], supports: CssModule["supports"], media: CssModule["media"], inheritance: CssModule["inheritance"], chain: Inheritance }>}
- */
- const moduleInheritanceCache = new WeakMap();
- /**
- * Gets the memoized inheritance chain of a css module.
- * @param {CssModule} module css module
- * @returns {Inheritance} inheritance chain including the module's own entry
- */
- const getModuleInheritance = (module) => {
- const entry = moduleInheritanceCache.get(module);
- if (
- entry !== undefined &&
- entry.cssLayer === module.cssLayer &&
- entry.supports === module.supports &&
- entry.media === module.media &&
- entry.inheritance === module.inheritance
- ) {
- return entry.chain;
- }
- /** @type {Inheritance} */
- const chain = [[module.cssLayer, module.supports, module.media]];
- if (module.inheritance) chain.push(...module.inheritance);
- moduleInheritanceCache.set(module, {
- cssLayer: module.cssLayer,
- supports: module.supports,
- media: module.media,
- inheritance: module.inheritance,
- chain
- });
- return chain;
- };
- /**
- * Locate the public-path placeholders in a materialized module source.
- * @param {string} content materialized module source
- * @returns {PublicPathPlaceholderPlan} placeholder offsets
- */
- const computePublicPathPlaceholderPlan = (content) => {
- /** @type {number[]} */
- const autos = [];
- const autoLen = PUBLIC_PATH_AUTO.length;
- for (
- let idx = content.indexOf(PUBLIC_PATH_AUTO);
- idx !== -1;
- idx = content.indexOf(PUBLIC_PATH_AUTO, idx + autoLen)
- ) {
- autos.push(idx);
- }
- /** @type {{ start: number, end: number, length: number }[]} */
- const hashes = [];
- walkFullHashPlaceholders(content, (start, end, length) => {
- hashes.push({ start, end, length });
- });
- return { autos, hashes };
- };
- /**
- * Returns ], definitions: import("../../schemas/WebpackOptions.json")["definitions"] }} schema.
- * @param {string} name name
- * @returns {{ oneOf: [{ $ref: string }], definitions: import("../../schemas/WebpackOptions.json")["definitions"] }} schema
- */
- const getSchema = (name) => {
- const { definitions } = require("../../schemas/WebpackOptions.json");
- return {
- definitions,
- oneOf: [{ $ref: `#/definitions/${name}` }]
- };
- };
- const parserValidationOptions = {
- name: "Css Modules Plugin",
- baseDataPath: "parser"
- };
- const generatorValidationOptions = {
- name: "Css Modules Plugin",
- baseDataPath: "generator"
- };
- const PLUGIN_NAME = "CssModulesPlugin";
- const createCompilationHooks = () => ({
- /**
- * @type {SyncWaterfallHook<[Source, Module, ChunkRenderContext]>}
- * @since 5.94.0
- */
- renderModulePackage: new SyncWaterfallHook([
- "source",
- "module",
- "renderContext"
- ]),
- /**
- * @type {SyncHook<[Chunk, Hash, ChunkHashContext]>}
- * @since 5.94.0
- */
- chunkHash: new SyncHook(["chunk", "hash", "context"]),
- /**
- * Called for each CSS source type (CSS_IMPORT_TYPE, CSS_TYPE) with the chunk's modules pre-sorted by full module name; return an ordered `Module[]` to override the default import-order topological sort, or return `undefined` to keep the default.
- * @type {SyncBailHook<[Chunk, Module[], Compilation], Module[] | undefined | void>}
- * @since 5.107.0
- */
- orderModules: new SyncBailHook(["chunk", "modules", "compilation"])
- });
- /**
- * Defines the compilation hooks type used by this module.
- * @typedef {ReturnType<typeof createCompilationHooks>} CompilationHooks
- */
- class CssModulesPlugin {
- constructor() {
- /** @type {WeakMap<Source, ModuleFactoryCacheEntry>} */
- this._moduleFactoryCache = new WeakMap();
- }
- /**
- * Applies the plugin by registering its hooks on the compiler.
- * @param {Compiler} compiler the compiler instance
- * @returns {void}
- */
- apply(compiler) {
- // `"auto"` marks implicit enablement (no user rule for CSS files) — see
- // `applyExperimentsDefaults`. Only then loaders win over the built-in type.
- // TODO webpack 6: css defaults to `true`, drop this implicit-only fallback.
- const implicitlyEnabled = compiler.options.experiments.css === "auto";
- compiler.hooks.compilation.tap(
- PLUGIN_NAME,
- (compilation, { normalModuleFactory }) => {
- if (implicitlyEnabled) {
- implicitTypeLoaderFallback(
- normalModuleFactory,
- PLUGIN_NAME,
- /\.css$/i,
- CSS_MODULE_TYPE_AUTO
- );
- }
- const hooks = CssModulesPlugin.getCompilationHooks(compilation);
- let dependenciesRegistered = false;
- // runs when the first css module is prepared, so a build without
- // css never loads these dependency classes
- const registerDependencies = () => {
- // every css type taps this — the registration is shared
- if (dependenciesRegistered) return;
- dependenciesRegistered = true;
- const CssImportDependency = require("../dependencies/CssImportDependency");
- const CssUrlDependency = require("../dependencies/CssUrlDependency");
- const CssIcssImportDependency = require("../dependencies/CssIcssImportDependency");
- const CssIcssExportDependency = require("../dependencies/CssIcssExportDependency");
- const CssIcssSymbolDependency = require("../dependencies/CssIcssSymbolDependency");
- compilation.dependencyFactories.set(
- CssImportDependency,
- normalModuleFactory
- );
- compilation.dependencyTemplates.set(
- CssImportDependency,
- new CssImportDependency.Template()
- );
- compilation.dependencyFactories.set(
- CssUrlDependency,
- normalModuleFactory
- );
- compilation.dependencyTemplates.set(
- CssUrlDependency,
- new CssUrlDependency.Template()
- );
- compilation.dependencyFactories.set(
- CssIcssImportDependency,
- normalModuleFactory
- );
- compilation.dependencyTemplates.set(
- CssIcssImportDependency,
- new CssIcssImportDependency.Template()
- );
- compilation.dependencyTemplates.set(
- CssIcssExportDependency,
- new CssIcssExportDependency.Template()
- );
- compilation.dependencyTemplates.set(
- CssIcssSymbolDependency,
- new CssIcssSymbolDependency.Template()
- );
- };
- for (const type of CSS_MODULE_TYPES) {
- normalModuleFactory.hooks.prepareModuleType
- .for(type)
- .tap(PLUGIN_NAME, registerDependencies);
- }
- compilation.dependencyTemplates.set(
- StaticExportsDependency,
- new StaticExportsDependency.Template()
- );
- preloadModuleType(
- normalModuleFactory,
- PLUGIN_NAME,
- CSS_MODULE_TYPES.map((type) => [
- type,
- [getCssParser, getCssGenerator]
- ])
- );
- for (const type of CSS_MODULE_TYPES) {
- normalModuleFactory.hooks.createParser
- .for(type)
- .tap(PLUGIN_NAME, (parserOptions) => {
- /** @type {undefined | "global" | "local" | "auto"} */
- let defaultMode;
- switch (type) {
- case CSS_MODULE_TYPE: {
- compiler.validate(
- () => getSchema("CssParserOptions"),
- parserOptions,
- parserValidationOptions,
- (options) =>
- require("../../schemas/plugins/css/CssParserOptions.check")(
- options
- )
- );
- break;
- }
- case CSS_MODULE_TYPE_GLOBAL: {
- defaultMode = "global";
- compiler.validate(
- () => getSchema("CssModuleParserOptions"),
- parserOptions,
- parserValidationOptions,
- (options) =>
- require("../../schemas/plugins/css/CssModuleParserOptions.check")(
- options
- )
- );
- break;
- }
- case CSS_MODULE_TYPE_MODULE: {
- defaultMode = "local";
- compiler.validate(
- () => getSchema("CssAutoOrModuleParserOptions"),
- parserOptions,
- parserValidationOptions,
- (options) =>
- require("../../schemas/plugins/css/CssAutoOrModuleParserOptions.check")(
- options
- )
- );
- break;
- }
- case CSS_MODULE_TYPE_AUTO: {
- defaultMode = "auto";
- compiler.validate(
- () => getSchema("CssAutoOrModuleParserOptions"),
- parserOptions,
- parserValidationOptions,
- (options) =>
- require("../../schemas/plugins/css/CssAutoOrModuleParserOptions.check")(
- options
- )
- );
- break;
- }
- }
- return new (getCssParser.loaded())({
- defaultMode,
- ...parserOptions
- });
- });
- normalModuleFactory.hooks.createGenerator
- .for(type)
- .tap(PLUGIN_NAME, (generatorOptions) => {
- switch (type) {
- case CSS_MODULE_TYPE: {
- compiler.validate(
- () => getSchema("CssGeneratorOptions"),
- generatorOptions,
- generatorValidationOptions,
- (options) =>
- require("../../schemas/plugins/css/CssGeneratorOptions.check")(
- options
- )
- );
- break;
- }
- case CSS_MODULE_TYPE_GLOBAL: {
- compiler.validate(
- () => getSchema("CssModuleGeneratorOptions"),
- generatorOptions,
- generatorValidationOptions,
- (options) =>
- require("../../schemas/plugins/css/CssModuleGeneratorOptions.check")(
- options
- )
- );
- break;
- }
- case CSS_MODULE_TYPE_MODULE: {
- compiler.validate(
- () => getSchema("CssModuleGeneratorOptions"),
- generatorOptions,
- generatorValidationOptions,
- (options) =>
- require("../../schemas/plugins/css/CssModuleGeneratorOptions.check")(
- options
- )
- );
- break;
- }
- case CSS_MODULE_TYPE_AUTO: {
- compiler.validate(
- () => getSchema("CssModuleGeneratorOptions"),
- generatorOptions,
- generatorValidationOptions,
- (options) =>
- require("../../schemas/plugins/css/CssModuleGeneratorOptions.check")(
- options
- )
- );
- break;
- }
- }
- return new (getCssGenerator.loaded())(
- generatorOptions,
- compilation.moduleGraph
- );
- });
- normalModuleFactory.hooks.createModuleClass
- .for(type)
- .tap(PLUGIN_NAME, (createData, resolveData) => {
- const exportType =
- /** @type {CssParser} */
- (createData.parser).options.exportType;
- // When CSS is imported from CSS there is only one dependency
- const dependency =
- resolveData.dependencies.length > 0
- ? resolveData.dependencies[0]
- : undefined;
- if (dependency instanceof getCssImportDependency()) {
- return new CssModule(
- /** @type {CssModuleCreateData} */
- ({
- ...createData,
- cssLayer: dependency.layer,
- supports: dependency.supports,
- media: dependency.media,
- inheritance: dependency.inheritance,
- exportType: dependency.exportType || exportType
- })
- );
- }
- return new CssModule(
- /** @type {CssModuleCreateData} */
- (
- /** @type {unknown} */ ({
- ...createData,
- exportType
- })
- )
- );
- });
- NormalModule.getCompilationHooks(compilation).processResult.tap(
- PLUGIN_NAME,
- (result, module) => {
- if (module.type === type) {
- const [source, ...rest] = result;
- return [removeBOM(source), ...rest];
- }
- return result;
- }
- );
- }
- JavascriptModulesPlugin.getCompilationHooks(
- compilation
- ).renderModuleContent.tap(PLUGIN_NAME, (source, module, ctx) => {
- const injectCssStylesVar =
- module instanceof ConcatenatedModule &&
- module.modules.find(
- (m) =>
- m instanceof CssModule &&
- m.exportType === "style" &&
- !(/** @type {CssGenerator} */ (m.generator)._exportsOnly)
- );
- const injectHMRCode =
- (module instanceof CssModule && module.hot) ||
- (module instanceof ConcatenatedModule &&
- module.rootModule instanceof CssModule &&
- module.rootModule.hot);
- if (injectCssStylesVar) {
- source = new ConcatSource(
- "var __webpack_css_styles__ = [];",
- "\n",
- source
- );
- }
- if (injectHMRCode) {
- const currentModule = /** @type {CssModule} */ (
- module instanceof ConcatenatedModule ? module.rootModule : module
- );
- const exportType = currentModule.exportType || "link";
- // When exportType !== "link", modules behave like JavaScript modules
- if (["link", "style"].includes(exportType)) {
- // For exportType === "link", we can optimize with self-acceptance
- const cssData = /** @type {CssModuleBuildInfo} */ (
- module.buildInfo
- ).cssData;
- if (!cssData) {
- return source;
- }
- const exports = cssData.exports;
- /** @type {Record<string, string>} */
- const exportsObj = {};
- for (const [key, value] of exports) {
- exportsObj[key] = value;
- }
- const stringifiedExports = JSON.stringify(
- JSON.stringify(exportsObj)
- );
- const hmrCode = Template.asString([
- "",
- `var __webpack_css_exports__ = ${stringifiedExports};`,
- "// only invalidate when locals change",
- `if (${ctx.runtimeTemplate.optionalChaining(
- "module.hot.data",
- "__webpack_css_exports__"
- )} && module.hot.data.__webpack_css_exports__ != __webpack_css_exports__) {`,
- Template.indent("module.hot.invalidate();"),
- "} else {",
- Template.indent("module.hot.accept();"),
- "}",
- "module.hot.dispose(function(data) {",
- Template.indent([
- "data.__webpack_css_exports__ = __webpack_css_exports__;"
- ]),
- "});"
- ]);
- source = new ConcatSource(source, "\n", new RawSource(hmrCode));
- }
- }
- if (injectCssStylesVar) {
- /** @type {ConcatSource} */
- (source).add(
- "for (let i = 0; i < __webpack_css_styles__.length; i++) {\n" +
- `${RuntimeGlobals.cssInjectStyle}(__webpack_css_styles__[i][0], __webpack_css_styles__[i][1]);\n` +
- "}"
- );
- }
- return source;
- });
- /** @type {WeakMap<Chunk, CssModule[]>} */
- const orderedCssModulesPerChunk = new WeakMap();
- compilation.hooks.afterCodeGeneration.tap(PLUGIN_NAME, () => {
- const { chunkGraph } = compilation;
- for (const chunk of compilation.chunks) {
- if (CssModulesPlugin.chunkHasCss(chunk, chunkGraph)) {
- orderedCssModulesPerChunk.set(
- chunk,
- this.getOrderedChunkCssModules(chunk, chunkGraph, compilation)
- );
- }
- }
- });
- compilation.hooks.chunkHash.tap(PLUGIN_NAME, (chunk, hash, context) => {
- hooks.chunkHash.call(chunk, hash, context);
- });
- compilation.hooks.contentHash.tap(PLUGIN_NAME, (chunk) => {
- const {
- chunkGraph,
- moduleGraph,
- runtimeTemplate,
- outputOptions: {
- hashSalt,
- hashDigest,
- hashDigestLength,
- hashFunction
- }
- } = compilation;
- const hash = createHash(hashFunction);
- if (hashSalt) hash.update(hashSalt);
- const codeGenerationResults =
- /** @type {CodeGenerationResults} */
- (compilation.codeGenerationResults);
- hooks.chunkHash.call(chunk, hash, {
- chunkGraph,
- codeGenerationResults,
- moduleGraph,
- runtimeTemplate
- });
- const modules = orderedCssModulesPerChunk.get(chunk);
- if (modules) {
- for (const module of modules) {
- hash.update(chunkGraph.getModuleHash(module, chunk.runtime));
- }
- }
- [chunk.contentHash.css, chunk.contentHashFull.css] =
- digestNonNumericOnlyWithFull(hash, hashDigest, hashDigestLength);
- });
- compilation.hooks.renderManifest.tap(PLUGIN_NAME, (result, options) => {
- const { chunkGraph } = compilation;
- const { hash, chunk, codeGenerationResults, runtimeTemplate } =
- options;
- if (chunk instanceof HotUpdateChunk) return result;
- /** @type {CssModule[] | undefined} */
- const modules = orderedCssModulesPerChunk.get(chunk);
- if (modules !== undefined) {
- const { path: filename, info } = compilation.getPathWithInfo(
- CssModulesPlugin.getChunkFilenameTemplate(
- chunk,
- compilation.outputOptions
- ),
- {
- hash,
- runtime: chunk.runtime,
- chunk,
- contentHashType: "css"
- }
- );
- const undoPath = getUndoPath(
- filename,
- compilation.outputOptions.path,
- false
- );
- result.push({
- render: () =>
- this.renderChunk(
- {
- chunk,
- chunkGraph,
- codeGenerationResults,
- uniqueName: compilation.outputOptions.uniqueName,
- undoPath,
- hash,
- modules,
- runtimeTemplate
- },
- hooks
- ),
- filename,
- info,
- identifier: `css${chunk.id}`,
- hash: chunk.contentHash.css
- });
- }
- return result;
- });
- const globalChunkLoading = compilation.outputOptions.chunkLoading;
- /**
- * Checks whether this css modules plugin is enabled for chunk.
- * @param {Chunk} chunk the chunk
- * @returns {boolean} true, when enabled
- */
- const isEnabledForChunk = (chunk) => {
- const options = chunk.getEntryOptions();
- const chunkLoading =
- options && options.chunkLoading !== undefined
- ? options.chunkLoading
- : globalChunkLoading;
- return chunkLoading === "jsonp" || chunkLoading === "import";
- };
- /**
- * The neutral platform's no-DOM branch stores styles on the global.
- * @param {RuntimeRequirements} set runtime requirements
- */
- const addNoDomStyleRegistry = (set) => {
- if (
- compilation.runtimeTemplate.isNeutralPlatform() &&
- !compilation.outputOptions.environment.globalThis
- ) {
- set.add(RuntimeGlobals.global);
- }
- };
- /**
- * Whether a reachable chunk carries css, which is what the loading branch is
- * rendered from — a css module in the graph is not enough.
- * @param {Chunk} chunk the chunk
- * @param {ChunkGraph} chunkGraph the chunk graph
- * @returns {boolean} true, when there is a stylesheet to load
- */
- const hasCssToLoad = (chunk, chunkGraph) => {
- for (const referenced of chunk.getAllReferencedChunks()) {
- if (CssModulesPlugin.chunkHasCss(referenced, chunkGraph)) {
- return true;
- }
- }
- return false;
- };
- /** @type {WeakSet<Chunk>} */
- const onceForChunkSet = new WeakSet();
- /**
- * Handles the hook callback for this code path.
- * @param {Chunk} chunk chunk to check
- * @param {RuntimeRequirements} set runtime requirements
- */
- const handler = (chunk, set) => {
- if (onceForChunkSet.has(chunk)) return;
- onceForChunkSet.add(chunk);
- if (!isEnabledForChunk(chunk)) return;
- compilation.addLazyRuntimeModule(
- chunk,
- getCssLoadingRuntimeModule,
- (CssLoadingRuntimeModule) => new CssLoadingRuntimeModule(set)
- );
- };
- compilation.hooks.runtimeRequirementInTree
- .for(RuntimeGlobals.hasCssModules)
- .tap(PLUGIN_NAME, handler);
- compilation.hooks.runtimeRequirementInTree
- .for(RuntimeGlobals.ensureChunkHandlers)
- .tap(PLUGIN_NAME, (chunk, set, { chunkGraph }) => {
- if (!isEnabledForChunk(chunk)) return;
- if (!hasCssToLoad(chunk, chunkGraph)) return;
- set.add(RuntimeGlobals.hasOwnProperty);
- // A baked url carries where the stylesheet is, so neither the public
- // path nor the name lookup is read for it. Asked exactly as the
- // loading runtime module asks, so the two always agree.
- if (
- compilation.runtimeTemplate.analyzableCssChunkUrls(
- chunk,
- chunkGraph,
- set
- ) === null
- ) {
- set.add(RuntimeGlobals.publicPath);
- set.add(RuntimeGlobals.getChunkCssFilename);
- }
- // Asked here rather than for `hasCssModules`: the loading runtime
- // module renders nothing without loading or hmr, so declaring it
- // there ships the polyfill to a build that never reads it.
- addNoDomStyleRegistry(set);
- // A chunk whose only stylesheet is an `@import` external has no css
- // module, so `hasCssModules` never asks for the loading runtime.
- handler(chunk, set);
- });
- compilation.hooks.runtimeRequirementInTree
- .for(RuntimeGlobals.hmrDownloadUpdateHandlers)
- .tap(PLUGIN_NAME, (chunk, set, { chunkGraph }) => {
- if (!isEnabledForChunk(chunk)) return;
- if (
- !chunkGraph.hasModuleInGraph(
- chunk,
- (m) =>
- m.type === CSS_MODULE_TYPE ||
- m.type === CSS_MODULE_TYPE_GLOBAL ||
- m.type === CSS_MODULE_TYPE_MODULE ||
- m.type === CSS_MODULE_TYPE_AUTO
- )
- ) {
- return;
- }
- set.add(RuntimeGlobals.publicPath);
- set.add(RuntimeGlobals.getChunkCssFilename);
- addNoDomStyleRegistry(set);
- });
- compilation.hooks.runtimeRequirementInTree
- .for(RuntimeGlobals.cssInjectStyle)
- .tap(PLUGIN_NAME, (chunk, set) => {
- // Same as above: namespace stub is enough.
- set.add(RuntimeGlobals.requireScope);
- addNoDomStyleRegistry(set);
- compilation.addLazyRuntimeModule(
- chunk,
- getCssInjectStyleRuntimeModule,
- (CssInjectStyleRuntimeModule) =>
- new CssInjectStyleRuntimeModule(set)
- );
- });
- }
- );
- }
- /**
- * Gets modules in order.
- * @param {Chunk} chunk chunk
- * @param {Iterable<Module> | undefined} modules unordered modules
- * @param {Compilation} compilation compilation
- * @returns {Module[]} ordered modules
- */
- getModulesInOrder(chunk, modules, compilation) {
- if (!modules) return [];
- /** @type {Module[]} */
- 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) => ({
- module,
- index: chunkGroup.getModulePostOrderIndex(module)
- }))
- .filter((item) => item.index !== undefined)
- .sort(
- (a, b) =>
- /** @type {number} */ (b.index) - /** @type {number} */ (a.index)
- )
- .map((item) => item.module);
- return { list: sortedModules, set: new Set(sortedModules) };
- }
- );
- if (modulesByChunkGroup.length === 1) {
- return modulesByChunkGroup[0].list.reverse();
- }
- const boundCompareModulesByFullName = compareModulesByFullName(
- compilation.compiler
- );
- /**
- * Compares module lists.
- * @param {{ list: Module[] }} a a
- * @param {{ list: Module[] }} b b
- * @returns {-1 | 0 | 1} result
- */
- const compareModuleLists = ({ list: a }, { list: b }) => {
- if (a.length === 0) {
- return b.length === 0 ? 0 : 1;
- }
- if (b.length === 0) return -1;
- return boundCompareModulesByFullName(a[a.length - 1], b[b.length - 1]);
- };
- modulesByChunkGroup.sort(compareModuleLists);
- /** @type {Module[]} */
- const finalModules = [];
- for (;;) {
- /** @type {Set<Module>} */
- const failedModules = new Set();
- const list = modulesByChunkGroup[0].list;
- if (list.length === 0) {
- // done, everything empty
- break;
- }
- /** @type {Module} */
- let selectedModule = list[list.length - 1];
- /** @type {undefined | false | Module} */
- let hasFailed;
- 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) {
- const fallbackModule = /** @type {Module} */ (hasFailed);
- const fallbackIssuers = [
- ...compilation.moduleGraph
- .getIncomingConnectionsByOriginModule(fallbackModule)
- .keys()
- ].filter(Boolean);
- const selectedIssuers = [
- ...compilation.moduleGraph
- .getIncomingConnectionsByOriginModule(selectedModule)
- .keys()
- ].filter(Boolean);
- const allIssuers = [
- ...new Set([...fallbackIssuers, ...selectedIssuers])
- ]
- .map((m) =>
- /** @type {Module} */ (m).readableIdentifier(
- compilation.requestShortener
- )
- )
- .sort();
- // There is a not resolve-able conflict with the selectedModule
- compilation.warnings.push(
- new WebpackError(
- `chunk ${
- chunk.name || chunk.id
- }\nConflicting order between ${fallbackModule.readableIdentifier(
- compilation.requestShortener
- )} and ${selectedModule.readableIdentifier(
- compilation.requestShortener
- )}\nCSS modules are imported in:\n - ${allIssuers.join("\n - ")}`
- )
- );
- selectedModule = fallbackModule;
- }
- // 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;
- }
- /**
- * Gets ordered chunk css modules.
- * @param {Chunk} chunk chunk
- * @param {ChunkGraph} chunkGraph chunk graph
- * @param {Compilation} compilation compilation
- * @returns {CssModule[]} ordered css modules
- */
- getOrderedChunkCssModules(chunk, chunkGraph, compilation) {
- /** @type {string | undefined} */
- let charset;
- const hooks = CssModulesPlugin.getCompilationHooks(compilation);
- /**
- * @param {Iterable<Module> | undefined} iter modules pre-sorted by full module name
- * @returns {Module[]} ordered modules
- */
- const orderModules = (iter) => {
- const modules = iter ? [...iter] : [];
- const result = hooks.orderModules.call(chunk, modules, compilation);
- if (result !== undefined) return result;
- return this.getModulesInOrder(chunk, modules, compilation);
- };
- const comparator = compareModulesByFullName(compilation.compiler);
- const importModules = orderModules(
- chunkGraph.getOrderedChunkModulesIterableBySourceType(
- chunk,
- CSS_IMPORT_TYPE,
- comparator
- )
- );
- const cssModules = orderModules(
- chunkGraph.getOrderedChunkModulesIterableBySourceType(
- chunk,
- CSS_TYPE,
- comparator
- )
- );
- for (const module of cssModules) {
- if (
- typeof (
- /** @type {CssModuleBuildInfo} */ (module.buildInfo).charset
- ) !== "undefined"
- ) {
- if (
- typeof charset !== "undefined" &&
- charset !==
- /** @type {CssModuleBuildInfo} */ (module.buildInfo).charset
- ) {
- const err = new WebpackError(
- `Conflicting @charset at-rules detected: the module ${module.readableIdentifier(
- compilation.requestShortener
- )} (in chunk ${chunk.name || chunk.id}) specifies "${
- /** @type {CssModuleBuildInfo} */ (module.buildInfo).charset
- }", but "${charset}" was expected, all modules must use the same character set`
- );
- err.chunk = chunk;
- err.module = module;
- err.hideStack = true;
- compilation.warnings.push(err);
- }
- if (typeof charset === "undefined") {
- charset = /** @type {CssModuleBuildInfo} */ (module.buildInfo)
- .charset;
- }
- }
- }
- return /** @type {CssModule[]} */ ([...importModules, ...cssModules]);
- }
- /**
- * Renders css module source.
- * @param {CssModule} module css module
- * @param {ChunkRenderContext} renderContext options object
- * @param {CompilationHooks} hooks hooks
- * @returns {Source | null} css module source
- */
- static renderModule(module, renderContext, hooks) {
- const { undoPath, hash, moduleFactoryCache, moduleSourceContent } =
- renderContext;
- const cacheEntry = moduleFactoryCache.get(moduleSourceContent);
- const inheritance = getModuleInheritance(module);
- /** @type {CachedSource} */
- let source;
- if (
- cacheEntry &&
- cacheEntry.undoPath === undoPath &&
- cacheEntry.hash === hash &&
- // Memoized per module, so identity implies equal entries.
- cacheEntry.inheritance === inheritance
- ) {
- source = cacheEntry.source;
- } else {
- if (!moduleSourceContent) return null;
- let plan = publicPathPlaceholderPlans.get(moduleSourceContent);
- if (plan === undefined) {
- plan = computePublicPathPlaceholderPlan(
- /** @type {string} */ (moduleSourceContent.source())
- );
- publicPathPlaceholderPlans.set(moduleSourceContent, plan);
- }
- /** @type {Source} */
- let moduleSource = moduleSourceContent;
- // Apply placeholder substitutions only when present; the common
- // (no-placeholder) case skips the ReplaceSource wrapper entirely.
- if (plan.autos.length > 0 || (hash && plan.hashes.length > 0)) {
- const replaceSource = new ReplaceSource(moduleSourceContent);
- const autoLen = PUBLIC_PATH_AUTO.length;
- for (let i = 0; i < plan.autos.length; i++) {
- const start = plan.autos[i];
- replaceSource.replace(start, start + autoLen - 1, undoPath);
- }
- if (hash) {
- for (let i = 0; i < plan.hashes.length; i++) {
- const { start, end, length } = plan.hashes[i];
- // `end` is exclusive; ReplaceSource.replace takes an inclusive end.
- replaceSource.replace(
- start,
- end - 1,
- length === 0 ? hash : hash.slice(0, length)
- );
- }
- }
- moduleSource = replaceSource;
- }
- for (let i = 0; i < inheritance.length; i++) {
- const layer = inheritance[i][0];
- const supports = inheritance[i][1];
- const media = inheritance[i][2];
- if (media) {
- moduleSource = new ConcatSource(
- `@media ${media} {\n`,
- new PrefixSource("\t", moduleSource),
- "}\n"
- );
- }
- if (supports) {
- moduleSource = new ConcatSource(
- `@supports (${supports}) {\n`,
- new PrefixSource("\t", moduleSource),
- "}\n"
- );
- }
- // Layer can be anonymous
- if (layer !== undefined && layer !== null) {
- moduleSource = new ConcatSource(
- `@layer${layer ? ` ${layer}` : ""} {\n`,
- new PrefixSource("\t", moduleSource),
- "}\n"
- );
- }
- }
- if (moduleSource) {
- moduleSource = new ConcatSource(moduleSource, "\n");
- }
- source = new CachedSource(moduleSource);
- moduleFactoryCache.set(moduleSourceContent, {
- inheritance,
- undoPath,
- hash,
- source
- });
- }
- return tryRunOrWebpackError(
- () => hooks.renderModulePackage.call(source, module, renderContext),
- "CssModulesPlugin.getCompilationHooks().renderModulePackage"
- );
- }
- /**
- * Renders generated source.
- * @param {RenderContext} renderContext the render context
- * @param {CompilationHooks} hooks hooks
- * @returns {Source} generated source
- */
- renderChunk(
- {
- undoPath,
- chunk,
- codeGenerationResults,
- modules,
- runtimeTemplate,
- chunkGraph,
- hash
- },
- hooks
- ) {
- const source = new ConcatSource();
- /** @type {string | undefined} */
- let charset;
- for (const module of modules) {
- if (
- typeof (
- /** @type {CssModuleBuildInfo} */ (module.buildInfo).charset
- ) !== "undefined" &&
- typeof charset === "undefined"
- ) {
- charset = /** @type {CssModuleBuildInfo} */ (module.buildInfo).charset;
- }
- try {
- const codeGenResult = codeGenerationResults.get(module, chunk.runtime);
- const moduleSourceContent =
- /** @type {Source} */
- (
- codeGenResult.sources.get(CSS_TYPE) ||
- codeGenResult.sources.get(CSS_IMPORT_TYPE)
- );
- const moduleSource = CssModulesPlugin.renderModule(
- module,
- {
- undoPath,
- hash,
- chunk,
- chunkGraph,
- codeGenerationResults,
- moduleSourceContent,
- moduleFactoryCache: this._moduleFactoryCache,
- runtimeTemplate
- },
- hooks
- );
- if (moduleSource) {
- source.add(moduleSource);
- }
- } catch (err) {
- /** @type {Error} */
- (err).message += `\nduring rendering of css ${module.identifier()}`;
- throw err;
- }
- }
- chunk.rendered = true;
- if (charset) {
- return new ConcatSource(`@charset "${charset}";\n`, source);
- }
- return source;
- }
- /**
- * Gets chunk filename template.
- * @param {Chunk} chunk chunk
- * @param {OutputOptions} outputOptions output options
- * @returns {ChunkFilenameTemplate} used filename template
- */
- static getChunkFilenameTemplate(chunk, outputOptions) {
- if (chunk.cssFilenameTemplate) {
- return chunk.cssFilenameTemplate;
- } else if (chunk.canBeInitial()) {
- return outputOptions.cssFilename;
- }
- return outputOptions.cssChunkFilename;
- }
- /**
- * Returns true, when the chunk has css.
- * @param {Chunk} chunk chunk
- * @param {ChunkGraph} chunkGraph chunk graph
- * @returns {boolean} true, when the chunk has css
- */
- static chunkHasCss(chunk, chunkGraph) {
- return (
- Boolean(
- chunkGraph.getChunkModulesIterableBySourceType(chunk, CSS_TYPE)
- ) ||
- Boolean(
- chunkGraph.getChunkModulesIterableBySourceType(chunk, CSS_IMPORT_TYPE)
- )
- );
- }
- }
- CssModulesPlugin.getCompilationHooks = createHooksRegistry(
- createCompilationHooks
- );
- module.exports = CssModulesPlugin;
|