webpack 5.108.4 → 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 (186) 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 +49 -4
  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/HotModuleReplacementPlugin.js +18 -15
  16. package/lib/Module.js +3 -0
  17. package/lib/ModuleFilenameHelpers.js +2 -2
  18. package/lib/ModuleTypeConstants.js +8 -1
  19. package/lib/MultiCompiler.js +13 -2
  20. package/lib/MultiWatching.js +1 -0
  21. package/lib/NodeStuffPlugin.js +51 -33
  22. package/lib/NormalModule.js +169 -401
  23. package/lib/NormalModuleFactory.js +76 -2
  24. package/lib/ProgressPlugin.js +211 -119
  25. package/lib/ProvidePlugin.js +2 -2
  26. package/lib/RuntimeGlobals.js +47 -0
  27. package/lib/RuntimePlugin.js +33 -0
  28. package/lib/RuntimeTemplate.js +304 -16
  29. package/lib/UseStrictPlugin.js +1 -1
  30. package/lib/WebpackIsIncludedPlugin.js +1 -1
  31. package/lib/WebpackOptionsApply.js +24 -19
  32. package/lib/asset/AssetGenerator.js +110 -28
  33. package/lib/asset/AssetModulesPlugin.js +92 -2
  34. package/lib/asset/WebManifestGenerator.js +164 -0
  35. package/lib/asset/WebManifestParser.js +130 -0
  36. package/lib/async-modules/AwaitDependenciesInitFragment.js +14 -4
  37. package/lib/async-modules/isGeneratorLowered.js +26 -0
  38. package/lib/cache/PackFileCacheStrategy.js +9 -3
  39. package/lib/config/browserslistTargetHandler.js +40 -0
  40. package/lib/config/defaults.js +219 -29
  41. package/lib/config/normalization.js +26 -0
  42. package/lib/config/target.js +9 -1
  43. package/lib/css/CssGenerator.js +40 -31
  44. package/lib/css/CssLoadingRuntimeModule.js +5 -1
  45. package/lib/css/CssModulesPlugin.js +13 -0
  46. package/lib/css/CssParser.js +594 -263
  47. package/lib/css/syntax.js +875 -462
  48. package/lib/dependencies/AMDDefineDependencyParserPlugin.js +6 -6
  49. package/lib/dependencies/AMDPlugin.js +2 -2
  50. package/lib/dependencies/AMDRequireDependenciesBlockParserPlugin.js +13 -13
  51. package/lib/dependencies/CommonJsDependencyHelpers.js +39 -0
  52. package/lib/dependencies/CommonJsExportsDependency.js +47 -2
  53. package/lib/dependencies/CommonJsExportsParserPlugin.js +10 -10
  54. package/lib/dependencies/CommonJsImportsParserPlugin.js +18 -18
  55. package/lib/dependencies/CommonJsPlugin.js +2 -2
  56. package/lib/dependencies/CommonJsRequireDependency.js +26 -1
  57. package/lib/dependencies/CommonJsSelfReferenceDependency.js +32 -1
  58. package/lib/dependencies/ContextDependencyHelpers.js +3 -3
  59. package/lib/dependencies/CreateRequireParserPlugin.js +6 -6
  60. package/lib/dependencies/CssIcssExportDependency.js +162 -71
  61. package/lib/dependencies/CssUrlDependency.js +35 -4
  62. package/lib/dependencies/ExternalModuleInitFragmentDependency.js +2 -2
  63. package/lib/dependencies/HarmonyCompatibilityDependency.js +32 -14
  64. package/lib/dependencies/HarmonyDetectionParserPlugin.js +43 -13
  65. package/lib/dependencies/HarmonyExportDependencyParserPlugin.js +7 -19
  66. package/lib/dependencies/HarmonyImportDependency.js +12 -1
  67. package/lib/dependencies/HarmonyImportDependencyParserPlugin.js +11 -11
  68. package/lib/dependencies/HarmonyModulesPlugin.js +6 -0
  69. package/lib/dependencies/HarmonyTopLevelThisParserPlugin.js +1 -1
  70. package/lib/dependencies/HtmlEntryDependency.js +797 -172
  71. package/lib/dependencies/HtmlSourceDependency.js +26 -0
  72. package/lib/dependencies/ImportDependency.js +43 -1
  73. package/lib/dependencies/ImportMetaContextDependencyParserPlugin.js +31 -18
  74. package/lib/dependencies/ImportMetaContextPlugin.js +38 -7
  75. package/lib/dependencies/ImportMetaGlobDependency.js +81 -0
  76. package/lib/dependencies/ImportMetaGlobDependencyParserPlugin.js +78 -0
  77. package/lib/dependencies/ImportMetaGlobHelpers.js +660 -0
  78. package/lib/dependencies/ImportMetaPlugin.js +532 -180
  79. package/lib/dependencies/ImportMetaResolveDependency.js +90 -0
  80. package/lib/dependencies/ImportParserPlugin.js +80 -17
  81. package/lib/dependencies/ImportPhase.js +10 -8
  82. package/lib/dependencies/LoaderPlugin.js +5 -1
  83. package/lib/dependencies/RequireContextDependencyParserPlugin.js +1 -1
  84. package/lib/dependencies/RequireEnsureDependenciesBlockParserPlugin.js +3 -6
  85. package/lib/dependencies/RequireIncludeDependencyParserPlugin.js +4 -11
  86. package/lib/dependencies/SystemPlugin.js +2 -4
  87. package/lib/dependencies/TopLevelAwaitDependency.js +93 -0
  88. package/lib/dependencies/URLDependency.js +141 -21
  89. package/lib/dependencies/{WorkerPlugin.js → WorkerAndWorkletPlugin.js} +324 -112
  90. package/lib/dependencies/WorkerDependency.js +86 -18
  91. package/lib/dependencies/WorkletDependency.js +278 -0
  92. package/lib/dll/DelegatedModuleFactoryPlugin.js +19 -0
  93. package/lib/errors/EnvironmentNotSupportAsyncWarning.js +1 -1
  94. package/lib/errors/LoaderLoadingError.js +20 -0
  95. package/lib/errors/ModuleParseError.js +39 -22
  96. package/lib/errors/NodeStuffInWebError.js +1 -1
  97. package/lib/esm/ModuleChunkFormatPlugin.js +47 -14
  98. package/lib/esm/ModuleChunkLoadingPlugin.js +8 -1
  99. package/lib/esm/ModuleChunkLoadingRuntimeModule.js +29 -1
  100. package/lib/hmr/lazyCompilationBackend.js +32 -8
  101. package/lib/html/HtmlGenerator.js +887 -15
  102. package/lib/html/HtmlModule.js +2 -2
  103. package/lib/html/HtmlModulesPlugin.js +610 -166
  104. package/lib/html/HtmlParser.js +603 -247
  105. package/lib/html/favicon.svg +1 -0
  106. package/lib/html/syntax.js +1264 -661
  107. package/lib/ids/IdHelpers.js +1 -1
  108. package/lib/index.js +6 -0
  109. package/lib/javascript/ArrayPushCallbackChunkFormatPlugin.js +4 -5
  110. package/lib/javascript/BasicEvaluatedExpression.js +366 -48
  111. package/lib/javascript/JavascriptGenerator.js +197 -49
  112. package/lib/javascript/JavascriptModulesPlugin.js +178 -24
  113. package/lib/javascript/JavascriptParser.js +701 -355
  114. package/lib/javascript/JavascriptParserHelpers.js +3 -6
  115. package/lib/javascript/syntax.js +4380 -0
  116. package/lib/library/ModuleLibraryPlugin.js +38 -1
  117. package/lib/loaders/LoaderRunner.js +714 -0
  118. package/lib/loaders/loadLoader.js +100 -0
  119. package/lib/node/ReadFileCompileAsyncWasmPlugin.js +19 -0
  120. package/lib/node/ReadFileCompileWasmPlugin.js +14 -0
  121. package/lib/optimize/ConcatenatedModule.js +120 -16
  122. package/lib/optimize/FlagIncludedChunksPlugin.js +13 -3
  123. package/lib/optimize/InnerGraphPlugin.js +50 -30
  124. package/lib/optimize/ModuleConcatenationPlugin.js +69 -3
  125. package/lib/optimize/SideEffectsFlagPlugin.js +86 -43
  126. package/lib/prefetch/ChunkPrefetchPreloadPlugin.js +15 -0
  127. package/lib/prefetch/ChunkPreloadTriggerRuntimeModule.js +24 -13
  128. package/lib/prefetch/ResourceHintPlugin.js +663 -0
  129. package/lib/prefetch/ResourceHintRuntimeModule.js +149 -0
  130. package/lib/prefetch/StartupAssetHintRuntimeModule.js +210 -0
  131. package/lib/prefetch/parseResourceHintOptions.js +103 -0
  132. package/lib/rules/RuleSetCompiler.js +114 -0
  133. package/lib/runtime/AsyncModuleGeneratorRuntimeModule.js +56 -0
  134. package/lib/runtime/AsyncModuleRuntimeModule.js +24 -8
  135. package/lib/runtime/AutoPublicPathRuntimeModule.js +7 -1
  136. package/lib/runtime/CommonJsWrapRuntimeModule.js +37 -0
  137. package/lib/runtime/GetWorkletBootstrapRuntimeModule.js +59 -0
  138. package/lib/runtime/MakeDeferredNamespaceObjectRuntime.js +204 -8
  139. package/lib/runtime/SetAnonymousDefaultNameRuntimeModule.js +4 -1
  140. package/lib/schemes/HttpUriPlugin.js +40 -1
  141. package/lib/schemes/VirtualUrlPlugin.js +1 -2
  142. package/lib/serialization/FileMiddleware.js +29 -3
  143. package/lib/stats/DefaultStatsFactoryPlugin.js +32 -3
  144. package/lib/stats/DefaultStatsPresetPlugin.js +2 -0
  145. package/lib/stats/DefaultStatsPrinterPlugin.js +12 -2
  146. package/lib/stats/StatsFactory.js +15 -8
  147. package/lib/url/URLParserPlugin.js +66 -13
  148. package/lib/util/AsyncQueue.js +122 -72
  149. package/lib/util/SetHelpers.js +1 -1
  150. package/lib/util/StackedMap.js +122 -48
  151. package/lib/util/binarySearchBounds.js +2 -2
  152. package/lib/util/concatenate.js +40 -14
  153. package/lib/util/createHooksRegistry.js +13 -5
  154. package/lib/util/dataURL.js +2 -2
  155. package/lib/util/deterministicGrouping.js +7 -5
  156. package/lib/util/formatSize.js +6 -2
  157. package/lib/util/globUtils.js +710 -0
  158. package/lib/util/identifier.js +23 -6
  159. package/lib/util/implicitTypeLoaderFallback.js +62 -0
  160. package/lib/util/internalSerializables.js +88 -71
  161. package/lib/util/magicComment.js +121 -25
  162. package/lib/util/numberHash.js +2 -2
  163. package/lib/wasm-async/AsyncWasmCompileRuntimeModule.js +24 -18
  164. package/lib/wasm-async/AsyncWasmLoadingRuntimeModule.js +33 -22
  165. package/lib/wasm-sync/WasmChunkLoadingRuntimeModule.js +79 -16
  166. package/lib/web/FetchCompileAsyncWasmPlugin.js +19 -0
  167. package/lib/web/FetchCompileWasmPlugin.js +14 -0
  168. package/lib/web/JsonpChunkLoadingRuntimeModule.js +2 -0
  169. package/lib/webpack.js +65 -0
  170. package/lib/webworker/ImportScriptsChunkLoadingRuntimeModule.js +3 -0
  171. package/module.d.ts +21 -0
  172. package/package.json +13 -12
  173. package/schemas/WebpackOptions.check.js +1 -1
  174. package/schemas/WebpackOptions.json +759 -36
  175. package/schemas/plugins/HtmlParserOptions.check.js +1 -1
  176. package/schemas/plugins/ProgressPlugin.check.js +1 -1
  177. package/schemas/plugins/ProgressPlugin.json +17 -1
  178. package/schemas/plugins/container/ContainerReferencePlugin.check.js +1 -1
  179. package/schemas/plugins/container/ContainerReferencePlugin.json +1 -0
  180. package/schemas/plugins/container/ExternalsType.check.js +1 -1
  181. package/schemas/plugins/container/ModuleFederationPlugin.check.js +1 -1
  182. package/schemas/plugins/container/ModuleFederationPlugin.json +1 -0
  183. package/schemas/plugins/css/CssAutoOrModuleParserOptions.check.js +1 -1
  184. package/schemas/plugins/css/CssModuleParserOptions.check.js +1 -1
  185. package/schemas/plugins/css/CssParserOptions.check.js +1 -1
  186. package/types.d.ts +1213 -96
