webpack 5.108.4 → 5.109.1
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/bin/webpack.js +12 -0
- package/lib/APIPlugin.js +48 -29
- package/lib/CacheFacade.js +8 -0
- package/lib/ChunkGraph.js +17 -15
- package/lib/ChunkGroup.js +1 -0
- package/lib/CompatibilityPlugin.js +62 -29
- package/lib/Compilation.js +58 -6
- package/lib/ConstPlugin.js +11 -11
- package/lib/ContextModule.js +239 -6
- package/lib/ContextModuleFactory.js +139 -14
- package/lib/DefinePlugin.js +433 -112
- package/lib/Entrypoint.js +1 -1
- package/lib/ExportsInfo.js +22 -5
- package/lib/ExportsInfoApiPlugin.js +2 -2
- package/lib/ExternalModule.js +107 -5
- package/lib/ExternalModuleFactoryPlugin.js +20 -1
- package/lib/FileSystemInfo.js +256 -100
- package/lib/FlagDependencyUsagePlugin.js +33 -12
- package/lib/HotModuleReplacementPlugin.js +18 -15
- package/lib/InitFragment.js +20 -32
- 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 +175 -401
- 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 +67 -107
- package/lib/RuntimeTemplate.js +304 -16
- package/lib/Template.js +23 -3
- 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 +219 -29
- package/lib/config/normalization.js +26 -0
- package/lib/config/target.js +9 -1
- package/lib/container/HoistContainerReferencesPlugin.js +47 -55
- package/lib/css/CssGenerator.js +40 -31
- package/lib/css/CssLoadingRuntimeModule.js +5 -1
- package/lib/css/CssModulesPlugin.js +13 -0
- package/lib/css/CssParser.js +729 -313
- package/lib/css/syntax.js +1371 -861
- 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/CommonJsExportRequireDependency.js +19 -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 +162 -71
- 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/HarmonyEvaluatedImportSpecifierDependency.js +2 -1
- package/lib/dependencies/HarmonyExportDependencyParserPlugin.js +7 -19
- package/lib/dependencies/HarmonyExportImportedSpecifierDependency.js +17 -9
- package/lib/dependencies/HarmonyImportDependency.js +13 -2
- package/lib/dependencies/HarmonyImportDependencyParserPlugin.js +11 -11
- package/lib/dependencies/HarmonyImportSpecifierDependency.js +10 -7
- 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 +80 -17
- 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 +51 -12
- package/lib/html/HtmlGenerator.js +907 -15
- package/lib/html/HtmlModule.js +2 -2
- package/lib/html/HtmlModulesPlugin.js +610 -166
- package/lib/html/HtmlParser.js +603 -247
- package/lib/html/favicon.svg +1 -0
- package/lib/html/syntax.js +3451 -2762
- package/lib/ids/IdHelpers.js +25 -11
- 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 +704 -355
- package/lib/javascript/JavascriptParserHelpers.js +3 -6
- package/lib/javascript/syntax.js +5037 -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 +29 -2
- package/lib/node/ReadFileCompileWasmPlugin.js +24 -2
- package/lib/optimize/AggressiveMergingPlugin.js +19 -15
- package/lib/optimize/ConcatenatedModule.js +139 -31
- package/lib/optimize/FlagIncludedChunksPlugin.js +13 -3
- package/lib/optimize/InnerGraphPlugin.js +50 -30
- package/lib/optimize/ModuleConcatenationPlugin.js +139 -24
- package/lib/optimize/SideEffectsFlagPlugin.js +86 -43
- package/lib/optimize/SplitChunksPlugin.js +146 -44
- package/lib/prefetch/ChunkPrefetchPreloadPlugin.js +15 -0
- package/lib/prefetch/ChunkPreloadTriggerRuntimeModule.js +24 -13
- package/lib/prefetch/ResourceHintPlugin.js +687 -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/BinaryMiddleware.js +270 -938
- package/lib/serialization/FileMiddleware.js +29 -3
- package/lib/stats/DefaultStatsFactoryPlugin.js +229 -194
- package/lib/stats/DefaultStatsPresetPlugin.js +2 -0
- package/lib/stats/DefaultStatsPrinterPlugin.js +36 -168
- package/lib/stats/StatsFactory.js +46 -17
- package/lib/url/URLParserPlugin.js +66 -13
- package/lib/util/AsyncQueue.js +122 -72
- package/lib/util/SetHelpers.js +1 -1
- 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 +12 -11
- 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 +1212 -99
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,12 +19,12 @@ 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 {
|
|
@@ -34,6 +33,12 @@ const {
|
|
|
34
33
|
SourceProcessor,
|
|
35
34
|
buildSkipSet,
|
|
36
35
|
equalsLowerCase,
|
|
36
|
+
isDashedIdentifier,
|
|
37
|
+
isWhitespace,
|
|
38
|
+
normalizeUrl,
|
|
39
|
+
rangeEquals,
|
|
40
|
+
rangeEqualsLowerCase,
|
|
41
|
+
toLowerCaseIfNeeded,
|
|
37
42
|
unescapeIdentifier
|
|
38
43
|
} = require("./syntax");
|
|
39
44
|
|
|
@@ -75,6 +80,9 @@ const CC_LINE_FEED = "\n".charCodeAt(0);
|
|
|
75
80
|
const CC_CARRIAGE_RETURN = "\r".charCodeAt(0);
|
|
76
81
|
const CC_FORM_FEED = "\f".charCodeAt(0);
|
|
77
82
|
const CC_LEFT_CURLY = "{".charCodeAt(0);
|
|
83
|
+
const CC_LOWER_V = "v".charCodeAt(0);
|
|
84
|
+
const CC_UPPER_V = "V".charCodeAt(0);
|
|
85
|
+
const CC_REVERSE_SOLIDUS = "\\".charCodeAt(0);
|
|
78
86
|
|
|
79
87
|
// A parsed CSS comment. `loc` is computed on demand — only magic-comment error
|
|
80
88
|
// warnings read it, so comment-heavy CSS skips the per-comment line/col work.
|
|
@@ -82,22 +90,37 @@ const CC_LEFT_CURLY = "{".charCodeAt(0);
|
|
|
82
90
|
/** @typedef {{ value: string, range: Range }} Comment */
|
|
83
91
|
|
|
84
92
|
// Newlines (CSS Syntax 3 §3.3) — listed explicitly since there's no preprocessing stage.
|
|
85
|
-
const STRING_MULTILINE = /\\[\n\r\f]/g;
|
|
86
93
|
// https://www.w3.org/TR/css-syntax-3/#whitespace
|
|
87
|
-
const TRIM_WHITE_SPACES = /(^[ \t\n\r\f]*|[ \t\n\r\f]*$)/g;
|
|
88
94
|
// 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.
|
|
89
95
|
const PURE_IGNORE_RE = /^\s*cssmodules-pure-ignore(?:\s|$)/;
|
|
90
96
|
const PURE_NO_CHECK_RE = /^\s*cssmodules-pure-no-check(?:\s|$)/;
|
|
91
|
-
const UNESCAPE = /\\([0-9a-f]{1,6}[ \t\n\r\f]?|[\s\S])/gi;
|
|
92
97
|
const IMAGE_SET_FUNCTION = /^(?:-\w+-)?image-set$/i;
|
|
93
|
-
// `parseCommentOptions` fast path: a whole-comment `webpackXxx: <bool|number|null>`; every other (and every failing) shape falls back to the `vm.runInContext` path.
|
|
94
|
-
const MAGIC_COMMENT_FAST_PATH =
|
|
95
|
-
/^\s*(webpack[A-Z][A-Za-z]+)\s*:\s*(true|false|null|-?\d+(?:\.\d+)?)\s*$/;
|
|
96
98
|
const OPTIONALLY_VENDOR_PREFIXED_KEYFRAMES_AT_RULE = /^@(?:-\w+-)?keyframes$/;
|
|
97
99
|
const VENDOR_PREFIX = /^-\w+-/;
|
|
98
100
|
const COMPOSES_PROPERTY = /^(?:composes|compose-with)$/i;
|
|
101
|
+
// Functional view-transition pseudo-elements whose `(<name> .class…)` argument names are scoped like `view-transition-name`/`-class` values.
|
|
102
|
+
const VIEW_TRANSITION_PART_PSEUDO =
|
|
103
|
+
/^view-transition-(?:group|image-pair|old|new)$/i;
|
|
99
104
|
const IS_MODULES = /\.modules?\.[^.]+$/i;
|
|
100
105
|
|
|
106
|
+
// `@font-face` src extension → preload `type`; the emission guesses `as="font"`.
|
|
107
|
+
const FONT_MIME_TYPES = new Map([
|
|
108
|
+
["woff2", "font/woff2"],
|
|
109
|
+
["woff", "font/woff"],
|
|
110
|
+
["ttf", "font/ttf"],
|
|
111
|
+
["otf", "font/otf"],
|
|
112
|
+
["eot", "application/vnd.ms-fontobject"]
|
|
113
|
+
]);
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* @param {string} request font url (may carry a query/hash)
|
|
117
|
+
* @returns {string | undefined} preload `type` for a known font extension
|
|
118
|
+
*/
|
|
119
|
+
const fontMimeType = (request) => {
|
|
120
|
+
const match = /\.([a-z0-9]+)(?:[?#]|$)/i.exec(request);
|
|
121
|
+
return match ? FONT_MIME_TYPES.get(match[1].toLowerCase()) : undefined;
|
|
122
|
+
};
|
|
123
|
+
|
|
101
124
|
// Skip options for a non-CSS-Modules parse: drop the selector prelude (never
|
|
102
125
|
// walked without modules) plus value / function-arg leaves nothing reads (the
|
|
103
126
|
// `Ident` visitor no-ops, the `Declaration` visitor returns early, no ICSS).
|
|
@@ -120,6 +143,20 @@ const SKIP_NON_MODULES = {
|
|
|
120
143
|
]),
|
|
121
144
|
selectorPrelude: true
|
|
122
145
|
};
|
|
146
|
+
// Like SKIP_NON_MODULES but keeps selector preludes and the `Colon` / `Ident`
|
|
147
|
+
// tokens inside function-arg lists, so `@custom-selector` `:--name` references
|
|
148
|
+
// (including nested ones like `:is(:--name)`) survive a non-modules parse.
|
|
149
|
+
const SKIP_NON_MODULES_KEEP_SELECTORS = {
|
|
150
|
+
types: buildSkipSet([
|
|
151
|
+
NodeType.Number,
|
|
152
|
+
NodeType.Dimension,
|
|
153
|
+
NodeType.Percentage,
|
|
154
|
+
NodeType.Hash,
|
|
155
|
+
NodeType.Delim,
|
|
156
|
+
NodeType.Whitespace
|
|
157
|
+
]),
|
|
158
|
+
selectorPrelude: false
|
|
159
|
+
};
|
|
123
160
|
const CSS_COMMENT = /\/\*((?!\*\/)[\s\S]*?)\*\//g;
|
|
124
161
|
// `@value` recognizers (postcss-modules-values shape): the import form `<names> from <source>`, and the `<importName> as <localName>` alias inside it.
|
|
125
162
|
const VALUE_IMPORT_FORM = /from(\/\*|\s)(?:[\s\S]+)$/i;
|
|
@@ -149,110 +186,6 @@ const matchAll = (regexp, str) => {
|
|
|
149
186
|
return result;
|
|
150
187
|
};
|
|
151
188
|
|
|
152
|
-
/**
|
|
153
|
-
* Returns normalized url.
|
|
154
|
-
* @param {string} str url string
|
|
155
|
-
* @param {boolean} isString is url wrapped in quotes
|
|
156
|
-
* @returns {string} normalized url
|
|
157
|
-
*/
|
|
158
|
-
const normalizeUrl = (str, isString) => {
|
|
159
|
-
// Fast paths: skip the regex engine for the common URL with no escape and
|
|
160
|
-
// no edge whitespace (e.g. `./img.png`). Each guard is equivalent to the
|
|
161
|
-
// regex being a no-op.
|
|
162
|
-
// Remove escaped newlines from a string-token url like `url("im\<newline>g.png")`.
|
|
163
|
-
if (isString && str.includes("\\")) {
|
|
164
|
-
str = str.replace(STRING_MULTILINE, "");
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
// Remove unnecessary spaces from `url(" img.png ")`
|
|
168
|
-
if (
|
|
169
|
-
str.length !== 0 &&
|
|
170
|
-
(isCssWhitespace(str.charCodeAt(0)) ||
|
|
171
|
-
isCssWhitespace(str.charCodeAt(str.length - 1)))
|
|
172
|
-
) {
|
|
173
|
-
str = str.replace(TRIM_WHITE_SPACES, "");
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
// Unescape
|
|
177
|
-
if (str.includes("\\")) {
|
|
178
|
-
str = str.replace(UNESCAPE, (match) => {
|
|
179
|
-
if (match.length > 2) {
|
|
180
|
-
return String.fromCharCode(Number.parseInt(match.slice(1).trim(), 16));
|
|
181
|
-
}
|
|
182
|
-
return match[1];
|
|
183
|
-
});
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
if (/^data:/i.test(str)) {
|
|
187
|
-
return str;
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
if (str.includes("%")) {
|
|
191
|
-
// Convert `url('%2E/img.png')` -> `url('./img.png')`
|
|
192
|
-
try {
|
|
193
|
-
str = decodeURIComponent(str);
|
|
194
|
-
} catch (_err) {
|
|
195
|
-
// Ignore
|
|
196
|
-
}
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
return str;
|
|
200
|
-
};
|
|
201
|
-
|
|
202
|
-
// `escapeIdentifier` / `unescapeIdentifier` live in ./syntax.js; they're re-exported at the bottom for back-compat callers.
|
|
203
|
-
|
|
204
|
-
/**
|
|
205
|
-
* A custom property name (`<dashed-ident>`): a `--`-prefixed identifier other than bare `--`.
|
|
206
|
-
* @param {string} identifier identifier
|
|
207
|
-
* @returns {boolean} true when identifier is dashed, otherwise false
|
|
208
|
-
*/
|
|
209
|
-
const isDashedIdentifier = (identifier) =>
|
|
210
|
-
identifier.startsWith("--") && identifier.length >= 3;
|
|
211
|
-
|
|
212
|
-
/**
|
|
213
|
-
* `s.toLowerCase()` that returns `s` itself (no allocation) when it can't
|
|
214
|
-
* change — no ASCII uppercase and no non-ASCII (whose Unicode case mapping is
|
|
215
|
-
* left to the real `toLowerCase`).
|
|
216
|
-
* @param {string} s string
|
|
217
|
-
* @returns {string} lowercased string
|
|
218
|
-
*/
|
|
219
|
-
const toLowerCaseIfNeeded = (s) => {
|
|
220
|
-
for (let i = 0; i < s.length; i++) {
|
|
221
|
-
const c = s.charCodeAt(i);
|
|
222
|
-
if ((c >= 65 && c <= 90) || c > 127) return s.toLowerCase();
|
|
223
|
-
}
|
|
224
|
-
return s;
|
|
225
|
-
};
|
|
226
|
-
|
|
227
|
-
/**
|
|
228
|
-
* Case-sensitive equality of a source range against a literal — no slice.
|
|
229
|
-
* @param {string} input source
|
|
230
|
-
* @param {number} start range start
|
|
231
|
-
* @param {number} end range end (exclusive)
|
|
232
|
-
* @param {string} lit literal to match
|
|
233
|
-
* @returns {boolean} true when the range equals `lit`
|
|
234
|
-
*/
|
|
235
|
-
const rangeEquals = (input, start, end, lit) =>
|
|
236
|
-
end - start === lit.length && input.startsWith(lit, start);
|
|
237
|
-
|
|
238
|
-
/**
|
|
239
|
-
* ASCII case-insensitive equality of a source range against a lowercase ASCII literal — no slice.
|
|
240
|
-
* @param {string} input source
|
|
241
|
-
* @param {number} start range start
|
|
242
|
-
* @param {number} end range end (exclusive)
|
|
243
|
-
* @param {string} lit lowercase ASCII literal to match
|
|
244
|
-
* @returns {boolean} true when the range equals `lit` ignoring ASCII case
|
|
245
|
-
*/
|
|
246
|
-
const rangeEqualsLowerCase = (input, start, end, lit) => {
|
|
247
|
-
if (end - start !== lit.length) return false;
|
|
248
|
-
for (let i = 0; i < lit.length; i++) {
|
|
249
|
-
let c = input.charCodeAt(start + i);
|
|
250
|
-
if (c >= 65 && c <= 90) c |= 0x20;
|
|
251
|
-
if (c !== lit.charCodeAt(i)) return false;
|
|
252
|
-
}
|
|
253
|
-
return true;
|
|
254
|
-
};
|
|
255
|
-
|
|
256
189
|
/**
|
|
257
190
|
* Range-keyed index over a known-properties table: ASCII-case-folded 31-hash
|
|
258
191
|
* of the name bytes → canonical key(s). Lets the Declaration visitor answer
|
|
@@ -312,16 +245,6 @@ const knownPropertyForRange = (index, input, start, end) => {
|
|
|
312
245
|
return undefined;
|
|
313
246
|
};
|
|
314
247
|
|
|
315
|
-
/**
|
|
316
|
-
* `binarySearchBounds` comparator for `getComments` — hoisted so the lookup
|
|
317
|
-
* doesn't allocate a fresh closure per call.
|
|
318
|
-
* @param {Comment} comment comment
|
|
319
|
-
* @param {number} needle needle (byte offset)
|
|
320
|
-
* @returns {number} comparison
|
|
321
|
-
*/
|
|
322
|
-
const compareCommentStart = (comment, needle) =>
|
|
323
|
-
/** @type {Range} */ (comment.range)[0] - needle;
|
|
324
|
-
|
|
325
248
|
/** @type {Record<string, number>} */
|
|
326
249
|
const PREDEFINED_COUNTER_STYLES = {
|
|
327
250
|
decimal: 1,
|
|
@@ -561,6 +484,22 @@ const buildKnownProperties = (options = {}) => {
|
|
|
561
484
|
"spell-out": 1,
|
|
562
485
|
...PREDEFINED_COUNTER_STYLES
|
|
563
486
|
});
|
|
487
|
+
knownProperties.set("view-transition-name", {
|
|
488
|
+
none: Infinity,
|
|
489
|
+
auto: Infinity,
|
|
490
|
+
"match-element": Infinity,
|
|
491
|
+
...GLOBAL_VALUES
|
|
492
|
+
});
|
|
493
|
+
knownProperties.set("view-transition-group", {
|
|
494
|
+
normal: Infinity,
|
|
495
|
+
contain: Infinity,
|
|
496
|
+
nearest: Infinity,
|
|
497
|
+
...GLOBAL_VALUES
|
|
498
|
+
});
|
|
499
|
+
knownProperties.set("view-transition-class", {
|
|
500
|
+
none: Infinity,
|
|
501
|
+
...GLOBAL_VALUES
|
|
502
|
+
});
|
|
564
503
|
}
|
|
565
504
|
|
|
566
505
|
if (options.grid) {
|
|
@@ -606,24 +545,8 @@ const getKnownProperties = (options = {}) => {
|
|
|
606
545
|
return table;
|
|
607
546
|
};
|
|
608
547
|
|
|
609
|
-
const EMPTY_COMMENT_OPTIONS = {
|
|
610
|
-
options: null,
|
|
611
|
-
errors: null
|
|
612
|
-
};
|
|
613
|
-
|
|
614
548
|
// Byte-level source-cursor scans for computing replacement / strip ranges on raw source after parsing.
|
|
615
549
|
|
|
616
|
-
/**
|
|
617
|
-
* @param {number} cc char code
|
|
618
|
-
* @returns {boolean} true when `cc` is CSS whitespace (space/tab/newline/CR/FF)
|
|
619
|
-
*/
|
|
620
|
-
const isCssWhitespace = (cc) =>
|
|
621
|
-
cc === CC_SPACE ||
|
|
622
|
-
cc === CC_TAB ||
|
|
623
|
-
cc === CC_LINE_FEED ||
|
|
624
|
-
cc === CC_CARRIAGE_RETURN ||
|
|
625
|
-
cc === CC_FORM_FEED;
|
|
626
|
-
|
|
627
550
|
/**
|
|
628
551
|
* Skip trailing whitespace + at most one newline (CRLF-aware).
|
|
629
552
|
* @param {string} input source
|
|
@@ -673,6 +596,20 @@ const isContainerKeyword = (input, start, end) => {
|
|
|
673
596
|
}
|
|
674
597
|
};
|
|
675
598
|
|
|
599
|
+
/**
|
|
600
|
+
* Whether the byte range contains a CSS escape (`\`) — function names are short, so this scan replaces a per-name slice.
|
|
601
|
+
* @param {string} input source
|
|
602
|
+
* @param {number} start start offset
|
|
603
|
+
* @param {number} end end offset (exclusive)
|
|
604
|
+
* @returns {boolean} true when the range contains a backslash
|
|
605
|
+
*/
|
|
606
|
+
const rangeHasEscape = (input, start, end) => {
|
|
607
|
+
for (let i = start; i < end; i++) {
|
|
608
|
+
if (input.charCodeAt(i) === CC_REVERSE_SOLIDUS) return true;
|
|
609
|
+
}
|
|
610
|
+
return false;
|
|
611
|
+
};
|
|
612
|
+
|
|
676
613
|
/**
|
|
677
614
|
* @param {string} input source
|
|
678
615
|
* @param {number} pos position
|
|
@@ -943,6 +880,8 @@ class CssParser extends Parser {
|
|
|
943
880
|
animation: true,
|
|
944
881
|
container: true,
|
|
945
882
|
customIdents: true,
|
|
883
|
+
customMedia: true,
|
|
884
|
+
customSelectors: true,
|
|
946
885
|
dashedIdents: true,
|
|
947
886
|
function: true,
|
|
948
887
|
grid: true,
|
|
@@ -1012,6 +951,43 @@ class CssParser extends Parser {
|
|
|
1012
951
|
/** @type {Comment[]} */
|
|
1013
952
|
const comments = [];
|
|
1014
953
|
|
|
954
|
+
const urlHints = this.options.urlHints;
|
|
955
|
+
const fontPreload = this.options.fontPreload;
|
|
956
|
+
/**
|
|
957
|
+
* Apply `parser.css.urlHints` defaults + `webpackPrefetch` /
|
|
958
|
+
* `webpackPreload` / `webpackFetchPriority` / `webpackAs` /
|
|
959
|
+
* `webpackType` / `webpackMedia` magic-comment overrides to a fresh
|
|
960
|
+
* `CssUrlDependency`. Magic comments win over the project-wide default.
|
|
961
|
+
* @param {CssUrlDependency} dep dep
|
|
962
|
+
* @param {string} request asset request
|
|
963
|
+
* @param {Record<string, EXPECTED_ANY> | null | undefined} options parsed comment options
|
|
964
|
+
* @param {import("../Dependency").DependencyLocation} loc location for warnings
|
|
965
|
+
* @returns {void}
|
|
966
|
+
*/
|
|
967
|
+
const applyResourceHintDefaults = (dep, request, options, loc) => {
|
|
968
|
+
// `fontPreload` heuristic: seed `preload`/`as`/`type` for the first url
|
|
969
|
+
// of each `@font-face` (the nearest enclosing at-rule) as the lowest
|
|
970
|
+
// default, so `urlHints` rules and magic comments below still override.
|
|
971
|
+
if (fontPreload && atRuleStateStack.length > 0) {
|
|
972
|
+
const top = atRuleStateStack[atRuleStateStack.length - 1];
|
|
973
|
+
if (top.name === "@font-face" && !top.fontPreloaded) {
|
|
974
|
+
top.fontPreloaded = true;
|
|
975
|
+
dep.preload = true;
|
|
976
|
+
dep.asAttribute = "font";
|
|
977
|
+
const type = fontMimeType(request);
|
|
978
|
+
if (type) dep.typeAttribute = type;
|
|
979
|
+
}
|
|
980
|
+
}
|
|
981
|
+
ResourceHintPlugin.applyResourceHints(
|
|
982
|
+
dep,
|
|
983
|
+
urlHints,
|
|
984
|
+
request,
|
|
985
|
+
options,
|
|
986
|
+
state.module,
|
|
987
|
+
loc
|
|
988
|
+
);
|
|
989
|
+
};
|
|
990
|
+
|
|
1015
991
|
const module = state.module;
|
|
1016
992
|
|
|
1017
993
|
// All `:export`-style exports are collected into a single
|
|
@@ -1162,14 +1138,21 @@ class CssParser extends Parser {
|
|
|
1162
1138
|
};
|
|
1163
1139
|
|
|
1164
1140
|
/**
|
|
1165
|
-
* Apply the magic comments in `range`: warn on any compilation error, validate `webpackIgnore`, and
|
|
1141
|
+
* 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.
|
|
1166
1142
|
* @param {Range} range byte range to scan for magic comments
|
|
1167
1143
|
* @param {number} warnStart start offset of the loc for an invalid-`webpackIgnore` warning (computed lazily)
|
|
1168
1144
|
* @param {number} warnEnd end offset of that loc
|
|
1169
|
-
* @returns {boolean}
|
|
1145
|
+
* @returns {{ ignored: boolean, options: Record<string, EXPECTED_ANY> | null }} parsed options and `webpackIgnore` flag
|
|
1170
1146
|
*/
|
|
1171
|
-
const
|
|
1172
|
-
|
|
1147
|
+
const magicCommentsIn = (range, warnStart, warnEnd) => {
|
|
1148
|
+
/** @type {{ options: Record<string, EXPECTED_ANY> | null, errors: (Error & { comment: Comment })[] | null }} */
|
|
1149
|
+
const { options, errors } = parseCommentOptionsInRange(
|
|
1150
|
+
/** @type {(Comment & { range: [number, number], value: string })[]} */ (
|
|
1151
|
+
comments
|
|
1152
|
+
),
|
|
1153
|
+
range,
|
|
1154
|
+
this.magicCommentContext
|
|
1155
|
+
);
|
|
1173
1156
|
if (errors) {
|
|
1174
1157
|
for (const e of errors) {
|
|
1175
1158
|
state.module.addWarning(
|
|
@@ -1180,6 +1163,7 @@ class CssParser extends Parser {
|
|
|
1180
1163
|
);
|
|
1181
1164
|
}
|
|
1182
1165
|
}
|
|
1166
|
+
let ignored = false;
|
|
1183
1167
|
if (options && options.webpackIgnore !== undefined) {
|
|
1184
1168
|
if (typeof options.webpackIgnore !== "boolean") {
|
|
1185
1169
|
// Loc is computed lazily here — it's only needed for this rare
|
|
@@ -1191,15 +1175,38 @@ class CssParser extends Parser {
|
|
|
1191
1175
|
)
|
|
1192
1176
|
);
|
|
1193
1177
|
} else {
|
|
1194
|
-
|
|
1178
|
+
ignored = options.webpackIgnore;
|
|
1195
1179
|
}
|
|
1196
1180
|
}
|
|
1197
|
-
return
|
|
1181
|
+
return { ignored, options };
|
|
1198
1182
|
};
|
|
1183
|
+
/**
|
|
1184
|
+
* Backwards-compatible boolean shortcut of {@link magicCommentsIn} for call sites that only need the ignore flag.
|
|
1185
|
+
* @param {Range} range byte range to scan for magic comments
|
|
1186
|
+
* @param {number} warnStart start offset of the loc for an invalid-`webpackIgnore` warning
|
|
1187
|
+
* @param {number} warnEnd end offset of that loc
|
|
1188
|
+
* @returns {boolean} true when `webpackIgnore: true`
|
|
1189
|
+
*/
|
|
1190
|
+
const webpackIgnored = (range, warnStart, warnEnd) =>
|
|
1191
|
+
magicCommentsIn(range, warnStart, warnEnd).ignored;
|
|
1199
1192
|
|
|
1200
1193
|
// Closure-scope alias for `source` used by AST-walking helpers for substring extraction.
|
|
1201
1194
|
const input = source;
|
|
1202
1195
|
|
|
1196
|
+
// `@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.
|
|
1197
|
+
const mayHaveCustomMedia =
|
|
1198
|
+
this.options.customMedia && input.includes("@custom-media");
|
|
1199
|
+
const mayHaveCustomSelectors =
|
|
1200
|
+
this.options.customSelectors && input.includes("@custom-selector");
|
|
1201
|
+
/** @type {Map<string, { text: string, kind: ("condition" | "type" | "unsupported") }> | undefined} */
|
|
1202
|
+
let customMediaDefs;
|
|
1203
|
+
/** @type {{ name: string, start: number, end: number, invalid: boolean, leading: boolean }[] | undefined} */
|
|
1204
|
+
let customMediaUses;
|
|
1205
|
+
/** @type {Map<string, string> | undefined} */
|
|
1206
|
+
let customSelectorDefs;
|
|
1207
|
+
/** @type {{ name: string, start: number, end: number }[] | undefined} */
|
|
1208
|
+
let customSelectorUses;
|
|
1209
|
+
|
|
1203
1210
|
/**
|
|
1204
1211
|
* Unescape a CSS identifier from a source byte range — for value spans not
|
|
1205
1212
|
* backed by a single token (string contents, `--` dashed-ident bodies,
|
|
@@ -1393,7 +1400,7 @@ class CssParser extends Parser {
|
|
|
1393
1400
|
modeData ? modeData === "local" : mode === "local";
|
|
1394
1401
|
|
|
1395
1402
|
/**
|
|
1396
|
-
* Comment visitor (`NodeType.Comment`): push every comment (in source order) onto the local `comments`, read back by `advanceCommentCursor` (pure-mode flags) and `
|
|
1403
|
+
* 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).
|
|
1397
1404
|
* @param {import("./syntax").CssPath} path walk path at the comment node
|
|
1398
1405
|
*/
|
|
1399
1406
|
const commentVisitor = (path) => {
|
|
@@ -1424,88 +1431,6 @@ class CssParser extends Parser {
|
|
|
1424
1431
|
};
|
|
1425
1432
|
})();
|
|
1426
1433
|
|
|
1427
|
-
const magicCommentContext = this.magicCommentContext;
|
|
1428
|
-
|
|
1429
|
-
/**
|
|
1430
|
-
* Comments fully inside `range`, via binary search over the source-ordered `comments`.
|
|
1431
|
-
* @param {Range} range range
|
|
1432
|
-
* @returns {Comment[]} comments in the range
|
|
1433
|
-
*/
|
|
1434
|
-
const getComments = (range) => {
|
|
1435
|
-
// No comments: skip the binary search + result-array allocation (the common case — most CSS has no magic comments).
|
|
1436
|
-
if (comments.length === 0) return [];
|
|
1437
|
-
const [start, end] = range;
|
|
1438
|
-
let idx = binarySearchBounds.ge(comments, start, compareCommentStart);
|
|
1439
|
-
/** @type {Comment[]} */
|
|
1440
|
-
const commentsInRange = [];
|
|
1441
|
-
while (comments[idx] && comments[idx].range[1] <= end) {
|
|
1442
|
-
commentsInRange.push(comments[idx]);
|
|
1443
|
-
idx++;
|
|
1444
|
-
}
|
|
1445
|
-
return commentsInRange;
|
|
1446
|
-
};
|
|
1447
|
-
|
|
1448
|
-
/**
|
|
1449
|
-
* Parse webpack magic-comment options from any comment inside `range`.
|
|
1450
|
-
* @param {Range} range range of the comment
|
|
1451
|
-
* @returns {{ options: Record<string, EXPECTED_ANY> | null, errors: (Error & { comment: Comment })[] | null }} result
|
|
1452
|
-
*/
|
|
1453
|
-
const parseCommentOptions = (range) => {
|
|
1454
|
-
const found = getComments(range);
|
|
1455
|
-
if (found.length === 0) {
|
|
1456
|
-
return EMPTY_COMMENT_OPTIONS;
|
|
1457
|
-
}
|
|
1458
|
-
/** @type {Record<string, EXPECTED_ANY>} */
|
|
1459
|
-
const options = {};
|
|
1460
|
-
/** @type {(Error & { comment: Comment })[]} */
|
|
1461
|
-
const errors = [];
|
|
1462
|
-
for (const c of found) {
|
|
1463
|
-
const { value } = c;
|
|
1464
|
-
if (value && webpackCommentRegExp.test(value)) {
|
|
1465
|
-
// Fast path for the common `webpackXxx: <bool|number|null>` pair, keeping it out of `vm.runInContext`.
|
|
1466
|
-
const fast = MAGIC_COMMENT_FAST_PATH.exec(value);
|
|
1467
|
-
if (fast !== null) {
|
|
1468
|
-
const key = fast[1];
|
|
1469
|
-
const raw = fast[2];
|
|
1470
|
-
options[key] =
|
|
1471
|
-
raw === "true"
|
|
1472
|
-
? true
|
|
1473
|
-
: raw === "false"
|
|
1474
|
-
? false
|
|
1475
|
-
: raw === "null"
|
|
1476
|
-
? null
|
|
1477
|
-
: Number(raw);
|
|
1478
|
-
continue;
|
|
1479
|
-
}
|
|
1480
|
-
// try compile only if webpack options comment is present
|
|
1481
|
-
try {
|
|
1482
|
-
for (let [key, val] of Object.entries(
|
|
1483
|
-
vm.runInContext(
|
|
1484
|
-
`(function(){return {${value}};})()`,
|
|
1485
|
-
magicCommentContext
|
|
1486
|
-
)
|
|
1487
|
-
)) {
|
|
1488
|
-
if (typeof val === "object" && val !== null) {
|
|
1489
|
-
val =
|
|
1490
|
-
val.constructor.name === "RegExp"
|
|
1491
|
-
? new RegExp(val)
|
|
1492
|
-
: JSON.parse(JSON.stringify(val));
|
|
1493
|
-
}
|
|
1494
|
-
options[key] = val;
|
|
1495
|
-
}
|
|
1496
|
-
} catch (err) {
|
|
1497
|
-
const newErr = new Error(
|
|
1498
|
-
String(/** @type {Error} */ (err).message)
|
|
1499
|
-
);
|
|
1500
|
-
newErr.stack = String(/** @type {Error} */ (err).stack);
|
|
1501
|
-
Object.assign(newErr, { comment: c });
|
|
1502
|
-
errors.push(/** @type {(Error & { comment: Comment })} */ (newErr));
|
|
1503
|
-
}
|
|
1504
|
-
}
|
|
1505
|
-
}
|
|
1506
|
-
return { options, errors };
|
|
1507
|
-
};
|
|
1508
|
-
|
|
1509
1434
|
// CSS modules stuff
|
|
1510
1435
|
|
|
1511
1436
|
/**
|
|
@@ -1938,26 +1863,6 @@ class CssParser extends Parser {
|
|
|
1938
1863
|
}
|
|
1939
1864
|
};
|
|
1940
1865
|
|
|
1941
|
-
/**
|
|
1942
|
-
* The child list of an AST node (a function/block `value` or a rule `prelude`) for sibling lookahead.
|
|
1943
|
-
* @param {AstNode | null} parent parent node
|
|
1944
|
-
* @returns {AstNode[] | undefined} the child list, or undefined
|
|
1945
|
-
*/
|
|
1946
|
-
const childrenOf = (parent) => {
|
|
1947
|
-
if (!parent) return undefined;
|
|
1948
|
-
switch (A.type(parent)) {
|
|
1949
|
-
case NodeType.Function:
|
|
1950
|
-
case NodeType.SimpleBlock:
|
|
1951
|
-
case NodeType.Declaration:
|
|
1952
|
-
return A.children(parent);
|
|
1953
|
-
case NodeType.AtRule:
|
|
1954
|
-
case NodeType.QualifiedRule:
|
|
1955
|
-
return A.prelude(parent);
|
|
1956
|
-
default:
|
|
1957
|
-
return undefined;
|
|
1958
|
-
}
|
|
1959
|
-
};
|
|
1960
|
-
|
|
1961
1866
|
// `allowImport` mirrors `allowImportAtRule`: true until the first top-level block-bearing rule.
|
|
1962
1867
|
let allowImport = true;
|
|
1963
1868
|
// Persistent CSS-Modules mode for a top-level rule: set by bare `:local` / `:global`, leaks into sibling rules, reset at each top-level `}`.
|
|
@@ -2000,7 +1905,7 @@ class CssParser extends Parser {
|
|
|
2000
1905
|
|
|
2001
1906
|
/**
|
|
2002
1907
|
* Per-at-rule scope frames; `exit` reads `hasBlock` to pick the block-cleanup vs `suppressNextRulePrelude` branch.
|
|
2003
|
-
* @type {{ savedAnchor: boolean, savedLocalIdentifiers: string[], name: string, hasBlock: boolean, endsWithSemicolon: boolean }[]}
|
|
1908
|
+
* @type {{ savedAnchor: boolean, savedLocalIdentifiers: string[], name: string, hasBlock: boolean, endsWithSemicolon: boolean, fontPreloaded: boolean }[]}
|
|
2004
1909
|
*/
|
|
2005
1910
|
const atRuleStateStack = [];
|
|
2006
1911
|
|
|
@@ -2052,7 +1957,7 @@ class CssParser extends Parser {
|
|
|
2052
1957
|
if (isLocal) {
|
|
2053
1958
|
while (
|
|
2054
1959
|
trailStart > 0 &&
|
|
2055
|
-
|
|
1960
|
+
isWhitespace(source.charCodeAt(trailStart - 1))
|
|
2056
1961
|
) {
|
|
2057
1962
|
trailStart--;
|
|
2058
1963
|
}
|
|
@@ -2138,6 +2043,37 @@ class CssParser extends Parser {
|
|
|
2138
2043
|
);
|
|
2139
2044
|
};
|
|
2140
2045
|
|
|
2046
|
+
/**
|
|
2047
|
+
* 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.
|
|
2048
|
+
* @param {AstNode[]} cvs the pseudo-element's argument component values
|
|
2049
|
+
* @returns {void}
|
|
2050
|
+
*/
|
|
2051
|
+
const walkViewTransitionPart = (cvs) => {
|
|
2052
|
+
// customIdents off: leave the whole `(…)` untouched (both name and classes), consistent with unscoped `view-transition-name`/`-class` declarations.
|
|
2053
|
+
if (!this.options.customIdents) return;
|
|
2054
|
+
for (const cv of cvs) {
|
|
2055
|
+
if (A.type(cv) !== NodeType.Ident) continue;
|
|
2056
|
+
const name = A.unescaped(cv);
|
|
2057
|
+
if (isDashedIdentifier(name)) continue;
|
|
2058
|
+
const start = A.start(cv);
|
|
2059
|
+
const end = A.end(cv);
|
|
2060
|
+
const { line: sl, column: sc } = locConverter.get(start);
|
|
2061
|
+
const { line: el, column: ec } = locConverter.get(end);
|
|
2062
|
+
addCssExport(
|
|
2063
|
+
sl,
|
|
2064
|
+
sc,
|
|
2065
|
+
el,
|
|
2066
|
+
ec,
|
|
2067
|
+
name,
|
|
2068
|
+
getReexport(name),
|
|
2069
|
+
[start, end],
|
|
2070
|
+
true,
|
|
2071
|
+
CssIcssExportDependency.EXPORT_MODE.ONCE
|
|
2072
|
+
);
|
|
2073
|
+
pure.markLocal();
|
|
2074
|
+
}
|
|
2075
|
+
};
|
|
2076
|
+
|
|
2141
2077
|
/**
|
|
2142
2078
|
* 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).
|
|
2143
2079
|
* @param {AstNode[]} values component values to walk
|
|
@@ -2221,10 +2157,22 @@ class CssParser extends Parser {
|
|
|
2221
2157
|
}
|
|
2222
2158
|
break;
|
|
2223
2159
|
}
|
|
2224
|
-
case NodeType.Function:
|
|
2160
|
+
case NodeType.Function: {
|
|
2161
|
+
// `::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).
|
|
2162
|
+
const fnNameStart = A.nameStart(v);
|
|
2163
|
+
if (
|
|
2164
|
+
segmentMode === "local" &&
|
|
2165
|
+
(input.charCodeAt(fnNameStart) === CC_LOWER_V ||
|
|
2166
|
+
input.charCodeAt(fnNameStart) === CC_UPPER_V) &&
|
|
2167
|
+
VIEW_TRANSITION_PART_PSEUDO.test(A.unescapedName(v))
|
|
2168
|
+
) {
|
|
2169
|
+
walkViewTransitionPart(A.children(v));
|
|
2170
|
+
break;
|
|
2171
|
+
}
|
|
2225
2172
|
// Any other function (`:not(…)`, `:is(…)`, …): recurse with the segment mode preserved (only `:local(…)` / `:global(…)`, handled above, switch mode).
|
|
2226
2173
|
walkSelectorList(A.children(v), segmentMode, false);
|
|
2227
2174
|
break;
|
|
2175
|
+
}
|
|
2228
2176
|
case NodeType.Hash: {
|
|
2229
2177
|
if (A.typeFlag(v) !== "id" || segmentMode !== "local") {
|
|
2230
2178
|
break;
|
|
@@ -2347,6 +2295,8 @@ class CssParser extends Parser {
|
|
|
2347
2295
|
name === "@namespace" ||
|
|
2348
2296
|
name === "@value" ||
|
|
2349
2297
|
name === "@scope" ||
|
|
2298
|
+
(mayHaveCustomMedia && name === "@custom-media") ||
|
|
2299
|
+
(mayHaveCustomSelectors && name === "@custom-selector") ||
|
|
2350
2300
|
(isModules &&
|
|
2351
2301
|
((this.options.animation &&
|
|
2352
2302
|
OPTIONALLY_VENDOR_PREFIXED_KEYFRAMES_AT_RULE.test(name)) ||
|
|
@@ -2378,7 +2328,10 @@ class CssParser extends Parser {
|
|
|
2378
2328
|
const t = A.type(currentStructural);
|
|
2379
2329
|
if (t === NodeType.AtRule) {
|
|
2380
2330
|
return (
|
|
2381
|
-
currentAtRuleName !== "@value" &&
|
|
2331
|
+
currentAtRuleName !== "@value" &&
|
|
2332
|
+
currentAtRuleName !== "@import" &&
|
|
2333
|
+
currentAtRuleName !== "@custom-media" &&
|
|
2334
|
+
currentAtRuleName !== "@custom-selector"
|
|
2382
2335
|
);
|
|
2383
2336
|
}
|
|
2384
2337
|
if (t === NodeType.Declaration) {
|
|
@@ -2671,7 +2624,7 @@ class CssParser extends Parser {
|
|
|
2671
2624
|
let resumeAt = A.end(decl);
|
|
2672
2625
|
if (source.charCodeAt(A.end(decl)) === CC_SEMICOLON) {
|
|
2673
2626
|
resumeAt = A.end(decl) + 1;
|
|
2674
|
-
while (
|
|
2627
|
+
while (isWhitespace(source.charCodeAt(resumeAt))) resumeAt++;
|
|
2675
2628
|
}
|
|
2676
2629
|
module.addPresentationalDependency(
|
|
2677
2630
|
new ConstDependency("", [A.nameStart(decl), resumeAt])
|
|
@@ -2681,23 +2634,49 @@ class CssParser extends Parser {
|
|
|
2681
2634
|
/**
|
|
2682
2635
|
* Emit url() deps for a `url(...)` / `src(...)` / `image-set(...)` value function.
|
|
2683
2636
|
* @param {FunctionNode} fn the function node
|
|
2684
|
-
* @param {string}
|
|
2637
|
+
* @param {string | undefined} escapedName the unescaped function name when it carries an escape, undefined for the raw byte-range path
|
|
2685
2638
|
*/
|
|
2686
|
-
const emitUrlFunction = (fn,
|
|
2687
|
-
|
|
2639
|
+
const emitUrlFunction = (fn, escapedName) => {
|
|
2640
|
+
const fnNameStart = A.nameStart(fn);
|
|
2641
|
+
const fnNameEnd = A.nameEnd(fn);
|
|
2642
|
+
let isUrlOrSrc;
|
|
2643
|
+
let isImageSet = false;
|
|
2644
|
+
if (escapedName !== undefined) {
|
|
2645
|
+
isUrlOrSrc =
|
|
2646
|
+
equalsLowerCase(escapedName, "url") ||
|
|
2647
|
+
equalsLowerCase(escapedName, "src");
|
|
2648
|
+
if (!isUrlOrSrc) isImageSet = IMAGE_SET_FUNCTION.test(escapedName);
|
|
2649
|
+
} else {
|
|
2650
|
+
const nameLength = fnNameEnd - fnNameStart;
|
|
2651
|
+
isUrlOrSrc =
|
|
2652
|
+
nameLength === 3 &&
|
|
2653
|
+
(rangeEqualsLowerCase(input, fnNameStart, fnNameEnd, "url") ||
|
|
2654
|
+
rangeEqualsLowerCase(input, fnNameStart, fnNameEnd, "src"));
|
|
2655
|
+
if (!isUrlOrSrc) {
|
|
2656
|
+
// Suffix probe first, so only a vendor-prefixed `…-image-set` pays the slice + regex.
|
|
2657
|
+
isImageSet =
|
|
2658
|
+
nameLength >= 9 &&
|
|
2659
|
+
rangeEqualsLowerCase(
|
|
2660
|
+
input,
|
|
2661
|
+
fnNameEnd - 9,
|
|
2662
|
+
fnNameEnd,
|
|
2663
|
+
"image-set"
|
|
2664
|
+
) &&
|
|
2665
|
+
(nameLength === 9 ||
|
|
2666
|
+
IMAGE_SET_FUNCTION.test(input.slice(fnNameStart, fnNameEnd)));
|
|
2667
|
+
}
|
|
2668
|
+
}
|
|
2669
|
+
if (isUrlOrSrc) {
|
|
2688
2670
|
// Quoted `url("…")` / `src("…")`: first non-whitespace value must be the string token.
|
|
2689
2671
|
const first = A.children(fn)[nextNonWhitespace(A.children(fn), 0)];
|
|
2690
2672
|
if (!first || A.type(first) !== NodeType.String) return;
|
|
2691
2673
|
const string = /** @type {Token} */ (first);
|
|
2692
|
-
|
|
2693
|
-
|
|
2694
|
-
|
|
2695
|
-
|
|
2696
|
-
|
|
2697
|
-
|
|
2698
|
-
) {
|
|
2699
|
-
return;
|
|
2700
|
-
}
|
|
2674
|
+
const { ignored, options } = magicCommentsIn(
|
|
2675
|
+
[lastTokenEndForComments, A.start(fn)],
|
|
2676
|
+
A.start(string),
|
|
2677
|
+
A.end(string)
|
|
2678
|
+
);
|
|
2679
|
+
if (ignored) return;
|
|
2701
2680
|
const value = normalizeUrl(
|
|
2702
2681
|
input.slice(A.start(string) + 1, A.end(string) - 1),
|
|
2703
2682
|
true
|
|
@@ -2710,11 +2689,17 @@ class CssParser extends Parser {
|
|
|
2710
2689
|
"string"
|
|
2711
2690
|
);
|
|
2712
2691
|
setDepLoc(dep, A.start(string), A.end(string));
|
|
2692
|
+
applyResourceHintDefaults(
|
|
2693
|
+
dep,
|
|
2694
|
+
value,
|
|
2695
|
+
options,
|
|
2696
|
+
rangeLoc(A.start(string), A.end(string))
|
|
2697
|
+
);
|
|
2713
2698
|
module.addDependency(dep);
|
|
2714
2699
|
module.addCodeGenerationDependency(dep);
|
|
2715
|
-
} else if (
|
|
2700
|
+
} else if (isImageSet) {
|
|
2716
2701
|
// `image-set(…)`: each comma segment's first string is the URL; advance the magic-comment fence per string.
|
|
2717
|
-
lastTokenEndForComments =
|
|
2702
|
+
lastTokenEndForComments = fnNameEnd + 1;
|
|
2718
2703
|
let prevStringEnd = A.start(fn);
|
|
2719
2704
|
let firstInSegment = true;
|
|
2720
2705
|
for (const cv of A.children(fn)) {
|
|
@@ -2734,21 +2719,24 @@ class CssParser extends Parser {
|
|
|
2734
2719
|
true
|
|
2735
2720
|
);
|
|
2736
2721
|
if (value.length === 0) continue;
|
|
2737
|
-
|
|
2738
|
-
|
|
2739
|
-
|
|
2740
|
-
|
|
2741
|
-
|
|
2742
|
-
|
|
2743
|
-
) {
|
|
2744
|
-
continue;
|
|
2745
|
-
}
|
|
2722
|
+
const { ignored, options } = magicCommentsIn(
|
|
2723
|
+
[start, A.end(string)],
|
|
2724
|
+
A.start(string),
|
|
2725
|
+
A.end(string)
|
|
2726
|
+
);
|
|
2727
|
+
if (ignored) continue;
|
|
2746
2728
|
const dep = new CssUrlDependency(
|
|
2747
2729
|
value,
|
|
2748
2730
|
[A.start(string), A.end(string)],
|
|
2749
2731
|
"url"
|
|
2750
2732
|
);
|
|
2751
2733
|
setDepLoc(dep, A.start(string), A.end(string));
|
|
2734
|
+
applyResourceHintDefaults(
|
|
2735
|
+
dep,
|
|
2736
|
+
value,
|
|
2737
|
+
options,
|
|
2738
|
+
rangeLoc(A.start(string), A.end(string))
|
|
2739
|
+
);
|
|
2752
2740
|
module.addDependency(dep);
|
|
2753
2741
|
module.addCodeGenerationDependency(dep);
|
|
2754
2742
|
}
|
|
@@ -3072,6 +3060,336 @@ class CssParser extends Parser {
|
|
|
3072
3060
|
module.addPresentationalDependency(dep);
|
|
3073
3061
|
};
|
|
3074
3062
|
|
|
3063
|
+
/**
|
|
3064
|
+
* 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).
|
|
3065
|
+
* @param {SimpleBlock} block parenthesised block node
|
|
3066
|
+
* @param {{ hasExtra: boolean }} out extra-token flag output
|
|
3067
|
+
* @returns {string | undefined} the dashed-ident name, or undefined
|
|
3068
|
+
*/
|
|
3069
|
+
const loneDashedIdentOfBlock = (block, out) => {
|
|
3070
|
+
out.hasExtra = false;
|
|
3071
|
+
let ident;
|
|
3072
|
+
for (const k of A.children(block)) {
|
|
3073
|
+
if (A.type(k) === NodeType.Whitespace) continue;
|
|
3074
|
+
if (ident !== undefined) {
|
|
3075
|
+
out.hasExtra = true;
|
|
3076
|
+
return ident;
|
|
3077
|
+
}
|
|
3078
|
+
if (A.type(k) !== NodeType.Ident) return undefined;
|
|
3079
|
+
const v = A.value(k);
|
|
3080
|
+
if (!isDashedIdentifier(v)) return undefined;
|
|
3081
|
+
ident = v;
|
|
3082
|
+
}
|
|
3083
|
+
return ident;
|
|
3084
|
+
};
|
|
3085
|
+
|
|
3086
|
+
/**
|
|
3087
|
+
* 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).
|
|
3088
|
+
* @param {AtRule} at the `@custom-media` at-rule
|
|
3089
|
+
*/
|
|
3090
|
+
const collectCustomMedia = (at) => {
|
|
3091
|
+
const start = A.start(at);
|
|
3092
|
+
const end = A.end(at);
|
|
3093
|
+
const ruleEnd = input.charCodeAt(end) === CC_SEMICOLON ? end + 1 : end;
|
|
3094
|
+
module.addPresentationalDependency(
|
|
3095
|
+
new ConstDependency("", [start, ruleEnd])
|
|
3096
|
+
);
|
|
3097
|
+
|
|
3098
|
+
const prelude = A.prelude(at);
|
|
3099
|
+
let i = 0;
|
|
3100
|
+
while (i < prelude.length && A.type(prelude[i]) === NodeType.Whitespace) {
|
|
3101
|
+
i++;
|
|
3102
|
+
}
|
|
3103
|
+
if (i >= prelude.length || A.type(prelude[i]) !== NodeType.Ident) return;
|
|
3104
|
+
const name = A.value(prelude[i]);
|
|
3105
|
+
if (!isDashedIdentifier(name)) return;
|
|
3106
|
+
i++;
|
|
3107
|
+
|
|
3108
|
+
/**
|
|
3109
|
+
* @param {string} text resolved text
|
|
3110
|
+
* @param {"condition" | "type" | "unsupported"} kind value kind
|
|
3111
|
+
*/
|
|
3112
|
+
const store = (text, kind) => {
|
|
3113
|
+
(customMediaDefs || (customMediaDefs = new Map())).set(name, {
|
|
3114
|
+
text,
|
|
3115
|
+
kind
|
|
3116
|
+
});
|
|
3117
|
+
};
|
|
3118
|
+
const extra = { hasExtra: false };
|
|
3119
|
+
|
|
3120
|
+
// Value span after the name, trimmed.
|
|
3121
|
+
let vs = i;
|
|
3122
|
+
let ve = prelude.length;
|
|
3123
|
+
while (vs < ve && A.type(prelude[vs]) === NodeType.Whitespace) vs++;
|
|
3124
|
+
while (ve > vs && A.type(prelude[ve - 1]) === NodeType.Whitespace) ve--;
|
|
3125
|
+
if (vs >= ve) {
|
|
3126
|
+
store("", "unsupported");
|
|
3127
|
+
return;
|
|
3128
|
+
}
|
|
3129
|
+
|
|
3130
|
+
let hasComma = false;
|
|
3131
|
+
for (let k = vs; k < ve; k++) {
|
|
3132
|
+
if (A.type(prelude[k]) === NodeType.Comma) {
|
|
3133
|
+
hasComma = true;
|
|
3134
|
+
break;
|
|
3135
|
+
}
|
|
3136
|
+
}
|
|
3137
|
+
|
|
3138
|
+
if (!hasComma) {
|
|
3139
|
+
const first = prelude[vs];
|
|
3140
|
+
const valueText = input.slice(A.start(first), A.end(prelude[ve - 1]));
|
|
3141
|
+
if (A.type(first) === NodeType.SimpleBlock) {
|
|
3142
|
+
// A lone `(--x)` is an unresolved nested custom media; anything else is a media-in-parens.
|
|
3143
|
+
const nested =
|
|
3144
|
+
loneDashedIdentOfBlock(
|
|
3145
|
+
/** @type {SimpleBlock} */ (first),
|
|
3146
|
+
extra
|
|
3147
|
+
) !== undefined;
|
|
3148
|
+
store(nested ? "" : valueText, nested ? "unsupported" : "condition");
|
|
3149
|
+
return;
|
|
3150
|
+
}
|
|
3151
|
+
if (A.type(first) !== NodeType.Ident) {
|
|
3152
|
+
store("", "unsupported");
|
|
3153
|
+
return;
|
|
3154
|
+
}
|
|
3155
|
+
if (
|
|
3156
|
+
rangeEqualsLowerCase(input, A.start(first), A.end(first), "true") ||
|
|
3157
|
+
rangeEqualsLowerCase(input, A.start(first), A.end(first), "false")
|
|
3158
|
+
) {
|
|
3159
|
+
store("", "unsupported");
|
|
3160
|
+
return;
|
|
3161
|
+
}
|
|
3162
|
+
if (rangeEqualsLowerCase(input, A.start(first), A.end(first), "not")) {
|
|
3163
|
+
// `not (…)` is a media condition (wrap so it stays a media-in-parens); `not <type>` is a media type.
|
|
3164
|
+
let j = vs + 1;
|
|
3165
|
+
while (j < ve && A.type(prelude[j]) === NodeType.Whitespace) j++;
|
|
3166
|
+
if (j < ve && A.type(prelude[j]) === NodeType.SimpleBlock) {
|
|
3167
|
+
store(`(${valueText})`, "condition");
|
|
3168
|
+
} else {
|
|
3169
|
+
store(valueText, "type");
|
|
3170
|
+
}
|
|
3171
|
+
return;
|
|
3172
|
+
}
|
|
3173
|
+
// `screen`, `only screen`, `screen and (…)`, …
|
|
3174
|
+
store(valueText, "type");
|
|
3175
|
+
return;
|
|
3176
|
+
}
|
|
3177
|
+
|
|
3178
|
+
// Comma list → `(seg1 or seg2 …)`; supported only when every segment is a media-in-parens.
|
|
3179
|
+
/** @type {string[]} */
|
|
3180
|
+
const segments = [];
|
|
3181
|
+
let ok = true;
|
|
3182
|
+
/** @type {AstNode[]} */
|
|
3183
|
+
let seg = [];
|
|
3184
|
+
const flush = () => {
|
|
3185
|
+
let a = 0;
|
|
3186
|
+
let b = seg.length;
|
|
3187
|
+
while (a < b && A.type(seg[a]) === NodeType.Whitespace) a++;
|
|
3188
|
+
while (b > a && A.type(seg[b - 1]) === NodeType.Whitespace) b--;
|
|
3189
|
+
if (a >= b) {
|
|
3190
|
+
ok = false;
|
|
3191
|
+
return;
|
|
3192
|
+
}
|
|
3193
|
+
const f = seg[a];
|
|
3194
|
+
const text = input.slice(A.start(f), A.end(seg[b - 1]));
|
|
3195
|
+
if (
|
|
3196
|
+
b - a === 1 &&
|
|
3197
|
+
A.type(f) === NodeType.SimpleBlock &&
|
|
3198
|
+
loneDashedIdentOfBlock(/** @type {SimpleBlock} */ (f), extra) ===
|
|
3199
|
+
undefined
|
|
3200
|
+
) {
|
|
3201
|
+
segments.push(text);
|
|
3202
|
+
} else if (
|
|
3203
|
+
A.type(f) === NodeType.Ident &&
|
|
3204
|
+
rangeEqualsLowerCase(input, A.start(f), A.end(f), "not")
|
|
3205
|
+
) {
|
|
3206
|
+
segments.push(`(${text})`);
|
|
3207
|
+
} else {
|
|
3208
|
+
ok = false;
|
|
3209
|
+
}
|
|
3210
|
+
};
|
|
3211
|
+
for (let k = vs; k < ve; k++) {
|
|
3212
|
+
if (A.type(prelude[k]) === NodeType.Comma) {
|
|
3213
|
+
flush();
|
|
3214
|
+
seg = [];
|
|
3215
|
+
} else {
|
|
3216
|
+
seg.push(prelude[k]);
|
|
3217
|
+
}
|
|
3218
|
+
}
|
|
3219
|
+
flush();
|
|
3220
|
+
if (ok && segments.length > 0) {
|
|
3221
|
+
store(
|
|
3222
|
+
segments.length === 1 ? segments[0] : `(${segments.join(" or ")})`,
|
|
3223
|
+
"condition"
|
|
3224
|
+
);
|
|
3225
|
+
} else {
|
|
3226
|
+
store("", "unsupported");
|
|
3227
|
+
}
|
|
3228
|
+
};
|
|
3229
|
+
|
|
3230
|
+
/**
|
|
3231
|
+
* 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).
|
|
3232
|
+
* @param {readonly AstNode[]} tokens prelude (or nested block) tokens
|
|
3233
|
+
* @param {boolean} topLevel whether `tokens` is the media prelude itself (vs. a nested group)
|
|
3234
|
+
*/
|
|
3235
|
+
const scanCustomMediaUses = (tokens, topLevel) => {
|
|
3236
|
+
const extra = { hasExtra: false };
|
|
3237
|
+
let leading = true;
|
|
3238
|
+
for (const t of tokens) {
|
|
3239
|
+
const tt = A.type(t);
|
|
3240
|
+
if (tt === NodeType.Whitespace) continue;
|
|
3241
|
+
if (topLevel && tt === NodeType.Comma) {
|
|
3242
|
+
leading = true;
|
|
3243
|
+
continue;
|
|
3244
|
+
}
|
|
3245
|
+
if (tt === NodeType.SimpleBlock) {
|
|
3246
|
+
const name = loneDashedIdentOfBlock(
|
|
3247
|
+
/** @type {SimpleBlock} */ (t),
|
|
3248
|
+
extra
|
|
3249
|
+
);
|
|
3250
|
+
if (name !== undefined) {
|
|
3251
|
+
(customMediaUses || (customMediaUses = [])).push({
|
|
3252
|
+
name,
|
|
3253
|
+
start: A.start(t),
|
|
3254
|
+
end: A.end(t),
|
|
3255
|
+
invalid: extra.hasExtra,
|
|
3256
|
+
leading: Boolean(topLevel && leading)
|
|
3257
|
+
});
|
|
3258
|
+
} else {
|
|
3259
|
+
scanCustomMediaUses(A.children(t), false);
|
|
3260
|
+
}
|
|
3261
|
+
} else if (tt === NodeType.Function) {
|
|
3262
|
+
scanCustomMediaUses(A.children(t), false);
|
|
3263
|
+
}
|
|
3264
|
+
if (topLevel) leading = false;
|
|
3265
|
+
}
|
|
3266
|
+
};
|
|
3267
|
+
|
|
3268
|
+
/**
|
|
3269
|
+
* Collect one `@custom-selector :--name <selector-list>` definition (last-wins) and drop the rule.
|
|
3270
|
+
* @param {AtRule} at the `@custom-selector` at-rule
|
|
3271
|
+
*/
|
|
3272
|
+
const collectCustomSelector = (at) => {
|
|
3273
|
+
const start = A.start(at);
|
|
3274
|
+
const end = A.end(at);
|
|
3275
|
+
const ruleEnd = input.charCodeAt(end) === CC_SEMICOLON ? end + 1 : end;
|
|
3276
|
+
module.addPresentationalDependency(
|
|
3277
|
+
new ConstDependency("", [start, ruleEnd])
|
|
3278
|
+
);
|
|
3279
|
+
|
|
3280
|
+
const prelude = A.prelude(at);
|
|
3281
|
+
let i = 0;
|
|
3282
|
+
while (i < prelude.length && A.type(prelude[i]) === NodeType.Whitespace) {
|
|
3283
|
+
i++;
|
|
3284
|
+
}
|
|
3285
|
+
if (i + 1 >= prelude.length || A.type(prelude[i]) !== NodeType.Colon) {
|
|
3286
|
+
return;
|
|
3287
|
+
}
|
|
3288
|
+
const nameNode = prelude[i + 1];
|
|
3289
|
+
if (
|
|
3290
|
+
A.type(nameNode) !== NodeType.Ident ||
|
|
3291
|
+
A.end(prelude[i]) !== A.start(nameNode)
|
|
3292
|
+
) {
|
|
3293
|
+
return;
|
|
3294
|
+
}
|
|
3295
|
+
const name = A.value(nameNode);
|
|
3296
|
+
if (!isDashedIdentifier(name)) return;
|
|
3297
|
+
const list = input.slice(A.end(nameNode), end).trim();
|
|
3298
|
+
if (list.length === 0) return;
|
|
3299
|
+
(customSelectorDefs || (customSelectorDefs = new Map())).set(name, list);
|
|
3300
|
+
};
|
|
3301
|
+
|
|
3302
|
+
/**
|
|
3303
|
+
* 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.
|
|
3304
|
+
* @param {readonly AstNode[]} tokens selector prelude (or nested) tokens
|
|
3305
|
+
*/
|
|
3306
|
+
const scanCustomSelectorUses = (tokens) => {
|
|
3307
|
+
for (let k = 0; k < tokens.length; k++) {
|
|
3308
|
+
const t = tokens[k];
|
|
3309
|
+
const tt = A.type(t);
|
|
3310
|
+
if (tt === NodeType.Colon) {
|
|
3311
|
+
const next = tokens[k + 1];
|
|
3312
|
+
if (
|
|
3313
|
+
next &&
|
|
3314
|
+
A.type(next) === NodeType.Ident &&
|
|
3315
|
+
A.end(t) === A.start(next) &&
|
|
3316
|
+
isDashedIdentifier(A.value(next))
|
|
3317
|
+
) {
|
|
3318
|
+
(customSelectorUses || (customSelectorUses = [])).push({
|
|
3319
|
+
name: A.value(next),
|
|
3320
|
+
start: A.start(t),
|
|
3321
|
+
end: A.end(next)
|
|
3322
|
+
});
|
|
3323
|
+
}
|
|
3324
|
+
} else if (tt === NodeType.Function || tt === NodeType.SimpleBlock) {
|
|
3325
|
+
scanCustomSelectorUses(A.children(t));
|
|
3326
|
+
}
|
|
3327
|
+
}
|
|
3328
|
+
};
|
|
3329
|
+
|
|
3330
|
+
/**
|
|
3331
|
+
* 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.
|
|
3332
|
+
*/
|
|
3333
|
+
const resolveCustomMediaAndSelectors = () => {
|
|
3334
|
+
if (customMediaUses) {
|
|
3335
|
+
for (const use of customMediaUses) {
|
|
3336
|
+
if (use.invalid) {
|
|
3337
|
+
this._emitWarning(
|
|
3338
|
+
state,
|
|
3339
|
+
`Custom media query '${use.name}' must be used in a boolean context`,
|
|
3340
|
+
locConverter,
|
|
3341
|
+
use.start,
|
|
3342
|
+
use.end
|
|
3343
|
+
);
|
|
3344
|
+
continue;
|
|
3345
|
+
}
|
|
3346
|
+
const def = customMediaDefs && customMediaDefs.get(use.name);
|
|
3347
|
+
if (!def) {
|
|
3348
|
+
this._emitWarning(
|
|
3349
|
+
state,
|
|
3350
|
+
`Unknown custom media query '${use.name}'`,
|
|
3351
|
+
locConverter,
|
|
3352
|
+
use.start,
|
|
3353
|
+
use.end
|
|
3354
|
+
);
|
|
3355
|
+
continue;
|
|
3356
|
+
}
|
|
3357
|
+
if (def.kind === "unsupported") {
|
|
3358
|
+
this._emitWarning(
|
|
3359
|
+
state,
|
|
3360
|
+
`Custom media query '${use.name}' uses an unsupported value ('true'/'false' or a nested custom media) and was left unresolved`,
|
|
3361
|
+
locConverter,
|
|
3362
|
+
use.start,
|
|
3363
|
+
use.end
|
|
3364
|
+
);
|
|
3365
|
+
continue;
|
|
3366
|
+
}
|
|
3367
|
+
if (def.kind === "type" && !use.leading) {
|
|
3368
|
+
this._emitWarning(
|
|
3369
|
+
state,
|
|
3370
|
+
`Custom media query '${use.name}' resolves to a media type and can only be used at the start of a media query`,
|
|
3371
|
+
locConverter,
|
|
3372
|
+
use.start,
|
|
3373
|
+
use.end
|
|
3374
|
+
);
|
|
3375
|
+
continue;
|
|
3376
|
+
}
|
|
3377
|
+
module.addPresentationalDependency(
|
|
3378
|
+
new ConstDependency(def.text, [use.start, use.end])
|
|
3379
|
+
);
|
|
3380
|
+
}
|
|
3381
|
+
}
|
|
3382
|
+
if (customSelectorUses) {
|
|
3383
|
+
for (const use of customSelectorUses) {
|
|
3384
|
+
const list = customSelectorDefs && customSelectorDefs.get(use.name);
|
|
3385
|
+
if (list === undefined) continue;
|
|
3386
|
+
module.addPresentationalDependency(
|
|
3387
|
+
new ConstDependency(`:is(${list})`, [use.start, use.end])
|
|
3388
|
+
);
|
|
3389
|
+
}
|
|
3390
|
+
}
|
|
3391
|
+
};
|
|
3392
|
+
|
|
3075
3393
|
/**
|
|
3076
3394
|
* 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.
|
|
3077
3395
|
* @param {Declaration} decl the declaration
|
|
@@ -3082,6 +3400,10 @@ class CssParser extends Parser {
|
|
|
3082
3400
|
/** @type {Record<string, number>} */
|
|
3083
3401
|
let parsedKeywords = Object.create(null);
|
|
3084
3402
|
const isGridProperty = Boolean(declPropertyName.startsWith("grid"));
|
|
3403
|
+
// Only `view-transition-*` legitimately takes a dashed-ident value; other known properties keep the historical plain-ident handling.
|
|
3404
|
+
const isViewTransitionProperty = Boolean(
|
|
3405
|
+
declPropertyName.startsWith("view-transition")
|
|
3406
|
+
);
|
|
3085
3407
|
const isGridTemplate = isGridProperty
|
|
3086
3408
|
? Boolean(
|
|
3087
3409
|
declPropertyName === "grid" ||
|
|
@@ -3141,6 +3463,13 @@ class CssParser extends Parser {
|
|
|
3141
3463
|
break;
|
|
3142
3464
|
}
|
|
3143
3465
|
const identifier = A.value(cv);
|
|
3466
|
+
// `view-transition-name: --foo` — a dashed name scopes as a custom property under `dashedIdents`, not as a plain ident export.
|
|
3467
|
+
if (isViewTransitionProperty && isDashedIdentifier(identifier)) {
|
|
3468
|
+
if (this.options.dashedIdents) {
|
|
3469
|
+
emitDashedIdentExport(A.start(cv), A.end(cv));
|
|
3470
|
+
}
|
|
3471
|
+
break;
|
|
3472
|
+
}
|
|
3144
3473
|
// Values are almost always lowercase already — avoid the copy.
|
|
3145
3474
|
const keyword = toLowerCaseIfNeeded(identifier);
|
|
3146
3475
|
parsedKeywords[keyword] =
|
|
@@ -3300,6 +3629,18 @@ class CssParser extends Parser {
|
|
|
3300
3629
|
handleImportAtRule(at, topLevel);
|
|
3301
3630
|
break;
|
|
3302
3631
|
}
|
|
3632
|
+
case "@custom-media": {
|
|
3633
|
+
if (mayHaveCustomMedia) collectCustomMedia(at);
|
|
3634
|
+
break;
|
|
3635
|
+
}
|
|
3636
|
+
case "@custom-selector": {
|
|
3637
|
+
if (mayHaveCustomSelectors) collectCustomSelector(at);
|
|
3638
|
+
break;
|
|
3639
|
+
}
|
|
3640
|
+
case "@media": {
|
|
3641
|
+
if (mayHaveCustomMedia) scanCustomMediaUses(A.prelude(at), true);
|
|
3642
|
+
break;
|
|
3643
|
+
}
|
|
3303
3644
|
default: {
|
|
3304
3645
|
if (!isModules) break;
|
|
3305
3646
|
if (name === "@value") {
|
|
@@ -3357,7 +3698,8 @@ class CssParser extends Parser {
|
|
|
3357
3698
|
this.options.dashedIdents &&
|
|
3358
3699
|
isModules &&
|
|
3359
3700
|
!isProcessedByLocalAtRule &&
|
|
3360
|
-
effectiveLocalMode
|
|
3701
|
+
effectiveLocalMode &&
|
|
3702
|
+
!(mayHaveCustomMedia && name === "@media")
|
|
3361
3703
|
);
|
|
3362
3704
|
dashed.emit = dashed.active;
|
|
3363
3705
|
|
|
@@ -3410,7 +3752,8 @@ class CssParser extends Parser {
|
|
|
3410
3752
|
savedLocalIdentifiers,
|
|
3411
3753
|
name,
|
|
3412
3754
|
hasBlock: atHasBlock,
|
|
3413
|
-
endsWithSemicolon: source.charCodeAt(A.end(at)) === CC_SEMICOLON
|
|
3755
|
+
endsWithSemicolon: source.charCodeAt(A.end(at)) === CC_SEMICOLON,
|
|
3756
|
+
fontPreloaded: false
|
|
3414
3757
|
});
|
|
3415
3758
|
},
|
|
3416
3759
|
// At-rule exit: pure-frame finalization, `suppressNextRulePrelude`, scope restore, top-level reset.
|
|
@@ -3440,6 +3783,8 @@ class CssParser extends Parser {
|
|
|
3440
3783
|
const rule = /** @type {QualifiedRule} */ (node);
|
|
3441
3784
|
const topLevel = path.parent === null;
|
|
3442
3785
|
advanceCommentCursor(A.start(rule));
|
|
3786
|
+
// `:--name` custom-selector references (dashed idents never collide with the `:import`/`:export` ICSS preludes below).
|
|
3787
|
+
if (mayHaveCustomSelectors) scanCustomSelectorUses(A.prelude(rule));
|
|
3443
3788
|
// `:import(…) { … }` / `:export { … }` ICSS pseudo-rules are processed inline at top level; nested ones bail out.
|
|
3444
3789
|
if (isModules) {
|
|
3445
3790
|
const rulePrelude = A.prelude(rule);
|
|
@@ -3673,15 +4018,12 @@ class CssParser extends Parser {
|
|
|
3673
4018
|
) {
|
|
3674
4019
|
return;
|
|
3675
4020
|
}
|
|
3676
|
-
|
|
3677
|
-
|
|
3678
|
-
|
|
3679
|
-
|
|
3680
|
-
|
|
3681
|
-
|
|
3682
|
-
) {
|
|
3683
|
-
return;
|
|
3684
|
-
}
|
|
4021
|
+
const { ignored, options: urlComments } = magicCommentsIn(
|
|
4022
|
+
[lastTokenEndForComments, A.end(node)],
|
|
4023
|
+
lastTokenEndForComments,
|
|
4024
|
+
A.end(node)
|
|
4025
|
+
);
|
|
4026
|
+
if (ignored) return;
|
|
3685
4027
|
let value = normalizeUrl(
|
|
3686
4028
|
input.slice(A.contentStart(url), A.contentEnd(url)),
|
|
3687
4029
|
false
|
|
@@ -3717,6 +4059,12 @@ class CssParser extends Parser {
|
|
|
3717
4059
|
"url"
|
|
3718
4060
|
);
|
|
3719
4061
|
setDepLoc(dep, A.start(node), A.end(node));
|
|
4062
|
+
applyResourceHintDefaults(
|
|
4063
|
+
dep,
|
|
4064
|
+
value,
|
|
4065
|
+
urlComments,
|
|
4066
|
+
rangeLoc(A.start(node), A.end(node))
|
|
4067
|
+
);
|
|
3720
4068
|
module.addDependency(dep);
|
|
3721
4069
|
module.addCodeGenerationDependency(dep);
|
|
3722
4070
|
},
|
|
@@ -3728,12 +4076,34 @@ class CssParser extends Parser {
|
|
|
3728
4076
|
enter: (path) => {
|
|
3729
4077
|
const node = path.node;
|
|
3730
4078
|
const fn = /** @type {FunctionNode} */ (node);
|
|
3731
|
-
const
|
|
3732
|
-
|
|
3733
|
-
|
|
3734
|
-
|
|
3735
|
-
|
|
3736
|
-
|
|
4079
|
+
const fnNameStart = A.nameStart(fn);
|
|
4080
|
+
const fnNameEnd = A.nameEnd(fn);
|
|
4081
|
+
const fnNameLength = fnNameEnd - fnNameStart;
|
|
4082
|
+
// Functions are the densest value nodes, so the name is matched by raw byte range; only a name carrying an escape (rare) pays the unescaped slice.
|
|
4083
|
+
/** @type {string | undefined} */
|
|
4084
|
+
let escapedName;
|
|
4085
|
+
let isLocalFn = false;
|
|
4086
|
+
let isGlobalFn = false;
|
|
4087
|
+
if (rangeHasEscape(input, fnNameStart, fnNameEnd)) {
|
|
4088
|
+
escapedName = A.unescapedName(fn);
|
|
4089
|
+
isLocalFn = equalsLowerCase(escapedName, "local");
|
|
4090
|
+
isGlobalFn = !isLocalFn && equalsLowerCase(escapedName, "global");
|
|
4091
|
+
} else if (fnNameLength === 5) {
|
|
4092
|
+
isLocalFn = rangeEqualsLowerCase(
|
|
4093
|
+
input,
|
|
4094
|
+
fnNameStart,
|
|
4095
|
+
fnNameEnd,
|
|
4096
|
+
"local"
|
|
4097
|
+
);
|
|
4098
|
+
} else if (fnNameLength === 6) {
|
|
4099
|
+
isGlobalFn = rangeEqualsLowerCase(
|
|
4100
|
+
input,
|
|
4101
|
+
fnNameStart,
|
|
4102
|
+
fnNameEnd,
|
|
4103
|
+
"global"
|
|
4104
|
+
);
|
|
4105
|
+
}
|
|
4106
|
+
if (urlActive()) emitUrlFunction(fn, escapedName);
|
|
3737
4107
|
if (localGlobalActive() && (isLocalFn || isGlobalFn)) {
|
|
3738
4108
|
processLocalOrGlobalFunction(fn, isLocalFn ? 1 : 2);
|
|
3739
4109
|
}
|
|
@@ -3741,10 +4111,16 @@ class CssParser extends Parser {
|
|
|
3741
4111
|
icssActive() &&
|
|
3742
4112
|
!isLocalFn &&
|
|
3743
4113
|
!isGlobalFn &&
|
|
3744
|
-
|
|
3745
|
-
icssDefinitions.has(fnameRaw)
|
|
4114
|
+
icssDefinitions.size !== 0
|
|
3746
4115
|
) {
|
|
3747
|
-
|
|
4116
|
+
// Without an escape the raw name equals the unescaped one — only the `@value`-lookup path needs the string at all.
|
|
4117
|
+
const fname = escapedName === undefined ? A.name(fn) : escapedName;
|
|
4118
|
+
if (
|
|
4119
|
+
!(dashed.active && isDashedIdentifier(fname)) &&
|
|
4120
|
+
icssDefinitions.has(fname)
|
|
4121
|
+
) {
|
|
4122
|
+
emitICSSSymbol(fname, fnNameStart, fnNameEnd);
|
|
4123
|
+
}
|
|
3748
4124
|
}
|
|
3749
4125
|
// Dashed-ident scoping: handle this function, then set the child nesting level's state for the walk.
|
|
3750
4126
|
dashed.push();
|
|
@@ -3753,15 +4129,35 @@ class CssParser extends Parser {
|
|
|
3753
4129
|
// `local()` / `global()` dashed args go through the ICSS path above, not here.
|
|
3754
4130
|
dashed.active = false;
|
|
3755
4131
|
} else if (
|
|
3756
|
-
|
|
3757
|
-
|
|
4132
|
+
escapedName === undefined
|
|
4133
|
+
? (fnNameLength === 3 &&
|
|
4134
|
+
rangeEqualsLowerCase(
|
|
4135
|
+
input,
|
|
4136
|
+
fnNameStart,
|
|
4137
|
+
fnNameEnd,
|
|
4138
|
+
"var"
|
|
4139
|
+
)) ||
|
|
4140
|
+
(fnNameLength === 5 &&
|
|
4141
|
+
rangeEqualsLowerCase(
|
|
4142
|
+
input,
|
|
4143
|
+
fnNameStart,
|
|
4144
|
+
fnNameEnd,
|
|
4145
|
+
"style"
|
|
4146
|
+
))
|
|
4147
|
+
: equalsLowerCase(escapedName, "var") ||
|
|
4148
|
+
equalsLowerCase(escapedName, "style")
|
|
3758
4149
|
) {
|
|
3759
4150
|
// `var(--foo, …)` / `style(--foo, …)`: emit the first ident; the fallback doesn't self-emit.
|
|
3760
4151
|
processDashedIdentInVarFunction(fn);
|
|
3761
4152
|
dashed.emit = false;
|
|
3762
|
-
} else if (
|
|
3763
|
-
|
|
3764
|
-
|
|
4153
|
+
} else if (
|
|
4154
|
+
dashed.emit &&
|
|
4155
|
+
fnNameLength >= 3 &&
|
|
4156
|
+
input.charCodeAt(fnNameStart) === CC_HYPHEN_MINUS &&
|
|
4157
|
+
input.charCodeAt(fnNameStart + 1) === CC_HYPHEN_MINUS
|
|
4158
|
+
) {
|
|
4159
|
+
// Custom-function call `--my-func(args)` — the name is the exported dashed-ident (a literal `--` prefix, like the `A.name` string check it replaces).
|
|
4160
|
+
emitDashedIdentExport(fnNameStart, fnNameEnd);
|
|
3765
4161
|
}
|
|
3766
4162
|
}
|
|
3767
4163
|
},
|
|
@@ -3783,23 +4179,36 @@ class CssParser extends Parser {
|
|
|
3783
4179
|
if (dashedActive && isDashedIdentifier(identValue)) {
|
|
3784
4180
|
// Dashed idents are scoped here, never `@value` ICSS-rewritten.
|
|
3785
4181
|
if (!dashed.emit) return;
|
|
3786
|
-
// Resolve the `--foo from "./x.css"` / `--foo from global` import suffix via sibling lookahead.
|
|
3787
|
-
const
|
|
3788
|
-
|
|
3789
|
-
|
|
3790
|
-
|
|
4182
|
+
// Resolve the `--foo from "./x.css"` / `--foo from global` import suffix via sibling lookahead over the parent's child span (`path.index` avoids materializing the sibling list per dashed ident).
|
|
4183
|
+
const parent = path.parent;
|
|
4184
|
+
if (parent) {
|
|
4185
|
+
const count = A.childCount(parent);
|
|
4186
|
+
let j = path.index + 1;
|
|
4187
|
+
while (
|
|
4188
|
+
j < count &&
|
|
4189
|
+
A.type(A.childAt(parent, j)) === NodeType.Whitespace
|
|
4190
|
+
) {
|
|
4191
|
+
j++;
|
|
4192
|
+
}
|
|
4193
|
+
const fromIdent = j < count ? A.childAt(parent, j) : undefined;
|
|
3791
4194
|
if (
|
|
3792
|
-
|
|
3793
|
-
A.type(
|
|
4195
|
+
fromIdent &&
|
|
4196
|
+
A.type(fromIdent) === NodeType.Ident &&
|
|
3794
4197
|
rangeEqualsLowerCase(
|
|
3795
4198
|
input,
|
|
3796
|
-
A.start(
|
|
3797
|
-
A.end(
|
|
4199
|
+
A.start(fromIdent),
|
|
4200
|
+
A.end(fromIdent),
|
|
3798
4201
|
"from"
|
|
3799
4202
|
)
|
|
3800
4203
|
) {
|
|
3801
|
-
|
|
3802
|
-
|
|
4204
|
+
j++;
|
|
4205
|
+
while (
|
|
4206
|
+
j < count &&
|
|
4207
|
+
A.type(A.childAt(parent, j)) === NodeType.Whitespace
|
|
4208
|
+
) {
|
|
4209
|
+
j++;
|
|
4210
|
+
}
|
|
4211
|
+
const sourceNode = j < count ? A.childAt(parent, j) : undefined;
|
|
3803
4212
|
if (
|
|
3804
4213
|
sourceNode &&
|
|
3805
4214
|
A.type(sourceNode) === NodeType.Ident &&
|
|
@@ -3842,11 +4251,18 @@ class CssParser extends Parser {
|
|
|
3842
4251
|
// and ICSS-captured values).
|
|
3843
4252
|
new SourceProcessor({
|
|
3844
4253
|
as: /** @type {"stylesheet" | "block-contents"} */ (this.options.as),
|
|
3845
|
-
skip
|
|
4254
|
+
// Non-modules parses skip selector preludes, but `@custom-selector` expansion needs them, so keep them when the file may use one.
|
|
4255
|
+
skip: isModules
|
|
4256
|
+
? undefined
|
|
4257
|
+
: mayHaveCustomSelectors
|
|
4258
|
+
? SKIP_NON_MODULES_KEEP_SELECTORS
|
|
4259
|
+
: SKIP_NON_MODULES
|
|
3846
4260
|
})
|
|
3847
4261
|
.use(/** @type {VisitorMap} */ (visitors))
|
|
3848
4262
|
.process(source, { locConverter });
|
|
3849
4263
|
|
|
4264
|
+
if (customMediaUses || customSelectorUses) resolveCustomMediaAndSelectors();
|
|
4265
|
+
|
|
3850
4266
|
/** @type {BuildInfo} */
|
|
3851
4267
|
(module.buildInfo).strict = true;
|
|
3852
4268
|
|