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.
Files changed (191) hide show
  1. package/lib/APIPlugin.js +48 -29
  2. package/lib/CacheFacade.js +8 -0
  3. package/lib/ChunkGroup.js +1 -0
  4. package/lib/CompatibilityPlugin.js +31 -29
  5. package/lib/Compilation.js +50 -8
  6. package/lib/ConstPlugin.js +11 -11
  7. package/lib/ContextModule.js +239 -6
  8. package/lib/ContextModuleFactory.js +139 -14
  9. package/lib/DefinePlugin.js +396 -101
  10. package/lib/Entrypoint.js +1 -1
  11. package/lib/ExportsInfoApiPlugin.js +2 -2
  12. package/lib/ExternalModule.js +107 -5
  13. package/lib/ExternalModuleFactoryPlugin.js +20 -1
  14. package/lib/FileSystemInfo.js +130 -74
  15. package/lib/FlagDependencyExportsPlugin.js +55 -8
  16. package/lib/HotModuleReplacementPlugin.js +18 -15
  17. package/lib/LazyBarrel.js +30 -3
  18. package/lib/Module.js +3 -0
  19. package/lib/ModuleFilenameHelpers.js +2 -2
  20. package/lib/ModuleTypeConstants.js +8 -1
  21. package/lib/MultiCompiler.js +13 -2
  22. package/lib/MultiWatching.js +1 -0
  23. package/lib/NodeStuffPlugin.js +51 -33
  24. package/lib/NormalModule.js +169 -405
  25. package/lib/NormalModuleFactory.js +76 -2
  26. package/lib/ProgressPlugin.js +211 -119
  27. package/lib/ProvidePlugin.js +2 -2
  28. package/lib/RuntimeGlobals.js +47 -0
  29. package/lib/RuntimePlugin.js +33 -0
  30. package/lib/RuntimeTemplate.js +304 -16
  31. package/lib/UseStrictPlugin.js +1 -1
  32. package/lib/WebpackIsIncludedPlugin.js +1 -1
  33. package/lib/WebpackOptionsApply.js +24 -19
  34. package/lib/asset/AssetGenerator.js +110 -28
  35. package/lib/asset/AssetModulesPlugin.js +92 -2
  36. package/lib/asset/WebManifestGenerator.js +164 -0
  37. package/lib/asset/WebManifestParser.js +130 -0
  38. package/lib/async-modules/AwaitDependenciesInitFragment.js +14 -4
  39. package/lib/async-modules/isGeneratorLowered.js +26 -0
  40. package/lib/cache/PackFileCacheStrategy.js +9 -3
  41. package/lib/config/browserslistTargetHandler.js +40 -0
  42. package/lib/config/defaults.js +222 -32
  43. package/lib/config/normalization.js +26 -0
  44. package/lib/config/target.js +9 -1
  45. package/lib/css/CssGenerator.js +73 -73
  46. package/lib/css/CssLoadingRuntimeModule.js +5 -1
  47. package/lib/css/CssModulesPlugin.js +92 -53
  48. package/lib/css/CssParser.js +843 -302
  49. package/lib/css/syntax.js +1219 -483
  50. package/lib/dependencies/AMDDefineDependencyParserPlugin.js +6 -6
  51. package/lib/dependencies/AMDPlugin.js +2 -2
  52. package/lib/dependencies/AMDRequireDependenciesBlockParserPlugin.js +13 -13
  53. package/lib/dependencies/CommonJsDependencyHelpers.js +39 -0
  54. package/lib/dependencies/CommonJsExportsDependency.js +47 -2
  55. package/lib/dependencies/CommonJsExportsParserPlugin.js +10 -10
  56. package/lib/dependencies/CommonJsImportsParserPlugin.js +18 -18
  57. package/lib/dependencies/CommonJsPlugin.js +2 -2
  58. package/lib/dependencies/CommonJsRequireDependency.js +26 -1
  59. package/lib/dependencies/CommonJsSelfReferenceDependency.js +32 -1
  60. package/lib/dependencies/ContextDependencyHelpers.js +3 -3
  61. package/lib/dependencies/CreateRequireParserPlugin.js +6 -6
  62. package/lib/dependencies/CssIcssExportDependency.js +190 -87
  63. package/lib/dependencies/CssUrlDependency.js +35 -4
  64. package/lib/dependencies/ExternalModuleInitFragmentDependency.js +2 -2
  65. package/lib/dependencies/HarmonyCompatibilityDependency.js +32 -14
  66. package/lib/dependencies/HarmonyDetectionParserPlugin.js +43 -13
  67. package/lib/dependencies/HarmonyExportDependencyParserPlugin.js +7 -19
  68. package/lib/dependencies/HarmonyImportDependency.js +12 -1
  69. package/lib/dependencies/HarmonyImportDependencyParserPlugin.js +34 -107
  70. package/lib/dependencies/HarmonyImportGuard.js +192 -20
  71. package/lib/dependencies/HarmonyModulesPlugin.js +6 -0
  72. package/lib/dependencies/HarmonyTopLevelThisParserPlugin.js +1 -1
  73. package/lib/dependencies/HtmlEntryDependency.js +797 -172
  74. package/lib/dependencies/HtmlSourceDependency.js +26 -0
  75. package/lib/dependencies/ImportDependency.js +43 -1
  76. package/lib/dependencies/ImportMetaContextDependencyParserPlugin.js +31 -18
  77. package/lib/dependencies/ImportMetaContextPlugin.js +38 -7
  78. package/lib/dependencies/ImportMetaGlobDependency.js +81 -0
  79. package/lib/dependencies/ImportMetaGlobDependencyParserPlugin.js +78 -0
  80. package/lib/dependencies/ImportMetaGlobHelpers.js +660 -0
  81. package/lib/dependencies/ImportMetaPlugin.js +532 -180
  82. package/lib/dependencies/ImportMetaResolveDependency.js +90 -0
  83. package/lib/dependencies/ImportParserPlugin.js +87 -26
  84. package/lib/dependencies/ImportPhase.js +10 -8
  85. package/lib/dependencies/LoaderPlugin.js +5 -1
  86. package/lib/dependencies/RequireContextDependencyParserPlugin.js +1 -1
  87. package/lib/dependencies/RequireEnsureDependenciesBlockParserPlugin.js +3 -6
  88. package/lib/dependencies/RequireIncludeDependencyParserPlugin.js +4 -11
  89. package/lib/dependencies/SystemPlugin.js +2 -4
  90. package/lib/dependencies/TopLevelAwaitDependency.js +93 -0
  91. package/lib/dependencies/URLDependency.js +141 -21
  92. package/lib/dependencies/{WorkerPlugin.js → WorkerAndWorkletPlugin.js} +324 -112
  93. package/lib/dependencies/WorkerDependency.js +86 -18
  94. package/lib/dependencies/WorkletDependency.js +278 -0
  95. package/lib/dll/DelegatedModuleFactoryPlugin.js +19 -0
  96. package/lib/errors/EnvironmentNotSupportAsyncWarning.js +1 -1
  97. package/lib/errors/LoaderLoadingError.js +20 -0
  98. package/lib/errors/ModuleParseError.js +39 -22
  99. package/lib/errors/NodeStuffInWebError.js +1 -1
  100. package/lib/esm/ModuleChunkFormatPlugin.js +47 -14
  101. package/lib/esm/ModuleChunkLoadingPlugin.js +8 -1
  102. package/lib/esm/ModuleChunkLoadingRuntimeModule.js +29 -1
  103. package/lib/hmr/lazyCompilationBackend.js +32 -8
  104. package/lib/html/HtmlGenerator.js +940 -91
  105. package/lib/html/HtmlModule.js +3 -2
  106. package/lib/html/HtmlModulesPlugin.js +625 -170
  107. package/lib/html/HtmlParser.js +812 -340
  108. package/lib/html/favicon.svg +1 -0
  109. package/lib/html/syntax.js +2116 -834
  110. package/lib/ids/IdHelpers.js +1 -1
  111. package/lib/index.js +6 -0
  112. package/lib/javascript/ArrayPushCallbackChunkFormatPlugin.js +4 -5
  113. package/lib/javascript/BasicEvaluatedExpression.js +366 -48
  114. package/lib/javascript/JavascriptGenerator.js +197 -49
  115. package/lib/javascript/JavascriptModulesPlugin.js +178 -24
  116. package/lib/javascript/JavascriptParser.js +711 -355
  117. package/lib/javascript/JavascriptParserHelpers.js +3 -6
  118. package/lib/javascript/syntax.js +4380 -0
  119. package/lib/library/ModuleLibraryPlugin.js +38 -1
  120. package/lib/loaders/LoaderRunner.js +714 -0
  121. package/lib/loaders/loadLoader.js +100 -0
  122. package/lib/node/ReadFileCompileAsyncWasmPlugin.js +19 -0
  123. package/lib/node/ReadFileCompileWasmPlugin.js +14 -0
  124. package/lib/optimize/ConcatenatedModule.js +120 -16
  125. package/lib/optimize/FlagIncludedChunksPlugin.js +13 -3
  126. package/lib/optimize/InnerGraphPlugin.js +50 -30
  127. package/lib/optimize/ModuleConcatenationPlugin.js +69 -3
  128. package/lib/optimize/SideEffectsFlagPlugin.js +86 -43
  129. package/lib/prefetch/ChunkPrefetchPreloadPlugin.js +15 -0
  130. package/lib/prefetch/ChunkPreloadTriggerRuntimeModule.js +24 -13
  131. package/lib/prefetch/ResourceHintPlugin.js +663 -0
  132. package/lib/prefetch/ResourceHintRuntimeModule.js +149 -0
  133. package/lib/prefetch/StartupAssetHintRuntimeModule.js +210 -0
  134. package/lib/prefetch/parseResourceHintOptions.js +103 -0
  135. package/lib/rules/RuleSetCompiler.js +114 -0
  136. package/lib/runtime/AsyncModuleGeneratorRuntimeModule.js +56 -0
  137. package/lib/runtime/AsyncModuleRuntimeModule.js +24 -8
  138. package/lib/runtime/AutoPublicPathRuntimeModule.js +7 -1
  139. package/lib/runtime/CommonJsWrapRuntimeModule.js +37 -0
  140. package/lib/runtime/GetWorkletBootstrapRuntimeModule.js +59 -0
  141. package/lib/runtime/MakeDeferredNamespaceObjectRuntime.js +204 -8
  142. package/lib/runtime/SetAnonymousDefaultNameRuntimeModule.js +4 -1
  143. package/lib/schemes/HttpUriPlugin.js +40 -1
  144. package/lib/schemes/VirtualUrlPlugin.js +1 -2
  145. package/lib/serialization/FileMiddleware.js +29 -3
  146. package/lib/stats/DefaultStatsFactoryPlugin.js +32 -3
  147. package/lib/stats/DefaultStatsPresetPlugin.js +2 -0
  148. package/lib/stats/DefaultStatsPrinterPlugin.js +12 -2
  149. package/lib/stats/StatsFactory.js +15 -8
  150. package/lib/url/URLParserPlugin.js +98 -19
  151. package/lib/util/AsyncQueue.js +122 -72
  152. package/lib/util/LocConverter.js +9 -8
  153. package/lib/util/SetHelpers.js +1 -1
  154. package/lib/util/SourceProcessor.js +41 -28
  155. package/lib/util/StackedMap.js +122 -48
  156. package/lib/util/binarySearchBounds.js +2 -2
  157. package/lib/util/concatenate.js +40 -14
  158. package/lib/util/createHooksRegistry.js +13 -5
  159. package/lib/util/dataURL.js +2 -2
  160. package/lib/util/deterministicGrouping.js +7 -5
  161. package/lib/util/formatSize.js +6 -2
  162. package/lib/util/globUtils.js +710 -0
  163. package/lib/util/identifier.js +23 -6
  164. package/lib/util/implicitTypeLoaderFallback.js +62 -0
  165. package/lib/util/internalSerializables.js +88 -71
  166. package/lib/util/magicComment.js +121 -25
  167. package/lib/util/numberHash.js +2 -2
  168. package/lib/wasm-async/AsyncWasmCompileRuntimeModule.js +24 -18
  169. package/lib/wasm-async/AsyncWasmLoadingRuntimeModule.js +33 -22
  170. package/lib/wasm-sync/WasmChunkLoadingRuntimeModule.js +79 -16
  171. package/lib/web/FetchCompileAsyncWasmPlugin.js +19 -0
  172. package/lib/web/FetchCompileWasmPlugin.js +14 -0
  173. package/lib/web/JsonpChunkLoadingRuntimeModule.js +2 -0
  174. package/lib/webpack.js +65 -0
  175. package/lib/webworker/ImportScriptsChunkLoadingRuntimeModule.js +3 -0
  176. package/module.d.ts +21 -0
  177. package/package.json +14 -13
  178. package/schemas/WebpackOptions.check.js +1 -1
  179. package/schemas/WebpackOptions.json +759 -36
  180. package/schemas/plugins/HtmlParserOptions.check.js +1 -1
  181. package/schemas/plugins/ProgressPlugin.check.js +1 -1
  182. package/schemas/plugins/ProgressPlugin.json +17 -1
  183. package/schemas/plugins/container/ContainerReferencePlugin.check.js +1 -1
  184. package/schemas/plugins/container/ContainerReferencePlugin.json +1 -0
  185. package/schemas/plugins/container/ExternalsType.check.js +1 -1
  186. package/schemas/plugins/container/ModuleFederationPlugin.check.js +1 -1
  187. package/schemas/plugins/container/ModuleFederationPlugin.json +1 -0
  188. package/schemas/plugins/css/CssAutoOrModuleParserOptions.check.js +1 -1
  189. package/schemas/plugins/css/CssModuleParserOptions.check.js +1 -1
  190. package/schemas/plugins/css/CssParserOptions.check.js +1 -1
  191. package/types.d.ts +1300 -325