@@ -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,8 @@ 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);
78
85
 
79
86
  // A parsed CSS comment. `loc` is computed on demand — only magic-comment error
80
87
  // warnings read it, so comment-heavy CSS skips the per-comment line/col work.
@@ -82,22 +89,37 @@ const CC_LEFT_CURLY = "{".charCodeAt(0);
82
89
  /** @typedef {{ value: string, range: Range }} Comment */
83
90
 
84
91
  // Newlines (CSS Syntax 3 §3.3) — listed explicitly since there's no preprocessing stage.
85
- const STRING_MULTILINE = /\\[\n\r\f]/g;
86
92
  // https://www.w3.org/TR/css-syntax-3/#whitespace
87
- const TRIM_WHITE_SPACES = /(^[ \t\n\r\f]*|[ \t\n\r\f]*$)/g;
88
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.
89
94
  const PURE_IGNORE_RE = /^\s*cssmodules-pure-ignore(?:\s|$)/;
90
95
  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
96
  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
97
  const OPTIONALLY_VENDOR_PREFIXED_KEYFRAMES_AT_RULE = /^@(?:-\w+-)?keyframes$/;
97
98
  const VENDOR_PREFIX = /^-\w+-/;
98
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;
99
103
  const IS_MODULES = /\.modules?\.[^.]+$/i;
