webpack 5.108.3 → 5.109.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/APIPlugin.js +48 -29
- package/lib/CacheFacade.js +8 -0
- package/lib/ChunkGroup.js +1 -0
- package/lib/CompatibilityPlugin.js +31 -29
- package/lib/Compilation.js +50 -8
- package/lib/ConstPlugin.js +11 -11
- package/lib/ContextModule.js +239 -6
- package/lib/ContextModuleFactory.js +139 -14
- package/lib/DefinePlugin.js +396 -101
- package/lib/Entrypoint.js +1 -1
- package/lib/ExportsInfoApiPlugin.js +2 -2
- package/lib/ExternalModule.js +107 -5
- package/lib/ExternalModuleFactoryPlugin.js +20 -1
- package/lib/FileSystemInfo.js +130 -74
- package/lib/FlagDependencyExportsPlugin.js +55 -8
- package/lib/HotModuleReplacementPlugin.js +18 -15
- package/lib/LazyBarrel.js +30 -3
- package/lib/Module.js +3 -0
- package/lib/ModuleFilenameHelpers.js +2 -2
- package/lib/ModuleTypeConstants.js +8 -1
- package/lib/MultiCompiler.js +13 -2
- package/lib/MultiWatching.js +1 -0
- package/lib/NodeStuffPlugin.js +51 -33
- package/lib/NormalModule.js +169 -405
- package/lib/NormalModuleFactory.js +76 -2
- package/lib/ProgressPlugin.js +211 -119
- package/lib/ProvidePlugin.js +2 -2
- package/lib/RuntimeGlobals.js +47 -0
- package/lib/RuntimePlugin.js +33 -0
- package/lib/RuntimeTemplate.js +304 -16
- package/lib/UseStrictPlugin.js +1 -1
- package/lib/WebpackIsIncludedPlugin.js +1 -1
- package/lib/WebpackOptionsApply.js +24 -19
- package/lib/asset/AssetGenerator.js +110 -28
- package/lib/asset/AssetModulesPlugin.js +92 -2
- package/lib/asset/WebManifestGenerator.js +164 -0
- package/lib/asset/WebManifestParser.js +130 -0
- package/lib/async-modules/AwaitDependenciesInitFragment.js +14 -4
- package/lib/async-modules/isGeneratorLowered.js +26 -0
- package/lib/cache/PackFileCacheStrategy.js +9 -3
- package/lib/config/browserslistTargetHandler.js +40 -0
- package/lib/config/defaults.js +222 -32
- package/lib/config/normalization.js +26 -0
- package/lib/config/target.js +9 -1
- package/lib/css/CssGenerator.js +73 -73
- package/lib/css/CssLoadingRuntimeModule.js +5 -1
- package/lib/css/CssModulesPlugin.js +92 -53
- package/lib/css/CssParser.js +843 -302
- package/lib/css/syntax.js +1219 -483
- package/lib/dependencies/AMDDefineDependencyParserPlugin.js +6 -6
- package/lib/dependencies/AMDPlugin.js +2 -2
- package/lib/dependencies/AMDRequireDependenciesBlockParserPlugin.js +13 -13
- package/lib/dependencies/CommonJsDependencyHelpers.js +39 -0
- package/lib/dependencies/CommonJsExportsDependency.js +47 -2
- package/lib/dependencies/CommonJsExportsParserPlugin.js +10 -10
- package/lib/dependencies/CommonJsImportsParserPlugin.js +18 -18
- package/lib/dependencies/CommonJsPlugin.js +2 -2
- package/lib/dependencies/CommonJsRequireDependency.js +26 -1
- package/lib/dependencies/CommonJsSelfReferenceDependency.js +32 -1
- package/lib/dependencies/ContextDependencyHelpers.js +3 -3
- package/lib/dependencies/CreateRequireParserPlugin.js +6 -6
- package/lib/dependencies/CssIcssExportDependency.js +190 -87
- package/lib/dependencies/CssUrlDependency.js +35 -4
- package/lib/dependencies/ExternalModuleInitFragmentDependency.js +2 -2
- package/lib/dependencies/HarmonyCompatibilityDependency.js +32 -14
- package/lib/dependencies/HarmonyDetectionParserPlugin.js +43 -13
- package/lib/dependencies/HarmonyExportDependencyParserPlugin.js +7 -19
- package/lib/dependencies/HarmonyImportDependency.js +12 -1
- package/lib/dependencies/HarmonyImportDependencyParserPlugin.js +34 -107
- package/lib/dependencies/HarmonyImportGuard.js +192 -20
- package/lib/dependencies/HarmonyModulesPlugin.js +6 -0
- package/lib/dependencies/HarmonyTopLevelThisParserPlugin.js +1 -1
- package/lib/dependencies/HtmlEntryDependency.js +797 -172
- package/lib/dependencies/HtmlSourceDependency.js +26 -0
- package/lib/dependencies/ImportDependency.js +43 -1
- package/lib/dependencies/ImportMetaContextDependencyParserPlugin.js +31 -18
- package/lib/dependencies/ImportMetaContextPlugin.js +38 -7
- package/lib/dependencies/ImportMetaGlobDependency.js +81 -0
- package/lib/dependencies/ImportMetaGlobDependencyParserPlugin.js +78 -0
- package/lib/dependencies/ImportMetaGlobHelpers.js +660 -0
- package/lib/dependencies/ImportMetaPlugin.js +532 -180
- package/lib/dependencies/ImportMetaResolveDependency.js +90 -0
- package/lib/dependencies/ImportParserPlugin.js +87 -26
- package/lib/dependencies/ImportPhase.js +10 -8
- package/lib/dependencies/LoaderPlugin.js +5 -1
- package/lib/dependencies/RequireContextDependencyParserPlugin.js +1 -1
- package/lib/dependencies/RequireEnsureDependenciesBlockParserPlugin.js +3 -6
- package/lib/dependencies/RequireIncludeDependencyParserPlugin.js +4 -11
- package/lib/dependencies/SystemPlugin.js +2 -4
- package/lib/dependencies/TopLevelAwaitDependency.js +93 -0
- package/lib/dependencies/URLDependency.js +141 -21
- package/lib/dependencies/{WorkerPlugin.js → WorkerAndWorkletPlugin.js} +324 -112
- package/lib/dependencies/WorkerDependency.js +86 -18
- package/lib/dependencies/WorkletDependency.js +278 -0
- package/lib/dll/DelegatedModuleFactoryPlugin.js +19 -0
- package/lib/errors/EnvironmentNotSupportAsyncWarning.js +1 -1
- package/lib/errors/LoaderLoadingError.js +20 -0
- package/lib/errors/ModuleParseError.js +39 -22
- package/lib/errors/NodeStuffInWebError.js +1 -1
- package/lib/esm/ModuleChunkFormatPlugin.js +47 -14
- package/lib/esm/ModuleChunkLoadingPlugin.js +8 -1
- package/lib/esm/ModuleChunkLoadingRuntimeModule.js +29 -1
- package/lib/hmr/lazyCompilationBackend.js +32 -8
- package/lib/html/HtmlGenerator.js +940 -91
- package/lib/html/HtmlModule.js +3 -2
- package/lib/html/HtmlModulesPlugin.js +625 -170
- package/lib/html/HtmlParser.js +812 -340
- package/lib/html/favicon.svg +1 -0
- package/lib/html/syntax.js +2116 -834
- package/lib/ids/IdHelpers.js +1 -1
- package/lib/index.js +6 -0
- package/lib/javascript/ArrayPushCallbackChunkFormatPlugin.js +4 -5
- package/lib/javascript/BasicEvaluatedExpression.js +366 -48
- package/lib/javascript/JavascriptGenerator.js +197 -49
- package/lib/javascript/JavascriptModulesPlugin.js +178 -24
- package/lib/javascript/JavascriptParser.js +711 -355
- package/lib/javascript/JavascriptParserHelpers.js +3 -6
- package/lib/javascript/syntax.js +4380 -0
- package/lib/library/ModuleLibraryPlugin.js +38 -1
- package/lib/loaders/LoaderRunner.js +714 -0
- package/lib/loaders/loadLoader.js +100 -0
- package/lib/node/ReadFileCompileAsyncWasmPlugin.js +19 -0
- package/lib/node/ReadFileCompileWasmPlugin.js +14 -0
- package/lib/optimize/ConcatenatedModule.js +120 -16
- package/lib/optimize/FlagIncludedChunksPlugin.js +13 -3
- package/lib/optimize/InnerGraphPlugin.js +50 -30
- package/lib/optimize/ModuleConcatenationPlugin.js +69 -3
- package/lib/optimize/SideEffectsFlagPlugin.js +86 -43
- package/lib/prefetch/ChunkPrefetchPreloadPlugin.js +15 -0
- package/lib/prefetch/ChunkPreloadTriggerRuntimeModule.js +24 -13
- package/lib/prefetch/ResourceHintPlugin.js +663 -0
- package/lib/prefetch/ResourceHintRuntimeModule.js +149 -0
- package/lib/prefetch/StartupAssetHintRuntimeModule.js +210 -0
- package/lib/prefetch/parseResourceHintOptions.js +103 -0
- package/lib/rules/RuleSetCompiler.js +114 -0
- package/lib/runtime/AsyncModuleGeneratorRuntimeModule.js +56 -0
- package/lib/runtime/AsyncModuleRuntimeModule.js +24 -8
- package/lib/runtime/AutoPublicPathRuntimeModule.js +7 -1
- package/lib/runtime/CommonJsWrapRuntimeModule.js +37 -0
- package/lib/runtime/GetWorkletBootstrapRuntimeModule.js +59 -0
- package/lib/runtime/MakeDeferredNamespaceObjectRuntime.js +204 -8
- package/lib/runtime/SetAnonymousDefaultNameRuntimeModule.js +4 -1
- package/lib/schemes/HttpUriPlugin.js +40 -1
- package/lib/schemes/VirtualUrlPlugin.js +1 -2
- package/lib/serialization/FileMiddleware.js +29 -3
- package/lib/stats/DefaultStatsFactoryPlugin.js +32 -3
- package/lib/stats/DefaultStatsPresetPlugin.js +2 -0
- package/lib/stats/DefaultStatsPrinterPlugin.js +12 -2
- package/lib/stats/StatsFactory.js +15 -8
- package/lib/url/URLParserPlugin.js +98 -19
- package/lib/util/AsyncQueue.js +122 -72
- package/lib/util/LocConverter.js +9 -8
- package/lib/util/SetHelpers.js +1 -1
- package/lib/util/SourceProcessor.js +41 -28
- package/lib/util/StackedMap.js +122 -48
- package/lib/util/binarySearchBounds.js +2 -2
- package/lib/util/concatenate.js +40 -14
- package/lib/util/createHooksRegistry.js +13 -5
- package/lib/util/dataURL.js +2 -2
- package/lib/util/deterministicGrouping.js +7 -5
- package/lib/util/formatSize.js +6 -2
- package/lib/util/globUtils.js +710 -0
- package/lib/util/identifier.js +23 -6
- package/lib/util/implicitTypeLoaderFallback.js +62 -0
- package/lib/util/internalSerializables.js +88 -71
- package/lib/util/magicComment.js +121 -25
- package/lib/util/numberHash.js +2 -2
- package/lib/wasm-async/AsyncWasmCompileRuntimeModule.js +24 -18
- package/lib/wasm-async/AsyncWasmLoadingRuntimeModule.js +33 -22
- package/lib/wasm-sync/WasmChunkLoadingRuntimeModule.js +79 -16
- package/lib/web/FetchCompileAsyncWasmPlugin.js +19 -0
- package/lib/web/FetchCompileWasmPlugin.js +14 -0
- package/lib/web/JsonpChunkLoadingRuntimeModule.js +2 -0
- package/lib/webpack.js +65 -0
- package/lib/webworker/ImportScriptsChunkLoadingRuntimeModule.js +3 -0
- package/module.d.ts +21 -0
- package/package.json +14 -13
- package/schemas/WebpackOptions.check.js +1 -1
- package/schemas/WebpackOptions.json +759 -36
- package/schemas/plugins/HtmlParserOptions.check.js +1 -1
- package/schemas/plugins/ProgressPlugin.check.js +1 -1
- package/schemas/plugins/ProgressPlugin.json +17 -1
- package/schemas/plugins/container/ContainerReferencePlugin.check.js +1 -1
- package/schemas/plugins/container/ContainerReferencePlugin.json +1 -0
- package/schemas/plugins/container/ExternalsType.check.js +1 -1
- package/schemas/plugins/container/ModuleFederationPlugin.check.js +1 -1
- package/schemas/plugins/container/ModuleFederationPlugin.json +1 -0
- package/schemas/plugins/css/CssAutoOrModuleParserOptions.check.js +1 -1
- package/schemas/plugins/css/CssModuleParserOptions.check.js +1 -1
- package/schemas/plugins/css/CssParserOptions.check.js +1 -1
- package/types.d.ts +1300 -325
package/lib/css/CssParser.js
CHANGED
|
@@ -6,7 +6,6 @@
|
|
|
6
6
|
"use strict";
|
|
7
7
|
|
|
8
8
|
const path = require("path");
|
|
9
|
-
const vm = require("vm");
|
|
10
9
|
const { CSS_MODULE_TYPE_AUTO } = require("../ModuleTypeConstants");
|
|
11
10
|
const Parser = require("../Parser");
|
|
12
11
|
const ConstDependency = require("../dependencies/ConstDependency");
|
|
@@ -20,19 +19,26 @@ const CommentCompilationWarning = require("../errors/CommentCompilationWarning")
|
|
|
20
19
|
const ModuleDependencyWarning = require("../errors/ModuleDependencyWarning");
|
|
21
20
|
const UnsupportedFeatureWarning = require("../errors/UnsupportedFeatureWarning");
|
|
22
21
|
const WebpackError = require("../errors/WebpackError");
|
|
22
|
+
const ResourceHintPlugin = require("../prefetch/ResourceHintPlugin");
|
|
23
23
|
const LocConverter = require("../util/LocConverter");
|
|
24
|
-
const binarySearchBounds = require("../util/binarySearchBounds");
|
|
25
24
|
const { parseResource } = require("../util/identifier");
|
|
26
25
|
const {
|
|
27
26
|
createMagicCommentContext,
|
|
28
|
-
|
|
27
|
+
parseCommentOptionsInRange
|
|
29
28
|
} = require("../util/magicComment");
|
|
30
29
|
const topologicalSort = require("../util/topologicalSort");
|
|
31
30
|
const {
|
|
32
31
|
A,
|
|
33
32
|
NodeType,
|
|
34
33
|
SourceProcessor,
|
|
34
|
+
buildSkipSet,
|
|
35
35
|
equalsLowerCase,
|
|
36
|
+
isDashedIdentifier,
|
|
37
|
+
isWhitespace,
|
|
38
|
+
normalizeUrl,
|
|
39
|
+
rangeEquals,
|
|
40
|
+
rangeEqualsLowerCase,
|
|
41
|
+
toLowerCaseIfNeeded,
|
|
36
42
|
unescapeIdentifier
|
|
37
43
|
} = require("./syntax");
|
|
38
44
|
|
|
@@ -74,6 +80,8 @@ const CC_LINE_FEED = "\n".charCodeAt(0);
|
|
|
74
80
|
const CC_CARRIAGE_RETURN = "\r".charCodeAt(0);
|
|
75
81
|
const CC_FORM_FEED = "\f".charCodeAt(0);
|
|
76
82
|
const CC_LEFT_CURLY = "{".charCodeAt(0);
|
|
83
|
+
const CC_LOWER_V = "v".charCodeAt(0);
|
|
84
|
+
const CC_UPPER_V = "V".charCodeAt(0);
|
|
77
85
|
|
|
78
86
|
// A parsed CSS comment. `loc` is computed on demand — only magic-comment error
|
|
79
87
|
// warnings read it, so comment-heavy CSS skips the per-comment line/col work.
|
|
@@ -81,20 +89,73 @@ const CC_LEFT_CURLY = "{".charCodeAt(0);
|
|
|
81
89
|
/** @typedef {{ value: string, range: Range }} Comment */
|
|
82
90
|
|
|
83
91
|
// Newlines (CSS Syntax 3 §3.3) — listed explicitly since there's no preprocessing stage.
|
|
84
|
-
const STRING_MULTILINE = /\\[\n\r\f]/g;
|
|
85
92
|
// https://www.w3.org/TR/css-syntax-3/#whitespace
|
|
86
|
-
const TRIM_WHITE_SPACES = /(^[ \t\n\r\f]*|[ \t\n\r\f]*$)/g;
|
|
87
93
|
// 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.
|
|
88
94
|
const PURE_IGNORE_RE = /^\s*cssmodules-pure-ignore(?:\s|$)/;
|
|
89
95
|
const PURE_NO_CHECK_RE = /^\s*cssmodules-pure-no-check(?:\s|$)/;
|
|
90
|
-
const UNESCAPE = /\\([0-9a-f]{1,6}[ \t\n\r\f]?|[\s\S])/gi;
|
|
91
96
|
const IMAGE_SET_FUNCTION = /^(?:-\w+-)?image-set$/i;
|
|
92
|
-
// `parseCommentOptions` fast path: a whole-comment `webpackXxx: <bool|number|null>`; every other (and every failing) shape falls back to the `vm.runInContext` path.
|
|
93
|
-
const MAGIC_COMMENT_FAST_PATH =
|
|
94
|
-
/^\s*(webpack[A-Z][A-Za-z]+)\s*:\s*(true|false|null|-?\d+(?:\.\d+)?)\s*$/;
|
|
95
97
|
const OPTIONALLY_VENDOR_PREFIXED_KEYFRAMES_AT_RULE = /^@(?:-\w+-)?keyframes$/;
|
|
98
|
+
const VENDOR_PREFIX = /^-\w+-/;
|
|
96
99
|
const COMPOSES_PROPERTY = /^(?:composes|compose-with)$/i;
|
|
100
|
+
// Functional view-transition pseudo-elements whose `(<name> .class…)` argument names are scoped like `view-transition-name`/`-class` values.
|
|
101
|
+
const VIEW_TRANSITION_PART_PSEUDO =
|
|
102
|
+
/^view-transition-(?:group|image-pair|old|new)$/i;
|
|
97
103
|
const IS_MODULES = /\.modules?\.[^.]+$/i;
|
|
104
|
+
|
|
105
|
+
// `@font-face` src extension → preload `type`; the emission guesses `as="font"`.
|
|
106
|
+
const FONT_MIME_TYPES = new Map([
|
|
107
|
+
["woff2", "font/woff2"],
|
|
108
|
+
["woff", "font/woff"],
|
|
109
|
+
["ttf", "font/ttf"],
|
|
110
|
+
["otf", "font/otf"],
|
|
111
|
+
["eot", "application/vnd.ms-fontobject"]
|
|
112
|
+
]);
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* @param {string} request font url (may carry a query/hash)
|
|
116
|
+
* @returns {string | undefined} preload `type` for a known font extension
|
|
117
|
+
*/
|
|
118
|
+
const fontMimeType = (request) => {
|
|
119
|
+
const match = /\.([a-z0-9]+)(?:[?#]|$)/i.exec(request);
|
|
120
|
+
return match ? FONT_MIME_TYPES.get(match[1].toLowerCase()) : undefined;
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
// Skip options for a non-CSS-Modules parse: drop the selector prelude (never
|
|
124
|
+
// walked without modules) plus value / function-arg leaves nothing reads (the
|
|
125
|
+
// `Ident` visitor no-ops, the `Declaration` visitor returns early, no ICSS).
|
|
126
|
+
// `url` / functions / strings / blocks / commas are kept — they carry url()
|
|
127
|
+
// rewrites and image-set fences. At-rule preludes are kept (`@media` / `@import`
|
|
128
|
+
// are read). CSS-Modules parses skip nothing: selectors are walked and ICSS
|
|
129
|
+
// `:export { k: v }` captures each value's byte range from its first / last node.
|
|
130
|
+
const SKIP_NON_MODULES = {
|
|
131
|
+
types: buildSkipSet([
|
|
132
|
+
NodeType.Number,
|
|
133
|
+
NodeType.Dimension,
|
|
134
|
+
NodeType.Percentage,
|
|
135
|
+
NodeType.Ident,
|
|
136
|
+
NodeType.Hash,
|
|
137
|
+
NodeType.Colon,
|
|
138
|
+
NodeType.Delim,
|
|
139
|
+
// Nothing reads value/arg whitespace either — consumers use
|
|
140
|
+
// `nextNonWhitespace` / type checks that tolerate its absence.
|
|
141
|
+
NodeType.Whitespace
|
|
142
|
+
]),
|
|
143
|
+
selectorPrelude: true
|
|
144
|
+
};
|
|
145
|
+
// Like SKIP_NON_MODULES but keeps selector preludes and the `Colon` / `Ident`
|
|
146
|
+
// tokens inside function-arg lists, so `@custom-selector` `:--name` references
|
|
147
|
+
// (including nested ones like `:is(:--name)`) survive a non-modules parse.
|
|
148
|
+
const SKIP_NON_MODULES_KEEP_SELECTORS = {
|
|
149
|
+
types: buildSkipSet([
|
|
150
|
+
NodeType.Number,
|
|
151
|
+
NodeType.Dimension,
|
|
152
|
+
NodeType.Percentage,
|
|
153
|
+
NodeType.Hash,
|
|
154
|
+
NodeType.Delim,
|
|
155
|
+
NodeType.Whitespace
|
|
156
|
+
]),
|
|
157
|
+
selectorPrelude: false
|
|
158
|
+
};
|
|
98
159
|
const CSS_COMMENT = /\/\*((?!\*\/)[\s\S]*?)\*\//g;
|
|
99
160
|
// `@value` recognizers (postcss-modules-values shape): the import form `<names> from <source>`, and the `<importName> as <localName>` alias inside it.
|
|
100
161
|
const VALUE_IMPORT_FORM = /from(\/\*|\s)(?:[\s\S]+)$/i;
|
|
@@ -125,74 +186,63 @@ const matchAll = (regexp, str) => {
|
|
|
125
186
|
};
|
|
126
187
|
|
|
127
188
|
/**
|
|
128
|
-
*
|
|
129
|
-
*
|
|
130
|
-
*
|
|
131
|
-
*
|
|
189
|
+
* Range-keyed index over a known-properties table: ASCII-case-folded 31-hash
|
|
190
|
+
* of the name bytes → canonical key(s). Lets the Declaration visitor answer
|
|
191
|
+
* "is this a known property" (and get the canonical lowercase name) without
|
|
192
|
+
* slicing the property name out of the source per declaration.
|
|
193
|
+
* @type {WeakMap<Map<string, Record<string, number>>, Map<number, string | string[]>>}
|
|
132
194
|
*/
|
|
133
|
-
const
|
|
134
|
-
// Fast paths: skip the regex engine for the common URL with no escape and
|
|
135
|
-
// no edge whitespace (e.g. `./img.png`). Each guard is equivalent to the
|
|
136
|
-
// regex being a no-op.
|
|
137
|
-
// Remove escaped newlines from a string-token url like `url("im\<newline>g.png")`.
|
|
138
|
-
if (isString && str.includes("\\")) {
|
|
139
|
-
str = str.replace(STRING_MULTILINE, "");
|
|
140
|
-
}
|
|
195
|
+
const KNOWN_PROPERTY_INDEX_CACHE = new WeakMap();
|
|
141
196
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
return String.fromCharCode(Number.parseInt(match.slice(1).trim(), 16));
|
|
197
|
+
/**
|
|
198
|
+
* Gets (or builds) the hash index for a known-properties table.
|
|
199
|
+
* @param {Map<string, Record<string, number>>} knownProperties known properties table
|
|
200
|
+
* @returns {Map<number, string | string[]>} hash → canonical name(s)
|
|
201
|
+
*/
|
|
202
|
+
const getKnownPropertyIndex = (knownProperties) => {
|
|
203
|
+
let index = KNOWN_PROPERTY_INDEX_CACHE.get(knownProperties);
|
|
204
|
+
if (index === undefined) {
|
|
205
|
+
index = new Map();
|
|
206
|
+
for (const name of knownProperties.keys()) {
|
|
207
|
+
let h = name.length;
|
|
208
|
+
for (let i = 0; i < name.length; i++) {
|
|
209
|
+
h = ((h << 5) - h + name.charCodeAt(i)) | 0;
|
|
156
210
|
}
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
if (/^data:/i.test(str)) {
|
|
162
|
-
return str;
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
if (str.includes("%")) {
|
|
166
|
-
// Convert `url('%2E/img.png')` -> `url('./img.png')`
|
|
167
|
-
try {
|
|
168
|
-
str = decodeURIComponent(str);
|
|
169
|
-
} catch (_err) {
|
|
170
|
-
// Ignore
|
|
211
|
+
const hit = index.get(h);
|
|
212
|
+
if (hit === undefined) index.set(h, name);
|
|
213
|
+
else if (typeof hit === "string") index.set(h, [hit, name]);
|
|
214
|
+
else hit.push(name);
|
|
171
215
|
}
|
|
216
|
+
KNOWN_PROPERTY_INDEX_CACHE.set(knownProperties, index);
|
|
172
217
|
}
|
|
173
|
-
|
|
174
|
-
return str;
|
|
218
|
+
return index;
|
|
175
219
|
};
|
|
176
220
|
|
|
177
|
-
// `escapeIdentifier` / `unescapeIdentifier` live in ./syntax.js; they're re-exported at the bottom for back-compat callers.
|
|
178
|
-
|
|
179
221
|
/**
|
|
180
|
-
*
|
|
181
|
-
* @param {string}
|
|
182
|
-
* @
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
/**
|
|
188
|
-
* `binarySearchBounds` comparator for `getComments` — hoisted so the lookup
|
|
189
|
-
* doesn't allocate a fresh closure per call.
|
|
190
|
-
* @param {Comment} comment comment
|
|
191
|
-
* @param {number} needle needle (byte offset)
|
|
192
|
-
* @returns {number} comparison
|
|
222
|
+
* Canonical known-property name for a source range (ASCII case-insensitive), without slicing.
|
|
223
|
+
* @param {Map<number, string | string[]>} index hash index from `getKnownPropertyIndex`
|
|
224
|
+
* @param {string} input source
|
|
225
|
+
* @param {number} start name start
|
|
226
|
+
* @param {number} end name end (exclusive)
|
|
227
|
+
* @returns {string | undefined} canonical lowercase name, or undefined when unknown
|
|
193
228
|
*/
|
|
194
|
-
const
|
|
195
|
-
|
|
229
|
+
const knownPropertyForRange = (index, input, start, end) => {
|
|
230
|
+
let h = end - start;
|
|
231
|
+
for (let i = start; i < end; i++) {
|
|
232
|
+
let c = input.charCodeAt(i);
|
|
233
|
+
if (c >= 65 && c <= 90) c |= 0x20;
|
|
234
|
+
h = ((h << 5) - h + c) | 0;
|
|
235
|
+
}
|
|
236
|
+
const hit = index.get(h);
|
|
237
|
+
if (hit === undefined) return undefined;
|
|
238
|
+
if (typeof hit === "string") {
|
|
239
|
+
return rangeEqualsLowerCase(input, start, end, hit) ? hit : undefined;
|
|
240
|
+
}
|
|
241
|
+
for (let i = 0; i < hit.length; i++) {
|
|
242
|
+
if (rangeEqualsLowerCase(input, start, end, hit[i])) return hit[i];
|
|
243
|
+
}
|
|
244
|
+
return undefined;
|
|
245
|
+
};
|
|
196
246
|
|
|
197
247
|
/** @type {Record<string, number>} */
|
|
198
248
|
const PREDEFINED_COUNTER_STYLES = {
|
|
@@ -433,6 +483,22 @@ const buildKnownProperties = (options = {}) => {
|
|
|
433
483
|
"spell-out": 1,
|
|
434
484
|
...PREDEFINED_COUNTER_STYLES
|
|
435
485
|
});
|
|
486
|
+
knownProperties.set("view-transition-name", {
|
|
487
|
+
none: Infinity,
|
|
488
|
+
auto: Infinity,
|
|
489
|
+
"match-element": Infinity,
|
|
490
|
+
...GLOBAL_VALUES
|
|
491
|
+
});
|
|
492
|
+
knownProperties.set("view-transition-group", {
|
|
493
|
+
normal: Infinity,
|
|
494
|
+
contain: Infinity,
|
|
495
|
+
nearest: Infinity,
|
|
496
|
+
...GLOBAL_VALUES
|
|
497
|
+
});
|
|
498
|
+
knownProperties.set("view-transition-class", {
|
|
499
|
+
none: Infinity,
|
|
500
|
+
...GLOBAL_VALUES
|
|
501
|
+
});
|
|
436
502
|
}
|
|
437
503
|
|
|
438
504
|
if (options.grid) {
|
|
@@ -478,24 +544,8 @@ const getKnownProperties = (options = {}) => {
|
|
|
478
544
|
return table;
|
|
479
545
|
};
|
|
480
546
|
|
|
481
|
-
const EMPTY_COMMENT_OPTIONS = {
|
|
482
|
-
options: null,
|
|
483
|
-
errors: null
|
|
484
|
-
};
|
|
485
|
-
|
|
486
547
|
// Byte-level source-cursor scans for computing replacement / strip ranges on raw source after parsing.
|
|
487
548
|
|
|
488
|
-
/**
|
|
489
|
-
* @param {number} cc char code
|
|
490
|
-
* @returns {boolean} true when `cc` is CSS whitespace (space/tab/newline/CR/FF)
|
|
491
|
-
*/
|
|
492
|
-
const isCssWhitespace = (cc) =>
|
|
493
|
-
cc === CC_SPACE ||
|
|
494
|
-
cc === CC_TAB ||
|
|
495
|
-
cc === CC_LINE_FEED ||
|
|
496
|
-
cc === CC_CARRIAGE_RETURN ||
|
|
497
|
-
cc === CC_FORM_FEED;
|
|
498
|
-
|
|
499
549
|
/**
|
|
500
550
|
* Skip trailing whitespace + at most one newline (CRLF-aware).
|
|
501
551
|
* @param {string} input source
|
|
@@ -598,7 +648,7 @@ const scanRuleBody = (declarations, childRules) => {
|
|
|
598
648
|
hasNestedBlock = true;
|
|
599
649
|
if (
|
|
600
650
|
!hasDirectDecl &&
|
|
601
|
-
!isPureBodyAtRule(`@${A.name(child)
|
|
651
|
+
!isPureBodyAtRule(`@${toLowerCaseIfNeeded(A.name(child))}`) &&
|
|
602
652
|
scanRuleBody(atDecls, atChildRules).hasDirectDecl
|
|
603
653
|
) {
|
|
604
654
|
hasDirectDecl = true;
|
|
@@ -815,6 +865,8 @@ class CssParser extends Parser {
|
|
|
815
865
|
animation: true,
|
|
816
866
|
container: true,
|
|
817
867
|
customIdents: true,
|
|
868
|
+
customMedia: true,
|
|
869
|
+
customSelectors: true,
|
|
818
870
|
dashedIdents: true,
|
|
819
871
|
function: true,
|
|
820
872
|
grid: true,
|
|
@@ -884,6 +936,43 @@ class CssParser extends Parser {
|
|
|
884
936
|
/** @type {Comment[]} */
|
|
885
937
|
const comments = [];
|
|
886
938
|
|
|
939
|
+
const urlHints = this.options.urlHints;
|
|
940
|
+
const fontPreload = this.options.fontPreload;
|
|
941
|
+
/**
|
|
942
|
+
* Apply `parser.css.urlHints` defaults + `webpackPrefetch` /
|
|
943
|
+
* `webpackPreload` / `webpackFetchPriority` / `webpackAs` /
|
|
944
|
+
* `webpackType` / `webpackMedia` magic-comment overrides to a fresh
|
|
945
|
+
* `CssUrlDependency`. Magic comments win over the project-wide default.
|
|
946
|
+
* @param {CssUrlDependency} dep dep
|
|
947
|
+
* @param {string} request asset request
|
|
948
|
+
* @param {Record<string, EXPECTED_ANY> | null | undefined} options parsed comment options
|
|
949
|
+
* @param {import("../Dependency").DependencyLocation} loc location for warnings
|
|
950
|
+
* @returns {void}
|
|
951
|
+
*/
|
|
952
|
+
const applyResourceHintDefaults = (dep, request, options, loc) => {
|
|
953
|
+
// `fontPreload` heuristic: seed `preload`/`as`/`type` for the first url
|
|
954
|
+
// of each `@font-face` (the nearest enclosing at-rule) as the lowest
|
|
955
|
+
// default, so `urlHints` rules and magic comments below still override.
|
|
956
|
+
if (fontPreload && atRuleStateStack.length > 0) {
|
|
957
|
+
const top = atRuleStateStack[atRuleStateStack.length - 1];
|
|
958
|
+
if (top.name === "@font-face" && !top.fontPreloaded) {
|
|
959
|
+
top.fontPreloaded = true;
|
|
960
|
+
dep.preload = true;
|
|
961
|
+
dep.asAttribute = "font";
|
|
962
|
+
const type = fontMimeType(request);
|
|
963
|
+
if (type) dep.typeAttribute = type;
|
|
964
|
+
}
|
|
965
|
+
}
|
|
966
|
+
ResourceHintPlugin.applyResourceHints(
|
|
967
|
+
dep,
|
|
968
|
+
urlHints,
|
|
969
|
+
request,
|
|
970
|
+
options,
|
|
971
|
+
state.module,
|
|
972
|
+
loc
|
|
973
|
+
);
|
|
974
|
+
};
|
|
975
|
+
|
|
887
976
|
const module = state.module;
|
|
888
977
|
|
|
889
978
|
// All `:export`-style exports are collected into a single
|
|
@@ -916,6 +1005,8 @@ class CssParser extends Parser {
|
|
|
916
1005
|
exportMode = CssIcssExportDependency.EXPORT_MODE.REPLACE,
|
|
917
1006
|
exportType = CssIcssExportDependency.EXPORT_TYPE.NORMAL
|
|
918
1007
|
) => {
|
|
1008
|
+
// Flat location numbers — the nested loc objects were the parser's
|
|
1009
|
+
// hottest allocation (3 objects per exported name).
|
|
919
1010
|
cssExportEntries.push({
|
|
920
1011
|
name,
|
|
921
1012
|
value,
|
|
@@ -923,7 +1014,10 @@ class CssParser extends Parser {
|
|
|
923
1014
|
interpolate,
|
|
924
1015
|
exportMode,
|
|
925
1016
|
exportType,
|
|
926
|
-
|
|
1017
|
+
locStartLine: sl,
|
|
1018
|
+
locStartColumn: sc,
|
|
1019
|
+
locEndLine: el,
|
|
1020
|
+
locEndColumn: ec
|
|
927
1021
|
});
|
|
928
1022
|
};
|
|
929
1023
|
|
|
@@ -940,12 +1034,29 @@ class CssParser extends Parser {
|
|
|
940
1034
|
|
|
941
1035
|
const isModules = mode === "global" || mode === "local";
|
|
942
1036
|
|
|
1037
|
+
/** @type {Map<string, boolean>} */
|
|
1038
|
+
const selfReferenceCache = new Map();
|
|
1039
|
+
|
|
943
1040
|
/**
|
|
944
1041
|
* Whether a relative `from "<request>"` resolves back to the current module (matching query/fragment too).
|
|
1042
|
+
* Memoized per parse — `composes … from "./x.css"` repeats the same request many times per file.
|
|
945
1043
|
* @param {string} request request string from `from "<request>"`
|
|
946
1044
|
* @returns {boolean} true if request resolves to the current module
|
|
947
1045
|
*/
|
|
948
1046
|
const isSelfReferenceRequest = (request) => {
|
|
1047
|
+
const cached = selfReferenceCache.get(request);
|
|
1048
|
+
if (cached !== undefined) return cached;
|
|
1049
|
+
const result = isSelfReferenceRequestUncached(request);
|
|
1050
|
+
selfReferenceCache.set(request, result);
|
|
1051
|
+
return result;
|
|
1052
|
+
};
|
|
1053
|
+
|
|
1054
|
+
/**
|
|
1055
|
+
* Uncached `isSelfReferenceRequest`.
|
|
1056
|
+
* @param {string} request request string from `from "<request>"`
|
|
1057
|
+
* @returns {boolean} true if request resolves to the current module
|
|
1058
|
+
*/
|
|
1059
|
+
const isSelfReferenceRequestUncached = (request) => {
|
|
949
1060
|
if (!RELATIVE_REQUEST.test(request)) return false;
|
|
950
1061
|
if (!module.context) return false;
|
|
951
1062
|
const parsedRequest = parseResource(request);
|
|
@@ -969,6 +1080,7 @@ class CssParser extends Parser {
|
|
|
969
1080
|
customIdents: this.options.customIdents,
|
|
970
1081
|
grid: this.options.grid
|
|
971
1082
|
});
|
|
1083
|
+
const knownPropertyIndex = getKnownPropertyIndex(knownProperties);
|
|
972
1084
|
|
|
973
1085
|
/** @type {CssModuleBuildMeta} */
|
|
974
1086
|
(module.buildMeta).isCssModule = isModules;
|
|
@@ -1011,14 +1123,21 @@ class CssParser extends Parser {
|
|
|
1011
1123
|
};
|
|
1012
1124
|
|
|
1013
1125
|
/**
|
|
1014
|
-
* Apply the magic comments in `range`: warn on any compilation error, validate `webpackIgnore`, and
|
|
1126
|
+
* 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.
|
|
1015
1127
|
* @param {Range} range byte range to scan for magic comments
|
|
1016
1128
|
* @param {number} warnStart start offset of the loc for an invalid-`webpackIgnore` warning (computed lazily)
|
|
1017
1129
|
* @param {number} warnEnd end offset of that loc
|
|
1018
|
-
* @returns {boolean}
|
|
1130
|
+
* @returns {{ ignored: boolean, options: Record<string, EXPECTED_ANY> | null }} parsed options and `webpackIgnore` flag
|
|
1019
1131
|
*/
|
|
1020
|
-
const
|
|
1021
|
-
|
|
1132
|
+
const magicCommentsIn = (range, warnStart, warnEnd) => {
|
|
1133
|
+
/** @type {{ options: Record<string, EXPECTED_ANY> | null, errors: (Error & { comment: Comment })[] | null }} */
|
|
1134
|
+
const { options, errors } = parseCommentOptionsInRange(
|
|
1135
|
+
/** @type {(Comment & { range: [number, number], value: string })[]} */ (
|
|
1136
|
+
comments
|
|
1137
|
+
),
|
|
1138
|
+
range,
|
|
1139
|
+
this.magicCommentContext
|
|
1140
|
+
);
|
|
1022
1141
|
if (errors) {
|
|
1023
1142
|
for (const e of errors) {
|
|
1024
1143
|
state.module.addWarning(
|
|
@@ -1029,6 +1148,7 @@ class CssParser extends Parser {
|
|
|
1029
1148
|
);
|
|
1030
1149
|
}
|
|
1031
1150
|
}
|
|
1151
|
+
let ignored = false;
|
|
1032
1152
|
if (options && options.webpackIgnore !== undefined) {
|
|
1033
1153
|
if (typeof options.webpackIgnore !== "boolean") {
|
|
1034
1154
|
// Loc is computed lazily here — it's only needed for this rare
|
|
@@ -1040,15 +1160,38 @@ class CssParser extends Parser {
|
|
|
1040
1160
|
)
|
|
1041
1161
|
);
|
|
1042
1162
|
} else {
|
|
1043
|
-
|
|
1163
|
+
ignored = options.webpackIgnore;
|
|
1044
1164
|
}
|
|
1045
1165
|
}
|
|
1046
|
-
return
|
|
1166
|
+
return { ignored, options };
|
|
1047
1167
|
};
|
|
1168
|
+
/**
|
|
1169
|
+
* Backwards-compatible boolean shortcut of {@link magicCommentsIn} for call sites that only need the ignore flag.
|
|
1170
|
+
* @param {Range} range byte range to scan for magic comments
|
|
1171
|
+
* @param {number} warnStart start offset of the loc for an invalid-`webpackIgnore` warning
|
|
1172
|
+
* @param {number} warnEnd end offset of that loc
|
|
1173
|
+
* @returns {boolean} true when `webpackIgnore: true`
|
|
1174
|
+
*/
|
|
1175
|
+
const webpackIgnored = (range, warnStart, warnEnd) =>
|
|
1176
|
+
magicCommentsIn(range, warnStart, warnEnd).ignored;
|
|
1048
1177
|
|
|
1049
1178
|
// Closure-scope alias for `source` used by AST-walking helpers for substring extraction.
|
|
1050
1179
|
const input = source;
|
|
1051
1180
|
|
|
1181
|
+
// `@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.
|
|
1182
|
+
const mayHaveCustomMedia =
|
|
1183
|
+
this.options.customMedia && input.includes("@custom-media");
|
|
1184
|
+
const mayHaveCustomSelectors =
|
|
1185
|
+
this.options.customSelectors && input.includes("@custom-selector");
|
|
1186
|
+
/** @type {Map<string, { text: string, kind: ("condition" | "type" | "unsupported") }> | undefined} */
|
|
1187
|
+
let customMediaDefs;
|
|
1188
|
+
/** @type {{ name: string, start: number, end: number, invalid: boolean, leading: boolean }[] | undefined} */
|
|
1189
|
+
let customMediaUses;
|
|
1190
|
+
/** @type {Map<string, string> | undefined} */
|
|
1191
|
+
let customSelectorDefs;
|
|
1192
|
+
/** @type {{ name: string, start: number, end: number }[] | undefined} */
|
|
1193
|
+
let customSelectorUses;
|
|
1194
|
+
|
|
1052
1195
|
/**
|
|
1053
1196
|
* Unescape a CSS identifier from a source byte range — for value spans not
|
|
1054
1197
|
* backed by a single token (string contents, `--` dashed-ident bodies,
|
|
@@ -1242,18 +1385,17 @@ class CssParser extends Parser {
|
|
|
1242
1385
|
modeData ? modeData === "local" : mode === "local";
|
|
1243
1386
|
|
|
1244
1387
|
/**
|
|
1245
|
-
* Comment
|
|
1246
|
-
* @param {
|
|
1247
|
-
* @param {number} start start
|
|
1248
|
-
* @param {number} end end
|
|
1249
|
-
* @returns {number} end
|
|
1388
|
+
* 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).
|
|
1389
|
+
* @param {import("./syntax").CssPath} path walk path at the comment node
|
|
1250
1390
|
*/
|
|
1251
|
-
const
|
|
1391
|
+
const commentVisitor = (path) => {
|
|
1392
|
+
const node = path.node;
|
|
1393
|
+
const start = A.start(node);
|
|
1394
|
+
const end = A.end(node);
|
|
1252
1395
|
comments.push({
|
|
1253
|
-
value:
|
|
1396
|
+
value: source.slice(start + 2, end - 2),
|
|
1254
1397
|
range: [start, end]
|
|
1255
1398
|
});
|
|
1256
|
-
return end;
|
|
1257
1399
|
};
|
|
1258
1400
|
|
|
1259
1401
|
/**
|
|
@@ -1274,88 +1416,6 @@ class CssParser extends Parser {
|
|
|
1274
1416
|
};
|
|
1275
1417
|
})();
|
|
1276
1418
|
|
|
1277
|
-
const magicCommentContext = this.magicCommentContext;
|
|
1278
|
-
|
|
1279
|
-
/**
|
|
1280
|
-
* Comments fully inside `range`, via binary search over the source-ordered `comments`.
|
|
1281
|
-
* @param {Range} range range
|
|
1282
|
-
* @returns {Comment[]} comments in the range
|
|
1283
|
-
*/
|
|
1284
|
-
const getComments = (range) => {
|
|
1285
|
-
// No comments: skip the binary search + result-array allocation (the common case — most CSS has no magic comments).
|
|
1286
|
-
if (comments.length === 0) return [];
|
|
1287
|
-
const [start, end] = range;
|
|
1288
|
-
let idx = binarySearchBounds.ge(comments, start, compareCommentStart);
|
|
1289
|
-
/** @type {Comment[]} */
|
|
1290
|
-
const commentsInRange = [];
|
|
1291
|
-
while (comments[idx] && comments[idx].range[1] <= end) {
|
|
1292
|
-
commentsInRange.push(comments[idx]);
|
|
1293
|
-
idx++;
|
|
1294
|
-
}
|
|
1295
|
-
return commentsInRange;
|
|
1296
|
-
};
|
|
1297
|
-
|
|
1298
|
-
/**
|
|
1299
|
-
* Parse webpack magic-comment options from any comment inside `range`.
|
|
1300
|
-
* @param {Range} range range of the comment
|
|
1301
|
-
* @returns {{ options: Record<string, EXPECTED_ANY> | null, errors: (Error & { comment: Comment })[] | null }} result
|
|
1302
|
-
*/
|
|
1303
|
-
const parseCommentOptions = (range) => {
|
|
1304
|
-
const found = getComments(range);
|
|
1305
|
-
if (found.length === 0) {
|
|
1306
|
-
return EMPTY_COMMENT_OPTIONS;
|
|
1307
|
-
}
|
|
1308
|
-
/** @type {Record<string, EXPECTED_ANY>} */
|
|
1309
|
-
const options = {};
|
|
1310
|
-
/** @type {(Error & { comment: Comment })[]} */
|
|
1311
|
-
const errors = [];
|
|
1312
|
-
for (const c of found) {
|
|
1313
|
-
const { value } = c;
|
|
1314
|
-
if (value && webpackCommentRegExp.test(value)) {
|
|
1315
|
-
// Fast path for the common `webpackXxx: <bool|number|null>` pair, keeping it out of `vm.runInContext`.
|
|
1316
|
-
const fast = MAGIC_COMMENT_FAST_PATH.exec(value);
|
|
1317
|
-
if (fast !== null) {
|
|
1318
|
-
const key = fast[1];
|
|
1319
|
-
const raw = fast[2];
|
|
1320
|
-
options[key] =
|
|
1321
|
-
raw === "true"
|
|
1322
|
-
? true
|
|
1323
|
-
: raw === "false"
|
|
1324
|
-
? false
|
|
1325
|
-
: raw === "null"
|
|
1326
|
-
? null
|
|
1327
|
-
: Number(raw);
|
|
1328
|
-
continue;
|
|
1329
|
-
}
|
|
1330
|
-
// try compile only if webpack options comment is present
|
|
1331
|
-
try {
|
|
1332
|
-
for (let [key, val] of Object.entries(
|
|
1333
|
-
vm.runInContext(
|
|
1334
|
-
`(function(){return {${value}};})()`,
|
|
1335
|
-
magicCommentContext
|
|
1336
|
-
)
|
|
1337
|
-
)) {
|
|
1338
|
-
if (typeof val === "object" && val !== null) {
|
|
1339
|
-
val =
|
|
1340
|
-
val.constructor.name === "RegExp"
|
|
1341
|
-
? new RegExp(val)
|
|
1342
|
-
: JSON.parse(JSON.stringify(val));
|
|
1343
|
-
}
|
|
1344
|
-
options[key] = val;
|
|
1345
|
-
}
|
|
1346
|
-
} catch (err) {
|
|
1347
|
-
const newErr = new Error(
|
|
1348
|
-
String(/** @type {Error} */ (err).message)
|
|
1349
|
-
);
|
|
1350
|
-
newErr.stack = String(/** @type {Error} */ (err).stack);
|
|
1351
|
-
Object.assign(newErr, { comment: c });
|
|
1352
|
-
errors.push(/** @type {(Error & { comment: Comment })} */ (newErr));
|
|
1353
|
-
}
|
|
1354
|
-
}
|
|
1355
|
-
}
|
|
1356
|
-
return { options, errors };
|
|
1357
|
-
};
|
|
1358
|
-
|
|
1359
1419
|
// CSS modules stuff
|
|
1360
1420
|
|
|
1361
1421
|
/**
|
|
@@ -1516,10 +1576,9 @@ class CssParser extends Parser {
|
|
|
1516
1576
|
for (const cv of A.children(fn)) {
|
|
1517
1577
|
if (A.type(cv) !== NodeType.Ident) continue;
|
|
1518
1578
|
let identifier = A.unescaped(cv);
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
} = A.loc(cv);
|
|
1579
|
+
// Cursor reads instead of `A.loc` — no location objects allocated.
|
|
1580
|
+
const { line: sl, column: sc } = locConverter.get(A.start(cv));
|
|
1581
|
+
const { line: el, column: ec } = locConverter.get(A.end(cv));
|
|
1523
1582
|
const isDashedIdent = isDashedIdentifier(identifier);
|
|
1524
1583
|
if (isDashedIdent) identifier = identifier.slice(2);
|
|
1525
1584
|
addCssExport(
|
|
@@ -1560,10 +1619,8 @@ class CssParser extends Parser {
|
|
|
1560
1619
|
if (cvType === NodeType.String) {
|
|
1561
1620
|
if (!found && options.string) {
|
|
1562
1621
|
const value = A.unescaped(cv);
|
|
1563
|
-
const {
|
|
1564
|
-
|
|
1565
|
-
end: { line: el, column: ec }
|
|
1566
|
-
} = A.loc(cv);
|
|
1622
|
+
const { line: sl, column: sc } = locConverter.get(A.start(cv));
|
|
1623
|
+
const { line: el, column: ec } = locConverter.get(A.end(cv));
|
|
1567
1624
|
addCssExport(
|
|
1568
1625
|
sl,
|
|
1569
1626
|
sc,
|
|
@@ -1590,10 +1647,8 @@ class CssParser extends Parser {
|
|
|
1590
1647
|
) {
|
|
1591
1648
|
continue;
|
|
1592
1649
|
}
|
|
1593
|
-
const {
|
|
1594
|
-
|
|
1595
|
-
end: { line: el, column: ec }
|
|
1596
|
-
} = A.loc(cv);
|
|
1650
|
+
const { line: sl, column: sc } = locConverter.get(A.start(cv));
|
|
1651
|
+
const { line: el, column: ec } = locConverter.get(A.end(cv));
|
|
1597
1652
|
addCssExport(
|
|
1598
1653
|
sl,
|
|
1599
1654
|
sc,
|
|
@@ -1614,9 +1669,12 @@ class CssParser extends Parser {
|
|
|
1614
1669
|
|
|
1615
1670
|
if (cvType === NodeType.Function) {
|
|
1616
1671
|
const fn = /** @type {FunctionNode} */ (cv);
|
|
1617
|
-
const fname = A.unescapedName(fn)
|
|
1618
|
-
const type =
|
|
1619
|
-
|
|
1672
|
+
const fname = A.unescapedName(fn);
|
|
1673
|
+
const type = equalsLowerCase(fname, "local")
|
|
1674
|
+
? 1
|
|
1675
|
+
: equalsLowerCase(fname, "global")
|
|
1676
|
+
? 2
|
|
1677
|
+
: undefined;
|
|
1620
1678
|
if (!found && type) {
|
|
1621
1679
|
found = true;
|
|
1622
1680
|
if (type === 1) pure.markLocal();
|
|
@@ -1626,7 +1684,7 @@ class CssParser extends Parser {
|
|
|
1626
1684
|
if (
|
|
1627
1685
|
this.options.dashedIdents &&
|
|
1628
1686
|
isLocalMode() &&
|
|
1629
|
-
(fname
|
|
1687
|
+
(equalsLowerCase(fname, "var") || equalsLowerCase(fname, "style"))
|
|
1630
1688
|
) {
|
|
1631
1689
|
processDashedIdentInVarFunction(fn);
|
|
1632
1690
|
}
|
|
@@ -1758,7 +1816,7 @@ class CssParser extends Parser {
|
|
|
1758
1816
|
if (
|
|
1759
1817
|
j >= fv.length ||
|
|
1760
1818
|
A.type(fv[j]) !== NodeType.Ident ||
|
|
1761
|
-
!
|
|
1819
|
+
!rangeEqualsLowerCase(input, A.start(fv[j]), A.end(fv[j]), "from")
|
|
1762
1820
|
) {
|
|
1763
1821
|
emitDashedIdentExport(identStart, identEnd);
|
|
1764
1822
|
return;
|
|
@@ -1772,7 +1830,10 @@ class CssParser extends Parser {
|
|
|
1772
1830
|
if (j >= fv.length) return;
|
|
1773
1831
|
|
|
1774
1832
|
const src = fv[j];
|
|
1775
|
-
if (
|
|
1833
|
+
if (
|
|
1834
|
+
A.type(src) === NodeType.Ident &&
|
|
1835
|
+
rangeEquals(input, A.start(src), A.end(src), "global")
|
|
1836
|
+
) {
|
|
1776
1837
|
emitDashedIdentFromGlobal(identEnd, A.end(src));
|
|
1777
1838
|
return;
|
|
1778
1839
|
}
|
|
@@ -1849,7 +1910,7 @@ class CssParser extends Parser {
|
|
|
1849
1910
|
|
|
1850
1911
|
/**
|
|
1851
1912
|
* Per-at-rule scope frames; `exit` reads `hasBlock` to pick the block-cleanup vs `suppressNextRulePrelude` branch.
|
|
1852
|
-
* @type {{ savedAnchor: boolean, savedLocalIdentifiers: string[], name: string, hasBlock: boolean, endsWithSemicolon: boolean }[]}
|
|
1913
|
+
* @type {{ savedAnchor: boolean, savedLocalIdentifiers: string[], name: string, hasBlock: boolean, endsWithSemicolon: boolean, fontPreloaded: boolean }[]}
|
|
1853
1914
|
*/
|
|
1854
1915
|
const atRuleStateStack = [];
|
|
1855
1916
|
|
|
@@ -1901,7 +1962,7 @@ class CssParser extends Parser {
|
|
|
1901
1962
|
if (isLocal) {
|
|
1902
1963
|
while (
|
|
1903
1964
|
trailStart > 0 &&
|
|
1904
|
-
|
|
1965
|
+
isWhitespace(source.charCodeAt(trailStart - 1))
|
|
1905
1966
|
) {
|
|
1906
1967
|
trailStart--;
|
|
1907
1968
|
}
|
|
@@ -1987,6 +2048,37 @@ class CssParser extends Parser {
|
|
|
1987
2048
|
);
|
|
1988
2049
|
};
|
|
1989
2050
|
|
|
2051
|
+
/**
|
|
2052
|
+
* Scope a `::view-transition-*()` pseudo-element's `(<pt-name> .<pt-class>…)` argument: each non-dashed custom-ident (the part name and its classes) is localized like a `view-transition-name` value; `*` and dashed idents are left untouched. Keeps the pseudo consistent with the scoped `view-transition-name`/`-class` declarations.
|
|
2053
|
+
* @param {AstNode[]} cvs the pseudo-element's argument component values
|
|
2054
|
+
* @returns {void}
|
|
2055
|
+
*/
|
|
2056
|
+
const walkViewTransitionPart = (cvs) => {
|
|
2057
|
+
// customIdents off: leave the whole `(…)` untouched (both name and classes), consistent with unscoped `view-transition-name`/`-class` declarations.
|
|
2058
|
+
if (!this.options.customIdents) return;
|
|
2059
|
+
for (const cv of cvs) {
|
|
2060
|
+
if (A.type(cv) !== NodeType.Ident) continue;
|
|
2061
|
+
const name = A.unescaped(cv);
|
|
2062
|
+
if (isDashedIdentifier(name)) continue;
|
|
2063
|
+
const start = A.start(cv);
|
|
2064
|
+
const end = A.end(cv);
|
|
2065
|
+
const { line: sl, column: sc } = locConverter.get(start);
|
|
2066
|
+
const { line: el, column: ec } = locConverter.get(end);
|
|
2067
|
+
addCssExport(
|
|
2068
|
+
sl,
|
|
2069
|
+
sc,
|
|
2070
|
+
el,
|
|
2071
|
+
ec,
|
|
2072
|
+
name,
|
|
2073
|
+
getReexport(name),
|
|
2074
|
+
[start, end],
|
|
2075
|
+
true,
|
|
2076
|
+
CssIcssExportDependency.EXPORT_MODE.ONCE
|
|
2077
|
+
);
|
|
2078
|
+
pure.markLocal();
|
|
2079
|
+
}
|
|
2080
|
+
};
|
|
2081
|
+
|
|
1990
2082
|
/**
|
|
1991
2083
|
* Walk component values as a selector list, emitting ID / attribute deps and recursing into `:not()`/`:is()`/`:local()`/`:global()` wrappers; `localMode` is the sub-tree mode and `topLevel` controls whether commas reset it (only outside parentheses).
|
|
1992
2084
|
* @param {AstNode[]} values component values to walk
|
|
@@ -2070,10 +2162,22 @@ class CssParser extends Parser {
|
|
|
2070
2162
|
}
|
|
2071
2163
|
break;
|
|
2072
2164
|
}
|
|
2073
|
-
case NodeType.Function:
|
|
2165
|
+
case NodeType.Function: {
|
|
2166
|
+
// `::view-transition-group(name .class…)` etc.: scope the part name + classes as custom idents (the `v` char guard skips the slice for the common `:is`/`:not`/… functions).
|
|
2167
|
+
const fnNameStart = A.nameStart(v);
|
|
2168
|
+
if (
|
|
2169
|
+
segmentMode === "local" &&
|
|
2170
|
+
(input.charCodeAt(fnNameStart) === CC_LOWER_V ||
|
|
2171
|
+
input.charCodeAt(fnNameStart) === CC_UPPER_V) &&
|
|
2172
|
+
VIEW_TRANSITION_PART_PSEUDO.test(A.unescapedName(v))
|
|
2173
|
+
) {
|
|
2174
|
+
walkViewTransitionPart(A.children(v));
|
|
2175
|
+
break;
|
|
2176
|
+
}
|
|
2074
2177
|
// Any other function (`:not(…)`, `:is(…)`, …): recurse with the segment mode preserved (only `:local(…)` / `:global(…)`, handled above, switch mode).
|
|
2075
2178
|
walkSelectorList(A.children(v), segmentMode, false);
|
|
2076
2179
|
break;
|
|
2180
|
+
}
|
|
2077
2181
|
case NodeType.Hash: {
|
|
2078
2182
|
if (A.typeFlag(v) !== "id" || segmentMode !== "local") {
|
|
2079
2183
|
break;
|
|
@@ -2081,10 +2185,8 @@ class CssParser extends Parser {
|
|
|
2081
2185
|
// ID selectors emit the ICSS export but aren't a `composes:` anchor.
|
|
2082
2186
|
const idValueStart = A.start(v) + 1;
|
|
2083
2187
|
const idName = A.unescaped(v);
|
|
2084
|
-
const {
|
|
2085
|
-
|
|
2086
|
-
end: { line: idEl, column: idEc }
|
|
2087
|
-
} = A.loc(v);
|
|
2188
|
+
const { line: idSl, column: idSc } = locConverter.get(A.start(v));
|
|
2189
|
+
const { line: idEl, column: idEc } = locConverter.get(A.end(v));
|
|
2088
2190
|
addCssExport(
|
|
2089
2191
|
idSl,
|
|
2090
2192
|
idSc,
|
|
@@ -2124,10 +2226,9 @@ class CssParser extends Parser {
|
|
|
2124
2226
|
if (segmentMode === "local") {
|
|
2125
2227
|
// `.<ident>` in local mode is a class selector (dep covers the ident bytes only).
|
|
2126
2228
|
const name = A.unescaped(next);
|
|
2127
|
-
|
|
2128
|
-
|
|
2129
|
-
|
|
2130
|
-
} = A.loc(next);
|
|
2229
|
+
// Cursor reads instead of `A.loc` — this is the hottest export site.
|
|
2230
|
+
const { line: sl, column: sc } = locConverter.get(A.start(next));
|
|
2231
|
+
const { line: el, column: ec } = locConverter.get(A.end(next));
|
|
2131
2232
|
addCssExport(
|
|
2132
2233
|
sl,
|
|
2133
2234
|
sc,
|
|
@@ -2142,7 +2243,7 @@ class CssParser extends Parser {
|
|
|
2142
2243
|
currentRule.hasLocalAnchor = true;
|
|
2143
2244
|
currentRule.localIdentifiers.push(name);
|
|
2144
2245
|
pure.markLocal();
|
|
2145
|
-
} else {
|
|
2246
|
+
} else if (icssDefinitions.size !== 0) {
|
|
2146
2247
|
// `.<ident>` in global mode: not localized, but the ident may be `@value`-defined and need ICSS rewrite.
|
|
2147
2248
|
const ident = A.value(next);
|
|
2148
2249
|
if (!isDashedIdentifier(ident) && icssDefinitions.has(ident)) {
|
|
@@ -2154,7 +2255,8 @@ class CssParser extends Parser {
|
|
|
2154
2255
|
break;
|
|
2155
2256
|
}
|
|
2156
2257
|
case NodeType.Ident: {
|
|
2157
|
-
// ICSS rewrite for a bare `@value`-defined ident used as a type-style selector.
|
|
2258
|
+
// ICSS rewrite for a bare `@value`-defined ident used as a type-style selector; only worth the slice when definitions exist.
|
|
2259
|
+
if (icssDefinitions.size === 0) break;
|
|
2158
2260
|
const ident = A.value(v);
|
|
2159
2261
|
if (!isDashedIdentifier(ident) && icssDefinitions.has(ident)) {
|
|
2160
2262
|
emitICSSSymbol(ident, A.start(v), A.end(v));
|
|
@@ -2181,10 +2283,8 @@ class CssParser extends Parser {
|
|
|
2181
2283
|
const urlActive = () => {
|
|
2182
2284
|
if (!this.options.url || !currentStructural) return false;
|
|
2183
2285
|
if (A.type(currentStructural) === NodeType.AtRule) {
|
|
2184
|
-
|
|
2185
|
-
|
|
2186
|
-
currentUrlRecovery
|
|
2187
|
-
);
|
|
2286
|
+
// `currentAtRuleName` is cached on at-rule enter — no per-value slice.
|
|
2287
|
+
return currentAtRuleName !== "@import" || currentUrlRecovery;
|
|
2188
2288
|
}
|
|
2189
2289
|
return true;
|
|
2190
2290
|
};
|
|
@@ -2200,6 +2300,8 @@ class CssParser extends Parser {
|
|
|
2200
2300
|
name === "@namespace" ||
|
|
2201
2301
|
name === "@value" ||
|
|
2202
2302
|
name === "@scope" ||
|
|
2303
|
+
(mayHaveCustomMedia && name === "@custom-media") ||
|
|
2304
|
+
(mayHaveCustomSelectors && name === "@custom-selector") ||
|
|
2203
2305
|
(isModules &&
|
|
2204
2306
|
((this.options.animation &&
|
|
2205
2307
|
OPTIONALLY_VENDOR_PREFIXED_KEYFRAMES_AT_RULE.test(name)) ||
|
|
@@ -2231,7 +2333,10 @@ class CssParser extends Parser {
|
|
|
2231
2333
|
const t = A.type(currentStructural);
|
|
2232
2334
|
if (t === NodeType.AtRule) {
|
|
2233
2335
|
return (
|
|
2234
|
-
currentAtRuleName !== "@value" &&
|
|
2336
|
+
currentAtRuleName !== "@value" &&
|
|
2337
|
+
currentAtRuleName !== "@import" &&
|
|
2338
|
+
currentAtRuleName !== "@custom-media" &&
|
|
2339
|
+
currentAtRuleName !== "@custom-selector"
|
|
2235
2340
|
);
|
|
2236
2341
|
}
|
|
2237
2342
|
if (t === NodeType.Declaration) {
|
|
@@ -2524,7 +2629,7 @@ class CssParser extends Parser {
|
|
|
2524
2629
|
let resumeAt = A.end(decl);
|
|
2525
2630
|
if (source.charCodeAt(A.end(decl)) === CC_SEMICOLON) {
|
|
2526
2631
|
resumeAt = A.end(decl) + 1;
|
|
2527
|
-
while (
|
|
2632
|
+
while (isWhitespace(source.charCodeAt(resumeAt))) resumeAt++;
|
|
2528
2633
|
}
|
|
2529
2634
|
module.addPresentationalDependency(
|
|
2530
2635
|
new ConstDependency("", [A.nameStart(decl), resumeAt])
|
|
@@ -2534,23 +2639,20 @@ class CssParser extends Parser {
|
|
|
2534
2639
|
/**
|
|
2535
2640
|
* Emit url() deps for a `url(...)` / `src(...)` / `image-set(...)` value function.
|
|
2536
2641
|
* @param {FunctionNode} fn the function node
|
|
2537
|
-
* @param {string} fname the function name
|
|
2642
|
+
* @param {string} fname the unescaped function name (any case)
|
|
2538
2643
|
*/
|
|
2539
2644
|
const emitUrlFunction = (fn, fname) => {
|
|
2540
|
-
if (fname
|
|
2645
|
+
if (equalsLowerCase(fname, "url") || equalsLowerCase(fname, "src")) {
|
|
2541
2646
|
// Quoted `url("…")` / `src("…")`: first non-whitespace value must be the string token.
|
|
2542
2647
|
const first = A.children(fn)[nextNonWhitespace(A.children(fn), 0)];
|
|
2543
2648
|
if (!first || A.type(first) !== NodeType.String) return;
|
|
2544
2649
|
const string = /** @type {Token} */ (first);
|
|
2545
|
-
|
|
2546
|
-
|
|
2547
|
-
|
|
2548
|
-
|
|
2549
|
-
|
|
2550
|
-
|
|
2551
|
-
) {
|
|
2552
|
-
return;
|
|
2553
|
-
}
|
|
2650
|
+
const { ignored, options } = magicCommentsIn(
|
|
2651
|
+
[lastTokenEndForComments, A.start(fn)],
|
|
2652
|
+
A.start(string),
|
|
2653
|
+
A.end(string)
|
|
2654
|
+
);
|
|
2655
|
+
if (ignored) return;
|
|
2554
2656
|
const value = normalizeUrl(
|
|
2555
2657
|
input.slice(A.start(string) + 1, A.end(string) - 1),
|
|
2556
2658
|
true
|
|
@@ -2563,6 +2665,12 @@ class CssParser extends Parser {
|
|
|
2563
2665
|
"string"
|
|
2564
2666
|
);
|
|
2565
2667
|
setDepLoc(dep, A.start(string), A.end(string));
|
|
2668
|
+
applyResourceHintDefaults(
|
|
2669
|
+
dep,
|
|
2670
|
+
value,
|
|
2671
|
+
options,
|
|
2672
|
+
rangeLoc(A.start(string), A.end(string))
|
|
2673
|
+
);
|
|
2566
2674
|
module.addDependency(dep);
|
|
2567
2675
|
module.addCodeGenerationDependency(dep);
|
|
2568
2676
|
} else if (IMAGE_SET_FUNCTION.test(fname)) {
|
|
@@ -2587,21 +2695,24 @@ class CssParser extends Parser {
|
|
|
2587
2695
|
true
|
|
2588
2696
|
);
|
|
2589
2697
|
if (value.length === 0) continue;
|
|
2590
|
-
|
|
2591
|
-
|
|
2592
|
-
|
|
2593
|
-
|
|
2594
|
-
|
|
2595
|
-
|
|
2596
|
-
) {
|
|
2597
|
-
continue;
|
|
2598
|
-
}
|
|
2698
|
+
const { ignored, options } = magicCommentsIn(
|
|
2699
|
+
[start, A.end(string)],
|
|
2700
|
+
A.start(string),
|
|
2701
|
+
A.end(string)
|
|
2702
|
+
);
|
|
2703
|
+
if (ignored) continue;
|
|
2599
2704
|
const dep = new CssUrlDependency(
|
|
2600
2705
|
value,
|
|
2601
2706
|
[A.start(string), A.end(string)],
|
|
2602
2707
|
"url"
|
|
2603
2708
|
);
|
|
2604
2709
|
setDepLoc(dep, A.start(string), A.end(string));
|
|
2710
|
+
applyResourceHintDefaults(
|
|
2711
|
+
dep,
|
|
2712
|
+
value,
|
|
2713
|
+
options,
|
|
2714
|
+
rangeLoc(A.start(string), A.end(string))
|
|
2715
|
+
);
|
|
2605
2716
|
module.addDependency(dep);
|
|
2606
2717
|
module.addCodeGenerationDependency(dep);
|
|
2607
2718
|
}
|
|
@@ -2925,6 +3036,336 @@ class CssParser extends Parser {
|
|
|
2925
3036
|
module.addPresentationalDependency(dep);
|
|
2926
3037
|
};
|
|
2927
3038
|
|
|
3039
|
+
/**
|
|
3040
|
+
* The lone dashed-ident name of a `( … )` block, or `undefined` — `(--foo)` yields `--foo`, `(--foo: 1px)` / `(min-width: 0)` yield `undefined`. `hasExtra` reports whether any non-whitespace token follows the ident (a boolean-context violation).
|
|
3041
|
+
* @param {SimpleBlock} block parenthesised block node
|
|
3042
|
+
* @param {{ hasExtra: boolean }} out extra-token flag output
|
|
3043
|
+
* @returns {string | undefined} the dashed-ident name, or undefined
|
|
3044
|
+
*/
|
|
3045
|
+
const loneDashedIdentOfBlock = (block, out) => {
|
|
3046
|
+
out.hasExtra = false;
|
|
3047
|
+
let ident;
|
|
3048
|
+
for (const k of A.children(block)) {
|
|
3049
|
+
if (A.type(k) === NodeType.Whitespace) continue;
|
|
3050
|
+
if (ident !== undefined) {
|
|
3051
|
+
out.hasExtra = true;
|
|
3052
|
+
return ident;
|
|
3053
|
+
}
|
|
3054
|
+
if (A.type(k) !== NodeType.Ident) return undefined;
|
|
3055
|
+
const v = A.value(k);
|
|
3056
|
+
if (!isDashedIdentifier(v)) return undefined;
|
|
3057
|
+
ident = v;
|
|
3058
|
+
}
|
|
3059
|
+
return ident;
|
|
3060
|
+
};
|
|
3061
|
+
|
|
3062
|
+
/**
|
|
3063
|
+
* Collect one `@custom-media --name <value>` definition (last-wins) and drop the rule. Classifies the value: `"condition"` (a media-in-parens, or comma list → `(a or b)`) inlines anywhere; `"type"` (a media type like `screen` / `not all` / `screen and (…)`) inlines only at a query's start; `true` / `false` / nested custom-media values are `"unsupported"` (warned on use — the ecosystem disagrees on these).
|
|
3064
|
+
* @param {AtRule} at the `@custom-media` at-rule
|
|
3065
|
+
*/
|
|
3066
|
+
const collectCustomMedia = (at) => {
|
|
3067
|
+
const start = A.start(at);
|
|
3068
|
+
const end = A.end(at);
|
|
3069
|
+
const ruleEnd = input.charCodeAt(end) === CC_SEMICOLON ? end + 1 : end;
|
|
3070
|
+
module.addPresentationalDependency(
|
|
3071
|
+
new ConstDependency("", [start, ruleEnd])
|
|
3072
|
+
);
|
|
3073
|
+
|
|
3074
|
+
const prelude = A.prelude(at);
|
|
3075
|
+
let i = 0;
|
|
3076
|
+
while (i < prelude.length && A.type(prelude[i]) === NodeType.Whitespace) {
|
|
3077
|
+
i++;
|
|
3078
|
+
}
|
|
3079
|
+
if (i >= prelude.length || A.type(prelude[i]) !== NodeType.Ident) return;
|
|
3080
|
+
const name = A.value(prelude[i]);
|
|
3081
|
+
if (!isDashedIdentifier(name)) return;
|
|
3082
|
+
i++;
|
|
3083
|
+
|
|
3084
|
+
/**
|
|
3085
|
+
* @param {string} text resolved text
|
|
3086
|
+
* @param {"condition" | "type" | "unsupported"} kind value kind
|
|
3087
|
+
*/
|
|
3088
|
+
const store = (text, kind) => {
|
|
3089
|
+
(customMediaDefs || (customMediaDefs = new Map())).set(name, {
|
|
3090
|
+
text,
|
|
3091
|
+
kind
|
|
3092
|
+
});
|
|
3093
|
+
};
|
|
3094
|
+
const extra = { hasExtra: false };
|
|
3095
|
+
|
|
3096
|
+
// Value span after the name, trimmed.
|
|
3097
|
+
let vs = i;
|
|
3098
|
+
let ve = prelude.length;
|
|
3099
|
+
while (vs < ve && A.type(prelude[vs]) === NodeType.Whitespace) vs++;
|
|
3100
|
+
while (ve > vs && A.type(prelude[ve - 1]) === NodeType.Whitespace) ve--;
|
|
3101
|
+
if (vs >= ve) {
|
|
3102
|
+
store("", "unsupported");
|
|
3103
|
+
return;
|
|
3104
|
+
}
|
|
3105
|
+
|
|
3106
|
+
let hasComma = false;
|
|
3107
|
+
for (let k = vs; k < ve; k++) {
|
|
3108
|
+
if (A.type(prelude[k]) === NodeType.Comma) {
|
|
3109
|
+
hasComma = true;
|
|
3110
|
+
break;
|
|
3111
|
+
}
|
|
3112
|
+
}
|
|
3113
|
+
|
|
3114
|
+
if (!hasComma) {
|
|
3115
|
+
const first = prelude[vs];
|
|
3116
|
+
const valueText = input.slice(A.start(first), A.end(prelude[ve - 1]));
|
|
3117
|
+
if (A.type(first) === NodeType.SimpleBlock) {
|
|
3118
|
+
// A lone `(--x)` is an unresolved nested custom media; anything else is a media-in-parens.
|
|
3119
|
+
const nested =
|
|
3120
|
+
loneDashedIdentOfBlock(
|
|
3121
|
+
/** @type {SimpleBlock} */ (first),
|
|
3122
|
+
extra
|
|
3123
|
+
) !== undefined;
|
|
3124
|
+
store(nested ? "" : valueText, nested ? "unsupported" : "condition");
|
|
3125
|
+
return;
|
|
3126
|
+
}
|
|
3127
|
+
if (A.type(first) !== NodeType.Ident) {
|
|
3128
|
+
store("", "unsupported");
|
|
3129
|
+
return;
|
|
3130
|
+
}
|
|
3131
|
+
if (
|
|
3132
|
+
rangeEqualsLowerCase(input, A.start(first), A.end(first), "true") ||
|
|
3133
|
+
rangeEqualsLowerCase(input, A.start(first), A.end(first), "false")
|
|
3134
|
+
) {
|
|
3135
|
+
store("", "unsupported");
|
|
3136
|
+
return;
|
|
3137
|
+
}
|
|
3138
|
+
if (rangeEqualsLowerCase(input, A.start(first), A.end(first), "not")) {
|
|
3139
|
+
// `not (…)` is a media condition (wrap so it stays a media-in-parens); `not <type>` is a media type.
|
|
3140
|
+
let j = vs + 1;
|
|
3141
|
+
while (j < ve && A.type(prelude[j]) === NodeType.Whitespace) j++;
|
|
3142
|
+
if (j < ve && A.type(prelude[j]) === NodeType.SimpleBlock) {
|
|
3143
|
+
store(`(${valueText})`, "condition");
|
|
3144
|
+
} else {
|
|
3145
|
+
store(valueText, "type");
|
|
3146
|
+
}
|
|
3147
|
+
return;
|
|
3148
|
+
}
|
|
3149
|
+
// `screen`, `only screen`, `screen and (…)`, …
|
|
3150
|
+
store(valueText, "type");
|
|
3151
|
+
return;
|
|
3152
|
+
}
|
|
3153
|
+
|
|
3154
|
+
// Comma list → `(seg1 or seg2 …)`; supported only when every segment is a media-in-parens.
|
|
3155
|
+
/** @type {string[]} */
|
|
3156
|
+
const segments = [];
|
|
3157
|
+
let ok = true;
|
|
3158
|
+
/** @type {AstNode[]} */
|
|
3159
|
+
let seg = [];
|
|
3160
|
+
const flush = () => {
|
|
3161
|
+
let a = 0;
|
|
3162
|
+
let b = seg.length;
|
|
3163
|
+
while (a < b && A.type(seg[a]) === NodeType.Whitespace) a++;
|
|
3164
|
+
while (b > a && A.type(seg[b - 1]) === NodeType.Whitespace) b--;
|
|
3165
|
+
if (a >= b) {
|
|
3166
|
+
ok = false;
|
|
3167
|
+
return;
|
|
3168
|
+
}
|
|
3169
|
+
const f = seg[a];
|
|
3170
|
+
const text = input.slice(A.start(f), A.end(seg[b - 1]));
|
|
3171
|
+
if (
|
|
3172
|
+
b - a === 1 &&
|
|
3173
|
+
A.type(f) === NodeType.SimpleBlock &&
|
|
3174
|
+
loneDashedIdentOfBlock(/** @type {SimpleBlock} */ (f), extra) ===
|
|
3175
|
+
undefined
|
|
3176
|
+
) {
|
|
3177
|
+
segments.push(text);
|
|
3178
|
+
} else if (
|
|
3179
|
+
A.type(f) === NodeType.Ident &&
|
|
3180
|
+
rangeEqualsLowerCase(input, A.start(f), A.end(f), "not")
|
|
3181
|
+
) {
|
|
3182
|
+
segments.push(`(${text})`);
|
|
3183
|
+
} else {
|
|
3184
|
+
ok = false;
|
|
3185
|
+
}
|
|
3186
|
+
};
|
|
3187
|
+
for (let k = vs; k < ve; k++) {
|
|
3188
|
+
if (A.type(prelude[k]) === NodeType.Comma) {
|
|
3189
|
+
flush();
|
|
3190
|
+
seg = [];
|
|
3191
|
+
} else {
|
|
3192
|
+
seg.push(prelude[k]);
|
|
3193
|
+
}
|
|
3194
|
+
}
|
|
3195
|
+
flush();
|
|
3196
|
+
if (ok && segments.length > 0) {
|
|
3197
|
+
store(
|
|
3198
|
+
segments.length === 1 ? segments[0] : `(${segments.join(" or ")})`,
|
|
3199
|
+
"condition"
|
|
3200
|
+
);
|
|
3201
|
+
} else {
|
|
3202
|
+
store("", "unsupported");
|
|
3203
|
+
}
|
|
3204
|
+
};
|
|
3205
|
+
|
|
3206
|
+
/**
|
|
3207
|
+
* Record every `(--name)` custom-media reference in a media prelude (recursing into nested groups/functions) for post-walk resolution; `invalid` flags a non-boolean-context use like `(--name: 1px)`, `leading` marks a reference that is the first term of a top-level query (the only spot a media-type value may be inlined).
|
|
3208
|
+
* @param {readonly AstNode[]} tokens prelude (or nested block) tokens
|
|
3209
|
+
* @param {boolean} topLevel whether `tokens` is the media prelude itself (vs. a nested group)
|
|
3210
|
+
*/
|
|
3211
|
+
const scanCustomMediaUses = (tokens, topLevel) => {
|
|
3212
|
+
const extra = { hasExtra: false };
|
|
3213
|
+
let leading = true;
|
|
3214
|
+
for (const t of tokens) {
|
|
3215
|
+
const tt = A.type(t);
|
|
3216
|
+
if (tt === NodeType.Whitespace) continue;
|
|
3217
|
+
if (topLevel && tt === NodeType.Comma) {
|
|
3218
|
+
leading = true;
|
|
3219
|
+
continue;
|
|
3220
|
+
}
|
|
3221
|
+
if (tt === NodeType.SimpleBlock) {
|
|
3222
|
+
const name = loneDashedIdentOfBlock(
|
|
3223
|
+
/** @type {SimpleBlock} */ (t),
|
|
3224
|
+
extra
|
|
3225
|
+
);
|
|
3226
|
+
if (name !== undefined) {
|
|
3227
|
+
(customMediaUses || (customMediaUses = [])).push({
|
|
3228
|
+
name,
|
|
3229
|
+
start: A.start(t),
|
|
3230
|
+
end: A.end(t),
|
|
3231
|
+
invalid: extra.hasExtra,
|
|
3232
|
+
leading: Boolean(topLevel && leading)
|
|
3233
|
+
});
|
|
3234
|
+
} else {
|
|
3235
|
+
scanCustomMediaUses(A.children(t), false);
|
|
3236
|
+
}
|
|
3237
|
+
} else if (tt === NodeType.Function) {
|
|
3238
|
+
scanCustomMediaUses(A.children(t), false);
|
|
3239
|
+
}
|
|
3240
|
+
if (topLevel) leading = false;
|
|
3241
|
+
}
|
|
3242
|
+
};
|
|
3243
|
+
|
|
3244
|
+
/**
|
|
3245
|
+
* Collect one `@custom-selector :--name <selector-list>` definition (last-wins) and drop the rule.
|
|
3246
|
+
* @param {AtRule} at the `@custom-selector` at-rule
|
|
3247
|
+
*/
|
|
3248
|
+
const collectCustomSelector = (at) => {
|
|
3249
|
+
const start = A.start(at);
|
|
3250
|
+
const end = A.end(at);
|
|
3251
|
+
const ruleEnd = input.charCodeAt(end) === CC_SEMICOLON ? end + 1 : end;
|
|
3252
|
+
module.addPresentationalDependency(
|
|
3253
|
+
new ConstDependency("", [start, ruleEnd])
|
|
3254
|
+
);
|
|
3255
|
+
|
|
3256
|
+
const prelude = A.prelude(at);
|
|
3257
|
+
let i = 0;
|
|
3258
|
+
while (i < prelude.length && A.type(prelude[i]) === NodeType.Whitespace) {
|
|
3259
|
+
i++;
|
|
3260
|
+
}
|
|
3261
|
+
if (i + 1 >= prelude.length || A.type(prelude[i]) !== NodeType.Colon) {
|
|
3262
|
+
return;
|
|
3263
|
+
}
|
|
3264
|
+
const nameNode = prelude[i + 1];
|
|
3265
|
+
if (
|
|
3266
|
+
A.type(nameNode) !== NodeType.Ident ||
|
|
3267
|
+
A.end(prelude[i]) !== A.start(nameNode)
|
|
3268
|
+
) {
|
|
3269
|
+
return;
|
|
3270
|
+
}
|
|
3271
|
+
const name = A.value(nameNode);
|
|
3272
|
+
if (!isDashedIdentifier(name)) return;
|
|
3273
|
+
const list = input.slice(A.end(nameNode), end).trim();
|
|
3274
|
+
if (list.length === 0) return;
|
|
3275
|
+
(customSelectorDefs || (customSelectorDefs = new Map())).set(name, list);
|
|
3276
|
+
};
|
|
3277
|
+
|
|
3278
|
+
/**
|
|
3279
|
+
* Record every `:--name` custom-selector reference in a selector prelude (recursing into `:is(…)` etc.) for post-walk expansion. A `:--name` is a colon immediately followed by a dashed ident.
|
|
3280
|
+
* @param {readonly AstNode[]} tokens selector prelude (or nested) tokens
|
|
3281
|
+
*/
|
|
3282
|
+
const scanCustomSelectorUses = (tokens) => {
|
|
3283
|
+
for (let k = 0; k < tokens.length; k++) {
|
|
3284
|
+
const t = tokens[k];
|
|
3285
|
+
const tt = A.type(t);
|
|
3286
|
+
if (tt === NodeType.Colon) {
|
|
3287
|
+
const next = tokens[k + 1];
|
|
3288
|
+
if (
|
|
3289
|
+
next &&
|
|
3290
|
+
A.type(next) === NodeType.Ident &&
|
|
3291
|
+
A.end(t) === A.start(next) &&
|
|
3292
|
+
isDashedIdentifier(A.value(next))
|
|
3293
|
+
) {
|
|
3294
|
+
(customSelectorUses || (customSelectorUses = [])).push({
|
|
3295
|
+
name: A.value(next),
|
|
3296
|
+
start: A.start(t),
|
|
3297
|
+
end: A.end(next)
|
|
3298
|
+
});
|
|
3299
|
+
}
|
|
3300
|
+
} else if (tt === NodeType.Function || tt === NodeType.SimpleBlock) {
|
|
3301
|
+
scanCustomSelectorUses(A.children(t));
|
|
3302
|
+
}
|
|
3303
|
+
}
|
|
3304
|
+
};
|
|
3305
|
+
|
|
3306
|
+
/**
|
|
3307
|
+
* Rewrite the collected `@custom-media` / `@custom-selector` uses now that all (possibly later-defined) definitions are known; warns on invalid, unknown, or unsupported custom-media uses.
|
|
3308
|
+
*/
|
|
3309
|
+
const resolveCustomMediaAndSelectors = () => {
|
|
3310
|
+
if (customMediaUses) {
|
|
3311
|
+
for (const use of customMediaUses) {
|
|
3312
|
+
if (use.invalid) {
|
|
3313
|
+
this._emitWarning(
|
|
3314
|
+
state,
|
|
3315
|
+
`Custom media query '${use.name}' must be used in a boolean context`,
|
|
3316
|
+
locConverter,
|
|
3317
|
+
use.start,
|
|
3318
|
+
use.end
|
|
3319
|
+
);
|
|
3320
|
+
continue;
|
|
3321
|
+
}
|
|
3322
|
+
const def = customMediaDefs && customMediaDefs.get(use.name);
|
|
3323
|
+
if (!def) {
|
|
3324
|
+
this._emitWarning(
|
|
3325
|
+
state,
|
|
3326
|
+
`Unknown custom media query '${use.name}'`,
|
|
3327
|
+
locConverter,
|
|
3328
|
+
use.start,
|
|
3329
|
+
use.end
|
|
3330
|
+
);
|
|
3331
|
+
continue;
|
|
3332
|
+
}
|
|
3333
|
+
if (def.kind === "unsupported") {
|
|
3334
|
+
this._emitWarning(
|
|
3335
|
+
state,
|
|
3336
|
+
`Custom media query '${use.name}' uses an unsupported value ('true'/'false' or a nested custom media) and was left unresolved`,
|
|
3337
|
+
locConverter,
|
|
3338
|
+
use.start,
|
|
3339
|
+
use.end
|
|
3340
|
+
);
|
|
3341
|
+
continue;
|
|
3342
|
+
}
|
|
3343
|
+
if (def.kind === "type" && !use.leading) {
|
|
3344
|
+
this._emitWarning(
|
|
3345
|
+
state,
|
|
3346
|
+
`Custom media query '${use.name}' resolves to a media type and can only be used at the start of a media query`,
|
|
3347
|
+
locConverter,
|
|
3348
|
+
use.start,
|
|
3349
|
+
use.end
|
|
3350
|
+
);
|
|
3351
|
+
continue;
|
|
3352
|
+
}
|
|
3353
|
+
module.addPresentationalDependency(
|
|
3354
|
+
new ConstDependency(def.text, [use.start, use.end])
|
|
3355
|
+
);
|
|
3356
|
+
}
|
|
3357
|
+
}
|
|
3358
|
+
if (customSelectorUses) {
|
|
3359
|
+
for (const use of customSelectorUses) {
|
|
3360
|
+
const list = customSelectorDefs && customSelectorDefs.get(use.name);
|
|
3361
|
+
if (list === undefined) continue;
|
|
3362
|
+
module.addPresentationalDependency(
|
|
3363
|
+
new ConstDependency(`:is(${list})`, [use.start, use.end])
|
|
3364
|
+
);
|
|
3365
|
+
}
|
|
3366
|
+
}
|
|
3367
|
+
};
|
|
3368
|
+
|
|
2928
3369
|
/**
|
|
2929
3370
|
* Export the localizable idents / strings in a known property's value (`animation-name: foo`, grid line-names / template-areas, …). Top-level only, except `grid-template` recurses into `repeat(…)` and `[line-name]` blocks.
|
|
2930
3371
|
* @param {Declaration} decl the declaration
|
|
@@ -2935,6 +3376,10 @@ class CssParser extends Parser {
|
|
|
2935
3376
|
/** @type {Record<string, number>} */
|
|
2936
3377
|
let parsedKeywords = Object.create(null);
|
|
2937
3378
|
const isGridProperty = Boolean(declPropertyName.startsWith("grid"));
|
|
3379
|
+
// Only `view-transition-*` legitimately takes a dashed-ident value; other known properties keep the historical plain-ident handling.
|
|
3380
|
+
const isViewTransitionProperty = Boolean(
|
|
3381
|
+
declPropertyName.startsWith("view-transition")
|
|
3382
|
+
);
|
|
2938
3383
|
const isGridTemplate = isGridProperty
|
|
2939
3384
|
? Boolean(
|
|
2940
3385
|
declPropertyName === "grid" ||
|
|
@@ -2994,7 +3439,15 @@ class CssParser extends Parser {
|
|
|
2994
3439
|
break;
|
|
2995
3440
|
}
|
|
2996
3441
|
const identifier = A.value(cv);
|
|
2997
|
-
|
|
3442
|
+
// `view-transition-name: --foo` — a dashed name scopes as a custom property under `dashedIdents`, not as a plain ident export.
|
|
3443
|
+
if (isViewTransitionProperty && isDashedIdentifier(identifier)) {
|
|
3444
|
+
if (this.options.dashedIdents) {
|
|
3445
|
+
emitDashedIdentExport(A.start(cv), A.end(cv));
|
|
3446
|
+
}
|
|
3447
|
+
break;
|
|
3448
|
+
}
|
|
3449
|
+
// Values are almost always lowercase already — avoid the copy.
|
|
3450
|
+
const keyword = toLowerCaseIfNeeded(identifier);
|
|
2998
3451
|
parsedKeywords[keyword] =
|
|
2999
3452
|
typeof parsedKeywords[keyword] !== "undefined"
|
|
3000
3453
|
? parsedKeywords[keyword] + 1
|
|
@@ -3101,11 +3554,13 @@ class CssParser extends Parser {
|
|
|
3101
3554
|
// Drive the walk through SourceProcessor: structural enter / exit map to the `walkAst…Enter` / `…Exit` halves; value visitors handle url / ICSS / local-global.
|
|
3102
3555
|
/** @type {VisitorMap} */
|
|
3103
3556
|
const visitors = {
|
|
3557
|
+
[NodeType.Comment]: commentVisitor,
|
|
3104
3558
|
[NodeType.AtRule]: {
|
|
3105
3559
|
// At-rule enter: scope save, name dispatch, prelude value context, pure-block push.
|
|
3106
|
-
enter: (
|
|
3560
|
+
enter: (path) => {
|
|
3561
|
+
const node = path.node;
|
|
3107
3562
|
const at = /** @type {AtRule} */ (node);
|
|
3108
|
-
const topLevel = parent === null;
|
|
3563
|
+
const topLevel = path.parent === null;
|
|
3109
3564
|
currentUrlRecovery = false;
|
|
3110
3565
|
advanceCommentCursor(A.start(at));
|
|
3111
3566
|
const savedAnchor = currentRule.hasLocalAnchor;
|
|
@@ -3115,7 +3570,7 @@ class CssParser extends Parser {
|
|
|
3115
3570
|
currentRule.localIdentifiers = isModules
|
|
3116
3571
|
? [...savedLocalIdentifiers]
|
|
3117
3572
|
: savedLocalIdentifiers;
|
|
3118
|
-
const name = `@${A.name(at)
|
|
3573
|
+
const name = `@${toLowerCaseIfNeeded(A.name(at))}`;
|
|
3119
3574
|
switch (name) {
|
|
3120
3575
|
case "@namespace": {
|
|
3121
3576
|
this._emitWarning(
|
|
@@ -3150,6 +3605,18 @@ class CssParser extends Parser {
|
|
|
3150
3605
|
handleImportAtRule(at, topLevel);
|
|
3151
3606
|
break;
|
|
3152
3607
|
}
|
|
3608
|
+
case "@custom-media": {
|
|
3609
|
+
if (mayHaveCustomMedia) collectCustomMedia(at);
|
|
3610
|
+
break;
|
|
3611
|
+
}
|
|
3612
|
+
case "@custom-selector": {
|
|
3613
|
+
if (mayHaveCustomSelectors) collectCustomSelector(at);
|
|
3614
|
+
break;
|
|
3615
|
+
}
|
|
3616
|
+
case "@media": {
|
|
3617
|
+
if (mayHaveCustomMedia) scanCustomMediaUses(A.prelude(at), true);
|
|
3618
|
+
break;
|
|
3619
|
+
}
|
|
3153
3620
|
default: {
|
|
3154
3621
|
if (!isModules) break;
|
|
3155
3622
|
if (name === "@value") {
|
|
@@ -3207,7 +3674,8 @@ class CssParser extends Parser {
|
|
|
3207
3674
|
this.options.dashedIdents &&
|
|
3208
3675
|
isModules &&
|
|
3209
3676
|
!isProcessedByLocalAtRule &&
|
|
3210
|
-
effectiveLocalMode
|
|
3677
|
+
effectiveLocalMode &&
|
|
3678
|
+
!(mayHaveCustomMedia && name === "@media")
|
|
3211
3679
|
);
|
|
3212
3680
|
dashed.emit = dashed.active;
|
|
3213
3681
|
|
|
@@ -3260,11 +3728,13 @@ class CssParser extends Parser {
|
|
|
3260
3728
|
savedLocalIdentifiers,
|
|
3261
3729
|
name,
|
|
3262
3730
|
hasBlock: atHasBlock,
|
|
3263
|
-
endsWithSemicolon: source.charCodeAt(A.end(at)) === CC_SEMICOLON
|
|
3731
|
+
endsWithSemicolon: source.charCodeAt(A.end(at)) === CC_SEMICOLON,
|
|
3732
|
+
fontPreloaded: false
|
|
3264
3733
|
});
|
|
3265
3734
|
},
|
|
3266
3735
|
// At-rule exit: pure-frame finalization, `suppressNextRulePrelude`, scope restore, top-level reset.
|
|
3267
|
-
exit: (
|
|
3736
|
+
exit: (path) => {
|
|
3737
|
+
const node = path.node;
|
|
3268
3738
|
const state = atRuleStateStack.pop();
|
|
3269
3739
|
if (!state) return;
|
|
3270
3740
|
if (state.hasBlock) {
|
|
@@ -3279,15 +3749,18 @@ class CssParser extends Parser {
|
|
|
3279
3749
|
}
|
|
3280
3750
|
currentRule.hasLocalAnchor = state.savedAnchor;
|
|
3281
3751
|
currentRule.localIdentifiers = state.savedLocalIdentifiers;
|
|
3282
|
-
if (parent === null) finishTopLevelRule(node, true);
|
|
3752
|
+
if (path.parent === null) finishTopLevelRule(node, true);
|
|
3283
3753
|
}
|
|
3284
3754
|
},
|
|
3285
3755
|
[NodeType.QualifiedRule]: {
|
|
3286
|
-
// Qualified-rule enter: scope setup, selector + prelude context, pure-block push; `:import` / `:export` bail via `
|
|
3287
|
-
enter: (
|
|
3756
|
+
// Qualified-rule enter: scope setup, selector + prelude context, pure-block push; `:import` / `:export` bail via `path.skipChildren()`.
|
|
3757
|
+
enter: (path) => {
|
|
3758
|
+
const node = path.node;
|
|
3288
3759
|
const rule = /** @type {QualifiedRule} */ (node);
|
|
3289
|
-
const topLevel = parent === null;
|
|
3760
|
+
const topLevel = path.parent === null;
|
|
3290
3761
|
advanceCommentCursor(A.start(rule));
|
|
3762
|
+
// `:--name` custom-selector references (dashed idents never collide with the `:import`/`:export` ICSS preludes below).
|
|
3763
|
+
if (mayHaveCustomSelectors) scanCustomSelectorUses(A.prelude(rule));
|
|
3291
3764
|
// `:import(…) { … }` / `:export { … }` ICSS pseudo-rules are processed inline at top level; nested ones bail out.
|
|
3292
3765
|
if (isModules) {
|
|
3293
3766
|
const rulePrelude = A.prelude(rule);
|
|
@@ -3325,7 +3798,7 @@ class CssParser extends Parser {
|
|
|
3325
3798
|
A.setEnd(rule, A.blockEnd(rule));
|
|
3326
3799
|
}
|
|
3327
3800
|
// Don't recurse into the body — handled inline above.
|
|
3328
|
-
|
|
3801
|
+
path.skipChildren();
|
|
3329
3802
|
qualifiedRuleStateStack.push({ bailed: true });
|
|
3330
3803
|
return;
|
|
3331
3804
|
}
|
|
@@ -3400,7 +3873,8 @@ class CssParser extends Parser {
|
|
|
3400
3873
|
});
|
|
3401
3874
|
},
|
|
3402
3875
|
// Qualified-rule exit: pure-frame finalization, scope restore, top-level reset; no-op for bailed ICSS.
|
|
3403
|
-
exit: (
|
|
3876
|
+
exit: (path) => {
|
|
3877
|
+
const node = path.node;
|
|
3404
3878
|
const state = qualifiedRuleStateStack.pop();
|
|
3405
3879
|
if (!state || state.bailed) return;
|
|
3406
3880
|
pure.exitBlock();
|
|
@@ -3408,11 +3882,12 @@ class CssParser extends Parser {
|
|
|
3408
3882
|
currentRule.localIdentifiers = state.savedLocalIdentifiers;
|
|
3409
3883
|
currentRule.composesPrevFile = state.savedPrevComposesFile;
|
|
3410
3884
|
currentRule.composesFiles = state.savedComposesFiles;
|
|
3411
|
-
if (parent === null) finishTopLevelRule(node, false);
|
|
3885
|
+
if (path.parent === null) finishTopLevelRule(node, false);
|
|
3412
3886
|
}
|
|
3413
3887
|
},
|
|
3414
3888
|
// Top-level declarations are parse errors (dropped by `parseAStylesheet`), so a declaration's parent is always a block.
|
|
3415
|
-
[NodeType.Declaration]: (
|
|
3889
|
+
[NodeType.Declaration]: (path) => {
|
|
3890
|
+
const node = path.node;
|
|
3416
3891
|
const decl = /** @type {Declaration} */ (node);
|
|
3417
3892
|
// Reset value-visitor context, read by the value visitors below.
|
|
3418
3893
|
currentStructural = decl;
|
|
@@ -3432,20 +3907,47 @@ class CssParser extends Parser {
|
|
|
3432
3907
|
// Property-name analysis and value exports are CSS-Modules-only; a plain
|
|
3433
3908
|
// stylesheet's declarations need no per-decl name slice / known-property lookup.
|
|
3434
3909
|
if (!isModules) return;
|
|
3435
|
-
const
|
|
3436
|
-
const
|
|
3437
|
-
//
|
|
3438
|
-
|
|
3910
|
+
const nameStart = A.nameStart(decl);
|
|
3911
|
+
const nameEnd = A.nameEnd(decl);
|
|
3912
|
+
// Range-based name analysis — the common declaration never
|
|
3913
|
+
// slices its name out of the source.
|
|
3914
|
+
/** @type {string | undefined} */
|
|
3915
|
+
let declName;
|
|
3916
|
+
/** @type {string | undefined} */
|
|
3917
|
+
let declPropertyName;
|
|
3918
|
+
if (source.charCodeAt(nameStart) === CC_HYPHEN_MINUS) {
|
|
3919
|
+
// Vendor-prefixed or custom property — rare, take the string path.
|
|
3920
|
+
declName = A.name(decl);
|
|
3921
|
+
declPropertyName = toLowerCaseIfNeeded(
|
|
3922
|
+
declName.replace(VENDOR_PREFIX, "")
|
|
3923
|
+
);
|
|
3924
|
+
currentDeclIsKnownProperty = knownProperties.has(declPropertyName);
|
|
3925
|
+
} else {
|
|
3926
|
+
declPropertyName = knownPropertyForRange(
|
|
3927
|
+
knownPropertyIndex,
|
|
3928
|
+
source,
|
|
3929
|
+
nameStart,
|
|
3930
|
+
nameEnd
|
|
3931
|
+
);
|
|
3932
|
+
// Cache the known-property flag so the per-token value visitors don't recompute it.
|
|
3933
|
+
currentDeclIsKnownProperty = declPropertyName !== undefined;
|
|
3934
|
+
}
|
|
3439
3935
|
const effectiveLocalMode = isEffectivelyLocal();
|
|
3440
3936
|
// `composes:` with a local anchor: its strip-dep covers the whole declaration, so suppress the value's local/global/dashed/ICSS rewrites.
|
|
3441
3937
|
currentDeclComposesSkip =
|
|
3442
3938
|
currentRule.hasLocalAnchor &&
|
|
3443
|
-
|
|
3939
|
+
(declPropertyName !== undefined
|
|
3940
|
+
? COMPOSES_PROPERTY.test(declPropertyName)
|
|
3941
|
+
: rangeEqualsLowerCase(source, nameStart, nameEnd, "composes") ||
|
|
3942
|
+
rangeEqualsLowerCase(source, nameStart, nameEnd, "compose-with"));
|
|
3444
3943
|
if (currentDeclComposesSkip) emitComposesWithAnchor(decl);
|
|
3445
3944
|
const skipForComposes = currentDeclComposesSkip;
|
|
3446
3945
|
// Known-property value localization (`animation-name: foo` exports `foo`).
|
|
3447
3946
|
if (effectiveLocalMode && currentDeclIsKnownProperty) {
|
|
3448
|
-
emitKnownPropertyExports(
|
|
3947
|
+
emitKnownPropertyExports(
|
|
3948
|
+
decl,
|
|
3949
|
+
/** @type {string} */ (declPropertyName)
|
|
3950
|
+
);
|
|
3449
3951
|
}
|
|
3450
3952
|
// Dashed-ident (custom-property) export of the property name; the value's dashed idents are scoped by the Ident / Function visitors (top-level only for unknown properties).
|
|
3451
3953
|
if (
|
|
@@ -3453,24 +3955,33 @@ class CssParser extends Parser {
|
|
|
3453
3955
|
effectiveLocalMode &&
|
|
3454
3956
|
!skipForComposes
|
|
3455
3957
|
) {
|
|
3456
|
-
|
|
3457
|
-
|
|
3958
|
+
// Only the `-`-prefixed path can carry a dashed ident.
|
|
3959
|
+
if (declName !== undefined && isDashedIdentifier(declName)) {
|
|
3960
|
+
emitDashedIdentExport(nameStart, nameEnd);
|
|
3458
3961
|
}
|
|
3459
3962
|
dashed.active = true;
|
|
3460
3963
|
dashed.emit = !currentDeclIsKnownProperty;
|
|
3461
3964
|
}
|
|
3462
|
-
// ICSS-symbol rewrite (`color: foo` when `foo` is `@value`-defined), skipping known properties, the composes anchor, and dashed idents (handled above).
|
|
3965
|
+
// ICSS-symbol rewrite (`color: foo` when `foo` is `@value`-defined), skipping known properties, the composes anchor, and dashed idents (handled above). Nothing to rewrite without definitions — don't slice the name.
|
|
3463
3966
|
if (
|
|
3464
3967
|
!skipForComposes &&
|
|
3465
3968
|
!currentDeclIsKnownProperty &&
|
|
3466
|
-
|
|
3467
|
-
icssDefinitions.has(declName)
|
|
3969
|
+
icssDefinitions.size !== 0
|
|
3468
3970
|
) {
|
|
3469
|
-
|
|
3971
|
+
if (declName === undefined) {
|
|
3972
|
+
declName = source.slice(nameStart, nameEnd);
|
|
3973
|
+
}
|
|
3974
|
+
if (
|
|
3975
|
+
!(dashed.active && isDashedIdentifier(declName)) &&
|
|
3976
|
+
icssDefinitions.has(declName)
|
|
3977
|
+
) {
|
|
3978
|
+
emitICSSSymbol(declName, nameStart, nameEnd);
|
|
3979
|
+
}
|
|
3470
3980
|
}
|
|
3471
3981
|
},
|
|
3472
3982
|
// Value-level visitors decide handling from the enclosing node via `urlActive()` / `localGlobalActive()` / `icssActive()`.
|
|
3473
|
-
[NodeType.Url]: (
|
|
3983
|
+
[NodeType.Url]: (path) => {
|
|
3984
|
+
const node = path.node;
|
|
3474
3985
|
const url = /** @type {UrlToken} */ (node);
|
|
3475
3986
|
if (!urlActive()) return;
|
|
3476
3987
|
// Skip bare url-tokens for a known property in CSS-Modules local mode.
|
|
@@ -3483,15 +3994,12 @@ class CssParser extends Parser {
|
|
|
3483
3994
|
) {
|
|
3484
3995
|
return;
|
|
3485
3996
|
}
|
|
3486
|
-
|
|
3487
|
-
|
|
3488
|
-
|
|
3489
|
-
|
|
3490
|
-
|
|
3491
|
-
|
|
3492
|
-
) {
|
|
3493
|
-
return;
|
|
3494
|
-
}
|
|
3997
|
+
const { ignored, options: urlComments } = magicCommentsIn(
|
|
3998
|
+
[lastTokenEndForComments, A.end(node)],
|
|
3999
|
+
lastTokenEndForComments,
|
|
4000
|
+
A.end(node)
|
|
4001
|
+
);
|
|
4002
|
+
if (ignored) return;
|
|
3495
4003
|
let value = normalizeUrl(
|
|
3496
4004
|
input.slice(A.contentStart(url), A.contentEnd(url)),
|
|
3497
4005
|
false
|
|
@@ -3527,28 +4035,36 @@ class CssParser extends Parser {
|
|
|
3527
4035
|
"url"
|
|
3528
4036
|
);
|
|
3529
4037
|
setDepLoc(dep, A.start(node), A.end(node));
|
|
4038
|
+
applyResourceHintDefaults(
|
|
4039
|
+
dep,
|
|
4040
|
+
value,
|
|
4041
|
+
urlComments,
|
|
4042
|
+
rangeLoc(A.start(node), A.end(node))
|
|
4043
|
+
);
|
|
3530
4044
|
module.addDependency(dep);
|
|
3531
4045
|
module.addCodeGenerationDependency(dep);
|
|
3532
4046
|
},
|
|
3533
|
-
[NodeType.Comma](
|
|
4047
|
+
[NodeType.Comma](path) {
|
|
4048
|
+
const node = path.node;
|
|
3534
4049
|
if (urlActive()) lastTokenEndForComments = A.start(node);
|
|
3535
4050
|
},
|
|
3536
4051
|
[NodeType.Function]: {
|
|
3537
|
-
enter: (
|
|
4052
|
+
enter: (path) => {
|
|
4053
|
+
const node = path.node;
|
|
3538
4054
|
const fn = /** @type {FunctionNode} */ (node);
|
|
3539
4055
|
const fnameRaw = A.unescapedName(fn);
|
|
3540
|
-
|
|
3541
|
-
|
|
3542
|
-
|
|
3543
|
-
|
|
3544
|
-
|
|
3545
|
-
) {
|
|
3546
|
-
processLocalOrGlobalFunction(fn,
|
|
4056
|
+
// `equalsLowerCase` keeps this visitor free of a per-function
|
|
4057
|
+
// lowercased copy — functions are the densest value nodes.
|
|
4058
|
+
const isLocalFn = equalsLowerCase(fnameRaw, "local");
|
|
4059
|
+
const isGlobalFn = !isLocalFn && equalsLowerCase(fnameRaw, "global");
|
|
4060
|
+
if (urlActive()) emitUrlFunction(fn, fnameRaw);
|
|
4061
|
+
if (localGlobalActive() && (isLocalFn || isGlobalFn)) {
|
|
4062
|
+
processLocalOrGlobalFunction(fn, isLocalFn ? 1 : 2);
|
|
3547
4063
|
}
|
|
3548
4064
|
if (
|
|
3549
4065
|
icssActive() &&
|
|
3550
|
-
|
|
3551
|
-
|
|
4066
|
+
!isLocalFn &&
|
|
4067
|
+
!isGlobalFn &&
|
|
3552
4068
|
!(dashed.active && isDashedIdentifier(fnameRaw)) &&
|
|
3553
4069
|
icssDefinitions.has(fnameRaw)
|
|
3554
4070
|
) {
|
|
@@ -3557,10 +4073,13 @@ class CssParser extends Parser {
|
|
|
3557
4073
|
// Dashed-ident scoping: handle this function, then set the child nesting level's state for the walk.
|
|
3558
4074
|
dashed.push();
|
|
3559
4075
|
if (dashed.active) {
|
|
3560
|
-
if (
|
|
4076
|
+
if (isLocalFn || isGlobalFn) {
|
|
3561
4077
|
// `local()` / `global()` dashed args go through the ICSS path above, not here.
|
|
3562
4078
|
dashed.active = false;
|
|
3563
|
-
} else if (
|
|
4079
|
+
} else if (
|
|
4080
|
+
equalsLowerCase(fnameRaw, "var") ||
|
|
4081
|
+
equalsLowerCase(fnameRaw, "style")
|
|
4082
|
+
) {
|
|
3564
4083
|
// `var(--foo, …)` / `style(--foo, …)`: emit the first ident; the fallback doesn't self-emit.
|
|
3565
4084
|
processDashedIdentInVarFunction(fn);
|
|
3566
4085
|
dashed.emit = false;
|
|
@@ -3574,34 +4093,46 @@ class CssParser extends Parser {
|
|
|
3574
4093
|
dashed.pop();
|
|
3575
4094
|
}
|
|
3576
4095
|
},
|
|
3577
|
-
[NodeType.Ident](
|
|
4096
|
+
[NodeType.Ident](path) {
|
|
4097
|
+
const node = path.node;
|
|
3578
4098
|
// Fast exit before slicing the ident value: outside dashed-ident scoping
|
|
3579
4099
|
// and ICSS context (any non-CSS-Modules stylesheet) a bare ident carries
|
|
3580
4100
|
// no work, and idents are the most common node, so skipping the per-ident
|
|
3581
|
-
// `value` slice matters.
|
|
4101
|
+
// `value` slice matters. With no `@value`/`:import` definitions the ICSS
|
|
4102
|
+
// probe can never hit, so it doesn't warrant the slice either.
|
|
3582
4103
|
const dashedActive = dashed.active;
|
|
3583
|
-
const icss = icssActive();
|
|
4104
|
+
const icss = icssActive() && icssDefinitions.size !== 0;
|
|
3584
4105
|
if (!dashedActive && !icss) return;
|
|
3585
4106
|
const identValue = A.value(node);
|
|
3586
4107
|
if (dashedActive && isDashedIdentifier(identValue)) {
|
|
3587
4108
|
// Dashed idents are scoped here, never `@value` ICSS-rewritten.
|
|
3588
4109
|
if (!dashed.emit) return;
|
|
3589
4110
|
// Resolve the `--foo from "./x.css"` / `--foo from global` import suffix via sibling lookahead.
|
|
3590
|
-
const siblings = childrenOf(parent);
|
|
4111
|
+
const siblings = childrenOf(path.parent);
|
|
3591
4112
|
const i = siblings ? siblings.indexOf(node) : -1;
|
|
3592
4113
|
if (siblings && i !== -1) {
|
|
3593
4114
|
const j = nextNonWhitespace(siblings, i + 1);
|
|
3594
4115
|
if (
|
|
3595
4116
|
j < siblings.length &&
|
|
3596
4117
|
A.type(siblings[j]) === NodeType.Ident &&
|
|
3597
|
-
|
|
4118
|
+
rangeEqualsLowerCase(
|
|
4119
|
+
input,
|
|
4120
|
+
A.start(siblings[j]),
|
|
4121
|
+
A.end(siblings[j]),
|
|
4122
|
+
"from"
|
|
4123
|
+
)
|
|
3598
4124
|
) {
|
|
3599
4125
|
const fromIdent = siblings[j];
|
|
3600
4126
|
const sourceNode = siblings[nextNonWhitespace(siblings, j + 1)];
|
|
3601
4127
|
if (
|
|
3602
4128
|
sourceNode &&
|
|
3603
4129
|
A.type(sourceNode) === NodeType.Ident &&
|
|
3604
|
-
|
|
4130
|
+
rangeEquals(
|
|
4131
|
+
input,
|
|
4132
|
+
A.start(sourceNode),
|
|
4133
|
+
A.end(sourceNode),
|
|
4134
|
+
"global"
|
|
4135
|
+
)
|
|
3605
4136
|
) {
|
|
3606
4137
|
emitDashedIdentFromGlobal(A.end(node), A.end(sourceNode));
|
|
3607
4138
|
return;
|
|
@@ -3628,14 +4159,24 @@ class CssParser extends Parser {
|
|
|
3628
4159
|
}
|
|
3629
4160
|
};
|
|
3630
4161
|
// `as` selects the top-level production (§5.3): a `style` attribute is a
|
|
3631
|
-
// block's contents, everything else a full stylesheet (the default).
|
|
3632
|
-
|
|
4162
|
+
// block's contents, everything else a full stylesheet (the default). `as`
|
|
4163
|
+
// and `skip` are per-parse config on the processor; only `locConverter`
|
|
4164
|
+
// (per-source) is passed to `process`. One skip set: selector prelude +
|
|
4165
|
+
// unread value leaves (non-modules only; CSS Modules needs its selectors
|
|
4166
|
+
// and ICSS-captured values).
|
|
4167
|
+
new SourceProcessor({
|
|
4168
|
+
as: /** @type {"stylesheet" | "block-contents"} */ (this.options.as),
|
|
4169
|
+
// Non-modules parses skip selector preludes, but `@custom-selector` expansion needs them, so keep them when the file may use one.
|
|
4170
|
+
skip: isModules
|
|
4171
|
+
? undefined
|
|
4172
|
+
: mayHaveCustomSelectors
|
|
4173
|
+
? SKIP_NON_MODULES_KEEP_SELECTORS
|
|
4174
|
+
: SKIP_NON_MODULES
|
|
4175
|
+
})
|
|
3633
4176
|
.use(/** @type {VisitorMap} */ (visitors))
|
|
3634
|
-
.process(source, {
|
|
3635
|
-
|
|
3636
|
-
|
|
3637
|
-
as: /** @type {"stylesheet" | "block-contents"} */ (this.options.as)
|
|
3638
|
-
});
|
|
4177
|
+
.process(source, { locConverter });
|
|
4178
|
+
|
|
4179
|
+
if (customMediaUses || customSelectorUses) resolveCustomMediaAndSelectors();
|
|
3639
4180
|
|
|
3640
4181
|
/** @type {BuildInfo} */
|
|
3641
4182
|
(module.buildInfo).strict = true;
|