webpack 5.112.0 → 5.112.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (263) hide show
  1. package/bin/webpack.js +13 -7
  2. package/lib/Compilation.js +152 -60
  3. package/lib/Compiler.js +12 -25
  4. package/lib/asset/AssetBytesGenerator.js +14 -6
  5. package/lib/asset/AssetBytesParser.js +10 -8
  6. package/lib/asset/AssetGenerator.js +36 -48
  7. package/lib/asset/AssetModule.js +11 -0
  8. package/lib/asset/AssetModulesPlugin.js +5 -4
  9. package/lib/asset/AssetParser.js +8 -8
  10. package/lib/asset/AssetSourceGenerator.js +16 -8
  11. package/lib/asset/AssetSourceParser.js +10 -8
  12. package/lib/asset/WebManifestGenerator.js +11 -6
  13. package/lib/asset/WebManifestParser.js +3 -3
  14. package/lib/async-modules/AsyncModuleHelpers.js +8 -2
  15. package/lib/async-modules/InferAsyncModulesPlugin.js +7 -2
  16. package/lib/cache/CacheFacade.js +1 -1
  17. package/lib/cache/PackFileCacheStrategy.js +6 -10
  18. package/lib/cache/ResolverCachePlugin.js +58 -8
  19. package/lib/config/WebpackOptionsApply.js +8 -28
  20. package/lib/config/cli.js +9 -6
  21. package/lib/config/defaults.js +24 -13
  22. package/lib/config/getClaimedAssetTypes.js +2 -2
  23. package/lib/config/target.js +1 -1
  24. package/lib/context/ContextModule.js +1 -1
  25. package/lib/context/ContextModuleFactory.js +1 -1
  26. package/lib/context/ContextReplacementPlugin.js +3 -9
  27. package/lib/css/CssGenerator.js +40 -37
  28. package/lib/css/CssModule.js +10 -0
  29. package/lib/css/CssModulesPlugin.js +2 -2
  30. package/lib/css/CssParser.js +33 -32
  31. package/lib/css/conventions.js +1 -1
  32. package/lib/css/data.js +224 -219
  33. package/lib/css/syntax-parser.js +1159 -298
  34. package/lib/css/syntax-printer.js +90 -35
  35. package/lib/debug/ProfilingPlugin.js +65 -43
  36. package/lib/define/DefinePlugin.js +10 -15
  37. package/lib/define/DotenvPlugin.js +10 -6
  38. package/lib/define/EnvironmentPlugin.js +4 -2
  39. package/lib/dependencies/amd/AMDDefineDependencyParserPlugin.js +1 -2
  40. package/lib/dependencies/amd/AMDRequireDependenciesBlockParserPlugin.js +4 -1
  41. package/lib/dependencies/commonjs/CommonJsExportRequireDependency.js +1 -4
  42. package/lib/dependencies/commonjs/CommonJsExportsParserPlugin.js +18 -33
  43. package/lib/dependencies/commonjs/CommonJsImportsParserPlugin.js +1 -3
  44. package/lib/dependencies/commonjs/CommonJsObjectExportDependency.js +4 -4
  45. package/lib/dependencies/commonjs/CommonJsPlugin.js +2 -5
  46. package/lib/dependencies/commonjs/RequireEnsureDependenciesBlockParserPlugin.js +4 -1
  47. package/lib/dependencies/context/ImportMetaContextDependencyParserPlugin.js +1 -2
  48. package/lib/dependencies/context/ImportMetaGlobDependencyParserPlugin.js +4 -7
  49. package/lib/dependencies/context/ImportMetaGlobHelpers.js +8 -10
  50. package/lib/dependencies/core/CachedConstDependency.js +9 -6
  51. package/lib/dependencies/css/CssIcssExportDependency.js +6 -7
  52. package/lib/dependencies/css/CssIcssImportDependency.js +1 -3
  53. package/lib/dependencies/css/CssUrlDependency.js +2 -6
  54. package/lib/dependencies/esm/ESMDetectionParserPlugin.js +2 -4
  55. package/lib/dependencies/esm/ESMExportExpressionDependency.js +2 -2
  56. package/lib/dependencies/esm/ESMExportImportedSpecifierDependency.js +16 -18
  57. package/lib/dependencies/esm/ESMExportSpecifierDependency.js +20 -3
  58. package/lib/dependencies/esm/ESMExports.js +2 -10
  59. package/lib/dependencies/esm/ESMImportDependency.js +17 -2
  60. package/lib/dependencies/esm/ESMImportDependencyParserPlugin.js +6 -17
  61. package/lib/dependencies/esm/ESMImportGuard.js +11 -23
  62. package/lib/dependencies/esm/ESMImportSideEffectDependency.js +1 -1
  63. package/lib/dependencies/esm/ESMImportSpecifierDependency.js +4 -1
  64. package/lib/dependencies/esm/ESMLiveExportDependency.js +311 -0
  65. package/lib/dependencies/esm/ESMLiveExportParserPlugin.js +862 -0
  66. package/lib/dependencies/esm/ESMModulesPlugin.js +1 -3
  67. package/lib/dependencies/esm/TopLevelAwaitDependency.js +1 -8
  68. package/lib/dependencies/helpers/DynamicExports.js +4 -6
  69. package/lib/dependencies/html/HtmlEntryDependency.js +5 -14
  70. package/lib/dependencies/html/HtmlInlineHtmlDependency.js +1 -4
  71. package/lib/dependencies/html/HtmlInlineScriptDependency.js +2 -6
  72. package/lib/dependencies/html/HtmlInlineStyleDependency.js +1 -4
  73. package/lib/dependencies/import/ImportParserPlugin.js +3 -6
  74. package/lib/dependencies/import-meta/ImportMetaPlugin.js +4 -6
  75. package/lib/dependencies/url/URLParserPlugin.js +1 -1
  76. package/lib/dependencies/worker/WorkerAndWorkletPlugin.js +51 -20
  77. package/lib/devtool/ModuleFilenameHelpers.js +1 -1
  78. package/lib/devtool/SourceMapDevToolPlugin.js +1 -1
  79. package/lib/dll/DelegatedModule.js +3 -3
  80. package/lib/dll/DllModule.js +3 -1
  81. package/lib/dll/DllReferencePlugin.js +1 -4
  82. package/lib/errors/AbstractMethodError.js +1 -1
  83. package/lib/errors/ErrorHelpers.js +2 -1
  84. package/lib/errors/HookWebpackError.js +2 -1
  85. package/lib/errors/JSONParseError.js +4 -4
  86. package/lib/errors/WebpackError.js +3 -2
  87. package/lib/esm/ModuleChunkFormatPlugin.js +36 -1
  88. package/lib/externals/ExternalModule.js +12 -9
  89. package/lib/externals/ExternalModuleFactoryPlugin.js +1 -2
  90. package/lib/externals/ExternalsPlugin.js +1 -1
  91. package/lib/fs/FileSystemInfo.js +4 -4
  92. package/lib/graph/AsyncDependenciesBlock.js +1 -1
  93. package/lib/graph/ChunkGraph.js +5 -0
  94. package/lib/graph/Dependency.js +8 -2
  95. package/lib/graph/ExportsInfo.js +1 -1
  96. package/lib/graph/ModuleGraph.js +14 -3
  97. package/lib/graph/buildChunkGraph.js +17 -19
  98. package/lib/graph/findGraphRoots.js +1 -1
  99. package/lib/hmr/HotModuleReplacement.runtime.js +10 -9
  100. package/lib/hmr/HotModuleReplacementPlugin.js +5 -11
  101. package/lib/hmr/JavascriptHotModuleReplacement.runtime.js +16 -19
  102. package/lib/hmr/LazyCompilationPlugin.js +1 -7
  103. package/lib/html/HtmlGenerator.js +25 -17
  104. package/lib/html/HtmlModule.js +11 -1
  105. package/lib/html/HtmlModulesPlugin.js +12 -11
  106. package/lib/html/HtmlParser.js +10 -9
  107. package/lib/html/builtinEmbeddedRenderer.js +31 -33
  108. package/lib/html/data.js +42 -21
  109. package/lib/html/htmlMinify.js +4 -1
  110. package/lib/html/syntax-parser.js +34 -35
  111. package/lib/html/syntax-printer.js +2 -3
  112. package/lib/html/xmlMinify.js +4 -1
  113. package/lib/ids/HashedModuleIdsPlugin.js +1 -1
  114. package/lib/index.js +13 -12
  115. package/lib/javascript/APIPlugin.js +7 -13
  116. package/lib/javascript/BasicEvaluatedExpression.js +5 -5
  117. package/lib/javascript/CommonJsChunkFormatPlugin.js +2 -7
  118. package/lib/javascript/ConstPlugin.js +1 -1
  119. package/lib/javascript/JavascriptGenerator.js +16 -11
  120. package/lib/javascript/JavascriptMetaInfoPlugin.js +3 -15
  121. package/lib/javascript/JavascriptModule.js +10 -0
  122. package/lib/javascript/JavascriptModulesPlugin.js +2 -4
  123. package/lib/javascript/JavascriptParser.js +99 -11
  124. package/lib/javascript/ScopeAnalyzer.js +183 -107
  125. package/lib/javascript/UseStrictPlugin.js +2 -5
  126. package/lib/javascript/jsMinify.js +1 -1
  127. package/lib/javascript/regexp.js +2 -2
  128. package/lib/javascript/syntax-parser.js +2114 -1506
  129. package/lib/javascript/syntax-printer-data.js +167 -1
  130. package/lib/javascript/syntax-printer.js +9554 -2782
  131. package/lib/json/JsonGenerator.js +15 -7
  132. package/lib/json/JsonModule.js +11 -0
  133. package/lib/json/JsonParser.js +7 -6
  134. package/lib/library/AssignLibraryPlugin.js +1 -1
  135. package/lib/library/JsonpLibraryPlugin.js +1 -1
  136. package/lib/library/ModuleLibraryPlugin.js +399 -23
  137. package/lib/loaders/LoaderRunner.js +76 -38
  138. package/lib/loaders/loadLoader.js +16 -10
  139. package/lib/module/Generator.js +17 -9
  140. package/lib/module/Module.js +2 -2
  141. package/lib/module/NormalModule.js +7 -5
  142. package/lib/module/NormalModuleFactory.js +11 -17
  143. package/lib/module/Parser.js +9 -2
  144. package/lib/module/extractSourceMap.js +69 -18
  145. package/lib/module/findCaseMismatch.js +2 -2
  146. package/lib/node/ExternalModuleInitFragment.js +2 -2
  147. package/lib/node/ReadFileCompileAsyncWasmPlugin.js +4 -12
  148. package/lib/node/ReadFileCompileWasmPlugin.js +2 -6
  149. package/lib/node/nodeConsole.js +4 -3
  150. package/lib/optimize/CircularModulesPlugin.js +1 -1
  151. package/lib/optimize/ConcatenatedModule.js +2 -6
  152. package/lib/optimize/FlagDependencyExportsPlugin.js +6 -11
  153. package/lib/optimize/FlagDependencyUsagePlugin.js +28 -3
  154. package/lib/optimize/InlineExports.js +4 -7
  155. package/lib/optimize/LazyBarrels.js +41 -5
  156. package/lib/optimize/ModuleConcatenationPlugin.js +8 -10
  157. package/lib/optimize/RealContentHashPlugin.js +2 -6
  158. package/lib/optimize/SideEffectsFlagPlugin.js +7 -21
  159. package/lib/optimize/SplitChunksPlugin.js +11 -24
  160. package/lib/optimize/TupleQueue.js +1 -1
  161. package/lib/output/CleanPlugin.js +3 -1
  162. package/lib/output/CopyPlugin.js +3 -4
  163. package/lib/performance/EvalUsagePlugin.js +1 -3
  164. package/lib/performance/HotspotsPlugin.js +33 -9
  165. package/lib/performance/PureAnnotationsPlugin.js +16 -10
  166. package/lib/performance/TopLevelThisPlugin.js +17 -9
  167. package/lib/prefetch/ResourceHintPlugin.js +1 -1
  168. package/lib/provide/ProvidedDependency.js +36 -4
  169. package/lib/rules/BasicEffectRulePlugin.js +1 -1
  170. package/lib/rules/RuleSetCompiler.js +15 -5
  171. package/lib/rules/UseEffectRulePlugin.js +1 -3
  172. package/lib/runtime/AsyncModuleRuntimeModule.js +7 -1
  173. package/lib/runtime/GetChunkFilenameRuntimeModule.js +1 -5
  174. package/lib/runtime/RuntimeGlobals.js +6 -0
  175. package/lib/runtime/RuntimePlugin.js +2 -0
  176. package/lib/schemes/HttpUriPlugin.js +1 -2
  177. package/lib/schemes/VirtualUrlPlugin.js +3 -1
  178. package/lib/serialization/AggregateErrorSerializer.js +3 -3
  179. package/lib/serialization/BinaryMiddleware.js +4 -1
  180. package/lib/serialization/ErrorObjectSerializer.js +2 -5
  181. package/lib/serialization/ObjectMiddleware.js +17 -15
  182. package/lib/serialization/PlainObjectSerializer.js +3 -7
  183. package/lib/serialization/Serializer.js +21 -11
  184. package/lib/serialization/SerializerMiddleware.js +8 -6
  185. package/lib/stats/DefaultStatsFactoryPlugin.js +35 -35
  186. package/lib/stats/DefaultStatsPresetPlugin.js +2 -6
  187. package/lib/stats/DefaultStatsPrinterPlugin.js +12 -11
  188. package/lib/stats/StatsFactory.js +194 -24
  189. package/lib/stats/StatsPrinter.js +126 -13
  190. package/lib/template/DependencyTemplate.js +1 -1
  191. package/lib/template/RuntimeTemplate.js +13 -32
  192. package/lib/template/Template.js +1 -1
  193. package/lib/util/SourceProcessor.js +98 -15
  194. package/lib/util/WeakTupleMap.js +10 -4
  195. package/lib/util/cleverMerge.js +61 -33
  196. package/lib/util/comparators.js +9 -5
  197. package/lib/util/dataURL.js +2 -0
  198. package/lib/util/deprecation.js +4 -3
  199. package/lib/util/globUtils.js +1 -4
  200. package/lib/util/internalSerializables.js +2 -0
  201. package/lib/util/makeSerializable.js +6 -4
  202. package/lib/util/memoize.js +1 -1
  203. package/lib/util/parseJson.js +2 -2
  204. package/lib/util/serialization.js +31 -27
  205. package/lib/wasm/AsyncWasmModule.js +12 -2
  206. package/lib/wasm/AsyncWebAssemblyGenerator.js +12 -4
  207. package/lib/wasm/AsyncWebAssemblyJavascriptGenerator.js +13 -5
  208. package/lib/wasm/AsyncWebAssemblyParser.js +7 -5
  209. package/lib/wasm/UniversalCompileAsyncWasmPlugin.js +2 -6
  210. package/lib/wasm-sync/SyncWasmModule.js +16 -1
  211. package/lib/wasm-sync/WebAssemblyGenerator.js +15 -10
  212. package/lib/wasm-sync/WebAssemblyJavascriptGenerator.js +13 -5
  213. package/lib/wasm-sync/WebAssemblyParser.js +10 -12
  214. package/lib/web/FetchCompileAsyncWasmPlugin.js +4 -12
  215. package/lib/web/FetchCompileWasmPlugin.js +2 -6
  216. package/package.json +16 -18
  217. package/schemas/WebpackOptions.check.js +3 -3
  218. package/schemas/WebpackOptions.json +2 -2
  219. package/schemas/plugins/BannerPlugin.check.js +3 -3
  220. package/schemas/plugins/HtmlGeneratorOptions.check.js +3 -3
  221. package/schemas/plugins/HtmlParserOptions.check.js +3 -3
  222. package/schemas/plugins/IgnorePlugin.check.js +3 -3
  223. package/schemas/plugins/LoaderOptionsPlugin.check.js +3 -3
  224. package/schemas/plugins/ManifestPlugin.check.js +3 -3
  225. package/schemas/plugins/ProgressPlugin.check.js +3 -3
  226. package/schemas/plugins/SSRManifestPlugin.check.js +3 -3
  227. package/schemas/plugins/SourceMapDevToolPlugin.check.js +3 -3
  228. package/schemas/plugins/WatchIgnorePlugin.check.js +3 -3
  229. package/schemas/plugins/asset/AssetGeneratorOptions.check.js +3 -3
  230. package/schemas/plugins/asset/AssetInlineGeneratorOptions.check.js +3 -3
  231. package/schemas/plugins/asset/AssetParserOptions.check.js +3 -3
  232. package/schemas/plugins/asset/AssetResourceGeneratorOptions.check.js +3 -3
  233. package/schemas/plugins/container/ContainerPlugin.check.js +3 -3
  234. package/schemas/plugins/container/ContainerReferencePlugin.check.js +3 -3
  235. package/schemas/plugins/container/ExternalsType.check.js +3 -3
  236. package/schemas/plugins/container/ModuleFederationPlugin.check.js +3 -3
  237. package/schemas/plugins/css/CssAutoOrModuleParserOptions.check.js +3 -3
  238. package/schemas/plugins/css/CssGeneratorOptions.check.js +3 -3
  239. package/schemas/plugins/css/CssModuleGeneratorOptions.check.js +3 -3
  240. package/schemas/plugins/css/CssModuleParserOptions.check.js +3 -3
  241. package/schemas/plugins/css/CssParserOptions.check.js +3 -3
  242. package/schemas/plugins/debug/ProfilingPlugin.check.js +3 -3
  243. package/schemas/plugins/define/DotenvPluginOptions.check.js +7 -0
  244. package/schemas/plugins/define/DotenvPluginOptions.json +3 -0
  245. package/schemas/plugins/dll/DllPlugin.check.js +3 -3
  246. package/schemas/plugins/dll/DllReferencePlugin.check.js +3 -3
  247. package/schemas/plugins/ids/HashedModuleIdsPlugin.check.js +3 -3
  248. package/schemas/plugins/ids/OccurrenceChunkIdsPlugin.check.js +3 -3
  249. package/schemas/plugins/ids/OccurrenceModuleIdsPlugin.check.js +3 -3
  250. package/schemas/plugins/json/JsonModulesPluginGenerator.check.js +3 -3
  251. package/schemas/plugins/json/JsonModulesPluginParser.check.js +3 -3
  252. package/schemas/plugins/optimize/AggressiveSplittingPlugin.check.js +3 -3
  253. package/schemas/plugins/optimize/LimitChunkCountPlugin.check.js +3 -3
  254. package/schemas/plugins/optimize/MergeDuplicateChunksPlugin.check.js +3 -3
  255. package/schemas/plugins/optimize/MinChunkSizePlugin.check.js +3 -3
  256. package/schemas/plugins/output/CleanOptions.check.js +7 -0
  257. package/schemas/plugins/output/CleanOptions.json +3 -0
  258. package/schemas/plugins/schemes/HttpUriPlugin.check.js +3 -3
  259. package/schemas/plugins/schemes/VirtualUrlPlugin.check.js +3 -3
  260. package/schemas/plugins/sharing/ConsumeSharedPlugin.check.js +3 -3
  261. package/schemas/plugins/sharing/ProvideSharedPlugin.check.js +3 -3
  262. package/schemas/plugins/sharing/SharePlugin.check.js +3 -3
  263. package/types.d.ts +1482 -358
@@ -285,6 +285,8 @@ const CC_LOW_LINE = "_".charCodeAt(0);
285
285
  const CC_LOWER_A = "a".charCodeAt(0);