100
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
+
101
123
  // Skip options for a non-CSS-Modules parse: drop the selector prelude (never
102
124
  // walked without modules) plus value / function-arg leaves nothing reads (the
103
125
  // `Ident` visitor no-ops, the `Declaration` visitor returns early, no ICSS).
@@ -120,6 +142,20 @@ const SKIP_NON_MODULES = {
120
142
  ]),
121
143
  selectorPrelude: true
122
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
+ };
123
159
  const CSS_COMMENT = /\/\*((?!\*\/)[\s\S]*?)\*\//g;
124
160
  // `@value` recognizers (postcss-modules-values shape): the import form `<names> from <source>`, and the `<importName> as <localName>` alias inside it.
125
161
  const VALUE_IMPORT_FORM = /from(\/\*|\s)(?:[\s\S]+)$/i;
@@ -149,110 +185,6 @@ const matchAll = (regexp, str) => {
149
185
  return result;
150
186
  };
151
187
 
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
188
  /**
257
189
  * Range-keyed index over a known-properties table: ASCII-case-folded 31-hash
258
190
  * of the name bytes → canonical key(s). Lets the Declaration visitor answer
@@ -312,16 +244,6 @@ const knownPropertyForRange = (index, input, start, end) => {
312
244
  return undefined;
313
245
  };
314
246
 
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
247
  /** @type {Record<string, number>} */
