webpack 5.112.0 → 5.112.2

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 +198 -61
  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 -47
  28. package/lib/css/CssModule.js +10 -0
  29. package/lib/css/CssModulesPlugin.js +13 -6
  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 +1312 -312
  34. package/lib/css/syntax-printer.js +1142 -1025
  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 +4 -8
  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 +146 -29
  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 +18 -19
  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[]} shared and empty until `_noteSpans` gives it its own */
5384
+ this.spans = _NO_RULE_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,29 @@ 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);
6008
- taken.spans.push(span);
6375
+ span.taken = taken;
6376
+ }
6377
+ // A copy its own size: a first push would give the list room for 17, and
6378
+ // the piece holds it until the sheet ends.
6379
+ if (taken.spans.length === 0) {
6380
+ taken.spans = [...spans];
6381
+ } else {
6382
+ for (const span of spans) taken.spans.push(span);
6009
6383
  }
6010
6384
  for (const span of spans) {
6011
6385
  if (span.key === UNKEYED) continue;
6012
6386
  const rules = span.scope.rules;
6013
6387
  const before = rules.get(span.key);
6014
- if (before !== undefined) _cutSpan(writer, before.taken, before.span);
6015
- rules.set(span.key, { taken, span });
6388
+ if (before !== undefined) {
6389
+ _cutSpan(writer, /** @type {TakenPiece} */ (before.taken), before);
6390
+ }
6391
+ rules.set(span.key, span);
6016
6392
  }
6017
6393
  // After every copy is taken back, so an earlier rule a cut here left flat is
6018
6394
  // read as flat. A distant merge moves a block up past others, out of this order.
6019
6395
  if (!_S._transforms.removeDeadRules || _mergingDistantRules) return;