286
286
  const CC_LOWER_D = "d".charCodeAt(0);
287
287
  const CC_LOWER_F = "f".charCodeAt(0);
288
+ const CC_LOWER_I = "i".charCodeAt(0);
289
+ const CC_LOWER_S = "s".charCodeAt(0);
288
290
  const CC_LOWER_E = "e".charCodeAt(0);
289
291
  const CC_LOWER_U = "u".charCodeAt(0);
290
292
  const CC_LOWER_R = "r".charCodeAt(0);
@@ -1790,13 +1792,13 @@ const _NO_SKIP_TYPES = new Uint8Array(32);
1790
1792
  // rules, the common case. Every consumer reads these lists read-only, so one
1791
1793
  // instance replaces an empty array per rule, and freezing makes a push fail.
1792
1794
  const _EMPTY_LIST = /** @type {Rule[]} */ (
1793
- /** @type {unknown} */ (Object.freeze([]))
1795
+ /** @type {Rule[] | ReadonlyArray<never>} */ (Object.freeze([]))
1794
1796
  );
1795
1797
  // `consumeABlock`'s and `consumeABlocksContentsInto`'s results, written instead
1796
1798
  // of returned — see there. Read them immediately; the next block overwrites them.
1797
1799
  /** @type {Declaration[]} */
1798
1800
  let _blockDecls = /** @type {Declaration[]} */ (
1799
- /** @type {unknown} */ (_EMPTY_LIST)
1801
+ /** @type {Declaration[] | Rule[]} */ (_EMPTY_LIST)
1800
1802
  );
1801
1803
  /** @type {Rule[]} */
1802
1804
  let _blockRules = _EMPTY_LIST;
@@ -1804,7 +1806,7 @@ let _blockStart = 0;
1804
1806
  let _blockEnd = 0;
1805
1807
  /** @type {Declaration[]} */
1806
1808
  let _bcDecls = /** @type {Declaration[]} */ (
1807
- /** @type {unknown} */ (_EMPTY_LIST)
1809
+ /** @type {Declaration[] | Rule[]} */ (_EMPTY_LIST)
1808
1810
  );
1809
1811
  /** @type {Rule[]} */
1810
1812
  let _bcRules = _EMPTY_LIST;
@@ -1888,7 +1890,9 @@ let _declBodies = [];
1888
1890
  /** @type {Node[][]} */
1889
1891
  let _ruleBodies = [];
1890
1892
 
1891
- let _locConverter = /** @type {LocConverter} */ (/** @type {unknown} */ (null));
1893
+ let _locConverter = /** @type {LocConverter} */ (
1894
+ /** @type {LocConverter | null} */ (null)
1895
+ );
1892
1896
  // The caller's input before §3.3 replaced its NULs, the same length, which
1893
1897
  // `A.source` slices so a node reads exactly as written.
1894
1898
  let _originalInput = "";
@@ -1896,9 +1900,10 @@ let _originalInput = "";
1896
1900
  // Node refs are integers here but typed `Node` across the parser; these are
1897
1901
  // identity casts that just satisfy the type system at the boundary.
1898
1902
  /** @type {(n: Node) => number} */
1899
- const _nodeIndex = (n) => /** @type {number} */ (/** @type {unknown} */ (n));
1903
+ const _nodeIndex = (n) =>
1904
+ /** @type {number} */ (/** @type {number | Node} */ (n));
1900
1905
  /** @type {(i: number) => Node} */
1901
- const _nodeRef = (i) => /** @type {Node} */ (/** @type {unknown} */ (i));
1906
+ const _nodeRef = (i) => /** @type {Node} */ (/** @type {Node | number} */ (i));
1902
1907
 
1903
1908
  /** @param {number} need minimum capacity */
1904
1909
  const _grow = (need) => {
@@ -2105,6 +2110,16 @@ const _setValue = (r, list) => {
2105
2110
  }
2106
2111
  _listPool.push(list);
2107
2112
  };
2113
+ /**
2114
+ * Empties the body arrays between top-level nodes by popping: `length = 0`
2115
+ * drops the backing store, so the next rule's first push reallocates it.
2116
+ */
2117
+ const _dropBodies = () => {
2118
+ for (let k = _declBodies.length; k > 0; k--) {
2119
+ _declBodies.pop();
2120
+ _ruleBodies.pop();
2121
+ }
2122
+ };
2108
2123
  /** @type {(r: Node, decls: Node[], childRules: Node[]) => void} */