326
248
  const PREDEFINED_COUNTER_STYLES = {
327
249
  decimal: 1,
@@ -561,6 +483,22 @@ const buildKnownProperties = (options = {}) => {
561
483
  "spell-out": 1,
562
484
  ...PREDEFINED_COUNTER_STYLES
563
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
+ });
564
502
  }
565
503
 
566
504
  if (options.grid) {
@@ -606,24 +544,8 @@ const getKnownProperties = (options = {}) => {
606
544
  return table;
607
545
  };
608
546
 
609
- const EMPTY_COMMENT_OPTIONS = {
610
- options: null,
611
- errors: null
612
- };
613
-
614
547
  // Byte-level source-cursor scans for computing replacement / strip ranges on raw source after parsing.
615
548
 
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
549
  /**
628
550
  * Skip trailing whitespace + at most one newline (CRLF-aware).
629
551
  * @param {string} input source
@@ -943,6 +865,8 @@ class CssParser extends Parser {
943
865
  animation: true,
944
866
  container: true,
945
867
  customIdents: true,
868
+ customMedia: true,
869
+ customSelectors: true,
946
870
  dashedIdents: true,
947
871
  function: true,
948
872
  grid: true,
@@ -1012,6 +936,43 @@ class CssParser extends Parser {
1012
936
  /** @type {Comment[]} */
1013
937
  const comments = [];
1014
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
+
1015
976
  const module = state.module;
1016
977
 
1017
978
  // All `:export`-style exports are collected into a single
@@ -1162,14 +1123,21 @@ class CssParser extends Parser {
1162
1123
  };
1163
1124
 
1164
1125
  /**
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).
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.
1166
1127
  * @param {Range} range byte range to scan for magic comments
1167
1128
  * @param {number} warnStart start offset of the loc for an invalid-`webpackIgnore` warning (computed lazily)
1168
1129
  * @param {number} warnEnd end offset of that loc
1169
- * @returns {boolean} true when `webpackIgnore: true`
1130
+ * @returns {{ ignored: boolean, options: Record<string, EXPECTED_ANY> | null }} parsed options and `webpackIgnore` flag
1170
1131
  */
1171
- const webpackIgnored = (range, warnStart, warnEnd) => {
1172
- 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
+ );
1173
1141
  if (errors) {
1174
1142
  for (const e of errors) {
1175
1143
  state.module.addWarning(
@@ -1180,6 +1148,7 @@ class CssParser extends Parser {
1180
1148
  );
1181
1149
  }
1182
1150
  }
1151
+ let ignored = false;
1183
1152
  if (options && options.webpackIgnore !== undefined) {
1184
1153
  if (typeof options.webpackIgnore !== "boolean") {
1185
1154
  // Loc is computed lazily here — it's only needed for this rare
@@ -1191,15 +1160,38 @@ class CssParser extends Parser {
1191
1160
  )
1192
1161
  );
1193
1162
  } else {
1194
- return options.webpackIgnore;
1163
+ ignored = options.webpackIgnore;
1195
1164
  }
1196
1165
  }
1197
- return false;
1166
+ return { ignored, options };
1198
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;
1199
1177
 
1200
1178
  // Closure-scope alias for `source` used by AST-walking helpers for substring extraction.
1201
1179
  const input = source;
1202
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
+
1203
1195
  /**
1204
1196
  * Unescape a CSS identifier from a source byte range — for value spans not
1205
1197
  * backed by a single token (string contents, `--` dashed-ident bodies,
@@ -1393,7 +1385,7 @@ class CssParser extends Parser {
1393
1385
  modeData ? modeData === "local" : mode === "local";
1394
1386
 
1395
1387
  /**
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).
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).
1397
1389
  * @param {import("./syntax").CssPath} path walk path at the comment node
1398
1390
  */
1399
1391
  const commentVisitor = (path) => {
@@ -1424,88 +1416,6 @@ class CssParser extends Parser {
1424
1416
  };
1425
1417
  })();
