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
package/lib/css/syntax.js CHANGED
@@ -199,6 +199,7 @@ const CC_AT_SIGN = "@".charCodeAt(0);
199
199
 
200
200
  const CC_LOW_LINE = "_".charCodeAt(0);
201
201
  const CC_LOWER_A = "a".charCodeAt(0);
202
+ const CC_LOWER_D = "d".charCodeAt(0);
202
203
  const CC_LOWER_F = "f".charCodeAt(0);
203
204
  const CC_LOWER_E = "e".charCodeAt(0);
204
205
  const CC_LOWER_U = "u".charCodeAt(0);
@@ -341,11 +342,15 @@ const _isIdentStartCodePointCC = (cc) =>
341
342
  /**
342
343
  * Spec: ident-code = ident-start / digit / hyphen-minus.
343
344
  */
344
- // ASCII lookup built from the predicate so behaviour is identical; non-ASCII
345
- // (>= 128) keeps the inline check. `_consumeAnIdentSequence` hits this per code
346
- // point, and class/property names (hyphen + digit heavy) are the longest
347
- // comparison-chain path — a table load is constant-time instead.
348
- const _identCharTable = new Uint8Array(128);
345
+ // Full `charCodeAt` range (0..0xFFFF) so the per-code-point ident test is one
346
+ // table load with no `cc < 128` branch — `_consumeAnIdentSequence` runs this on
347
+ // every character of every ident / class / property name (the tokenizer's
348
+ // hottest loop). Every non-ASCII code unit (>= 0x80) is an ident code point per
349
+ // spec, so those default to 1; only the ASCII rows carry real classification.
350
+ // Callers must index with `cc | 0`: EOF (`charCodeAt` → NaN) becomes 0 (NUL,
351
+ // not an ident) — a raw NaN index is an out-of-range access that permanently
352
+ // degrades the load site's IC.
353
+ const _identCharTable = new Uint8Array(0x10000).fill(1);
349
354
  for (let i = 0; i < 128; i++) {
350
355
  _identCharTable[i] =
351
356
  _isLetter(i) || i === CC_LOW_LINE || _isDigit(i) || i === CC_HYPHEN_MINUS
@@ -356,8 +361,7 @@ for (let i = 0; i < 128; i++) {
356
361
  * @param {number} cc char code
357
362
  * @returns {boolean} true, if cc is an ident-sequence code point
358
363
  */
359
- const _isIdentCodePoint = (cc) =>
360
- cc < 128 ? _identCharTable[cc] === 1 : _isIdentStartCodePointCC(cc);
364
+ const _isIdentCodePoint = (cc) => _identCharTable[cc | 0] === 1;
361
365
 
362
366
  /**
363
367
  * ASCII case-insensitive equality against a lowercase literal — avoids the
@@ -377,6 +381,58 @@ const equalsLowerCase = (s, lit) => {
377
381
  return true;
378
382
  };
379
383
 
384
+ /**
385
+ * Case-sensitive equality of a source range against a literal — no slice.
386
+ * @param {string} input source
387
+ * @param {number} start range start
388
+ * @param {number} end range end (exclusive)
389
+ * @param {string} lit literal to match
390
+ * @returns {boolean} true when the range equals `lit`
391
+ */
392
+ const rangeEquals = (input, start, end, lit) =>
393
+ end - start === lit.length && input.startsWith(lit, start);
394
+
395
+ /**
396
+ * ASCII case-insensitive equality of a source range against a lowercase ASCII literal — no slice.
397
+ * @param {string} input source
398
+ * @param {number} start range start
399
+ * @param {number} end range end (exclusive)
400
+ * @param {string} lit lowercase ASCII literal to match
401
+ * @returns {boolean} true when the range equals `lit` ignoring ASCII case
402
+ */
403
+ const rangeEqualsLowerCase = (input, start, end, lit) => {
404
+ if (end - start !== lit.length) return false;
405
+ for (let i = 0; i < lit.length; i++) {
406
+ let c = input.charCodeAt(start + i);
407
+ if (c >= CC_UPPER_A && c <= CC_UPPER_Z) c |= 0x20;
408
+ if (c !== lit.charCodeAt(i)) return false;
409
+ }
410
+ return true;
411
+ };
412
+
413
+ /**
414
+ * `s.toLowerCase()` that returns `s` itself (no allocation) when it can't
415
+ * change — no ASCII uppercase and no non-ASCII (whose Unicode case mapping is
416
+ * left to the real `toLowerCase`).
417
+ * @param {string} s string
418
+ * @returns {string} lowercased string
419
+ */
420
+ const toLowerCaseIfNeeded = (s) => {
421
+ for (let i = 0; i < s.length; i++) {
422
+ const c = s.charCodeAt(i);
423
+ if ((c >= CC_UPPER_A && c <= CC_UPPER_Z) || c > 127) return s.toLowerCase();
424
+ }
425
+ return s;
426
+ };
427
+
428
+ /**
429
+ * A custom property name (`<dashed-ident>`): a `--`-prefixed identifier other than bare `--`.
430
+ * @param {string} identifier identifier
431
+ * @returns {boolean} true when identifier is dashed, otherwise false
432
+ */
433
+ const isDashedIdentifier = (identifier) =>
434
+ identifier.startsWith("--") && identifier.length >= 3;
435
+
380
436
  /**
381
437
  * Consume an escaped code point.
382
438
  * @param {string} input input
@@ -394,7 +450,8 @@ const _consumeAnEscapedCodePoint = (input, pos) => {
394
450
  if (pos === input.length) return pos;
395
451
  if (_isHexDigit(cc)) {
396
452
  for (let i = 0; i < 5; i++) {
397
- if (_isHexDigit(input.charCodeAt(pos))) pos++;
453
+ if (!_isHexDigit(input.charCodeAt(pos))) break;
454
+ pos++;
398
455
  }
399
456
  const trail = input.charCodeAt(pos);
400
457
  if (_isWhiteSpace(trail)) {
@@ -480,12 +537,13 @@ const _ifThreeCodePointsWouldStartANumber = (input, pos, f, s, t) => {
480
537
  const _consumeAnIdentSequence = (input, pos) => {
481
538
  // Hot loop (every ident, at-keyword, hash, function name, unit). Both checks
482
539
  // are inlined from `_isIdentCodePoint` / `_ifTwoCodePointsAreValidEscape`: the
483
- // ASCII ident test is a single table load, and the escape test reads the
484
- // following code point only when `cc` is a `\` (rare) instead of eagerly.
540
+ // ident test is a single full-range table load (no `cc < 128` branch), and the
541
+ // escape test reads the following code point only when `cc` is a `\` (rare)
542
+ // instead of eagerly.
485
543
  for (;;) {
486
- const cc = input.charCodeAt(pos);
544
+ const cc = input.charCodeAt(pos) | 0;
487
545
  pos++;
488
- if (cc < 128 ? _identCharTable[cc] === 1 : _isIdentStartCodePointCC(cc)) {
546
+ if (_identCharTable[cc] === 1) {
489
547
  continue;
490
548
  }
491
549
  if (cc === CC_REVERSE_SOLIDUS && !_isNewline(input.charCodeAt(pos))) {
@@ -700,17 +758,33 @@ function consumeNumberSign(input, pos, out) {
700
758
  * @returns {MutableToken | undefined} the resulting token, or undefined at EOF
701
759
  */
702
760
  function consumeHyphenMinus(input, pos, out) {
703
- if (_ifThreeCodePointsWouldStartANumber(input, pos)) {
761
+ // Read the two lookahead code points once; the lead is the known `-`.
762
+ const second = input.charCodeAt(pos);
763
+ const third = input.charCodeAt(pos + 1);
764
+ if (
765
+ _ifThreeCodePointsWouldStartANumber(
766
+ input,
767
+ pos,
768
+ CC_HYPHEN_MINUS,
769
+ second,
770
+ third
771
+ )
772
+ ) {
704
773
  pos--;
705
774
  return consumeANumericToken(input, pos, out);
706
775
  }
707
- if (
708
- input.charCodeAt(pos) === CC_HYPHEN_MINUS &&
709
- input.charCodeAt(pos + 1) === CC_GREATER_THAN_SIGN
710
- ) {
776
+ if (second === CC_HYPHEN_MINUS && third === CC_GREATER_THAN_SIGN) {
711
777
  return fill(out, TT_CDC, pos - 1, pos + 2);
712
778
  }
713
- if (_ifThreeCodePointsWouldStartAnIdentSequence(input, pos)) {
779
+ if (
780
+ _ifThreeCodePointsWouldStartAnIdentSequence(
781
+ input,
782
+ pos,
783
+ CC_HYPHEN_MINUS,
784
+ second,
785
+ third
786
+ )
787
+ ) {
714
788
  pos--;
715
789
  return consumeAnIdentLikeToken(input, pos, out);
716
790
  }
@@ -801,7 +875,7 @@ function consumeAUrlToken(input, pos, fnStart, out) {
801
875
  out.contentStart = contentStart;
802
876
  for (;;) {
803
877
  if (pos === input.length) {
804
- out.contentEnd = pos - 1;
878
+ out.contentEnd = pos;
805
879
  return fill(out, TT_URL, fnStart, pos);
806
880
  }
807
881
  const cc = input.charCodeAt(pos);
@@ -999,7 +1073,11 @@ const HC_AT_SIGN = 9;
999
1073
  const HC_REVERSE_SOLIDUS = 10;
1000
1074
  const HC_DIGIT = 11;
1001
1075
  const HC_IDENT = 12;
1002
- const _charClass = new Uint8Array(128);
1076
+ // Full `charCodeAt` range so `consumeAToken` dispatches with one table load and
1077
+ // no `cc < 128` branch. Every non-ASCII code point (>= 0x80) is an ident-start
1078
+ // lead per §4, so those rows are seeded to `HC_IDENT`; the ASCII rows below
1079
+ // overwrite 0..127 with their real class.
1080
+ const _charClass = new Uint8Array(0x10000).fill(HC_IDENT, 128);
1003
1081
  const _singleTT = new Uint8Array(128);
1004
1082
  _singleTT[CC_LEFT_PARENTHESIS] = TT_LEFT_PARENTHESIS;
1005
1083
  _singleTT[CC_RIGHT_PARENTHESIS] = TT_RIGHT_PARENTHESIS;
@@ -1054,7 +1132,7 @@ for (let i = 0; i < 128; i++) {
1054
1132
  function consumeAToken(input, pos, cc, out) {
1055
1133
  // `u` / `U` would start a unicode-range token in the spec; those are not
1056
1134
  // produced, so they map to HC_IDENT and fall through to ident-like.
1057
- switch (cc < 128 ? _charClass[cc] : HC_IDENT) {
1135
+ switch (_charClass[cc]) {
1058
1136
  // Run of whitespace → one <whitespace-token>.
1059
1137
  case HC_WHITESPACE:
1060
1138
  return consumeSpace(input, pos, out);
@@ -1605,7 +1683,9 @@ _ttToNodeType[TT_CDC] = T_CDC;
1605
1683
  // field access differs, so only those ops are swapped.
1606
1684
 
1607
1685
  // Current loc converter for the active parse (object backend reads it).
1608
- let _lc = /** @type {LocConverter} */ (/** @type {unknown} */ (null));
1686
+ let _objLocConverter = /** @type {LocConverter} */ (
1687
+ /** @type {unknown} */ (null)
1688
+ );
1609
1689
 
1610
1690
  // Active skip state (from `CssProcessOptions.skip`), applied by the grammar.
1611
1691
  // `_skipTypes` is indexed by `NodeType` (1 = skip): drop that component-value
@@ -1616,6 +1696,13 @@ let _lc = /** @type {LocConverter} */ (/** @type {unknown} */ (null));
1616
1696
  // so it is never walked or read — the caller must only skip what nothing reads.
1617
1697
  // `useObjectBackend` restores the defaults so `parseA*` build the full tree.
1618
1698
  const _NO_SKIP_TYPES = new Uint8Array(32);
1699
+ // Shared frozen empty list for block bodies with no decls / no child rules (the
1700
+ // common case — most rules carry only declarations). Every consumer reads these
1701
+ // lists read-only and null-guards, so one immutable instance replaces ~one empty
1702
+ // array allocation per rule; frozen so any errant push fails loud.
1703
+ const _EMPTY_LIST = /** @type {Rule[]} */ (
1704
+ /** @type {unknown} */ (Object.freeze([]))
1705
+ );
1619
1706
  /** @type {Uint8Array} */
1620
1707
  let _skipTypes = _NO_SKIP_TYPES;
1621
1708
  // Fast-path flag: true only when a real skip set is active, so the (dominant)
@@ -1625,14 +1712,17 @@ let _skipSelectorPrelude = false;
1625
1712
  let _skipAtRulePrelude = false;
1626
1713
 
1627
1714
  /** @type {(type: number, start: number, end: number) => Node} */
1628
- let _mkLeaf;
1715
+ let _makeLeaf;
1629
1716
  /** @type {(start: number, end: number, contentStart: number, contentEnd: number) => Node} */
1630
- let _mkUrl;
1717
+ let _makeUrl;
1631
1718
  /** @type {(type: number, start: number, end: number) => Node} */
1632
- let _mkContainer;
1719
+ let _makeContainer;
1633
1720
  /** @type {(start: number) => Node} */
1634
- let _mkStylesheet;
1635
- /** @type {(r: Node, v: string, nameStart: number, nameEnd: number) => void} */
1721
+ let _makeStylesheet;
1722
+ // Offsets only — the object backend derives the name string itself (an
1723
+ // at-rule's name skips its `@`), so the SoA backend never pays for a slice
1724
+ // it would immediately discard (it re-derives names from offsets on read).
1725
+ /** @type {(r: Node, nameStart: number, nameEnd: number) => void} */
1636
1726
  let _setName;
1637
1727
  /** @type {(r: Node, v: number) => void} */
1638
1728
  let _setEnd;
@@ -1651,182 +1741,264 @@ let _setBody;
1651
1741
  /** @type {(r: Node, list: Node[]) => void} */
1652
1742
  let _setRules;
1653
1743
  /** @type {(r: Node) => number} */
1654
- let _nType;
1744
+ let _nodeTypeOf;
1655
1745
  /** @type {(r: Node) => number} */
1656
- let _nStart;
1746
+ let _nodeStartOf;
1657
1747
  /** @type {(r: Node) => string} */
1658
- let _nValue;
1748
+ let _nodeValueOf;
1659
1749
  /** @type {(r: Node) => SimpleBlockToken} */
1660
- let _nToken;
1750
+ let _nodeTokenOf;
1661
1751
 
1662
1752
  // Child lists are plain arrays in every backend; refs are pushed by value.
1663
- const _list = () => /** @type {Node[]} */ ([]);
1753
+ // Content-list allocation: the object backend uses a per-site `[]` literal
1754
+ // (each site keeps its own V8 allocation site — a shared helper collapses them
1755
+ // and defeats escape analysis / pretenuring), the SoA backend recycles scratch
1756
+ // arrays through a pool — a sealed list is copied into the flat value buffer
1757
+ // and its array returned to the pool by `_soaSetValue`, so the streaming parse
1758
+ // allocates no per-list array. An abandoned (never-sealed) list simply falls
1759
+ // out of the pool. `_soaActive` gates the pool at each site.
1760
+ let _soaActive = false;
1761
+ /** @type {Node[][]} */
1762
+ const _listPool = [];
1763
+ const _soaList = () =>
1764
+ _listPool.length > 0
1765
+ ? /** @type {Node[]} */ (_listPool.pop())
1766
+ : /** @type {Node[]} */ ([]);
1664
1767
 
1665
1768
  // -- object backend: builds the retainable class-instance tree --
1666
- /** @type {typeof _mkLeaf} */
1667
- const _objLeaf = (type, start, end) => new Token(type, start, end, _lc);
1668
- /** @type {typeof _mkUrl} */
1769
+ /** @type {typeof _makeLeaf} */
1770
+ const _objLeaf = (type, start, end) =>
1771
+ new Token(type, start, end, _objLocConverter);
1772
+ /** @type {typeof _makeUrl} */
1669
1773
  const _objUrl = (start, end, cs, ce) => {
1670
- const u = /** @type {UrlToken} */ (new Token(T_URL, start, end, _lc));
1774
+ const u = /** @type {UrlToken} */ (
1775
+ new Token(T_URL, start, end, _objLocConverter)
1776
+ );
1671
1777
  u.contentStart = cs;
1672
1778
  u.contentEnd = ce;
1673
1779
  return u;
1674
1780
  };
1675
- /** @type {typeof _mkContainer} */
1781
+ /** @type {typeof _makeContainer} */
1676
1782
  const _objContainer = (type, start, end) =>
1677
- new Container(type, start, end, _lc);
1678
- /** @type {typeof _mkStylesheet} */
1783
+ new Container(type, start, end, _objLocConverter);
1784
+ /** @type {typeof _makeStylesheet} */
1679
1785
  const _objStylesheet = (start) => {
1680
1786
  const s = /** @type {Stylesheet} */ (
1681
- new Node(T_STYLESHEET, start, start, _lc)
1787
+ new Node(T_STYLESHEET, start, start, _objLocConverter)
1682
1788
  );
1683
1789
  s.rules = [];
1684
1790
  return s;
1685
1791
  };
1792
+ // The backend function sets are module-level constants (not closures rebuilt
1793
+ // per parse): the dispatch slots keep one function identity forever, so the
1794
+ // per-node call sites in the consume algorithms stay monomorphic instead of
1795
+ // seeing a fresh closure per parse. They capture only module-level state.
1796
+ /** @type {typeof _setName} */
1797
+ const _objSetName = (r, ns, ne) => {
1798
+ const c = /** @type {Container} */ (r);
1799
+ // An at-rule's `nameStart` points at its `@`, which the name excludes.
1800
+ c.name = _objLocConverter._input.slice(
1801
+ r.type === T_AT_RULE ? ns + 1 : ns,
1802
+ ne
1803
+ );
1804
+ c.nameStart = ns;
1805
+ c.nameEnd = ne;
1806
+ };
1807
+ /** @type {typeof _setEnd} */
1808
+ const _objSetEnd = (r, v) => {
1809
+ r.end = v;
1810
+ };
1811
+ /** @type {typeof _setBlock} */
1812
+ const _objSetBlock = (r, bs, be) => {
1813
+ const c = /** @type {Container} */ (r);
1814
+ c.blockStart = bs;
1815
+ c.blockEnd = be;
1816
+ };
1817
+ /** @type {typeof _setImportant} */
1818
+ const _objSetImportant = (r) => {
1819
+ /** @type {Container} */ (r).important = true;
1820
+ };
1821
+ /** @type {typeof _setToken} */
1822
+ const _objSetToken = (r, ch) => {
1823
+ /** @type {Container} */ (r).token = ch;
1824
+ };
1825
+ /** @type {typeof _setValue} */
1826
+ const _objSetValue = (r, list) => {
1827
+ /** @type {Container} */ (r).value = /** @type {ComponentValue[]} */ (list);
1828
+ };
1829
+ /** @type {typeof _setPrelude} */
1830
+ const _objSetPrelude = (r, list) => {
1831
+ /** @type {Container} */ (r).prelude = /** @type {ComponentValue[]} */ (list);
1832
+ };
1833
+ /** @type {typeof _setBody} */
1834
+ const _objSetBody = (r, decls, childRules) => {
1835
+ const c = /** @type {Container} */ (r);
1836
+ c.declarations = /** @type {Declaration[]} */ (decls);
1837
+ c.childRules = /** @type {Rule[]} */ (childRules);
1838
+ };
1839
+ /** @type {typeof _setRules} */
1840
+ const _objSetRules = (r, list) => {
1841
+ /** @type {Stylesheet} */ (r).rules = /** @type {Rule[]} */ (list);
1842
+ };
1843
+ /** @type {typeof _nodeTypeOf} */
1844
+ const _objNodeTypeOf = (r) => r.type;
1845
+ /** @type {typeof _nodeStartOf} */
1846
+ const _objNodeStartOf = (r) => r.start;
1847
+ /** @type {typeof _nodeValueOf} */
1848
+ const _objNodeValueOf = (r) => /** @type {Token} */ (r).value;
1849
+ /** @type {typeof _nodeTokenOf} */
1850
+ const _objNodeTokenOf = (r) =>
1851
+ /** @type {SimpleBlockToken} */ (/** @type {Container} */ (r).token);
1686
1852
  /** @param {LocConverter} lc loc converter for this parse */
1687
1853
  const useObjectBackend = (lc) => {
1688
- _lc = lc;
1854
+ _objLocConverter = lc;
1855
+ _soaActive = false;
1689
1856
  // `parseA*` return the full tree, so nothing is skipped.
1690
1857
  _skipTypes = _NO_SKIP_TYPES;
1691
1858
  _skipActive = false;
1692
1859
  _skipSelectorPrelude = false;
1693
1860
  _skipAtRulePrelude = false;
1694
- _mkLeaf = _objLeaf;
1695
- _mkUrl = _objUrl;
1696
- _mkContainer = _objContainer;
1697
- _mkStylesheet = _objStylesheet;
1698
- _setName = (r, v, ns, ne) => {
1699
- const c = /** @type {Container} */ (r);
1700
- c.name = v;
1701
- c.nameStart = ns;
1702
- c.nameEnd = ne;
1703
- };
1704
- _setEnd = (r, v) => {
1705
- r.end = v;
1706
- };
1707
- _setBlock = (r, bs, be) => {
1708
- const c = /** @type {Container} */ (r);
1709
- c.blockStart = bs;
1710
- c.blockEnd = be;
1711
- };
1712
- _setImportant = (r) => {
1713
- /** @type {Container} */ (r).important = true;
1714
- };
1715
- _setToken = (r, ch) => {
1716
- /** @type {Container} */ (r).token = ch;
1717
- };
1718
- _setValue = (r, list) => {
1719
- /** @type {Container} */ (r).value = /** @type {ComponentValue[]} */ (list);
1720
- };
1721
- _setPrelude = (r, list) => {
1722
- /** @type {Container} */ (r).prelude = /** @type {ComponentValue[]} */ (
1723
- list
1724
- );
1725
- };
1726
- _setBody = (r, decls, childRules) => {
1727
- const c = /** @type {Container} */ (r);
1728
- c.declarations = /** @type {Declaration[]} */ (decls);
1729
- c.childRules = /** @type {Rule[]} */ (childRules);
1730
- };
1731
- _setRules = (r, list) => {
1732
- /** @type {Stylesheet} */ (r).rules = /** @type {Rule[]} */ (list);
1733
- };
1734
- _nType = (r) => r.type;
1735
- _nStart = (r) => r.start;
1736
- _nValue = (r) => /** @type {Token} */ (r).value;
1737
- _nToken = (r) =>
1738
- /** @type {SimpleBlockToken} */ (/** @type {Container} */ (r).token);
1861
+ _makeLeaf = _objLeaf;
1862
+ _makeUrl = _objUrl;
1863
+ _makeContainer = _objContainer;
1864
+ _makeStylesheet = _objStylesheet;
1865
+ _setName = _objSetName;
1866
+ _setEnd = _objSetEnd;
1867
+ _setBlock = _objSetBlock;
1868
+ _setImportant = _objSetImportant;
1869
+ _setToken = _objSetToken;
1870
+ _setValue = _objSetValue;
1871
+ _setPrelude = _objSetPrelude;
1872
+ _setBody = _objSetBody;
1873
+ _setRules = _objSetRules;
1874
+ _nodeTypeOf = _objNodeTypeOf;
1875
+ _nodeStartOf = _objNodeStartOf;
1876
+ _nodeValueOf = _objNodeValueOf;
1877
+ _nodeTokenOf = _objNodeTokenOf;
1739
1878
  };
1740
1879
 
1741
1880
  // -- struct-of-arrays backend: writes nodes into reused typed arrays --
1742
1881
  // A node ref is its integer id; fields live in parallel arrays indexed by id.
1743
- // Three reused int slots (`_soaA0/1/2`) plus a flags byte carry the per-type
1744
- // extras; child lists hang off three object arrays. Slot meaning by type:
1745
- // url: a0 contentStart, a1 contentEnd
1746
- // function: a0 nameEnd
1747
- // declaration: a0 nameEnd, flags bit0 important
1748
- // at-rule: a0 nameEnd, a1 blockStart, a2 blockEnd
1749
- // qualified: a1 blockStart, a2 blockEnd
1882
+ // Three reused int slots (`_soaAux0/1/2`) plus a flags byte carry the per-type
1883
+ // extras; child lists hang off three object arrays. Aux slot meaning by type:
1884
+ // url: aux0 contentStart, aux1 contentEnd
1885
+ // function: aux0 nameEnd
1886
+ // declaration: aux0 nameEnd, flags bit0 important
1887
+ // at-rule: aux0 nameEnd, aux1 blockStart, aux2 blockEnd
1888
+ // qualified: aux1 blockStart, aux2 blockEnd
1750
1889
  // `name` / `nameStart` / a simple block's `token` are derived from the source
1751
- // on read (see the SoA accessors), so they need no slot. Lists: l0 =
1752
- // value | prelude | stylesheet rules, l1 = declarations, l2 = childRules.
1753
- // `grammar` resets `_soaN` to 0 after each top-level rule's walk, so the
1890
+ // on read (see the SoA accessors), so they need no slot. A node's main content
1891
+ // (value | prelude | stylesheet rules) is a `_soaFlat` span (see below).
1892
+ // `grammar` resets `_soaNodeCount` to 0 after each top-level rule's walk, so the
1754
1893
  // buffers are reused across rules and the parse allocates almost nothing.
1755
- let _soaCap = 0;
1756
- let _soaN = 0;
1757
- let _soaTy = new Uint8Array(0);
1758
- let _soaSt = new Int32Array(0);
1759
- let _soaEn = new Int32Array(0);
1760
- let _soaA0 = new Int32Array(0);
1761
- let _soaA1 = new Int32Array(0);
1762
- let _soaA2 = new Int32Array(0);
1763
- let _soaFl = new Uint8Array(0);
1764
- /** @type {(Node[] | null)[]} */
1765
- const _soaL0 = [];
1894
+ let _soaCapacity = 0;
1895
+ let _soaNodeCount = 0;
1896
+ let _soaTypes = new Uint8Array(0);
1897
+ let _soaStarts = new Int32Array(0);
1898
+ let _soaEnds = new Int32Array(0);
1899
+ let _soaAux0 = new Int32Array(0);
1900
+ let _soaAux1 = new Int32Array(0);
1901
+ let _soaAux2 = new Int32Array(0);
1902
+ let _soaFlags = new Uint8Array(0);
1903
+ // Content-list spans: a container's value / prelude is `_soaFlat[start, start+len)`
1904
+ // (node refs), recycled per top-level rule like the node columns.
1905
+ let _soaListStarts = new Int32Array(0);
1906
+ let _soaListLens = new Int32Array(0);
1907
+ let _soaFlat = new Int32Array(0);
1908
+ let _soaFlatTop = 0;
1909
+ // Peak usage of the current parse, and use-once regrow hints: after an
1910
+ // over-capacity shrink the next grow jumps straight back to the previous
1911
+ // parse's peak (one exact-fit allocation instead of re-doubling up).
1912
+ let _soaPeak = 0;
1913
+ let _soaFlatPeak = 0;
1914
+ let _soaGrowHint = 0;
1915
+ let _soaFlatGrowHint = 0;
1916
+
1917
+ /** @param {number} need minimum flat-buffer capacity */
1918
+ const _soaFlatGrow = (need) => {
1919
+ let cap = _soaFlat.length || 4096;
1920
+ if (_soaFlatGrowHint > cap) cap = _soaFlatGrowHint;
1921
+ _soaFlatGrowHint = 0;
1922
+ while (cap < need) cap *= 2;
1923
+ const next = new Int32Array(cap);
1924
+ next.set(_soaFlat);
1925
+ _soaFlat = next;
1926
+ };
1766
1927
  /** @type {(Node[] | null)[]} */
1767
- const _soaL1 = [];
1928
+ const _soaDeclarationLists = [];
1768
1929
  /** @type {(Node[] | null)[]} */
1769
- const _soaL2 = [];
1930
+ const _soaChildRuleLists = [];
1770
1931
  let _soaInput = "";
1771
- let _soaLC = /** @type {LocConverter} */ (/** @type {unknown} */ (null));
1932
+ let _soaLocConverter = /** @type {LocConverter} */ (
1933
+ /** @type {unknown} */ (null)
1934
+ );
1772
1935
 
1773
1936
  // Node refs are integers here but typed `Node` across the parser; these are
1774
1937
  // identity casts that just satisfy the type system at the boundary.
1775
1938
  /** @type {(n: Node) => number} */
1776
- const _ix = (n) => /** @type {number} */ (/** @type {unknown} */ (n));
1939
+ const _nodeIndex = (n) => /** @type {number} */ (/** @type {unknown} */ (n));
1777
1940
  /** @type {(i: number) => Node} */
1778
- const _ref = (i) => /** @type {Node} */ (/** @type {unknown} */ (i));
1941
+ const _nodeRef = (i) => /** @type {Node} */ (/** @type {unknown} */ (i));
1779
1942
 
1780
1943
  /** @param {number} need minimum capacity */
1781
1944
  const _soaGrow = (need) => {
1782
- let cap = _soaCap || 4096;
1945
+ let cap = _soaCapacity || 4096;
1946
+ if (_soaGrowHint > cap) cap = _soaGrowHint;
1947
+ _soaGrowHint = 0;
1783
1948
  while (cap < need) cap *= 2;
1784
1949
  const ty = new Uint8Array(cap);
1785
- ty.set(_soaTy);
1786
- _soaTy = ty;
1950
+ ty.set(_soaTypes);
1951
+ _soaTypes = ty;
1787
1952
  const st = new Int32Array(cap);
1788
- st.set(_soaSt);
1789
- _soaSt = st;
1953
+ st.set(_soaStarts);
1954
+ _soaStarts = st;
1790
1955
  const en = new Int32Array(cap);
1791
- en.set(_soaEn);
1792
- _soaEn = en;
1956
+ en.set(_soaEnds);
1957
+ _soaEnds = en;
1793
1958
  const a0 = new Int32Array(cap);
1794
- a0.set(_soaA0);
1795
- _soaA0 = a0;
1959
+ a0.set(_soaAux0);
1960
+ _soaAux0 = a0;
1796
1961
  const a1 = new Int32Array(cap);
1797
- a1.set(_soaA1);
1798
- _soaA1 = a1;
1962
+ a1.set(_soaAux1);
1963
+ _soaAux1 = a1;
1799
1964
  const a2 = new Int32Array(cap);
1800
- a2.set(_soaA2);
1801
- _soaA2 = a2;
1965
+ a2.set(_soaAux2);
1966
+ _soaAux2 = a2;
1802
1967
  const fl = new Uint8Array(cap);
1803
- fl.set(_soaFl);
1804
- _soaFl = fl;
1805
- _soaCap = cap;
1968
+ fl.set(_soaFlags);
1969
+ _soaFlags = fl;
1970
+ const ls = new Int32Array(cap);
1971
+ ls.set(_soaListStarts);
1972
+ _soaListStarts = ls;
1973
+ const ll = new Int32Array(cap);
1974
+ ll.set(_soaListLens);
1975
+ _soaListLens = ll;
1976
+ _soaCapacity = cap;
1806
1977
  };
1807
1978
  /** @type {(type: number, start: number, end: number) => Node} */
1808
- const _soaAlloc = (type, start, end) => {
1979
+ const _soaAllocNode = (type, start, end) => {
1809
1980
  // Ids are 1-based: a node ref is used in truthiness checks (`if (!parent)`),
1810
1981
  // so 0 must stay reserved for "no node".
1811
1982
  // Leaves never read the flag / list slots — `_soaAllocContainer` clears
1812
1983
  // them instead, keeping the dominant leaf allocation at three writes.
1813
- const i = _soaN + 1;
1814
- if (i >= _soaCap) _soaGrow(i + 1);
1815
- _soaTy[i] = type;
1816
- _soaSt[i] = start;
1817
- _soaEn[i] = end;
1818
- _soaN = i;
1819
- return _ref(i);
1984
+ const i = _soaNodeCount + 1;
1985
+ if (i >= _soaCapacity) _soaGrow(i + 1);
1986
+ _soaTypes[i] = type;
1987
+ _soaStarts[i] = start;
1988
+ _soaEnds[i] = end;
1989
+ _soaNodeCount = i;
1990
+ return _nodeRef(i);
1820
1991
  };
1821
1992
  /** @type {(type: number, start: number, end: number) => Node} */
1822
1993
  const _soaAllocContainer = (type, start, end) => {
1823
- const r = _soaAlloc(type, start, end);
1824
- const i = _ix(r);
1825
- _soaFl[i] = 0;
1826
- // Clear list slots so a reused id never exposes a previous node's children.
1827
- _soaL0[i] = null;
1828
- _soaL1[i] = null;
1829
- _soaL2[i] = null;
1994
+ const r = _soaAllocNode(type, start, end);
1995
+ const i = _nodeIndex(r);
1996
+ _soaFlags[i] = 0;
1997
+ // Clear list slots so a reused id never exposes a previous node's children
1998
+ // (content lists are flat spans, so zeroing the length suffices).
1999
+ _soaListLens[i] = 0;
2000
+ _soaDeclarationLists[i] = null;
2001
+ _soaChildRuleLists[i] = null;
1830
2002
  return r;
1831
2003
  };
1832
2004
  // Raw token value (the lazy `Token.value` form): hash / at-keyword drop their
@@ -1837,65 +2009,107 @@ const _soaAllocContainer = (type, start, end) => {
1837
2009
  * @returns {string} raw token value
1838
2010
  */
1839
2011
  const _soaValueOf = (i) => {
1840
- const ty = _soaTy[i];
2012
+ const ty = _soaTypes[i];
1841
2013
  if (ty === T_HASH || ty === T_AT_KEYWORD) {
1842
- return _soaInput.slice(_soaSt[i] + 1, _soaEn[i]);
2014
+ return _soaInput.slice(_soaStarts[i] + 1, _soaEnds[i]);
1843
2015
  }
1844
- if (ty === T_URL) return _soaInput.slice(_soaA0[i], _soaA1[i]);
1845
- return _soaInput.slice(_soaSt[i], _soaEn[i]);
2016
+ if (ty === T_URL) return _soaInput.slice(_soaAux0[i], _soaAux1[i]);
2017
+ return _soaInput.slice(_soaStarts[i], _soaEnds[i]);
2018
+ };
2019
+ // Module-level constants for the same reason as the `_objSet*` set above.
2020
+ /** @type {typeof _makeUrl} */
2021
+ const _soaMakeUrl = (start, end, cs, ce) => {
2022
+ const r = _soaAllocNode(T_URL, start, end);
2023
+ _soaAux0[_nodeIndex(r)] = cs;
2024
+ _soaAux1[_nodeIndex(r)] = ce;
2025
+ return r;
2026
+ };
2027
+ /** @type {typeof _makeStylesheet} */
2028
+ const _soaMakeStylesheet = (start) =>
2029
+ _soaAllocContainer(T_STYLESHEET, start, start);
2030
+ // name / nameStart are derived from start + nameEnd; only nameEnd is stored.
2031
+ /** @type {typeof _setName} */
2032
+ const _soaSetName = (r, ns, ne) => {
2033
+ _soaAux0[_nodeIndex(r)] = ne;
1846
2034
  };
2035
+ /** @type {typeof _setEnd} */
2036
+ const _soaSetEnd = (r, v) => {
2037
+ _soaEnds[_nodeIndex(r)] = v;
2038
+ };
2039
+ /** @type {typeof _setBlock} */
2040
+ const _soaSetBlock = (r, bs, be) => {
2041
+ const i = _nodeIndex(r);
2042
+ _soaAux1[i] = bs;
2043
+ _soaAux2[i] = be;
2044
+ };
2045
+ /** @type {typeof _setImportant} */
2046
+ const _soaSetImportant = (r) => {
2047
+ _soaFlags[_nodeIndex(r)] |= 1;
2048
+ };
2049
+ // A simple block's token is derived from its opening char on read.
2050
+ /** @type {typeof _setToken} */
2051
+ const _soaSetToken = (r, ch) => {};
2052
+ /** @type {typeof _setValue} */
2053
+ const _soaSetValue = (r, list) => {
2054
+ // Seal the finished list: copy its refs into the flat buffer and hand the
2055
+ // scratch array back to the pool. The caller never touches `list` again.
2056
+ const i = _nodeIndex(r);
2057
+ const len = list.length;
2058
+ const start = _soaFlatTop;
2059
+ if (start + len > _soaFlat.length) _soaFlatGrow(start + len);
2060
+ for (let k = 0; k < len; k++) {
2061
+ _soaFlat[start + k] = _nodeIndex(list[k]);
2062
+ }
2063
+ _soaFlatTop = start + len;
2064
+ _soaListStarts[i] = start;
2065
+ _soaListLens[i] = len;
2066
+ list.length = 0;
2067
+ _listPool.push(list);
2068
+ };
2069
+ /** @type {typeof _setBody} */
2070
+ const _soaSetBody = (r, decls, childRules) => {
2071
+ const i = _nodeIndex(r);
2072
+ _soaDeclarationLists[i] = decls;
2073
+ _soaChildRuleLists[i] = childRules;
2074
+ };
2075
+ /** @type {typeof _nodeTypeOf} */
2076
+ const _soaNodeTypeOf = (r) => _soaTypes[_nodeIndex(r)];
2077
+ /** @type {typeof _nodeStartOf} */
2078
+ const _soaNodeStartOf = (r) => _soaStarts[_nodeIndex(r)];
2079
+ /** @type {typeof _nodeValueOf} */
2080
+ const _soaNodeValueOf = (r) => _soaValueOf(_nodeIndex(r));
2081
+ /** @type {typeof _nodeTokenOf} */
2082
+ const _soaNodeTokenOf = (r) =>
2083
+ /** @type {SimpleBlockToken} */ (_soaInput[_soaStarts[_nodeIndex(r)]]);
1847
2084
  /**
1848
2085
  * @param {string} input source
1849
2086
  * @param {LocConverter} lc loc converter
1850
2087
  */
1851
2088
  const useSoaBackend = (input, lc) => {
1852
2089
  _soaInput = input;
1853
- _soaLC = lc;
1854
- _soaN = 0;
1855
- _mkLeaf = (type, start, end) => _soaAlloc(type, start, end);
1856
- _mkUrl = (start, end, cs, ce) => {
1857
- const r = _soaAlloc(T_URL, start, end);
1858
- _soaA0[_ix(r)] = cs;
1859
- _soaA1[_ix(r)] = ce;
1860
- return r;
1861
- };
1862
- _mkContainer = (type, start, end) => _soaAllocContainer(type, start, end);
1863
- _mkStylesheet = (start) => _soaAllocContainer(T_STYLESHEET, start, start);
1864
- // name / nameStart are derived from start + nameEnd; only nameEnd is stored.
1865
- _setName = (r, v, ns, ne) => {
1866
- _soaA0[_ix(r)] = ne;
1867
- };
1868
- _setEnd = (r, v) => {
1869
- _soaEn[_ix(r)] = v;
1870
- };
1871
- _setBlock = (r, bs, be) => {
1872
- const i = _ix(r);
1873
- _soaA1[i] = bs;
1874
- _soaA2[i] = be;
1875
- };
1876
- _setImportant = (r) => {
1877
- _soaFl[_ix(r)] |= 1;
1878
- };
1879
- // A simple block's token is derived from its opening char on read.
1880
- _setToken = (r, ch) => {};
1881
- _setValue = (r, list) => {
1882
- _soaL0[_ix(r)] = list;
1883
- };
1884
- _setPrelude = (r, list) => {
1885
- _soaL0[_ix(r)] = list;
1886
- };
1887
- _setBody = (r, decls, childRules) => {
1888
- const i = _ix(r);
1889
- _soaL1[i] = decls;
1890
- _soaL2[i] = childRules;
1891
- };
1892
- _setRules = (r, list) => {
1893
- _soaL0[_ix(r)] = list;
1894
- };
1895
- _nType = (r) => _soaTy[_ix(r)];
1896
- _nStart = (r) => _soaSt[_ix(r)];
1897
- _nValue = (r) => _soaValueOf(_ix(r));
1898
- _nToken = (r) => /** @type {SimpleBlockToken} */ (_soaInput[_soaSt[_ix(r)]]);
2090
+ _soaLocConverter = lc;
2091
+ _soaNodeCount = 0;
2092
+ _soaFlatTop = 0;
2093
+ _soaActive = true;
2094
+ // The alloc primitives are the slot functions directly — no wrapper hop.
2095
+ _makeLeaf = _soaAllocNode;
2096
+ _makeUrl = _soaMakeUrl;
2097
+ _makeContainer = _soaAllocContainer;
2098
+ _makeStylesheet = _soaMakeStylesheet;
2099
+ _setName = _soaSetName;
2100
+ _setEnd = _soaSetEnd;
2101
+ _setBlock = _soaSetBlock;
2102
+ _setImportant = _soaSetImportant;
2103
+ _setToken = _soaSetToken;
2104
+ _setValue = _soaSetValue;
2105
+ // value / prelude / rules all land in the one content-list slot.
2106
+ _setPrelude = _soaSetValue;
2107
+ _setBody = _soaSetBody;
2108
+ _setRules = _soaSetValue;
2109
+ _nodeTypeOf = _soaNodeTypeOf;
2110
+ _nodeStartOf = _soaNodeStartOf;
2111
+ _nodeValueOf = _soaNodeValueOf;
2112
+ _nodeTokenOf = _soaNodeTokenOf;
1899
2113
  };
1900
2114
 
1901
2115
  /**
@@ -1909,9 +2123,9 @@ const tokenToNode = (t) => {
1909
2123
  // plain leaf whose node type comes from the map.
1910
2124
  if (tt === TT_URL) {
1911
2125
  const ut = /** @type {CssUrlToken} */ (t);
1912
- return _mkUrl(t.start, t.end, ut.contentStart, ut.contentEnd);
2126
+ return _makeUrl(t.start, t.end, ut.contentStart, ut.contentEnd);
1913
2127
  }
1914
- return _mkLeaf(_ttToNodeType[tt], t.start, t.end);
2128
+ return _makeLeaf(_ttToNodeType[tt], t.start, t.end);
1915
2129
  };
1916
2130
 
1917
2131
  /**
@@ -2088,7 +2302,7 @@ const normalizeIntoTokenStream = (input, pos, onComment) =>
2088
2302
  // algorithms that drive the tokenizer.
2089
2303
 
2090
2304
  /**
2091
- * @typedef {object} ParseListOptions
2305
+ * @typedef {object} ParseOptions
2092
2306
  * @property {((input: string, start: number, end: number) => number)=} comment optional comment-token callback; the public `parse*` entry points use it to build the `TokenStream` so the outer parser's comment tracker still sees magic comments inside the consumed range
2093
2307
  */
2094
2308
 
@@ -2096,17 +2310,18 @@ const normalizeIntoTokenStream = (input, pos, onComment) =>
2096
2310
  * Parse a stylesheet, CSS Syntax Level 3
2097
2311
  * [§5.3.4](https://drafts.csswg.org/css-syntax/#parse-stylesheet).
2098
2312
  * @param {string | TokenStream} input source string or an existing token stream
2099
- * @param {((input: string, start: number, end: number) => number)=} comment optional comment-token callback (string input only)
2313
+ * @param {number=} pos start position (string input only)
2314
+ * @param {ParseOptions=} options optional comment-token callback (string input only)
2100
2315
  * @returns {Stylesheet} the parsed stylesheet
2101
2316
  */
2102
- const parseAStylesheet = (input, comment) => {
2317
+ const parseAStylesheet = (input, pos = 0, options = {}) => {
2103
2318
  // 1. If input is a byte stream for a stylesheet, decode bytes from input, and set input to the result.
2104
2319
  // 2. Normalize input, and set input to the result.
2105
- const ts = normalizeIntoTokenStream(input, 0, comment);
2320
+ const ts = normalizeIntoTokenStream(input, pos, options.comment);
2106
2321
  useObjectBackend(ts.locConverter);
2107
2322
  // 3. Create a new stylesheet, with its location set to location (or null, if location was not passed).
2108
2323
  const start = ts.next().start;
2109
- const stylesheet = /** @type {Stylesheet} */ (_mkStylesheet(start));
2324
+ const stylesheet = /** @type {Stylesheet} */ (_makeStylesheet(start));
2110
2325
  // 4. Consume a stylesheet's contents from input, and set the stylesheet's rules to the result.
2111
2326
  _setRules(stylesheet, consumeAStylesheetsContents(ts));
2112
2327
  _setEnd(stylesheet, ts.next().start);
@@ -2121,12 +2336,13 @@ const parseAStylesheet = (input, comment) => {
2121
2336
  * declarations are parse errors (never produced) and top-level CDO (`<!--`) /
2122
2337
  * CDC (`-->`) tokens are discarded.
2123
2338
  * @param {string | TokenStream} input source string or an existing token stream
2124
- * @param {((input: string, start: number, end: number) => number)=} comment optional comment-token callback (string input only)
2339
+ * @param {number=} pos start position (string input only)
2340
+ * @param {ParseOptions=} options optional comment-token callback (string input only)
2125
2341
  * @returns {Rule[]} top-level rules
2126
2342
  */
2127
- const parseAStylesheetsContents = (input, comment) => {
2343
+ const parseAStylesheetsContents = (input, pos = 0, options = {}) => {
2128
2344
  // 1. Normalize input, and set input to the result.
2129
- const ts = normalizeIntoTokenStream(input, 0, comment);
2345
+ const ts = normalizeIntoTokenStream(input, pos, options.comment);
2130
2346
  useObjectBackend(ts.locConverter);
2131
2347
  // 2. Consume a stylesheet’s contents from input, and return the result.
2132
2348
  return consumeAStylesheetsContents(ts);
@@ -2137,12 +2353,12 @@ const parseAStylesheetsContents = (input, comment) => {
2137
2353
  * [§5.3.6](https://drafts.csswg.org/css-syntax/#parse-block-contents).
2138
2354
  * @param {string | TokenStream} input source string or an existing token stream
2139
2355
  * @param {number=} pos start position (string input only; just past the opening `{`, or 0)
2140
- * @param {((input: string, start: number, end: number) => number)=} comment optional comment-token callback (string input only)
2356
+ * @param {ParseOptions=} options optional comment-token callback (string input only)
2141
2357
  * @returns {{ decls: Declaration[], rules: Rule[] }} block decls + rules
2142
2358
  */
2143
- const parseABlocksContents = (input, pos, comment) => {
2359
+ const parseABlocksContents = (input, pos = 0, options = {}) => {
2144
2360
  // 1. Normalize input, and set input to the result.
2145
- const ts = normalizeIntoTokenStream(input, pos, comment);
2361
+ const ts = normalizeIntoTokenStream(input, pos, options.comment);
2146
2362
  useObjectBackend(ts.locConverter);
2147
2363
  // 2. Consume a block’s contents from input, and return the result.
2148
2364
  return consumeABlocksContents(ts);
@@ -2155,12 +2371,12 @@ const parseABlocksContents = (input, pos, comment) => {
2155
2371
  * whitespace; `undefined` (syntax error) otherwise.
2156
2372
  * @param {string | TokenStream} input source string or an existing token stream
2157
2373
  * @param {number=} pos start position (string input only)
2158
- * @param {((input: string, start: number, end: number) => number)=} comment optional comment-token callback (string input only)
2374
+ * @param {ParseOptions=} options optional comment-token callback (string input only)
2159
2375
  * @returns {Rule | undefined} the parsed rule
2160
2376
  */
2161
- const parseARule = (input, pos, comment) => {
2377
+ const parseARule = (input, pos = 0, options = {}) => {
2162
2378
  // 1. Normalize input, and set input to the result.
2163
- const ts = normalizeIntoTokenStream(input, pos, comment);
2379
+ const ts = normalizeIntoTokenStream(input, pos, options.comment);
2164
2380
  useObjectBackend(ts.locConverter);
2165
2381
  // 2. Discard whitespace from input.
2166
2382
  while (ts.next().type === TT_WHITESPACE) ts.discard();
@@ -2186,12 +2402,12 @@ const parseARule = (input, pos, comment) => {
2186
2402
  * [§5.3.8](https://drafts.csswg.org/css-syntax/#parse-declaration).
2187
2403
  * @param {string | TokenStream} input source string or an existing token stream
2188
2404
  * @param {number=} pos start position (string input only)
2189
- * @param {((input: string, start: number, end: number) => number)=} comment optional comment-token callback (string input only)
2405
+ * @param {ParseOptions=} options optional comment-token callback (string input only)
2190
2406
  * @returns {Declaration | undefined} the parsed declaration, or undefined
2191
2407
  */
2192
- const parseADeclaration = (input, pos, comment) => {
2408
+ const parseADeclaration = (input, pos = 0, options = {}) => {
2193
2409
  // 1. Normalize input, and set input to the result.
2194
- const ts = normalizeIntoTokenStream(input, pos, comment);
2410
+ const ts = normalizeIntoTokenStream(input, pos, options.comment);
2195
2411
  useObjectBackend(ts.locConverter);
2196
2412
  // 2. Discard whitespace from input.
2197
2413
  while (ts.next().type === TT_WHITESPACE) ts.discard();
@@ -2203,10 +2419,10 @@ const parseADeclaration = (input, pos, comment) => {
2203
2419
  * Parse a component value, CSS Syntax Level 3 [§5.3.9](https://drafts.csswg.org/css-syntax/#parse-component-value) — strict entry point that consumes one value and returns `undefined` if non-whitespace input trails (use `consumeAComponentValue` for "one value, ignore the rest").
2204
2420
  * @param {string | TokenStream} input source string or an existing token stream
2205
2421
  * @param {number=} pos start position (string input only)
2206
- * @param {{ comment?: (input: string, start: number, end: number) => number }=} options optional comment-token callback (string input only)
2422
+ * @param {ParseOptions=} options optional comment-token callback (string input only)
2207
2423
  * @returns {ComponentValue | undefined} the parsed component value, or `undefined` on empty / trailing-garbage input
2208
2424
  */
2209
- const parseAComponentValue = (input, pos, options = {}) => {
2425
+ const parseAComponentValue = (input, pos = 0, options = {}) => {
2210
2426
  // 1. Normalize input, and set input to the result.
2211
2427
  const ts = normalizeIntoTokenStream(input, pos, options.comment);
2212
2428
  useObjectBackend(ts.locConverter);
@@ -2228,27 +2444,28 @@ const parseAComponentValue = (input, pos, options = {}) => {
2228
2444
  * [§5.3.10](https://drafts.csswg.org/css-syntax/#parse-list-of-components).
2229
2445
  * @param {string | TokenStream} input source string or an existing token stream
2230
2446
  * @param {number=} pos start position (string input only)
2231
- * @param {ParseListOptions=} options comment callback
2447
+ * @param {ParseOptions=} options comment callback
2232
2448
  * @returns {ComponentValue[]} component values
2233
2449
  */
2234
- const parseAListOfComponentValues = (input, pos, options = {}) => {
2450
+ const parseAListOfComponentValues = (input, pos = 0, options = {}) => {
2235
2451
  // 1. Normalize input, and set input to the result.
2236
2452
  const ts = normalizeIntoTokenStream(input, pos, options.comment);
2237
2453
  useObjectBackend(ts.locConverter);
2238
2454
  // 2. Consume a list of component values from input, and return the result.
2239
- return consumeAListOfComponentValues(ts);
2455
+ // (`null` needs `bailOnCurly`, which is not passed here.)
2456
+ return /** @type {ComponentValue[]} */ (consumeAListOfComponentValues(ts));
2240
2457
  };
2241
2458
 
2242
2459
  /**
2243
2460
  * Parse a comma-separated list of component values, CSS Syntax Level 3 [§5.3.11](https://drafts.csswg.org/css-syntax/#parse-comma-list) — consumes one `<comma-token>`-stopped group of component values per iteration until EOF.
2244
2461
  * @param {string | TokenStream} input source string or an existing token stream
2245
2462
  * @param {number=} pos start position (string input only)
2246
- * @param {ParseListOptions=} options comment callback
2463
+ * @param {ParseOptions=} options comment callback
2247
2464
  * @returns {ComponentValue[][]} comma-separated groups of component values
2248
2465
  */
2249
2466
  const parseACommaSeparatedListOfComponentValues = (
2250
2467
  input,
2251
- pos,
2468
+ pos = 0,
2252
2469
  options = {}
2253
2470
  ) => {
2254
2471
  // 1. Normalize input, and set input to the result.
@@ -2260,7 +2477,11 @@ const parseACommaSeparatedListOfComponentValues = (
2260
2477
  // 3. While input is not empty:
2261
2478
  while (ts.next().type !== TT_EOF) {
2262
2479
  // 3.1. Consume a list of component values from input, with <comma-token> as the stop token, and append the result to groups.
2263
- groups.push(consumeAListOfComponentValues(ts, TT_COMMA));
2480
+ groups.push(
2481
+ /** @type {ComponentValue[]} */ (
2482
+ consumeAListOfComponentValues(ts, TT_COMMA)
2483
+ )
2484
+ );
2264
2485
  // 3.2 Discard a token from input.
2265
2486
  ts.discard();
2266
2487
  }
@@ -2335,16 +2556,12 @@ const consumeAnAtRule = (ts, nested = false) => {
2335
2556
  // Consume a token from input, and let rule be a new at-rule with its name set to the returned token’s value, its prelude initially set to an empty list, and no declarations or child rules.
2336
2557
  const head = ts.consume();
2337
2558
  const rule = /** @type {AtRule} */ (
2338
- _mkContainer(T_AT_RULE, head.start, head.end)
2339
- );
2340
- _setName(
2341
- rule,
2342
- ts.input.slice(head.start + 1, head.end),
2343
- head.start,
2344
- head.end
2559
+ _makeContainer(T_AT_RULE, head.start, head.end)
2345
2560
  );
2346
- const prelude = _list();
2347
- _setPrelude(rule, prelude);
2561
+ _setName(rule, head.start, head.end);
2562
+ // Sealed (`_setPrelude`) at each return — the SoA backend consumes the
2563
+ // scratch array when sealing, so it must be complete by then.
2564
+ const prelude = _soaActive ? _soaList() : [];
2348
2565
  // declarations / childRules / blockStart (-1) / blockEnd (-1) keep their
2349
2566
  // defaults (no block: the `;` / EOF / nested-`}` at-rule forms).
2350
2567
 
@@ -2362,6 +2579,7 @@ const consumeAnAtRule = (ts, nested = false) => {
2362
2579
  // Discard a token from input. If rule is valid in the current context, return it; otherwise return nothing.
2363
2580
  if (t.type === TT_SEMICOLON || t.type === TT_EOF) {
2364
2581
  ts.discard();
2582
+ _setPrelude(rule, prelude);
2365
2583
  _setEnd(rule, t.start);
2366
2584
  return rule;
2367
2585
  }
@@ -2370,6 +2588,7 @@ const consumeAnAtRule = (ts, nested = false) => {
2370
2588
  // Otherwise, consume a token and append the result to rule’s prelude.
2371
2589
  else if (t.type === TT_RIGHT_CURLY_BRACKET) {
2372
2590
  if (nested) {
2591
+ _setPrelude(rule, prelude);
2373
2592
  _setEnd(rule, t.start);
2374
2593
  return rule;
2375
2594
  }
@@ -2380,6 +2599,7 @@ const consumeAnAtRule = (ts, nested = false) => {
2380
2599
  // <{-token>
2381
2600
  // Consume a block from input, and assign the result to rule's declarations and child rules.
2382
2601
  else if (t.type === TT_LEFT_CURLY_BRACKET) {
2602
+ _setPrelude(rule, prelude);
2383
2603
  const block = consumeABlock(ts);
2384
2604
  _setBody(rule, block.decls, block.rules);
2385
2605
  _setBlock(rule, block.blockStart, block.blockEnd);
@@ -2392,7 +2612,10 @@ const consumeAnAtRule = (ts, nested = false) => {
2392
2612
  const node = consumeAComponentValue(ts, t);
2393
2613
  if (!skip) {
2394
2614
  prelude.push(node);
2395
- } else if (_nType(node) === T_FUNCTION || _nType(node) === T_URL) {
2615
+ } else if (
2616
+ _nodeTypeOf(node) === T_FUNCTION ||
2617
+ _nodeTypeOf(node) === T_URL
2618
+ ) {
2396
2619
  prelude.push(node);
2397
2620
  }
2398
2621
  }
@@ -2423,10 +2646,11 @@ const consumeAQualifiedRule = (ts, stopToken, nested = false) => {
2423
2646
  const start = ts.next().start;
2424
2647
  // Let rule be a new qualified rule with its prelude, declarations, and child rules all initially set to empty lists.
2425
2648
  const rule = /** @type {QualifiedRule} */ (
2426
- _mkContainer(T_QUALIFIED_RULE, start, start)
2649
+ _makeContainer(T_QUALIFIED_RULE, start, start)
2427
2650
  );
2428
- const prelude = _list();
2429
- _setPrelude(rule, prelude);
2651
+ // Sealed (`_setPrelude`) at the `{` exit — the only path that returns the
2652
+ // rule; the parse-error exits abandon the scratch unsealed.
2653
+ const prelude = _soaActive ? _soaList() : [];
2430
2654
  // declarations / childRules / blockStart (-1) / blockEnd (-1) keep their
2431
2655
  // defaults (no block until a `{` is reached).
2432
2656
 
@@ -2471,14 +2695,14 @@ const consumeAQualifiedRule = (ts, stopToken, nested = false) => {
2471
2695
  /* istanbul ignore next -- @preserve: leading whitespace is discarded before the rule, so the prelude never starts with it */
2472
2696
  while (
2473
2697
  firstIdx < prelude.length &&
2474
- _nType(prelude[firstIdx]) === T_WHITESPACE
2698
+ _nodeTypeOf(prelude[firstIdx]) === T_WHITESPACE
2475
2699
  ) {
2476
2700
  firstIdx++;
2477
2701
  }
2478
2702
  let secondIdx = firstIdx + 1;
2479
2703
  while (
2480
2704
  secondIdx < prelude.length &&
2481
- _nType(prelude[secondIdx]) === T_WHITESPACE
2705
+ _nodeTypeOf(prelude[secondIdx]) === T_WHITESPACE
2482
2706
  ) {
2483
2707
  secondIdx++;
2484
2708
  }
@@ -2487,12 +2711,12 @@ const consumeAQualifiedRule = (ts, stopToken, nested = false) => {
2487
2711
  }
2488
2712
  if (
2489
2713
  first &&
2490
- _nType(first) === T_IDENT &&
2714
+ _nodeTypeOf(first) === T_IDENT &&
2491
2715
  // Test the source bytes directly — avoids forcing the lazy `value`
2492
2716
  // slice just to check the `--` custom-property prefix.
2493
- ts.input.startsWith("--", _nStart(first)) &&
2717
+ ts.input.startsWith("--", _nodeStartOf(first)) &&
2494
2718
  second &&
2495
- _nType(second) === T_COLON
2719
+ _nodeTypeOf(second) === T_COLON
2496
2720
  ) {
2497
2721
  /* istanbul ignore if -- @preserve: when nested, `declarationStartLikely` routes every `--x:` to consumeADeclaration (which accepts custom properties), so this fallthrough is unreachable */
2498
2722
  if (nested) {
@@ -2502,6 +2726,7 @@ const consumeAQualifiedRule = (ts, stopToken, nested = false) => {
2502
2726
  }
2503
2727
  return undefined;
2504
2728
  }
2729
+ _setPrelude(rule, prelude);
2505
2730
  const block = consumeABlock(ts);
2506
2731
  _setBody(rule, block.decls, block.rules);
2507
2732
  _setBlock(rule, block.blockStart, block.blockEnd);
@@ -2516,7 +2741,7 @@ const consumeAQualifiedRule = (ts, stopToken, nested = false) => {
2516
2741
  // Keep only url-bearing nodes (url tokens, or functions that may hold a
2517
2742
  // url like `:unknown(url(x))`) so the url visitor still rewrites them;
2518
2743
  // other selector tokens have no non-modules consumer, so drop them.
2519
- const ty = _nType(node);
2744
+ const ty = _nodeTypeOf(node);
2520
2745
  if (ty === T_FUNCTION || ty === T_URL) prelude.push(node);
2521
2746
  // Track the first two non-whitespace tokens for the disambiguation above.
2522
2747
  if (t.type !== TT_WHITESPACE) {
@@ -2601,8 +2826,12 @@ const declarationStartLikely = (ts) => {
2601
2826
  const consumeABlocksContents = (ts, onNode) => {
2602
2827
  /** @type {Declaration[]} */
2603
2828
  const decls = [];
2604
- /** @type {Rule[]} */
2605
- const rules = [];
2829
+ // Child rules are the common empty case (most rules carry only declarations),
2830
+ // so `rules` is allocated lazily and returned as the shared frozen
2831
+ // `_EMPTY_LIST` when nothing was appended — one fewer array per rule. `decls`
2832
+ // stays eager so the hot declaration append keeps a branch-free `push`.
2833
+ /** @type {Rule[] | null} */
2834
+ let rules = null;
2606
2835
 
2607
2836
  // Process input:
2608
2837
  for (;;) {
@@ -2616,7 +2845,7 @@ const consumeABlocksContents = (ts, onNode) => {
2616
2845
  // <EOF-token> / <}-token>
2617
2846
  // Return decls and rules.
2618
2847
  else if (t.type === TT_EOF || t.type === TT_RIGHT_CURLY_BRACKET) {
2619
- return { decls, rules };
2848
+ return { decls, rules: rules || _EMPTY_LIST };
2620
2849
  }
2621
2850
  // <at-keyword-token>
2622
2851
  // Consume an at-rule from input, with nested set to true. If a rule was returned, append it to rules.
@@ -2624,7 +2853,7 @@ const consumeABlocksContents = (ts, onNode) => {
2624
2853
  const atRule = consumeAnAtRule(ts, true);
2625
2854
  if (atRule) {
2626
2855
  if (onNode) onNode(atRule);
2627
- else rules.push(atRule);
2856
+ else (rules || (rules = [])).push(atRule);
2628
2857
  }
2629
2858
  }
2630
2859
  // anything else
@@ -2647,7 +2876,7 @@ const consumeABlocksContents = (ts, onNode) => {
2647
2876
  const rule = consumeAQualifiedRule(ts, TT_SEMICOLON, true);
2648
2877
  if (rule) {
2649
2878
  if (onNode) onNode(rule);
2650
- else rules.push(rule);
2879
+ else (rules || (rules = [])).push(rule);
2651
2880
  }
2652
2881
  }
2653
2882
  }
@@ -2695,14 +2924,14 @@ const consumeADeclaration = (ts, nested = false) => {
2695
2924
  // name "" / nameStart / nameEnd (= start) / important (false) keep their
2696
2925
  // `Container` defaults; `value` is set unconditionally at step 5 below.
2697
2926
  const decl = /** @type {Declaration} */ (
2698
- _mkContainer(T_DECLARATION, start, start)
2927
+ _makeContainer(T_DECLARATION, start, start)
2699
2928
  );
2700
2929
 
2701
2930
  // 1. If the next token is an <ident-token>, consume a token from input and set decl's name to the returned token's value.
2702
2931
  // Otherwise, consume the remnants of a bad declaration from input, with nested, and return nothing.
2703
2932
  if (ts.next().type === TT_IDENTIFIER) {
2704
2933
  const head = ts.consume();
2705
- _setName(decl, input.slice(head.start, head.end), head.start, head.end);
2934
+ _setName(decl, head.start, head.end);
2706
2935
  } else {
2707
2936
  consumeTheRemnantsOfABadDeclaration(ts, nested);
2708
2937
  return undefined;
@@ -2723,23 +2952,39 @@ const consumeADeclaration = (ts, nested = false) => {
2723
2952
  // 4. Discard whitespace from input.
2724
2953
  while (ts.next().type === TT_WHITESPACE) ts.discard();
2725
2954
 
2955
+ // Step 8's custom-property test, computed early so the value parse can bail.
2956
+ const isCustomProperty = input.startsWith("--", start);
2957
+
2726
2958
  // 5. Consume a list of component values from input, with nested, and with <semicolon-token> as the stop token, and set decl's value to the result.
2727
- const value = consumeAListOfComponentValues(ts, TT_SEMICOLON, nested);
2728
- _setValue(decl, value);
2959
+ // A nested non-custom declaration bails on a top-level `{` — step 8 would
2960
+ // reject it and the caller restores its mark, so parsing the block (the
2961
+ // entire nested-rule body, re-parsed as a qualified rule after the
2962
+ // restore) would be pure waste.
2963
+ const value = consumeAListOfComponentValues(
2964
+ ts,
2965
+ TT_SEMICOLON,
2966
+ nested,
2967
+ nested && !isCustomProperty
2968
+ );
2969
+ if (value === null) return undefined;
2970
+ // `_setValue` waits until step 9: steps 6-8 still trim / scan the scratch,
2971
+ // and the SoA backend consumes it when sealing.
2729
2972
  _setEnd(decl, ts.next().start);
2730
2973
 
2731
2974
  // 6. If the last two non-<whitespace-token>s in decl's value are a <delim-token> with the value "!" followed by an <ident-token> with a value that is an ASCII case-insensitive match for "important", remove them from decl's value and set decl's important flag.
2732
2975
  {
2733
2976
  let last = value.length - 1;
2734
- while (last >= 0 && _nType(value[last]) === T_WHITESPACE) last--;
2977
+ while (last >= 0 && _nodeTypeOf(value[last]) === T_WHITESPACE) last--;
2735
2978
  let prev = last - 1;
2736
- while (prev >= 0 && _nType(value[prev]) === T_WHITESPACE) prev--;
2979
+ while (prev >= 0 && _nodeTypeOf(value[prev]) === T_WHITESPACE) prev--;
2980
+ // `!` delim first: it's almost always absent, and `_nodeValueOf` allocates
2981
+ // a slice — this order pays it only for genuine `!important` candidates.
2737
2982
  if (
2738
2983
  prev >= 0 &&
2739
- _nType(value[last]) === T_IDENT &&
2740
- equalsLowerCase(_nValue(value[last]), "important") &&
2741
- _nType(value[prev]) === T_DELIM &&
2742
- input.charCodeAt(_nStart(value[prev])) === CC_EXCLAMATION
2984
+ _nodeTypeOf(value[prev]) === T_DELIM &&
2985
+ input.charCodeAt(_nodeStartOf(value[prev])) === CC_EXCLAMATION &&
2986
+ _nodeTypeOf(value[last]) === T_IDENT &&
2987
+ equalsLowerCase(_nodeValueOf(value[last]), "important")
2743
2988
  ) {
2744
2989
  _setImportant(decl);
2745
2990
  value.length = prev;
@@ -2747,7 +2992,10 @@ const consumeADeclaration = (ts, nested = false) => {
2747
2992
  }
2748
2993
 
2749
2994
  // 7. While the last item in decl's value is a <whitespace-token>, remove that token.
2750
- while (value.length > 0 && _nType(value[value.length - 1]) === T_WHITESPACE) {
2995
+ while (
2996
+ value.length > 0 &&
2997
+ _nodeTypeOf(value[value.length - 1]) === T_WHITESPACE
2998
+ ) {
2751
2999
  value.pop();
2752
3000
  }
2753
3001
 
@@ -2755,17 +3003,17 @@ const consumeADeclaration = (ts, nested = false) => {
2755
3003
  // Otherwise, if decl's value contains a top-level simple block with an associated token of <{-token>, return nothing.
2756
3004
  // (That is, a top-level {}-block is only allowed as the entire value of a non-custom property — for CSS Nesting, `consumeABlocksContents`'s `mark` / `restore a mark` will retry the input as a qualified rule.)
2757
3005
  // Otherwise, accept the declaration. (The spec also checks "contains any non-whitespace-tokens at the top level" → return nothing; we keep empty-value declarations because callers — e.g. `@value name:;` — rely on them.)
2758
- const isCustomProperty = input.startsWith("--", start);
2759
3006
  if (!isCustomProperty) {
2760
3007
  for (let i = 0; i < value.length; i++) {
2761
3008
  const v = value[i];
2762
- if (_nType(v) === T_SIMPLE_BLOCK && _nToken(v) === "{") {
3009
+ if (_nodeTypeOf(v) === T_SIMPLE_BLOCK && _nodeTokenOf(v) === "{") {
2763
3010
  return undefined;
2764
3011
  }
2765
3012
  }
2766
3013
  }
2767
3014
 
2768
3015
  // 9. Return decl.
3016
+ _setValue(decl, value);
2769
3017
  return decl;
2770
3018
  };
2771
3019
 
@@ -2774,11 +3022,16 @@ const consumeADeclaration = (ts, nested = false) => {
2774
3022
  * @param {TokenStream} ts token stream
2775
3023
  * @param {number=} stopToken token type that terminates the list (left unconsumed)
2776
3024
  * @param {boolean=} nested true inside a `{}` block — a top-level `}` ends the list (left unconsumed)
2777
- * @returns {ComponentValue[]} consumed component values
2778
- */
2779
- const consumeAListOfComponentValues = (ts, stopToken, nested = false) => {
2780
- /** @type {ComponentValue[]} */
2781
- const values = [];
3025
+ * @param {boolean=} bailOnCurly abort with `null` on a top-level `{` (left unconsumed) — for callers that would reject the list anyway (consume-a-declaration step 8) and restore a mark
3026
+ * @returns {ComponentValue[] | null} consumed component values, or `null` when `bailOnCurly` hit
3027
+ */
3028
+ const consumeAListOfComponentValues = (
3029
+ ts,
3030
+ stopToken,
3031
+ nested = false,
3032
+ bailOnCurly = false
3033
+ ) => {
3034
+ const values = /** @type {ComponentValue[]} */ (_soaActive ? _soaList() : []);
2782
3035
  // Process input
2783
3036
  for (;;) {
2784
3037
  const t = ts.next();
@@ -2797,9 +3050,14 @@ const consumeAListOfComponentValues = (ts, stopToken, nested = false) => {
2797
3050
  const closer = consumeATokenAsNode(ts);
2798
3051
  // Keep unless the type is explicitly marked skip (1); an out-of-range
2799
3052
  // lookup on a short `skip.types` yields `undefined`, which must not drop.
2800
- if (!_skipActive || _skipTypes[_nType(closer)] !== 1) values.push(closer);
3053
+ if (!_skipActive || _skipTypes[_nodeTypeOf(closer)] !== 1) {
3054
+ values.push(closer);
3055
+ }
2801
3056
  continue;
2802
3057
  }
3058
+ // A top-level `{` dooms the list for a bailing caller — stop before the
3059
+ // whole block is parsed only to be thrown away on the caller's restore.
3060
+ if (bailOnCurly && t.type === TT_LEFT_CURLY_BRACKET) return null;
2803
3061
  // anything else
2804
3062
  // Consume a component value from input, and append the result to values.
2805
3063
  // Skipped leaf types short-circuit before materializing: no SoA slot is
@@ -2815,7 +3073,7 @@ const consumeAListOfComponentValues = (ts, stopToken, nested = false) => {
2815
3073
  continue;
2816
3074
  }
2817
3075
  const node = consumeAComponentValue(ts, t);
2818
- if (!_skipActive || _skipTypes[_nType(node)] !== 1) values.push(node);
3076
+ if (!_skipActive || _skipTypes[_nodeTypeOf(node)] !== 1) values.push(node);
2819
3077
  }
2820
3078
  };
2821
3079
 
@@ -2861,11 +3119,11 @@ const consumeASimpleBlock = (ts) => {
2861
3119
 
2862
3120
  // Let block be a new simple block with its associated token set to the next token and with its value initially set to an empty list.
2863
3121
  const block = /** @type {SimpleBlock} */ (
2864
- _mkContainer(T_SIMPLE_BLOCK, open.start, open.end)
3122
+ _makeContainer(T_SIMPLE_BLOCK, open.start, open.end)
2865
3123
  );
2866
3124
  _setToken(block, token);
2867
- const val = _list();
2868
- _setValue(block, val);
3125
+ // Sealed (`_setValue`) at the return, once complete.
3126
+ const val = _soaActive ? _soaList() : [];
2869
3127
 
2870
3128
  // Discard a token from input.
2871
3129
  ts.discard();
@@ -2879,6 +3137,7 @@ const consumeASimpleBlock = (ts) => {
2879
3137
  // Discard a token from input. Return block.
2880
3138
  if (t.type === TT_EOF || t.type === ending) {
2881
3139
  ts.discard();
3140
+ _setValue(block, val);
2882
3141
  _setEnd(block, t.end);
2883
3142
  return block;
2884
3143
  }
@@ -2895,16 +3154,15 @@ const consumeASimpleBlock = (ts) => {
2895
3154
  * @returns {FunctionNode | undefined} the consumed function node
2896
3155
  */
2897
3156
  const consumeAFunction = (ts) => {
2898
- const { input } = ts;
2899
3157
  // Assert (spec): the next token is a <function-token>.
2900
3158
  // Consume a token from input, and let function be a new function with its name equal the returned token’s value, and a value set to an empty list.
2901
3159
  const tFn = ts.consume();
2902
3160
  const fn = /** @type {FunctionNode} */ (
2903
- _mkContainer(T_FUNCTION, tFn.start, tFn.end)
3161
+ _makeContainer(T_FUNCTION, tFn.start, tFn.end)
2904
3162
  );
2905
- _setName(fn, input.slice(tFn.start, tFn.end - 1), tFn.start, tFn.end - 1);
2906
- const val = _list();
2907
- _setValue(fn, val);
3163
+ _setName(fn, tFn.start, tFn.end - 1);
3164
+ // Sealed (`_setValue`) at the return, once complete.
3165
+ const val = _soaActive ? _soaList() : [];
2908
3166
 
2909
3167
  // Process input
2910
3168
  for (;;) {
@@ -2915,6 +3173,7 @@ const consumeAFunction = (ts) => {
2915
3173
  // <)-token>
2916
3174
  // Discard a token from input. Return function.
2917
3175
  ts.discard();
3176
+ _setValue(fn, val);
2918
3177
  _setEnd(fn, t.end);
2919
3178
  return fn;
2920
3179
  }
@@ -2933,7 +3192,7 @@ const consumeAFunction = (ts) => {
2933
3192
  continue;
2934
3193
  }
2935
3194
  const node = consumeAComponentValue(ts, t);
2936
- if (!_skipActive || _skipTypes[_nType(node)] !== 1) val.push(node);
3195
+ if (!_skipActive || _skipTypes[_nodeTypeOf(node)] !== 1) val.push(node);
2937
3196
  }
2938
3197
  };
2939
3198
 
@@ -2991,13 +3250,18 @@ const _escapeIdentifier = (str) => {
2991
3250
  }
2992
3251
  output = lastFlush === 0 ? str : output + str.slice(lastFlush);
2993
3252
 
2994
- const firstChar = str.charAt(0);
2995
-
2996
- if (/^-[-\d]/.test(output)) {
3253
+ // `-` and digits are class 0 (never escaped above), so testing `str`'s lead
3254
+ // char codes is equivalent to regexes over `output` — and keeps the common
3255
+ // nothing-to-do call regex-free.
3256
+ const first = str.charCodeAt(0);
3257
+ if (
3258
+ first === CC_HYPHEN_MINUS &&
3259
+ (str.charCodeAt(1) === CC_HYPHEN_MINUS || _isDigit(str.charCodeAt(1)))
3260
+ ) {
2997
3261
  output = `\\-${output.slice(1)}`;
2998
- } else if (/\d/.test(firstChar)) {
3262
+ } else if (_isDigit(first)) {
2999
3263
  // A leading digit becomes `\3<digit> `, another `\HEX ` run to clean up.
3000
- output = `\\3${firstChar} ${output.slice(1)}`;
3264
+ output = `\\3${str.charAt(0)} ${output.slice(1)}`;
3001
3265
  needSpaceFix = true;
3002
3266
  }
3003
3267
 
@@ -3031,7 +3295,6 @@ const _escapeIdentifier = (str) => {
3031
3295
  */
3032
3296
  const gobbleHex = (str, start) => {
3033
3297
  let hex = "";
3034
- let spaceTerminated = false;
3035
3298
 
3036
3299
  for (let i = 0; i < 6; i++) {
3037
3300
  const code = str.charCodeAt(start + i);
@@ -3040,8 +3303,6 @@ const gobbleHex = (str, start) => {
3040
3303
  (code >= 48 && code <= 57) ||
3041
3304
  (code >= 65 && code <= 70) ||
3042
3305
  (code >= 97 && code <= 102);
3043
- // https://drafts.csswg.org/css-syntax/#consume-escaped-code-point
3044
- spaceTerminated = code === 32;
3045
3306
  if (!valid) break;
3046
3307
  // parseInt below is case-insensitive, so keep the original char.
3047
3308
  hex += str[start + i];
@@ -3049,6 +3310,16 @@ const gobbleHex = (str, start) => {
3049
3310
 
3050
3311
  if (hex.length === 0) return undefined;
3051
3312
 
3313
+ // One trailing whitespace terminates the escape, matching the tokenizer's
3314
+ // `_consumeAnEscapedCodePoint` — including after a full 6-digit escape, for
3315
+ // any CSS whitespace (not just space), plus the extra LF of a CRLF pair.
3316
+ // https://drafts.csswg.org/css-syntax/#consume-escaped-code-point
3317
+ let consumed = hex.length;
3318
+ const trail = str.charCodeAt(start + hex.length);
3319
+ if (_isWhiteSpace(trail)) {
3320
+ consumed = consumeExtraNewline(trail, str, start + hex.length + 1) - start;
3321
+ }
3322
+
3052
3323
  const codePoint = Number.parseInt(hex, 16);
3053
3324
  const isSurrogate = codePoint >= 0xd800 && codePoint <= 0xdfff;
3054
3325
 
@@ -3056,13 +3327,10 @@ const gobbleHex = (str, start) => {
3056
3327
  // "If this number is zero, or is for a surrogate, or is greater than the maximum allowed code point"
3057
3328
  // https://drafts.csswg.org/css-syntax/#maximum-allowed-code-point
3058
3329
  if (isSurrogate || codePoint === 0x0000 || codePoint > 0x10ffff) {
3059
- return ["�", hex.length + (spaceTerminated ? 1 : 0)];
3330
+ return ["�", consumed];
3060
3331
  }
3061
3332
 
3062
- return [
3063
- String.fromCodePoint(codePoint),
3064
- hex.length + (spaceTerminated ? 1 : 0)
3065
- ];
3333
+ return [String.fromCodePoint(codePoint), consumed];
3066
3334
  };
3067
3335
 
3068
3336
  /**
@@ -3108,6 +3376,67 @@ const _unescapeIdentifier = (str) => {
3108
3376
  const escapeIdentifier = makeCacheable(_escapeIdentifier);
3109
3377
  const unescapeIdentifier = makeCacheable(_unescapeIdentifier);
3110
3378
 
3379
+ // A url-token / url-string value's escaped newlines (`url("im\<newline>g.png")`).
3380
+ const STRING_MULTILINE = /\\[\n\r\f]/g;
3381
+ // Leading / trailing CSS whitespace inside a quoted url value.
3382
+ const TRIM_WHITE_SPACES = /(^[ \t\n\r\f]*|[ \t\n\r\f]*$)/g;
3383
+ // One CSS escape: `\` + up to 6 hex digits (+ optional whitespace) or any char.
3384
+ const UNESCAPE = /\\([0-9a-f]{1,6}[ \t\n\r\f]?|[\s\S])/gi;
3385
+
3386
+ /**
3387
+ * Normalize a url value (a url-token's content or a url string's body) into
3388
+ * the form requests are resolved from: escaped newlines removed (string form),
3389
+ * edge whitespace trimmed, CSS escapes and percent-encoding decoded
3390
+ * (`data:` URIs excepted).
3391
+ * @param {string} str url string
3392
+ * @param {boolean} isString is url wrapped in quotes
3393
+ * @returns {string} normalized url
3394
+ */
3395
+ const normalizeUrl = (str, isString) => {
3396
+ // Fast paths: skip the regex engine for the common URL with no escape and
3397
+ // no edge whitespace (e.g. `./img.png`). Each guard is equivalent to the
3398
+ // regex being a no-op.
3399
+ // Remove escaped newlines from a string-token url like `url("im\<newline>g.png")`.
3400
+ if (isString && str.includes("\\")) {
3401
+ str = str.replace(STRING_MULTILINE, "");
3402
+ }
3403
+
3404
+ // Remove unnecessary spaces from `url(" img.png ")`
3405
+ if (
3406
+ str.length !== 0 &&
3407
+ (_isWhiteSpace(str.charCodeAt(0)) ||
3408
+ _isWhiteSpace(str.charCodeAt(str.length - 1)))
3409
+ ) {
3410
+ str = str.replace(TRIM_WHITE_SPACES, "");
3411
+ }
3412
+
3413
+ // Unescape
3414
+ if (str.includes("\\")) {
3415
+ str = str.replace(UNESCAPE, (match) => {
3416
+ if (match.length > 2) {
3417
+ return String.fromCharCode(Number.parseInt(match.slice(1).trim(), 16));
3418
+ }
3419
+ return match[1];
3420
+ });
3421
+ }
3422
+
3423
+ // Char-code gate so the dominant non-`data:` url skips the regex test.
3424
+ if ((str.charCodeAt(0) | 0x20) === CC_LOWER_D && /^data:/i.test(str)) {
3425
+ return str;
3426
+ }
3427
+
3428
+ if (str.includes("%")) {
3429
+ // Convert `url('%2E/img.png')` -> `url('./img.png')`
3430
+ try {
3431
+ str = decodeURIComponent(str);
3432
+ } catch (_err) {
3433
+ // Ignore
3434
+ }
3435
+ }
3436
+
3437
+ return str;
3438
+ };
3439
+
3111
3440
  // CSS-typed views over the generic visitor machinery (`util/SourceProcessor`),
3112
3441
  // re-exported so consumers keep importing them from this module.
3113
3442
  /**
@@ -3151,6 +3480,137 @@ const TOP_LEVEL_CONSUMERS = {
3151
3480
  * @property {boolean=} atRulePrelude drop at-rule preludes — the at-rule and its block are still produced (default false)
3152
3481
  */
3153
3482
 
3483
+ // Per-parse walk state in module slots (same pattern as `_skip*`) so the walk
3484
+ // functions below are module-level constants: one function identity across
3485
+ // parses keeps the recursive per-node call sites monomorphic and drops the
3486
+ // per-parse closure allocations.
3487
+ /** @typedef {import("../util/SourceProcessor").CompiledVisitorBucket<CssPath>} CompiledVisitorBucket */
3488
+ /** @type {CompiledVisitorMap} */
3489
+ let _visitors = /** @type {CompiledVisitorMap} */ (/** @type {unknown} */ ([]));
3490
+ let _recurseBlocks = true;
3491
+ /** @type {CompiledVisitorBucket | undefined} */
3492
+ let _commentBucket;
3493
+
3494
+ // Comments reach the visitor map through `NodeType.Comment` instead of a
3495
+ // side callback. They fire during tokenization — in source order among
3496
+ // comments, not interleaved with the node walk — on a transient SoA node so
3497
+ // `A.start`/`end`/`loc`/`source` work. No comment visitor → no callback →
3498
+ // the tokenizer skips comments with zero overhead.
3499
+ /** @type {(input: string, start: number, end: number) => number} */
3500
+ const _grammarOnComment = (_input, start, end) => {
3501
+ const node = _soaAllocNode(T_COMMENT, start, end);
3502
+ _currentNode = node;
3503
+ _currentParent = null;
3504
+ const bucket = /** @type {CompiledVisitorBucket} */ (_commentBucket);
3505
+ const e = bucket.enter;
3506
+ for (let i = 0; i < e.length; i++) e[i](A);
3507
+ const x = bucket.exit;
3508
+ for (let i = 0; i < x.length; i++) x[i](A);
3509
+ return end;
3510
+ };
3511
+
3512
+ /**
3513
+ * Walk a component-value subtree; children are already materialized. Fetches
3514
+ * the node's visitor bucket once (reused for enter + exit) and uses index
3515
+ * loops — `for…of` would allocate an iterator per node on this hot path.
3516
+ * @param {Node} node component-value root
3517
+ * @param {Node | null} parent enclosing node
3518
+ */
3519
+ const _walkValue = (node, parent) => {
3520
+ const ty = _soaTypes[_nodeIndex(node)];
3521
+ const b = _visitors[ty];
3522
+ let skip = false;
3523
+ if (b !== undefined && b.enter.length !== 0) {
3524
+ _walkSkip = false;
3525
+ _currentNode = node;
3526
+ _currentParent = parent;
3527
+ const e = b.enter;
3528
+ for (let i = 0; i < e.length; i++) e[i](A);
3529
+ skip = _walkSkip;
3530
+ _walkSkip = false;
3531
+ }
3532
+ if (!skip && (ty === T_FUNCTION || ty === T_SIMPLE_BLOCK)) {
3533
+ const i0 = _nodeIndex(node);
3534
+ const vs = _soaListStarts[i0];
3535
+ const ve = vs + _soaListLens[i0];
3536
+ for (let i = vs; i < ve; i++) _walkValue(_nodeRef(_soaFlat[i]), node);
3537
+ }
3538
+ if (b !== undefined) {
3539
+ // Rebind: descending into children moved the path.
3540
+ _currentNode = node;
3541
+ _currentParent = parent;
3542
+ const x = b.exit;
3543
+ for (let i = 0; i < x.length; i++) x[i](A);
3544
+ }
3545
+ };
3546
+
3547
+ /**
3548
+ * Walk a structural subtree; an at-rule / qualified-rule's block was parsed
3549
+ * eagerly (§5.4.4), so its `value` holds the nested rules / declarations.
3550
+ * @param {Node} node structural-tree root
3551
+ * @param {Node | null} parent enclosing node
3552
+ */
3553
+ const _walkRule = (node, parent) => {
3554
+ const i0 = _nodeIndex(node);
3555
+ const ty = _soaTypes[i0];
3556
+ const b = _visitors[ty];
3557
+ let skip = false;
3558
+ if (b !== undefined && b.enter.length !== 0) {
3559
+ _walkSkip = false;
3560
+ _currentNode = node;
3561
+ _currentParent = parent;
3562
+ const e = b.enter;
3563
+ for (let i = 0; i < e.length; i++) e[i](A);
3564
+ skip = _walkSkip;
3565
+ _walkSkip = false;
3566
+ }
3567
+ if (!skip) {
3568
+ if (ty === T_AT_RULE || ty === T_QUALIFIED_RULE) {
3569
+ const ps = _soaListStarts[i0];
3570
+ const pe = ps + _soaListLens[i0];
3571
+ for (let i = ps; i < pe; i++) _walkValue(_nodeRef(_soaFlat[i]), node);
3572
+ if (_recurseBlocks) {
3573
+ // Declarations then child rules — downstream consumers don't need them strictly interleaved in source order.
3574
+ const decls = _soaDeclarationLists[i0];
3575
+ if (decls) {
3576
+ for (let i = 0; i < decls.length; i++) _walkRule(decls[i], node);
3577
+ }
3578
+ const ch = _soaChildRuleLists[i0];
3579
+ if (ch) for (let i = 0; i < ch.length; i++) _walkRule(ch[i], node);
3580
+ }
3581
+ } else if (ty === T_DECLARATION) {
3582
+ const vs = _soaListStarts[i0];
3583
+ const ve = vs + _soaListLens[i0];
3584
+ for (let i = vs; i < ve; i++) _walkValue(_nodeRef(_soaFlat[i]), node);
3585
+ }
3586
+ }
3587
+ if (b !== undefined) {
3588
+ // Rebind: descending into children moved the path.
3589
+ _currentNode = node;
3590
+ _currentParent = parent;
3591
+ const x = b.exit;
3592
+ for (let i = 0; i < x.length; i++) x[i](A);
3593
+ }
3594
+ };
3595
+
3596
+ /**
3597
+ * The `grammar` streaming sink: walk one top-level node, then recycle the SoA
3598
+ * buffers for the next.
3599
+ * @param {Rule | Declaration} node top-level node
3600
+ */
3601
+ const _walkTopLevel = (node) => {
3602
+ _walkRule(node, null);
3603
+ if (_soaNodeCount > _soaPeak) _soaPeak = _soaNodeCount;
3604
+ if (_soaFlatTop > _soaFlatPeak) _soaFlatPeak = _soaFlatTop;
3605
+ _soaNodeCount = 0;
3606
+ _soaFlatTop = 0;
3607
+ };
3608
+
3609
+ // The SoA buffers grow to the largest single top-level rule ever parsed and
3610
+ // live at module level; above this capacity they are re-shrunk after a parse
3611
+ // so one pathological rule can't pin megabytes for the process lifetime.
3612
+ const _SOA_SHRINK_CAPACITY = 65536;
3613
+
3154
3614
  /**
3155
3615
  * The CSS `SourceProcessor` grammar: consume top-level rules one at a time
3156
3616
  * (§5.4.1) and walk each immediately, firing `enter` / `exit` in source order
@@ -3169,130 +3629,58 @@ const grammar = (input, visitors, options) => {
3169
3629
  _skipActive = _skipTypes !== _NO_SKIP_TYPES;
3170
3630
  _skipSelectorPrelude = skip !== undefined && skip.selectorPrelude === true;
3171
3631
  _skipAtRulePrelude = skip !== undefined && skip.atRulePrelude === true;
3172
- const recurseBlocks = options.recurseBlocks !== false;
3173
-
3174
- // Comments reach the visitor map through `NodeType.Comment` instead of a
3175
- // side callback. They fire during tokenization — in source order among
3176
- // comments, not interleaved with the node walk — on a transient SoA node so
3177
- // `A.start`/`end`/`loc`/`source` work. No comment visitor → no callback →
3178
- // the tokenizer skips comments with zero overhead.
3179
- const commentBucket = visitors[T_COMMENT];
3180
- const onComment =
3181
- commentBucket === undefined
3182
- ? undefined
3183
- : /** @type {(input: string, start: number, end: number) => number} */ (
3184
- (_input, start, end) => {
3185
- const node = _soaAlloc(T_COMMENT, start, end);
3186
- _curNode = node;
3187
- _curParent = null;
3188
- const e = commentBucket.enter;
3189
- for (let i = 0; i < e.length; i++) e[i](A);
3190
- const x = commentBucket.exit;
3191
- for (let i = 0; i < x.length; i++) x[i](A);
3192
- return end;
3193
- }
3194
- );
3195
-
3196
- /**
3197
- * Walk a component-value subtree; children are already materialized. Fetches
3198
- * the node's visitor bucket once (reused for enter + exit) and uses index
3199
- * loops — `for…of` would allocate an iterator per node on this hot path.
3200
- * @param {Node} node component-value root
3201
- * @param {Node | null} parent enclosing node
3202
- */
3203
- const walkValue = (node, parent) => {
3204
- const ty = _soaTy[_ix(node)];
3205
- const b = visitors[ty];
3206
- let skip = false;
3207
- if (b !== undefined && b.enter.length !== 0) {
3208
- _walkSkip = false;
3209
- _curNode = node;
3210
- _curParent = parent;
3211
- const e = b.enter;
3212
- for (let i = 0; i < e.length; i++) e[i](A);
3213
- skip = _walkSkip;
3214
- _walkSkip = false;
3215
- }
3216
- if (!skip && (ty === T_FUNCTION || ty === T_SIMPLE_BLOCK)) {
3217
- const v = /** @type {Node[]} */ (_soaL0[_ix(node)]);
3218
- for (let i = 0; i < v.length; i++) walkValue(v[i], node);
3219
- }
3220
- if (b !== undefined) {
3221
- // Rebind: descending into children moved the path.
3222
- _curNode = node;
3223
- _curParent = parent;
3224
- const x = b.exit;
3225
- for (let i = 0; i < x.length; i++) x[i](A);
3226
- }
3227
- };
3228
-
3229
- /**
3230
- * Walk a structural subtree; an at-rule / qualified-rule's block was parsed
3231
- * eagerly (§5.4.4), so its `value` holds the nested rules / declarations.
3232
- * @param {Node} node structural-tree root
3233
- * @param {Node | null} parent enclosing node
3234
- */
3235
- const walkRule = (node, parent) => {
3236
- const i0 = _ix(node);
3237
- const ty = _soaTy[i0];
3238
- const b = visitors[ty];
3239
- let skip = false;
3240
- if (b !== undefined && b.enter.length !== 0) {
3241
- _walkSkip = false;
3242
- _curNode = node;
3243
- _curParent = parent;
3244
- const e = b.enter;
3245
- for (let i = 0; i < e.length; i++) e[i](A);
3246
- skip = _walkSkip;
3247
- _walkSkip = false;
3248
- }
3249
- if (!skip) {
3250
- if (ty === T_AT_RULE || ty === T_QUALIFIED_RULE) {
3251
- const p = /** @type {Node[]} */ (_soaL0[i0]);
3252
- for (let i = 0; i < p.length; i++) walkValue(p[i], node);
3253
- if (recurseBlocks) {
3254
- // Declarations then child rules — downstream consumers don't need them strictly interleaved in source order.
3255
- const decls = _soaL1[i0];
3256
- if (decls) {
3257
- for (let i = 0; i < decls.length; i++) walkRule(decls[i], node);
3258
- }
3259
- const ch = _soaL2[i0];
3260
- if (ch) for (let i = 0; i < ch.length; i++) walkRule(ch[i], node);
3261
- }
3262
- } else if (ty === T_DECLARATION) {
3263
- const v = /** @type {Node[]} */ (_soaL0[i0]);
3264
- for (let i = 0; i < v.length; i++) walkValue(v[i], node);
3265
- }
3266
- }
3267
- if (b !== undefined) {
3268
- // Rebind: descending into children moved the path.
3269
- _curNode = node;
3270
- _curParent = parent;
3271
- const x = b.exit;
3272
- for (let i = 0; i < x.length; i++) x[i](A);
3273
- }
3274
- };
3632
+ _recurseBlocks = options.recurseBlocks !== false;
3633
+ _visitors = visitors;
3634
+ _commentBucket = visitors[T_COMMENT];
3275
3635
 
3276
3636
  // Stream each top-level node (selected by `as`) to the walker the moment it's
3277
3637
  // consumed, rather than collecting them first — so the whole AST is never
3278
3638
  // held at once; peak heap is ~one top-level node's subtree.
3279
- const ts = new TokenStream(input, 0, locConverter, onComment);
3639
+ const ts = new TokenStream(
3640
+ input,
3641
+ 0,
3642
+ locConverter,
3643
+ _commentBucket === undefined ? undefined : _grammarOnComment
3644
+ );
3280
3645
  const consume =
3281
3646
  TOP_LEVEL_CONSUMERS[options.as || "stylesheet"] ||
3282
3647
  consumeAStylesheetsContents;
3283
3648
  try {
3284
- consume(ts, (node) => {
3285
- walkRule(node, null);
3286
- _soaN = 0;
3287
- });
3649
+ consume(ts, _walkTopLevel);
3288
3650
  } finally {
3289
3651
  // Drop the module-level SoA references so the last parsed source (and
3290
- // its LocConverter / child lists) don't stay alive between parses.
3652
+ // its LocConverter / child lists / visitors) don't stay alive between
3653
+ // parses.
3291
3654
  _soaInput = "";
3292
- _soaLC = /** @type {LocConverter} */ (/** @type {unknown} */ (null));
3293
- _soaL0.length = 0;
3294
- _soaL1.length = 0;
3295
- _soaL2.length = 0;
3655
+ _soaLocConverter = /** @type {LocConverter} */ (
3656
+ /** @type {unknown} */ (null)
3657
+ );
3658
+ _soaDeclarationLists.length = 0;
3659
+ _soaChildRuleLists.length = 0;
3660
+ _soaFlatTop = 0;
3661
+ _listPool.length = 0;
3662
+ if (_soaFlat.length > _SOA_SHRINK_CAPACITY) {
3663
+ _soaFlatGrowHint = _soaFlatPeak;
3664
+ _soaFlat = new Int32Array(0);
3665
+ }
3666
+ _visitors = /** @type {CompiledVisitorMap} */ (/** @type {unknown} */ ([]));
3667
+ _commentBucket = undefined;
3668
+ if (_soaCapacity > _SOA_SHRINK_CAPACITY) {
3669
+ // +1: node ids are 1-based and grow fires at `id >= capacity`.
3670
+ _soaGrowHint = _soaPeak + 1;
3671
+ _soaCapacity = 0;
3672
+ _soaTypes = new Uint8Array(0);
3673
+ _soaStarts = new Int32Array(0);
3674
+ _soaEnds = new Int32Array(0);
3675
+ _soaAux0 = new Int32Array(0);
3676
+ _soaAux1 = new Int32Array(0);
3677
+ _soaAux2 = new Int32Array(0);
3678
+ _soaFlags = new Uint8Array(0);
3679
+ _soaListStarts = new Int32Array(0);
3680
+ _soaListLens = new Int32Array(0);
3681
+ }
3682
+ _soaPeak = 0;
3683
+ _soaFlatPeak = 0;
3296
3684
  }
3297
3685
  };
3298
3686
 
@@ -3340,15 +3728,28 @@ const buildSkipSet = (nodeTypes) => {
3340
3728
  */
3341
3729
  /* eslint-enable jsdoc/require-template */
3342
3730
 
3731
+ // A fresh (safely retainable) array view of a node's flat content span —
3732
+ // visitors that read `A.children` / `A.prelude` may keep the result.
3733
+ /** @type {(n: Node) => Node[]} */
3734
+ const _materializeList = (n) => {
3735
+ const i = _nodeIndex(n);
3736
+ const start = _soaListStarts[i];
3737
+ const len = _soaListLens[i];
3738
+ /** @type {Node[]} */
3739
+ const out = [];
3740
+ for (let k = 0; k < len; k++) out.push(_nodeRef(_soaFlat[start + k]));
3741
+ return out;
3742
+ };
3743
+
3343
3744
  // Babel's `path.skip()`, children-only: set by `A.skipChildren()` during an
3344
3745
  // `enter` dispatch, consumed by the walk.
3345
3746
  let _walkSkip = false;
3346
3747
  // The walk's current position (`A.node` / `A.parent` read these; module-level
3347
3748
  // so the accessor methods' defaults avoid self-referential `this` typing).
3348
3749
  /** @type {Node} */
3349
- let _curNode = /** @type {Node} */ (/** @type {unknown} */ (0));
3750
+ let _currentNode = /** @type {Node} */ (/** @type {unknown} */ (0));
3350
3751
  /** @type {Node | null} */
3351
- let _curParent = null;
3752
+ let _currentParent = null;
3352
3753
 
3353
3754
  // AST field-access seam. Every AST-node field read by `CssParser` goes through
3354
3755
  // one of these accessors so the node representation can change underneath the
@@ -3363,13 +3764,13 @@ const A = {
3363
3764
  * @returns {Node} current node — only valid during a visitor callback
3364
3765
  */
3365
3766
  get node() {
3366
- return _curNode;
3767
+ return _currentNode;
3367
3768
  },
3368
3769
  /**
3369
3770
  * @returns {Node | null} enclosing node (null = a top-level node)
3370
3771
  */
3371
3772
  get parent() {
3372
- return _curParent;
3773
+ return _currentParent;
3373
3774
  },
3374
3775
  /** Stop the walk descending into the current node (enter only). */
3375
3776
  skipChildren() {
@@ -3380,42 +3781,42 @@ const A = {
3380
3781
  * @param {Node=} n node
3381
3782
  * @returns {number} node type
3382
3783
  */
3383
- type(n = _curNode) {
3384
- return _soaTy[_ix(n)];
3784
+ type(n = _currentNode) {
3785
+ return _soaTypes[_nodeIndex(n)];
3385
3786
  },
3386
3787
  /**
3387
3788
  * @param {Node=} n node
3388
3789
  * @returns {number} start offset
3389
3790
  */
3390
- start(n = _curNode) {
3391
- return _soaSt[_ix(n)];
3791
+ start(n = _currentNode) {
3792
+ return _soaStarts[_nodeIndex(n)];
3392
3793
  },
3393
3794
  /**
3394
3795
  * @param {Node=} n node
3395
3796
  * @returns {number} end offset
3396
3797
  */
3397
- end(n = _curNode) {
3398
- return _soaEn[_ix(n)];
3798
+ end(n = _currentNode) {
3799
+ return _soaEnds[_nodeIndex(n)];
3399
3800
  },
3400
3801
  /**
3401
3802
  * @param {Node=} n node
3402
3803
  * @returns {[number, number]} start / end offsets
3403
3804
  */
3404
- range(n = _curNode) {
3405
- const i = _ix(n);
3406
- return [_soaSt[i], _soaEn[i]];
3805
+ range(n = _currentNode) {
3806
+ const i = _nodeIndex(n);
3807
+ return [_soaStarts[i], _soaEnds[i]];
3407
3808
  },
3408
3809
  /**
3409
3810
  * @param {Node=} n node
3410
3811
  * @returns {{ start: { line: number, column: number }, end: { line: number, column: number } }} source location
3411
3812
  */
3412
- loc(n = _curNode) {
3413
- const i = _ix(n);
3414
- const lc = _soaLC;
3415
- const s = lc.get(_soaSt[i]);
3813
+ loc(n = _currentNode) {
3814
+ const i = _nodeIndex(n);
3815
+ const lc = _soaLocConverter;
3816
+ const s = lc.get(_soaStarts[i]);
3416
3817
  const sl = s.line;
3417
3818
  const sc = s.column;
3418
- const e = lc.get(_soaEn[i]);
3819
+ const e = lc.get(_soaEnds[i]);
3419
3820
  return {
3420
3821
  start: { line: sl, column: sc },
3421
3822
  end: { line: e.line, column: e.column }
@@ -3425,25 +3826,25 @@ const A = {
3425
3826
  * @param {Node=} n node
3426
3827
  * @returns {string} raw source slice
3427
3828
  */
3428
- source(n = _curNode) {
3429
- const i = _ix(n);
3430
- return _soaInput.slice(_soaSt[i], _soaEn[i]);
3829
+ source(n = _currentNode) {
3830
+ const i = _nodeIndex(n);
3831
+ return _soaInput.slice(_soaStarts[i], _soaEnds[i]);
3431
3832
  },
3432
3833
  /**
3433
3834
  * @param {Node=} n node
3434
3835
  * @returns {string} raw token value
3435
3836
  */
3436
- value(n = _curNode) {
3437
- return _soaValueOf(_ix(n));
3837
+ value(n = _currentNode) {
3838
+ return _soaValueOf(_nodeIndex(n));
3438
3839
  },
3439
3840
  /**
3440
3841
  * @param {Node=} n node
3441
3842
  * @returns {string} unescaped token value
3442
3843
  */
3443
- unescaped(n = _curNode) {
3444
- const i = _ix(n);
3844
+ unescaped(n = _currentNode) {
3845
+ const i = _nodeIndex(n);
3445
3846
  const v = _soaValueOf(i);
3446
- return _soaTy[i] === T_STRING
3847
+ return _soaTypes[i] === T_STRING
3447
3848
  ? unescapeIdentifier(v.slice(1, -1))
3448
3849
  : unescapeIdentifier(v);
3449
3850
  },
@@ -3451,11 +3852,11 @@ const A = {
3451
3852
  * @param {Node=} n node
3452
3853
  * @returns {string} hash / numeric type flag
3453
3854
  */
3454
- typeFlag(n = _curNode) {
3455
- const i = _ix(n);
3456
- if (_soaTy[i] === T_HASH) {
3855
+ typeFlag(n = _currentNode) {
3856
+ const i = _nodeIndex(n);
3857
+ if (_soaTypes[i] === T_HASH) {
3457
3858
  const input = _soaInput;
3458
- const p = _soaSt[i] + 1;
3859
+ const p = _soaStarts[i] + 1;
3459
3860
  return _ifThreeCodePointsWouldStartAnIdentSequence(
3460
3861
  input,
3461
3862
  p,
@@ -3468,109 +3869,113 @@ const A = {
3468
3869
  }
3469
3870
  const v = _soaValueOf(i);
3470
3871
  return _typeFlagOf(
3471
- _soaTy[i] === T_DIMENSION ? v.slice(0, _consumeANumber(v, 0)) : v
3872
+ _soaTypes[i] === T_DIMENSION ? v.slice(0, _consumeANumber(v, 0)) : v
3472
3873
  );
3473
3874
  },
3474
3875
  /**
3475
3876
  * @param {Node=} n node
3476
3877
  * @returns {number} url content start offset
3477
3878
  */
3478
- contentStart(n = _curNode) {
3479
- return _soaA0[_ix(n)];
3879
+ contentStart(n = _currentNode) {
3880
+ return _soaAux0[_nodeIndex(n)];
3480
3881
  },
3481
3882
  /**
3482
3883
  * @param {Node=} n node
3483
3884
  * @returns {number} url content end offset
3484
3885
  */
3485
- contentEnd(n = _curNode) {
3486
- return _soaA1[_ix(n)];
3886
+ contentEnd(n = _currentNode) {
3887
+ return _soaAux1[_nodeIndex(n)];
3487
3888
  },
3488
3889
  /**
3489
3890
  * @param {Node=} n node
3490
3891
  * @returns {string} rule / declaration / function name
3491
3892
  */
3492
- name(n = _curNode) {
3493
- const i = _ix(n);
3494
- return _soaTy[i] === T_AT_RULE
3495
- ? _soaInput.slice(_soaSt[i] + 1, _soaA0[i])
3496
- : _soaInput.slice(_soaSt[i], _soaA0[i]);
3893
+ name(n = _currentNode) {
3894
+ const i = _nodeIndex(n);
3895
+ return _soaTypes[i] === T_AT_RULE
3896
+ ? _soaInput.slice(_soaStarts[i] + 1, _soaAux0[i])
3897
+ : _soaInput.slice(_soaStarts[i], _soaAux0[i]);
3497
3898
  },
3498
3899
  /**
3499
3900
  * @param {Node=} n node
3500
3901
  * @returns {number} name start offset
3501
3902
  */
3502
- nameStart(n = _curNode) {
3503
- return _soaSt[_ix(n)];
3903
+ nameStart(n = _currentNode) {
3904
+ return _soaStarts[_nodeIndex(n)];
3504
3905
  },
3505
3906
  /**
3506
3907
  * @param {Node=} n node
3507
3908
  * @returns {number} name end offset
3508
3909
  */
3509
- nameEnd(n = _curNode) {
3510
- return _soaA0[_ix(n)];
3910
+ nameEnd(n = _currentNode) {
3911
+ return _soaAux0[_nodeIndex(n)];
3511
3912
  },
3512
3913
  /**
3513
3914
  * @param {Node=} n node
3514
3915
  * @returns {string} unescaped name
3515
3916
  */
3516
- unescapedName(n = _curNode) {
3917
+ unescapedName(n = _currentNode) {
3517
3918
  return unescapeIdentifier(A.name(n));
3518
3919
  },
3519
3920
  /**
3520
3921
  * @param {Node=} n node
3521
3922
  * @returns {ComponentValue[]} function / block children
3522
3923
  */
3523
- children(n = _curNode) {
3524
- return /** @type {ComponentValue[]} */ (_soaL0[_ix(n)]);
3924
+ children(n = _currentNode) {
3925
+ return /** @type {ComponentValue[]} */ (_materializeList(n));
3525
3926
  },
3526
3927
  /**
3527
3928
  * @param {Node=} n node
3528
3929
  * @returns {ComponentValue[]} rule prelude
3529
3930
  */
3530
- prelude(n = _curNode) {
3531
- return /** @type {ComponentValue[]} */ (_soaL0[_ix(n)]);
3931
+ prelude(n = _currentNode) {
3932
+ return /** @type {ComponentValue[]} */ (_materializeList(n));
3532
3933
  },
3533
3934
  /**
3534
3935
  * @param {Node=} n node
3535
3936
  * @returns {Declaration[] | null} block declarations
3536
3937
  */
3537
- declarations(n = _curNode) {
3538
- return /** @type {Declaration[] | null} */ (_soaL1[_ix(n)]);
3938
+ declarations(n = _currentNode) {
3939
+ return /** @type {Declaration[] | null} */ (
3940
+ _soaDeclarationLists[_nodeIndex(n)]
3941
+ );
3539
3942
  },
3540
3943
  /**
3541
3944
  * @param {Node=} n node
3542
3945
  * @returns {Rule[] | null} block child rules
3543
3946
  */
3544
- childRules(n = _curNode) {
3545
- return /** @type {Rule[] | null} */ (_soaL2[_ix(n)]);
3947
+ childRules(n = _currentNode) {
3948
+ return /** @type {Rule[] | null} */ (_soaChildRuleLists[_nodeIndex(n)]);
3546
3949
  },
3547
3950
  /**
3548
3951
  * @param {Node=} n node
3549
3952
  * @returns {number} block start offset
3550
3953
  */
3551
- blockStart(n = _curNode) {
3552
- return _soaA1[_ix(n)];
3954
+ blockStart(n = _currentNode) {
3955
+ return _soaAux1[_nodeIndex(n)];
3553
3956
  },
3554
3957
  /**
3555
3958
  * @param {Node=} n node
3556
3959
  * @returns {number} block end offset
3557
3960
  */
3558
- blockEnd(n = _curNode) {
3559
- return _soaA2[_ix(n)];
3961
+ blockEnd(n = _currentNode) {
3962
+ return _soaAux2[_nodeIndex(n)];
3560
3963
  },
3561
3964
  /**
3562
3965
  * @param {Node=} n node
3563
3966
  * @returns {boolean} `!important` flag
3564
3967
  */
3565
- important(n = _curNode) {
3566
- return (_soaFl[_ix(n)] & 1) !== 0;
3968
+ important(n = _currentNode) {
3969
+ return (_soaFlags[_nodeIndex(n)] & 1) !== 0;
3567
3970
  },
3568
3971
  /**
3569
3972
  * @param {Node=} n node
3570
3973
  * @returns {SimpleBlockToken} block opening token
3571
3974
  */
3572
- blockToken(n = _curNode) {
3573
- return /** @type {SimpleBlockToken} */ (_soaInput[_soaSt[_ix(n)]]);
3975
+ blockToken(n = _currentNode) {
3976
+ return /** @type {SimpleBlockToken} */ (
3977
+ _soaInput[_soaStarts[_nodeIndex(n)]]
3978
+ );
3574
3979
  },
3575
3980
  // Writers — `CssParser` rewrites a rule's end / block-end when it folds an
3576
3981
  // inline ICSS `:import` / `:export` body into a single dependency. The
@@ -3580,14 +3985,14 @@ const A = {
3580
3985
  * @param {number} v new end offset
3581
3986
  */
3582
3987
  setEnd(n, v) {
3583
- _soaEn[_ix(n)] = v;
3988
+ _soaEnds[_nodeIndex(n)] = v;
3584
3989
  },
3585
3990
  /**
3586
3991
  * @param {Node} n node
3587
3992
  * @param {number} v new block end offset
3588
3993
  */
3589
3994
  setBlockEnd(n, v) {
3590
- _soaA2[_ix(n)] = v;
3995
+ _soaAux2[_nodeIndex(n)] = v;
3591
3996
  }
3592
3997
  };
3593
3998
 
@@ -3632,6 +4037,11 @@ module.exports.TokenStream = TokenStream;
3632
4037
  module.exports.buildSkipSet = buildSkipSet;
3633
4038
  module.exports.equalsLowerCase = equalsLowerCase;
3634
4039
  module.exports.escapeIdentifier = escapeIdentifier;
4040
+ module.exports.isDashedIdentifier = isDashedIdentifier;
4041
+ // CSS Syntax §4.2 "whitespace" (space / tab / newline / CR / FF) — the
4042
+ // tokenizer's whitespace class, exported under the spec's name.
4043
+ module.exports.isWhitespace = _isWhiteSpace;
4044
+ module.exports.normalizeUrl = normalizeUrl;
3635
4045
  module.exports.parseABlocksContents = parseABlocksContents;
3636
4046
  module.exports.parseACommaSeparatedListOfComponentValues =
3637
4047
  parseACommaSeparatedListOfComponentValues;
@@ -3641,5 +4051,8 @@ module.exports.parseAListOfComponentValues = parseAListOfComponentValues;
3641
4051
  module.exports.parseARule = parseARule;
3642
4052
  module.exports.parseAStylesheet = parseAStylesheet;
3643
4053
  module.exports.parseAStylesheetsContents = parseAStylesheetsContents;
4054
+ module.exports.rangeEquals = rangeEquals;
4055
+ module.exports.rangeEqualsLowerCase = rangeEqualsLowerCase;
3644
4056
  module.exports.readToken = readToken;
4057
+ module.exports.toLowerCaseIfNeeded = toLowerCaseIfNeeded;
3645
4058
  module.exports.unescapeIdentifier = unescapeIdentifier;