webpack
Version:
Packs ECMAScript/CommonJs/AMD modules for the browser. Allows you to split your codebase into multiple bundles, which can be loaded on demand. Supports loaders to preprocess files, i.e. json, jsx, es7, css, less, ... and your custom stuff.
1,495 lines (1,379 loc) • 143 kB
JavaScript
/*
MIT License http://www.opensource.org/licenses/mit-license.php
Author Tobias Koppers @sokra
*/
"use strict";
const path = require("path");
const { CSS_MODULE_TYPE_AUTO } = require("../ModuleTypeConstants");
const Parser = require("../Parser");
const ConstDependency = require("../dependencies/ConstDependency");
const CssIcssExportDependency = require("../dependencies/CssIcssExportDependency");
const CssIcssImportDependency = require("../dependencies/CssIcssImportDependency");
const CssIcssSymbolDependency = require("../dependencies/CssIcssSymbolDependency");
const CssImportDependency = require("../dependencies/CssImportDependency");
const CssUrlDependency = require("../dependencies/CssUrlDependency");
const StaticExportsDependency = require("../dependencies/StaticExportsDependency");
const CommentCompilationWarning = require("../errors/CommentCompilationWarning");
const ModuleDependencyWarning = require("../errors/ModuleDependencyWarning");
const UnsupportedFeatureWarning = require("../errors/UnsupportedFeatureWarning");
const WebpackError = require("../errors/WebpackError");
const ResourceHintPlugin = require("../prefetch/ResourceHintPlugin");
const LocConverter = require("../util/LocConverter");
const { parseResource } = require("../util/identifier");
const {
createMagicCommentContext,
parseCommentOptionsInRange
} = require("../util/magicComment");
const topologicalSort = require("../util/topologicalSort");
const {
A,
NodeType,
SourceProcessor,
buildSkipSet,
equalsLowerCase,
isDashedIdentifier,
isWhitespace,
normalizeUrl,
rangeEquals,
rangeEqualsLowerCase,
toLowerCaseIfNeeded,
unescapeIdentifier
} = require("./syntax");
// `SourceProcessor` drives the parse and hands already-built AST nodes to the visitors; positions are read from those nodes' ranges rather than re-scanning the source.
/** @typedef {import("../Module").BuildInfo} BuildInfo */
/** @typedef {import("../Module").BuildMeta} BuildMeta */
/** @typedef {import("./CssModule").CssModuleBuildInfo} CssModuleBuildInfo */
/** @typedef {import("./CssModule").CssModuleBuildMeta} CssModuleBuildMeta */
/** @typedef {import("../Parser").ParserState} ParserState */
/** @typedef {import("../Parser").PreparsedAst} PreparsedAst */
/** @typedef {import("./syntax").AtRule} AtRule */
/** @typedef {import("./syntax").Declaration} Declaration */
/** @typedef {import("./syntax").FunctionNode} FunctionNode */
/** @typedef {import("./syntax").Node} AstNode */
/** @typedef {import("./syntax").QualifiedRule} QualifiedRule */
/** @typedef {import("./syntax").Rule} Rule */
/** @typedef {import("./syntax").SimpleBlock} SimpleBlock */
/** @typedef {import("./syntax").Token} Token */
/** @typedef {import("./syntax").UrlToken} UrlToken */
/** @typedef {import("./syntax").HashToken} HashToken */
/** @typedef {import("./syntax").VisitorMap} VisitorMap */
/** @typedef {import("../../declarations/WebpackOptions").CssAutoOrModuleParserOptions} CssAutoOrModuleParserOptions */
/** @typedef {import("../../declarations/WebpackOptions").CssModuleParserOptions} CssModuleParserOptions */
/** @typedef {import("./CssModule")} CssModule */
/** @typedef {import("./CssModule").Inheritance} Inheritance */
/** @typedef {[number, number]} Range */
/** @typedef {{ line: number, column: number }} Position */
/** @typedef {{ from: string, items: ({ localName: string, importName: string })[] }} ValueAtRuleImport */
/** @typedef {{ localName: string, value: string }} ValueAtRuleValue */
const CC_COLON = ":".charCodeAt(0);
const CC_HYPHEN_MINUS = "-".charCodeAt(0);
const CC_SEMICOLON = ";".charCodeAt(0);
const CC_TAB = "\t".charCodeAt(0);
const CC_SPACE = " ".charCodeAt(0);
const CC_LINE_FEED = "\n".charCodeAt(0);
const CC_CARRIAGE_RETURN = "\r".charCodeAt(0);
const CC_FORM_FEED = "\f".charCodeAt(0);
const CC_LEFT_CURLY = "{".charCodeAt(0);
const CC_LOWER_V = "v".charCodeAt(0);
const CC_UPPER_V = "V".charCodeAt(0);
// A parsed CSS comment. `loc` is computed on demand — only magic-comment error
// warnings read it, so comment-heavy CSS skips the per-comment line/col work.
// Comments are kept in a flat per-parse `comments` side array (not AST nodes); `loc` is derived lazily via `rangeLoc` only where needed (magic-comment errors).
/** @typedef {{ value: string, range: Range }} Comment */
// Newlines (CSS Syntax 3 §3.3) — listed explicitly since there's no preprocessing stage.
// https://www.w3.org/TR/css-syntax-3/#whitespace
// Pure-mode markers: `cssmodules-pure-ignore` opts a single rule out of the purity check, `cssmodules-pure-no-check` (before the first rule) opts the whole file out.
const PURE_IGNORE_RE = /^\s*cssmodules-pure-ignore(?:\s|$)/;
const PURE_NO_CHECK_RE = /^\s*cssmodules-pure-no-check(?:\s|$)/;
const IMAGE_SET_FUNCTION = /^(?:-\w+-)?image-set$/i;
const OPTIONALLY_VENDOR_PREFIXED_KEYFRAMES_AT_RULE = /^@(?:-\w+-)?keyframes$/;
const VENDOR_PREFIX = /^-\w+-/;
const COMPOSES_PROPERTY = /^(?:composes|compose-with)$/i;
// Functional view-transition pseudo-elements whose `(<name> .class…)` argument names are scoped like `view-transition-name`/`-class` values.
const VIEW_TRANSITION_PART_PSEUDO =
/^view-transition-(?:group|image-pair|old|new)$/i;
const IS_MODULES = /\.modules?\.[^.]+$/i;
// `@font-face` src extension → preload `type`; the emission guesses `as="font"`.
const FONT_MIME_TYPES = new Map([
["woff2", "font/woff2"],
["woff", "font/woff"],
["ttf", "font/ttf"],
["otf", "font/otf"],
["eot", "application/vnd.ms-fontobject"]
]);
/**
* @param {string} request font url (may carry a query/hash)
* @returns {string | undefined} preload `type` for a known font extension
*/
const fontMimeType = (request) => {
const match = /\.([a-z0-9]+)(?:[?#]|$)/i.exec(request);
return match ? FONT_MIME_TYPES.get(match[1].toLowerCase()) : undefined;
};
// Skip options for a non-CSS-Modules parse: drop the selector prelude (never
// walked without modules) plus value / function-arg leaves nothing reads (the
// `Ident` visitor no-ops, the `Declaration` visitor returns early, no ICSS).
// `url` / functions / strings / blocks / commas are kept — they carry url()
// rewrites and image-set fences. At-rule preludes are kept (`@media` / `@import`
// are read). CSS-Modules parses skip nothing: selectors are walked and ICSS
// `:export { k: v }` captures each value's byte range from its first / last node.
const SKIP_NON_MODULES = {
types: buildSkipSet([
NodeType.Number,
NodeType.Dimension,
NodeType.Percentage,
NodeType.Ident,
NodeType.Hash,
NodeType.Colon,
NodeType.Delim,
// Nothing reads value/arg whitespace either — consumers use
// `nextNonWhitespace` / type checks that tolerate its absence.
NodeType.Whitespace
]),
selectorPrelude: true
};
// Like SKIP_NON_MODULES but keeps selector preludes and the `Colon` / `Ident`
// tokens inside function-arg lists, so `@custom-selector` `:--name` references
// (including nested ones like `:is(:--name)`) survive a non-modules parse.
const SKIP_NON_MODULES_KEEP_SELECTORS = {
types: buildSkipSet([
NodeType.Number,
NodeType.Dimension,
NodeType.Percentage,
NodeType.Hash,
NodeType.Delim,
NodeType.Whitespace
]),
selectorPrelude: false
};
const CSS_COMMENT = /\/\*((?!\*\/)[\s\S]*?)\*\//g;
// `@value` recognizers (postcss-modules-values shape): the import form `<names> from <source>`, and the `<importName> as <localName>` alias inside it.
const VALUE_IMPORT_FORM = /from(\/\*|\s)(?:[\s\S]+)$/i;
const VALUE_AS_ALIAS = /\s+as\s+/;
// `@value name value`: end of the name run (first non-space followed by space).
const VALUE_NAME_BOUNDARY = /\S\s/;
const ONLY_WHITESPACE = /^\s+$/;
// Relative request prefix (`./` or `../`) — `isSelfReferenceRequest` per `from`.
const RELATIVE_REQUEST = /^\.{1,2}\//;
/**
* Returns matches.
* @param {RegExp} regexp a regexp
* @param {string} str a string
* @returns {RegExpExecArray[]} matches
*/
const matchAll = (regexp, str) => {
/** @type {RegExpExecArray[]} */
const result = [];
/** @type {null | RegExpExecArray} */
let match;
while ((match = regexp.exec(str)) !== null) {
result.push(match);
}
return result;
};
/**
* Range-keyed index over a known-properties table: ASCII-case-folded 31-hash
* of the name bytes → canonical key(s). Lets the Declaration visitor answer
* "is this a known property" (and get the canonical lowercase name) without
* slicing the property name out of the source per declaration.
* @type {WeakMap<Map<string, Record<string, number>>, Map<number, string | string[]>>}
*/
const KNOWN_PROPERTY_INDEX_CACHE = new WeakMap();
/**
* Gets (or builds) the hash index for a known-properties table.
* @param {Map<string, Record<string, number>>} knownProperties known properties table
* @returns {Map<number, string | string[]>} hash → canonical name(s)
*/
const getKnownPropertyIndex = (knownProperties) => {
let index = KNOWN_PROPERTY_INDEX_CACHE.get(knownProperties);
if (index === undefined) {
index = new Map();
for (const name of knownProperties.keys()) {
let h = name.length;
for (let i = 0; i < name.length; i++) {
h = ((h << 5) - h + name.charCodeAt(i)) | 0;
}
const hit = index.get(h);
if (hit === undefined) index.set(h, name);
else if (typeof hit === "string") index.set(h, [hit, name]);
else hit.push(name);
}
KNOWN_PROPERTY_INDEX_CACHE.set(knownProperties, index);
}
return index;
};
/**
* Canonical known-property name for a source range (ASCII case-insensitive), without slicing.
* @param {Map<number, string | string[]>} index hash index from `getKnownPropertyIndex`
* @param {string} input source
* @param {number} start name start
* @param {number} end name end (exclusive)
* @returns {string | undefined} canonical lowercase name, or undefined when unknown
*/
const knownPropertyForRange = (index, input, start, end) => {
let h = end - start;
for (let i = start; i < end; i++) {
let c = input.charCodeAt(i);
if (c >= 65 && c <= 90) c |= 0x20;
h = ((h << 5) - h + c) | 0;
}
const hit = index.get(h);
if (hit === undefined) return undefined;
if (typeof hit === "string") {
return rangeEqualsLowerCase(input, start, end, hit) ? hit : undefined;
}
for (let i = 0; i < hit.length; i++) {
if (rangeEqualsLowerCase(input, start, end, hit[i])) return hit[i];
}
return undefined;
};
/** @type {Record<string, number>} */
const PREDEFINED_COUNTER_STYLES = {
decimal: 1,
"decimal-leading-zero": 1,
"arabic-indic": 1,
armenian: 1,
"upper-armenian": 1,
"lower-armenian": 1,
bengali: 1,
cambodian: 1,
khmer: 1,
"cjk-decimal": 1,
devanagari: 1,
georgian: 1,
gujarati: 1,
/* cspell:disable-next-line */
gurmukhi: 1,
hebrew: 1,
kannada: 1,
lao: 1,
malayalam: 1,
mongolian: 1,
myanmar: 1,
oriya: 1,
persian: 1,
"lower-roman": 1,
"upper-roman": 1,
tamil: 1,
telugu: 1,
thai: 1,
tibetan: 1,
"lower-alpha": 1,
"lower-latin": 1,
"upper-alpha": 1,
"upper-latin": 1,
"lower-greek": 1,
hiragana: 1,
/* cspell:disable-next-line */
"hiragana-iroha": 1,
katakana: 1,
/* cspell:disable-next-line */
"katakana-iroha": 1,
disc: 1,
circle: 1,
square: 1,
"disclosure-open": 1,
"disclosure-closed": 1,
"cjk-earthly-branch": 1,
"cjk-heavenly-stem": 1,
"japanese-informal": 1,
"japanese-formal": 1,
"korean-hangul-formal": 1,
/* cspell:disable-next-line */
"korean-hanja-informal": 1,
/* cspell:disable-next-line */
"korean-hanja-formal": 1,
"simp-chinese-informal": 1,
"simp-chinese-formal": 1,
"trad-chinese-informal": 1,
"trad-chinese-formal": 1,
"cjk-ideographic": 1,
"ethiopic-numeric": 1
};
/** @type {Record<string, number>} */
const GLOBAL_VALUES = {
// Global values
initial: Infinity,
inherit: Infinity,
unset: Infinity,
revert: Infinity,
"revert-layer": Infinity
};
/** @type {Record<string, number>} */
const GRID_AREA_OR_COLUMN_OR_ROW = {
auto: Infinity,
span: Infinity,
...GLOBAL_VALUES
};
/** @type {Record<string, number>} */
const GRID_AUTO_COLUMNS_OR_ROW = {
"min-content": Infinity,
"max-content": Infinity,
auto: Infinity,
...GLOBAL_VALUES
};
/** @type {Record<string, number>} */
const GRID_AUTO_FLOW = {
row: 1,
column: 1,
dense: 1,
...GLOBAL_VALUES
};
/** @type {Record<string, number>} */
const GRID_TEMPLATE_AREAS = {
// Special
none: 1,
...GLOBAL_VALUES
};
/** @type {Record<string, number>} */
const GRID_TEMPLATE_COLUMNS_OR_ROWS = {
none: 1,
subgrid: 1,
masonry: 1,
"max-content": Infinity,
"min-content": Infinity,
auto: Infinity,
...GLOBAL_VALUES
};
/** @type {Record<string, number>} */
const GRID_TEMPLATE = {
...GRID_TEMPLATE_AREAS,
...GRID_TEMPLATE_COLUMNS_OR_ROWS
};
/** @type {Record<string, number>} */
const GRID = {
"auto-flow": 1,
dense: 1,
...GRID_AUTO_COLUMNS_OR_ROW,
...GRID_AUTO_FLOW,
...GRID_TEMPLATE_AREAS,
...GRID_TEMPLATE_COLUMNS_OR_ROWS
};
/**
* Gets known properties.
* @param {{ animation?: boolean, container?: boolean, customIdents?: boolean, grid?: boolean }=} options options
* @returns {Map<string, Record<string, number>>} list of known properties
*/
const buildKnownProperties = (options = {}) => {
/** @type {Map<string, Record<string, number>>} */
const knownProperties = new Map();
if (options.animation) {
knownProperties.set("animation", {
// animation-direction
normal: 1,
reverse: 1,
alternate: 1,
"alternate-reverse": 1,
// animation-fill-mode
forwards: 1,
backwards: 1,
both: 1,
// animation-iteration-count
infinite: 1,
// animation-play-state
paused: 1,
running: 1,
// animation-timing-function
ease: 1,
"ease-in": 1,
"ease-out": 1,
"ease-in-out": 1,
linear: 1,
"step-end": 1,
"step-start": 1,
// Special
none: Infinity, // No matter how many times you write none, it will never be an animation name
...GLOBAL_VALUES
});
knownProperties.set("animation-name", {
// Special
none: Infinity, // No matter how many times you write none, it will never be an animation name
...GLOBAL_VALUES
});
}
if (options.container) {
knownProperties.set("container", {
// container-type
normal: 1,
size: 1,
"inline-size": 1,
"scroll-state": 1,
// Special
none: Infinity,
...GLOBAL_VALUES
});
knownProperties.set("container-name", {
// Special
none: Infinity,
...GLOBAL_VALUES
});
}
if (options.customIdents) {
knownProperties.set("list-style", {
// list-style-position
inside: 1,
outside: 1,
// list-style-type
...PREDEFINED_COUNTER_STYLES,
// Special
none: Infinity,
...GLOBAL_VALUES
});
knownProperties.set("list-style-type", {
// list-style-type
...PREDEFINED_COUNTER_STYLES,
// Special
none: Infinity,
...GLOBAL_VALUES
});
knownProperties.set("system", {
cyclic: 1,
numeric: 1,
alphabetic: 1,
symbolic: 1,
additive: 1,
fixed: 1,
extends: 1,
...PREDEFINED_COUNTER_STYLES
});
knownProperties.set("fallback", {
...PREDEFINED_COUNTER_STYLES
});
knownProperties.set("speak-as", {
auto: 1,
bullets: 1,
numbers: 1,
words: 1,
"spell-out": 1,
...PREDEFINED_COUNTER_STYLES
});
knownProperties.set("view-transition-name", {
none: Infinity,
auto: Infinity,
"match-element": Infinity,
...GLOBAL_VALUES
});
knownProperties.set("view-transition-group", {
normal: Infinity,
contain: Infinity,
nearest: Infinity,
...GLOBAL_VALUES
});
knownProperties.set("view-transition-class", {
none: Infinity,
...GLOBAL_VALUES
});
}
if (options.grid) {
knownProperties.set("grid", GRID);
knownProperties.set("grid-area", GRID_AREA_OR_COLUMN_OR_ROW);
knownProperties.set("grid-column", GRID_AREA_OR_COLUMN_OR_ROW);
knownProperties.set("grid-column-end", GRID_AREA_OR_COLUMN_OR_ROW);
knownProperties.set("grid-column-start", GRID_AREA_OR_COLUMN_OR_ROW);
knownProperties.set("grid-row", GRID_AREA_OR_COLUMN_OR_ROW);
knownProperties.set("grid-row-end", GRID_AREA_OR_COLUMN_OR_ROW);
knownProperties.set("grid-row-start", GRID_AREA_OR_COLUMN_OR_ROW);
knownProperties.set("grid-template", GRID_TEMPLATE);
knownProperties.set("grid-template-areas", GRID_TEMPLATE_AREAS);
knownProperties.set("grid-template-columns", GRID_TEMPLATE_COLUMNS_OR_ROWS);
knownProperties.set("grid-template-rows", GRID_TEMPLATE_COLUMNS_OR_ROWS);
}
return knownProperties;
};
/** @type {Map<number, Map<string, Record<string, number>>>} */
const KNOWN_PROPERTIES_CACHE = new Map();
/**
* Memoized {@link buildKnownProperties}: the table depends only on the four
* boolean options (≤16 combinations) and is read-only, while the same parser is
* reused across modules — so build each combination once and share it instead
* of rebuilding the Map (and its value objects) per parsed module.
* @param {{ animation?: boolean, container?: boolean, customIdents?: boolean, grid?: boolean }=} options options
* @returns {Map<string, Record<string, number>>} known properties table
*/
const getKnownProperties = (options = {}) => {
const key =
(options.animation ? 1 : 0) |
(options.container ? 2 : 0) |
(options.customIdents ? 4 : 0) |
(options.grid ? 8 : 0);
let table = KNOWN_PROPERTIES_CACHE.get(key);
if (table === undefined) {
table = buildKnownProperties(options);
KNOWN_PROPERTIES_CACHE.set(key, table);
}
return table;
};
// Byte-level source-cursor scans for computing replacement / strip ranges on raw source after parsing.
/**
* Skip trailing whitespace + at most one newline (CRLF-aware).
* @param {string} input source
* @param {number} pos position
* @returns {number} position past whitespace + one newline
*/
const skipWhiteLine = (input, pos) => {
for (;;) {
const cc = input.charCodeAt(pos);
if (cc === CC_SPACE || cc === CC_TAB) {
pos++;
continue;
}
if (
cc === CC_LINE_FEED ||
cc === CC_CARRIAGE_RETURN ||
cc === CC_FORM_FEED
) {
pos++;
}
// Treat CRLF as one newline: a CR followed by LF advances past the LF.
if (cc === CC_CARRIAGE_RETURN && input.charCodeAt(pos) === CC_LINE_FEED) {
pos++;
}
break;
}
return pos;
};
/**
* Whether the ident byte-range is a `@container` prelude keyword (`none`/`and`/`or`/`not`, lowercase only) — byte comparison avoids slicing a transient string per prelude ident.
* @param {string} input source
* @param {number} start start offset
* @param {number} end end offset
* @returns {boolean} true for a container keyword
*/
const isContainerKeyword = (input, start, end) => {
switch (end - start) {
case 2:
return input.startsWith("or", start);
case 3:
return input.startsWith("and", start) || input.startsWith("not", start);
case 4:
return input.startsWith("none", start);
default:
return false;
}
};
/**
* @param {string} input source
* @param {number} pos position
* @returns {number} position of the next `{`, or EOF if none
*/
const findLeftCurly = (input, pos) => {
while (pos < input.length) {
if (input.charCodeAt(pos) === CC_LEFT_CURLY) return pos;
pos++;
}
return pos;
};
/**
* Defines the css parser own options type used by this module.
* @typedef {object} CssParserOwnOptions
* @property {("pure" | "global" | "local" | "auto")=} defaultMode default mode
*/
/** @typedef {CssAutoOrModuleParserOptions & CssParserOwnOptions} CssParserOptions */
/**
* Pure-mode at-rules whose prelude is selector-checked, so their body is opaque to the enclosing rule's declaration accounting.
* @param {string} name at-rule name including the leading `@`, lower-cased
* @returns {boolean} true for `@keyframes` / `@counter-style` / `@container` / `@scope`
*/
const isPureBodyAtRule = (name) =>
OPTIONALLY_VENDOR_PREFIXED_KEYFRAMES_AT_RULE.test(name) ||
name === "@counter-style" ||
name === "@container" ||
name === "@scope";
/**
* Scan a rule body once: does it hold a direct declaration counted against the enclosing rule (a declaration, or one in a transparent conditional-group at-rule like `@media`/`@supports`/…) and does it hold a nested block (qualified rule or any block-bearing at-rule)?
* @param {Declaration[] | null} declarations rule-body declarations
* @param {Rule[] | null} childRules rule-body child rules
* @returns {{ hasDirectDecl: boolean, hasNestedBlock: boolean }} scan result
*/
const scanRuleBody = (declarations, childRules) => {
let hasDirectDecl = Boolean(declarations && declarations.length > 0);
let hasNestedBlock = false;
if (childRules) {
for (const child of childRules) {
const t = A.type(child);
if (t === NodeType.QualifiedRule) {
hasNestedBlock = true;
} else if (t === NodeType.AtRule) {
const atDecls = A.declarations(child);
const atChildRules = A.childRules(child);
if (!atDecls && !atChildRules) continue;
hasNestedBlock = true;
if (
!hasDirectDecl &&
!isPureBodyAtRule(`@${toLowerCaseIfNeeded(A.name(child))}`) &&
scanRuleBody(atDecls, atChildRules).hasDirectDecl
) {
hasDirectDecl = true;
}
}
}
}
return { hasDirectDecl, hasNestedBlock };
};
/**
* Parses value at rule params.
* @param {string} str value at-rule params
* @returns {ValueAtRuleImport | ValueAtRuleValue} parsed result
*/
const parseValueAtRuleParams = (str) => {
if (VALUE_IMPORT_FORM.test(str)) {
str = str.replace(CSS_COMMENT, " ").trim().replace(/;$/, "");
const fromIdx = str.lastIndexOf("from");
const path = str
.slice(fromIdx + 5)
.trim()
.replace(/['"]/g, "");
let content = str.slice(0, fromIdx).trim();
if (content.startsWith("(") && content.endsWith(")")) {
content = content.slice(1, -1);
}
return {
from: path,
items: content.split(",").map((item) => {
item = item.trim();
if (item.includes(":")) {
const [local, remote] = item.split(":");
return { localName: local.trim(), importName: remote.trim() };
}
const asParts = item.split(VALUE_AS_ALIAS);
if (asParts.length === 2) {
return {
localName: asParts[1].trim(),
importName: asParts[0].trim()
};
}
return { localName: item, importName: item };
})
};
}
/** @type {string} */
let localName;
/** @type {string} */
let value;
const idx = str.indexOf(":");
if (idx !== -1) {
localName = str.slice(0, idx).replace(CSS_COMMENT, "").trim();
value = str.slice(idx + 1);
} else {
const mask = str.replace(CSS_COMMENT, (m) => " ".repeat(m.length));
const idx = mask.search(VALUE_NAME_BOUNDARY) + 1;
localName = str.slice(0, idx).replace(CSS_COMMENT, "").trim();
value = str.slice(idx + (str[idx] === " " ? 1 : 0));
}
if (
value.length > 0 &&
!ONLY_WHITESPACE.test(value.replace(CSS_COMMENT, ""))
) {
value = value.trim();
}
return { localName, value };
};
/**
* Index of the next non-whitespace child at or after `from`, or `nodes.length`.
* @param {AstNode[]} nodes node list
* @param {number} from start index (inclusive)
* @returns {number} index of the next non-whitespace node
*/
const nextNonWhitespace = (nodes, from) => {
let i = from;
while (i < nodes.length && A.type(nodes[i]) === NodeType.Whitespace) i++;
return i;
};
/** @typedef {{ urlNode: (AstNode | undefined), layerNode: (AstNode | undefined), supportsNode: (FunctionNode | undefined) }} ImportPrelude */
/**
* Scan an `@import` prelude in spec order — URL → `layer` / `layer(…)`? → `supports(…)`? — stopping at the first media-query token (the caller slices the media query out separately).
* @param {AstNode[]} prelude the at-rule prelude nodes
* @returns {ImportPrelude} the recognized prefix parts (any may be undefined)
*/
const parseImportPrelude = (prelude) => {
/** @type {AstNode | undefined} */
let urlNode;
/** @type {AstNode | undefined} */
let layerNode;
/** @type {FunctionNode | undefined} */
let supportsNode;
for (const cv of prelude) {
const t = A.type(cv);
if (t === NodeType.Whitespace) continue;
if (!urlNode) {
if (t === NodeType.Url || t === NodeType.String) {
urlNode = cv;
continue;
}
if (
t === NodeType.Function &&
equalsLowerCase(A.unescapedName(cv), "url")
) {
urlNode = cv;
continue;
}
if (t === NodeType.Ident) {
// CSS Modules: bare ident is a `@value` reference.
urlNode = cv;
continue;
}
break;
}
if (!layerNode && !supportsNode) {
if (t === NodeType.Ident) {
if (equalsLowerCase(A.unescaped(cv), "layer")) {
layerNode = cv;
continue;
}
} else if (
t === NodeType.Function &&
equalsLowerCase(A.unescapedName(cv), "layer")
) {
layerNode = cv;
continue;
}
}
if (
!supportsNode &&
t === NodeType.Function &&
equalsLowerCase(A.unescapedName(cv), "supports")
) {
supportsNode = /** @type {FunctionNode} */ (cv);
continue;
}
// First media-query token — stop scanning for the prefix.
break;
}
return { urlNode, layerNode, supportsNode };
};
/**
* Recognize the request of an ICSS `:import("path")` prelude — the `import(…)` function's args, or the spaced `:import (…)` simple block. Pure — the caller emits the "expected string" warning from `errorPos`.
* @param {AstNode} second the `import(…)` function / first prelude node after the `:`
* @param {QualifiedRule} rule the `:import` rule
* @param {string} source full CSS source, for the path slice
* @returns {{ request: string } | { errorPos: number }} the unquoted request, or the position for the parse warning
*/
const parseIcssImportRequest = (second, rule, source) => {
/** @type {AstNode[] | undefined} */
let args;
if (A.type(second) === NodeType.Function) {
args = A.children(second);
} else {
for (const p of A.prelude(rule)) {
if (A.type(p) === NodeType.SimpleBlock && A.blockToken(p) === "(") {
args = A.children(p);
break;
}
}
}
// The first non-whitespace value inside `(...)` must be a string.
const innerStrToken =
args && args.find((v) => A.type(v) !== NodeType.Whitespace);
if (!innerStrToken || A.type(innerStrToken) !== NodeType.String) {
const errorPos =
A.type(second) === NodeType.Function
? A.nameEnd(second) + 1
: A.end(second);
return { errorPos };
}
return {
request: source.slice(A.start(innerStrToken) + 1, A.end(innerStrToken) - 1)
};
};
class CssParser extends Parser {
/**
* Creates an instance of CssParser.
* @param {CssParserOptions=} options options
*/
constructor(options = {}) {
super();
this.defaultMode =
typeof options.defaultMode !== "undefined" ? options.defaultMode : "pure";
this.options = {
as: "stylesheet",
url: true,
import: true,
namedExports: true,
animation: true,
container: true,
customIdents: true,
customMedia: true,
customSelectors: true,
dashedIdents: true,
function: true,
grid: true,
...options
};
this.magicCommentContext = createMagicCommentContext();
}
/**
* Processes the provided state.
* @param {ParserState} state parser state
* @param {string} message warning message
* @param {LocConverter} locConverter location converter
* @param {number} start start offset
* @param {number} end end offset
*/
_emitWarning(state, message, locConverter, start, end) {
const { line: sl, column: sc } = locConverter.get(start);
const { line: el, column: ec } = locConverter.get(end);
state.current.addWarning(
new ModuleDependencyWarning(state.module, new WebpackError(message), {
start: { line: sl, column: sc },
end: { line: el, column: ec }
})
);
}
/**
* Emits a build error for the provided range.
* @param {ParserState} state parser state
* @param {string} message error message
* @param {LocConverter} locConverter location converter
* @param {number} start start offset
* @param {number} end end offset
*/
_emitError(state, message, locConverter, start, end) {
const { line: sl, column: sc } = locConverter.get(start);
const { line: el, column: ec } = locConverter.get(end);
const err = new WebpackError(message);
err.module = state.module;
err.loc = {
start: { line: sl, column: sc },
end: { line: el, column: ec }
};
state.module.addError(err);
}
/**
* Parses the provided source and updates the parser state.
* @param {string | Buffer | PreparsedAst} source the source to parse
* @param {ParserState} state the parser state
* @returns {ParserState} the parser state
*/
parse(source, state) {
if (Buffer.isBuffer(source)) {
source = source.toString("utf8");
} else if (typeof source === "object") {
throw new Error("webpackAst is unexpected for the CssParser");
}
if (source[0] === "\uFEFF") {
source = source.slice(1);
}
// Per-parse comment side-array — kept local (like HtmlParser) so nothing is retained on the reused parser instance between modules.
/** @type {Comment[]} */
const comments = [];
const urlHints = this.options.urlHints;
const fontPreload = this.options.fontPreload;
/**
* Apply `parser.css.urlHints` defaults + `webpackPrefetch` /
* `webpackPreload` / `webpackFetchPriority` / `webpackAs` /
* `webpackType` / `webpackMedia` magic-comment overrides to a fresh
* `CssUrlDependency`. Magic comments win over the project-wide default.
* @param {CssUrlDependency} dep dep
* @param {string} request asset request
* @param {Record<string, EXPECTED_ANY> | null | undefined} options parsed comment options
* @param {import("../Dependency").DependencyLocation} loc location for warnings
* @returns {void}
*/
const applyResourceHintDefaults = (dep, request, options, loc) => {
// `fontPreload` heuristic: seed `preload`/`as`/`type` for the first url
// of each `@font-face` (the nearest enclosing at-rule) as the lowest
// default, so `urlHints` rules and magic comments below still override.
if (fontPreload && atRuleStateStack.length > 0) {
const top = atRuleStateStack[atRuleStateStack.length - 1];
if (top.name === "@font-face" && !top.fontPreloaded) {
top.fontPreloaded = true;
dep.preload = true;
dep.asAttribute = "font";
const type = fontMimeType(request);
if (type) dep.typeAttribute = type;
}
}
ResourceHintPlugin.applyResourceHints(
dep,
urlHints,
request,
options,
state.module,
loc
);
};
const module = state.module;
// All `:export`-style exports are collected into a single
// `CssIcssExportDependency` per module (emitted at parse end) instead of one
// `Dependency` per export — far fewer retained objects on CSS-heavy builds.
/** @type {import("../dependencies/CssIcssExportDependency").CssExportEntry[]} */
const cssExportEntries = [];
/**
* @param {number} sl start line
* @param {number} sc start column
* @param {number} el end line
* @param {number} ec end column
* @param {string} name export name
* @param {import("../dependencies/CssIcssExportDependency").Value} value value or [localName, importName, request?]
* @param {Range=} range source range to replace, when present
* @param {boolean=} interpolate whether the value needs interpolation
* @param {import("../dependencies/CssIcssExportDependency").ExportMode=} exportMode export mode
* @param {import("../dependencies/CssIcssExportDependency").ExportType=} exportType export type
* @returns {void}
*/
const addCssExport = (
sl,
sc,
el,
ec,
name,
value,
range,
interpolate = false,
exportMode = CssIcssExportDependency.EXPORT_MODE.REPLACE,
exportType = CssIcssExportDependency.EXPORT_TYPE.NORMAL
) => {
// Flat location numbers — the nested loc objects were the parser's
// hottest allocation (3 objects per exported name).
cssExportEntries.push({
name,
value,
range,
interpolate,
exportMode,
exportType,
locStartLine: sl,
locStartColumn: sc,
locEndLine: el,
locEndColumn: ec
});
};
const parsedModuleResource = parseResource(
/** @type {string} */ (module.getResource())
);
const mode =
this.defaultMode === "auto" &&
module.type === CSS_MODULE_TYPE_AUTO &&
IS_MODULES.test(parsedModuleResource.path)
? "local"
: this.defaultMode;
const isModules = mode === "global" || mode === "local";
/** @type {Map<string, boolean>} */
const selfReferenceCache = new Map();
/**
* Whether a relative `from "<request>"` resolves back to the current module (matching query/fragment too).
* Memoized per parse — `composes … from "./x.css"` repeats the same request many times per file.
* @param {string} request request string from `from "<request>"`
* @returns {boolean} true if request resolves to the current module
*/
const isSelfReferenceRequest = (request) => {
const cached = selfReferenceCache.get(request);
if (cached !== undefined) return cached;
const result = isSelfReferenceRequestUncached(request);
selfReferenceCache.set(request, result);
return result;
};
/**
* Uncached `isSelfReferenceRequest`.
* @param {string} request request string from `from "<request>"`
* @returns {boolean} true if request resolves to the current module
*/
const isSelfReferenceRequestUncached = (request) => {
if (!RELATIVE_REQUEST.test(request)) return false;
if (!module.context) return false;
const parsedRequest = parseResource(request);
if (parsedRequest.query !== parsedModuleResource.query) return false;
if (parsedRequest.fragment !== parsedModuleResource.fragment) {
return false;
}
try {
return (
path.resolve(module.context, parsedRequest.path) ===
parsedModuleResource.path
);
} catch (_err) {
return false;
}
};
const knownProperties = getKnownProperties({
animation: this.options.animation,
container: this.options.container,
customIdents: this.options.customIdents,
grid: this.options.grid
});
const knownPropertyIndex = getKnownPropertyIndex(knownProperties);
/** @type {CssModuleBuildMeta} */
(module.buildMeta).isCssModule = isModules;
if (/** @type {CssModule} */ (module).exportType === "style") {
/** @type {CssModuleBuildMeta} */
(module.buildMeta).needIdInConcatenation = true;
}
const locConverter = new LocConverter(source);
/**
* Source location of a byte range. `LocConverter#get` mutates and returns itself, so snapshot between the two calls.
* @param {number} start start offset
* @param {number} end end offset
* @returns {{ start: Position, end: Position }} the source location
*/
const rangeLoc = (start, end) => {
const s = locConverter.get(start);
const sl = s.line;
const sc = s.column;
const e = locConverter.get(end);
return {
start: { line: sl, column: sc },
end: { line: e.line, column: e.column }
};
};
/**
* Set a dependency's source location from a byte range.
* @param {ConstDependency | CssUrlDependency | CssImportDependency | CssIcssImportDependency | CssIcssSymbolDependency} dep dependency with `setLoc`
* @param {number} start start offset
* @param {number} end end offset
*/
const setDepLoc = (dep, start, end) => {
const s = locConverter.get(start);
const sl = s.line;
const sc = s.column;
const e = locConverter.get(end);
dep.setLoc(sl, sc, e.line, e.column);
};
/**
* Apply the magic comments in `range`: warn on any compilation error, validate `webpackIgnore`, and return both the parsed options (for resource-hint / other magic-comment consumers) and whether the resource is ignored.
* @param {Range} range byte range to scan for magic comments
* @param {number} warnStart start offset of the loc for an invalid-`webpackIgnore` warning (computed lazily)
* @param {number} warnEnd end offset of that loc
* @returns {{ ignored: boolean, options: Record<string, EXPECTED_ANY> | null }} parsed options and `webpackIgnore` flag
*/
const magicCommentsIn = (range, warnStart, warnEnd) => {
/** @type {{ options: Record<string, EXPECTED_ANY> | null, errors: (Error & { comment: Comment })[] | null }} */
const { options, errors } = parseCommentOptionsInRange(
/** @type {(Comment & { range: [number, number], value: string })[]} */ (
comments
),
range,
this.magicCommentContext
);
if (errors) {
for (const e of errors) {
state.module.addWarning(
new CommentCompilationWarning(
`Compilation error while processing magic comment(-s): /*${e.comment.value}*/: ${e.message}`,
rangeLoc(e.comment.range[0], e.comment.range[1])
)
);
}
}
let ignored = false;
if (options && options.webpackIgnore !== undefined) {
if (typeof options.webpackIgnore !== "boolean") {
// Loc is computed lazily here — it's only needed for this rare
// warning, not on every checked `url()` / `@import`.
state.module.addWarning(
new UnsupportedFeatureWarning(
`\`webpackIgnore\` expected a boolean, but received: ${options.webpackIgnore}.`,
rangeLoc(warnStart, warnEnd)
)
);
} else {
ignored = options.webpackIgnore;
}
}
return { ignored, options };
};
/**
* Backwards-compatible boolean shortcut of {@link magicCommentsIn} for call sites that only need the ignore flag.
* @param {Range} range byte range to scan for magic comments
* @param {number} warnStart start offset of the loc for an invalid-`webpackIgnore` warning
* @param {number} warnEnd end offset of that loc
* @returns {boolean} true when `webpackIgnore: true`
*/
const webpackIgnored = (range, warnStart, warnEnd) =>
magicCommentsIn(range, warnStart, warnEnd).ignored;
// Closure-scope alias for `source` used by AST-walking helpers for substring extraction.
const input = source;
// `@custom-media` / `@custom-selector` are build-time only (no engine ships them), so they're resolved by file-local substitution. The `includes` gates keep files without them at zero overhead; definitions may follow their uses (names are stylesheet-global), so uses are collected during the walk and resolved after it.
const mayHaveCustomMedia =
this.options.customMedia && input.includes("@custom-media");
const mayHaveCustomSelectors =
this.options.customSelectors && input.includes("@custom-selector");
/** @type {Map<string, { text: string, kind: ("condition" | "type" | "unsupported") }> | undefined} */
let customMediaDefs;
/** @type {{ name: string, start: number, end: number, invalid: boolean, leading: boolean }[] | undefined} */
let customMediaUses;
/** @type {Map<string, string> | undefined} */
let customSelectorDefs;
/** @type {{ name: string, start: number, end: number }[] | undefined} */
let customSelectorUses;
/**
* Unescape a CSS identifier from a source byte range — for value spans not
* backed by a single token (string contents, `--` dashed-ident bodies,
* composed names). Token-backed names use `A.unescaped(node)` instead.
* @param {number} start start offset
* @param {number} end end offset
* @returns {string} the unescaped identifier
*/
const unescapeRange = (start, end) =>
unescapeIdentifier(input.slice(start, end));
let lastTokenEndForComments = 0;
/** Generates unique `__ICSS_IMPORT_${n}__` placeholders per ICSS import. */
const nextIcssImportName = (() => {
let n = 0;
return () => `__ICSS_IMPORT_${n++}__`;
})();
// All pure-mode state and helpers live on `pure`. When `pure.enabled` is false, the methods are no-ops, so callers can use them unconditionally.
const pure = {
enabled: isModules && Boolean(this.options.pure),
/** Whether the current rule's prelude has so far seen any impure comma-separated selector (set by `finalizeSelector`). */
ruleImpure: false,
/** Whether the current comma-separated selector has carried a local class / id (cleared by `finalizeSelector`). */
segmentLocal: false,
/** File-level kill switch from a top-of-file `cssmodules-pure-no-check` comment. */
noCheck: false,
/** Single-shot ignore from a `cssmodules-pure-ignore` comment — consumed by the next rule frame. */
ignorePending: false,
/** Has any top-level rule been processed (locks `noCheck`)? */
seenTopLevelRule: false,
/**
* Inherited per open block: `ancestorHadLocal` (nested rules inherit purity from a local-bearing ancestor) and `skipChildren` (a check-suppressing ancestor like `@keyframes`).
* @type {{ ancestorHadLocal: boolean, skipChildren: boolean }[]}
*/
stack: [],
/**
* Whether any ancestor (self inclusive) was pure — for ancestor-inheritance and `&`-resolution.
* @returns {boolean} true if any ancestor provided a local
*/
ancestorHadLocal() {
const top = this.stack[this.stack.length - 1];
return top ? top.ancestorHadLocal : false;
},
/**
* Record that the current comma-separated selector carries a local class / id (no-op when pure-mode is off).
*/
markLocal() {
if (this.enabled) this.segmentLocal = true;
},
/**
* Close the current comma-separated selector segment (or whole prelude at `{`): if it had no local and no ancestor compensates, the rule is impure (no-op when pure-mode is off).
*/
finalizeSelector() {
if (!this.enabled) return;
if (!this.segmentLocal && !this.ancestorHadLocal()) {
this.ruleImpure = true;
}
this.segmentLocal = false;
},
/**
* Mark that a top-level rule has been processed; locks `noCheck` (no-op when pure-mode is off).
*/
markSeenTopLevelRule() {
if (this.enabled) this.seenTopLevelRule = true;
},
/**
* Report a pure-mode violation covering the entire rule prelude.
* @param {number} start prelude start offset
* @param {number} end prelude end offset (`{` position)
*/
report: (start, end) => {
const slice = source.slice(start, end);
const lead = /** @type {RegExpExecArray} */ (
/^(?:\s|\/\*[\s\S]*?\*\/)*/.exec(slice)
)[0].length;
const trail = /** @type {RegExpExecArray} */ (/\s*$/.exec(slice))[0]
.length;
const from = start + lead;
const to = end - trail;
if (to <= from) return;
this._emitError(
state,
`Selector "${source.slice(
from,
to
)}" is not pure (pure selectors must contain at least one local class or id)`,
locConverter,
from,
to
);
},
/**
* Rule entry: report an impure leaf-ish rule (prelude purity is known, body already parsed), push the inherited-context frame, reset per-rule selector flags.
* @param {{ isRulePrelude: boolean, treatAsLeaf: boolean, ownSkip: boolean, declarations: Declaration[] | null, childRules: Rule[] | null, preludeStart: number, preludeEnd: number }} opts entry options
*/
enterBlock(opts) {
if (!this.enabled) return;
const {
isRulePrelude,
treatAsLeaf,
ownSkip,
declarations,
childRules,
preludeStart,
preludeEnd
} = opts;
const top = this.stack[this.stack.length - 1];
const skipOwn = top ? top.skipChildren : false;
const reportable =
!this.noCheck &&
!this.ignorePending &&
!skipOwn &&
isRulePrelude &&
this.ruleImpure;
if (reportable) {
const hasBody = Boolean(declarations || childRules);
let leaf = treatAsLeaf || !hasBody;
if (!leaf && hasBody) {
const { hasDirectDecl, hasNestedBlock } = scanRuleBody(
declarations,
childRules
);
leaf = hasDirectDecl || !hasNestedBlock;
}
if (leaf) this.report(preludeStart, preludeEnd);
}
this.stack.push({
ancestorHadLocal:
this.ancestorHadLocal() || (isRulePrelude && !this.ruleImpure),
skipChildren: ownSkip || skipOwn
});
this.ignorePending = false;
this.segmentLocal = false;
this.ruleImpure = false;
},
/**
* Drop the inherited-context frame (no-op when pure-mode is off).
*/
exitBlock() {
if (this.enabled) this.stack.pop();
},
/**
* Apply a comment's pure-mode side effect: `ignorePending` for `cssmodules-pure-ignore`, or the file-level `noCheck` for `cssmodules-pure-no-check` before the first top-level rule.
* @param {string} value comment body (without the surrounding delimiters)
*/
applyComment(value) {
if (PURE_IGNORE_RE.test(value)) {
this.ignorePending = true;
} else if (PURE_NO_CHECK_RE.test(value) && !this.seenTopLevelRule) {
this.noCheck = true;
}
}
};
/** @typedef {{ value?: string, importName?: string, localName?: string, request?: string }} IcssDefinition */
/** @type {Map<string, IcssDefinition>} */
const icssDefinitions = new Map();
// `composes: … from "<file>"` load-order graph (postcss-modules-extract-imports#138); topologically sorted at end-of-parse to tag each file's first composes-import with `sourceOrder`.
/** @type {Map<string, Set<string>>} */
const composesGraph = new Map();
/** @type {Map<string, CssIcssImportDependency>} */
const composesFirstFileImport = new Map();
// Per-rule CSS-Modules state, saved on the rule's state stack at enter and restored at exit. `composesPrevFile` / `composesFiles` are only meaningful inside qualified rules (composes can't appear in at-rule preludes).
const currentRule = {
/** Did this rule's prelude declare a local-mode anchor selector? */
hasLocalAnchor: false,
/** Local class / id names in source order (composes reads `[0]` as the anchor). */
/** @type {string[]} */
localIdentifiers: [],
/** Previous `composes: … from "…"` file in this rule (for the load-order graph edges). */
/** @type {string | undefined} */
composesPrevFile: undefined,
/** All files this rule has composed from (so an edge is added only once per file pair); lazily created — null until the rule's first `composes: … from`. */
/** @type {Set<string> | null} */
composesFiles: null
};
/**
* Whether the module's default mode is local (callers here have no `:local`/`:global` wrapper in scope, so it reduces to the default mode).
* @returns {boolean} true when the module's default mode is `local`
*/
const isLocalMode = () => mode === "local";
/**
* Effective local mode: persistent `:local`/`:global` from `modeData` if any, else the module's default.
* @returns {boolean} true when the effective mode is local
*/
const isEffectivelyLocal = () =>
modeData ? modeData === "local" : mode === "local";
/**
* Comment visitor (`NodeType.Comment`): push every comment (in source order) onto the local `comments`, read back by `advanceCommentCursor` (pure-mode flags) and `parseCommentOptionsInRange` (magic comments).
* @param {import("./syntax").CssPath} path walk path at the comment node
*/
const commentVisitor = (path) => {
const node = path.node;
const start = A.start(node);
const end = A.end(node);
comments.push({
value: source.slice(start + 2, end - 2),
range: [start, end]
});
};
/**
* Advance past every comment closing at/before `until` (in source order) and apply its pure-mode side effect: `pure.ignorePending` (next rule) or the file-level `pure.noCheck` (only before the first top-level rule). The cursor is closed over so it isn't visible at parser scope.
* @returns {(until: number) => void} the cursor-advance function
*/
const advanceCommentCursor = (() => {
let cursor = 0;
/** @param {number} until source position to advance the cursor to */
return (until) => {
if (!pure.enabled) return;
while (cursor < comments.length) {
const c = comments[cursor];
if (c.range[1] > until) return;
pure.applyComment(c.value);
cursor++;
}
};
})();
// CSS modules stuff
/**
* Returns resolved reexport (localName and importName).
* @param {string} value value to resolve
* @param {string=} localName override local name
* @param {boolean=} isCustomProperty true when it is custom property, otherwise false
* @returns {string | [string, string] | [string, string, string]} resolved reexport (`localName`, `importName` and optional `request` of the active `@value` import)
*/
const getReexport = (value, localName, isCustomProperty) => {
// No `@value` / `:import` / composes definitions: skip the `--` key
// concat + map probe (the common case for plain CSS-Modules files).
const reexport =
icssDefinitions.size === 0
? undefined
: icssDefinitions.get(isCustomProperty ? `--${value}` : value);
if (reexport) {
if (reexport.importName) {
const resolvedLocalName =
reexport.localName || (isCustomProperty ? `--${value}` : value);
return reexport.request
? [resolvedLocalName, reexport.importName, reexport.request]
: [resolvedLocalName, reexport.importName];
}
if (isCustomProperty) {
return /** @type {string} */ (reexport.value).slice(2);
}
return /** @type {string} */ (reexport.value);
}
if (localName) {
return [localName, value];
}
return value;
};
/**
* Process import or export, reusing the already-parsed rule nodes.
* @param {0 | 1} type import or export
* @param {AstNode} second the `import(…)` function / `export` ident node from the prelude
* @param {QualifiedRule} rule the `:import` / `:export` qualified rule
* @returns {number} position after parse
*/
const processImportOrExport = (type, second, rule) => {
/** @type {string | undefined} */
let request;
if (type === 0) {
const parsed = parseIcssImportRequest(second, rule, source);
if ("errorPos" in parsed) {
const { errorPos } = parsed;
this._emitWarning(
state,
`Unexpected '${source[errorPos]}' at ${errorPos} during parsing of ':import' (expected string)`,
locConverter,
errorPos,
errorPos
);
return errorPos;
}
request = parsed.request;
}
/**
* Creates a dep from the provided name.
* @param {string} name name
* @param {string} value value
* @param {number} start start of position
* @param {number} end end of position
*/
const createDep = (name, value, start, end) => {
if (type === 0) {
const dep = new CssIcssImportDependency(