1426
1418
 
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
1419
  // CSS modules stuff
1510
1420
 
1511
1421
  /**
@@ -2000,7 +1910,7 @@ class CssParser extends Parser {
2000
1910
 
2001
1911
  /**
2002
1912
  * 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 }[]}
1913
+ * @type {{ savedAnchor: boolean, savedLocalIdentifiers: string[], name: string, hasBlock: boolean, endsWithSemicolon: boolean, fontPreloaded: boolean }[]}
2004
1914
  */
2005
1915
  const atRuleStateStack = [];
2006
1916
 
@@ -2052,7 +1962,7 @@ class CssParser extends Parser {
2052
1962
  if (isLocal) {
2053
1963
  while (
2054
1964
  trailStart > 0 &&
2055
- isCssWhitespace(source.charCodeAt(trailStart - 1))
1965
+ isWhitespace(source.charCodeAt(trailStart - 1))
2056
1966
  ) {
2057
1967
  trailStart--;
2058
1968
  }
@@ -2138,6 +2048,37 @@ class CssParser extends Parser {
2138
2048
  );
2139
2049
  };
2140
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
+
2141
2082
  /**
2142
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).
2143
2084
  * @param {AstNode[]} values component values to walk
@@ -2221,10 +2162,22 @@ class CssParser extends Parser {
2221
2162
  }
2222
2163
  break;
2223
2164
  }
2224
- 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
+ }
2225
2177
  // Any other function (`:not(…)`, `:is(…)`, …): recurse with the segment mode preserved (only `:local(…)` / `:global(…)`, handled above, switch mode).
2226
2178
  walkSelectorList(A.children(v), segmentMode, false);
2227
2179
  break;
2180
+ }
2228
2181
  case NodeType.Hash: {
2229
2182
  if (A.typeFlag(v) !== "id" || segmentMode !== "local") {
2230
2183
  break;
@@ -2347,6 +2300,8 @@ class CssParser extends Parser {
2347
2300
  name === "@namespace" ||
2348
2301
  name === "@value" ||
2349
2302
  name === "@scope" ||
2303
+ (mayHaveCustomMedia && name === "@custom-media") ||
2304
+ (mayHaveCustomSelectors && name === "@custom-selector") ||
2350
2305
  (isModules &&
2351
2306
  ((this.options.animation &&
2352
2307
  OPTIONALLY_VENDOR_PREFIXED_KEYFRAMES_AT_RULE.test(name)) ||
@@ -2378,7 +2333,10 @@ class CssParser extends Parser {
2378
2333
  const t = A.type(currentStructural);
2379
2334
  if (t === NodeType.AtRule) {
2380
2335
  return (
2381
- currentAtRuleName !== "@value" && currentAtRuleName !== "@import"
2336
+ currentAtRuleName !== "@value" &&
2337
+ currentAtRuleName !== "@import" &&
2338
+ currentAtRuleName !== "@custom-media" &&
2339
+ currentAtRuleName !== "@custom-selector"
2382
2340
  );
2383
2341
  }
2384
2342
  if (t === NodeType.Declaration) {
@@ -2671,7 +2629,7 @@ class CssParser extends Parser {
2671
2629
  let resumeAt = A.end(decl);
2672
2630
  if (source.charCodeAt(A.end(decl)) === CC_SEMICOLON) {
2673
2631
  resumeAt = A.end(decl) + 1;
2674
- while (isCssWhitespace(source.charCodeAt(resumeAt))) resumeAt++;
2632
+ while (isWhitespace(source.charCodeAt(resumeAt))) resumeAt++;
2675
2633
  }
2676
2634
  module.addPresentationalDependency(
2677
2635
  new ConstDependency("", [A.nameStart(decl), resumeAt])
@@ -2689,15 +2647,12 @@ class CssParser extends Parser {
2689
2647
  const first = A.children(fn)[nextNonWhitespace(A.children(fn), 0)];
2690
2648
  if (!first || A.type(first) !== NodeType.String) return;
2691
2649
  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
- }
2650
+ const { ignored, options } = magicCommentsIn(
2651
+ [lastTokenEndForComments, A.start(fn)],
2652
+ A.start(string),
2653
+ A.end(string)
2654
+ );
2655
+ if (ignored) return;
2701
2656
  const value = normalizeUrl(
2702
2657
  input.slice(A.start(string) + 1, A.end(string) - 1),
2703
2658
  true
@@ -2710,6 +2665,12 @@ class CssParser extends Parser {
2710
2665
  "string"
2711
2666
  );
2712
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
+ );
2713
2674
  module.addDependency(dep);
2714
2675
  module.addCodeGenerationDependency(dep);
2715
2676
  } else if (IMAGE_SET_FUNCTION.test(fname)) {
@@ -2734,21 +2695,24 @@ class CssParser extends Parser {
2734
2695
  true
2735
2696
  );
2736
2697
  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
- }
2698
+ const { ignored, options } = magicCommentsIn(
2699
+ [start, A.end(string)],
2700
+ A.start(string),
2701
+ A.end(string)
2702
+ );
2703
+ if (ignored) continue;
2746
2704
  const dep = new CssUrlDependency(
2747
2705
  value,
2748
2706
  [A.start(string), A.end(string)],
2749
2707
  "url"
2750
2708
  );
2751
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
+ );
2752
2716
  module.addDependency(dep);
2753
2717
  module.addCodeGenerationDependency(dep);
2754
2718
  }
@@ -3072,6 +3036,336 @@ class CssParser extends Parser {
3072
3036
  module.addPresentationalDependency(dep);
3073
3037
  };
3074
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
+
3075
3369
  /**
3076
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.
3077
3371
  * @param {Declaration} decl the declaration
@@ -3082,6 +3376,10 @@ class CssParser extends Parser {
3082
3376
  /** @type {Record<string, number>} */
3083
3377
  let parsedKeywords = Object.create(null);
3084
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
+ );
3085
3383
  const isGridTemplate = isGridProperty
