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.
Files changed (200) hide show
  1. package/bin/webpack.js +12 -0
  2. package/lib/APIPlugin.js +48 -29
  3. package/lib/CacheFacade.js +8 -0
  4. package/lib/ChunkGraph.js +17 -15
  5. package/lib/ChunkGroup.js +1 -0
  6. package/lib/CompatibilityPlugin.js +62 -29
  7. package/lib/Compilation.js +58 -6
  8. package/lib/ConstPlugin.js +11 -11
  9. package/lib/ContextModule.js +239 -6
  10. package/lib/ContextModuleFactory.js +139 -14
  11. package/lib/DefinePlugin.js +433 -112
  12. package/lib/Entrypoint.js +1 -1
  13. package/lib/ExportsInfo.js +22 -5
  14. package/lib/ExportsInfoApiPlugin.js +2 -2
  15. package/lib/ExternalModule.js +107 -5
  16. package/lib/ExternalModuleFactoryPlugin.js +20 -1
  17. package/lib/FileSystemInfo.js +256 -100
  18. package/lib/FlagDependencyUsagePlugin.js +33 -12
  19. package/lib/HotModuleReplacementPlugin.js +18 -15
  20. package/lib/InitFragment.js +20 -32
  21. package/lib/Module.js +3 -0
  22. package/lib/ModuleFilenameHelpers.js +2 -2
  23. package/lib/ModuleTypeConstants.js +8 -1
  24. package/lib/MultiCompiler.js +13 -2
  25. package/lib/MultiWatching.js +1 -0
  26. package/lib/NodeStuffPlugin.js +51 -33
  27. package/lib/NormalModule.js +175 -401
  28. package/lib/NormalModuleFactory.js +76 -2
  29. package/lib/ProgressPlugin.js +211 -119
  30. package/lib/ProvidePlugin.js +2 -2
  31. package/lib/RuntimeGlobals.js +47 -0
  32. package/lib/RuntimePlugin.js +67 -107
  33. package/lib/RuntimeTemplate.js +304 -16
  34. package/lib/Template.js +23 -3
  35. package/lib/UseStrictPlugin.js +1 -1
  36. package/lib/WebpackIsIncludedPlugin.js +1 -1
  37. package/lib/WebpackOptionsApply.js +24 -19
  38. package/lib/asset/AssetGenerator.js +110 -28
  39. package/lib/asset/AssetModulesPlugin.js +92 -2
  40. package/lib/asset/WebManifestGenerator.js +164 -0
  41. package/lib/asset/WebManifestParser.js +130 -0
  42. package/lib/async-modules/AwaitDependenciesInitFragment.js +14 -4
  43. package/lib/async-modules/isGeneratorLowered.js +26 -0
  44. package/lib/cache/PackFileCacheStrategy.js +9 -3
  45. package/lib/config/browserslistTargetHandler.js +40 -0
  46. package/lib/config/defaults.js +219 -29
  47. package/lib/config/normalization.js +26 -0
  48. package/lib/config/target.js +9 -1
  49. package/lib/container/HoistContainerReferencesPlugin.js +47 -55
  50. package/lib/css/CssGenerator.js +40 -31
  51. package/lib/css/CssLoadingRuntimeModule.js +5 -1
  52. package/lib/css/CssModulesPlugin.js +13 -0
  53. package/lib/css/CssParser.js +729 -313
  54. package/lib/css/syntax.js +1371 -861
  55. package/lib/dependencies/AMDDefineDependencyParserPlugin.js +6 -6
  56. package/lib/dependencies/AMDPlugin.js +2 -2
  57. package/lib/dependencies/AMDRequireDependenciesBlockParserPlugin.js +13 -13
  58. package/lib/dependencies/CommonJsDependencyHelpers.js +39 -0
  59. package/lib/dependencies/CommonJsExportRequireDependency.js +19 -0
  60. package/lib/dependencies/CommonJsExportsDependency.js +47 -2
  61. package/lib/dependencies/CommonJsExportsParserPlugin.js +10 -10
  62. package/lib/dependencies/CommonJsImportsParserPlugin.js +18 -18
  63. package/lib/dependencies/CommonJsPlugin.js +2 -2
  64. package/lib/dependencies/CommonJsRequireDependency.js +26 -1
  65. package/lib/dependencies/CommonJsSelfReferenceDependency.js +32 -1
  66. package/lib/dependencies/ContextDependencyHelpers.js +3 -3
  67. package/lib/dependencies/CreateRequireParserPlugin.js +6 -6
  68. package/lib/dependencies/CssIcssExportDependency.js +162 -71
  69. package/lib/dependencies/CssUrlDependency.js +35 -4
  70. package/lib/dependencies/ExternalModuleInitFragmentDependency.js +2 -2
  71. package/lib/dependencies/HarmonyCompatibilityDependency.js +32 -14
  72. package/lib/dependencies/HarmonyDetectionParserPlugin.js +43 -13
  73. package/lib/dependencies/HarmonyEvaluatedImportSpecifierDependency.js +2 -1
  74. package/lib/dependencies/HarmonyExportDependencyParserPlugin.js +7 -19
  75. package/lib/dependencies/HarmonyExportImportedSpecifierDependency.js +17 -9
  76. package/lib/dependencies/HarmonyImportDependency.js +13 -2
  77. package/lib/dependencies/HarmonyImportDependencyParserPlugin.js +11 -11
  78. package/lib/dependencies/HarmonyImportSpecifierDependency.js +10 -7
  79. package/lib/dependencies/HarmonyModulesPlugin.js +6 -0
  80. package/lib/dependencies/HarmonyTopLevelThisParserPlugin.js +1 -1
  81. package/lib/dependencies/HtmlEntryDependency.js +797 -172
  82. package/lib/dependencies/HtmlSourceDependency.js +26 -0
  83. package/lib/dependencies/ImportDependency.js +43 -1
  84. package/lib/dependencies/ImportMetaContextDependencyParserPlugin.js +31 -18
  85. package/lib/dependencies/ImportMetaContextPlugin.js +38 -7
  86. package/lib/dependencies/ImportMetaGlobDependency.js +81 -0
  87. package/lib/dependencies/ImportMetaGlobDependencyParserPlugin.js +78 -0
  88. package/lib/dependencies/ImportMetaGlobHelpers.js +660 -0
  89. package/lib/dependencies/ImportMetaPlugin.js +532 -180
  90. package/lib/dependencies/ImportMetaResolveDependency.js +90 -0
  91. package/lib/dependencies/ImportParserPlugin.js +80 -17
  92. package/lib/dependencies/ImportPhase.js +10 -8
  93. package/lib/dependencies/LoaderPlugin.js +5 -1
  94. package/lib/dependencies/RequireContextDependencyParserPlugin.js +1 -1
  95. package/lib/dependencies/RequireEnsureDependenciesBlockParserPlugin.js +3 -6
  96. package/lib/dependencies/RequireIncludeDependencyParserPlugin.js +4 -11
  97. package/lib/dependencies/SystemPlugin.js +2 -4
  98. package/lib/dependencies/TopLevelAwaitDependency.js +93 -0
  99. package/lib/dependencies/URLDependency.js +141 -21
  100. package/lib/dependencies/{WorkerPlugin.js → WorkerAndWorkletPlugin.js} +324 -112
  101. package/lib/dependencies/WorkerDependency.js +86 -18
  102. package/lib/dependencies/WorkletDependency.js +278 -0
  103. package/lib/dll/DelegatedModuleFactoryPlugin.js +19 -0
  104. package/lib/errors/EnvironmentNotSupportAsyncWarning.js +1 -1
  105. package/lib/errors/LoaderLoadingError.js +20 -0
  106. package/lib/errors/ModuleParseError.js +39 -22
  107. package/lib/errors/NodeStuffInWebError.js +1 -1
  108. package/lib/esm/ModuleChunkFormatPlugin.js +47 -14
  109. package/lib/esm/ModuleChunkLoadingPlugin.js +8 -1
  110. package/lib/esm/ModuleChunkLoadingRuntimeModule.js +29 -1
  111. package/lib/hmr/lazyCompilationBackend.js +51 -12
  112. package/lib/html/HtmlGenerator.js +907 -15
  113. package/lib/html/HtmlModule.js +2 -2
  114. package/lib/html/HtmlModulesPlugin.js +610 -166
  115. package/lib/html/HtmlParser.js +603 -247
  116. package/lib/html/favicon.svg +1 -0
  117. package/lib/html/syntax.js +3451 -2762
  118. package/lib/ids/IdHelpers.js +25 -11
  119. package/lib/index.js +6 -0
  120. package/lib/javascript/ArrayPushCallbackChunkFormatPlugin.js +4 -5
  121. package/lib/javascript/BasicEvaluatedExpression.js +366 -48
  122. package/lib/javascript/JavascriptGenerator.js +197 -49
  123. package/lib/javascript/JavascriptModulesPlugin.js +178 -24
  124. package/lib/javascript/JavascriptParser.js +704 -355
  125. package/lib/javascript/JavascriptParserHelpers.js +3 -6
  126. package/lib/javascript/syntax.js +5037 -0
  127. package/lib/library/ModuleLibraryPlugin.js +38 -1
  128. package/lib/loaders/LoaderRunner.js +714 -0
  129. package/lib/loaders/loadLoader.js +100 -0
  130. package/lib/node/ReadFileCompileAsyncWasmPlugin.js +29 -2
  131. package/lib/node/ReadFileCompileWasmPlugin.js +24 -2
  132. package/lib/optimize/AggressiveMergingPlugin.js +19 -15
  133. package/lib/optimize/ConcatenatedModule.js +139 -31
  134. package/lib/optimize/FlagIncludedChunksPlugin.js +13 -3
  135. package/lib/optimize/InnerGraphPlugin.js +50 -30
  136. package/lib/optimize/ModuleConcatenationPlugin.js +139 -24
  137. package/lib/optimize/SideEffectsFlagPlugin.js +86 -43
  138. package/lib/optimize/SplitChunksPlugin.js +146 -44
  139. package/lib/prefetch/ChunkPrefetchPreloadPlugin.js +15 -0
  140. package/lib/prefetch/ChunkPreloadTriggerRuntimeModule.js +24 -13
  141. package/lib/prefetch/ResourceHintPlugin.js +687 -0
  142. package/lib/prefetch/ResourceHintRuntimeModule.js +149 -0
  143. package/lib/prefetch/StartupAssetHintRuntimeModule.js +210 -0
  144. package/lib/prefetch/parseResourceHintOptions.js +103 -0
  145. package/lib/rules/RuleSetCompiler.js +114 -0
  146. package/lib/runtime/AsyncModuleGeneratorRuntimeModule.js +56 -0
  147. package/lib/runtime/AsyncModuleRuntimeModule.js +24 -8
  148. package/lib/runtime/AutoPublicPathRuntimeModule.js +7 -1
  149. package/lib/runtime/CommonJsWrapRuntimeModule.js +37 -0
  150. package/lib/runtime/GetWorkletBootstrapRuntimeModule.js +59 -0
  151. package/lib/runtime/MakeDeferredNamespaceObjectRuntime.js +204 -8
  152. package/lib/runtime/SetAnonymousDefaultNameRuntimeModule.js +4 -1
  153. package/lib/schemes/HttpUriPlugin.js +40 -1
  154. package/lib/schemes/VirtualUrlPlugin.js +1 -2
  155. package/lib/serialization/BinaryMiddleware.js +270 -938
  156. package/lib/serialization/FileMiddleware.js +29 -3
  157. package/lib/stats/DefaultStatsFactoryPlugin.js +229 -194
  158. package/lib/stats/DefaultStatsPresetPlugin.js +2 -0
  159. package/lib/stats/DefaultStatsPrinterPlugin.js +36 -168
  160. package/lib/stats/StatsFactory.js +46 -17
  161. package/lib/url/URLParserPlugin.js +66 -13
  162. package/lib/util/AsyncQueue.js +122 -72
  163. package/lib/util/SetHelpers.js +1 -1
  164. package/lib/util/StackedMap.js +122 -48
  165. package/lib/util/binarySearchBounds.js +2 -2
  166. package/lib/util/concatenate.js +40 -14
  167. package/lib/util/createHooksRegistry.js +13 -5
  168. package/lib/util/dataURL.js +2 -2
  169. package/lib/util/deterministicGrouping.js +7 -5
  170. package/lib/util/formatSize.js +6 -2
  171. package/lib/util/globUtils.js +710 -0
  172. package/lib/util/identifier.js +23 -6
  173. package/lib/util/implicitTypeLoaderFallback.js +62 -0
  174. package/lib/util/internalSerializables.js +88 -71
  175. package/lib/util/magicComment.js +121 -25
  176. package/lib/util/numberHash.js +2 -2
  177. package/lib/wasm-async/AsyncWasmCompileRuntimeModule.js +24 -18
  178. package/lib/wasm-async/AsyncWasmLoadingRuntimeModule.js +33 -22
  179. package/lib/wasm-sync/WasmChunkLoadingRuntimeModule.js +79 -16
  180. package/lib/web/FetchCompileAsyncWasmPlugin.js +19 -0
  181. package/lib/web/FetchCompileWasmPlugin.js +14 -0
  182. package/lib/web/JsonpChunkLoadingRuntimeModule.js +2 -0
  183. package/lib/webpack.js +65 -0
  184. package/lib/webworker/ImportScriptsChunkLoadingRuntimeModule.js +3 -0
  185. package/module.d.ts +21 -0
  186. package/package.json +12 -11
  187. package/schemas/WebpackOptions.check.js +1 -1
  188. package/schemas/WebpackOptions.json +759 -36
  189. package/schemas/plugins/HtmlParserOptions.check.js +1 -1
  190. package/schemas/plugins/ProgressPlugin.check.js +1 -1
  191. package/schemas/plugins/ProgressPlugin.json +17 -1
  192. package/schemas/plugins/container/ContainerReferencePlugin.check.js +1 -1
  193. package/schemas/plugins/container/ContainerReferencePlugin.json +1 -0
  194. package/schemas/plugins/container/ExternalsType.check.js +1 -1
  195. package/schemas/plugins/container/ModuleFederationPlugin.check.js +1 -1
  196. package/schemas/plugins/container/ModuleFederationPlugin.json +1 -0
  197. package/schemas/plugins/css/CssAutoOrModuleParserOptions.check.js +1 -1
  198. package/schemas/plugins/css/CssModuleParserOptions.check.js +1 -1
  199. package/schemas/plugins/css/CssParserOptions.check.js +1 -1
  200. package/types.d.ts +1212 -99