@@ -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
- webpackCommentRegExp
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
- * Returns normalized url.
129
- * @param {string} str url string
130
- * @param {boolean} isString is url wrapped in quotes
131
- * @returns {string} normalized url
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 normalizeUrl = (str, isString) => {
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
- // Remove unnecessary spaces from `url(" img.png ")`
143
- if (
144
- str.length !== 0 &&
145
- (isCssWhitespace(str.charCodeAt(0)) ||
146
- isCssWhitespace(str.charCodeAt(str.length - 1)))
147
- ) {
148
- str = str.replace(TRIM_WHITE_SPACES, "");
149
- }
150
-
151
- // Unescape
152
- if (str.includes("\\")) {
153
- str = str.replace(UNESCAPE, (match) => {
154
- if (match.length > 2) {
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
- return match[1];
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
- * A custom property name (`<dashed-ident>`): a `--`-prefixed identifier other than bare `--`.
181
- * @param {string} identifier identifier
182
- * @returns {boolean} true when identifier is dashed, otherwise false
183
- */
184
- const isDashedIdentifier = (identifier) =>
185
- identifier.startsWith("--") && identifier.length >= 3;
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 compareCommentStart = (comment, needle) =>
195
- /** @type {Range} */ (comment.range)[0] - needle;
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).toLowerCase()}`) &&
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
- loc: { start: { line: sl, column: sc }, end: { line: el, column: ec } }
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 report whether the resource is ignored (so the caller skips emitting its dependency).
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} true when `webpackIgnore: true`
1130
+ * @returns {{ ignored: boolean, options: Record<string, EXPECTED_ANY> | null }} parsed options and `webpackIgnore` flag
1019
1131
  */
1020
- const webpackIgnored = (range, warnStart, warnEnd) => {
1021
- const { options, errors } = parseCommentOptions(range);
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
- return options.webpackIgnore;
1163
+ ignored = options.webpackIgnore;
1044
1164
  }
1045
1165
  }
1046
- return false;
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 callback: push every comment-token (in source order) onto the local `comments`, read back by `advanceCommentCursor` (pure-mode flags) and `parseCommentOptions` (magic comments).
1246
- * @param {string} input input
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 comment = (input, start, end) => {
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: input.slice(start + 2, end - 2),
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
- const {
1520
- start: { line: sl, column: sc },
1521
- end: { line: el, column: ec }
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
- start: { line: sl, column: sc },
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
- start: { line: sl, column: sc },
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).toLowerCase();
1618
- const type =
1619
- fname === "local" ? 1 : fname === "global" ? 2 : undefined;
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 === "var" || fname === "style")
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
- !equalsLowerCase(A.value(fv[j]), "from")
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 (A.type(src) === NodeType.Ident && A.value(src) === "global") {
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
- isCssWhitespace(source.charCodeAt(trailStart - 1))
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
- start: { line: idSl, column: idSc },
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
- const {
2128
- start: { line: sl, column: sc },
2129
- end: { line: el, column: ec }
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
- return (
2185
- !equalsLowerCase(A.name(currentStructural), "import") ||
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" && currentAtRuleName !== "@import"
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 (isCssWhitespace(source.charCodeAt(resumeAt))) resumeAt++;
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, lower-cased
2642
+ * @param {string} fname the unescaped function name (any case)
2538
2643
  */
2539
2644
  const emitUrlFunction = (fn, fname) => {
2540
- if (fname === "url" || fname === "src") {
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
- if (
2546
- webpackIgnored(
2547
- [lastTokenEndForComments, A.start(fn)],
2548
- A.start(string),
2549
- A.end(string)
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
- if (
2591
- webpackIgnored(
2592
- [start, A.end(string)],
2593
- A.start(string),
2594
- A.end(string)
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
- const keyword = identifier.toLowerCase();
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: (node, parent) => {
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).toLowerCase()}`;
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: (node, parent) => {
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 `ctx.skipChildren()`.
3287
- enter: (node, parent, ctx) => {
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
- if (ctx) ctx.skipChildren();
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: (node, parent) => {
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]: (node) => {
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 declName = A.name(decl);
3436
- const declPropertyName = declName.replace(/^(-\w+-)/, "").toLowerCase();
3437
- // Cache the known-property flag so the per-token value visitors don't recompute it.
3438
- currentDeclIsKnownProperty = knownProperties.has(declPropertyName);
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
- COMPOSES_PROPERTY.test(declPropertyName);
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(decl, declPropertyName);
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
- if (isDashedIdentifier(declName)) {
3457
- emitDashedIdentExport(A.nameStart(decl), A.nameEnd(decl));
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
- !(dashed.active && isDashedIdentifier(declName)) &&
3467
- icssDefinitions.has(declName)
3969
+ icssDefinitions.size !== 0
3468
3970
  ) {
3469
- emitICSSSymbol(declName, A.nameStart(decl), A.nameEnd(decl));
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]: (node) => {
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
- if (
3487
- webpackIgnored(
3488
- [lastTokenEndForComments, A.end(node)],
3489
- lastTokenEndForComments,
3490
- A.end(node)
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](node) {
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: (node) => {
4052
+ enter: (path) => {
4053
+ const node = path.node;
3538
4054
  const fn = /** @type {FunctionNode} */ (node);
3539
4055
  const fnameRaw = A.unescapedName(fn);
3540
- const fname = fnameRaw.toLowerCase();
3541
- if (urlActive()) emitUrlFunction(fn, fname);
3542
- if (
3543
- localGlobalActive() &&
3544
- (fname === "local" || fname === "global")
3545
- ) {
3546
- processLocalOrGlobalFunction(fn, fname === "local" ? 1 : 2);
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
- fname !== "local" &&
3551
- fname !== "global" &&
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 (fname === "local" || fname === "global") {
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 (fname === "var" || fname === "style") {
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](node, parent) {
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
- equalsLowerCase(A.value(siblings[j]), "from")
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
- A.value(sourceNode) === "global"
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
- new SourceProcessor()
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
- locConverter,
3636
- comment,
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;