3086
3384
  ? Boolean(
3087
3385
  declPropertyName === "grid" ||
@@ -3141,6 +3439,13 @@ class CssParser extends Parser {
3141
3439
  break;
3142
3440
  }
3143
3441
  const identifier = A.value(cv);
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
+ }
3144
3449
  // Values are almost always lowercase already — avoid the copy.
3145
3450
  const keyword = toLowerCaseIfNeeded(identifier);
3146
3451
  parsedKeywords[keyword] =
@@ -3300,6 +3605,18 @@ class CssParser extends Parser {
3300
3605
  handleImportAtRule(at, topLevel);
3301
3606
  break;
3302
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
+ }
3303
3620
  default: {
3304
3621
  if (!isModules) break;
3305
3622
  if (name === "@value") {
@@ -3357,7 +3674,8 @@ class CssParser extends Parser {
3357
3674
  this.options.dashedIdents &&
3358
3675
  isModules &&
3359
3676
  !isProcessedByLocalAtRule &&
3360
- effectiveLocalMode
3677
+ effectiveLocalMode &&
3678
+ !(mayHaveCustomMedia && name === "@media")
3361
3679
  );
3362
3680
  dashed.emit = dashed.active;
3363
3681
 
@@ -3410,7 +3728,8 @@ class CssParser extends Parser {
3410
3728
  savedLocalIdentifiers,
3411
3729
  name,
3412
3730
  hasBlock: atHasBlock,
3413
- endsWithSemicolon: source.charCodeAt(A.end(at)) === CC_SEMICOLON
3731
+ endsWithSemicolon: source.charCodeAt(A.end(at)) === CC_SEMICOLON,
3732
+ fontPreloaded: false
3414
3733
  });
3415
3734
  },