@@ -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
- webpackCommentRegExp
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 report whether the resource is ignored (so the caller skips emitting its dependency).
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} true when `webpackIgnore: true`
1145
+ * @returns {{ ignored: boolean, options: Record<string, EXPECTED_ANY> | null }} parsed options and `webpackIgnore` flag
1170
1146
  */
1171
- const webpackIgnored = (range, warnStart, warnEnd) => {
1172
- const { options, errors } = parseCommentOptions(range);
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
- return options.webpackIgnore;
1178
+ ignored = options.webpackIgnore;
1195
1179
  }
1196
1180
  }
1197
- return false;
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 `parseCommentOptions` (magic comments).
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
- isCssWhitespace(source.charCodeAt(trailStart - 1))
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" && currentAtRuleName !== "@import"
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 (isCssWhitespace(source.charCodeAt(resumeAt))) resumeAt++;
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} fname the unescaped function name (any case)
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, fname) => {
2687
- if (equalsLowerCase(fname, "url") || equalsLowerCase(fname, "src")) {
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
- if (
2693
- webpackIgnored(
2694
- [lastTokenEndForComments, A.start(fn)],
2695
- A.start(string),
2696
- A.end(string)
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 (IMAGE_SET_FUNCTION.test(fname)) {
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 = A.nameEnd(fn) + 1;
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
- if (
2738
- webpackIgnored(
2739
- [start, A.end(string)],
2740
- A.start(string),
2741
- A.end(string)
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
- if (
3677
- webpackIgnored(
3678
- [lastTokenEndForComments, A.end(node)],
3679
- lastTokenEndForComments,
3680
- A.end(node)
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 fnameRaw = A.unescapedName(fn);
3732
- // `equalsLowerCase` keeps this visitor free of a per-function
3733
- // lowercased copy — functions are the densest value nodes.
3734
- const isLocalFn = equalsLowerCase(fnameRaw, "local");
3735
- const isGlobalFn = !isLocalFn && equalsLowerCase(fnameRaw, "global");
3736
- if (urlActive()) emitUrlFunction(fn, fnameRaw);
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
- !(dashed.active && isDashedIdentifier(fnameRaw)) &&
3745
- icssDefinitions.has(fnameRaw)
4114
+ icssDefinitions.size !== 0
3746
4115
  ) {
3747
- emitICSSSymbol(fnameRaw, A.nameStart(fn), A.nameEnd(fn));
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
- equalsLowerCase(fnameRaw, "var") ||
3757
- equalsLowerCase(fnameRaw, "style")
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 (dashed.emit && isDashedIdentifier(A.name(fn))) {
3763
- // Custom-function call `--my-func(args)` — the name is the exported dashed-ident.
3764
- emitDashedIdentExport(A.nameStart(fn), A.nameEnd(fn));
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 siblings = childrenOf(path.parent);
3788
- const i = siblings ? siblings.indexOf(node) : -1;
3789
- if (siblings && i !== -1) {
3790
- const j = nextNonWhitespace(siblings, i + 1);
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
- j < siblings.length &&
3793
- A.type(siblings[j]) === NodeType.Ident &&
4195
+ fromIdent &&
4196
+ A.type(fromIdent) === NodeType.Ident &&
3794
4197
  rangeEqualsLowerCase(
3795
4198
  input,
3796
- A.start(siblings[j]),
3797
- A.end(siblings[j]),
4199
+ A.start(fromIdent),
4200
+ A.end(fromIdent),
3798
4201
  "from"
3799
4202
  )
3800
4203
  ) {
3801
- const fromIdent = siblings[j];
3802
- const sourceNode = siblings[nextNonWhitespace(siblings, j + 1)];
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: isModules ? undefined : SKIP_NON_MODULES
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