6396
+ // TODO add an option to make this opt-in: over the CSS corpus it costs ~10% of
6397
+ // minify CPU for 248 of 2,456,527 gzip bytes.
6020
6398
  for (const span of spans) {
6021
6399
  if (span.key !== UNKEYED && span.len !== 0) {
6022
6400
  _shadowEarlierRule(writer, taken, span);
@@ -6032,6 +6410,29 @@ const _ROLLBACK_RE = /^revert(?:-[a-z]+)?$/i;
6032
6410
  // blocks are gathered into one without their rules noted, so none is there.
6033
6411
  const _NO_SHADOW_SCOPE_RE = /@(?:-[a-z]+-)?keyframes|@layer/i;
6034
6412
 
6413
+ // What each prior `_shadowEarlierRule` compared against declares, with the text
6414
+ // that was read from, so a prior a later rule writes nothing of is passed over.
6415
+ /** @type {Map<RuleSpan, { text: string, names: Set<string> }>} */
6416
+ const _shadowDeclared = new Map();
6417
+
6418
+ /**
6419
+ * The names a printed flat rule's declarations write, as `_cascadeName` reads
6420
+ * them.
6421
+ * @param {string} rule the rule's printed text
6422
+ * @param {number} open where its `{` sits
6423
+ * @returns {Set<string>} the names
6424
+ */
6425
+ const _declaredNames = (rule, open) => {
6426
+ /** @type {Set<string>} */
6427
+ const names = new Set();
6428
+ for (const declaration of _splitPrintedDeclarations(
6429
+ rule.slice(open + 1, -1)
6430
+ )) {
6431
+ if (declaration.indexOf(":") > 0) names.add(_cascadeName(declaration));
6432
+ }
6433
+ return names;
6434
+ };
6435
+
6035
6436
  /**
6036
6437
  * The declarations of a printed block body, split on the `;` between them; a
6037
6438
  * nested block is a piece of its own.
@@ -6114,24 +6515,30 @@ const _readWherever = (values, earlier) => {
6114
6515
  * @returns {void}
6115
6516
  */
6116
6517
  const _shadowEarlierRule = (writer, taken, span) => {
6117
- const text = taken.text.slice(span.at, span.at + span.len);
6118
- const open = text.indexOf("{");
6518
+ const whole = taken.text;
6519
+ const at = span.at;
6520
+ const brace = whole.indexOf("{", at);
6119
6521
  // Only a qualified rule: an at-rule is no one cascade. One a later cut
6120
6522
  // leaves flat is read as one, so every rule is noted, flat or not.
6121
6523
  if (
6122
- open <= 0 ||
6123
- text.charCodeAt(0) === CC_AT_SIGN ||
6124
- _NO_SHADOW_SCOPE_RE.test(span.scope.prefix)
6524
+ brace <= at ||
6525
+ brace >= at + span.len ||
6526
+ whole.charCodeAt(at) === CC_AT_SIGN ||
6527
+ span.scope.noShadow
6125
6528
  ) {
6126
6529
  return;
6127
6530
  }
6128
- const selector = text.slice(0, open);
6531
+ const open = brace - at;
6532
+ const selector = whole.slice(at, brace);
6129
6533
  const selectors = span.scope.selectors;
6130
- const priors = selectors.get(selector);
6131
- if (priors === undefined) {
6132
- selectors.set(selector, [{ taken, span }]);
6534
+ const noted = selectors.get(selector);
6535
+ // A lone prior is held bare: almost every selector is written once.
6536
+ if (noted === undefined) {
6537
+ selectors.set(selector, span);
6133
6538
  return;
6134
6539
  }
6540
+ const priors = Array.isArray(noted) ? noted : [noted];
6541
+ const text = whole.slice(at, at + span.len);
6135
6542
  // Per name, the later values and whether one every target reads is among
6136
6543
  // them, and whether one of those is `!important`.
6137
6544
  /** @type {Map<string, { values: Set<string>, read: boolean, important: boolean }>} */
@@ -6161,11 +6568,24 @@ const _shadowEarlierRule = (writer, taken, span) => {
6161
6568
  }
6162
6569
  }
6163
6570
  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
- );
6571
+ if (prior.len === 0) continue;
6572
+ const priorTaken = /** @type {TakenPiece} */ (prior.taken);
6573
+ const before = priorTaken.text.slice(prior.at, prior.at + prior.len);
6574
+ // A prior writing none of the names this rule writes keeps all it has, so
6575
+ // it is not split again for every later rule under its selector.
6576
+ let declared = _shadowDeclared.get(prior);
6577
+ if (declared === undefined || declared.text !== before) {
6578
+ declared = { text: before, names: _declaredNames(before, open) };
6579
+ _shadowDeclared.set(prior, declared);
6580
+ }
6581
+ let shared = false;
6582
+ for (const name of later.keys()) {
6583
+ if (declared.names.has(name)) {
6584
+ shared = true;
6585
+ break;
6586
+ }
6587
+ }
6588
+ if (!shared) continue;
6169
6589
  /* istanbul ignore if -- @preserve: a join grows a list only before its rule
6170
6590
  is noted unless distant merges are on, which skip this; kept as the guard */
6171
6591
  if (
@@ -6207,18 +6627,17 @@ const _shadowEarlierRule = (writer, taken, span) => {
6207
6627
  }
6208
6628
  if (!dropped) continue;
6209
6629
  if (kept.length === 0) {
6210
- _cutSpan(writer, prior.taken, prior.span);
6630
+ _cutSpan(writer, priorTaken, prior);
6211
6631
  continue;
6212
6632
  }
6213
- _rewriteSpan(
6214
- writer,
6215
- prior.taken,
6216
- prior.span,
6217
- `${selector}{${kept.join(";")}}`
6218
- );
6219
- _rekeyShrunkSpan(writer, prior.taken, prior.span);
6633
+ _rewriteSpan(writer, priorTaken, prior, `${selector}{${kept.join(";")}}`);
6634
+ _rekeyShrunkSpan(writer, priorTaken, prior);
6635
+ }
6636
+ if (priors === noted) {
6637
+ priors.push(span);
6638
+ } else {
6639
+ selectors.set(selector, [/** @type {RuleSpan} */ (noted), span]);
6220
6640
  }
6221
- priors.push({ taken, span });
6222
6641
  };
6223
6642
 
6224
6643
  /**
@@ -6315,15 +6734,7 @@ const _joinGatheredSeam = (writer, taken, at, entry) => {
6315
6734
  ) {
6316
6735
  return;
6317
6736
  }
6318
- if (
6319
- !_lendSelectorsTo(
6320
- writer,
6321
- taken,
6322
- before,
6323
- prelude,
6324
- first.text.slice(0, first.prelude)
6325
- )
6326
- ) {
6737
+ if (!_lendSelectorsTo(writer, taken, before, prelude, _entryPrelude(first))) {
6327
6738
  return;
6328
6739
  }
6329
6740
  _cutSpan(writer, taken, after);
@@ -6335,18 +6746,19 @@ const _joinGatheredSeam = (writer, taken, at, entry) => {
6335
6746
  * it, so this runs until none is left.
6336
6747
  * @param {PrintContext} writer the print context
6337
6748
  * @param {TakenPiece} taken the piece the cut was made in
6749
+ * @param {RuleSpan[]} held the rules that held what was cut, the only ones it can have emptied
6338
6750
  * @returns {void}
6339
6751
  */
6340
- const _cutEmptiedSpans = (writer, taken) => {
6752
+ const _cutEmptiedSpans = (writer, taken, held) => {
6341
6753
  if (!_S._transforms.removeDeadRules) return;
6342
6754
  // Every span is a rule dropped empty wherever it stands: a qualified rule, or
6343
6755
  // one of the at-rules `DROPPABLE_WHEN_EMPTY_AT_RULES` names (`_collectRuleSpans`).
6344
- for (const other of taken.spans) {
6756
+ for (const other of held) {
6345
6757
  const end = other.at + other.len;
6346
6758
  if (
6347
6759
  other.len > 2 &&
6348
- taken.text.charCodeAt(end - 1) === CC_RIGHT_CURLY &&
6349
- taken.text.charCodeAt(end - 2) === CC_LEFT_CURLY
6760
+ taken.charCodeAt(end - 1) === CC_RIGHT_CURLY &&
6761
+ taken.charCodeAt(end - 2) === CC_LEFT_CURLY
6350
6762
  ) {
6351
6763
  _cutSpan(writer, taken, other);
6352
6764
  return;
@@ -6369,17 +6781,17 @@ const _cutSpan = (writer, taken, span) => {
6369
6781
  // The rule stood last in its block, so the `;` in front of it is one the
6370
6782
  // printer drops itself — it goes with the rule rather than being left behind.
6371
6783
  if (
6372
- taken.text.charCodeAt(from + len) === CC_RIGHT_CURLY &&
6373
- taken.text.charCodeAt(from - 1) === CC_SEMICOLON
6784
+ taken.charCodeAt(from + len) === CC_RIGHT_CURLY &&
6785
+ taken.charCodeAt(from - 1) === CC_SEMICOLON
6374
6786
  ) {
6375
6787
  from -= 1;
6376
6788
  len += 1;
6377
6789
  }
6378
- taken.text = `${taken.text.slice(0, from)}${taken.text.slice(from + len)}`;
6379
- if (taken.empties !== -1 && taken.text.length === taken.empties + 1) {
6790
+ taken.cut(from, len);
6791
+ if (taken.empties !== -1 && taken.length === taken.empties + 1) {
6380
6792
  taken.text = "";
6381
6793
  }
6382
- writer.rewriteRetractable(taken.piece, taken.text);
6794
+ writer.deferRetractable(taken.piece, taken);
6383
6795
  const end = from + len;
6384
6796
  /** @type {RuleSpan[] | null} */
6385
6797
  let shrunk = null;
@@ -6401,8 +6813,8 @@ const _cutSpan = (writer, taken, span) => {
6401
6813
  span.len = 0;
6402
6814
  if (shrunk !== null) {
6403
6815
  for (const other of shrunk) _rekeyShrunkSpan(writer, taken, other);
6816
+ _cutEmptiedSpans(writer, taken, shrunk);
6404
6817
  }
6405
- _cutEmptiedSpans(writer, taken);
6406
6818
  };
6407
6819
 
6408
6820
  /**
@@ -6419,15 +6831,14 @@ const _rekeyShrunkSpan = (writer, taken, span) => {
6419
6831
  const rules = span.scope.rules;
6420
6832
  const other = rules.get(key);
6421
6833
  _rekeySpan(taken, span);
6422
- if (other === undefined || other.span === span || other.span.len === 0) {
6423
- return;
6424
- }
6834
+ if (other === undefined || other === span || other.len === 0) return;
6835
+ const otherTaken = /** @type {TakenPiece} */ (other.taken);
6425
6836
  const earlier =
6426
- other.taken.piece < taken.piece ||
6427
- (other.taken.piece === taken.piece && other.span.at < span.at);
6837
+ otherTaken.piece < taken.piece ||
6838
+ (otherTaken.piece === taken.piece && other.at < span.at);
6428
6839
  if (earlier) {
6429
- rules.set(key, { taken, span });
6430
- _cutSpan(writer, other.taken, other.span);
6840
+ rules.set(key, span);
6841
+ _cutSpan(writer, otherTaken, other);
6431
6842
  } else {
6432
6843
  _cutSpan(writer, taken, span);
6433
6844
  }
@@ -6647,13 +7058,13 @@ const _priorOf = (taken, entry) => {
6647
7058
  return null;
6648
7059
  }
6649
7060
  // 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.
7061
+ // that only restates it goes whatever its block holds. Whether they may is
7062
+ // asked of `entry` once a later rule prints the same block.
6651
7063
  return {
6652
7064
  taken,
6653
7065
  span,
6654
7066
  entry,
6655
7067
  prelude: entry.prelude,
6656
- listable: _entryListable(entry),
6657
7068
  grown: false
6658
7069
  };
6659
7070
  };
@@ -6682,12 +7093,9 @@ const _settlePrior = (writer) => {
6682
7093
  const _rekeySpan = (taken, span) => {
6683
7094
  if (span.key === UNKEYED) return;
6684
7095
  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
- }
7096
+ if (rules.get(span.key) === span) rules.delete(span.key);
6689
7097
  span.key = taken.text.slice(span.at, span.at + span.len);
6690
- if (!rules.has(span.key)) rules.set(span.key, { taken, span });
7098
+ if (!rules.has(span.key)) rules.set(span.key, span);
6691
7099
  };
6692
7100
 
6693
7101
  /**
@@ -6727,7 +7135,10 @@ const _spanEntry = (text, prelude) => ({
6727
7135
  listKind: LIST_KIND_SELECTOR,
6728
7136
  children: null,
6729
7137
  head: "",
6730
- unordered: false
7138
+ unordered: false,
7139
+ list: null,
7140
+ block: null,
7141
+ declarations: null
6731
7142
  });
6732
7143
 
6733
7144
  /**
@@ -6832,7 +7243,9 @@ const _joinsNeighbor = (writer, before, rule) => {
6832
7243
  return same && _foldsNeighborBody(writer, before, rule);
6833
7244
  }
6834
7245
  if (!same) {
6835
- if (!before.listable || !rule.listable) return false;
7246
+ if (!_entryListable(before.entry) || !_entryListable(rule.entry)) {
7247
+ return false;
7248
+ }
6836
7249
  const selector = tail.slice(second.at, second.at + rule.prelude);
6837
7250
  if (
6838
7251
  !_lendSelectorsTo(writer, before.taken, first, before.prelude, selector)
@@ -6918,14 +7331,11 @@ const _joinsPrior = (writer, prior, held) =>
6918
7331
  */
6919
7332
  const _joinsPriorEntry = (writer, prior, entry, parted) => {
6920
7333
  if (!_S._transforms.mergeRules) return false;
6921
- if (entry.prelude === -1 || entry.atRule || !_entryListable(entry)) {
6922
- return false;
6923
- }
7334
+ if (entry.prelude === -1 || entry.atRule) return false;
6924
7335
  // A kept comment between the two was written above the later rule, which is
6925
7336
  // read where it still stands rather than here, the flush having cleared it.
6926
7337
  if (parted) return false;
6927
7338
  const span = prior.span;
6928
- if (!prior.listable) return false;
6929
7339
  // Read where the rule now stands, never where it was taken: a cut inside its
6930
7340
  // block, or a list this already grew, leaves the entry it went out as stale.
6931
7341
  if (span.len <= prior.prelude) return false;
@@ -6942,6 +7352,8 @@ const _joinsPriorEntry = (writer, prior, entry, parted) => {
6942
7352
  ) {
6943
7353
  return false;
6944
7354
  }
7355
+ // After the blocks compare, which almost none do: the shape test is a regex.
7356
+ if (!_entryListable(entry) || !_entryListable(prior.entry)) return false;
6945
7357
  // The same rule twice is one rule: nothing stands between them, so the later
6946
7358
  // copy only restates what the earlier already says.
6947
7359
  if (
@@ -6963,7 +7375,7 @@ const _joinsPriorEntry = (writer, prior, entry, parted) => {
6963
7375
  prior.taken,
6964
7376
  span,
6965
7377
  prior.prelude,
6966
- entry.text.slice(0, entry.prelude)
7378
+ _entryPrelude(entry)
6967
7379
  )
6968
7380
  ) {
6969
7381
  return false;
@@ -7161,13 +7573,7 @@ const _mergeIntoDistantRule = (writer, entry) => {
7161
7573
  // grew is longer than the one that was taken.
7162
7574
  const preludeNow = span.len - block.length;
7163
7575
  if (
7164
- !_lendSelectorsTo(
7165
- writer,
7166
- taken,
7167
- span,
7168
- preludeNow,
7169
- entry.text.slice(0, entry.prelude)
7170
- )
7576
+ !_lendSelectorsTo(writer, taken, span, preludeNow, _entryPrelude(entry))
7171
7577
  ) {
7172
7578
  return false;
7173
7579
  }
@@ -7227,13 +7633,11 @@ const _orderDistantLists = (writer) => {
7227
7633
  * Merge the box families whose four longhands two joined blocks wrote between
7228
7634
  * them, as `_mergeBoxLonghands` does for one block, so the join prints what the
7229
7635
  * 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
7636
+ * @param {PrintedDeclaration[]} declarations the joined block's declarations
7637
+ * @returns {PrintedDeclaration[]} them, each family it could merge merged
7232
7638
  */
7233
- const _mergeJoinedBoxLonghands = (body) => {
7234
- if (!_S._transforms.mergeLonghands) return body;
7235
- const declarations = _blockDeclarations(body);
7236
- if (declarations === null) return body;
7639
+ const _mergeJoinedBoxLonghands = (declarations) => {
7640
+ if (!_S._transforms.mergeLonghands) return declarations;
7237
7641
  /** @type {Map<string, number>} */
7238
7642
  const at = new Map();
7239
7643
  /** @type {Set<string>} */
@@ -7285,13 +7689,29 @@ const _mergeJoinedBoxLonghands = (body) => {
7285
7689
  const box = _collapseBox(values);
7286
7690
  if (box === null) continue;
7287
7691
  if (texts === null) texts = declarations.map((one) => one.text);
7288
- texts[first] =
7289
- `${shorthand}:${_joinSlots(box)}${important ? "!important" : ""}`;
7692
+ texts[first] = `${shorthand}:${_joinSlots(box)}${
7693
+ important ? "!important" : ""
7694
+ }`;
7290
7695
  for (const index of indexes) if (index !== first) texts[index] = "";
7291
7696
  }
7292
- return texts === null
7293
- ? body
7294
- : texts.filter((text) => text.length !== 0).join(";");
7697
+ if (texts === null) return declarations;
7698
+ /** @type {PrintedDeclaration[]} */
7699
+ const out = [];
7700
+ for (let i = 0; i < texts.length; i++) {
7701
+ const text = texts[i];
7702
+ if (text.length === 0) continue;
7703
+ if (text === declarations[i].text) {
7704
+ out.push(declarations[i]);
7705
+ continue;
7706
+ }
7707
+ const colon = text.indexOf(":");
7708
+ out.push({
7709
+ property: text.slice(0, colon),
7710
+ value: text.slice(colon + 1),
7711
+ text
7712
+ });
7713
+ }
7714
+ return out;
7295
7715
  };
7296
7716
 
7297
7717
  /**
@@ -7304,7 +7724,7 @@ const _mergeJoinedBoxLonghands = (body) => {
7304
7724
  const _joinedAtRuleSpans = (merged) => {
7305
7725
  // An at-rule's entry is always built with its children, never `null`.
7306
7726
  const children = /** @type {RuleEntry[]} */ (merged.children);
7307
- const prelude = merged.text.slice(0, merged.prelude);
7727
+ const prelude = _entryPrelude(merged);
7308
7728
  /** @type {RuleSpan[]} */
7309
7729
  const spans = [];
7310
7730
  let at = 0;
@@ -7322,7 +7742,8 @@ const _joinedAtRuleSpans = (merged) => {
7322
7742
  scope: _rootScope(),
7323
7743
  key: child.text,
7324
7744
  at,
7325
- len: child.text.length
7745
+ len: child.text.length,
7746
+ taken: null
7326
7747
  });
7327
7748
  } else {
7328
7749
  const inner = _joinedAtRuleSpans(child);
@@ -7332,7 +7753,8 @@ const _joinedAtRuleSpans = (merged) => {
7332
7753
  scope: _rootScope(),
7333
7754
  key: UNKEYED,
7334
7755
  at,
7335
- len: child.text.length
7756
+ len: child.text.length,
7757
+ taken: null
7336
7758
  });
7337
7759
  }
7338
7760
  for (const span of inner.spans) {
@@ -7340,7 +7762,8 @@ const _joinedAtRuleSpans = (merged) => {
7340
7762
  scope: _enclosingRuleScope(span.scope, inner.keyPrelude),
7341
7763
  key: span.key,
7342
7764
  at: at + inner.bodyAt + span.at,
7343
- len: span.len
7765
+ len: span.len,
7766
+ taken: null
7344
7767
  });
7345
7768
  }
7346
7769
  }
@@ -7388,7 +7811,7 @@ const _joinedSpans = (before, beforeOwn, entry, own, merged) => {
7388
7811
  ) {
7389
7812
  return _WHOLE_TEXT_SPANS;
7390
7813
  }
7391
- const prelude = merged.text.slice(0, merged.prelude);
7814
+ const prelude = _entryPrelude(merged);
7392
7815
  const length = entry.text.length - entry.prelude - 2;
7393
7816
  const at = merged.text.length - 1 - length - (merged.prelude + 1);
7394
7817
  const shift = at + own.bodyAt - (entry.prelude + 1);
@@ -7399,7 +7822,8 @@ const _joinedSpans = (before, beforeOwn, entry, own, merged) => {
7399
7822
  scope: span.scope,
7400
7823
  key: span.key,
7401
7824
  at: span.at + shift,
7402
- len: span.len
7825
+ len: span.len,
7826
+ taken: null
7403
7827
  });
7404
7828
  }
7405
7829
  return {
@@ -7444,8 +7868,7 @@ const _takeTopLevel = (node, writer, offset, line, column) => {
7444
7868
  const at = writer.takeRetractable(node, offset, line, column, own);
7445
7869
  _settlePrior(writer);
7446
7870
  if (_topLevelRules !== null) {
7447
- /** @type {TakenPiece} */
7448
- const taken = { piece: at, text: own, spans: [], empties: -1 };
7871
+ const taken = new TakenPiece(at, own, -1);
7449
7872
  if (_prefixPieces === null) _prefixPieces = new Map();
7450
7873
  _prefixPieces.set(at, taken);
7451
7874
  _topLevelRules.push(taken);
@@ -7697,8 +8120,7 @@ const _walkTopLevel = (node, writer) => {
7697
8120
  _nodeCount = 0;
7698
8121
  _flatTop = 0;
7699
8122
  // Body indices recycle with node ids.
7700
- _declBodies.length = 0;
7701
- _ruleBodies.length = 0;
8123
+ _dropBodies();
7702
8124
  if (_walkStopped && writer === undefined) throw WALK_STOPPED;
7703
8125
  };
7704
8126
 
@@ -7712,8 +8134,7 @@ const _recycleTopLevel = (node) => {
7712
8134
  if (_flatTop > _flatPeak) _flatPeak = _flatTop;
7713
8135
  _nodeCount = 0;
7714
8136
  _flatTop = 0;
7715
- _declBodies.length = 0;
7716
- _ruleBodies.length = 0;
8137
+ _dropBodies();
7717
8138
  };
7718
8139
 
7719
8140
  // The store buffers grow to the largest single top-level rule ever parsed and
@@ -8048,6 +8469,21 @@ const grammar = (input, visitors, writer, options) => {
8048
8469
  }
8049
8470
  _visitors = visitors;
8050
8471
  _commentBucket = visitors[NodeType.Comment];
8472
+ _blockMemo =
8473
+ writer !== undefined &&
8474
+ writer.options.mode === "minify" &&
8475
+ visitors.length === 0 &&
8476
+ _unusedSymbols === null &&
8477
+ !_S._prefixingOn &&
8478
+ !_colorFallbacksNeeded &&
8479
+ _S._renderEmbeddedSource === undefined &&
8480
+ _S._deferEmbeddedSource === undefined
8481
+ ? new Map()
8482
+ : null;
8483
+ _declarationMemo = _blockMemo === null ? null : new Map();
8484
+ _blockMemoScopes.clear();
8485
+ _singleValueKeys = _blockMemo === null ? null : new Set();
8486
+ _declarationMemoKey = "";
8051
8487
  _walkStopped = false;
8052
8488
 
8053
8489
  // WHY: the comment sink fires the `Comment` visitor bucket and, while
@@ -8210,6 +8646,15 @@ const grammar = (input, visitors, writer, options) => {
8210
8646
  _blockSpans.length = 0;
8211
8647
  _ruleScopes = null;
8212
8648
  _rootRuleScope = null;
8649
+ _shadowDeclared.clear();
8650
+ _blockMemo = null;
8651
+ _blockMemoScopes.clear();
8652
+ _blockMemoTable = _blockMemoKeyTable = new Map();
8653
+ _declarationMemo = null;
8654
+ _singleValueKeys = null;
8655
+ _blockMemoNode = null;
8656
+ _blockMemoBody = undefined;
8657
+ _blockMemoKey = "";
8213
8658
  // Each holds a piece of the sheet just printed, so the last one printed
8214
8659
  // would be held until the next parse replaces them.
8215
8660
  _distantRules.clear();
@@ -8221,7 +8666,9 @@ const grammar = (input, visitors, writer, options) => {
8221
8666
  _anonymousLayers = 0;
8222
8667
  _S._input = "";
8223
8668
  _originalInput = "";
8224
- _locConverter = /** @type {LocConverter} */ (/** @type {unknown} */ (null));
8669
+ _locConverter = /** @type {LocConverter} */ (
8670
+ /** @type {LocConverter | null} */ (null)
8671
+ );
8225
8672
  _declBodies.length = 0;
8226
8673
  _ruleBodies.length = 0;
8227
8674
  _flatTop = 0;
@@ -8230,7 +8677,9 @@ const grammar = (input, visitors, writer, options) => {
8230
8677
  _flatGrowHint = _flatPeak;
8231
8678
  _flat = EMPTY_I32;
8232
8679
  }
8233
- _visitors = /** @type {CompiledVisitorMap} */ (/** @type {unknown} */ ([]));
8680
+ _visitors = /** @type {CompiledVisitorMap} */ (
8681
+ /** @type {CompiledVisitorMap | never[]} */ ([])
8682
+ );
8234
8683
  _commentBucket = undefined;
8235
8684
  if (_capacity > _SHRINK_CAPACITY) {
8236
8685
  // +1: node ids are 1-based and grow fires at `id >= capacity`.
@@ -8891,6 +9340,10 @@ const _landsAlike = (value, shorter) => {
8891
9340
  * @returns {string} the rounded number, or `num`
8892
9341
  */
8893
9342
  const _roundSignificant = (num, unit = "") => {
9343
+ // A number written in at most six characters carries at most six significant
9344
+ // digits, a sign and dot only taking away, so rounding hands back what it was
9345
+ // given. Almost every number is this short.
9346
+ if (num.length <= _SIGNIFICANT_DIGITS) return num;
8894
9347
  const digits =
8895
9348
  unit === "" ||
8896
9349
  unit === "%" ||
@@ -8898,15 +9351,24 @@ const _roundSignificant = (num, unit = "") => {
8898
9351
  ANGLE_UNITS.has(unit)
8899
9352
  ? _SIGNIFICANT_DIGITS
8900
9353
  : _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
9354
  if (num.includes("e") || num.includes("E")) return num;
8906
9355
  const value = Number(num);
8907
9356
  if (!Number.isFinite(value) || Math.abs(value) >= _ROUNDING_LIMIT) return num;
8908
9357
  let rounded = value.toPrecision(digits);
8909
- if (digits === _SCALED_LENGTH_DIGITS) {
9358
+ if (unit === "%") {
9359
+ // Gecko parses a percentage as a single-precision fraction laid out in 1/60px,
9360
+ // so the digits must reach that float: `14.285714%` drifts, `14.2857143%` not.
9361
+ const parsed = Math.fround(value / 100);
9362
+ for (
9363
+ let more = digits;
9364
+ more < _SCALED_LENGTH_DIGITS &&
9365
+ Math.fround(Number(rounded) / 100) !== parsed;
9366
+ more++
9367
+ ) {
9368
+ rounded = value.toPrecision(more + 1);
9369
+ }
9370
+ if (Math.fround(Number(rounded) / 100) !== parsed) return num;
9371
+ } else if (digits === _SCALED_LENGTH_DIGITS) {
8910
9372
  // The fewest digits landing where the written length does, asked again of
8911
9373
  // each answer until it holds, which is what a second pass would ask.
8912
9374
  let from = value;
@@ -9651,6 +10113,17 @@ const _readByEveryEngine = (profile) => {
9651
10113
  return true;
9652
10114
  };
9653
10115
 
10116
+ /**
10117
+ * Whether the targets read an ability: the selection's browsers, or with none
10118
+ * each engine shipping today, which is what `dropOverriddenDeclarations` assumes.
10119
+ * @param {number | undefined} profile its `getSupportProfiles()` row
10120
+ * @returns {boolean} true when the later value it names is read
10121
+ */
10122
+ const _readsOverridingValue = (profile) =>
10123
+ _prefixBrowsers === null
10124
+ ? profile !== undefined && _readByEveryEngine(profile)
10125
+ : _readsAll(profile);
10126
+
9654
10127
  /**
9655
10128
  * The vendor spellings at least one target browser still needs for a construct —
9656
10129
  * a target at version V needs spelling S when some `[browser, from, to]` of S has
@@ -10776,6 +11249,20 @@ const _minifyPolarColorFunction = (
10776
11249
  fn === "lab" || fn === "lch"
10777
11250
  ? _labToLinearSrgb(l, a, b)
10778
11251
  : _oklabToLinearSrgb(l, a, b);
11252
+ // WHY: a hex mixed in `oklch` reaches it through a conversion, and Chromium
11253
+ // reads a hue there as missing below a chroma of 0.02 — so `color-mix(in
11254
+ // oklch, currentColor 35%, #0000)` paints hue 0 where `oklch()` named one.
11255
+ if (fn === "oklch" && (a !== 0 || b !== 0)) {
11256
+ const space = getColorSpaceModel().get(fn);
11257
+ const clamped = linear.map((c) => Math.min(1, Math.max(0, c)));
11258
+ if (
11259
+ space !== undefined &&
11260
+ space.uncertainHue !== null &&
11261
+ space.uncertainHue(space.to(clamped))
11262
+ ) {
11263
+ return null;
11264
+ }
11265
+ }
10779
11266
  // `_channelByte` decides the gamut from the encoded value, where the
10780
11267
  // question is whether clamping would move the byte.
10781
11268
  raw = linear.map((c) => _gammaEncode(c) * 255);
@@ -10999,7 +11486,7 @@ const _resolveColor = (text) => {
10999
11486
  }
11000
11487
  // Every call that answers records them, so a non-null answer is one.
11001
11488
  const raw = /** @type {number[]} */ (
11002
- /** @type {unknown} */ (_fallbackChannels)
11489
+ /** @type {number[] | null} */ (_fallbackChannels)
11003
11490
  );
11004
11491
  return {
11005
11492
  linear: raw.map((channel) => _gammaDecode(channel / 255)),
@@ -11832,7 +12319,7 @@ const _CUSTOM_MEDIA_REFERENCE_RE = /^\(\s*(--[^\s()]+)\s*\)$/;
11832
12319
  // The same, anywhere in a printed `(…)` group.
11833
12320
  const _NESTED_CUSTOM_MEDIA_REFERENCE_RE = /\(\s*(--[^\s()]+)\s*\)/g;
11834
12321
  // A media type: an identifier, which no keyword between conditions is.
11835
- const _MEDIA_TYPE_RE = /^-?[a-z_\u0080-￿][\w\u0080-￿-]*$/i;
12322
+ const _MEDIA_TYPE_RE = /^-?[a-zA-Z_\u0080-￿][\w\u0080-￿-]*$/;
11836
12323
 
11837
12324
  /**
11838
12325
  * @param {string} token a printed query token
@@ -12374,10 +12861,11 @@ const _lowerDirSelector = (parts) => {
12374
12861
  * Empty parts are stepped over — a comment between the colons printed away, and
12375
12862
  * the tokenizer had already dropped it anyway.
12376
12863
  * @param {string[]} parts the prelude's printed parts
12377
- * @returns {void}
12864
+ * @returns {boolean} whether a pseudo it read may be an `:is()` for `_unwrapLoneIs`
12378
12865
  */
12379
12866
  const _foldPseudoNames = (parts) => {
12380
12867
  const dropping = _S._transforms.shortenSelectors;
12868
+ let mayUnwrap = false;
12381
12869
  // The two non-empty parts before the current one.
12382
12870
  let first = -1;
12383
12871
  let second = -1;
@@ -12388,10 +12876,19 @@ const _foldPseudoNames = (parts) => {
12388
12876
  // not, `linearGradient` being an SVG element, and nor does an id, class or
12389
12877
  // attribute value. A functional pseudo folds its own name alone.
12390
12878
  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;
12879
+ if (
12880
+ part.length > 4 &&
12881
+ part.charCodeAt(2) === CC_LEFT_PARENTHESIS &&
12882
+ (part.charCodeAt(0) | 0x20) === CC_LOWER_I
12883
+ ) {
12884
+ mayUnwrap = true;
12885
+ }
12886
+ if (_S._transforms.foldCase && !part.includes("(")) {
12887
+ const lowered = asciiLowercaseName(part);
12888
+ if (lowered !== part) {
12889
+ part = lowered;
12890
+ parts[i] = lowered;
12891
+ }
12395
12892
  }
12396
12893
  // Folded first, so the legacy name below is matched against the folded
12397
12894
  // text rather than folded a second time.
@@ -12407,6 +12904,7 @@ const _foldPseudoNames = (parts) => {
12407
12904
  first = second;
12408
12905
  second = i;
12409
12906
  }
12907
+ return mayUnwrap;
12410
12908
  };
12411
12909
 
12412
12910
  // A legacy pseudo-element written with one colon, which `:is()` cannot hold.
@@ -12450,7 +12948,7 @@ const _isCompoundText = (selector) => {
12450
12948
  * Unwrap each `:is()` holding one compound, which matches the same elements with
12451
12949
  * the same specificity. Only where the targets read every pseudo it names, since
12452
12950
  * `:is()` forgives an argument it cannot parse and a bare compound does not.
12453
- * @param {string[]} parts a selector's printed parts, rewritten in place
12951
+ * @param {string[]} parts a selector's printed parts, rewritten in place, called only where `_foldPseudoNames` read a pseudo that may be an `:is()`
12454
12952
  */
12455
12953
  const _unwrapLoneIs = (parts) => {
12456
12954
  let before = -1;
@@ -12473,12 +12971,16 @@ const _unwrapLoneIs = (parts) => {
12473
12971
  ) {
12474
12972
  pastElement = true;
12475
12973
  }
12476
- const inner = call.slice(3, -1);
12477
- if (
12974
+ const isCall =
12478
12975
  !pastElement &&
12479
12976
  call.length > 4 &&
12480
- asciiCaseInsensitiveMatch(call.slice(0, 3), "is(") &&
12481
- call.charCodeAt(call.length - 1) === CC_RIGHT_PARENTHESIS &&
12977
+ (call.charCodeAt(0) | 0x20) === CC_LOWER_I &&
12978
+ (call.charCodeAt(1) | 0x20) === CC_LOWER_S &&
12979
+ call.charCodeAt(2) === CC_LEFT_PARENTHESIS &&
12980
+ call.charCodeAt(call.length - 1) === CC_RIGHT_PARENTHESIS;
12981
+ const inner = isCall ? call.slice(3, -1) : "";
12982
+ if (
12983
+ isCall &&
12482
12984
  _isCompoundText(inner) &&
12483
12985
  !inner.includes("::") &&
12484
12986
  !_LEGACY_PSEUDO_ELEMENT_RE.test(inner) &&
@@ -12697,7 +13199,8 @@ const _dropRedundantSupportsParens = (parts) => {
12697
13199
  continue;
12698
13200
  }
12699
13201
  let text = part;
12700
- for (;;) {
13202
+ // Only an opening pair is one to drop, which almost no condition holds.
13203
+ while (text.includes("((")) {
12701
13204
  const close = _matchingParens(text);
12702
13205
  if (close === null) break;
12703
13206
  let at = -1;
@@ -12725,6 +13228,7 @@ const _dropRedundantSupportsParens = (parts) => {
12725
13228
  }
12726
13229
  if (groups !== 1) return;
12727
13230
  const text = parts[only];
13231
+ if (text.charCodeAt(1) !== CC_LEFT_PARENTHESIS) return;
12728
13232
  const close = _matchingParens(text);
12729
13233
  if (
12730
13234
  close !== null &&
@@ -12891,7 +13395,8 @@ const _selectsFeatureless = (parts, at) => {
12891
13395
  * @returns {void}
12892
13396
  */
12893
13397
  const _dropImpliedUniversalSelector = (parts) => {
12894
- if (!_S._transforms.shortenSelectors) return;
13398
+ // A native search first: almost no selector holds a `*` at all.
13399
+ if (!_S._transforms.shortenSelectors || !parts.includes("*")) return;
12895
13400
  let previous = -1;
12896
13401
  for (let i = 0; i < parts.length; i++) {
12897
13402
  const part = parts[i];
@@ -13581,6 +14086,47 @@ const _foldFollowingLonghands = (path, items, texts, superseded) => {
13581
14086
  }
13582
14087
  return written.has(base) && _prefixedPropertyRemovable(base, property);
13583
14088
  };
14089
+ // Each declaration's property, read once: the walk back from every longhand
14090
+ // asks again of the ones before it, and a fold keeps the property it rewrites.
14091
+ /** @type {(string | undefined)[]} */
14092
+ const properties = [];
14093
+ // The number `_declarationTable` gives each, or -1 for one it does not name.
14094
+ /** @type {number[]} */
14095
+ const ids = [];
14096
+ const table = _declarationTable();
14097
+ /**
14098
+ * The property a declaration sets, read through `_canonicalProperty`.
14099
+ * @param {number} at the declaration's index
14100
+ * @returns {string} its property
14101
+ */
14102
+ const propertyAt = (at) => {
14103
+ let property = properties[at];
14104
+ if (property === undefined) {
14105
+ const printed = _printedProperty(texts[at], texts[at].indexOf(":"));
14106
+ const id = table.ids.get(printed);
14107
+ if (id === undefined) {
14108
+ property = printed;
14109
+ ids[at] = -1;
14110
+ } else {
14111
+ ids[at] = table.canonical[id];
14112
+ property = table.names[ids[at]];
14113
+ }
14114
+ properties[at] = property;
14115
+ }
14116
+ return property;
14117
+ };
14118
+ /**
14119
+ * Whether two declarations must keep their order, as `_orderMatters` reads it.
14120
+ * @param {number} a a declaration's index, its property already read
14121
+ * @param {number} b another's
14122
+ * @returns {boolean} true when the two must keep their order
14123
+ */
14124
+ const orderMattersAt = (a, b) => {
14125
+ const property = propertyAt(b);
14126
+ return ids[a] !== -1 && ids[b] !== -1
14127
+ ? _orderMattersById(table, ids[a], ids[b])
14128
+ : _orderMatters(/** @type {string} */ (properties[a]), property);
14129
+ };
13584
14130
  for (let i = 0; i < items.length; i++) {
13585
14131
  // A dropped declaration prints nothing, so nothing stands where it stood.
13586
14132
  if (texts[i].length === 0 || superseded.has(i)) continue;
@@ -13591,7 +14137,16 @@ const _foldFollowingLonghands = (path, items, texts, superseded) => {
13591
14137
  if (dropsWithPrefix(i)) continue;
13592
14138
  if (
13593
14139
  shorthand !== -1 &&
13594
- _foldIntoEarlier(path, items, texts, superseded, i, dropsWithPrefix)
14140
+ _foldIntoEarlier(
14141
+ path,
14142
+ items,
14143
+ texts,
14144
+ superseded,
14145
+ i,
14146
+ dropsWithPrefix,
14147
+ propertyAt,
14148
+ orderMattersAt
14149
+ )
13595
14150
  ) {
13596
14151
  superseded.add(i);
13597
14152
  continue;
@@ -13615,29 +14170,39 @@ let _anyLonghand = null;
13615
14170
  * @param {Set<number>} superseded the indices already dropped
13616
14171
  * @param {number} from the longhand's index
13617
14172
  * @param {(at: number) => boolean} drops whether a declaration is dropped later
14173
+ * @param {(at: number) => string} propertyAt the property a declaration sets, read through `_canonicalProperty`
14174
+ * @param {(a: number, b: number) => boolean} orderMattersAt `_orderMatters` of two declarations' properties, the first already read
13618
14175
  * @returns {boolean} true when it was folded into one before it
13619
14176
  */
13620
- const _foldIntoEarlier = (path, items, texts, superseded, from, drops) => {
14177
+ const _foldIntoEarlier = (
14178
+ path,
14179
+ items,
14180
+ texts,
14181
+ superseded,
14182
+ from,
14183
+ drops,
14184
+ propertyAt,
14185
+ orderMattersAt
14186
+ ) => {
13621
14187
  if (_anyLonghand === null) {
13622
14188
  _anyLonghand = new Set();
13623
14189
  for (const longhands of getShorthandLonghands().values()) {
13624
14190
  for (const longhand of longhands) _anyLonghand.add(longhand);
13625
14191
  }
13626
14192
  }
13627
- const property = _canonicalProperty(
13628
- _printedProperty(texts[from], texts[from].indexOf(":"))
13629
- );
13630
- if (!_anyLonghand.has(property.replace(_VENDOR_ITEM_PREFIX_RE, ""))) {
14193
+ const property = propertyAt(from);
14194
+ const bare =
14195
+ property.charCodeAt(0) === CC_HYPHEN_MINUS
14196
+ ? property.replace(_VENDOR_ITEM_PREFIX_RE, "")
14197
+ : property;
14198
+ if (!_anyLonghand.has(bare)) {
13631
14199
  return false;
13632
14200
  }
13633
14201
  for (let at = from - 1; at >= 0; at--) {
13634
14202
  if (texts[at].length === 0 || superseded.has(at)) continue;
13635
14203
  if (path.type(items[at]) !== NodeType.Declaration) return false;
13636
14204
  if (drops(at)) continue;
13637
- const other = _canonicalProperty(
13638
- _printedProperty(texts[at], texts[at].indexOf(":"))
13639
- );
13640
- if (_orderMatters(property, other)) {
14205
+ if (orderMattersAt(from, at)) {
13641
14206
  return _foldInto(path, items, texts, at, from);
13642
14207
  }
13643
14208
  }
@@ -14570,9 +15135,9 @@ const _familyValue = (property, longhands, values) => {
14570
15135
  * @returns {boolean} true when no target is left reading the earlier one
14571
15136
  */
14572
15137
  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;
15138
+ // No selection names no browser to answer for, so the earlier one stands
15139
+ // unless the option assumes each engine shipping today, read against below.
15140
+ if (_prefixBrowsers === null && !_dropOverriddenDeclarations) return false;
14576
15141
  // A selection naming a browser the tables do not cover answers for less than
14577
15142
  // the whole of it. Naming a browser states a target, which the option that
14578
15143
  // assumes one does not override, so this stands whatever it says.
@@ -14582,7 +15147,7 @@ const _valueReadByTarget = (property, value) => {
14582
15147
  const substitutions = _heldSubstitutions(value);
14583
15148
  if (substitutions !== null) {
14584
15149
  for (const name of substitutions) {
14585
- if (!_readsAll(_valueSupportedFrom(name))) return false;
15150
+ if (!_readsOverridingValue(_valueSupportedFrom(name))) return false;
14586
15151
  }
14587
15152
  return true;
14588
15153
  }
@@ -14592,7 +15157,7 @@ const _valueReadByTarget = (property, value) => {
14592
15157
  const components = _splitPrintedComponents(value);
14593
15158
  if (components.length > 1) return _textDecorationRead(components);
14594
15159
  }
14595
- return _readsAll(
15160
+ return _readsOverridingValue(
14596
15161
  _valueSupportedFrom(`${property} ${toLowerCaseIfNeeded(value)}`)
14597
15162
  );
14598
15163
  }
@@ -14604,9 +15169,10 @@ const _valueReadByTarget = (property, value) => {
14604
15169
  return false;
14605
15170
  }
14606
15171
  const fn = toLowerCaseIfNeeded(value.slice(0, open));
14607
- if (!_readsAll(_valueSupportedFrom(fn))) return false;
15172
+ if (!_readsOverridingValue(_valueSupportedFrom(fn))) return false;
14608
15173
  if (fn.endsWith("gradient")) {
14609
- return _gradientArgumentsRead(value.slice(open + 1, -1));
15174
+ // A one-stop gradient is CSS Images 4's, which not every engine reads yet.
15175
+ return _gradientArgumentsRead(value.slice(open + 1, -1), 2);
14610
15176
  }
14611
15177
  const args = value.slice(open + 1, -1).match(_COLOR_ARGUMENT_RE);
14612
15178
  if (args === null) return false;
@@ -14660,33 +15226,61 @@ const _GRADIENT_AMOUNT_RE = /^[+-]?(?:\d+\.?\d*|\.\d+)([a-z]+|%)?$/i;
14660
15226
  * line, shape or position of keywords and amounts, and stops of a color every
14661
15227
  * engine names. A call inside, or an interpolation space, is not asked here.
14662
15228
  * @param {string} inner the printed arguments
15229
+ * @param {number} stopsNeeded how many color stops the arguments must hold
14663
15230
  * @returns {boolean} true when the target reads every one
14664
15231
  */
14665
- const _gradientArgumentsRead = (inner) => {
14666
- for (const argument of _splitTopLevelArguments(inner)) {
14667
- for (const component of _splitPrintedComponents(argument.trim())) {
15232
+ const _gradientArgumentsRead = (inner, stopsNeeded) => {
15233
+ const args = _splitTopLevelArguments(inner);
15234
+ let stops = 0;
15235
+ for (let index = 0; index < args.length; index++) {
15236
+ // A line, shape or position opens the list alone; a stop is one color
15237
+ // and its amounts. Anything else, an empty argument too, is invalid.
15238
+ if (args[index].trim() === "") return false;
15239
+ let words = 0;
15240
+ let colors = 0;
15241
+ for (const component of _splitPrintedComponents(args[index].trim())) {
14668
15242
  const lowered = toLowerCaseIfNeeded(component);
14669
- if (_GRADIENT_KEYWORDS.has(lowered)) continue;
15243
+ if (_GRADIENT_KEYWORDS.has(lowered)) {
15244
+ if (lowered === "transparent" || lowered === "currentcolor") {
15245
+ colors++;
15246
+ } else {
15247
+ words++;
15248
+ }
15249
+ if (words !== 0 && (index !== 0 || colors !== 0)) return false;
15250
+ if (colors > 1) return false;
15251
+ continue;
15252
+ }
14670
15253
  const amount = _GRADIENT_AMOUNT_RE.exec(lowered);
14671
15254
  if (amount !== null) {
14672
15255
  // A unit some target does not read drops the gradient there.
14673
15256
  const unit = amount[1];
14674
15257
  if (unit === undefined || unit === "%") continue;
14675
- if (_readsAll(_valueSupportedFrom(`unit ${unit}`))) continue;
14676
- return false;
15258
+ if (!_readsOverridingValue(_valueSupportedFrom(`unit ${unit}`))) {
15259
+ return false;
15260
+ }
15261
+ continue;
14677
15262
  }
15263
+ let color = false;
14678
15264
  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;
15265
+ if (
15266
+ component.length !== 4 &&
15267
+ component.length !== 7 &&
15268
+ !_S._hexAlphaAllowed
15269
+ ) {
15270
+ return false;
15271
+ }
15272
+ color = true;
15273
+ } else if (
15274
+ getColorNameToRgb().has(lowered) &&
15275
+ !LATER_COLOR_NAMES.has(lowered)
15276
+ ) {
15277
+ color = true;
14685
15278
  }
14686
- return false;
15279
+ if (!color || ++colors > 1 || words !== 0) return false;
14687
15280
  }
15281
+ if (colors !== 0) stops++;
14688
15282
  }
14689
- return true;
15283
+ return stops >= stopsNeeded;
14690
15284
  };
14691
15285
 
14692
15286
  /**
@@ -14714,7 +15308,7 @@ const _textDecorationRead = (components) => {
14714
15308
  );
14715
15309
  if (keywords.includes(toLowerCaseIfNeeded(written))) continue;
14716
15310
  // A color or an amount, read as a gradient's stop or length is.
14717
- if (!_gradientArgumentsRead(written)) return false;
15311
+ if (!_gradientArgumentsRead(written, 0)) return false;
14718
15312
  }
14719
15313
  return true;
14720
15314
  };
@@ -14794,6 +15388,17 @@ const _writeIsDead = (path, writer, earlier, later, keyframe) => {
14794
15388
  );
14795
15389
  };
14796
15390
 
15391
+ /**
15392
+ * A property's first word, what an unnamed property of its family starts with:
15393
+ * `margin` for `margin-top`.
15394
+ * @param {string} name a property
15395
+ * @returns {string} the word before its first `-`
15396
+ */
15397
+ const _firstPropertyWord = (name) => {
15398
+ const dash = name.indexOf("-");
15399
+ return dash === -1 ? name : name.slice(0, dash);
15400
+ };
15401
+
14797
15402
  /**
14798
15403
  * Merge a box family's four longhands into their shorthand, which sets exactly
14799
15404
  * those four (see `BOX_LONGHANDS`) — so the rule computes the same either way.
@@ -14834,7 +15439,22 @@ const _mergeBoxLonghands = (
14834
15439
  // tail uncovered, dropping its declaration.
14835
15440
  /** @type {Set<Node> | null} */
14836
15441
  let claimed = null;
14837
- for (let e = 0; e < table.length; e++) {
15442
+ // Only an entry whose first longhand the block writes can merge, and blocks
15443
+ // write few properties against a table of dozens of entries.
15444
+ const firsts = _mergeTableFirsts(table);
15445
+ /** @type {number[]} */
15446
+ const candidates = [];
15447
+ for (const property of at.keys()) {
15448
+ const entries = firsts.get(property);
15449
+ if (entries !== undefined) {
15450
+ for (const entry of entries) candidates.push(entry);
15451
+ }
15452
+ }
15453
+ if (candidates.length === 0) return null;
15454
+ // In table order, as the claims between entries are made.
15455
+ if (candidates.length > 1) candidates.sort((a, b) => a - b);
15456
+ for (let c = 0; c < candidates.length; c++) {
15457
+ const e = candidates[c];
14838
15458
  const shorthand = table[e][0];
14839
15459
  const longhands = table[e][1];
14840
15460
  // A plain loop: this runs for every table entry of every rule, where one
@@ -14869,10 +15489,9 @@ const _mergeBoxLonghands = (
14869
15489
  // merges the same before and after it is reordered. A pair family states no
14870
15490
  // prefix at all — its longhands are the two names — so a property no dataset
14871
15491
  // 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)];
15492
+ const prefix =
15493
+ BOX_FAMILY_PREFIX.get(shorthand) || _firstPropertyWord(shorthand);
15494
+ const families = [prefix, ...longhands.map(_firstPropertyWord)];
14876
15495
  let blocked = false;
14877
15496
  for (
14878
15497
  let i = ordered[0] + 1;
@@ -14904,15 +15523,14 @@ const _mergeBoxLonghands = (
14904
15523
  if (indexes.some((i) => _isImportant(decls[i]) !== important)) continue;
14905
15524
  const values = [];
14906
15525
  for (const i of indexes) {
14907
- const components = _valueComponents(path, decls[i], writer);
14908
- if (components.length !== 1) break;
15526
+ if (!_isSingleValue(path, decls[i], writer)) break;
14909
15527
  // What the declaration prints, not what its tokens spell: a zero length
14910
15528
  // loses its unit as a declaration is written, and a token knows no property.
14911
15529
  const text = writer.get(decls[i]);
14912
15530
  const colon = text.indexOf(":");
14913
15531
  values.push(
14914
15532
  colon === -1
14915
- ? components[0]
15533
+ ? _valueComponents(path, decls[i], writer)[0]
14916
15534
  : text.slice(colon + 1, _printedValueEnd(text, colon))
14917
15535
  );
14918
15536
  }
@@ -15027,7 +15645,7 @@ const _mergeBoxLonghands = (
15027
15645
  };
15028
15646
 
15029
15647
  // 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;
15648
+ const _GRID_IDENT_RE = /^(?:--|-?[a-zA-Z_\u0080-\uFFFF])[-\w\u0080-\uFFFF]*$/;
15031
15649
  const _GRID_INTEGER_RE = /^[-+]?\d+$/;
15032
15650
 
15033
15651
  /**
@@ -15324,6 +15942,30 @@ const MERGE_FAMILY = 1;
15324
15942
  const MERGE_SLASH = 2;
15325
15943
  const MERGE_ORDERED = 3;
15326
15944
 
15945
+ /** @type {WeakMap<[string, string[]][], Map<string, number[]>>} */
15946
+ const _mergeTableFirstsCache = new WeakMap();
15947
+
15948
+ /**
15949
+ * A merge table's entries by their first longhand, which a block has to write
15950
+ * for the entry to merge.
15951
+ * @param {[string, string[]][]} table a merge table
15952
+ * @returns {Map<string, number[]>} the indexes of the entries each first longhand leads
15953
+ */
15954
+ const _mergeTableFirsts = (table) => {
15955
+ let firsts = _mergeTableFirstsCache.get(table);
15956
+ if (firsts === undefined) {
15957
+ firsts = new Map();
15958
+ for (let e = 0; e < table.length; e++) {
15959
+ const first = table[e][1][0];
15960
+ const entries = firsts.get(first);
15961
+ if (entries === undefined) firsts.set(first, [e]);
15962
+ else entries.push(e);
15963
+ }
15964
+ _mergeTableFirstsCache.set(table, firsts);
15965
+ }
15966
+ return firsts;
15967
+ };
15968
+
15327
15969
  // Each table as its entries, read by index: a `for…of` over the map would
15328
15970
  // allocate an entry pair per row of every table for every block merged.
15329
15971
  /** @type {[[string, string[]][], number][]} */
@@ -15358,6 +16000,14 @@ const MERGE_TABLES = [
15358
16000
  * concatenated: its own, or one a child carries. The run that grew it settles
15359
16001
  * it where the run ends (`_settleJoinedSeams`), so a join stays what it costs
15360
16002
  * to append rather than re-reading the list it has
16003
+ * @property {string | null} list its prelude where a join wrote it, so the next
16004
+ * join reads or appends to it rather than cutting it back out of `text`, which
16005
+ * would flatten the whole run so far; null to read it off `text`
16006
+ * @property {string | null} block the text from its `{` on, held beside `list`
16007
+ * for the same reason
16008
+ * @property {PrintedDeclaration[] | null} declarations its block's declarations
16009
+ * where a join of two blocks under one prelude wrote them, so the next join
16010
+ * reads them rather than parsing the whole block again; null to parse `text`
15361
16011
  */
15362
16012
 
15363
16013
  // Each printed rule the merge can join -> its entry, by node id. Emptied per
@@ -15450,7 +16100,10 @@ const _opaqueEntry = (text) => ({
15450
16100
  listKind: LIST_KIND_SELECTOR,
15451
16101
  children: null,
15452
16102
  head: "",
15453
- unordered: false
16103
+ unordered: false,
16104
+ list: null,
16105
+ block: null,
16106
+ declarations: null
15454
16107
  });
15455
16108
 
15456
16109
  /**
@@ -15462,7 +16115,7 @@ const _opaqueEntry = (text) => ({
15462
16115
  const _entryListable = (entry) => {
15463
16116
  if (entry.listable === LIST_UNKNOWN) {
15464
16117
  entry.listable = _isJoinablePrelude(
15465
- entry.text.slice(0, entry.prelude),
16118
+ _entryPrelude(entry),
15466
16119
  entry.listKind === LIST_KIND_KEYFRAME,
15467
16120
  entry.listKind === LIST_KIND_NESTED
15468
16121
  )
@@ -15501,22 +16154,33 @@ const _STRING = String.raw`"(?:[^"\\]|\\[\s\S])*"|'(?:[^'\\]|\\[\s\S])*'`;
15501
16154
  const _ATTRIBUTE = String.raw`\[(?:${_IDENT}\|)?${_IDENT}(?:[~|^$*]?=(?:${_IDENT}|${_STRING})(?: [iI])?)?\]`;
15502
16155
  // CSS whitespace: U+00A0 is an identifier's character, not a separator.
15503
16156
  const _SPACE = "[ \\t\\n\\f\\r]";
15504
- const _LEGACY_ELEMENT_NAMES = [...LEGACY_PSEUDO_ELEMENTS].join("|");
16157
+ /**
16158
+ * A pattern matching the word in any ASCII case, where an `i` flag would make V8
16159
+ * case-fold the whole `\u00A0-\uFFFF` class too, costing milliseconds to compile.
16160
+ * @param {string} word a lowercase word
16161
+ * @returns {string} the pattern
16162
+ */
16163
+ const _anyCase = (word) =>
16164
+ word.replace(/[a-z]/g, (letter) => `[${letter}${letter.toUpperCase()}]`);
16165
+ const _LEGACY_ELEMENT_NAMES = [...LEGACY_PSEUDO_ELEMENTS]
16166
+ .map(_anyCase)
16167
+ .join("|");
15505
16168
  // A pseudo-class with no selector inside it: argument-less, or the `An+B` of an
15506
16169
  // `:nth-*()`, whose grammar is closed — the one argument a shape may carry.
15507
16170
  const _PSEUDO_CLASS = String.raw`:(?!(?:${_LEGACY_ELEMENT_NAMES})(?![-\w]))(?:(?:${[
15508
16171
  ...NTH_PSEUDO_FUNCTIONS
15509
- ].join(
15510
- "|"
15511
- )})\(${_SPACE}*(?:odd|even|[-+]?(?:\d*n(?:${_SPACE}*[-+]${_SPACE}*\d+)?|\d+))${_SPACE}*\)|[-\w]+(?![-\w(]))`;
16172
+ ]
16173
+ .map(_anyCase)
16174
+ .join("|")})\(${_SPACE}*(?:${_anyCase("odd")}|${_anyCase(
16175
+ "even"
16176
+ )}|[-+]?(?:\d*[nN](?:${_SPACE}*[-+]${_SPACE}*\d+)?|\d+))${_SPACE}*\)|[-\w]+(?![-\w(]))`;
15512
16177
  // A pseudo-element ends the selector it is in: anything after it is no shape
15513
16178
  // an engine parses.
15514
16179
  const _PSEUDO_ELEMENT = String.raw`(?:::[-\w]+|:(?:${_LEGACY_ELEMENT_NAMES}))(?![-\w(])`;
15515
16180
  const _COMPOUND = `(?:\\*|${_IDENT}|[.#]${_IDENT}|${_ATTRIBUTE})(?:[.#]${_IDENT}|${_ATTRIBUTE}|${_PSEUDO_CLASS})*`;
15516
16181
  const _COMBINATOR = `(?:${_SPACE}*[>+~]${_SPACE}*|${_SPACE}+)`;
15517
16182
  const _JOINABLE_SELECTOR_RE = new RegExp(
15518
- `^(?:${_COMPOUND}${_COMBINATOR})*${_COMPOUND}(?:${_PSEUDO_ELEMENT})?$`,
15519
- "i"
16183
+ `^(?:${_COMPOUND}${_COMBINATOR})*${_COMPOUND}(?:${_PSEUDO_ELEMENT})?$`
15520
16184
  );
15521
16185
 
15522
16186
  // The same, for a rule nested in another: `&` joins there because an engine that
@@ -15524,8 +16188,7 @@ const _JOINABLE_SELECTOR_RE = new RegExp(
15524
16188
  // dropped together rather than one taking the other down.
15525
16189
  const _NESTED_COMPOUND = `(?:&(?:${_COMPOUND})?|${_COMPOUND})`;
15526
16190
  const _JOINABLE_NESTED_RE = new RegExp(
15527
- `^(?:${_NESTED_COMPOUND}${_COMBINATOR})*${_NESTED_COMPOUND}(?:${_PSEUDO_ELEMENT})?$`,
15528
- "i"
16191
+ `^(?:${_NESTED_COMPOUND}${_COMBINATOR})*${_NESTED_COMPOUND}(?:${_PSEUDO_ELEMENT})?$`
15529
16192
  );
15530
16193
 
15531
16194
  // A keyframe selector is a percentage or `to` — `from` is already printed `0%`.
@@ -15717,6 +16380,8 @@ const _blockProperties = (body) => {
15717
16380
  // The `!important` a printed declaration ends in, whatever it was written as.
15718
16381
  const _PRINTED_IMPORTANT_RE = /!\s*important$/i;
15719
16382
 
16383
+ /** @typedef {{ property: string, value: string, text: string }} PrintedDeclaration one declaration of a printed block: its lowercased property, its value and its whole text */
16384
+
15720
16385
  /**
15721
16386
  * The declarations one printed block holds, each as the whole of what it is
15722
16387
  * written as. Built on the same walk `_blockProperties` makes, since what a
@@ -15724,10 +16389,10 @@ const _PRINTED_IMPORTANT_RE = /!\s*important$/i;
15724
16389
  * else — a nested rule of its own, whose declarations are read at its place
15725
16390
  * rather than at the block's.
15726
16391
  * @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
16392
+ * @returns {PrintedDeclaration[] | null} the declarations, in order, or null
15728
16393
  */
15729
16394
  const _blockDeclarations = (body) => {
15730
- /** @type {{ property: string, value: string, text: string }[]} */
16395
+ /** @type {PrintedDeclaration[]} */
15731
16396
  const out = [];
15732
16397
  let depth = 0;
15733
16398
  let quote = 0;
@@ -15807,6 +16472,28 @@ const _rangeEquals = (a, aStart, aEnd, b, bStart, bEnd) => {
15807
16472
  return true;
15808
16473
  };
15809
16474
 
16475
+ /**
16476
+ * A rule's prelude, read from where a join holds it.
16477
+ * @param {RuleEntry} entry the rule
16478
+ * @returns {string} its prelude, as printed
16479
+ */
16480
+ const _entryPrelude = (entry) =>
16481
+ entry.list === null ? entry.text.slice(0, entry.prelude) : entry.list;
16482
+
16483
+ /**
16484
+ * Whether two rules' blocks say the same, each read from where it is held.
16485
+ * @param {RuleEntry} a one rule
16486
+ * @param {RuleEntry} b another
16487
+ * @returns {boolean} true when the blocks are equal
16488
+ */
16489
+ const _blocksEqual = (a, b) => {
16490
+ const aText = a.block === null ? a.text : a.block;
16491
+ const aStart = a.block === null ? a.prelude : 0;
16492
+ const bText = b.block === null ? b.text : b.block;
16493
+ const bStart = b.block === null ? b.prelude : 0;
16494
+ return _rangeEquals(aText, aStart, aText.length, bText, bStart, bText.length);
16495
+ };
16496
+
15810
16497
  // A layer with no name: the one at-rule prelude that is not the same rule twice.
15811
16498
  const ANONYMOUS_LAYER_RE = /^@layer[ \t\n\r\f]*$/i;
15812
16499
 
@@ -15902,7 +16589,11 @@ const _joinedHead = (joined) => {
15902
16589
  * @returns {string} the rule the block prints as
15903
16590
  */
15904
16591
  const _joinedBlockText = (prelude, head, joined) => {
15905
- let lastText = joined[joined.length - 1].text;
16592
+ const last = joined[joined.length - 1];
16593
+ let lastText = last.text;
16594
+ // Read off the block where a join holds one: the text before it is a list a
16595
+ // run of joins is still growing, which reading any of the text flattens.
16596
+ if (last.block !== null) return `${prelude}{${head}${lastText}}`;
15906
16597
  while (
15907
16598
  lastText.length !== 0 &&
15908
16599
  lastText.charCodeAt(lastText.length - 1) === CC_SEMICOLON
@@ -15943,16 +16634,16 @@ const _joinRuleEntries = (before, entry, owned) => {
15943
16634
  // cutting each one's prelude and block out to answer that is two pieces of
15944
16635
  // string per meeting for an answer that is usually no.
15945
16636
  const preludesEqual = _rangeEquals(
15946
- before.text,
16637
+ before.list === null ? before.text : before.list,
15947
16638
  0,
15948
16639
  before.prelude,
15949
- entry.text,
16640
+ entry.list === null ? entry.text : entry.list,
15950
16641
  0,
15951
16642
  entry.prelude
15952
16643
  );
15953
16644
  if (before.atRule) {
15954
16645
  if (!preludesEqual) return null;
15955
- const beforePrelude = before.text.slice(0, before.prelude);
16646
+ const beforePrelude = _entryPrelude(before);
15956
16647
  // CSS Cascade 5 §6.4.1: every `@layer {` opens a layer of its own, and a
15957
16648
  // later layer beats an earlier one whatever the selectors say — so joining
15958
16649
  // the two hands the block back to specificity.
@@ -16036,17 +16727,13 @@ const _joinRuleEntries = (before, entry, owned) => {
16036
16727
  listKind: LIST_KIND_SELECTOR,
16037
16728
  children: joined,
16038
16729
  head,
16039
- unordered
16730
+ unordered,
16731
+ list: beforePrelude,
16732
+ block: null,
16733
+ declarations: null
16040
16734
  };
16041
16735
  }
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
- );
16736
+ const blocksEqual = _blocksEqual(before, entry);
16050
16737
  // The same selector twice is one rule: nothing stands between them, so its
16051
16738
  // declarations are read in the order they were written either way.
16052
16739
  if (preludesEqual) {
@@ -16056,12 +16743,14 @@ const _joinRuleEntries = (before, entry, owned) => {
16056
16743
  // Declarations after a nested rule are the implicit `& {…}` the engine
16057
16744
  // builds for them, which is a rule the sheet did not have.
16058
16745
  if (!before.plain) return null;
16059
- const beforePrelude = before.text.slice(0, before.prelude);
16060
- const body = before.text.slice(before.prelude + 1, -1);
16746
+ const beforePrelude = _entryPrelude(before);
16061
16747
  const rest = entry.text.slice(entry.prelude + 1, -1);
16062
16748
  // One block holds a property once, so two blocks join only where the later
16063
16749
  // declaration is one the earlier could not have been read behind.
16064
- const earlier = _blockDeclarations(body);
16750
+ const earlier =
16751
+ before.declarations === null
16752
+ ? _blockDeclarations(before.text.slice(before.prelude + 1, -1))
16753
+ : before.declarations;
16065
16754
  if (earlier === null) return null;
16066
16755
  // The later block's declarations, and only where it holds nothing else: a
16067
16756
  // nested rule leaves the properties readable but not their values.
@@ -16109,19 +16798,42 @@ const _joinRuleEntries = (before, entry, owned) => {
16109
16798
  }
16110
16799
  const kept =
16111
16800
  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
- );
16801
+ ? earlier
16802
+ : earlier.filter((_, at) => !dropped.has(at));
16803
+ const added =
16804
+ later !== null
16805
+ ? later
16806
+ : rest.length === 0
16807
+ ? []
16808
+ : _blockDeclarations(rest);
16809
+ /** @type {PrintedDeclaration[] | null} */
16810
+ let declarations = null;
16811
+ let joined;
16812
+ // Carried as a list only where reading the joined text back would split it
16813
+ // the same way, which a quote the split reads as left open would not.
16814
+ if (
16815
+ added === null ||
16816
+ !_lastDeclarationCloses(added) ||
16817
+ (before.declarations === null && !_lastDeclarationCloses(earlier))
16818
+ ) {
16819
+ const keptText = kept.map((one) => one.text).join(";");
16820
+ joined = _orderJoinedBody(
16821
+ keptText.length === 0 || rest.length === 0
16822
+ ? keptText + rest
16823
+ : _mergeJoinedBody(`${keptText};${rest}`),
16824
+ before.plain && entry.plain
16825
+ );
16826
+ } else {
16827
+ declarations = _orderJoinedDeclarations(
16828
+ kept.length === 0 || added.length === 0
16829
+ ? [...kept, ...added]
16830
+ : _mergeJoinedBoxLonghands([...kept, ...added]),
16831
+ before.plain && entry.plain
16832
+ );
16833
+ // Each block already dropped the `;` its own `}` made redundant, so the
16834
+ // one that is no longer last needs it back.
16835
+ joined = declarations.map((one) => one.text).join(";");
16836
+ }
16125
16837
  return {
16126
16838
  text: `${beforePrelude}{${joined}}`,
16127
16839
  prelude: before.prelude,
@@ -16136,7 +16848,10 @@ const _joinRuleEntries = (before, entry, owned) => {
16136
16848
  head: "",
16137
16849
  // The two preludes are equal, so the list this keeps is the one `before`
16138
16850
  // already carried — settled or not.
16139
- unordered: before.unordered
16851
+ unordered: before.unordered,
16852
+ list: beforePrelude,
16853
+ block: null,
16854
+ declarations
16140
16855
  };
16141
16856
  }
16142
16857
  if (!blocksEqual || !_entryListable(before) || !_entryListable(entry)) {
@@ -16145,12 +16860,11 @@ const _joinRuleEntries = (before, entry, owned) => {
16145
16860
  // Concatenated, never canonicalized here: a run of joins would then re-read
16146
16861
  // the whole list once per rule. It is noted as a list still to settle, and
16147
16862
  // 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
- )}`;
16863
+ const list = `${_entryPrelude(before)},${_entryPrelude(entry)}`;
16864
+ const block =
16865
+ before.block === null ? before.text.slice(before.prelude) : before.block;
16152
16866
  return {
16153
- text: list + before.text.slice(before.prelude),
16867
+ text: list + block,
16154
16868
  prelude: list.length,
16155
16869
  atRule: false,
16156
16870
  plain: true,
@@ -16158,13 +16872,30 @@ const _joinRuleEntries = (before, entry, owned) => {
16158
16872
  listKind: before.listKind,
16159
16873
  children: null,
16160
16874
  head: "",
16161
- unordered: true
16875
+ unordered: true,
16876
+ list,
16877
+ block,
16878
+ declarations: null
16162
16879
  };
16163
16880
  };
16164
16881
 
16165
16882
  /**
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.
16883
+ * `_mergeJoinedBoxLonghands` over a joined block's text.
16884
+ * @param {string} body the joined block's declarations
16885
+ * @returns {string} the body, each family it could merge merged
16886
+ */
16887
+ const _mergeJoinedBody = (body) => {
16888
+ if (!_S._transforms.mergeLonghands) return body;
16889
+ const declarations = _blockDeclarations(body);
16890
+ if (declarations === null) return body;
16891
+ const merged = _mergeJoinedBoxLonghands(declarations);
16892
+ return merged === declarations
16893
+ ? body
16894
+ : merged.map((one) => one.text).join(";");
16895
+ };
16896
+
16897
+ /**
16898
+ * `_orderJoinedDeclarations` over a joined block's text.
16168
16899
  * @param {string} body the joined body, its braces excluded
16169
16900
  * @param {boolean} plain whether it holds declarations and nothing else
16170
16901
  * @returns {string} the body, reordered where that is allowed
@@ -16172,10 +16903,64 @@ const _joinRuleEntries = (before, entry, owned) => {
16172
16903
  const _orderJoinedBody = (body, plain) => {
16173
16904
  if (!plain || !_S._transforms.reorderDeclarations) return body;
16174
16905
  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(";");
16906
+ if (declarations === null) return body;
16907
+ const ordered = _orderJoinedDeclarations(declarations, plain);
16908
+ return ordered === declarations
16909
+ ? body
16910
+ : ordered.map((one) => one.text).join(";");
16911
+ };
16912
+
16913
+ /**
16914
+ * Whether the last of a printed block's declarations leaves the split that read
16915
+ * them where it started: outside a string and every bracket. Only the last can
16916
+ * end elsewhere, since the split parts the others only there.
16917
+ * @param {PrintedDeclaration[]} declarations the declarations, in order
16918
+ * @returns {boolean} true when a text joined after them is read on its own
16919
+ */
16920
+ const _lastDeclarationCloses = (declarations) => {
16921
+ if (declarations.length === 0) return true;
16922
+ const text = declarations[declarations.length - 1].text;
16923
+ let depth = 0;
16924
+ let quote = 0;
16925
+ for (let i = 0; i < text.length; i++) {
16926
+ const cc = text.charCodeAt(i);
16927
+ if (quote !== 0) {
16928
+ if (cc === CC_REVERSE_SOLIDUS) {
16929
+ i++;
16930
+ } else if (cc === quote) {
16931
+ quote = 0;
16932
+ }
16933
+ } else if (cc === CC_QUOTATION_MARK || cc === CC_APOSTROPHE) {
16934
+ quote = cc;
16935
+ } else if (
16936
+ cc === CC_LEFT_PARENTHESIS ||
16937
+ cc === CC_LEFT_SQUARE ||
16938
+ cc === CC_LEFT_CURLY
16939
+ ) {
16940
+ depth++;
16941
+ } else if (
16942
+ cc === CC_RIGHT_PARENTHESIS ||
16943
+ cc === CC_RIGHT_SQUARE ||
16944
+ cc === CC_RIGHT_CURLY
16945
+ ) {
16946
+ depth--;
16947
+ }
16948
+ }
16949
+ return quote === 0 && depth === 0;
16950
+ };
16951
+
16952
+ /**
16953
+ * A joined style rule's declarations in the order `_declarationOrder` prints
16954
+ * one in, which each half was already in but the two concatenated need not be.
16955
+ * @param {PrintedDeclaration[]} declarations the joined block's declarations
16956
+ * @param {boolean} plain whether it holds declarations and nothing else
16957
+ * @returns {PrintedDeclaration[]} them, reordered where that is allowed
16958
+ */
16959
+ const _orderJoinedDeclarations = (declarations, plain) => {
16960
+ if (!plain || !_S._transforms.reorderDeclarations) return declarations;
16961
+ if (declarations.length < 2) return declarations;
16962
+ const order = _declarationOrder(declarations.map((one) => one.text));
16963
+ return order === null ? declarations : order.map((at) => declarations[at]);
16179
16964
  };
16180
16965
 
16181
16966
  // The group rules whose declarations, nested in a style rule, are the implicit
@@ -16237,7 +17022,7 @@ const _noteStreamedLayerClose = (d, opener, at) => {
16237
17022
  layers.set(opener, {
16238
17023
  at,
16239
17024
  subtree: false,
16240
- taken: { piece: at, text: "}", spans: [], empties: -1 }
17025
+ taken: new TakenPiece(at, "}", -1)
16241
17026
  });
16242
17027
  };
16243
17028
 
@@ -16500,7 +17285,7 @@ const _nothingBetweenShadows = (items, texts, from, to, declared, subjects) => {
16500
17285
  // selectors and pseudos are gone: what either holds may look like a combinator.
16501
17286
  const _SUBJECT_STRIP_RE = new RegExp(
16502
17287
  `${_ATTRIBUTE}|${_PSEUDO_CLASS}|${_PSEUDO_ELEMENT}`,
16503
- "gi"
17288
+ "g"
16504
17289
  );
16505
17290
  const _SUBJECT_TYPE_RE = /(?:^|[\s>+~])([-\w\u00A0-\uFFFF]+)[^\s>+~]*$/;
16506
17291
 
@@ -16532,11 +17317,11 @@ const _subjectTypes = (prelude) => {
16532
17317
  * @returns {Set<string> | null} the type names
16533
17318
  */
16534
17319
  const _entrySubjectTypes = (entry) => {
16535
- if (!entry.atRule) return _subjectTypes(entry.text.slice(0, entry.prelude));
17320
+ if (!entry.atRule) return _subjectTypes(_entryPrelude(entry));
16536
17321
  /** @type {Set<string>} */
16537
17322
  const out = new Set();
16538
17323
  for (const child of /** @type {RuleEntry[]} */ (entry.children)) {
16539
- const types = _subjectTypes(child.text.slice(0, child.prelude));
17324
+ const types = _subjectTypes(_entryPrelude(child));
16540
17325
  if (types === null) return null;
16541
17326
  for (const type of types) out.add(type);
16542
17327
  }
@@ -16565,7 +17350,7 @@ const _subjectsDisjoint = (a, b) => {
16565
17350
  * @returns {boolean} true where its block may be read for what it declares
16566
17351
  */
16567
17352
  const _readableCondition = (entry) => {
16568
- const name = _atRuleName(entry.text.slice(0, entry.prelude));
17353
+ const name = _atRuleName(_entryPrelude(entry));
16569
17354
  return name !== "layer" && MERGEABLE_AT_RULES.has(name);
16570
17355
  };
16571
17356
 
@@ -16604,7 +17389,7 @@ const _atRuleDeclaredProperties = (entry) => {
16604
17389
  * @returns {void}
16605
17390
  */
16606
17391
  const _mergeDistantAtRule = (items, texts, held, entry, at) => {
16607
- const prelude = entry.text.slice(0, entry.prelude);
17392
+ const prelude = _entryPrelude(entry);
16608
17393
  // WHY: a layer's place in the cascade is where its name is first written, not
16609
17394
  // what its prelude states, so two blocks naming one are not the pair of equal
16610
17395
  // conditions this joins. `_mergeNamedLayerBlocks` gathers those under the rule
@@ -16712,7 +17497,12 @@ const _dropRulesWrittenAgain = (items, texts) => {
16712
17497
  /** @type {Map<string, number[]> | null} */
16713
17498
  let held = null;
16714
17499
  for (let i = 0; i < items.length; i++) {
16715
- if (texts[i].length === 0) continue;
17500
+ if (
17501
+ texts[i].length === 0 ||
17502
+ _types[_nodeIndex(items[i])] === NodeType.Declaration
17503
+ ) {
17504
+ continue;
17505
+ }
16716
17506
  const entry = _ruleEntryOf(items[i], texts[i]);
16717
17507
  // Keyframes of one selector are not one cascade in every engine: an older
16718
17508
  // one reads only the last block written for it.
@@ -16724,7 +17514,7 @@ const _dropRulesWrittenAgain = (items, texts) => {
16724
17514
  ) {
16725
17515
  continue;
16726
17516
  }
16727
- const prelude = entry.text.slice(0, entry.prelude);
17517
+ const prelude = _entryPrelude(entry);
16728
17518
  if (held === null) held = new Map();
16729
17519
  const earlier = held.get(prelude);
16730
17520
  if (earlier === undefined) {
@@ -17106,17 +17896,19 @@ const _canonicalSelectorList = (prelude) => {
17106
17896
  if (!prelude.includes(",")) return prelude;
17107
17897
  const list = _splitSelectorList(prelude);
17108
17898
  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;
17899
+ // Strictly increasing answers both questions the common case turns on —
17900
+ // every selector distinct, and already in order — without a set: only two
17901
+ // equal selectors compare as zero.
17902
+ let i = 1;
17903
+ while (i < list.length && _compareSelectors(list[i - 1], list[i]) < 0) i++;
17904
+ if (i === list.length) return prelude;
17905
+ list.sort(_compareSelectors);
17906
+ let kept = 1;
17907
+ for (let j = 1; j < list.length; j++) {
17908
+ if (list[j] !== list[kept - 1]) list[kept++] = list[j];
17117
17909
  }
17118
- if (seen.size === list.length && sorted) return prelude;
17119
- return [...seen].sort(_compareSelectors).join(",");
17910
+ list.length = kept;
17911
+ return list.join(",");
17120
17912
  };
17121
17913
 
17122
17914
  /**
@@ -17155,10 +17947,13 @@ const _settleJoinedSeams = (entry) => {
17155
17947
  const head = _joinedHead(settled);
17156
17948
  return {
17157
17949
  ...entry,
17158
- text: _joinedBlockText(entry.text.slice(0, entry.prelude), head, settled),
17950
+ text: _joinedBlockText(_entryPrelude(entry), head, settled),
17159
17951
  children: settled,
17160
17952
  head,
17161
- unordered: false
17953
+ unordered: false,
17954
+ list: null,
17955
+ block: null,
17956
+ declarations: null
17162
17957
  };
17163
17958
  };
17164
17959
 
@@ -17173,7 +17968,7 @@ const _settleJoinedSeams = (entry) => {
17173
17968
  const _canonicalizeJoined = (entry) => {
17174
17969
  if (entry.atRule) return _settleJoinedSeams(entry);
17175
17970
  if (entry.prelude === -1) return entry;
17176
- const prelude = entry.text.slice(0, entry.prelude);
17971
+ const prelude = _entryPrelude(entry);
17177
17972
  const canonical = _canonicalSelectorList(prelude);
17178
17973
  if (canonical === prelude) {
17179
17974
  entry.unordered = false;
@@ -17183,7 +17978,10 @@ const _canonicalizeJoined = (entry) => {
17183
17978
  ...entry,
17184
17979
  text: canonical + entry.text.slice(entry.prelude),
17185
17980
  prelude: canonical.length,
17186
- unordered: false
17981
+ unordered: false,
17982
+ list: null,
17983
+ block: null,
17984
+ declarations: null
17187
17985
  };
17188
17986
  };
17189
17987
 
@@ -17358,8 +18156,9 @@ const _rulePrelude = (path, writer, minify, outParts) => {
17358
18156
  if (_S._transforms.customSelectors && _customSelectors.size !== 0) {
17359
18157
  _writeCustomSelector(parts);
17360
18158
  }
17361
- _foldPseudoNames(parts);
17362
- if (_S._transforms.shortenSelectors) _unwrapLoneIs(parts);
18159
+ if (_foldPseudoNames(parts) && _S._transforms.shortenSelectors) {
18160
+ _unwrapLoneIs(parts);
18161
+ }
17363
18162
  // After the fold, which is what makes the names above match, and before the
17364
18163
  // class replacement, so a pseudo written as a class is one either way.
17365
18164
  if (!_S._isSelectorAllowed) {
@@ -17410,9 +18209,7 @@ const _rulePrelude = (path, writer, minify, outParts) => {
17410
18209
  }
17411
18210
  const list = _canonicalSelectorList(prelude);
17412
18211
  if (_readsThroughGoneEngine(list)) {
17413
- /** @type {Set<Node>} */ (_unusedRules).add(
17414
- /** @type {Node} */ (_S._currentNode)
17415
- );
18212
+ /** @type {Set<Node>} */ (_unusedRules).add(_S._currentNode);
17416
18213
  return list;
17417
18214
  }
17418
18215
  if (_unusedSymbols === null) return list;
@@ -17420,9 +18217,7 @@ const _rulePrelude = (path, writer, minify, outParts) => {
17420
18217
  // Marked rather than dropped here: the rule's own printer takes it out, the
17421
18218
  // same way it takes out one whose block ends up empty.
17422
18219
  if (kept !== null) return kept;
17423
- /** @type {Set<Node>} */ (_unusedRules).add(
17424
- /** @type {Node} */ (_S._currentNode)
17425
- );
18220
+ /** @type {Set<Node>} */ (_unusedRules).add(_S._currentNode);
17426
18221
  return list;
17427
18222
  }
17428
18223
  if (
@@ -17435,9 +18230,7 @@ const _rulePrelude = (path, writer, minify, outParts) => {
17435
18230
  unescapeIdentifier(prelude.slice(_rawName(path.node).length + 1).trim())
17436
18231
  )
17437
18232
  ) {
17438
- /** @type {Set<Node>} */ (_unusedRules).add(
17439
- /** @type {Node} */ (_S._currentNode)
17440
- );
18233
+ /** @type {Set<Node>} */ (_unusedRules).add(_S._currentNode);
17441
18234
  }
17442
18235
  return prelude;
17443
18236
  };
@@ -17904,6 +18697,39 @@ const _canonicalProperty = (property) => {
17904
18697
  const _orderMatters = (a, b) => {
17905
18698
  if (a === b || a === "all" || b === "all") return true;
17906
18699
  if (a.startsWith("--") || b.startsWith("--")) return false;
18700
+ let row = _orderMattersMemo.get(a);
18701
+ let matters = row === undefined ? undefined : row.get(b);
18702
+ if (matters !== undefined) return matters;
18703
+ matters = _computeOrderMatters(a, b);
18704
+ // The names are the author's, so a sheet of made-up ones starts again.
18705
+ if (_orderMattersEntries === _ORDER_MATTERS_ENTRIES) {
18706
+ _orderMattersMemo.clear();
18707
+ _orderMattersEntries = 0;
18708
+ row = undefined;
18709
+ }
18710
+ if (row === undefined) {
18711
+ row = new Map();
18712
+ _orderMattersMemo.set(a, row);
18713
+ }
18714
+ row.set(b, matters);
18715
+ _orderMattersEntries++;
18716
+ return matters;
18717
+ };
18718
+
18719
+ // `_orderMatters` by its first property, then its second: a sheet names few
18720
+ // properties, and the walks back over a block ask of the same pairs again.
18721
+ /** @type {Map<string, Map<string, boolean>>} */
18722
+ const _orderMattersMemo = new Map();
18723
+ let _orderMattersEntries = 0;
18724
+ const _ORDER_MATTERS_ENTRIES = 1 << 15;
18725
+
18726
+ /**
18727
+ * `_orderMatters` without the memo.
18728
+ * @param {string} a a property, read through `_canonicalProperty`
18729
+ * @param {string} b another
18730
+ * @returns {boolean} true when the two must keep their order
18731
+ */
18732
+ const _computeOrderMatters = (a, b) => {
17907
18733
  // A corner's radius is no side's: `border-bottom` sets no `-radius` it starts.
17908
18734
  if (
17909
18735
  a.charCodeAt(0) !== CC_HYPHEN_MINUS &&
@@ -17912,7 +18738,7 @@ const _orderMatters = (a, b) => {
17912
18738
  ) {
17913
18739
  return false;
17914
18740
  }
17915
- if (a.startsWith(`${b}-`) || b.startsWith(`${a}-`)) return true;
18741
+ if (_extendsProperty(a, b) || _extendsProperty(b, a)) return true;
17916
18742
  // A vendor property no standard one aliases says nothing of what it sets
17917
18743
  // beyond its words: `-webkit-border-before` is a border side.
17918
18744
  if (
@@ -17938,6 +18764,18 @@ const _orderMatters = (a, b) => {
17938
18764
  );
17939
18765
  };
17940
18766
 
18767
+ /**
18768
+ * Whether a property name is another's followed by more words: `margin-top` of
18769
+ * `margin`. Read in place, as a `${base}-` prefix would be built on every call.
18770
+ * @param {string} name a property
18771
+ * @param {string} base another
18772
+ * @returns {boolean} true when `name` starts with `base` and a `-`
18773
+ */
18774
+ const _extendsProperty = (name, base) =>
18775
+ name.length > base.length &&
18776
+ name.charCodeAt(base.length) === CC_HYPHEN_MINUS &&
18777
+ name.startsWith(base);
18778
+
17941
18779
  // A box side, corner or axis a property names, physical or flow-relative.
17942
18780
  const _PHYSICAL_SIDE_RE =
17943
18781
  /-(?:top|right|bottom|left|x|y)(?=-|$)|^(?:(?:min|max)-)?(?:top|right|bottom|left|width|height)$|-(?:width|height)$/;
@@ -17967,6 +18805,11 @@ const _blocksMerge = (between, later, families) => {
17967
18805
  );
17968
18806
  };
17969
18807
 
18808
+ // The properties `_sideRoot` reads as `inset`'s and as `size`'s sides.
18809
+ const _INSET_SIDE_RE = /^(?:top|right|bottom|left)$/;
18810
+ const _SIZE_PROPERTY_RE =
18811
+ /^(?:(?:min|max)-)?(?:width|height|inline-size|block-size)$/;
18812
+
17970
18813
  /**
17971
18814
  * The word a property's sides share: `margin` for `margin-left` and
17972
18815
  * `margin-inline-start`, `inset` for `top`, `size` for `width` and `inline-size`.
@@ -17974,11 +18817,12 @@ const _blocksMerge = (between, later, families) => {
17974
18817
  * @returns {string} that word
17975
18818
  */
17976
18819
  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
- }
18820
+ const bare =
18821
+ property.charCodeAt(0) === CC_HYPHEN_MINUS
18822
+ ? property.replace(_VENDOR_ITEM_PREFIX_RE, "")
18823
+ : property;
18824
+ if (_INSET_SIDE_RE.test(bare)) return "inset";
18825
+ if (_SIZE_PROPERTY_RE.test(bare)) return "size";
17982
18826
  const dash = bare.indexOf("-");
17983
18827
  return dash === -1 ? bare : bare.slice(0, dash);
17984
18828
  };
@@ -18009,8 +18853,8 @@ const _writesOnlyDeclarations = (path, items, texts) => {
18009
18853
  * @returns {number[] | null} where each comes from, or null to keep them as written
18010
18854
  */
18011
18855
  const _declarationOrder = (texts) => {
18012
- const ranks = getDeclarationRanks();
18013
- /** @type {string[]} */
18856
+ const table = _declarationTable();
18857
+ /** @type {number[]} */
18014
18858
  const properties = [];
18015
18859
  /** @type {number[]} */
18016
18860
  const rankOf = [];
@@ -18020,20 +18864,38 @@ const _declarationOrder = (texts) => {
18020
18864
  if (colon <= 0) return null;
18021
18865
  const property = toLowerCaseIfNeeded(text.slice(0, colon));
18022
18866
  if (property.startsWith("--")) {
18023
- properties.push(property);
18867
+ properties.push(-1);
18024
18868
  rankOf.push(-1);
18025
18869
  continue;
18026
18870
  }
18027
- const rank = ranks.get(property);
18028
- if (rank === undefined) return null;
18871
+ const id = table.ids.get(property);
18872
+ if (id === undefined) return null;
18873
+ const rank = table.ranks[id];
18029
18874
  if (rank !== -1) ranked = true;
18030
- properties.push(_canonicalProperty(property));
18875
+ properties.push(table.canonical[id]);
18031
18876
  rankOf.push(rank);
18032
18877
  }
18033
18878
  if (!ranked) return null;
18034
- const order = texts
18035
- .map((_, i) => i)
18036
- .sort((a, b) => rankOf[a] - rankOf[b] || a - b);
18879
+ // Already in rank order, which half of all blocks are: the stable sort would
18880
+ // hand every index back where it stands.
18881
+ let inOrder = true;
18882
+ for (let i = 1; i < rankOf.length && inOrder; i++) {
18883
+ if (rankOf[i] < rankOf[i - 1]) inOrder = false;
18884
+ }
18885
+ if (inOrder) return null;
18886
+ // A stable insertion sort by rank: blocks are short, and this allocates no
18887
+ // comparator and no copy as `Array#sort` does.
18888
+ /** @type {number[]} */
18889
+ const order = [];
18890
+ for (let i = 0; i < rankOf.length; i++) {
18891
+ let at = order.length;
18892
+ order.push(i);
18893
+ while (at > 0 && rankOf[order[at - 1]] > rankOf[i]) {
18894
+ order[at] = order[at - 1];
18895
+ at--;
18896
+ }
18897
+ order[at] = i;
18898
+ }
18037
18899
  let moved = false;
18038
18900
  for (let i = 0; i < order.length; i++) {
18039
18901
  if (order[i] !== i) moved = true;
@@ -18042,7 +18904,7 @@ const _declarationOrder = (texts) => {
18042
18904
  for (let j = i + 1; j < order.length; j++) {
18043
18905
  if (
18044
18906
  order[j] < order[i] &&
18045
- _orderMatters(properties[order[i]], properties[order[j]])
18907
+ _orderMattersById(table, properties[order[i]], properties[order[j]])
18046
18908
  ) {
18047
18909
  return null;
18048
18910
  }
@@ -18051,6 +18913,73 @@ const _declarationOrder = (texts) => {
18051
18913
  return moved ? order : null;
18052
18914
  };
18053
18915
 
18916
+ /**
18917
+ * Every property `getDeclarationRanks` names by a small number: its rank, the
18918
+ * number of the property `_canonicalProperty` reads it as, and `_orderMatters`
18919
+ * of two such numbers, filled in as pairs are asked of.
18920
+ * @typedef {object} DeclarationTable
18921
+ * @property {Map<string, number>} ids each property's number
18922
+ * @property {Int32Array} ranks the rank of each number
18923
+ * @property {Int32Array} canonical the number of the property each is read as
18924
+ * @property {string[]} names the property each number stands for
18925
+ * @property {number} all the number of `all`
18926
+ * @property {Uint8Array} matters `_orderMatters` by `a * names.length + b`: 0 unknown, 1 no, 2 yes
18927
+ */
18928
+
18929
+ /** @type {DeclarationTable | undefined} */
18930
+ let _declarationTableValue;
18931
+
18932
+ /**
18933
+ * @returns {DeclarationTable} the table, built on first use
18934
+ */
18935
+ const _declarationTable = () => {
18936
+ if (_declarationTableValue !== undefined) return _declarationTableValue;
18937
+ const rankMap = getDeclarationRanks();
18938
+ /** @type {Map<string, number>} */
18939
+ const ids = new Map();
18940
+ /** @type {string[]} */
18941
+ const names = [];
18942
+ for (const name of rankMap.keys()) {
18943
+ ids.set(name, names.length);
18944
+ names.push(name);
18945
+ }
18946
+ const ranks = new Int32Array(names.length);
18947
+ const canonical = new Int32Array(names.length);
18948
+ for (let i = 0; i < names.length; i++) {
18949
+ ranks[i] = /** @type {number} */ (rankMap.get(names[i]));
18950
+ const id = ids.get(_canonicalProperty(names[i]));
18951
+ canonical[i] = id === undefined ? i : id;
18952
+ }
18953
+ _declarationTableValue = {
18954
+ ids,
18955
+ ranks,
18956
+ canonical,
18957
+ names,
18958
+ all: /** @type {number} */ (ids.get("all")),
18959
+ matters: new Uint8Array(names.length * names.length)
18960
+ };
18961
+ return _declarationTableValue;
18962
+ };
18963
+
18964
+ /**
18965
+ * `_orderMatters` of two numbered properties, -1 for a custom one.
18966
+ * @param {DeclarationTable} table the numbering
18967
+ * @param {number} a a property's canonical number
18968
+ * @param {number} b another
18969
+ * @returns {boolean} true when the two must keep their order
18970
+ */
18971
+ const _orderMattersById = (table, a, b) => {
18972
+ if (a === b || a === table.all || b === table.all) return true;
18973
+ // Two custom properties are both -1 yet never trade places: they share a rank.
18974
+ if (a === -1 || b === -1) return false;
18975
+ const at = a * table.names.length + b;
18976
+ const known = table.matters[at];
18977
+ if (known !== 0) return known === 2;
18978
+ const matters = _computeOrderMatters(table.names[a], table.names[b]);
18979
+ table.matters[at] = matters ? 2 : 1;
18980
+ return matters;
18981
+ };
18982
+
18054
18983
  /**
18055
18984
  * Put one declaration list together: the list-wide transforms (merge a family's
18056
18985
  * longhands into their shorthand, drop what a later declaration supersedes, add
@@ -18227,7 +19156,8 @@ const _composeBlockBody = (
18227
19156
  for (const node of held) pendingRules.add(node);
18228
19157
  }
18229
19158
  }
18230
- if (minify) {
19159
+ // A block of declarations alone, most of them, holds no rule to join or drop.
19160
+ if (minify && items !== decls) {
18231
19161
  // Before the joins, so a rule it takes out stands in none of their way;
18232
19162
  // and again after any join, which can write a rule again in its turn.
18233
19163
  if (_S._transforms.removeDeadRules) {
@@ -18430,6 +19360,7 @@ const _composeBlockBody = (
18430
19360
  for (let i = 0; i < properties.length; i++) {
18431
19361
  if (properties[i].length === 0) continue;
18432
19362
  present.add(properties[i]);
19363
+ (_isImportant(items[i]) ? importantAt : plainAt).set(properties[i], i);
18433
19364
  // A value is only ever asked for against its own property, so the
18434
19365
  // two share one set.
18435
19366
  if (values[i].length !== 0) {
@@ -18488,7 +19419,32 @@ const _composeBlockBody = (
18488
19419
  ? _prefixedPropertyName(property)
18489
19420
  : undefined;
18490
19421
  if (base !== undefined) {
18491
- if (present.has(base) && _prefixedPropertyRemovable(base, property)) {
19422
+ // An engine still reading the vendor name as an alias cascades the
19423
+ // two as one property, so it is dead only behind a twin that wins.
19424
+ const importantBase = importantAt.get(base);
19425
+ const plainBase = plainAt.get(base);
19426
+ const important = _isImportant(items[i]);
19427
+ const baseWins = important
19428
+ ? importantBase !== undefined && importantBase > i
19429
+ : importantBase !== undefined ||
19430
+ (plainBase !== undefined && plainBase > i);
19431
+ // ...or behind one of the same importance saying the same thing.
19432
+ const twinAt = important ? importantBase : plainBase;
19433
+ const sameTwin =
19434
+ twinAt !== undefined &&
19435
+ (important || importantBase === undefined) &&
19436
+ texts[twinAt].slice(
19437
+ base.length + 1,
19438
+ _printedValueEnd(texts[twinAt], base.length)
19439
+ ) ===
19440
+ texts[i].slice(
19441
+ property.length + 1,
19442
+ _printedValueEnd(texts[i], property.length)
19443
+ );
19444
+ if (
19445
+ (baseWins || sameTwin) &&
19446
+ _prefixedPropertyRemovable(base, property)
19447
+ ) {
18492
19448
  if (droppedPrefix === null) droppedPrefix = new Set();
18493
19449
  droppedPrefix.add(i);
18494
19450
  continue;
@@ -18588,6 +19544,10 @@ const _composeBlockBody = (
18588
19544
  : null;
18589
19545
  /** @type {string[]} */
18590
19546
  const pieceTexts = [];
19547
+ // The body before its last piece, and that piece: the `;` a `}` makes
19548
+ // redundant is cut off the piece, as reading the body's end would flatten it.
19549
+ let prior = "";
19550
+ let last = "";
18591
19551
  for (let i = 0; i < texts.length; i++) {
18592
19552
  const isBlock =
18593
19553
  bodies !== 0 && path.type(items[i]) !== NodeType.Declaration;
@@ -18630,6 +19590,8 @@ const _composeBlockBody = (
18630
19590
  }
18631
19591
  body += nl;
18632
19592
  const at = body.length;
19593
+ prior = body;
19594
+ last = text;
18633
19595
  body += text;
18634
19596
  if (minify && isBlock) {
18635
19597
  if (spans === null) spans = [];
@@ -18639,18 +19601,15 @@ const _composeBlockBody = (
18639
19601
  if (pieces !== null) {
18640
19602
  const order = _declarationOrder(pieceTexts);
18641
19603
  for (let i = 0; i < pieces.length; i++) {
18642
- body += pieces[order === null ? i : order[i]];
19604
+ prior = body;
19605
+ last = pieces[order === null ? i : order[i]];
19606
+ body += last;
18643
19607
  }
18644
19608
  }
18645
19609
  // Drop the redundant `;` a following `}` — or, at top level, the end of the
18646
19610
  // 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
- }
19611
+ if (minify && last.charCodeAt(last.length - 1) === CC_SEMICOLON) {
19612
+ body = prior + last.slice(0, -1);
18654
19613
  }
18655
19614
 
18656
19615
  return {
@@ -19199,7 +20158,7 @@ const _S = {
19199
20158
  // The walk's current position (`A.node` / `A.parent` read these; module-level
19200
20159
  // so the accessor methods' defaults avoid self-referential `this` typing).
19201
20160
  /** @type {Node} */
19202
- _currentNode: /** @type {Node} */ (/** @type {unknown} */ (0)),
20161
+ _currentNode: /** @type {Node} */ (/** @type {Node | number} */ (0)),
19203
20162
  // The declaration `_inKeywordOnlyValue` last answered for, and its answer: one
19204
20163
  // value's identifiers all ask it, and the read is a pointer compare after the
19205
20164
  // first. Cleared with the rest of the parse state, so no node outlives it.
@@ -19499,10 +20458,10 @@ const A = {
19499
20458
  field(i, field, n = _S._currentNode) {
19500
20459
  const k = _nodeIndex(n);
19501
20460
  if (i < 0 || i >= A.fieldCount(field, n)) {
19502
- return /** @type {Node} */ (_nodeRef(0));
20461
+ return _nodeRef(0);
19503
20462
  }
19504
20463
  if (_LIST_FIELD[_types[k]] === field) {
19505
- return /** @type {Node} */ (_nodeRef(_flat[_listStarts[k] + i]));
20464
+ return _nodeRef(_flat[_listStarts[k] + i]);
19506
20465
  }
19507
20466
  return (field === Field.declarations ? _declBodies : _ruleBodies)[
19508
20467
  _bodyIdx[k] - 1
@@ -19519,7 +20478,7 @@ const A = {
19519
20478
  const item = A.field(i, field, n);
19520
20479
  if (A.name(item) === name) return item;
19521
20480
  }
19522
- return /** @type {Node} */ (_nodeRef(0));
20481
+ return _nodeRef(0);
19523
20482
  },
19524
20483
  /**
19525
20484
  * `important` is a container's bit, cleared when its id is reused; a token's
@@ -19698,7 +20657,13 @@ const _collectRuleSpans = (spans, item, text, at, path, inner) => {
19698
20657
  // it — the parent first, so a cut that takes the whole rule is seen before
19699
20658
  // anything inside it.
19700
20659
  if (path.type(item) === NodeType.QualifiedRule) {
19701
- spans.push({ scope: _rootScope(), key: text, at, len: text.length });
20660
+ spans.push({
20661
+ scope: _rootScope(),
20662
+ key: text,
20663
+ at,
20664
+ len: text.length,
20665
+ taken: null
20666
+ });
19702
20667
  } else if (
19703
20668
  inner !== undefined &&
19704
20669
  inner.prelude.charCodeAt(0) === CC_AT_SIGN &&
@@ -19706,7 +20671,13 @@ const _collectRuleSpans = (spans, item, text, at, path, inner) => {
19706
20671
  ) {
19707
20672
  // Not a rule of its own — a condition its body is read under — so it is
19708
20673
  // 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 });
20674
+ spans.push({
20675
+ scope: _rootScope(),
20676
+ key: UNKEYED,
20677
+ at,
20678
+ len: text.length,
20679
+ taken: null
20680
+ });
19710
20681
  }
19711
20682
  if (inner === undefined) return;
19712
20683
  const base = at + inner.bodyAt;
@@ -19715,7 +20686,8 @@ const _collectRuleSpans = (spans, item, text, at, path, inner) => {
19715
20686
  scope: _enclosingRuleScope(span.scope, inner.keyPrelude),
19716
20687
  key: span.key,
19717
20688
  at: base + span.at,
19718
- len: span.len
20689
+ len: span.len,
20690
+ taken: null
19719
20691
  });
19720
20692
  }
19721
20693
  };
@@ -19764,6 +20736,7 @@ module.exports._NUMERIC_RE = _NUMERIC_RE;
19764
20736
  module.exports._atKeywordOf = _atKeywordOf;
19765
20737
  module.exports._printedSource = _printedSource;
19766
20738
  module.exports._rawName = _rawName;
20739
+ module.exports._setVerbatimLeafPrinter = setVerbatimLeafPrinter;
19767
20740
  module.exports._trimCssWhiteSpace = _trimCssWhiteSpace;
19768
20741
  module.exports._declarationsOf = _declarationsOf;
19769
20742
  module.exports._rulesOf = _rulesOf;
@@ -19773,6 +20746,33 @@ module.exports._S = _S;
19773
20746
  module.exports._SEP = _SEP;
19774
20747
  module.exports._keepKeywordComments = _keepKeywordComments;
19775
20748
  module.exports._TRIM_COMBINATORS = _TRIM_COMBINATORS;
20749
+ /**
20750
+ * The body a reused block composes to, for the rule being printed.
20751
+ * @param {Node} node the rule
20752
+ * @returns {string | undefined} the body, or undefined to compose it
20753
+ */
20754
+ module.exports._memoizedBlockBody = (node) =>
20755
+ _blockMemoNode === node ? _blockMemoBody : undefined;
20756
+ /**
20757
+ * Done with the rule the walk looked a block up for: keep the body it composed
20758
+ * where the walk asked for one, and forget the rule — node ids are reused.
20759
+ * @param {Node} node the rule
20760
+ * @param {string | null} body its composed body, null to keep none
20761
+ * @returns {void}
20762
+ */
20763
+ module.exports._memoizeBlockBody = (node, body) => {
20764
+ if (
20765
+ _blockMemoNode === node &&
20766
+ body !== null &&
20767
+ _blockMemoKey !== "" &&
20768
+ _blockMemo !== null
20769
+ ) {
20770
+ _blockMemoKeyTable.set(_blockMemoKey, body);
20771
+ }
20772
+ _blockMemoNode = null;
20773
+ _blockMemoBody = undefined;
20774
+ _blockMemoKey = "";
20775
+ };
19776
20776
  module.exports._TRIM_CONDITIONS = _TRIM_CONDITIONS;
19777
20777
  module.exports._TRIM_MATH = _TRIM_MATH;
19778
20778
  module.exports._TRIM_NOTHING = _TRIM_NOTHING;