3416
3735
  // At-rule exit: pure-frame finalization, `suppressNextRulePrelude`, scope restore, top-level reset.
@@ -3440,6 +3759,8 @@ class CssParser extends Parser {
3440
3759
  const rule = /** @type {QualifiedRule} */ (node);
3441
3760
  const topLevel = path.parent === null;
3442
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));
3443
3764
  // `:import(…) { … }` / `:export { … }` ICSS pseudo-rules are processed inline at top level; nested ones bail out.
3444
3765
  if (isModules) {
3445
3766
  const rulePrelude = A.prelude(rule);
@@ -3673,15 +3994,12 @@ class CssParser extends Parser {
3673
3994
  ) {
3674
3995
  return;
3675
3996
  }
3676
- if (
3677
- webpackIgnored(
3678
- [lastTokenEndForComments, A.end(node)],
3679
- lastTokenEndForComments,
3680
- A.end(node)
3681
- )
3682
- ) {
3683
- return;
3684
- }
3997
+ const { ignored, options: urlComments } = magicCommentsIn(
3998
+ [lastTokenEndForComments, A.end(node)],
3999
+ lastTokenEndForComments,
4000
+ A.end(node)
4001
+ );
4002
+ if (ignored) return;
3685
4003
  let value = normalizeUrl(
3686
4004
  input.slice(A.contentStart(url), A.contentEnd(url)),
3687
4005
  false
@@ -3717,6 +4035,12 @@ class CssParser extends Parser {
3717
4035
  "url"
3718
4036
  );
3719
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
+ );
3720
4044
  module.addDependency(dep);
3721
4045
  module.addCodeGenerationDependency(dep);
3722
4046
  },
@@ -3842,11 +4166,18 @@ class CssParser extends Parser {
3842
4166
  // and ICSS-captured values).
3843
4167
  new SourceProcessor({
3844
4168
  as: /** @type {"stylesheet" | "block-contents"} */ (this.options.as),
3845
- skip: isModules ? undefined : SKIP_NON_MODULES
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
3846
4175
  })
3847
4176
  .use(/** @type {VisitorMap} */ (visitors))
3848
4177
  .process(source, { locConverter });
3849
4178
 
4179
+ if (customMediaUses || customSelectorUses) resolveCustomMediaAndSelectors();
4180
+
3850
4181
  /** @type {BuildInfo} */
3851
4182
  (module.buildInfo).strict = true;
3852
4183