2109
2124
  const _setBody = (r, decls, childRules) => {
2110
2125
  _bodyIdx[_nodeIndex(r)] = _declBodies.length + 1;
@@ -2583,7 +2598,7 @@ const normalizeIntoTokenStream = (input, pos, onComment) => {
2583
2598
  // Shared frozen empty list for a block body with no declarations / child rules,
2584
2599
  // so equal-empty reads return one reference (mirrors `_EMPTY_LIST`).
2585
2600
  const _READER_EMPTY = /** @type {Rule[]} */ (
2586
- /** @type {unknown} */ (Object.freeze([]))
2601
+ /** @type {Rule[] | ReadonlyArray<never>} */ (Object.freeze([]))
2587
2602
  );
2588
2603
 
2589
2604
  /**
@@ -2651,7 +2666,7 @@ const _readRefList = (store, list) => {
2651
2666
  // that reader's `_store` snapshot by `_i`. Created once rather than per store,
2652
2667
  // so all readers share one hidden map and stay monomorphic across parses.
2653
2668
  const NODE_READER_PROTO = /** @type {NodeReader} */ ({
2654
- _store: /** @type {NodeStore} */ (/** @type {unknown} */ (null)),
2669
+ _store: /** @type {NodeStore} */ (/** @type {NodeStore | null} */ (null)),
2655
2670
  _i: 0,
2656
2671
  get type() {
2657
2672
  return this._store.types[this._i];
@@ -2880,7 +2895,9 @@ const _finishStore = () => {
2880
2895
  _ruleBodies = [];
2881
2896
  _listPool.length = 0;
2882
2897
  _S._input = "";
2883
- _locConverter = /** @type {LocConverter} */ (/** @type {unknown} */ (null));
2898
+ _locConverter = /** @type {LocConverter} */ (
2899
+ /** @type {LocConverter | null} */ (null)
2900
+ );
2884
2901
  return store;
2885
2902
  };
2886
2903
 
@@ -3271,8 +3288,8 @@ const consumeAQualifiedRule = (ts, stopToken, nested = false) => {
3271
3288
  // two non-whitespace tokens the `--foo: {` disambiguation below needs.
3272
3289
  const skip = _skipSelectorPrelude;
3273
3290
  // Non-skip: first two non-whitespace prelude nodes (computed at the `{`).
3274
- let first = /** @type {Node} */ (/** @type {unknown} */ (0));
3275
- let second = /** @type {Node} */ (/** @type {unknown} */ (0));
3291
+ let first = /** @type {Node} */ (/** @type {Node | number} */ (0));
3292
+ let second = /** @type {Node} */ (/** @type {Node | number} */ (0));
3276
3293
  // Skip mode: the same two tokens tracked by token type + start, so a dropped
3277
3294
  // leaf selector token needs no materialized node just for the disambiguation.
3278
3295
  // `0` (no token type) = unset.
@@ -4445,7 +4462,7 @@ const TOP_LEVEL_CONSUMERS = {
4445
4462
  * @property {boolean=} convertLengthUnits rewrite a length into a shorter unit it is exactly equal in (`16px` -> `1pc`); off by default because it earns nothing once the asset is compressed, and only read while printing. A time is always rewritten
4446
4463
  * @property {boolean=} convertApproximateColors write a polar or Lab color as the nearest hex even where that hex only approximates it: a channel too near a `.5` boundary for two engines to round it alike, and a color outside the sRGB gamut, which hex can only clip. Off by default, and only read while printing; a space engines read through transfers of their own (`a98-rgb`, `prophoto-rgb`) is left alone either way
4447
4464
  * @property {boolean=} mergeDistantRules give a rule the selectors of a later one printing the same block, and an at-rule the block of a later one stating the same condition, past the rules between them; off by default, since it reorders the cascade and is sound only where nothing between declares what the block being moved does for an element it can match — a condition between counting for what its own rules declare
4448
- * @property {boolean=} dropOverriddenDeclarations drop a declaration a later one in the same block overrides even where nothing states that the target can read the later value; off by default, and only read while printing. With a `browserslist` selection the drop is already made wherever every browser it names is known to read the later value, so this widens the case where no target is selected — a selection naming a browser the tables do not cover still answers for the whole of it
4465
+ * @property {boolean=} dropOverriddenDeclarations drop a declaration a later one in the same block overrides even where no target is selected, wherever Chromium, Gecko and WebKit each read the later value; off by default, and only read while printing. With a `browserslist` selection the drop is already made wherever every browser it names is known to read the later value, so this widens the case where no target is selected — a selection naming a browser the tables do not cover still answers for the whole of it
4449
4466
  * @property {boolean=} rewriteCustomProperties shorten a custom property's value the way any other value is shortened (`--x:#ffffff` -> `#fff`); off by default because `getPropertyValue()` hands that text back, and only read while printing. What it may rewrite is what any other value's tokens may be, a color in a substitution's fallback included — that being the property's value rather than the function's own argument
4450
4467
  * @property {EmbeddedSourceRenderer=} renderEmbeddedSource renders source this stylesheet embeds: the payload of a `url()` `data:` URL whose media type names a language webpack knows (SVG, CSS, HTML, JSON, JavaScript). Absent, a data URL is emitted exactly as written
4451
4468
  * @property {{ [name: string]: string }=} pseudoClasses each pseudo-class to write as a class instead (`{ "focus-visible": "focus-visible" }`), so a script can apply it where the engine does not. Only read while printing
@@ -4475,8 +4492,38 @@ const TOP_LEVEL_CONSUMERS = {
4475
4492
  // recursive call sites monomorphic and allocates no closure per parse.
4476
4493
  /** @typedef {import("../util/SourceProcessor").CompiledVisitorBucket<CssPath>} CompiledVisitorBucket */
4477
4494
  /** @type {CompiledVisitorMap} */
4478
- let _visitors = /** @type {CompiledVisitorMap} */ (/** @type {unknown} */ ([]));
4495
+ let _visitors = /** @type {CompiledVisitorMap} */ (
4496
+ /** @type {CompiledVisitorMap | never[]} */ ([])
4497
+ );
4479
4498
  let _recurseBlocks = true;
4499
+ // WHY: a block of declarations alone prints the same wherever its source text
4500
+ // and the few rules read around it are the same, and half the blocks a sheet
4501
+ // holds are written again; each body is composed once and handed back after.
4502
+ /** @type {Map<string, string> | null} */
4503
+ let _blockMemo = null;
4504
+ /** @type {Node | null} the rule `_blockMemoBody` was found for */
4505
+ let _blockMemoNode = null;
4506
+ /** @type {string | undefined} the body found for it, undefined to compose one */
4507
+ let _blockMemoBody;
4508
+ /** @type {string} the key a miss stores its body under, "" for none */
4509
+ let _blockMemoKey = "";
4510
+ // The tables a block is looked up in, one per what encloses it: the top level's
4511
+ // is `_blockMemo`. `_blockMemoTable` is the last one keyed, and
4512
+ // `_blockMemoKeyTable` the one `_blockMemoKey` belongs to.
4513
+ /** @type {Map<string, Map<string, string>>} */
4514
+ const _blockMemoScopes = new Map();
4515
+ /** @type {Map<string, string>} */
4516
+ let _blockMemoTable = new Map();
4517
+ /** @type {Map<string, string>} */
4518
+ let _blockMemoKeyTable = _blockMemoTable;
4519
+ // The same for one declaration, where its block is not one written before.
4520
+ /** @type {Map<string, string> | null} */
4521
+ let _declarationMemo = null;
4522
+ /** @type {string} the key the declaration being walked stores its text under */
4523
+ let _declarationMemoKey = "";
4524
+ // The declaration memo's keys whose value is one component.
4525
+ /** @type {Set<string> | null} */
4526
+ let _singleValueKeys = null;
4480
4527
  // Printing needs a faithful serialization, so it keeps dropped input as `NodeType.Raw`
4481
4528
  // nodes; a walk-only parse leaves this false and allocates none.
4482
4529
  let _printing = false;
@@ -4518,7 +4565,7 @@ const _grammarOnComment = (_src, start, end) => {
4518
4565
  _S._currentNode = node;
4519
4566
  _currentParent = null;
4520
4567
  _currentIndex = 0;
4521
- const bucket = /** @type {CompiledVisitorBucket} */ (_commentBucket);
4568
+ const bucket = _commentBucket;
4522
4569
  const e = bucket.enter;
4523
4570
  for (let i = 0; i < e.length; i++) e[i](A);
4524
4571
  const x = bucket.exit;
@@ -4572,6 +4619,76 @@ const _mayBeMathFunction = (start) => {
4572
4619
  return (_MATH_NAME_FIRST_LETTERS & (1 << (first - 0x61))) !== 0;
4573
4620
  };
4574
4621
 
4622
+ // The leaves the CSS printer writes as their source text, whitespace as `_SEP`
4623
+ // and an identifier or hash outside a value (`_printVerbatimLeaf`).
4624
+ const _VERBATIM_LEAVES = new Uint8Array(32);
4625
+ _VERBATIM_LEAVES[NodeType.Whitespace] = 1;
4626
+ _VERBATIM_LEAVES[NodeType.Delim] = 1;
4627
+ _VERBATIM_LEAVES[NodeType.Comma] = 1;
4628
+ _VERBATIM_LEAVES[NodeType.Colon] = 1;
4629
+ _VERBATIM_LEAVES[NodeType.Ident] = 1;
4630
+ _VERBATIM_LEAVES[NodeType.Hash] = 1;
4631
+ /** @type {EXPECTED_FUNCTION | null} */
4632
+ let _verbatimLeafPrinter = null;
4633
+
4634
+ /**
4635
+ * Name the printer that writes `_VERBATIM_LEAVES` as their source text, so a
4636
+ * walk printing through it stores their text without calling it.
4637
+ * @param {EXPECTED_FUNCTION} printer the CSS printer
4638
+ * @returns {void}
4639
+ */
4640
+ const setVerbatimLeafPrinter = (printer) => {
4641
+ _verbatimLeafPrinter = printer;
4642
+ };
4643
+
4644
+ /**
4645
+ * Store what the CSS printer writes for a `_VERBATIM_LEAVES` node, without a
4646
+ * call through it: they are half the nodes a stylesheet holds. Most print as
4647
+ * their source, which the writer reads only once something asks for it.
4648
+ * @param {number} ty the node's type
4649
+ * @param {Node} node the node
4650
+ * @param {Node | null} parent its parent
4651
+ * @param {PrintContext} writer the print context
4652
+ * @returns {boolean} false to call the printer instead
4653
+ */
4654
+ const _printVerbatimLeaf = (ty, node, parent, writer) => {
4655
+ if (ty === NodeType.Whitespace) {
4656
+ writer.printedAs(node, _SEP);
4657
+ return true;
4658
+ }
4659
+ // An identifier is a keyword and a hash a color the printer may rewrite only
4660
+ // in a value, and their escapes are respelled anywhere.
4661
+ const ident = ty === NodeType.Ident || ty === NodeType.Hash;
4662
+ if (ident && parent !== null && _within[NodeType.Declaration] !== 0) {
4663
+ return false;
4664
+ }
4665
+ const i = _nodeIndex(node);
4666
+ const start = _starts[i];
4667
+ const end = _ends[i];
4668
+ // The end of the input is where `_printedSource` resolves a trailing escape.
4669
+ if (end === _S._input.length) {
4670
+ const text = _printedSource(node);
4671
+ if (ident) {
4672
+ if (text.includes("\\")) return false;
4673
+ writer.printedAs(node, text);
4674
+ } else {
4675
+ writer.printedAs(node, text === "\\" ? "\\\n" : text);
4676
+ }
4677
+ return true;
4678
+ }
4679
+ if (ident) {
4680
+ if (_rangeHasEscape(start, end)) return false;
4681
+ } else if (
4682
+ end - start === 1 &&
4683
+ _S._input.charCodeAt(start) === CC_REVERSE_SOLIDUS
4684
+ ) {
4685
+ writer.printedAs(node, "\\\n");
4686
+ return true;
4687
+ }
4688
+ writer.printedSource(node, _printedSource);
4689
+ return true;
4690
+ };
4691
+
4575
4692
  /**
4576
4693
  * Walk a component-value subtree; children are already materialized. Fetches
4577
4694
  * the node's visitor bucket once (reused for enter + exit) and uses index
@@ -4665,7 +4782,15 @@ const _walkValue = (node, parent, index, writer) => {
4665
4782
  _within[ty]--;
4666
4783
  _S._inColorOperand = previousColorOperand;
4667
4784
  }
4668
- _exitNode(node, parent, index, b, writer);
4785
+ if (
4786
+ b !== undefined ||
4787
+ writer === undefined ||
4788
+ _VERBATIM_LEAVES[ty] === 0 ||
4789
+ writer._printer !== _verbatimLeafPrinter ||
4790
+ !_printVerbatimLeaf(ty, node, parent, writer)
4791
+ ) {
4792
+ _exitNode(node, parent, index, b, writer);
4793
+ }
4669
4794
  if (enteredMath) _S._mathFunctionDepth--;
4670
4795
  if (enteredStepped) _S._steppedFunctionDepth--;
4671
4796
  if (enteredSupports) {
@@ -4871,6 +4996,74 @@ const _enterAtRulePrelude = (i0) => {
4871
4996
  }
4872
4997
  };
4873
4998
 
4999
+ /**
5000
+ * The key a declaration prints under: its source, with the at-rules that read
5001
+ * one as a descriptor.
5002
+ * @param {Node} node the declaration
5003
+ * @param {number} i0 its node index
5004
+ * @returns {string} the key, "" where it is not one to reuse
5005
+ */
5006
+ const _declarationMemoKeyOf = (node, i0) => {
5007
+ const start = _starts[i0];
5008
+ const end = _ends[i0];
5009
+ const input = _S._input;
5010
+ if (end === input.length) return "";
5011
+ const text = input.slice(start, end);
5012
+ // A comment kept in the value is written where it stands, once.
5013
+ if (text.includes("/*")) return "";
5014
+ // Bare where no descriptor at-rule is around, which no other key can equal:
5015
+ // those start with a letter naming one, and no source holds a NUL.
5016
+ return !_S._inPropertyRule && !_S._inFunctionRule && !_S._inFeatureValuesRule
5017
+ ? text
5018
+ : `${_S._inPropertyRule ? "p" : ""}${_S._inFunctionRule ? "f" : ""}${
5019
+ _S._inFeatureValuesRule ? "v" : ""
5020
+ }\0${text}`;
5021
+ };
5022
+
5023
+ /**
5024
+ * The key a qualified rule's block of declarations is composed under: its
5025
+ * source, with what the walk around it decides of how it prints.
5026
+ * @param {number} i0 the rule's node index
5027
+ * @param {Node | null} parent the node it sits in
5028
+ * @returns {string} the key, "" where the block is not one to reuse
5029
+ */
5030
+ const _blockMemoKeyOf = (i0, parent) => {
5031
+ const start = _aux1[i0];
5032
+ const end = _ends[i0];
5033
+ const input = _S._input;
5034
+ if (start === -1 || end === input.length) return "";
5035
+ // A comment kept in the block is written where it stands, once.
5036
+ const block = input.slice(start, end);
5037
+ if (block.includes("/*")) return "";
5038
+ let around = "";
5039
+ if (parent !== null) {
5040
+ const p = _nodeIndex(parent);
5041
+ if (_types[p] !== NodeType.AtRule) return "";
5042
+ around = input.slice(_starts[p], _aux0[p]);
5043
+ }
5044
+ // What is around it picks the table, so the key is the block alone and no
5045
+ // copy of it is built per lookup.
5046
+ if (
5047
+ around.length === 0 &&
5048
+ !_S._inPropertyRule &&
5049
+ !_S._inFunctionRule &&
5050
+ !_S._inFeatureValuesRule
5051
+ ) {
5052
+ _blockMemoTable = /** @type {Map<string, string>} */ (_blockMemo);
5053
+ return block;
5054
+ }
5055
+ const scope = `${_S._inPropertyRule ? "p" : ""}${_S._inFunctionRule ? "f" : ""}${
5056
+ _S._inFeatureValuesRule ? "v" : ""
5057
+ }${around}`;
5058
+ let table = _blockMemoScopes.get(scope);
5059
+ if (table === undefined) {
5060
+ table = new Map();
5061
+ _blockMemoScopes.set(scope, table);
5062
+ }
5063
+ _blockMemoTable = table;
5064
+ return block;
5065
+ };
5066
+
4874
5067
  /**
4875
5068
  * Walk a structural subtree; an at-rule / qualified-rule's block was parsed
4876
5069
  * eagerly (§5.4.4), so its `value` holds the nested rules / declarations. When
@@ -4920,9 +5113,24 @@ const _walkRule = (node, parent, index, writer) => {
4920
5113
  const bi = _bodyIdx[i0];
4921
5114
  if (bi !== 0) {
4922
5115
  const decls = _declBodies[bi - 1];
4923
- for (let i = 0; i < decls.length; i++) {
4924
- _walkRule(decls[i], node, i, writer);
5116
+ const key =
5117
+ _blockMemo !== null &&
5118
+ ty === NodeType.QualifiedRule &&
5119
+ decls.length !== 0 &&
5120
+ _ruleBodies[bi - 1].length === 0
5121
+ ? _blockMemoKeyOf(i0, parent)
5122
+ : "";
5123
+ const table = _blockMemoTable;
5124
+ const memo = key === "" ? undefined : table.get(key);
5125
+ if (memo === undefined) {
5126
+ for (let i = 0; i < decls.length; i++) {
5127
+ _walkRule(decls[i], node, i, writer);
5128
+ }
4925
5129
  }
5130
+ _blockMemoNode = node;
5131
+ _blockMemoBody = memo;
5132
+ _blockMemoKey = memo === undefined ? key : "";
5133
+ _blockMemoKeyTable = table;
4926
5134
  const ch = _ruleBodies[bi - 1];
4927
5135
  for (let i = 0; i < ch.length; i++) _walkRule(ch[i], node, i, writer);
4928
5136
  }
@@ -4931,6 +5139,28 @@ const _walkRule = (node, parent, index, writer) => {
4931
5139
  _S._inFunctionRule = prevFunction;
4932
5140
  _S._inFeatureValuesRule = prevFeatureValues;
4933
5141
  } else if (ty === NodeType.Declaration) {
5142
+ const key =
5143
+ _declarationMemo !== null && b === undefined
5144
+ ? _declarationMemoKeyOf(node, i0)
5145
+ : "";
5146
+ if (key !== "") {
5147
+ const memo = /** @type {Map<string, string>} */ (_declarationMemo).get(
5148
+ key
5149
+ );
5150
+ if (memo !== undefined) {
5151
+ _within[ty]--;
5152
+ // Its value's tokens print no text, so a box merge reads what it
5153
+ // needs of them off the flags.
5154
+ if (_mayBeMergeableName(node)) {
5155
+ _flags[i0] |= /** @type {Set<string>} */ (_singleValueKeys).has(key)
5156
+ ? _FLAG_MEMO_PRINTED | _FLAG_SINGLE_VALUE
5157
+ : _FLAG_MEMO_PRINTED;
5158
+ }
5159
+ /** @type {PrintContext} */ (writer).printedAs(node, memo);
5160
+ return;
5161
+ }
5162
+ _declarationMemoKey = key;
5163
+ }
4934
5164
  const vs = _listStarts[i0];
4935
5165
  const ve = vs + _listLens[i0];
4936
5166
  // A declaration's value is a value context (its hashes are colors, not
@@ -4978,6 +5208,47 @@ const _walkRule = (node, parent, index, writer) => {
4978
5208
  _within[ty]--;
4979
5209
  }
4980
5210
  _exitNode(node, parent, index, b, writer);
5211
+ if (_declarationMemoKey !== "") {
5212
+ const context = /** @type {PrintContext} */ (writer);
5213
+ const printed = context.get(node);
5214
+ if (!_mayBeMergeableName(node)) {
5215
+ /** @type {Map<string, string>} */ (_declarationMemo).set(
5216
+ _declarationMemoKey,
5217
+ printed
5218
+ );
5219
+ } else if (printed.includes(":")) {
5220
+ /** @type {Map<string, string>} */ (_declarationMemo).set(
5221
+ _declarationMemoKey,
5222
+ printed
5223
+ );
5224
+ if (_valueComponents(A, node, context).length === 1) {
5225
+ /** @type {Set<string>} */ (_singleValueKeys).add(_declarationMemoKey);
5226
+ }
5227
+ }
5228
+ _declarationMemoKey = "";
5229
+ }
5230
+ };
5231
+
5232
+ // What a declaration printed from the memo carries in its flags, past the
5233
+ // public `Flag` bits: that its value's tokens printed nothing, and whether that
5234
+ // value is one component, which is all a box merge reads of them.
5235
+ const _FLAG_MEMO_PRINTED = 8;
5236
+ const _FLAG_SINGLE_VALUE = 16;
5237
+
5238
+ /**
5239
+ * Whether a declaration's value is one component, as `_valueComponents` splits
5240
+ * it, answered off its flags where it printed from the memo.
5241
+ * @param {CssPath} path the accessor
5242
+ * @param {Node} node the declaration
5243
+ * @param {PrintContext} writer the print context (children's printed text)
5244
+ * @returns {boolean} true when it is one component
5245
+ */
5246
+ const _isSingleValue = (path, node, writer) => {
5247
+ const flags = _flags[_nodeIndex(node)];
5248
+ if ((flags & _FLAG_MEMO_PRINTED) !== 0) {
5249
+ return (flags & _FLAG_SINGLE_VALUE) !== 0;
5250
+ }
5251
+ return _valueComponents(path, node, writer).length === 1;
4981
5252
  };
4982
5253
 
4983
5254
  // WHY: nested-block streaming.
@@ -5078,8 +5349,8 @@ const _frameChainKey = Array.from({ length: _STREAM_MAX_DEPTH }, () => null);
5078
5349
  * travels outward — only `scope` is replaced, by one standing for more — so the
5079
5350
  * collected and the streamed path split a rule the same way, which is what makes
5080
5351
  * the two agree on what a duplicate is.
5081
- * @typedef {{ scope: RuleScope, key: string, at: number, len: number }} RuleSpan
5082
- * @typedef {{ prefix: string, rules: Map<string, { taken: TakenPiece, span: RuleSpan }>, selectors: Map<string, { taken: TakenPiece, span: RuleSpan }[]>, inner: Map<string, RuleScope> }} RuleScope
5352
+ * @typedef {{ scope: RuleScope, key: string, at: number, len: number, taken: TakenPiece | null }} RuleSpan
5353
+ * @typedef {{ prefix: string, rules: Map<string, RuleSpan>, selectors: Map<string, RuleSpan | RuleSpan[]>, inner: Map<string, RuleScope>, noShadow: boolean }} RuleScope
5083
5354
  * @typedef {{ bodyAt: number, prelude: string, keyPrelude: string, qualified: boolean, spans: RuleSpan[] }} BlockSpans
5084
5355
  */
5085
5356
  // A span no later copy is read against: it is there to be cut, not to key a
@@ -5090,14 +5361,108 @@ const UNKEYED = "";
5090
5361
  /** @type {BlockSpans[]} */
5091
5362
  const _blockSpans = [];
5092
5363
  const _NO_RULE_SPANS = /** @type {RuleSpan[]} */ (
5093
- /** @type {unknown} */ (Object.freeze([]))
5364
+ /** @type {RuleSpan[] | ReadonlyArray<never>} */ (Object.freeze([]))
5094
5365
  );
5095
5366
  const _NO_BLOCK_SPANS = /** @type {BlockSpans[]} */ (
5096
- /** @type {unknown} */ (Object.freeze([]))
5367
+ /** @type {BlockSpans[] | ReadonlyArray<never>} */ (Object.freeze([]))
5097
5368
  );
5098
5369
  /**
5099
- * @typedef {{ piece: number, text: string, spans: RuleSpan[], empties: number }} TakenPiece
5370
+ * A top-level node's text as written out, which later rules cut dead rules from.
5371
+ * A cut is recorded against the text the piece last held, so a run of cuts builds
5372
+ * the text once, when something reads it, rather than once per cut.
5100
5373
  */
5374
+ class TakenPiece {
5375
+ /**
5376
+ * @param {number} piece the writer's index for it
5377
+ * @param {string} text the text it went out as
5378
+ * @param {number} empties its length with every rule it holds cut, less one, or -1
5379
+ */
5380
+ constructor(piece, text, empties) {
5381
+ /** @type {number} */
5382
+ this.piece = piece;
5383
+ /** @type {RuleSpan[]} */
5384
+ this.spans = [];
5385
+ /** @type {number} */
5386
+ this.empties = empties;
5387
+ /** @type {string} */
5388
+ this._text = text;
5389
+ /** @type {number[] | null} the ranges of `_text` cut since, as sorted start, end pairs */
5390
+ this._cuts = null;
5391
+ /** @type {number} */
5392
+ this.length = text.length;
5393
+ }
5394
+
5395
+ /**
5396
+ * @returns {string} what the piece reads now
5397
+ */
5398
+ get text() {
5399
+ const cuts = this._cuts;
5400
+ if (cuts !== null) {
5401
+ /** @type {string[]} */
5402
+ const parts = [];
5403
+ let from = 0;
5404
+ for (let i = 0; i < cuts.length; i += 2) {
5405
+ parts.push(this._text.slice(from, cuts[i]));
5406
+ from = cuts[i + 1];
5407
+ }
5408
+ parts.push(this._text.slice(from));
5409
+ this._text = parts.join("");
5410
+ this._cuts = null;
5411
+ }
5412
+ return this._text;
5413
+ }
5414
+
5415
+ /**
5416
+ * @param {string} value what the piece reads from now on
5417
+ */
5418
+ set text(value) {
5419
+ this._text = value;
5420
+ this._cuts = null;
5421
+ this.length = value.length;
5422
+ }
5423
+
5424
+ /**
5425
+ * @param {number} at an offset in what the piece reads now
5426
+ * @returns {number} the code unit there, read without building the text
5427
+ */
5428
+ charCodeAt(at) {
5429
+ return this._text.charCodeAt(this._uncut(at));
5430
+ }
5431
+
5432
+ /**
5433
+ * Take `len` code units out at `from`, offsets in what the piece reads now.
5434
+ * @param {number} from where the cut starts
5435
+ * @param {number} len how much it takes
5436
+ * @returns {void}
5437
+ */
5438
+ cut(from, len) {
5439
+ const start = this._uncut(from);
5440
+ const end = this._uncut(from + len - 1) + 1;
5441
+ const cuts = this._cuts === null ? [] : this._cuts;
5442
+ // The ranges cut before inside this one are taken with it.
5443
+ let at = 0;
5444
+ while (at < cuts.length && cuts[at] < start) at += 2;
5445
+ let past = at;
5446
+ while (past < cuts.length && cuts[past + 1] <= end) past += 2;
5447
+ cuts.splice(at, past - at, start, end);
5448
+ this._cuts = cuts;
5449
+ this.length -= len;
5450
+ }
5451
+
5452
+ /**
5453
+ * @param {number} at an offset in what the piece reads now
5454
+ * @returns {number} the same offset in `_text`
5455
+ */
5456
+ _uncut(at) {
5457
+ const cuts = this._cuts;
5458
+ if (cuts === null) return at;
5459
+ let offset = at;
5460
+ for (let i = 0; i < cuts.length && cuts[i] <= offset; i += 2) {
5461
+ offset += cuts[i + 1] - cuts[i];
5462
+ }
5463
+ return offset;
5464
+ }
5465
+ }
5101
5466
  // Every rule taken so far, sheet-wide, by what encloses it and then by the text
5102
5467
  // it printed to — neither of which changes once written (see `RuleSpan`).
5103
5468
  /** @type {Map<string, RuleScope> | null} */
@@ -5123,7 +5488,8 @@ const _ruleScopeFor = (prefix) => {
5123
5488
  prefix,
5124
5489
  rules: new Map(),
5125
5490
  selectors: new Map(),
5126
- inner: new Map()
5491
+ inner: new Map(),
5492
+ noShadow: _NO_SHADOW_SCOPE_RE.test(prefix)
5127
5493
  };
5128
5494
  scopes.set(prefix, scope);
5129
5495
  }
@@ -5406,10 +5772,7 @@ const _streamEmitChild = (d, child) => {
5406
5772
  const opener = _namedLayerOpener(text);
5407
5773
  if (opener === null) {
5408
5774
  if (_frameSeenLayers[d] !== null) {
5409
- _forgetLayersWritten(
5410
- /** @type {Map<string, SeenLayer>} */ (_frameSeenLayers[d]),
5411
- text
5412
- );
5775
+ _forgetLayersWritten(_frameSeenLayers[d], text);
5413
5776
  }
5414
5777
  } else {
5415
5778
  let layers = _frameSeenLayers[d];
@@ -5855,7 +6218,7 @@ const _streamConsumeBlock = (ts, rule) => {
5855
6218
  /** @type {HeldTopLevel | null} */
5856
6219
  let _heldTopLevel = null;
5857
6220
 
5858
- /** @typedef {{ taken: TakenPiece, span: RuleSpan, entry: RuleEntry, prelude: number, listable: boolean, grown: boolean }} PriorTopLevel the top-level rule last written out, the piece it stands in, how far into it its block starts, whether its selectors may join a list, and whether a later rule's did */
6221
+ /** @typedef {{ taken: TakenPiece, span: RuleSpan, entry: RuleEntry, prelude: number, grown: boolean }} PriorTopLevel the top-level rule last written out, the piece it stands in, how far into it its block starts, and whether a later rule's selectors joined its list */
5859
6222
 
5860
6223
  // The rule written out before the held one, kept so a join only its settled
5861
6224
  // block qualifies for can still be made. Null when that was not one whole rule.
@@ -5943,18 +6306,23 @@ const _drainTopLevelSpans = (node) => {
5943
6306
  const _topLevelSpans = (own, text) => {
5944
6307
  if (own === undefined) return null;
5945
6308
  if (own === _WHOLE_TEXT_SPANS) {
5946
- return [{ scope: _rootScope(), key: text, at: 0, len: text.length }];
6309
+ return [
6310
+ { scope: _rootScope(), key: text, at: 0, len: text.length, taken: null }
6311
+ ];
5947
6312
  }
5948
6313
  /** @type {RuleSpan[]} */
5949
6314
  const out = own.qualified
5950
- ? [{ scope: _rootScope(), key: text, at: 0, len: text.length }]
6315
+ ? [{ scope: _rootScope(), key: text, at: 0, len: text.length, taken: null }]
5951
6316
  : [];
5952
- for (const span of own.spans) {
6317
+ const spans = own.spans;
6318
+ for (let i = 0; i < spans.length; i++) {
6319
+ const span = spans[i];
5953
6320
  out.push({
5954
6321
  scope: _enclosingRuleScope(span.scope, own.keyPrelude),
5955
6322
  key: span.key,
5956
6323
  at: own.bodyAt + span.at,
5957
- len: span.len
6324
+ len: span.len,
6325
+ taken: null
5958
6326
  });
5959
6327
  }
5960
6328
  return out;
@@ -5985,8 +6353,7 @@ const _takeDeduped = (writer, piece, text, spans, own, chain) => {
5985
6353
  DROPPABLE_WHEN_EMPTY_AT_RULES.has(_atRuleName(own.prelude))))
5986
6354
  ? own.bodyAt
5987
6355
  : -1;
5988
- /** @type {TakenPiece} */
5989
- const taken = { piece, text, spans: [], empties };
6356
+ const taken = new TakenPiece(piece, text, empties);
5990
6357
  _noteSpans(writer, taken, spans, chain);
5991
6358
  return taken;
5992
6359
  };
@@ -6005,18 +6372,23 @@ const _noteSpans = (writer, taken, spans, chain) => {
6005
6372
  // `taken.spans` holds, and one not yet pushed would keep its old offset.
6006
6373
  for (const span of spans) {
6007
6374
  if (chain.length !== 0) span.scope = _enclosingRuleScope(span.scope, chain);
6375
+ span.taken = taken;
6008
6376
  taken.spans.push(span);
6009
6377
  }
6010
6378
  for (const span of spans) {
6011
6379
  if (span.key === UNKEYED) continue;
6012
6380
  const rules = span.scope.rules;
6013
6381
  const before = rules.get(span.key);
6014
- if (before !== undefined) _cutSpan(writer, before.taken, before.span);
6015
- rules.set(span.key, { taken, span });
6382
+ if (before !== undefined) {
6383
+ _cutSpan(writer, /** @type {TakenPiece} */ (before.taken), before);
6384
+ }
6385
+ rules.set(span.key, span);
6016
6386
  }
6017
6387
  // After every copy is taken back, so an earlier rule a cut here left flat is
6018
6388
  // read as flat. A distant merge moves a block up past others, out of this order.
6019
6389
  if (!_S._transforms.removeDeadRules || _mergingDistantRules) return;
6390
+ // TODO add an option to make this opt-in: over the CSS corpus it costs ~10% of
6391
+ // minify CPU for 248 of 2,456,527 gzip bytes.
6020
6392
  for (const span of spans) {
6021
6393
  if (span.key !== UNKEYED && span.len !== 0) {
6022
6394
  _shadowEarlierRule(writer, taken, span);
@@ -6032,6 +6404,29 @@ const _ROLLBACK_RE = /^revert(?:-[a-z]+)?$/i;
6032
6404
  // blocks are gathered into one without their rules noted, so none is there.
6033
6405
  const _NO_SHADOW_SCOPE_RE = /@(?:-[a-z]+-)?keyframes|@layer/i;
6034
6406
 
6407
+ // What each prior `_shadowEarlierRule` compared against declares, with the text
6408
+ // that was read from, so a prior a later rule writes nothing of is passed over.
6409
+ /** @type {Map<RuleSpan, { text: string, names: Set<string> }>} */
6410
+ const _shadowDeclared = new Map();
6411
+
6412
+ /**
6413
+ * The names a printed flat rule's declarations write, as `_cascadeName` reads
6414
+ * them.
6415
+ * @param {string} rule the rule's printed text
6416
+ * @param {number} open where its `{` sits
6417
+ * @returns {Set<string>} the names
6418
+ */
6419
+ const _declaredNames = (rule, open) => {
6420
+ /** @type {Set<string>} */
6421
+ const names = new Set();
6422
+ for (const declaration of _splitPrintedDeclarations(
6423
+ rule.slice(open + 1, -1)
6424
+ )) {
6425
+ if (declaration.indexOf(":") > 0) names.add(_cascadeName(declaration));
6426
+ }
6427
+ return names;
6428
+ };
6429
+
6035
6430
  /**
6036
6431
  * The declarations of a printed block body, split on the `;` between them; a
6037
6432
  * nested block is a piece of its own.
@@ -6114,24 +6509,30 @@ const _readWherever = (values, earlier) => {
6114
6509
  * @returns {void}
6115
6510
  */
6116
6511
  const _shadowEarlierRule = (writer, taken, span) => {
6117
- const text = taken.text.slice(span.at, span.at + span.len);
6118
- const open = text.indexOf("{");
6512
+ const whole = taken.text;
6513
+ const at = span.at;
6514
+ const brace = whole.indexOf("{", at);
6119
6515
  // Only a qualified rule: an at-rule is no one cascade. One a later cut
6120
6516
  // leaves flat is read as one, so every rule is noted, flat or not.
6121
6517
  if (
6122
- open <= 0 ||
6123
- text.charCodeAt(0) === CC_AT_SIGN ||
6124
- _NO_SHADOW_SCOPE_RE.test(span.scope.prefix)
6518
+ brace <= at ||
6519
+ brace >= at + span.len ||
6520
+ whole.charCodeAt(at) === CC_AT_SIGN ||
6521
+ span.scope.noShadow
6125
6522
  ) {
6126
6523
  return;
6127
6524
  }
6128
- const selector = text.slice(0, open);
6525
+ const open = brace - at;
6526
+ const selector = whole.slice(at, brace);
6129
6527
  const selectors = span.scope.selectors;
6130
- const priors = selectors.get(selector);
6131
- if (priors === undefined) {
6132
- selectors.set(selector, [{ taken, span }]);
6528
+ const noted = selectors.get(selector);
6529
+ // A lone prior is held bare: almost every selector is written once.
6530
+ if (noted === undefined) {
6531
+ selectors.set(selector, span);
6133
6532
  return;
6134
6533
  }
6534
+ const priors = Array.isArray(noted) ? noted : [noted];
6535
+ const text = whole.slice(at, at + span.len);
6135
6536
  // Per name, the later values and whether one every target reads is among
6136
6537
  // them, and whether one of those is `!important`.
6137
6538
  /** @type {Map<string, { values: Set<string>, read: boolean, important: boolean }>} */
@@ -6161,11 +6562,24 @@ const _shadowEarlierRule = (writer, taken, span) => {
6161
6562
  }
6162
6563
  }
6163
6564
  for (const prior of priors) {
6164
- if (prior.span.len === 0) continue;
6165
- const before = prior.taken.text.slice(
6166
- prior.span.at,
6167
- prior.span.at + prior.span.len
6168
- );
6565
+ if (prior.len === 0) continue;
6566
+ const priorTaken = /** @type {TakenPiece} */ (prior.taken);
6567
+ const before = priorTaken.text.slice(prior.at, prior.at + prior.len);
6568
+ // A prior writing none of the names this rule writes keeps all it has, so
6569
+ // it is not split again for every later rule under its selector.
6570
+ let declared = _shadowDeclared.get(prior);
6571
+ if (declared === undefined || declared.text !== before) {
6572
+ declared = { text: before, names: _declaredNames(before, open) };
6573
+ _shadowDeclared.set(prior, declared);
6574
+ }
6575
+ let shared = false;
6576
+ for (const name of later.keys()) {
6577
+ if (declared.names.has(name)) {
6578
+ shared = true;
6579
+ break;
6580
+ }
6581
+ }
6582
+ if (!shared) continue;
6169
6583
  /* istanbul ignore if -- @preserve: a join grows a list only before its rule
6170
6584
  is noted unless distant merges are on, which skip this; kept as the guard */
6171
6585
  if (
@@ -6207,18 +6621,17 @@ const _shadowEarlierRule = (writer, taken, span) => {
6207
6621
  }
6208
6622
  if (!dropped) continue;
6209
6623
  if (kept.length === 0) {
6210
- _cutSpan(writer, prior.taken, prior.span);
6624
+ _cutSpan(writer, priorTaken, prior);
6211
6625
  continue;
6212
6626
  }
6213
- _rewriteSpan(
6214
- writer,
6215
- prior.taken,
6216
- prior.span,
6217
- `${selector}{${kept.join(";")}}`
6218
- );
6219
- _rekeyShrunkSpan(writer, prior.taken, prior.span);
6627
+ _rewriteSpan(writer, priorTaken, prior, `${selector}{${kept.join(";")}}`);
6628
+ _rekeyShrunkSpan(writer, priorTaken, prior);
6629
+ }
6630
+ if (priors === noted) {
6631
+ priors.push(span);
6632
+ } else {
6633
+ selectors.set(selector, [/** @type {RuleSpan} */ (noted), span]);
6220
6634
  }
6221
- priors.push({ taken, span });
6222
6635
  };
6223
6636
 
6224
6637
  /**
@@ -6315,15 +6728,7 @@ const _joinGatheredSeam = (writer, taken, at, entry) => {
6315
6728
  ) {
6316
6729
  return;
6317
6730
  }
6318
- if (
6319
- !_lendSelectorsTo(
6320
- writer,
6321
- taken,
6322
- before,
6323
- prelude,
6324
- first.text.slice(0, first.prelude)
6325
- )
6326
- ) {
6731
+ if (!_lendSelectorsTo(writer, taken, before, prelude, _entryPrelude(first))) {
6327
6732
  return;
6328
6733
  }
6329
6734
  _cutSpan(writer, taken, after);
@@ -6335,18 +6740,19 @@ const _joinGatheredSeam = (writer, taken, at, entry) => {
6335
6740
  * it, so this runs until none is left.
6336
6741
  * @param {PrintContext} writer the print context
6337
6742
  * @param {TakenPiece} taken the piece the cut was made in
6743
+ * @param {RuleSpan[]} held the rules that held what was cut, the only ones it can have emptied
6338
6744
  * @returns {void}
6339
6745
  */
6340
- const _cutEmptiedSpans = (writer, taken) => {
6746
+ const _cutEmptiedSpans = (writer, taken, held) => {
6341
6747
  if (!_S._transforms.removeDeadRules) return;
6342
6748
  // Every span is a rule dropped empty wherever it stands: a qualified rule, or
6343
6749
  // one of the at-rules `DROPPABLE_WHEN_EMPTY_AT_RULES` names (`_collectRuleSpans`).
6344
- for (const other of taken.spans) {
6750
+ for (const other of held) {
6345
6751
  const end = other.at + other.len;
6346
6752
  if (
6347
6753
  other.len > 2 &&
6348
- taken.text.charCodeAt(end - 1) === CC_RIGHT_CURLY &&
6349
- taken.text.charCodeAt(end - 2) === CC_LEFT_CURLY
6754
+ taken.charCodeAt(end - 1) === CC_RIGHT_CURLY &&
6755
+ taken.charCodeAt(end - 2) === CC_LEFT_CURLY
6350
6756
  ) {
6351
6757
  _cutSpan(writer, taken, other);
6352
6758
  return;
@@ -6369,17 +6775,17 @@ const _cutSpan = (writer, taken, span) => {
6369
6775
  // The rule stood last in its block, so the `;` in front of it is one the
6370
6776
  // printer drops itself — it goes with the rule rather than being left behind.
6371
6777
  if (
6372
- taken.text.charCodeAt(from + len) === CC_RIGHT_CURLY &&
6373
- taken.text.charCodeAt(from - 1) === CC_SEMICOLON
6778
+ taken.charCodeAt(from + len) === CC_RIGHT_CURLY &&
6779
+ taken.charCodeAt(from - 1) === CC_SEMICOLON
6374
6780
  ) {
6375
6781
  from -= 1;
6376
6782
  len += 1;
6377
6783
  }
6378
- taken.text = `${taken.text.slice(0, from)}${taken.text.slice(from + len)}`;
6379
- if (taken.empties !== -1 && taken.text.length === taken.empties + 1) {
6784
+ taken.cut(from, len);
6785
+ if (taken.empties !== -1 && taken.length === taken.empties + 1) {
6380
6786
  taken.text = "";
6381
6787
  }
6382
- writer.rewriteRetractable(taken.piece, taken.text);
6788
+ writer.deferRetractable(taken.piece, taken);
6383
6789
  const end = from + len;
6384
6790
  /** @type {RuleSpan[] | null} */
6385
6791
  let shrunk = null;
@@ -6401,8 +6807,8 @@ const _cutSpan = (writer, taken, span) => {
6401
6807
  span.len = 0;
6402
6808
  if (shrunk !== null) {
6403
6809
  for (const other of shrunk) _rekeyShrunkSpan(writer, taken, other);
6810
+ _cutEmptiedSpans(writer, taken, shrunk);
6404
6811
  }
6405
- _cutEmptiedSpans(writer, taken);
6406
6812
  };
6407
6813
 
6408
6814
  /**
@@ -6419,15 +6825,14 @@ const _rekeyShrunkSpan = (writer, taken, span) => {
6419
6825
  const rules = span.scope.rules;
6420
6826
  const other = rules.get(key);
6421
6827
  _rekeySpan(taken, span);
6422
- if (other === undefined || other.span === span || other.span.len === 0) {
6423
- return;
6424
- }
6828
+ if (other === undefined || other === span || other.len === 0) return;
6829
+ const otherTaken = /** @type {TakenPiece} */ (other.taken);
6425
6830
  const earlier =
6426
- other.taken.piece < taken.piece ||
6427
- (other.taken.piece === taken.piece && other.span.at < span.at);
6831
+ otherTaken.piece < taken.piece ||
6832
+ (otherTaken.piece === taken.piece && other.at < span.at);
6428
6833
  if (earlier) {
6429
- rules.set(key, { taken, span });
6430
- _cutSpan(writer, other.taken, other.span);
6834
+ rules.set(key, span);
6835
+ _cutSpan(writer, otherTaken, other);
6431
6836
  } else {
6432
6837
  _cutSpan(writer, taken, span);
6433
6838
  }
@@ -6647,13 +7052,13 @@ const _priorOf = (taken, entry) => {
6647
7052
  return null;
6648
7053
  }
6649
7054
  // A rule whose selectors may not join a list is still a neighbor: the copy
6650
- // that only restates it goes whatever its block holds.
7055
+ // that only restates it goes whatever its block holds. Whether they may is
7056
+ // asked of `entry` once a later rule prints the same block.
6651
7057
  return {
6652
7058
  taken,
6653
7059
  span,
6654
7060
  entry,
6655
7061
  prelude: entry.prelude,
6656
- listable: _entryListable(entry),
6657
7062
  grown: false
6658
7063
  };
6659
7064
  };
@@ -6682,12 +7087,9 @@ const _settlePrior = (writer) => {
6682
7087
  const _rekeySpan = (taken, span) => {
6683
7088
  if (span.key === UNKEYED) return;
6684
7089
  const rules = span.scope.rules;
6685
- const registered = rules.get(span.key);
6686
- if (registered !== undefined && registered.span === span) {
6687
- rules.delete(span.key);
6688
- }
7090
+ if (rules.get(span.key) === span) rules.delete(span.key);
6689
7091
  span.key = taken.text.slice(span.at, span.at + span.len);
6690
- if (!rules.has(span.key)) rules.set(span.key, { taken, span });
7092
+ if (!rules.has(span.key)) rules.set(span.key, span);
6691
7093
  };
6692
7094
 
6693
7095
  /**
@@ -6727,7 +7129,10 @@ const _spanEntry = (text, prelude) => ({
6727
7129
  listKind: LIST_KIND_SELECTOR,
6728
7130
  children: null,
6729
7131
  head: "",
6730
- unordered: false
7132
+ unordered: false,
7133
+ list: null,
7134
+ block: null,
7135
+ declarations: null
6731
7136
  });
6732
7137
 
6733
7138
  /**
@@ -6832,7 +7237,9 @@ const _joinsNeighbor = (writer, before, rule) => {
6832
7237
  return same && _foldsNeighborBody(writer, before, rule);
6833
7238
  }
6834
7239
  if (!same) {
6835
- if (!before.listable || !rule.listable) return false;
7240
+ if (!_entryListable(before.entry) || !_entryListable(rule.entry)) {
7241
+ return false;
7242
+ }
6836
7243
  const selector = tail.slice(second.at, second.at + rule.prelude);
6837
7244
  if (
6838
7245
  !_lendSelectorsTo(writer, before.taken, first, before.prelude, selector)
@@ -6918,14 +7325,11 @@ const _joinsPrior = (writer, prior, held) =>
6918
7325
  */
6919
7326
  const _joinsPriorEntry = (writer, prior, entry, parted) => {
6920
7327
  if (!_S._transforms.mergeRules) return false;
6921
- if (entry.prelude === -1 || entry.atRule || !_entryListable(entry)) {
6922
- return false;
6923
- }
7328
+ if (entry.prelude === -1 || entry.atRule) return false;
6924
7329
  // A kept comment between the two was written above the later rule, which is
6925
7330
  // read where it still stands rather than here, the flush having cleared it.
6926
7331
  if (parted) return false;
6927
7332
  const span = prior.span;
6928
- if (!prior.listable) return false;
6929
7333
  // Read where the rule now stands, never where it was taken: a cut inside its
6930
7334
  // block, or a list this already grew, leaves the entry it went out as stale.
6931
7335
  if (span.len <= prior.prelude) return false;
@@ -6942,6 +7346,8 @@ const _joinsPriorEntry = (writer, prior, entry, parted) => {
6942
7346
  ) {
6943
7347
  return false;
6944
7348
  }
7349
+ // After the blocks compare, which almost none do: the shape test is a regex.
7350
+ if (!_entryListable(entry) || !_entryListable(prior.entry)) return false;
6945
7351
  // The same rule twice is one rule: nothing stands between them, so the later
6946
7352
  // copy only restates what the earlier already says.
6947
7353
  if (
@@ -6963,7 +7369,7 @@ const _joinsPriorEntry = (writer, prior, entry, parted) => {
6963
7369
  prior.taken,
6964
7370
  span,
6965
7371
  prior.prelude,
6966
- entry.text.slice(0, entry.prelude)
7372
+ _entryPrelude(entry)
6967
7373
  )
6968
7374
  ) {
6969
7375
  return false;
@@ -7161,13 +7567,7 @@ const _mergeIntoDistantRule = (writer, entry) => {
7161
7567
  // grew is longer than the one that was taken.
7162
7568
  const preludeNow = span.len - block.length;
7163
7569
  if (
7164
- !_lendSelectorsTo(
7165
- writer,
7166
- taken,
7167
- span,
7168
- preludeNow,
7169
- entry.text.slice(0, entry.prelude)
7170
- )
7570
+ !_lendSelectorsTo(writer, taken, span, preludeNow, _entryPrelude(entry))
7171
7571
  ) {
7172
7572
  return false;
7173
7573
  }
@@ -7227,13 +7627,11 @@ const _orderDistantLists = (writer) => {
7227
7627
  * Merge the box families whose four longhands two joined blocks wrote between
7228
7628
  * them, as `_mergeBoxLonghands` does for one block, so the join prints what the
7229
7629
  * one rule written whole would.
7230
- * @param {string} body the joined block's declarations
7231
- * @returns {string} the body, each family it could merge merged
7630
+ * @param {PrintedDeclaration[]} declarations the joined block's declarations
7631
+ * @returns {PrintedDeclaration[]} them, each family it could merge merged
7232
7632
  */
7233
- const _mergeJoinedBoxLonghands = (body) => {
7234
- if (!_S._transforms.mergeLonghands) return body;
7235
- const declarations = _blockDeclarations(body);
7236
- if (declarations === null) return body;
7633
+ const _mergeJoinedBoxLonghands = (declarations) => {
7634
+ if (!_S._transforms.mergeLonghands) return declarations;
7237
7635
  /** @type {Map<string, number>} */
7238
7636
  const at = new Map();
7239
7637
  /** @type {Set<string>} */
@@ -7285,13 +7683,29 @@ const _mergeJoinedBoxLonghands = (body) => {
7285
7683
  const box = _collapseBox(values);
7286
7684
  if (box === null) continue;
7287
7685
  if (texts === null) texts = declarations.map((one) => one.text);
7288
- texts[first] =
7289
- `${shorthand}:${_joinSlots(box)}${important ? "!important" : ""}`;
7686
+ texts[first] = `${shorthand}:${_joinSlots(box)}${
7687
+ important ? "!important" : ""
7688
+ }`;
7290
7689
  for (const index of indexes) if (index !== first) texts[index] = "";
7291
7690
  }
7292
- return texts === null
7293
- ? body
7294
- : texts.filter((text) => text.length !== 0).join(";");
7691
+ if (texts === null) return declarations;
7692
+ /** @type {PrintedDeclaration[]} */
7693
+ const out = [];
7694
+ for (let i = 0; i < texts.length; i++) {
7695
+ const text = texts[i];
7696
+ if (text.length === 0) continue;
7697
+ if (text === declarations[i].text) {
7698
+ out.push(declarations[i]);
7699
+ continue;
7700
+ }
7701
+ const colon = text.indexOf(":");
7702
+ out.push({
7703
+ property: text.slice(0, colon),
7704
+ value: text.slice(colon + 1),
7705
+ text
7706
+ });
7707
+ }
7708
+ return out;
7295
7709
  };
7296
7710
 
7297
7711
  /**
@@ -7304,7 +7718,7 @@ const _mergeJoinedBoxLonghands = (body) => {
7304
7718
  const _joinedAtRuleSpans = (merged) => {
7305
7719
  // An at-rule's entry is always built with its children, never `null`.
7306
7720
  const children = /** @type {RuleEntry[]} */ (merged.children);
7307
- const prelude = merged.text.slice(0, merged.prelude);
7721
+ const prelude = _entryPrelude(merged);
7308
7722
  /** @type {RuleSpan[]} */
7309
7723
  const spans = [];
7310
7724
  let at = 0;
@@ -7322,7 +7736,8 @@ const _joinedAtRuleSpans = (merged) => {
7322
7736
  scope: _rootScope(),
7323
7737
  key: child.text,
7324
7738
  at,
7325
- len: child.text.length
7739
+ len: child.text.length,
7740
+ taken: null
7326
7741
  });
7327
7742
  } else {
7328
7743
  const inner = _joinedAtRuleSpans(child);
@@ -7332,7 +7747,8 @@ const _joinedAtRuleSpans = (merged) => {
7332
7747
  scope: _rootScope(),
7333
7748
  key: UNKEYED,
7334
7749
  at,
7335
- len: child.text.length
7750
+ len: child.text.length,
7751
+ taken: null
7336
7752
  });
7337
7753
  }
7338
7754
  for (const span of inner.spans) {
@@ -7340,7 +7756,8 @@ const _joinedAtRuleSpans = (merged) => {
7340
7756
  scope: _enclosingRuleScope(span.scope, inner.keyPrelude),
7341
7757
  key: span.key,
7342
7758
  at: at + inner.bodyAt + span.at,
7343
- len: span.len
7759
+ len: span.len,
7760
+ taken: null
7344
7761
  });
7345
7762
  }
7346
7763
  }
@@ -7388,7 +7805,7 @@ const _joinedSpans = (before, beforeOwn, entry, own, merged) => {
7388
7805
  ) {
7389
7806
  return _WHOLE_TEXT_SPANS;
7390
7807
  }
7391
- const prelude = merged.text.slice(0, merged.prelude);
7808
+ const prelude = _entryPrelude(merged);
7392
7809
  const length = entry.text.length - entry.prelude - 2;
7393
7810
  const at = merged.text.length - 1 - length - (merged.prelude + 1);
7394
7811
  const shift = at + own.bodyAt - (entry.prelude + 1);
@@ -7399,7 +7816,8 @@ const _joinedSpans = (before, beforeOwn, entry, own, merged) => {
7399
7816
  scope: span.scope,
7400
7817
  key: span.key,
7401
7818
  at: span.at + shift,
7402
- len: span.len
7819
+ len: span.len,
7820
+ taken: null
7403
7821
  });
7404
7822
  }
7405
7823
  return {
@@ -7444,8 +7862,7 @@ const _takeTopLevel = (node, writer, offset, line, column) => {
7444
7862
  const at = writer.takeRetractable(node, offset, line, column, own);
7445
7863
  _settlePrior(writer);
7446
7864
  if (_topLevelRules !== null) {
7447
- /** @type {TakenPiece} */
7448
- const taken = { piece: at, text: own, spans: [], empties: -1 };
7865
+ const taken = new TakenPiece(at, own, -1);
7449
7866
  if (_prefixPieces === null) _prefixPieces = new Map();
7450
7867
  _prefixPieces.set(at, taken);
7451
7868
  _topLevelRules.push(taken);
@@ -7697,8 +8114,7 @@ const _walkTopLevel = (node, writer) => {
7697
8114
  _nodeCount = 0;
7698
8115
  _flatTop = 0;
7699
8116
  // Body indices recycle with node ids.
7700
- _declBodies.length = 0;
7701
- _ruleBodies.length = 0;
8117
+ _dropBodies();
7702
8118
  if (_walkStopped && writer === undefined) throw WALK_STOPPED;
7703
8119
  };
7704
8120
 
@@ -7712,8 +8128,7 @@ const _recycleTopLevel = (node) => {
7712
8128
  if (_flatTop > _flatPeak) _flatPeak = _flatTop;
7713
8129
  _nodeCount = 0;
7714
8130
  _flatTop = 0;
7715
- _declBodies.length = 0;
7716
- _ruleBodies.length = 0;
8131
+ _dropBodies();
7717
8132
  };
7718
8133
 
7719
8134
  // The store buffers grow to the largest single top-level rule ever parsed and
@@ -8048,6 +8463,21 @@ const grammar = (input, visitors, writer, options) => {
8048
8463
  }
8049
8464
  _visitors = visitors;
8050
8465
  _commentBucket = visitors[NodeType.Comment];
8466
+ _blockMemo =
8467
+ writer !== undefined &&
8468
+ writer.options.mode === "minify" &&
8469
+ visitors.length === 0 &&
8470
+ _unusedSymbols === null &&
8471
+ !_S._prefixingOn &&
8472
+ !_colorFallbacksNeeded &&
8473
+ _S._renderEmbeddedSource === undefined &&
8474
+ _S._deferEmbeddedSource === undefined
8475
+ ? new Map()
8476
+ : null;
8477
+ _declarationMemo = _blockMemo === null ? null : new Map();
8478
+ _blockMemoScopes.clear();
8479
+ _singleValueKeys = _blockMemo === null ? null : new Set();
8480
+ _declarationMemoKey = "";
8051
8481
  _walkStopped = false;
8052
8482
 
8053
8483
  // WHY: the comment sink fires the `Comment` visitor bucket and, while
@@ -8210,6 +8640,15 @@ const grammar = (input, visitors, writer, options) => {
8210
8640
  _blockSpans.length = 0;
8211
8641
  _ruleScopes = null;
8212
8642
  _rootRuleScope = null;
8643
+ _shadowDeclared.clear();
8644
+ _blockMemo = null;
8645
+ _blockMemoScopes.clear();
8646
+ _blockMemoTable = _blockMemoKeyTable = new Map();
8647
+ _declarationMemo = null;
8648
+ _singleValueKeys = null;
8649
+ _blockMemoNode = null;
8650
+ _blockMemoBody = undefined;
8651
+ _blockMemoKey = "";
8213
8652
  // Each holds a piece of the sheet just printed, so the last one printed
8214
8653
  // would be held until the next parse replaces them.
8215
8654
  _distantRules.clear();
@@ -8221,7 +8660,9 @@ const grammar = (input, visitors, writer, options) => {
8221
8660
  _anonymousLayers = 0;
8222
8661
  _S._input = "";
8223
8662
  _originalInput = "";
8224
- _locConverter = /** @type {LocConverter} */ (/** @type {unknown} */ (null));
8663
+ _locConverter = /** @type {LocConverter} */ (
8664
+ /** @type {LocConverter | null} */ (null)
8665
+ );
8225
8666
  _declBodies.length = 0;
8226
8667
  _ruleBodies.length = 0;
8227
8668
  _flatTop = 0;
@@ -8230,7 +8671,9 @@ const grammar = (input, visitors, writer, options) => {
8230
8671
  _flatGrowHint = _flatPeak;
8231
8672
  _flat = EMPTY_I32;
8232
8673
  }
8233
- _visitors = /** @type {CompiledVisitorMap} */ (/** @type {unknown} */ ([]));
8674
+ _visitors = /** @type {CompiledVisitorMap} */ (
8675
+ /** @type {CompiledVisitorMap | never[]} */ ([])
8676
+ );
8234
8677
  _commentBucket = undefined;
8235
8678
  if (_capacity > _SHRINK_CAPACITY) {
8236
8679
  // +1: node ids are 1-based and grow fires at `id >= capacity`.
@@ -8891,6 +9334,10 @@ const _landsAlike = (value, shorter) => {
8891
9334
  * @returns {string} the rounded number, or `num`
8892
9335
  */
8893
9336
  const _roundSignificant = (num, unit = "") => {
9337
+ // A number written in at most six characters carries at most six significant
9338
+ // digits, a sign and dot only taking away, so rounding hands back what it was
9339
+ // given. Almost every number is this short.
9340
+ if (num.length <= _SIGNIFICANT_DIGITS) return num;
8894
9341
  const digits =
8895
9342
  unit === "" ||
8896
9343
  unit === "%" ||
@@ -8898,15 +9345,24 @@ const _roundSignificant = (num, unit = "") => {
8898
9345
  ANGLE_UNITS.has(unit)
8899
9346
  ? _SIGNIFICANT_DIGITS
8900
9347
  : _SCALED_LENGTH_DIGITS;
8901
- // A number written in at most six characters carries at most six significant
8902
- // digits, a sign and dot only taking away, so rounding hands back what it was
8903
- // given. Almost every number is this short.
8904
- if (num.length <= _SIGNIFICANT_DIGITS) return num;
8905
9348
  if (num.includes("e") || num.includes("E")) return num;
8906
9349
  const value = Number(num);
8907
9350
  if (!Number.isFinite(value) || Math.abs(value) >= _ROUNDING_LIMIT) return num;
8908
9351
  let rounded = value.toPrecision(digits);
8909
- if (digits === _SCALED_LENGTH_DIGITS) {
9352
+ if (unit === "%") {
9353
+ // Gecko parses a percentage as a single-precision fraction laid out in 1/60px,
9354
+ // so the digits must reach that float: `14.285714%` drifts, `14.2857143%` not.
9355
+ const parsed = Math.fround(value / 100);
9356
+ for (
9357
+ let more = digits;
9358
+ more < _SCALED_LENGTH_DIGITS &&
9359
+ Math.fround(Number(rounded) / 100) !== parsed;
9360
+ more++
9361
+ ) {
9362
+ rounded = value.toPrecision(more + 1);
9363
+ }
9364
+ if (Math.fround(Number(rounded) / 100) !== parsed) return num;
9365
+ } else if (digits === _SCALED_LENGTH_DIGITS) {
8910
9366
  // The fewest digits landing where the written length does, asked again of
8911
9367
  // each answer until it holds, which is what a second pass would ask.
8912
9368
  let from = value;
@@ -9651,6 +10107,17 @@ const _readByEveryEngine = (profile) => {
9651
10107
  return true;
9652
10108
  };
9653
10109
 
10110
+ /**
10111
+ * Whether the targets read an ability: the selection's browsers, or with none
10112
+ * each engine shipping today, which is what `dropOverriddenDeclarations` assumes.
10113
+ * @param {number | undefined} profile its `getSupportProfiles()` row
10114
+ * @returns {boolean} true when the later value it names is read
10115
+ */
10116
+ const _readsOverridingValue = (profile) =>
10117
+ _prefixBrowsers === null
10118
+ ? profile !== undefined && _readByEveryEngine(profile)
10119
+ : _readsAll(profile);
10120
+
9654
10121
  /**
9655
10122
  * The vendor spellings at least one target browser still needs for a construct —
9656
10123
  * a target at version V needs spelling S when some `[browser, from, to]` of S has
@@ -10776,6 +11243,20 @@ const _minifyPolarColorFunction = (
10776
11243
  fn === "lab" || fn === "lch"
10777
11244
  ? _labToLinearSrgb(l, a, b)
10778
11245
  : _oklabToLinearSrgb(l, a, b);
11246
+ // WHY: a hex mixed in `oklch` reaches it through a conversion, and Chromium
11247
+ // reads a hue there as missing below a chroma of 0.02 — so `color-mix(in
11248
+ // oklch, currentColor 35%, #0000)` paints hue 0 where `oklch()` named one.
11249
+ if (fn === "oklch" && (a !== 0 || b !== 0)) {
11250
+ const space = getColorSpaceModel().get(fn);
11251
+ const clamped = linear.map((c) => Math.min(1, Math.max(0, c)));
11252
+ if (
11253
+ space !== undefined &&
11254
+ space.uncertainHue !== null &&
11255
+ space.uncertainHue(space.to(clamped))
11256
+ ) {
11257
+ return null;
11258
+ }
11259
+ }
10779
11260
  // `_channelByte` decides the gamut from the encoded value, where the
10780
11261
  // question is whether clamping would move the byte.
10781
11262
  raw = linear.map((c) => _gammaEncode(c) * 255);
@@ -10999,7 +11480,7 @@ const _resolveColor = (text) => {
10999
11480
  }
11000
11481
  // Every call that answers records them, so a non-null answer is one.
11001
11482
  const raw = /** @type {number[]} */ (
11002
- /** @type {unknown} */ (_fallbackChannels)
11483
+ /** @type {number[] | null} */ (_fallbackChannels)
11003
11484
  );
11004
11485
  return {
11005
11486
  linear: raw.map((channel) => _gammaDecode(channel / 255)),
@@ -11832,7 +12313,7 @@ const _CUSTOM_MEDIA_REFERENCE_RE = /^\(\s*(--[^\s()]+)\s*\)$/;
11832
12313
  // The same, anywhere in a printed `(…)` group.
11833
12314
  const _NESTED_CUSTOM_MEDIA_REFERENCE_RE = /\(\s*(--[^\s()]+)\s*\)/g;
11834
12315
  // A media type: an identifier, which no keyword between conditions is.
11835
- const _MEDIA_TYPE_RE = /^-?[a-z_\u0080-￿][\w\u0080-￿-]*$/i;
12316
+ const _MEDIA_TYPE_RE = /^-?[a-zA-Z_\u0080-￿][\w\u0080-￿-]*$/;
11836
12317
 
11837
12318
  /**
11838
12319
  * @param {string} token a printed query token
@@ -12388,10 +12869,12 @@ const _foldPseudoNames = (parts) => {
12388
12869
  // not, `linearGradient` being an SVG element, and nor does an id, class or
12389
12870
  // attribute value. A functional pseudo folds its own name alone.
12390
12871
  if (second !== -1 && parts[second] === ":") {
12391
- const lowered = asciiLowercaseName(part);
12392
- if (_S._transforms.foldCase && lowered !== part && !part.includes("(")) {
12393
- part = lowered;
12394
- parts[i] = lowered;
12872
+ if (_S._transforms.foldCase && !part.includes("(")) {
12873
+ const lowered = asciiLowercaseName(part);
12874
+ if (lowered !== part) {
12875
+ part = lowered;
12876
+ parts[i] = lowered;
12877
+ }
12395
12878
  }
12396
12879
  // Folded first, so the legacy name below is matched against the folded
12397
12880
  // text rather than folded a second time.
@@ -12453,6 +12936,16 @@ const _isCompoundText = (selector) => {
12453
12936
  * @param {string[]} parts a selector's printed parts, rewritten in place
12454
12937
  */
12455
12938
  const _unwrapLoneIs = (parts) => {
12939
+ // Almost no selector holds an `:is()`, and the walk below reads every part.
12940
+ let candidate = false;
12941
+ for (let i = 0; i < parts.length && !candidate; i++) {
12942
+ const part = parts[i];
12943
+ candidate =
12944
+ part.length > 4 &&
12945
+ part.charCodeAt(2) === CC_LEFT_PARENTHESIS &&
12946
+ (part.charCodeAt(0) | 0x20) === CC_LOWER_I;
12947
+ }
12948
+ if (!candidate) return;
12456
12949
  let before = -1;
12457
12950
  // Past a pseudo-element a compound takes only a few pseudo-classes, which an
12458
12951
  // `:is()` there may hold and a bare compound may not.
@@ -12473,12 +12966,16 @@ const _unwrapLoneIs = (parts) => {
12473
12966
  ) {
12474
12967
  pastElement = true;
12475
12968
  }
12476
- const inner = call.slice(3, -1);
12477
- if (
12969
+ const isCall =
12478
12970
  !pastElement &&
12479
12971
  call.length > 4 &&
12480
- asciiCaseInsensitiveMatch(call.slice(0, 3), "is(") &&
12481
- call.charCodeAt(call.length - 1) === CC_RIGHT_PARENTHESIS &&
12972
+ (call.charCodeAt(0) | 0x20) === CC_LOWER_I &&
12973
+ (call.charCodeAt(1) | 0x20) === CC_LOWER_S &&
12974
+ call.charCodeAt(2) === CC_LEFT_PARENTHESIS &&
12975
+ call.charCodeAt(call.length - 1) === CC_RIGHT_PARENTHESIS;
12976
+ const inner = isCall ? call.slice(3, -1) : "";
12977
+ if (
12978
+ isCall &&
12482
12979
  _isCompoundText(inner) &&
12483
12980
  !inner.includes("::") &&
12484
12981
  !_LEGACY_PSEUDO_ELEMENT_RE.test(inner) &&
@@ -12697,7 +13194,8 @@ const _dropRedundantSupportsParens = (parts) => {
12697
13194
  continue;
12698
13195
  }
12699
13196
  let text = part;
12700
- for (;;) {
13197
+ // Only an opening pair is one to drop, which almost no condition holds.
13198
+ while (text.includes("((")) {
12701
13199
  const close = _matchingParens(text);
12702
13200
  if (close === null) break;
12703
13201
  let at = -1;
@@ -12725,6 +13223,7 @@ const _dropRedundantSupportsParens = (parts) => {
12725
13223
  }
12726
13224
  if (groups !== 1) return;
12727
13225
  const text = parts[only];
13226
+ if (text.charCodeAt(1) !== CC_LEFT_PARENTHESIS) return;
12728
13227
  const close = _matchingParens(text);
12729
13228
  if (
12730
13229
  close !== null &&
@@ -13581,6 +14080,25 @@ const _foldFollowingLonghands = (path, items, texts, superseded) => {
13581
14080
  }
13582
14081
  return written.has(base) && _prefixedPropertyRemovable(base, property);
13583
14082
  };
14083
+ // Each declaration's property, read once: the walk back from every longhand
14084
+ // asks again of the ones before it, and a fold keeps the property it rewrites.
14085
+ /** @type {(string | undefined)[]} */
14086
+ const properties = [];
14087
+ /**
14088
+ * The property a declaration sets, read through `_canonicalProperty`.
14089
+ * @param {number} at the declaration's index
14090
+ * @returns {string} its property
14091
+ */
14092
+ const propertyAt = (at) => {
14093
+ let property = properties[at];
14094
+ if (property === undefined) {
14095
+ property = _canonicalProperty(
14096
+ _printedProperty(texts[at], texts[at].indexOf(":"))
14097
+ );
14098
+ properties[at] = property;
14099
+ }
14100
+ return property;
14101
+ };
13584
14102
  for (let i = 0; i < items.length; i++) {
13585
14103
  // A dropped declaration prints nothing, so nothing stands where it stood.
13586
14104
  if (texts[i].length === 0 || superseded.has(i)) continue;
@@ -13591,7 +14109,15 @@ const _foldFollowingLonghands = (path, items, texts, superseded) => {
13591
14109
  if (dropsWithPrefix(i)) continue;
13592
14110
  if (
13593
14111
  shorthand !== -1 &&
13594
- _foldIntoEarlier(path, items, texts, superseded, i, dropsWithPrefix)
14112
+ _foldIntoEarlier(
14113
+ path,
14114
+ items,
14115
+ texts,
14116
+ superseded,
14117
+ i,
14118
+ dropsWithPrefix,
14119
+ propertyAt
14120
+ )
13595
14121
  ) {
13596
14122
  superseded.add(i);
13597
14123
  continue;
@@ -13615,29 +14141,37 @@ let _anyLonghand = null;
13615
14141
  * @param {Set<number>} superseded the indices already dropped
13616
14142
  * @param {number} from the longhand's index
13617
14143
  * @param {(at: number) => boolean} drops whether a declaration is dropped later
14144
+ * @param {(at: number) => string} propertyAt the property a declaration sets, read through `_canonicalProperty`
13618
14145
  * @returns {boolean} true when it was folded into one before it
13619
14146
  */
13620
- const _foldIntoEarlier = (path, items, texts, superseded, from, drops) => {
14147
+ const _foldIntoEarlier = (
14148
+ path,
14149
+ items,
14150
+ texts,
14151
+ superseded,
14152
+ from,
14153
+ drops,
14154
+ propertyAt
14155
+ ) => {
13621
14156
  if (_anyLonghand === null) {
13622
14157
  _anyLonghand = new Set();
13623
14158
  for (const longhands of getShorthandLonghands().values()) {
13624
14159
  for (const longhand of longhands) _anyLonghand.add(longhand);
13625
14160
  }
13626
14161
  }
13627
- const property = _canonicalProperty(
13628
- _printedProperty(texts[from], texts[from].indexOf(":"))
13629
- );
13630
- if (!_anyLonghand.has(property.replace(_VENDOR_ITEM_PREFIX_RE, ""))) {
14162
+ const property = propertyAt(from);
14163
+ const bare =
14164
+ property.charCodeAt(0) === CC_HYPHEN_MINUS
14165
+ ? property.replace(_VENDOR_ITEM_PREFIX_RE, "")
14166
+ : property;
14167
+ if (!_anyLonghand.has(bare)) {
13631
14168
  return false;
13632
14169
  }
13633
14170
  for (let at = from - 1; at >= 0; at--) {
13634
14171
  if (texts[at].length === 0 || superseded.has(at)) continue;
13635
14172
  if (path.type(items[at]) !== NodeType.Declaration) return false;
13636
14173
  if (drops(at)) continue;
13637
- const other = _canonicalProperty(
13638
- _printedProperty(texts[at], texts[at].indexOf(":"))
13639
- );
13640
- if (_orderMatters(property, other)) {
14174
+ if (_orderMatters(property, propertyAt(at))) {
13641
14175
  return _foldInto(path, items, texts, at, from);
13642
14176
  }
13643
14177
  }
@@ -14570,9 +15104,9 @@ const _familyValue = (property, longhands, values) => {
14570
15104
  * @returns {boolean} true when no target is left reading the earlier one
14571
15105
  */
14572
15106
  const _valueReadByTarget = (property, value) => {
14573
- // No selection names no browser to answer for, so nothing is known to read the
14574
- // later value: the earlier one stands unless asked to assume otherwise.
14575
- if (_prefixBrowsers === null) return _dropOverriddenDeclarations;
15107
+ // No selection names no browser to answer for, so the earlier one stands
15108
+ // unless the option assumes each engine shipping today, read against below.
15109
+ if (_prefixBrowsers === null && !_dropOverriddenDeclarations) return false;
14576
15110
  // A selection naming a browser the tables do not cover answers for less than
14577
15111
  // the whole of it. Naming a browser states a target, which the option that
14578
15112
  // assumes one does not override, so this stands whatever it says.
@@ -14582,7 +15116,7 @@ const _valueReadByTarget = (property, value) => {
14582
15116
  const substitutions = _heldSubstitutions(value);
14583
15117
  if (substitutions !== null) {
14584
15118
  for (const name of substitutions) {
14585
- if (!_readsAll(_valueSupportedFrom(name))) return false;
15119
+ if (!_readsOverridingValue(_valueSupportedFrom(name))) return false;
14586
15120
  }
14587
15121
  return true;
14588
15122
  }
@@ -14592,7 +15126,7 @@ const _valueReadByTarget = (property, value) => {
14592
15126
  const components = _splitPrintedComponents(value);
14593
15127
  if (components.length > 1) return _textDecorationRead(components);
14594
15128
  }
14595
- return _readsAll(
15129
+ return _readsOverridingValue(
14596
15130
  _valueSupportedFrom(`${property} ${toLowerCaseIfNeeded(value)}`)
14597
15131
  );
14598
15132
  }
@@ -14604,9 +15138,10 @@ const _valueReadByTarget = (property, value) => {
14604
15138
  return false;
14605
15139
  }
14606
15140
  const fn = toLowerCaseIfNeeded(value.slice(0, open));
14607
- if (!_readsAll(_valueSupportedFrom(fn))) return false;
15141
+ if (!_readsOverridingValue(_valueSupportedFrom(fn))) return false;
14608
15142
  if (fn.endsWith("gradient")) {
14609
- return _gradientArgumentsRead(value.slice(open + 1, -1));
15143
+ // A one-stop gradient is CSS Images 4's, which not every engine reads yet.
15144
+ return _gradientArgumentsRead(value.slice(open + 1, -1), 2);
14610
15145
  }
14611
15146
  const args = value.slice(open + 1, -1).match(_COLOR_ARGUMENT_RE);
14612
15147
  if (args === null) return false;
@@ -14660,33 +15195,61 @@ const _GRADIENT_AMOUNT_RE = /^[+-]?(?:\d+\.?\d*|\.\d+)([a-z]+|%)?$/i;
14660
15195
  * line, shape or position of keywords and amounts, and stops of a color every
14661
15196
  * engine names. A call inside, or an interpolation space, is not asked here.
14662
15197
  * @param {string} inner the printed arguments
15198
+ * @param {number} stopsNeeded how many color stops the arguments must hold
14663
15199
  * @returns {boolean} true when the target reads every one
14664
15200
  */
14665
- const _gradientArgumentsRead = (inner) => {
14666
- for (const argument of _splitTopLevelArguments(inner)) {
14667
- for (const component of _splitPrintedComponents(argument.trim())) {
15201
+ const _gradientArgumentsRead = (inner, stopsNeeded) => {
15202
+ const args = _splitTopLevelArguments(inner);
15203
+ let stops = 0;
15204
+ for (let index = 0; index < args.length; index++) {
15205
+ // A line, shape or position opens the list alone; a stop is one color
15206
+ // and its amounts. Anything else, an empty argument too, is invalid.
15207
+ if (args[index].trim() === "") return false;
15208
+ let words = 0;
15209
+ let colors = 0;
15210
+ for (const component of _splitPrintedComponents(args[index].trim())) {
14668
15211
  const lowered = toLowerCaseIfNeeded(component);
14669
- if (_GRADIENT_KEYWORDS.has(lowered)) continue;
15212
+ if (_GRADIENT_KEYWORDS.has(lowered)) {
15213
+ if (lowered === "transparent" || lowered === "currentcolor") {
15214
+ colors++;
15215
+ } else {
15216
+ words++;
15217
+ }
15218
+ if (words !== 0 && (index !== 0 || colors !== 0)) return false;
15219
+ if (colors > 1) return false;
15220
+ continue;
15221
+ }
14670
15222
  const amount = _GRADIENT_AMOUNT_RE.exec(lowered);
14671
15223
  if (amount !== null) {
14672
15224
  // A unit some target does not read drops the gradient there.
14673
15225
  const unit = amount[1];
14674
15226
  if (unit === undefined || unit === "%") continue;
14675
- if (_readsAll(_valueSupportedFrom(`unit ${unit}`))) continue;
14676
- return false;
15227
+ if (!_readsOverridingValue(_valueSupportedFrom(`unit ${unit}`))) {
15228
+ return false;
15229
+ }
15230
+ continue;
14677
15231
  }
15232
+ let color = false;
14678
15233
  if (_LONE_HEX_RE.test(component)) {
14679
- if (component.length === 4 || component.length === 7) continue;
14680
- if (_S._hexAlphaAllowed) continue;
14681
- return false;
14682
- }
14683
- if (getColorNameToRgb().has(lowered) && !LATER_COLOR_NAMES.has(lowered)) {
14684
- continue;
15234
+ if (
15235
+ component.length !== 4 &&
15236
+ component.length !== 7 &&
15237
+ !_S._hexAlphaAllowed
15238
+ ) {
15239
+ return false;
15240
+ }
15241
+ color = true;
15242
+ } else if (
15243
+ getColorNameToRgb().has(lowered) &&
15244
+ !LATER_COLOR_NAMES.has(lowered)
15245
+ ) {
15246
+ color = true;
14685
15247
  }
14686
- return false;
15248
+ if (!color || ++colors > 1 || words !== 0) return false;
14687
15249
  }
15250
+ if (colors !== 0) stops++;
14688
15251
  }
14689
- return true;
15252
+ return stops >= stopsNeeded;
14690
15253
  };
14691
15254
 
14692
15255
  /**
@@ -14714,7 +15277,7 @@ const _textDecorationRead = (components) => {
14714
15277
  );
14715
15278
  if (keywords.includes(toLowerCaseIfNeeded(written))) continue;
14716
15279
  // A color or an amount, read as a gradient's stop or length is.
14717
- if (!_gradientArgumentsRead(written)) return false;
15280
+ if (!_gradientArgumentsRead(written, 0)) return false;
14718
15281
  }
14719
15282
  return true;
14720
15283
  };
@@ -14794,6 +15357,17 @@ const _writeIsDead = (path, writer, earlier, later, keyframe) => {
14794
15357
  );
14795
15358
  };
14796
15359
 
15360
+ /**
15361
+ * A property's first word, what an unnamed property of its family starts with:
15362
+ * `margin` for `margin-top`.
15363
+ * @param {string} name a property
15364
+ * @returns {string} the word before its first `-`
15365
+ */
15366
+ const _firstPropertyWord = (name) => {
15367
+ const dash = name.indexOf("-");
15368
+ return dash === -1 ? name : name.slice(0, dash);
15369
+ };
15370
+
14797
15371
  /**
14798
15372
  * Merge a box family's four longhands into their shorthand, which sets exactly
14799
15373
  * those four (see `BOX_LONGHANDS`) — so the rule computes the same either way.
@@ -14869,10 +15443,9 @@ const _mergeBoxLonghands = (
14869
15443
  // merges the same before and after it is reordered. A pair family states no
14870
15444
  // prefix at all — its longhands are the two names — so a property no dataset
14871
15445
  // names is read by either one's first segment.
14872
- const head = (/** @type {string} */ name) =>
14873
- /** @type {RegExpExecArray} */ (/^[^-]+/.exec(name))[0];
14874
- const prefix = BOX_FAMILY_PREFIX.get(shorthand) || head(shorthand);
14875
- const families = [prefix, ...longhands.map(head)];
15446
+ const prefix =
15447
+ BOX_FAMILY_PREFIX.get(shorthand) || _firstPropertyWord(shorthand);
15448
+ const families = [prefix, ...longhands.map(_firstPropertyWord)];
14876
15449
  let blocked = false;
14877
15450
  for (
14878
15451
  let i = ordered[0] + 1;
@@ -14904,15 +15477,14 @@ const _mergeBoxLonghands = (
14904
15477
  if (indexes.some((i) => _isImportant(decls[i]) !== important)) continue;
14905
15478
  const values = [];
14906
15479
  for (const i of indexes) {
14907
- const components = _valueComponents(path, decls[i], writer);
14908
- if (components.length !== 1) break;
15480
+ if (!_isSingleValue(path, decls[i], writer)) break;
14909
15481
  // What the declaration prints, not what its tokens spell: a zero length
14910
15482
  // loses its unit as a declaration is written, and a token knows no property.
14911
15483
  const text = writer.get(decls[i]);
14912
15484
  const colon = text.indexOf(":");
14913
15485
  values.push(
14914
15486
  colon === -1
14915
- ? components[0]
15487
+ ? _valueComponents(path, decls[i], writer)[0]
14916
15488
  : text.slice(colon + 1, _printedValueEnd(text, colon))
14917
15489
  );
14918
15490
  }
@@ -15027,7 +15599,7 @@ const _mergeBoxLonghands = (
15027
15599
  };
15028
15600
 
15029
15601
  // A `<custom-ident>` as printed; one the printer keeps escaped fails it.
15030
- const _GRID_IDENT_RE = /^(?:--|-?[a-z_\u0080-\uFFFF])[-\w\u0080-\uFFFF]*$/i;
15602
+ const _GRID_IDENT_RE = /^(?:--|-?[a-zA-Z_\u0080-\uFFFF])[-\w\u0080-\uFFFF]*$/;
15031
15603
  const _GRID_INTEGER_RE = /^[-+]?\d+$/;
15032
15604
 
15033
15605
  /**
@@ -15358,6 +15930,14 @@ const MERGE_TABLES = [
15358
15930
  * concatenated: its own, or one a child carries. The run that grew it settles
15359
15931
  * it where the run ends (`_settleJoinedSeams`), so a join stays what it costs
15360
15932
  * to append rather than re-reading the list it has
15933
+ * @property {string | null} list its prelude where a join wrote it, so the next
15934
+ * join reads or appends to it rather than cutting it back out of `text`, which
15935
+ * would flatten the whole run so far; null to read it off `text`
15936
+ * @property {string | null} block the text from its `{` on, held beside `list`
15937
+ * for the same reason
15938
+ * @property {PrintedDeclaration[] | null} declarations its block's declarations
15939
+ * where a join of two blocks under one prelude wrote them, so the next join
15940
+ * reads them rather than parsing the whole block again; null to parse `text`
15361
15941
  */
15362
15942
 
15363
15943
  // Each printed rule the merge can join -> its entry, by node id. Emptied per
@@ -15450,7 +16030,10 @@ const _opaqueEntry = (text) => ({
15450
16030
  listKind: LIST_KIND_SELECTOR,
15451
16031
  children: null,
15452
16032
  head: "",
15453
- unordered: false
16033
+ unordered: false,
16034
+ list: null,
16035
+ block: null,
16036
+ declarations: null
15454
16037
  });
15455
16038
 
15456
16039
  /**
@@ -15462,7 +16045,7 @@ const _opaqueEntry = (text) => ({
15462
16045
  const _entryListable = (entry) => {
15463
16046
  if (entry.listable === LIST_UNKNOWN) {
15464
16047
  entry.listable = _isJoinablePrelude(
15465
- entry.text.slice(0, entry.prelude),
16048
+ _entryPrelude(entry),
15466
16049
  entry.listKind === LIST_KIND_KEYFRAME,
15467
16050
  entry.listKind === LIST_KIND_NESTED
15468
16051
  )
@@ -15501,22 +16084,33 @@ const _STRING = String.raw`"(?:[^"\\]|\\[\s\S])*"|'(?:[^'\\]|\\[\s\S])*'`;
15501
16084
  const _ATTRIBUTE = String.raw`\[(?:${_IDENT}\|)?${_IDENT}(?:[~|^$*]?=(?:${_IDENT}|${_STRING})(?: [iI])?)?\]`;
15502
16085
  // CSS whitespace: U+00A0 is an identifier's character, not a separator.
15503
16086
  const _SPACE = "[ \\t\\n\\f\\r]";
15504
- const _LEGACY_ELEMENT_NAMES = [...LEGACY_PSEUDO_ELEMENTS].join("|");
16087
+ /**
16088
+ * A pattern matching the word in any ASCII case, where an `i` flag would make V8
16089
+ * case-fold the whole `\u00A0-\uFFFF` class too, costing milliseconds to compile.
16090
+ * @param {string} word a lowercase word
16091
+ * @returns {string} the pattern
16092
+ */
16093
+ const _anyCase = (word) =>
16094
+ word.replace(/[a-z]/g, (letter) => `[${letter}${letter.toUpperCase()}]`);
16095
+ const _LEGACY_ELEMENT_NAMES = [...LEGACY_PSEUDO_ELEMENTS]
16096
+ .map(_anyCase)
16097
+ .join("|");
15505
16098
  // A pseudo-class with no selector inside it: argument-less, or the `An+B` of an
15506
16099
  // `:nth-*()`, whose grammar is closed — the one argument a shape may carry.
15507
16100
  const _PSEUDO_CLASS = String.raw`:(?!(?:${_LEGACY_ELEMENT_NAMES})(?![-\w]))(?:(?:${[
15508
16101
  ...NTH_PSEUDO_FUNCTIONS
15509
- ].join(
15510
- "|"
15511
- )})\(${_SPACE}*(?:odd|even|[-+]?(?:\d*n(?:${_SPACE}*[-+]${_SPACE}*\d+)?|\d+))${_SPACE}*\)|[-\w]+(?![-\w(]))`;
16102
+ ]
16103
+ .map(_anyCase)
16104
+ .join("|")})\(${_SPACE}*(?:${_anyCase("odd")}|${_anyCase(
16105
+ "even"
16106
+ )}|[-+]?(?:\d*[nN](?:${_SPACE}*[-+]${_SPACE}*\d+)?|\d+))${_SPACE}*\)|[-\w]+(?![-\w(]))`;
15512
16107
  // A pseudo-element ends the selector it is in: anything after it is no shape
15513
16108
  // an engine parses.
15514
16109
  const _PSEUDO_ELEMENT = String.raw`(?:::[-\w]+|:(?:${_LEGACY_ELEMENT_NAMES}))(?![-\w(])`;
15515
16110
  const _COMPOUND = `(?:\\*|${_IDENT}|[.#]${_IDENT}|${_ATTRIBUTE})(?:[.#]${_IDENT}|${_ATTRIBUTE}|${_PSEUDO_CLASS})*`;
15516
16111
  const _COMBINATOR = `(?:${_SPACE}*[>+~]${_SPACE}*|${_SPACE}+)`;
15517
16112
  const _JOINABLE_SELECTOR_RE = new RegExp(
15518
- `^(?:${_COMPOUND}${_COMBINATOR})*${_COMPOUND}(?:${_PSEUDO_ELEMENT})?$`,
15519
- "i"
16113
+ `^(?:${_COMPOUND}${_COMBINATOR})*${_COMPOUND}(?:${_PSEUDO_ELEMENT})?$`
15520
16114
  );
15521
16115
 
15522
16116
  // The same, for a rule nested in another: `&` joins there because an engine that
@@ -15524,8 +16118,7 @@ const _JOINABLE_SELECTOR_RE = new RegExp(
15524
16118
  // dropped together rather than one taking the other down.
15525
16119
  const _NESTED_COMPOUND = `(?:&(?:${_COMPOUND})?|${_COMPOUND})`;
15526
16120
  const _JOINABLE_NESTED_RE = new RegExp(
15527
- `^(?:${_NESTED_COMPOUND}${_COMBINATOR})*${_NESTED_COMPOUND}(?:${_PSEUDO_ELEMENT})?$`,
15528
- "i"
16121
+ `^(?:${_NESTED_COMPOUND}${_COMBINATOR})*${_NESTED_COMPOUND}(?:${_PSEUDO_ELEMENT})?$`
15529
16122
  );
15530
16123
 
15531
16124
  // A keyframe selector is a percentage or `to` — `from` is already printed `0%`.
@@ -15717,6 +16310,8 @@ const _blockProperties = (body) => {
15717
16310
  // The `!important` a printed declaration ends in, whatever it was written as.
15718
16311
  const _PRINTED_IMPORTANT_RE = /!\s*important$/i;
15719
16312
 
16313
+ /** @typedef {{ property: string, value: string, text: string }} PrintedDeclaration one declaration of a printed block: its lowercased property, its value and its whole text */
16314
+
15720
16315
  /**
15721
16316
  * The declarations one printed block holds, each as the whole of what it is
15722
16317
  * written as. Built on the same walk `_blockProperties` makes, since what a
@@ -15724,10 +16319,10 @@ const _PRINTED_IMPORTANT_RE = /!\s*important$/i;
15724
16319
  * else — a nested rule of its own, whose declarations are read at its place
15725
16320
  * rather than at the block's.
15726
16321
  * @param {string} body the block's printed body, between its braces
15727
- * @returns {{ property: string, value: string, text: string }[] | null} the declarations, in order, or null
16322
+ * @returns {PrintedDeclaration[] | null} the declarations, in order, or null
15728
16323
  */
15729
16324
  const _blockDeclarations = (body) => {
15730
- /** @type {{ property: string, value: string, text: string }[]} */
16325
+ /** @type {PrintedDeclaration[]} */
15731
16326
  const out = [];
15732
16327
  let depth = 0;
15733
16328
  let quote = 0;
@@ -15807,6 +16402,28 @@ const _rangeEquals = (a, aStart, aEnd, b, bStart, bEnd) => {
15807
16402
  return true;
15808
16403
  };
15809
16404
 
16405
+ /**
16406
+ * A rule's prelude, read from where a join holds it.
16407
+ * @param {RuleEntry} entry the rule
16408
+ * @returns {string} its prelude, as printed
16409
+ */
16410
+ const _entryPrelude = (entry) =>
16411
+ entry.list === null ? entry.text.slice(0, entry.prelude) : entry.list;
16412
+
16413
+ /**
16414
+ * Whether two rules' blocks say the same, each read from where it is held.
16415
+ * @param {RuleEntry} a one rule
16416
+ * @param {RuleEntry} b another
16417
+ * @returns {boolean} true when the blocks are equal
16418
+ */
16419
+ const _blocksEqual = (a, b) => {
16420
+ const aText = a.block === null ? a.text : a.block;
16421
+ const aStart = a.block === null ? a.prelude : 0;
16422
+ const bText = b.block === null ? b.text : b.block;
16423
+ const bStart = b.block === null ? b.prelude : 0;
16424
+ return _rangeEquals(aText, aStart, aText.length, bText, bStart, bText.length);
16425
+ };
16426
+
15810
16427
  // A layer with no name: the one at-rule prelude that is not the same rule twice.
15811
16428
  const ANONYMOUS_LAYER_RE = /^@layer[ \t\n\r\f]*$/i;
15812
16429
 
@@ -15902,7 +16519,11 @@ const _joinedHead = (joined) => {
15902
16519
  * @returns {string} the rule the block prints as
15903
16520
  */
15904
16521
  const _joinedBlockText = (prelude, head, joined) => {
15905
- let lastText = joined[joined.length - 1].text;
16522
+ const last = joined[joined.length - 1];
16523
+ let lastText = last.text;
16524
+ // Read off the block where a join holds one: the text before it is a list a
16525
+ // run of joins is still growing, which reading any of the text flattens.
16526
+ if (last.block !== null) return `${prelude}{${head}${lastText}}`;
15906
16527
  while (
15907
16528
  lastText.length !== 0 &&
15908
16529
  lastText.charCodeAt(lastText.length - 1) === CC_SEMICOLON
@@ -15943,16 +16564,16 @@ const _joinRuleEntries = (before, entry, owned) => {
15943
16564
  // cutting each one's prelude and block out to answer that is two pieces of
15944
16565
  // string per meeting for an answer that is usually no.
15945
16566
  const preludesEqual = _rangeEquals(
15946
- before.text,
16567
+ before.list === null ? before.text : before.list,
15947
16568
  0,
15948
16569
  before.prelude,
15949
- entry.text,
16570
+ entry.list === null ? entry.text : entry.list,
15950
16571
  0,
15951
16572
  entry.prelude
15952
16573
  );
15953
16574
  if (before.atRule) {
15954
16575
  if (!preludesEqual) return null;
15955
- const beforePrelude = before.text.slice(0, before.prelude);
16576
+ const beforePrelude = _entryPrelude(before);
15956
16577
  // CSS Cascade 5 §6.4.1: every `@layer {` opens a layer of its own, and a
15957
16578
  // later layer beats an earlier one whatever the selectors say — so joining
15958
16579
  // the two hands the block back to specificity.
@@ -16036,17 +16657,13 @@ const _joinRuleEntries = (before, entry, owned) => {
16036
16657
  listKind: LIST_KIND_SELECTOR,
16037
16658
  children: joined,
16038
16659
  head,
16039
- unordered
16660
+ unordered,
16661
+ list: beforePrelude,
16662
+ block: null,
16663
+ declarations: null
16040
16664
  };
16041
16665
  }
16042
- const blocksEqual = _rangeEquals(
16043
- before.text,
16044
- before.prelude,
16045
- before.text.length,
16046
- entry.text,
16047
- entry.prelude,
16048
- entry.text.length
16049
- );
16666
+ const blocksEqual = _blocksEqual(before, entry);
16050
16667
  // The same selector twice is one rule: nothing stands between them, so its
16051
16668
  // declarations are read in the order they were written either way.
16052
16669
  if (preludesEqual) {
@@ -16056,12 +16673,14 @@ const _joinRuleEntries = (before, entry, owned) => {
16056
16673
  // Declarations after a nested rule are the implicit `& {…}` the engine
16057
16674
  // builds for them, which is a rule the sheet did not have.
16058
16675
  if (!before.plain) return null;
16059
- const beforePrelude = before.text.slice(0, before.prelude);
16060
- const body = before.text.slice(before.prelude + 1, -1);
16676
+ const beforePrelude = _entryPrelude(before);
16061
16677
  const rest = entry.text.slice(entry.prelude + 1, -1);
16062
16678
  // One block holds a property once, so two blocks join only where the later
16063
16679
  // declaration is one the earlier could not have been read behind.
16064
- const earlier = _blockDeclarations(body);
16680
+ const earlier =
16681
+ before.declarations === null
16682
+ ? _blockDeclarations(before.text.slice(before.prelude + 1, -1))
16683
+ : before.declarations;
16065
16684
  if (earlier === null) return null;
16066
16685
  // The later block's declarations, and only where it holds nothing else: a
16067
16686
  // nested rule leaves the properties readable but not their values.
@@ -16109,19 +16728,42 @@ const _joinRuleEntries = (before, entry, owned) => {
16109
16728
  }
16110
16729
  const kept =
16111
16730
  dropped.size === 0
16112
- ? body
16113
- : earlier
16114
- .filter((_, at) => !dropped.has(at))
16115
- .map((one) => one.text)
16116
- .join(";");
16117
- // Each block already dropped the `;` its own `}` made redundant, so the one
16118
- // that is no longer last needs it back.
16119
- const joined = _orderJoinedBody(
16120
- kept.length === 0 || rest.length === 0
16121
- ? kept + rest
16122
- : _mergeJoinedBoxLonghands(`${kept};${rest}`),
16123
- before.plain && entry.plain
16124
- );
16731
+ ? earlier
16732
+ : earlier.filter((_, at) => !dropped.has(at));
16733
+ const added =
16734
+ later !== null
16735
+ ? later
16736
+ : rest.length === 0
16737
+ ? []
16738
+ : _blockDeclarations(rest);
16739
+ /** @type {PrintedDeclaration[] | null} */
16740
+ let declarations = null;
16741
+ let joined;
16742
+ // Carried as a list only where reading the joined text back would split it
16743
+ // the same way, which a quote the split reads as left open would not.
16744
+ if (
16745
+ added === null ||
16746
+ !_lastDeclarationCloses(added) ||
16747
+ (before.declarations === null && !_lastDeclarationCloses(earlier))
16748
+ ) {
16749
+ const keptText = kept.map((one) => one.text).join(";");
16750
+ joined = _orderJoinedBody(
16751
+ keptText.length === 0 || rest.length === 0
16752
+ ? keptText + rest
16753
+ : _mergeJoinedBody(`${keptText};${rest}`),
16754
+ before.plain && entry.plain
16755
+ );
16756
+ } else {
16757
+ declarations = _orderJoinedDeclarations(
16758
+ kept.length === 0 || added.length === 0
16759
+ ? [...kept, ...added]
16760
+ : _mergeJoinedBoxLonghands([...kept, ...added]),
16761
+ before.plain && entry.plain
16762
+ );
16763
+ // Each block already dropped the `;` its own `}` made redundant, so the
16764
+ // one that is no longer last needs it back.
16765
+ joined = declarations.map((one) => one.text).join(";");
16766
+ }
16125
16767
  return {
16126
16768
  text: `${beforePrelude}{${joined}}`,
16127
16769
  prelude: before.prelude,
@@ -16136,7 +16778,10 @@ const _joinRuleEntries = (before, entry, owned) => {
16136
16778
  head: "",
16137
16779
  // The two preludes are equal, so the list this keeps is the one `before`
16138
16780
  // already carried — settled or not.
16139
- unordered: before.unordered
16781
+ unordered: before.unordered,
16782
+ list: beforePrelude,
16783
+ block: null,
16784
+ declarations
16140
16785
  };
16141
16786
  }
16142
16787
  if (!blocksEqual || !_entryListable(before) || !_entryListable(entry)) {
@@ -16145,12 +16790,11 @@ const _joinRuleEntries = (before, entry, owned) => {
16145
16790
  // Concatenated, never canonicalized here: a run of joins would then re-read
16146
16791
  // the whole list once per rule. It is noted as a list still to settle, and
16147
16792
  // the run that grew it orders it once where it ends.
16148
- const list = `${before.text.slice(0, before.prelude)},${entry.text.slice(
16149
- 0,
16150
- entry.prelude
16151
- )}`;
16793
+ const list = `${_entryPrelude(before)},${_entryPrelude(entry)}`;
16794
+ const block =
16795
+ before.block === null ? before.text.slice(before.prelude) : before.block;
16152
16796
  return {
16153
- text: list + before.text.slice(before.prelude),
16797
+ text: list + block,
16154
16798
  prelude: list.length,
16155
16799
  atRule: false,
16156
16800
  plain: true,
@@ -16158,13 +16802,30 @@ const _joinRuleEntries = (before, entry, owned) => {
16158
16802
  listKind: before.listKind,
16159
16803
  children: null,
16160
16804
  head: "",
16161
- unordered: true
16805
+ unordered: true,
16806
+ list,
16807
+ block,
16808
+ declarations: null
16162
16809
  };
16163
16810
  };
16164
16811
 
16165
16812
  /**
16166
- * A joined style rule's body in the order `_declarationOrder` prints one in,
16167
- * which each half was already in but the two concatenated need not be.
16813
+ * `_mergeJoinedBoxLonghands` over a joined block's text.
16814
+ * @param {string} body the joined block's declarations
16815
+ * @returns {string} the body, each family it could merge merged
16816
+ */
16817
+ const _mergeJoinedBody = (body) => {
16818
+ if (!_S._transforms.mergeLonghands) return body;
16819
+ const declarations = _blockDeclarations(body);
16820
+ if (declarations === null) return body;
16821
+ const merged = _mergeJoinedBoxLonghands(declarations);
16822
+ return merged === declarations
16823
+ ? body
16824
+ : merged.map((one) => one.text).join(";");
16825
+ };
16826
+
16827
+ /**
16828
+ * `_orderJoinedDeclarations` over a joined block's text.
16168
16829
  * @param {string} body the joined body, its braces excluded
16169
16830
  * @param {boolean} plain whether it holds declarations and nothing else
16170
16831
  * @returns {string} the body, reordered where that is allowed
@@ -16172,10 +16833,64 @@ const _joinRuleEntries = (before, entry, owned) => {
16172
16833
  const _orderJoinedBody = (body, plain) => {
16173
16834
  if (!plain || !_S._transforms.reorderDeclarations) return body;
16174
16835
  const declarations = _blockDeclarations(body);
16175
- if (declarations === null || declarations.length < 2) return body;
16176
- const texts = declarations.map((one) => one.text);
16177
- const order = _declarationOrder(texts);
16178
- return order === null ? body : order.map((at) => texts[at]).join(";");
16836
+ if (declarations === null) return body;
16837
+ const ordered = _orderJoinedDeclarations(declarations, plain);
16838
+ return ordered === declarations
16839
+ ? body
16840
+ : ordered.map((one) => one.text).join(";");
16841
+ };
16842
+
16843
+ /**
16844
+ * Whether the last of a printed block's declarations leaves the split that read
16845
+ * them where it started: outside a string and every bracket. Only the last can
16846
+ * end elsewhere, since the split parts the others only there.
16847
+ * @param {PrintedDeclaration[]} declarations the declarations, in order
16848
+ * @returns {boolean} true when a text joined after them is read on its own
16849
+ */
16850
+ const _lastDeclarationCloses = (declarations) => {
16851
+ if (declarations.length === 0) return true;
16852
+ const text = declarations[declarations.length - 1].text;
16853
+ let depth = 0;
16854
+ let quote = 0;
16855
+ for (let i = 0; i < text.length; i++) {
16856
+ const cc = text.charCodeAt(i);
16857
+ if (quote !== 0) {
16858
+ if (cc === CC_REVERSE_SOLIDUS) {
16859
+ i++;
16860
+ } else if (cc === quote) {
16861
+ quote = 0;
16862
+ }
16863
+ } else if (cc === CC_QUOTATION_MARK || cc === CC_APOSTROPHE) {
16864
+ quote = cc;
16865
+ } else if (
16866
+ cc === CC_LEFT_PARENTHESIS ||
16867
+ cc === CC_LEFT_SQUARE ||
16868
+ cc === CC_LEFT_CURLY
16869
+ ) {
16870
+ depth++;
16871
+ } else if (
16872
+ cc === CC_RIGHT_PARENTHESIS ||
16873
+ cc === CC_RIGHT_SQUARE ||
16874
+ cc === CC_RIGHT_CURLY
16875
+ ) {
16876
+ depth--;
16877
+ }
16878
+ }
16879
+ return quote === 0 && depth === 0;
16880
+ };
16881
+
16882
+ /**
16883
+ * A joined style rule's declarations in the order `_declarationOrder` prints
16884
+ * one in, which each half was already in but the two concatenated need not be.
16885
+ * @param {PrintedDeclaration[]} declarations the joined block's declarations
16886
+ * @param {boolean} plain whether it holds declarations and nothing else
16887
+ * @returns {PrintedDeclaration[]} them, reordered where that is allowed
16888
+ */
16889
+ const _orderJoinedDeclarations = (declarations, plain) => {
16890
+ if (!plain || !_S._transforms.reorderDeclarations) return declarations;
16891
+ if (declarations.length < 2) return declarations;
16892
+ const order = _declarationOrder(declarations.map((one) => one.text));
16893
+ return order === null ? declarations : order.map((at) => declarations[at]);
16179
16894
  };
16180
16895
 
16181
16896
  // The group rules whose declarations, nested in a style rule, are the implicit
@@ -16237,7 +16952,7 @@ const _noteStreamedLayerClose = (d, opener, at) => {
16237
16952
  layers.set(opener, {
16238
16953
  at,
16239
16954
  subtree: false,
16240
- taken: { piece: at, text: "}", spans: [], empties: -1 }
16955
+ taken: new TakenPiece(at, "}", -1)
16241
16956
  });
16242
16957
  };
16243
16958
 
@@ -16500,7 +17215,7 @@ const _nothingBetweenShadows = (items, texts, from, to, declared, subjects) => {
16500
17215
  // selectors and pseudos are gone: what either holds may look like a combinator.
16501
17216
  const _SUBJECT_STRIP_RE = new RegExp(
16502
17217
  `${_ATTRIBUTE}|${_PSEUDO_CLASS}|${_PSEUDO_ELEMENT}`,
16503
- "gi"
17218
+ "g"
16504
17219
  );
16505
17220
  const _SUBJECT_TYPE_RE = /(?:^|[\s>+~])([-\w\u00A0-\uFFFF]+)[^\s>+~]*$/;
16506
17221
 
@@ -16532,11 +17247,11 @@ const _subjectTypes = (prelude) => {
16532
17247
  * @returns {Set<string> | null} the type names
16533
17248
  */
16534
17249
  const _entrySubjectTypes = (entry) => {
16535
- if (!entry.atRule) return _subjectTypes(entry.text.slice(0, entry.prelude));
17250
+ if (!entry.atRule) return _subjectTypes(_entryPrelude(entry));
16536
17251
  /** @type {Set<string>} */
16537
17252
  const out = new Set();
16538
17253
  for (const child of /** @type {RuleEntry[]} */ (entry.children)) {
16539
- const types = _subjectTypes(child.text.slice(0, child.prelude));
17254
+ const types = _subjectTypes(_entryPrelude(child));
16540
17255
  if (types === null) return null;
16541
17256
  for (const type of types) out.add(type);
16542
17257
  }
@@ -16565,7 +17280,7 @@ const _subjectsDisjoint = (a, b) => {
16565
17280
  * @returns {boolean} true where its block may be read for what it declares
16566
17281
  */
16567
17282
  const _readableCondition = (entry) => {
16568
- const name = _atRuleName(entry.text.slice(0, entry.prelude));
17283
+ const name = _atRuleName(_entryPrelude(entry));
16569
17284
  return name !== "layer" && MERGEABLE_AT_RULES.has(name);
16570
17285
  };
16571
17286
 
@@ -16604,7 +17319,7 @@ const _atRuleDeclaredProperties = (entry) => {
16604
17319
  * @returns {void}
16605
17320
  */
16606
17321
  const _mergeDistantAtRule = (items, texts, held, entry, at) => {
16607
- const prelude = entry.text.slice(0, entry.prelude);
17322
+ const prelude = _entryPrelude(entry);
16608
17323
  // WHY: a layer's place in the cascade is where its name is first written, not
16609
17324
  // what its prelude states, so two blocks naming one are not the pair of equal
16610
17325
  // conditions this joins. `_mergeNamedLayerBlocks` gathers those under the rule
@@ -16712,7 +17427,12 @@ const _dropRulesWrittenAgain = (items, texts) => {
16712
17427
  /** @type {Map<string, number[]> | null} */
16713
17428
  let held = null;
16714
17429
  for (let i = 0; i < items.length; i++) {
16715
- if (texts[i].length === 0) continue;
17430
+ if (
17431
+ texts[i].length === 0 ||
17432
+ _types[_nodeIndex(items[i])] === NodeType.Declaration
17433
+ ) {
17434
+ continue;
17435
+ }
16716
17436
  const entry = _ruleEntryOf(items[i], texts[i]);
16717
17437
  // Keyframes of one selector are not one cascade in every engine: an older
16718
17438
  // one reads only the last block written for it.
@@ -16724,7 +17444,7 @@ const _dropRulesWrittenAgain = (items, texts) => {
16724
17444
  ) {
16725
17445
  continue;
16726
17446
  }
16727
- const prelude = entry.text.slice(0, entry.prelude);
17447
+ const prelude = _entryPrelude(entry);
16728
17448
  if (held === null) held = new Map();
16729
17449
  const earlier = held.get(prelude);
16730
17450
  if (earlier === undefined) {
@@ -17106,17 +17826,19 @@ const _canonicalSelectorList = (prelude) => {
17106
17826
  if (!prelude.includes(",")) return prelude;
17107
17827
  const list = _splitSelectorList(prelude);
17108
17828
  if (list.length < 2) return prelude;
17109
- // One pass answers both questions the common case turns on — every selector
17110
- // distinct, and already in order — so the list is walked once rather than
17111
- // twice before being handed back untouched.
17112
- const seen = new Set();
17113
- let sorted = true;
17114
- for (let i = 0; i < list.length; i++) {
17115
- seen.add(list[i]);
17116
- if (i !== 0 && _compareSelectors(list[i - 1], list[i]) > 0) sorted = false;
17829
+ // Strictly increasing answers both questions the common case turns on —
17830
+ // every selector distinct, and already in order — without a set: only two
17831
+ // equal selectors compare as zero.
17832
+ let i = 1;
17833
+ while (i < list.length && _compareSelectors(list[i - 1], list[i]) < 0) i++;
17834
+ if (i === list.length) return prelude;
17835
+ list.sort(_compareSelectors);
17836
+ let kept = 1;
17837
+ for (let j = 1; j < list.length; j++) {
17838
+ if (list[j] !== list[kept - 1]) list[kept++] = list[j];
17117
17839
  }
17118
- if (seen.size === list.length && sorted) return prelude;
17119
- return [...seen].sort(_compareSelectors).join(",");
17840
+ list.length = kept;
17841
+ return list.join(",");
17120
17842
  };
17121
17843
 
17122
17844
  /**
@@ -17155,10 +17877,13 @@ const _settleJoinedSeams = (entry) => {
17155
17877
  const head = _joinedHead(settled);
17156
17878
  return {
17157
17879
  ...entry,
17158
- text: _joinedBlockText(entry.text.slice(0, entry.prelude), head, settled),
17880
+ text: _joinedBlockText(_entryPrelude(entry), head, settled),
17159
17881
  children: settled,
17160
17882
  head,
17161
- unordered: false
17883
+ unordered: false,
17884
+ list: null,
17885
+ block: null,
17886
+ declarations: null
17162
17887
  };
17163
17888
  };
17164
17889
 
@@ -17173,7 +17898,7 @@ const _settleJoinedSeams = (entry) => {
17173
17898
  const _canonicalizeJoined = (entry) => {
17174
17899
  if (entry.atRule) return _settleJoinedSeams(entry);
17175
17900
  if (entry.prelude === -1) return entry;
17176
- const prelude = entry.text.slice(0, entry.prelude);
17901
+ const prelude = _entryPrelude(entry);
17177
17902
  const canonical = _canonicalSelectorList(prelude);
17178
17903
  if (canonical === prelude) {
17179
17904
  entry.unordered = false;
@@ -17183,7 +17908,10 @@ const _canonicalizeJoined = (entry) => {
17183
17908
  ...entry,
17184
17909
  text: canonical + entry.text.slice(entry.prelude),
17185
17910
  prelude: canonical.length,
17186
- unordered: false
17911
+ unordered: false,
17912
+ list: null,
17913
+ block: null,
17914
+ declarations: null
17187
17915
  };
17188
17916
  };
17189
17917
 
@@ -17410,9 +18138,7 @@ const _rulePrelude = (path, writer, minify, outParts) => {
17410
18138
  }
17411
18139
  const list = _canonicalSelectorList(prelude);
17412
18140
  if (_readsThroughGoneEngine(list)) {
17413
- /** @type {Set<Node>} */ (_unusedRules).add(
17414
- /** @type {Node} */ (_S._currentNode)
17415
- );
18141
+ /** @type {Set<Node>} */ (_unusedRules).add(_S._currentNode);
17416
18142
  return list;
17417
18143
  }
17418
18144
  if (_unusedSymbols === null) return list;
@@ -17420,9 +18146,7 @@ const _rulePrelude = (path, writer, minify, outParts) => {
17420
18146
  // Marked rather than dropped here: the rule's own printer takes it out, the
17421
18147
  // same way it takes out one whose block ends up empty.
17422
18148
  if (kept !== null) return kept;
17423
- /** @type {Set<Node>} */ (_unusedRules).add(
17424
- /** @type {Node} */ (_S._currentNode)
17425
- );
18149
+ /** @type {Set<Node>} */ (_unusedRules).add(_S._currentNode);
17426
18150
  return list;
17427
18151
  }
17428
18152
  if (
@@ -17435,9 +18159,7 @@ const _rulePrelude = (path, writer, minify, outParts) => {
17435
18159
  unescapeIdentifier(prelude.slice(_rawName(path.node).length + 1).trim())
17436
18160
  )
17437
18161
  ) {
17438
- /** @type {Set<Node>} */ (_unusedRules).add(
17439
- /** @type {Node} */ (_S._currentNode)
17440
- );
18162
+ /** @type {Set<Node>} */ (_unusedRules).add(_S._currentNode);
17441
18163
  }
17442
18164
  return prelude;
17443
18165
  };
@@ -17904,6 +18626,39 @@ const _canonicalProperty = (property) => {
17904
18626
  const _orderMatters = (a, b) => {
17905
18627
  if (a === b || a === "all" || b === "all") return true;
17906
18628
  if (a.startsWith("--") || b.startsWith("--")) return false;
18629
+ let row = _orderMattersMemo.get(a);
18630
+ let matters = row === undefined ? undefined : row.get(b);
18631
+ if (matters !== undefined) return matters;
18632
+ matters = _computeOrderMatters(a, b);
18633
+ // The names are the author's, so a sheet of made-up ones starts again.
18634
+ if (_orderMattersEntries === _ORDER_MATTERS_ENTRIES) {
18635
+ _orderMattersMemo.clear();
18636
+ _orderMattersEntries = 0;
18637
+ row = undefined;
18638
+ }
18639
+ if (row === undefined) {
18640
+ row = new Map();
18641
+ _orderMattersMemo.set(a, row);
18642
+ }
18643
+ row.set(b, matters);
18644
+ _orderMattersEntries++;
18645
+ return matters;
18646
+ };
18647
+
18648
+ // `_orderMatters` by its first property, then its second: a sheet names few
18649
+ // properties, and the walks back over a block ask of the same pairs again.
18650
+ /** @type {Map<string, Map<string, boolean>>} */
18651
+ const _orderMattersMemo = new Map();
18652
+ let _orderMattersEntries = 0;
18653
+ const _ORDER_MATTERS_ENTRIES = 1 << 15;
18654
+
18655
+ /**
18656
+ * `_orderMatters` without the memo.
18657
+ * @param {string} a a property, read through `_canonicalProperty`
18658
+ * @param {string} b another
18659
+ * @returns {boolean} true when the two must keep their order
18660
+ */
18661
+ const _computeOrderMatters = (a, b) => {
17907
18662
  // A corner's radius is no side's: `border-bottom` sets no `-radius` it starts.
17908
18663
  if (
17909
18664
  a.charCodeAt(0) !== CC_HYPHEN_MINUS &&
@@ -17912,7 +18667,7 @@ const _orderMatters = (a, b) => {
17912
18667
  ) {
17913
18668
  return false;
17914
18669
  }
17915
- if (a.startsWith(`${b}-`) || b.startsWith(`${a}-`)) return true;
18670
+ if (_extendsProperty(a, b) || _extendsProperty(b, a)) return true;
17916
18671
  // A vendor property no standard one aliases says nothing of what it sets
17917
18672
  // beyond its words: `-webkit-border-before` is a border side.
17918
18673
  if (
@@ -17938,6 +18693,18 @@ const _orderMatters = (a, b) => {
17938
18693
  );
17939
18694
  };
17940
18695
 
18696
+ /**
18697
+ * Whether a property name is another's followed by more words: `margin-top` of
18698
+ * `margin`. Read in place, as a `${base}-` prefix would be built on every call.
18699
+ * @param {string} name a property
18700
+ * @param {string} base another
18701
+ * @returns {boolean} true when `name` starts with `base` and a `-`
18702
+ */
18703
+ const _extendsProperty = (name, base) =>
18704
+ name.length > base.length &&
18705
+ name.charCodeAt(base.length) === CC_HYPHEN_MINUS &&
18706
+ name.startsWith(base);
18707
+
17941
18708
  // A box side, corner or axis a property names, physical or flow-relative.
17942
18709
  const _PHYSICAL_SIDE_RE =
17943
18710
  /-(?:top|right|bottom|left|x|y)(?=-|$)|^(?:(?:min|max)-)?(?:top|right|bottom|left|width|height)$|-(?:width|height)$/;
@@ -17967,6 +18734,11 @@ const _blocksMerge = (between, later, families) => {
17967
18734
  );
17968
18735
  };
17969
18736
 
18737
+ // The properties `_sideRoot` reads as `inset`'s and as `size`'s sides.
18738
+ const _INSET_SIDE_RE = /^(?:top|right|bottom|left)$/;
18739
+ const _SIZE_PROPERTY_RE =
18740
+ /^(?:(?:min|max)-)?(?:width|height|inline-size|block-size)$/;
18741
+
17970
18742
  /**
17971
18743
  * The word a property's sides share: `margin` for `margin-left` and
17972
18744
  * `margin-inline-start`, `inset` for `top`, `size` for `width` and `inline-size`.
@@ -17974,11 +18746,12 @@ const _blocksMerge = (between, later, families) => {
17974
18746
  * @returns {string} that word
17975
18747
  */
17976
18748
  const _sideRoot = (property) => {
17977
- const bare = property.replace(_VENDOR_ITEM_PREFIX_RE, "");
17978
- if (/^(?:top|right|bottom|left)$/.test(bare)) return "inset";
17979
- if (/^(?:(?:min|max)-)?(?:width|height|inline-size|block-size)$/.test(bare)) {
17980
- return "size";
17981
- }
18749
+ const bare =
18750
+ property.charCodeAt(0) === CC_HYPHEN_MINUS
18751
+ ? property.replace(_VENDOR_ITEM_PREFIX_RE, "")
18752
+ : property;
18753
+ if (_INSET_SIDE_RE.test(bare)) return "inset";
18754
+ if (_SIZE_PROPERTY_RE.test(bare)) return "size";
17982
18755
  const dash = bare.indexOf("-");
17983
18756
  return dash === -1 ? bare : bare.slice(0, dash);
17984
18757
  };
@@ -18031,9 +18804,26 @@ const _declarationOrder = (texts) => {
18031
18804
  rankOf.push(rank);
18032
18805
  }
18033
18806
  if (!ranked) return null;
18034
- const order = texts
18035
- .map((_, i) => i)
18036
- .sort((a, b) => rankOf[a] - rankOf[b] || a - b);
18807
+ // Already in rank order, which half of all blocks are: the stable sort would
18808
+ // hand every index back where it stands.
18809
+ let inOrder = true;
18810
+ for (let i = 1; i < rankOf.length && inOrder; i++) {
18811
+ if (rankOf[i] < rankOf[i - 1]) inOrder = false;
18812
+ }
18813
+ if (inOrder) return null;
18814
+ // A stable insertion sort by rank: blocks are short, and this allocates no
18815
+ // comparator and no copy as `Array#sort` does.
18816
+ /** @type {number[]} */
18817
+ const order = [];
18818
+ for (let i = 0; i < rankOf.length; i++) {
18819
+ let at = order.length;
18820
+ order.push(i);
18821
+ while (at > 0 && rankOf[order[at - 1]] > rankOf[i]) {
18822
+ order[at] = order[at - 1];
18823
+ at--;
18824
+ }
18825
+ order[at] = i;
18826
+ }
18037
18827
  let moved = false;
18038
18828
  for (let i = 0; i < order.length; i++) {
18039
18829
  if (order[i] !== i) moved = true;
@@ -18227,7 +19017,8 @@ const _composeBlockBody = (
18227
19017
  for (const node of held) pendingRules.add(node);
18228
19018
  }
18229
19019
  }
18230
- if (minify) {
19020
+ // A block of declarations alone, most of them, holds no rule to join or drop.
19021
+ if (minify && items !== decls) {
18231
19022
  // Before the joins, so a rule it takes out stands in none of their way;
18232
19023
  // and again after any join, which can write a rule again in its turn.
18233
19024
  if (_S._transforms.removeDeadRules) {
@@ -18430,6 +19221,7 @@ const _composeBlockBody = (
18430
19221
  for (let i = 0; i < properties.length; i++) {
18431
19222
  if (properties[i].length === 0) continue;
18432
19223
  present.add(properties[i]);
19224
+ (_isImportant(items[i]) ? importantAt : plainAt).set(properties[i], i);
18433
19225
  // A value is only ever asked for against its own property, so the
18434
19226
  // two share one set.
18435
19227
  if (values[i].length !== 0) {
@@ -18488,7 +19280,32 @@ const _composeBlockBody = (
18488
19280
  ? _prefixedPropertyName(property)
18489
19281
  : undefined;
18490
19282
  if (base !== undefined) {
18491
- if (present.has(base) && _prefixedPropertyRemovable(base, property)) {
19283
+ // An engine still reading the vendor name as an alias cascades the
19284
+ // two as one property, so it is dead only behind a twin that wins.
19285
+ const importantBase = importantAt.get(base);
19286
+ const plainBase = plainAt.get(base);
19287
+ const important = _isImportant(items[i]);
19288
+ const baseWins = important
19289
+ ? importantBase !== undefined && importantBase > i
19290
+ : importantBase !== undefined ||
19291
+ (plainBase !== undefined && plainBase > i);
19292
+ // ...or behind one of the same importance saying the same thing.
19293
+ const twinAt = important ? importantBase : plainBase;
19294
+ const sameTwin =
19295
+ twinAt !== undefined &&
19296
+ (important || importantBase === undefined) &&
19297
+ texts[twinAt].slice(
19298
+ base.length + 1,
19299
+ _printedValueEnd(texts[twinAt], base.length)
19300
+ ) ===
19301
+ texts[i].slice(
19302
+ property.length + 1,
19303
+ _printedValueEnd(texts[i], property.length)
19304
+ );
19305
+ if (
19306
+ (baseWins || sameTwin) &&
19307
+ _prefixedPropertyRemovable(base, property)
19308
+ ) {
18492
19309
  if (droppedPrefix === null) droppedPrefix = new Set();
18493
19310
  droppedPrefix.add(i);
18494
19311
  continue;
@@ -18588,6 +19405,10 @@ const _composeBlockBody = (
18588
19405
  : null;
18589
19406
  /** @type {string[]} */
18590
19407
  const pieceTexts = [];
19408
+ // The body before its last piece, and that piece: the `;` a `}` makes
19409
+ // redundant is cut off the piece, as reading the body's end would flatten it.
19410
+ let prior = "";
19411
+ let last = "";
18591
19412
  for (let i = 0; i < texts.length; i++) {
18592
19413
  const isBlock =
18593
19414
  bodies !== 0 && path.type(items[i]) !== NodeType.Declaration;
@@ -18630,6 +19451,8 @@ const _composeBlockBody = (
18630
19451
  }
18631
19452
  body += nl;
18632
19453
  const at = body.length;
19454
+ prior = body;
19455
+ last = text;
18633
19456
  body += text;
18634
19457
  if (minify && isBlock) {
18635
19458
  if (spans === null) spans = [];
@@ -18639,18 +19462,15 @@ const _composeBlockBody = (
18639
19462
  if (pieces !== null) {
18640
19463
  const order = _declarationOrder(pieceTexts);
18641
19464
  for (let i = 0; i < pieces.length; i++) {
18642
- body += pieces[order === null ? i : order[i]];
19465
+ prior = body;
19466
+ last = pieces[order === null ? i : order[i]];
19467
+ body += last;
18643
19468
  }
18644
19469
  }
18645
19470
  // Drop the redundant `;` a following `}` — or, at top level, the end of the
18646
19471
  // input — makes so, when minifying.
18647
- if (minify) {
18648
- while (
18649
- body.length !== 0 &&
18650
- body.charCodeAt(body.length - 1) === CC_SEMICOLON
18651
- ) {
18652
- body = body.slice(0, -1);
18653
- }
19472
+ if (minify && last.charCodeAt(last.length - 1) === CC_SEMICOLON) {
19473
+ body = prior + last.slice(0, -1);
18654
19474
  }
18655
19475
 
18656
19476
  return {
@@ -19199,7 +20019,7 @@ const _S = {
19199
20019
  // The walk's current position (`A.node` / `A.parent` read these; module-level
19200
20020
  // so the accessor methods' defaults avoid self-referential `this` typing).
19201
20021
  /** @type {Node} */
19202
- _currentNode: /** @type {Node} */ (/** @type {unknown} */ (0)),
20022
+ _currentNode: /** @type {Node} */ (/** @type {Node | number} */ (0)),
19203
20023
  // The declaration `_inKeywordOnlyValue` last answered for, and its answer: one
19204
20024
  // value's identifiers all ask it, and the read is a pointer compare after the
19205
20025
  // first. Cleared with the rest of the parse state, so no node outlives it.
@@ -19499,10 +20319,10 @@ const A = {
19499
20319
  field(i, field, n = _S._currentNode) {
19500
20320
  const k = _nodeIndex(n);
19501
20321
  if (i < 0 || i >= A.fieldCount(field, n)) {
19502
- return /** @type {Node} */ (_nodeRef(0));
20322
+ return _nodeRef(0);
19503
20323
  }
19504
20324
  if (_LIST_FIELD[_types[k]] === field) {
19505
- return /** @type {Node} */ (_nodeRef(_flat[_listStarts[k] + i]));
20325
+ return _nodeRef(_flat[_listStarts[k] + i]);
19506
20326
  }
19507
20327
  return (field === Field.declarations ? _declBodies : _ruleBodies)[
19508
20328
  _bodyIdx[k] - 1
@@ -19519,7 +20339,7 @@ const A = {
19519
20339
  const item = A.field(i, field, n);
19520
20340
  if (A.name(item) === name) return item;
19521
20341
  }
19522
- return /** @type {Node} */ (_nodeRef(0));
20342
+ return _nodeRef(0);
19523
20343
  },
19524
20344
  /**
19525
20345
  * `important` is a container's bit, cleared when its id is reused; a token's
@@ -19698,7 +20518,13 @@ const _collectRuleSpans = (spans, item, text, at, path, inner) => {
19698
20518
  // it — the parent first, so a cut that takes the whole rule is seen before
19699
20519
  // anything inside it.
19700
20520
  if (path.type(item) === NodeType.QualifiedRule) {
19701
- spans.push({ scope: _rootScope(), key: text, at, len: text.length });
20521
+ spans.push({
20522
+ scope: _rootScope(),
20523
+ key: text,
20524
+ at,
20525
+ len: text.length,
20526
+ taken: null
20527
+ });
19702
20528
  } else if (
19703
20529
  inner !== undefined &&
19704
20530
  inner.prelude.charCodeAt(0) === CC_AT_SIGN &&
@@ -19706,7 +20532,13 @@ const _collectRuleSpans = (spans, item, text, at, path, inner) => {
19706
20532
  ) {
19707
20533
  // Not a rule of its own — a condition its body is read under — so it is
19708
20534
  // noted only to be cut once the cuts inside it have left it empty.
19709
- spans.push({ scope: _rootScope(), key: UNKEYED, at, len: text.length });
20535
+ spans.push({
20536
+ scope: _rootScope(),
20537
+ key: UNKEYED,
20538
+ at,
20539
+ len: text.length,
20540
+ taken: null
20541
+ });
19710
20542
  }
19711
20543
  if (inner === undefined) return;
19712
20544
  const base = at + inner.bodyAt;
@@ -19715,7 +20547,8 @@ const _collectRuleSpans = (spans, item, text, at, path, inner) => {
19715
20547
  scope: _enclosingRuleScope(span.scope, inner.keyPrelude),
19716
20548
  key: span.key,
19717
20549
  at: base + span.at,
19718
- len: span.len
20550
+ len: span.len,
20551
+ taken: null
19719
20552
  });
19720
20553
  }
19721
20554
  };
@@ -19764,6 +20597,7 @@ module.exports._NUMERIC_RE = _NUMERIC_RE;
19764
20597
  module.exports._atKeywordOf = _atKeywordOf;
19765
20598
  module.exports._printedSource = _printedSource;
19766
20599
  module.exports._rawName = _rawName;
20600
+ module.exports._setVerbatimLeafPrinter = setVerbatimLeafPrinter;
19767
20601
  module.exports._trimCssWhiteSpace = _trimCssWhiteSpace;
19768
20602
  module.exports._declarationsOf = _declarationsOf;
19769
20603
  module.exports._rulesOf = _rulesOf;
@@ -19773,6 +20607,33 @@ module.exports._S = _S;
19773
20607
  module.exports._SEP = _SEP;
19774
20608
  module.exports._keepKeywordComments = _keepKeywordComments;
19775
20609
  module.exports._TRIM_COMBINATORS = _TRIM_COMBINATORS;
20610
+ /**
20611
+ * The body a reused block composes to, for the rule being printed.
20612
+ * @param {Node} node the rule
20613
+ * @returns {string | undefined} the body, or undefined to compose it
20614
+ */
20615
+ module.exports._memoizedBlockBody = (node) =>
20616
+ _blockMemoNode === node ? _blockMemoBody : undefined;
20617
+ /**
20618
+ * Done with the rule the walk looked a block up for: keep the body it composed
20619
+ * where the walk asked for one, and forget the rule — node ids are reused.
20620
+ * @param {Node} node the rule
20621
+ * @param {string | null} body its composed body, null to keep none
20622
+ * @returns {void}
20623
+ */
20624
+ module.exports._memoizeBlockBody = (node, body) => {
20625
+ if (
20626
+ _blockMemoNode === node &&
20627
+ body !== null &&
20628
+ _blockMemoKey !== "" &&
20629
+ _blockMemo !== null
20630
+ ) {
20631
+ _blockMemoKeyTable.set(_blockMemoKey, body);
20632
+ }
20633
+ _blockMemoNode = null;
20634
+ _blockMemoBody = undefined;
20635
+ _blockMemoKey = "";
20636
+ };
19776
20637
  module.exports._TRIM_CONDITIONS = _TRIM_CONDITIONS;
19777
20638
  module.exports._TRIM_MATH = _TRIM_MATH;
19778
20639
  module.exports._TRIM_NOTHING = _TRIM_NOTHING;