| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004 |
- /*
- MIT License http://www.opensource.org/licenses/mit-license.php
- Author Ivan Kopeykin @vankop
- */
- "use strict";
- const { JAVASCRIPT_TYPE } = require("../ModuleSourceTypeConstants");
- const { interpolate } = require("../TemplatedPathPlugin");
- const WebpackError = require("../errors/WebpackError");
- const { cssExportConvention } = require("../util/conventions");
- const createHash = require("../util/createHash");
- const { makePathsRelative } = require("../util/identifier");
- const makeSerializable = require("../util/makeSerializable");
- const memoize = require("../util/memoize");
- const { digestNonNumericOnly } = require("../util/nonNumericOnlyHash");
- const { updateHashFromSource } = require("../util/source");
- const CssIcssImportDependency = require("./CssIcssImportDependency");
- const NullDependency = require("./NullDependency");
- const getCssTokens = memoize(() => require("../css/syntax"));
- /** @import { ReplaceSource } from "webpack-sources" */
- /**
- * @import {
- * CssGeneratorExportsConvention,
- * CssGeneratorLocalIdentName,
- * HashFunction
- * } from "../../declarations/WebpackOptions"
- */
- /** @import CssGenerator from "../css/CssGenerator" */
- /** @import CssModule from "../css/CssModule" */
- /** @import ChunkGraph from "../ChunkGraph" */
- /**
- * @import Dependency, {
- * ExportsSpec,
- * ReferencedExports,
- * UpdateHashContext,
- * DependencyLocation
- * } from "../Dependency"
- */
- /**
- * @import {
- * CssDependencyTemplateContext as DependencyTemplateContext
- * } from "../DependencyTemplate"
- */
- /** @import ModuleGraph from "../ModuleGraph" */
- /** @import RuntimeTemplate from "../RuntimeTemplate" */
- /** @import { Range } from "../css/CssParser" */
- /** @typedef {import("../serialization/ObjectMiddleware").ObjectDeserializerContext<(number | string | Value | Range | boolean | ExportMode | ExportType | DependencyLocation | undefined)[]>} ObjectDeserializerContext */
- /** @typedef {import("../serialization/ObjectMiddleware").ObjectSerializerContext<(number | string | Value | Range | boolean | ExportMode | ExportType | DependencyLocation | undefined)[]>} ObjectSerializerContext */
- /** @import Hash from "../util/Hash" */
- /** @import { RuntimeSpec } from "../util/runtime" */
- /** @import WebpackErrorType from "../errors/WebpackError" */
- /** @typedef {string | [string, string] | [string, string, string]} Value */
- /** @typedef {(name: string) => string | string[]} ExportsConventionFn */
- // 0 - none, 1 - replace, 2 - append, 3 - once, 4 - self-reference
- /** @typedef {0 | 1 | 2 | 3 | 4} ExportMode */
- // 0 - normal, 1 - custom css variable, 2 - grid custom ident, 3 - composes
- /** @typedef {0 | 1 | 2 | 3} ExportType */
- /** @type {Record<"NONE" | "REPLACE" | "APPEND" | "ONCE" | "SELF_REFERENCE", ExportMode>} */
- const EXPORT_MODE = {
- NONE: 0,
- REPLACE: 1,
- APPEND: 2,
- ONCE: 3,
- SELF_REFERENCE: 4
- };
- /** @type {Record<"NORMAL" | "CUSTOM_VARIABLE" | "GRID_CUSTOM_IDENTIFIER" | "COMPOSES", ExportType>} */
- const EXPORT_TYPE = {
- NORMAL: 0,
- CUSTOM_VARIABLE: 1,
- GRID_CUSTOM_IDENTIFIER: 2,
- COMPOSES: 3
- };
- // Hoisted out of `getLocalIdent`'s `prepareId` so the patterns + the replacer
- // aren't recompiled/reallocated per interpolated id.
- const IDENT_LEADING_INVALID_REGEXP = /^([.-]|[^a-z0-9_-])+/i;
- const IDENT_INVALID_CHARS_REGEXP = /[^a-z0-9@_-]+/gi;
- const IDENT_LOCAL_PLACEHOLDER_REGEXP = /\[local\]/g;
- const IDENT_LEADING_PROTECT_REGEXP = /^((-?\d)|--)/;
- /**
- * `interpolate` `prepareId` hook — sanitize an id segment into a valid CSS ident.
- * Pure (captures nothing), so it's shared rather than allocated per call.
- * @param {string | number} id id segment
- * @returns {string | number} sanitized id segment
- */
- const prepareLocalIdentId = (id) => {
- if (typeof id !== "string") return id;
- return (
- id
- .replace(IDENT_LEADING_INVALID_REGEXP, "")
- // We keep the `@` symbol because it can be used in the package name (e.g. `@company/package`), and if we replace it with `_`, a class conflict may occur.
- // For example - `@import "@foo/package/style.module.css"` and `@import "foo/package/style.module.css"` (`foo` is a package, `package` is just a directory) will create a class conflict.
- .replace(IDENT_INVALID_CHARS_REGEXP, "_")
- );
- };
- /**
- * Returns local ident.
- * @param {string} local css local
- * @param {CssModule} module module
- * @param {ChunkGraph} chunkGraph chunk graph
- * @param {RuntimeTemplate} runtimeTemplate runtime template
- * @returns {string} local ident
- */
- const getLocalIdent = (local, module, chunkGraph, runtimeTemplate) => {
- const generator = /** @type {CssGenerator} */ (module.generator);
- const localIdentName =
- /** @type {CssGeneratorLocalIdentName} */
- (generator.options.localIdentName);
- const relativeResourcePath = makePathsRelative(
- /** @type {string} */
- (runtimeTemplate.compilation.compiler.context),
- /** @type {string} */
- (module.getResource()),
- runtimeTemplate.compilation.compiler.root
- );
- const { uniqueName } = runtimeTemplate.outputOptions;
- let localIdentHash = "";
- let localIdentHashFull = "";
- if (generator._localIdentNeedsHash) {
- const hashSalt = generator.options.localIdentHashSalt;
- const hashDigest =
- /** @type {string} */
- (generator.options.localIdentHashDigest);
- const hashDigestLength = generator.options.localIdentHashDigestLength;
- const hashFunction =
- /** @type {HashFunction} */
- (generator.options.localIdentHashFunction);
- const hash = createHash(hashFunction);
- if (hashSalt) {
- hash.update(hashSalt);
- }
- if (uniqueName) {
- hash.update(uniqueName);
- }
- hash.update(relativeResourcePath);
- hash.update(local);
- // Keep the untruncated digest so an inline `[fullhash:<digest>]` re-encodes
- // with full entropy (see the `fullHash`/`fullHashDigest` passed below).
- localIdentHashFull = hash.digest(hashDigest);
- localIdentHash = localIdentHashFull.slice(0, hashDigestLength);
- }
- let contentHash = "";
- if (generator._localIdentNeedsContentHash) {
- const hash = createHash(runtimeTemplate.outputOptions.hashFunction);
- const source = module.originalSource();
- if (source) {
- updateHashFromSource(hash, source);
- }
- if (module.error) {
- hash.update(module.error.toString());
- }
- contentHash = digestNonNumericOnly(
- hash,
- /** @type {string} */ (runtimeTemplate.outputOptions.hashDigest),
- /** @type {number} */ (runtimeTemplate.outputOptions.hashDigestLength)
- );
- }
- let localIdent = interpolate(localIdentName, {
- prepareId: prepareLocalIdentId,
- filename: relativeResourcePath,
- hash: localIdentHash,
- // css-loader semantics: `[hash]` is the local ident hash, not the module
- // hash (use `[modulehash]` for that).
- hashAsFullHash: true,
- // `[hash]`/`[contenthash]` are encoded with the output digest, but the
- // `[fullhash]` local ident hash uses `localIdentHashDigest`, so re-encode it
- // from its own (full) digest.
- hashDigest: runtimeTemplate.outputOptions.hashDigest,
- fullHash: localIdentHashFull,
- fullHashDigest:
- /** @type {string} */
- (generator.options.localIdentHashDigest),
- local,
- uniqueName,
- contentHash,
- chunkGraph,
- module
- });
- // TODO move this into interpolate
- // `replace` on a non-matching global regexp returns the string unchanged, so
- // the `.test` guard is redundant — one scan instead of two.
- localIdent = localIdent.replace(IDENT_LOCAL_PLACEHOLDER_REGEXP, local);
- // Protect the first character from unsupported values
- return localIdent.replace(IDENT_LEADING_PROTECT_REGEXP, "_$1");
- };
- /**
- * Computes the interpolated identifier for `(module, value, exportType)`.
- * Module-level reference so `moduleGraph.cached` can use it as a stable
- * computer key — repeated lookups during a build skip `cssExportConvention`,
- * `getLocalIdent` (with its content / path hashing) and `escapeIdentifier`.
- * Parameters are ordered by rising cardinality because `cached` keys a trie on
- * them — see the call site.
- * @param {ModuleGraph} _moduleGraph module graph (unused, kept for `cached` signature)
- * @param {CssModule} module css module the value resolves in
- * @param {ChunkGraph} chunkGraph chunk graph
- * @param {RuntimeTemplate} runtimeTemplate runtime template
- * @param {ExportType} exportType export type discriminator
- * @param {string} value raw value to interpolate
- * @returns {string} interpolated identifier
- */
- const computeInterpolatedIdentifier = (
- _moduleGraph,
- module,
- chunkGraph,
- runtimeTemplate,
- exportType,
- value
- ) => {
- const generator = /** @type {CssGenerator} */ (module.generator);
- const local = cssExportConvention(
- value,
- /** @type {CssGeneratorExportsConvention} */
- (generator.options.exportsConvention)
- )[0];
- const prefix = exportType === EXPORT_TYPE.CUSTOM_VARIABLE ? "--" : "";
- return (
- prefix +
- getCssTokens().escapeIdentifier(
- getLocalIdent(local, module, chunkGraph, runtimeTemplate),
- runtimeTemplate.compilation.compiler.root
- )
- );
- };
- /**
- * One `:export { name: value }` style entry — a plain object so a module holds
- * a single `Dependency` plus a flat array instead of thousands of `Dependency`
- * instances. `_conventionNames`/`_valueConventionNames` memoize the convention
- * expansion per entry.
- * @typedef {object} CssExportEntry
- * @property {string} name export name
- * @property {Value} value value or [localName, importName, request?]
- * @property {Range=} range source range to replace, when present
- * @property {boolean} interpolate whether the value needs interpolation
- * @property {ExportMode} exportMode export mode
- * @property {ExportType} exportType export type
- * @property {number} locStartLine per-export source start line (1-based; flat numbers instead of a nested `DependencyLocation` — entries are per-export hot allocations)
- * @property {number} locStartColumn per-export source start column
- * @property {number} locEndLine per-export source end line
- * @property {number} locEndColumn per-export source end column
- * @property {string[]=} _conventionNames memoized convention names for `name`
- * @property {string[]=} _valueConventionNames memoized convention names for `value`
- */
- /**
- * Memoized `cssExportConvention(entry.name, convention)`.
- * @param {CssExportEntry} entry entry
- * @param {CssGeneratorExportsConvention} convention convention
- * @returns {string[]} convention names
- */
- const entryConventionNames = (entry, convention) => {
- if (entry._conventionNames) return entry._conventionNames;
- entry._conventionNames = cssExportConvention(entry.name, convention);
- return entry._conventionNames;
- };
- /**
- * Memoized `cssExportConvention(entry.value, convention)`. Caller guarantees
- * `typeof entry.value === "string"`.
- * @param {CssExportEntry} entry entry
- * @param {CssGeneratorExportsConvention} convention convention
- * @returns {string[]} convention names
- */
- const entryValueConventionNames = (entry, convention) => {
- if (entry._valueConventionNames) return entry._valueConventionNames;
- entry._valueConventionNames = cssExportConvention(
- /** @type {string} */ (entry.value),
- convention
- );
- return entry._valueConventionNames;
- };
- class CssIcssExportDependency extends NullDependency {
- /**
- * One consolidated `:export` dependency for a whole CSS module.
- * @param {CssExportEntry[]} entries export entries in source order
- */
- constructor(entries) {
- super();
- /** @type {CssExportEntry[]} */
- this.entries = entries;
- /** @type {undefined | string} */
- this._hashUpdate = undefined;
- /** @type {undefined | Map<string, CssExportEntry[]>} lazily-built `name -> entries` index, reused across every importer's codegen */
- this._entriesByName = undefined;
- }
- /**
- * The `entries` grouped by export name, built once and reused. `entries` is
- * immutable after construction/deserialization, so this stays valid for the
- * dependency's lifetime (the composes/`@value` target of many importers).
- * @returns {Map<string, CssExportEntry[]>} entries grouped by export name
- */
- getEntriesByName() {
- if (this._entriesByName === undefined) {
- /** @type {Map<string, CssExportEntry[]>} */
- const map = new Map();
- for (const e of this.entries) {
- const list = map.get(e.name);
- if (list) list.push(e);
- else map.set(e.name, [e]);
- }
- this._entriesByName = map;
- }
- return this._entriesByName;
- }
- get type() {
- return "css :export";
- }
- /**
- * Returns the exported names
- * @param {ModuleGraph} moduleGraph module graph
- * @returns {ExportsSpec | undefined} export names
- */
- getExports(moduleGraph) {
- const module = /** @type {CssModule} */ (moduleGraph.getParentModule(this));
- const generator = /** @type {CssGenerator} */ (module.generator);
- const convention =
- /** @type {CssGeneratorExportsConvention} */
- (generator.options.exportsConvention);
- /** @type {{ name: string, canMangle: boolean }[]} */
- const exports = [];
- for (const entry of this.entries) {
- if (
- entry.exportMode === EXPORT_MODE.NONE ||
- entry.exportMode === EXPORT_MODE.SELF_REFERENCE
- ) {
- continue;
- }
- for (const name of entryConventionNames(entry, convention)) {
- exports.push({ name, canMangle: true });
- }
- }
- if (exports.length === 0) return undefined;
- return { exports, dependencies: undefined };
- }
- /**
- * Returns list of exports referenced by this dependency
- * @param {ModuleGraph} moduleGraph module graph
- * @param {RuntimeSpec} runtime the runtime for which the module is analysed
- * @returns {ReferencedExports} referenced exports
- */
- getReferencedExports(moduleGraph, runtime) {
- // `composes: foo;` (SELF_REFERENCE) references the composed class
- // (`entry.value`), not the composing class. Every other entry mode
- // keeps the whole exports object referenced (legacy behaviour), so the
- // presence of any such entry subsumes the self-reference names.
- let hasNonSelfReference = false;
- /** @type {ReferencedExports} */
- const referenced = [];
- let generator;
- for (const entry of this.entries) {
- if (
- entry.exportMode === EXPORT_MODE.SELF_REFERENCE &&
- typeof entry.value === "string"
- ) {
- const module =
- /** @type {CssModule} */
- (moduleGraph.getParentModule(this));
- generator = generator || /** @type {CssGenerator} */ (module.generator);
- const names = entryValueConventionNames(
- entry,
- /** @type {CssGeneratorExportsConvention} */
- (generator.options.exportsConvention)
- );
- for (const name of names) {
- referenced.push({ name: [name], canMangle: true });
- }
- } else {
- hasNonSelfReference = true;
- }
- }
- if (hasNonSelfReference) {
- return super.getReferencedExports(moduleGraph, runtime);
- }
- if (referenced.length === 0) {
- return super.getReferencedExports(moduleGraph, runtime);
- }
- return referenced;
- }
- /**
- * Returns warnings.
- * @param {ModuleGraph} moduleGraph module graph
- * @returns {WebpackError[] | null | undefined} warnings
- */
- getWarnings(moduleGraph) {
- /** @type {WebpackErrorType[] | null} */
- let warnings = null;
- let module;
- let exportsInfo;
- let convention;
- for (const entry of this.entries) {
- if (
- entry.exportMode !== EXPORT_MODE.SELF_REFERENCE ||
- typeof entry.value !== "string"
- ) {
- continue;
- }
- if (module === undefined) {
- module =
- /** @type {CssModule | undefined} */
- (moduleGraph.getParentModule(this));
- if (!module) return warnings;
- const generator = /** @type {CssGenerator} */ (module.generator);
- convention =
- /** @type {CssGeneratorExportsConvention} */
- (generator.options.exportsConvention);
- exportsInfo = moduleGraph.getExportsInfo(module);
- }
- const names = entryValueConventionNames(
- entry,
- /** @type {CssGeneratorExportsConvention} */ (convention)
- );
- const ei = /** @type {NonNullable<typeof exportsInfo>} */ (exportsInfo);
- const isProvided = names.some((name) => ei.isExportProvided(name));
- if (!isProvided) {
- const error = new WebpackError(
- `Self-referencing name "${entry.value}" not found`
- );
- error.module = module;
- // Per-entry loc so Compilation attributes the warning precisely
- // (it can't use the consolidated dependency's single loc). Built
- // on demand — entries carry flat numbers, not location objects.
- if (entry.locStartLine > 0) {
- error.loc = {
- start: { line: entry.locStartLine, column: entry.locStartColumn },
- end: { line: entry.locEndLine, column: entry.locEndColumn }
- };
- }
- if (warnings === null) warnings = [];
- warnings.push(error);
- }
- }
- return warnings;
- }
- /**
- * Updates the hash with the data contributed by this instance.
- * @param {Hash} hash hash to be updated
- * @param {UpdateHashContext} context context
- * @returns {void}
- */
- updateHash(hash, { chunkGraph }) {
- if (this._hashUpdate === undefined) {
- const module =
- /** @type {CssModule} */
- (chunkGraph.moduleGraph.getParentModule(this));
- const generator = /** @type {CssGenerator} */ (module.generator);
- const convention =
- /** @type {CssGeneratorExportsConvention} */
- (generator.options.exportsConvention);
- const localIdentName = JSON.stringify(generator.options.localIdentName);
- // Concatenate each entry's contribution with no separator so the byte
- // stream matches the legacy per-export `updateHash` sequence exactly
- // (keeps module hashes stable for import-free modules).
- let update = "";
- for (const entry of this.entries) {
- const names = entryConventionNames(entry, convention);
- update += `exportsConvention|${JSON.stringify(names)}|localIdentName|${localIdentName}|value|${JSON.stringify(entry.value)}|range|${JSON.stringify(entry.range)}|interpolate|${entry.interpolate}|exportMode|${entry.exportMode}|exportType|${entry.exportType}`;
- }
- this._hashUpdate = update;
- }
- hash.update(this._hashUpdate);
- }
- /**
- * Serializes this instance into the provided serializer context.
- * @param {ObjectSerializerContext} context context
- */
- serialize(context) {
- const { write } = context;
- write(this.entries.length);
- for (const entry of this.entries) {
- write(entry.name);
- write(entry.value);
- write(entry.range);
- write(entry.interpolate);
- write(entry.exportMode);
- write(entry.exportType);
- write(entry.locStartLine);
- write(entry.locStartColumn);
- write(entry.locEndLine);
- write(entry.locEndColumn);
- }
- super.serialize(context);
- }
- /**
- * Restores this instance from the provided deserializer context.
- * @param {ObjectDeserializerContext} context context
- */
- deserialize(context) {
- const { read } = context;
- const length = /** @type {number} */ (read());
- /** @type {CssExportEntry[]} */
- const entries = [];
- for (let i = 0; i < length; i++) {
- entries.push({
- name: /** @type {string} */ (read()),
- value: /** @type {Value} */ (read()),
- range: /** @type {Range=} */ (read()),
- interpolate: /** @type {boolean} */ (read()),
- exportMode: /** @type {ExportMode} */ (read()),
- exportType: /** @type {ExportType} */ (read()),
- locStartLine: /** @type {number} */ (read()),
- locStartColumn: /** @type {number} */ (read()),
- locEndLine: /** @type {number} */ (read()),
- locEndColumn: /** @type {number} */ (read())
- });
- }
- this.entries = entries;
- this._hashUpdate = undefined;
- this._entriesByName = undefined;
- super.deserialize(context);
- }
- }
- /**
- * @typedef {object} ResolutionCache
- * @property {Map<CssModule, Map<string, CssExportEntry[]>>} exports module -> (export name -> entries) across all its export deps
- * @property {Map<CssIcssImportDependency, CssModule | null>} targets import dep -> resolved target module
- */
- /**
- * Creates the transient per-`apply` cache shared by `resolve` and
- * `resolveReferences`. It is GC'd when the codegen call returns and is never
- * retained on the dependency.
- * @returns {ResolutionCache} empty resolution cache
- */
- const createResolutionCache = () => ({
- exports: new Map(),
- targets: new Map()
- });
- /**
- * Shared read-only index for modules that own no export dep.
- * @type {Map<string, CssExportEntry[]>}
- */
- const EMPTY_INDEX = new Map();
- /**
- * Per-module `local name -> import deps` index, keyed by module and validated by
- * `buildInfo` identity (a fresh object per build). `apply` runs once per source
- * type (CSS and JS), so this survives both passes — the index is built once per
- * build instead of once per `apply`. Weakly held, so it never keeps a module
- * alive.
- * @type {WeakMap<CssModule, { buildInfo: unknown, index: Map<string, CssIcssImportDependency[]> }>}
- */
- const moduleImportIndexes = new WeakMap();
- /**
- * Get (or lazily resolve) the target module of an `@value`/composes import dep.
- * The same import dep is followed by many entries within one codegen, so the
- * `moduleGraph.getModule` lookup is memoized per dep.
- * @param {ResolutionCache} cache resolution cache
- * @param {ModuleGraph} moduleGraph module graph
- * @param {CssIcssImportDependency} importDep import dependency
- * @returns {CssModule | null} resolved target module, or `null` if none
- */
- const getTargetModule = (cache, moduleGraph, importDep) => {
- let module = cache.targets.get(importDep);
- if (module === undefined) {
- module = /** @type {CssModule | null} */ (moduleGraph.getModule(importDep));
- cache.targets.set(importDep, module);
- }
- return module;
- };
- /**
- * Get (or lazily build) the `name -> entries` index for `module`. There is one
- * consolidated `CssIcssExportDependency` per module, and its own index is cached
- * on the dep — so a module composed/imported by many others builds its index
- * once per build rather than once per importer, and resolution does a single Map
- * lookup instead of re-scanning `module.dependencies` per reference.
- * @param {ResolutionCache} cache resolution cache
- * @param {CssModule} module css module
- * @returns {Map<string, CssExportEntry[]>} entries grouped by export name
- */
- const getExportIndex = (cache, module) => {
- let map = cache.exports.get(module);
- if (map === undefined) {
- map = EMPTY_INDEX;
- for (const dep of module.dependencies) {
- if (dep instanceof CssIcssExportDependency) {
- map = dep.getEntriesByName();
- break;
- }
- }
- cache.exports.set(module, map);
- }
- return map;
- };
- /**
- * Get (or lazily build) the `local name -> import deps` index for `module`, so
- * `findImportDep` is a Map lookup instead of a linear scan of every dependency.
- * Cached per build on the module (validated by `buildInfo` identity) so both the
- * CSS and JS codegen passes reuse one scan of `module.dependencies`.
- * @param {CssModule} module css module
- * @returns {Map<string, CssIcssImportDependency[]>} import deps grouped by local name
- */
- const getImportIndex = (module) => {
- const buildInfo = module.buildInfo;
- const cached = moduleImportIndexes.get(module);
- if (cached !== undefined && cached.buildInfo === buildInfo) {
- return cached.index;
- }
- /** @type {Map<string, CssIcssImportDependency[]>} */
- const index = new Map();
- for (const d of module.dependencies) {
- if (!(d instanceof CssIcssImportDependency)) continue;
- const list = index.get(d.localName);
- if (list) list.push(d);
- else index.set(d.localName, [d]);
- }
- moduleImportIndexes.set(module, { buildInfo, index });
- return index;
- };
- /**
- * Finds the active `CssIcssImportDependency` for a given local name. When a
- * `request` is provided the lookup also requires the import dependency's
- * `request` to match, so references between two `@value foo from "..."`
- * declarations resolve through the import in scope at the reference site.
- * @param {CssModule} module module owning the dependencies to search
- * @param {string} localName local name
- * @param {string=} request user request of the `@value` import to match
- * @returns {CssIcssImportDependency | undefined} matching import dep, if any
- */
- const findImportDep = (module, localName, request) => {
- const list = getImportIndex(module).get(localName);
- if (list === undefined) return undefined;
- // Import deps keep dependency order, so `list[0]` is the first match (the
- // legacy `firstMatch` fallback when no request-specific match exists).
- if (request === undefined) return list[0];
- for (const d of list) if (d.request === request) return d;
- return list[0];
- };
- CssIcssExportDependency.Template = class CssIcssExportDependencyTemplate extends (
- NullDependency.Template
- ) {
- /**
- * Resolves a `[localName, importName, request]` reference to its final
- * value by following `@value` imports into other modules' consolidated
- * export dependencies.
- * @param {string} localName local name
- * @param {string} importName import name
- * @param {DependencyTemplateContext} templateContext the context object
- * @param {string | undefined} request user request of the `@value` import
- * @param {Set<CssExportEntry>=} seen cycle guard
- * @param {ResolutionCache=} cache transient per-codegen index (shared with `resolveReferences` within one `apply`)
- * @returns {string | undefined} found reference
- */
- static resolve(
- localName,
- importName,
- templateContext,
- request,
- seen = new Set(),
- cache = createResolutionCache()
- ) {
- const { moduleGraph } = templateContext;
- const importDep = findImportDep(
- /** @type {CssModule} */ (templateContext.module),
- localName,
- request
- );
- if (!importDep) return undefined;
- const module = getTargetModule(cache, moduleGraph, importDep);
- if (!module) return undefined;
- const matches = getExportIndex(cache, module).get(importName);
- // Skip demoted (`NONE`) cross-kind entries so `@value name from` picks the same winner as the JS export map.
- let exportEntry;
- if (matches) {
- for (const m of matches) {
- if (m.exportMode !== EXPORT_MODE.NONE) {
- exportEntry = m;
- break;
- }
- }
- }
- if (!exportEntry) return undefined;
- if (seen.has(exportEntry)) return undefined;
- seen.add(exportEntry);
- const { value, interpolate } = exportEntry;
- if (Array.isArray(value)) {
- return CssIcssExportDependencyTemplate.resolve(
- value[0],
- value[1],
- { ...templateContext, module },
- value[2],
- seen,
- cache
- );
- }
- if (interpolate) {
- return CssIcssExportDependencyTemplate.getIdentifier(value, exportEntry, {
- ...templateContext,
- module
- });
- }
- return value;
- }
- /**
- * Resolves all references of a `composes` entry, deduplicated.
- * @param {CssExportEntry} entry composes entry
- * @param {DependencyTemplateContext} templateContext template context
- * @param {Set<CssExportEntry>} seen cycle guard
- * @param {ResolutionCache} cache transient per-codegen index, discarded after the module's `apply` (so it isn't retained on the dependency)
- * @returns {string[]} final names
- */
- static resolveReferences(entry, templateContext, seen, cache) {
- /** @type {string[]} */
- const references = [];
- if (seen.has(entry)) return references;
- seen.add(entry);
- if (Array.isArray(entry.value)) {
- const importDep = findImportDep(
- /** @type {CssModule} */ (templateContext.module),
- entry.value[0],
- entry.value[2]
- );
- if (!importDep) return references;
- const module = getTargetModule(
- cache,
- templateContext.moduleGraph,
- importDep
- );
- if (!module) return references;
- // Same overridden context and target name for every matched entry — build once, not per entry.
- const subContext = { ...templateContext, module };
- const name = /** @type {string[]} */ (entry.value)[1];
- const matches = getExportIndex(cache, module).get(name);
- if (matches) {
- for (const d of matches) {
- if (d.exportMode === EXPORT_MODE.NONE) continue;
- if (Array.isArray(d.value)) {
- references.push(
- ...CssIcssExportDependencyTemplate.resolveReferences(
- d,
- subContext,
- seen,
- cache
- )
- );
- } else {
- references.push(
- CssIcssExportDependencyTemplate.getIdentifier(
- d.value,
- d,
- subContext
- )
- );
- }
- }
- }
- } else {
- references.push(
- CssIcssExportDependencyTemplate.getIdentifier(
- /** @type {string} */ (entry.value),
- entry,
- templateContext
- )
- );
- const matches = getExportIndex(
- cache,
- /** @type {CssModule} */ (templateContext.module)
- ).get(/** @type {string} */ (entry.value));
- if (matches) {
- for (const d of matches) {
- if (d.exportMode === EXPORT_MODE.NONE) continue;
- if (d.exportType !== EXPORT_TYPE.COMPOSES) continue;
- if (Array.isArray(d.value)) {
- references.push(
- ...CssIcssExportDependencyTemplate.resolveReferences(
- d,
- templateContext,
- seen,
- cache
- )
- );
- } else {
- references.push(
- CssIcssExportDependencyTemplate.getIdentifier(
- d.value,
- d,
- templateContext
- )
- );
- }
- }
- }
- }
- return references.length > 1 ? [...new Set(references)] : references;
- }
- /**
- * Returns the interpolated identifier for an entry value.
- * @param {string} value value to identifier
- * @param {CssExportEntry} entry the entry
- * @param {DependencyTemplateContext} templateContext the context object
- * @returns {string} identifier
- */
- static getIdentifier(value, entry, templateContext) {
- if (!entry.interpolate) return value;
- const { moduleGraph, module, chunkGraph, runtimeTemplate } =
- templateContext;
- // Key order is by rising cardinality: `moduleGraph.cached` is a trie that
- // allocates a node per level per new prefix, so the per-export `value` goes
- // last — otherwise every export name forks the trie and re-allocates the
- // three elements below it, which are constant for the whole compilation.
- return moduleGraph.cached(
- computeInterpolatedIdentifier,
- /** @type {CssModule} */ (module),
- chunkGraph,
- runtimeTemplate,
- entry.exportType,
- value
- );
- }
- /**
- * Applies the plugin by registering its hooks on the compiler.
- * @param {Dependency} dependency the dependency for which the template should be applied
- * @param {ReplaceSource} source the current replace source which can be modified
- * @param {DependencyTemplateContext} templateContext the context object
- * @returns {void}
- */
- apply(dependency, source, templateContext) {
- const dep = /** @type {CssIcssExportDependency} */ (dependency);
- const { module: m, moduleGraph, runtime, cssData } = templateContext;
- const module = /** @type {CssModule} */ (m);
- const generator = /** @type {CssGenerator} */ (module.generator);
- const isJs = templateContext.type === JAVASCRIPT_TYPE;
- // All entries belong to this one module, so resolve its ExportsInfo once
- // instead of re-resolving `module -> exportsInfo` per export name.
- const exportsInfo = isJs ? moduleGraph.getExportsInfo(module) : undefined;
- // Transient per-module indexes for composes resolution within this
- // module's codegen; GC'd when apply returns (never retained on the dependency).
- const cache = createResolutionCache();
- for (const entry of dep.entries) {
- if (!entry.range && !isJs) continue;
- const isReference = Array.isArray(entry.value);
- /** @type {string} */
- let value;
- if (entry.exportType === EXPORT_TYPE.COMPOSES && isJs) {
- value = CssIcssExportDependencyTemplate.resolveReferences(
- entry,
- templateContext,
- new Set(),
- cache
- ).join(" ");
- } else if (isReference) {
- const resolved = CssIcssExportDependencyTemplate.resolve(
- /** @type {string[]} */ (entry.value)[0],
- /** @type {string[]} */ (entry.value)[1],
- templateContext,
- /** @type {string[]} */ (entry.value)[2],
- new Set(),
- cache
- );
- value = resolved || /** @type {string[]} */ (entry.value)[0];
- } else {
- value = CssIcssExportDependencyTemplate.getIdentifier(
- /** @type {string} */ (entry.value),
- entry,
- templateContext
- );
- }
- if (entry.exportType === EXPORT_TYPE.GRID_CUSTOM_IDENTIFIER) {
- value += `-${entry.name}`;
- }
- if (isJs && entry.exportMode !== EXPORT_MODE.NONE) {
- const names = entryConventionNames(
- entry,
- /** @type {CssGeneratorExportsConvention} */
- (generator.options.exportsConvention)
- );
- /** @type {Set<string>} */
- const allNames = new Set();
- for (const name of names) {
- if (
- /** @type {NonNullable<typeof exportsInfo>} */ (exportsInfo)
- .getExportInfo(name)
- .getUsedName(name, runtime)
- ) {
- allNames.add(name);
- }
- }
- const unescaped = getCssTokens().unescapeIdentifier(
- value,
- templateContext.runtimeTemplate.compilation.compiler.root
- );
- const depLocLine = entry.locStartLine;
- const depLocColumn = entry.locStartColumn;
- for (const used of allNames) {
- if (entry.exportMode === EXPORT_MODE.ONCE) {
- if (cssData.exports.has(used)) continue;
- cssData.exports.set(used, unescaped);
- if (
- depLocLine > 0 &&
- cssData.exportLocs &&
- !cssData.exportLocs.has(used)
- ) {
- cssData.exportLocs.set(used, {
- line: depLocLine,
- column: depLocColumn
- });
- }
- } else {
- const originalValue =
- entry.exportMode === EXPORT_MODE.REPLACE
- ? undefined
- : cssData.exports.get(used);
- cssData.exports.set(
- used,
- `${originalValue ? `${originalValue}${unescaped ? " " : ""}` : ""}${unescaped}`
- );
- if (
- depLocLine > 0 &&
- cssData.exportLocs &&
- !cssData.exportLocs.has(used)
- ) {
- cssData.exportLocs.set(used, {
- line: depLocLine,
- column: depLocColumn
- });
- }
- }
- }
- } else if (
- entry.range &&
- templateContext.type !== JAVASCRIPT_TYPE &&
- entry.exportMode !== EXPORT_MODE.APPEND &&
- entry.exportMode !== EXPORT_MODE.SELF_REFERENCE
- ) {
- source.replace(entry.range[0], entry.range[1] - 1, value);
- }
- }
- }
- };
- /** @type {Record<"NONE" | "REPLACE" | "APPEND" | "ONCE" | "SELF_REFERENCE", ExportMode>} */
- CssIcssExportDependency.EXPORT_MODE = EXPORT_MODE;
- /** @type {Record<"NORMAL" | "CUSTOM_VARIABLE" | "GRID_CUSTOM_IDENTIFIER" | "COMPOSES", ExportType>} */
- CssIcssExportDependency.EXPORT_TYPE = EXPORT_TYPE;
- makeSerializable(
- CssIcssExportDependency,
- "webpack/lib/dependencies/CssIcssExportDependency"
- );
- module.exports = CssIcssExportDependency;
|