NewYour coding agent can read the release notes before it upgrades.Set up the MCP server →
npm · #1507 most downloaded on npm
CSS minifier with structural optimisations
Last release 4 years ago
no release in 18 months
Release timing varies
gaps range from 1 weeks to 1.4 years
Nearly every release is documented
notes for 56 of the last 60 stable releases
Nothing withdrawn
no release was ever pulled
15 years old
82 releases · first in 2011
- Bumped css-tree to ~2.2.0
css-tree to ~2.2.0 (#458)Fixed usage filtering for from/to selectors in keyframes at-rule
medium in font declarations since it breaks the value since <font-size> is required (#449)One column per quarter.
Fixed CommonJS version bundling when browser field is used
browser field is usedmodule fieldExtended Node.js support to include ^10
^10main field in package.json to refer to CommonJS modulecss-tree to ~2.0.4 (fixed CSS serialization issue in IE11)Bumped css-tree to ~2.0.3 (various fixes on generate CSS)
css-tree to ~2.0.3 (various fixes on generate CSS)csso/syntaxAdded support for CSS Selectors Level 4 and legacy pseudos in specificity calculation, i.e. :has() , :not(<selector-list>) , :is() , :matches() , :-mo
:has(), :not(<selector-list>), :is(), :matches(), :-moz-any(), :-webkit-any(), :where(), :nth-child(... of <selector-list>) and :nth-last-child(... of <selector-list>)^12.20.0, ^14.13.0 and >=15.0.0dist/csso.js (an IIFE version with csso as a global name) and dist/csso.esm.js (as ES module). Both are minifiedcss-tree to 2.0:has(), :not(<selector-list>), :is(), :matches(), :-moz-any(), :-webkit-any(), :where(), :nth-child(... of <selector-list>) and :nth-last-child(... of <selector-list>)^12.20.0, ^14.13.0 and >=15.0.0 (extended in 5.0.2 to include ^10)dist/csso.js (an IIFE version with csso as a global name) and dist/csso.esm.js (as ES module). Both are minifiedcss-tree to 2.0Trim Custom Property values when possible
Fixed build setup to exclude full mdn/data that reduced the lib size:
mdn/data that reduced the lib size:
mdn/data that reduced the lib size:
Fixed wrongly merging of TRBL values when one of them contains var()
Prevented percent sign removal in flex / -ms-flex
flex/-ms-flex (#410)Fixed clean stage to avoid exceptions when source has unparsed or bad parts (#369, #378, #380, #397)
Bumped CSSTree to `1.0.0-alpha.37` to avoid source map generation inconsistency across Node.js versions
1.0.0-alpha.37 to avoid source map generation inconsistency across Node.js versionsRefreshed dev dependencies and scripts
1.0.0-alpha.36 (#399)dist/csso.js and dist/csso.min.js instead single dist/csso-browser.js (min version)compress() as syntax.compress()Bumped CSSTree to 1.0.0-alpha.29 (fixes some issues)
1.0.0-alpha.29 (fixes some issues)Migrated to CSSTree 1.0.0-alpha.27
1.0.0-alpha.27WARN: Internal API (i.e.
csso.syntax) was changed, see CSSTree's release notes for details
Added percent sign removal for zero percentages for some properties that is safe (@RubaXa, #286)
-ms-flex due it breaks flex in IE10/11 (#362)Fixed merge of position declarations when sticky fallback is using (thanks to @gruzzilkin, #356)
position declarations when sticky fallback is using (thanks to @gruzzilkin, #356)position declarations when sticky fallback is using (@gruzzilkin, #356)Migrated to CSSTree 1.0.0-alpha25
1.0.0-alpha25
Fixed named color compression to apply only when an identifier is guaranteed to be a color. Used CSSTree's feature to determine the meaning of a part
Arial Black, and with no hacks ;)@keyframes to the beginning of style sheet@keyframes, @media and @supports with no prelude@keyframes (#202)forceMediaMerge to force media rules merging. It's unsafe in general, but works fine in many cases. Use it on your own risk (#350)CSSTree to 1.0.0-alpha23@keyframes to the beginning of style sheet (chunk), but after @charset and @import rules@keyframes, @media and @supports with no prelude@keyframes (#202)forceMediaMerge to force media rules merging. It's unsafe in general, but works fine in many cases. Use it on your own risk (#350)CSSTree to 1.0.0-alpha23Fixed crash on a number processing when it used not in a list
Implemented optimisation for none keyword in border and outline properties (@zoobestik, #41)
none keyword in border and outline properties (@zoobestik, #41)rgba(x, x, x, 0) to transparentcalc(1px+2px) has been optimized to calc(1px2px) before, now it stays the same):nth-*(), :has(), :matches and other pseudos)blacklist filtering in usage (#334, see Black list filtering)css-tree to 1.0.0-alpha19Fixed declaration merging when declaration contains an !important
!importantInitially, CSSO used Gonzales for CSS parsing into AST, tree traversal and translating it back to a string. During last year numerous refactorings tra
Initially, CSSO used Gonzales for CSS parsing into AST, tree traversal and translating it back to a string. During last year numerous refactorings transformed it into something different. Eventually, AST backend became a brand new solution, too powerful to use it just for a single task like a CSS minification. Therefore it was extracted into a standalone project called CSSTree and developing as an independent.
CSSTree became stable enough. So it's a time bring it back to CSSO as dependency and get fast and efficient CSS parsing and other AST operations. At the same time, CSSTree is more specs compliant and works better for edge cases. Starting this release CSSO is based on CSSTree and gets great tools it provides.
Changes don't affect high-end users but those who're using internal API to deal with AST. The most significant change – every AST method is now behind syntax property of CSSO public API. Actually, the entire CSSTree syntax is behind syntax property. Worth noting that CSSTree that still in active development and syntax API shape may be changed in next CSSO releases while migration on new CSSTree versions. AST format was changed too, but it quite stable now so no surprises expected here.
Command line interface was integrated into CSSO from its beginning. It was common for tools in time when CSSO were invented. Since than frontend developing has been changed, task runners and bundlers rule the world today. Most users use CSSO plugins for task runners and bundlers rather than CLI. That's why extracting it into a separate package (csso-cli) looks reasonable to reduce dependency count. With this release, CLI should be installed separately.
syntax propertyFixed infinite loop on value parse
- Added \0 IE hack support
\0 IE hack support (#320)Added beforeCompress and afterCompress options support
beforeCompress and afterCompress options support (#316)Fixed shorthand optimisation issue when value has a color value or something unknown
cursor broken fallback (#306)Implement AST cloning by adding clone() function and clone option for compress() function
clone() function and clone option for compress() function (#296)[attrName i])Fix wrong declaration with \9 hack merge
\9 hack merge (#295)New option comments to specify what comments to left: exclamation, first-exclamation and none
comments to specify what comments to left: exclamation, first-exclamation and noneoffset to CSS parse error detailsoffset computationIt has been almost six months since CSSO came back to active development. During this time, it has become much faster, reduce memory consumption and h
It has been almost six months since CSSO came back to active development. During this time, it has become much faster, reduce memory consumption and has got new cool features. It is time to discard the legacy API to keep moving forward.
If you have used the CLI nothing has changed for you. Basically, the changes affect the internal API users and plugin authors. But the changes are not dramatic and aimed to provide a simple and consistent API.
For example earlier minify() method returns a string or an object, depending on whether the source map is required or not. Now the method is less confusing and always returns an object.
var csso = require('csso');
// before
console.log(csso.minify(css));
console.log(csso.minify(css, { sourceMap: true }).css);
// now it's consistent
console.log(csso.minify(css).css);
console.log(csso.minify(css, { sourceMap: true }).css);
Most significant changes is about AST format. For long time, CSSO used gonzales parser and its AST format for CSS transformation. That format is good enough, but not so convenient to maintain and extend. Therefore in 1.5.0 internal AST format was introduced. Despite the fact that the internal format is better in many ways, it led to unnecessary overhead in time and memory, as required converting gonzales<->internal. In addition, it has led to the need for dual-API – one for gonzales AST and another for internal.
Parser was reworked to build AST in "internal" format, that become single format is using by CSSO now. Gonzales format and related code was totally removed. The huge diff but it's worth it! This, as well as the introduction of a brand new dynamic scanner, made the parser faster by ~30%. And together with no extra converting compression accelerated at least by ~15% and reduced memory consumption by almost half.
These are important changes for the upcoming features! See current state of API in readme.
gonzales AST format and related codeminify() and minifyBlock() is always return an object as result now (i.e. { css: String, map: SourceMapGenerator or null })parse()
internal)context argument, context should be specified via optionsstylesheet, atrule, atruleExpression, ruleset, selector, simpleSelector, block, declaration and valueneedPositions option, positions option should be used insteadneedInfo option, info object is attaching to nodes when some information is requested by optionsoptions should be an object, otherwise it treats as empty objectcompress()
outputAst option{ ast: Object })justDoIt(), stringify(), cleanInfo()Fix wrong declaration with \9 hack merge
\9 hack merge (#295)Don't remove spaces after function/braces/urls since unsafe
The main feature of the release is usage data support. By default the optimizer doesn't know how CSS is using on markup and performs safe transformati
The main feature of the release is usage data support. By default the optimizer doesn't know how CSS is using on markup and performs safe transformations only. But such knowledge allows to do much more.
CSSO doesn't collect any information about CSS using (that's other tools task) but can use usage data to perform filtering and better compression. Data can be provided via --usage option for CLI or usage option for minify() and compress() methods.
Usage data can be used to filter selectors that contains something not in a white list. You can provide lists for tag names, class names or ids.
> cat example.css
.with { color: red; }
.usage.data { color: green; }
.we.can.do.more, .data { color: blue; }
.not.only.usage { color: yellow; }
> csso example.css
.with{color:red}.usage.data{color:green}.data,.we.can.do.more{color:#00f}.not.only.usage{color:#ff0}
> cat usage.json
{
"classes": ["usage", "data"]
}
> csso example.css --usage usage.json
.usage.data{color:green}.data{color:#00f}
CSS scope isolation solutions such as css-modules are becoming popular today. Scopes are similar to namespaces and defines lists of class names that exclusively used on some markup. This information allows the optimizer to move rulesets more agressive. Since it assumes selectors from different scopes can't to be matched on the same element. That leads to better ruleset merging.
In example we get better compression using scopes information (29 bytes extra saving).
> cat example.css
.module1-foo { color: red; }
.module1-bar { font-size: 1.5em; background: yellow; }
.module2-baz { color: red; }
.module2-qux { font-size: 1.5em; background: yellow; width: 50px; }
> csso example.css
.module1-foo{color:red}.module1-bar{font-size:1.5em;background:#ff0}.module2-baz{color:red}.module2-qux{font-size:1.5em;background:#ff0;width:50px}
> cat usage.json
{
"scopes": [
["module1-foo", "module1-bar"],
["module2-baz", "module2-qux"]
]
}
> csso example.css --usage usage.json
.module1-foo,.module2-baz{color:red}.module1-bar,.module2-qux{font-size:1.5em;background:#ff0}.module2-qux{width:50px}
Compression improvement with this feature depends on project structure. For a project in which we tested the feature, it gives 25-38% CSS size reduction compared to result without using usage data (the project uses its own CSS module isolation solution).
See more detail about feature in readme.
Some tools are using CSSO not only for stylesheet compression but also for style attribute compression. If use minify() method for this task it raises a parse error. So tool's authors do something like that:
var csso = require('csso');
function compressStyleAttributeContent(options) {
var tmp = '.dummy{' + style + '}';
var compressed = csso.minify(tmp, options);
return compressed.replace(/^\.dummy\{|\}$/);
}
compressStyleAttributeContent('color: rgba(255, 0, 0, 1); color: yellow', options);
// > 'color:#ff0'
Now it's all can be replaced for minifyBlock() function. No more hacks!
var csso = require('csso');
css.minifyBlock('color: rgba(255, 0, 0, 1); color: yellow', options);
// > 'color:#ff0'
--usage to pass usage data file.csso.minifyBlock() for css block compression (e.g. style attribute content).@media or @supports) now can be skipped during ruleset merge lookup if doesn't contain something prevents it.:not()) to pseudo signature to avoid unsafe merge (old browsers doesn't support it).pass block mode to tokenizer for correct parsing of declarations properties with // hack
// hack@import and @charset removal on double exclamation commentsupport for CSS Custom Properties
!important when check for property overriding (#280)text-align declarations with some values (#281)/deep/ combinator on translate, since it together with universal selector can produce a commentlogger option for compress() and minify()- npm publish issue
npm publish issue (#276)add file to generated source map since other tools can relay on it in source map transform chain
file to generated source map since other tools can relay on it in source map transform chainneedPosition parser option is deprecated, positions option should be used instead (needPosition is used still if positions option omitted)
:not() parsing and selector groups in :not() is supported now (#215)needPosition parser option is deprecated, positions option should be used instead (needPosition is used still if positions option omitted)csso.internal.*minify() adds sourcesContent by default when source map is generated0.10 until major release (#275)fix exception on zero length dimension compress outside declaration
The main feature of this release is Source Maps support. To get a source map use --map CLI option. Source map can be inlined or saved to external file
The main feature of this release is Source Maps support. To get a source map use --map CLI option. Source map can be inlined or saved to external file. For example:
$ echo '.example { color: #ff0000 }' | csso --map inline
.example{color:red}
/*# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIjxzdGRpbj4iXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsUSxDQUFXLFMiLCJzb3VyY2VzQ29udGVudCI6WyIuZXhhbXBsZSB7IGNvbG9yOiAjZmYwMDAwOyB9XG4iXX0= */
CSSO is trying to fetch input source map and use it for generating map (when --map option is used). More details and options see in readme.
Support of source maps needs the right positions in original CSS to be got on parsing and passed right to the end of optimisation till translation into string. This led to the need for a number of changes in the parser and ended by its full remake. Also compressor was reworked, its code simplified (for example, by using lists instead of arrays) and various optimisations were added. As the result CSSO became 2x faster:
| Library | 1.5.4 | 1.6.0 | Diff |
|---|---|---|---|
| 960.css - 9989 bytes | 91.9 ms | 43.31 ms | 2.1 |
| animate.css - 71088 bytes | 256.75 ms | 120.37 ms | 2.1 |
| blueprint.css - 17422 bytes | 94.59 ms | 46.31 ms | 2.0 |
| bootstrap.css - 147427 bytes | 544.69 ms | 249.96 ms | 2.2 |
| font-awesome.css - 28746 bytes | 109.36 ms | 20.7 ms | 5.3 |
| foundation.css - 200341 bytes | 500.43 ms | 208.74 ms | 2.4 |
| gumby.css - 167123 bytes | 413.62 ms | 201.71 ms | 2.1 |
| inuit.css - 53049 bytes | 101.66 ms | 18.46 ms | 5.5 |
| normalize.css - 7707 bytes | 24.81 ms | 4.19 ms | 5.9 |
| oocss.css - 40151 bytes | 70.98 ms | 35.21 ms | 2.0 |
| pure.css - 31318 bytes | 64.39 ms | 28.65 ms | 2.2 |
| reset.css - 1092 bytes | 7.02 ms | 2.57 ms | 2.7 |
It was hard before to realize where something is going wrong on CSS parsing and why. Here is the example:
$ echo '.test { color }' | csso
/usr/local/lib/node_modules/csso/lib/parser/index.js:115
throw new Error('Please check the validity of the CSS block starting from the line #' + currentBlockLN);
^
Error: Please check the validity of the CSS block starting from the line #1
at throwError (/usr/local/lib/node_modules/csso/lib/parser/index.js:115:11)
at getBlock (/usr/local/lib/node_modules/csso/lib/parser/index.js:523:14)
at getRuleset (/usr/local/lib/node_modules/csso/lib/parser/index.js:1585:18)
at getStylesheet (/usr/local/lib/node_modules/csso/lib/parser/index.js:1783:52)
at Object.CSSPRules.stylesheet (/usr/local/lib/node_modules/csso/lib/parser/index.js:80:65)
at parse (/usr/local/lib/node_modules/csso/lib/parser/index.js:2094:30)
at Object.minify (/usr/local/lib/node_modules/csso/lib/index.js:18:15)
at /usr/local/lib/node_modules/csso/lib/cli.js:38:31
at Socket.<anonymous> (/usr/local/lib/node_modules/csso/lib/cli.js:14:13)
at emitNone (events.js:72:20)
Refactoring of the parser helped to fix it. Starting with this release CSS error output has more details and solving problems should be much easier:
$ echo '.test { color }' | csso
Parse error <stdin>: Colon is expected
1 |.test { color }
---------------------^
2 |
Code coverage was set up and it helped to fix a number issues. Currently 99% of code is covered by tests. But truly speaking it doesn't mean minification is always correct (mostly because of structural optimizations). Minification improvements and its correctness is being the focus for next releases.
restructuring to restructure option for minify()/compress() (restructuring is alias for restructure now, with lower priority)one more fix (in restructRuleset this time) with merge of rulesets when a ruleset with same specificity places between them
restructRuleset this time) with merge of rulesets when a ruleset with same specificity places between them (#264)@keyframes rulesets (until sure it's correct)don't override display values with different browser support
don't merge rulesets if between them a ruleset with same specificity
ensure - is not used as an identifier in attribute selectors (thanks to @mathiasbynens)
- is not used as an identifier in attribute selectors (thanks to @mathiasbynens)justDoIt() functionThe main changes of this release are about compressor. It significant refactored in two aspects: code simplicity and performance. Also a new AST forma
The main changes of this release are about compressor. It significant refactored in two aspects: code simplicity and performance. Also a new AST format for compressor was introduced.
Let's start with clarification on the new AST format. As you may know csso uses gonzales to parse CSS to get AST. In the past it was one of fastest detailed CSS parsers written in JavaScript. But now gonzales isn't fast enough and hasn't support for some new things that were added to CSS last years. Further more gonzales uses arrays to describe AST nodes that isn't verbose, slow and expensive for memory. Also its format has several design mistakes and unsuitable for transformations as well.
Therefore, the replacement of the parser became obvious. But it isn't so easy since every single part of csso depends on AST format. Another problem is there is no suitable detailed CSS parser that can be a replacement for gonzales. So I have started working on this sort of parser a сouple of months ago. But still a lot of work to be done. Until that I decided to do first step for parser replacing by introducing new AST format (for now for internal usage only). Currently gonzales is used for getting initial AST which after that is converted into internal format. Theoretically it's possible to write AST convertors for other parsers too. Thus csso became less dependent on the parser.
Actually new format appeared in the middle of refactoring to simplify code and improve readability. But first tests showed performance boost too despite AST conversion overhead. That was the sign that we are on the right way.
Earlier source code of compressor was too complicated and was located in a single file. Now it is splitted into simple modules grouped by phase of processing: cleaning, compressing and restructuring. It makes dealing with compressor's source code much easier. Moreover, beside improved readability, the new AST format helped to simplify the source code since this format uses objects instead of arrays to describe AST nodes, has no redundant things and much more suitable for transformations.
Before we talk about performance, let's look at comparison table first (the table is based on css-minification-benchmark):
| Library | 1.4.4 | 1.5.0 | Diff |
|---|---|---|---|
| 960.css (9989 bytes) | 142.71 ms | 116.89 ms | 1.2 |
| animate.css (71088 bytes) | 432.12 ms | 290.27 ms | 1.5 |
| blueprint.css (17422 bytes) | 179.81 ms | 150.66 ms | 1.2 |
| bootstrap.css (147427 bytes) | 1022.78 ms | 547.48 ms | 1.9 |
| font-awesome.css (28746 bytes) | 173.37 ms | 93.74 ms | 1.8 |
| foundation.css (200341 bytes) | 1308.3 ms | 648.62 ms | 2.0 |
| gumby.css (167123 bytes) | 1537.72 ms | 452.67 ms | 3.4 |
| inuit.css (53049 bytes) | 160.17 ms | 103.83 ms | 1.5 |
| normalize.css (7707 bytes) | 10.73 ms | 9.58 ms | 1.1 |
| oocss.css (40151 bytes) | 111.71 ms | 75.98 ms | 1.5 |
| pure.css (31318 bytes) | 132.55 ms | 76.67 ms | 1.7 |
| reset.css (1092 bytes) | 24.15 ms | 7.2 ms | 3.4 |
As you can see, csso 1.5 is about 1.2–3.4 times faster than previous version. Take in account that those numbers contain time of all operations, i.e. parsing, compression and translation. In 1.4 compression takes more than ¾ of total time, but the new version takes less than a half of it. In fact compression become much faster than changes in total time because parsing takes significant time (one more reason to change parser) and has not changed.
As was mentioned above, some part of the performance boost due new AST format. It's not the single optimisation. Restructuring algorithms were also improved, redundant calculations and actions were removed, creating and copying of data structures was reduced, various caches and indexes are using now, some heuristics based on CSS specifics were used. Also final restructuring steps perform without a loop now. This loop was very expensive because snapshots (AST translating to string) were made in the beginning and in the end of each iteration and compared to check that iteration has good progress. It led to a lot of memory consumption and , even to processing freeze on large files. For example test file (3.7Mb) in issue #201 was never processed whereas now it takes about 12 seconds to be done (not so good but better than infinity). It is also worth noting that memory consumption has also been reduced by more than a half.
It's not the end of the compressor refactoring. The restructuring algorithms still need to be replaced for better ones that produce better results. More optimisation could be done when the parser will provide more details about CSS structure. So new improvements are coming.
gonzales→internal and internal→gonzales convertors, walkers, translator)@media with same queryoutputAst – new option to specify output AST format (gonzales by default for backward compatibility)from→0% and 100%→to at @keyframes (#205)@keyframes (#80, #197)gonzales AST was implemented--stat (output stat in stderr)level for --debug optionprevent removal of spaces after braces that before identifier that breaking at-rules expressions
fix unicode-range parsing that cause to wrong function detection
allow spaces between progid: and rest part of value for IE's filter property as autoprefixer generates this kind of code
progid: and rest part of value for IE's filter property as autoprefixer generates this kind of code (#249)allow merge for display property (#167, #244)
display property (#167, #244)rect (clip property value) mergeprepublish (thanks to @silentroach)deprecate justDoIt() method (use minify instead)
Back project to life. Changed files structure, cleaned up and refactored most of sources. Closed 60+ issues.
src folder)browserify (no more make, and web folder), browser version is available at dist/csso-browser.jslib/index.js nownode.js version is 0.12 nownpm (no more useless folders and files in package)jscs to control code stylegh-pages updateminify(src, options), options:
restructuring – if set to false, disable structure optimisations (true by default)debug - outputs intermediate state of CSS during compression (false by default)justDoIt() method (use minify instead)treeToString() method to stringify()printTree() methodcolumn and offset addedln renamed to line--rule and --parser (suppose nobody use it)-off alias for --restructure-off as incorrect (only one letter options should starts with single -)--debug that reflecting to options.debug for minify/deep/ combinator support (#209)hsla to rgba and hls to rgbrgba with 1 as alpha value to rgb (#122)rgb and rgba percentage values to absolute valuesrgba for normalized/interpolated values+ and - (#228)flex property as it could change value meaning (#200)\9 hack values (#231)!important issue for top-right-bottom-left properties (#189)top-right-bottom-left properties merge (#139, #175)We have been using csso in our building process for years. Not only as CSS compress utility, but also as a parser to get AST, transform it and pass to csso for compression.
All those years it worked stable enough and various bugs usually were not an issue. I have even rewritten some util functions to use it instead of csso functions. But project has no maintainer for years. In spite of the opinion that csso is dead, it has its own audience and number of downloads continues to grow. It's sad that many issues were left with no attention. Moreover I wanted to improve it to make CSS compression results much better and predictable.
That's why I've decided to change the situation. I’m happy that project's owners gave me this opportunity. I became maintainer of csso and started bringing it back to life.
As a first steps I've changed files structure, cleaned up and refactored most of sources. Now it has single code base, with reworked test suite, jscs for code style check, browserify to build the bundle, automated gh-pages publishing etc. All aim to ensure that nothing prevents us to move forward.
After initial refactoring and a bunch of bug fixes, it's now clear to me that all parts of csso need to be reworked. First of all compressor's struсtural optimization algorithms should be replaced for others, that are safer, faster and more efficient. Parser should become compliant to standards, with support for new CSS features and more predictable. When this is done, we can try new structural optimizations, that no one has tried before. That is the main reason why I've decided to work on csso.
Stay tuned!
@lahmatiy
Case insensitive check for !important (issue #187)
!important (issue #187)csso as cli command on Windows (issue #83, #136, #142 and others)Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
- Gonzales 1.0.7.
- Gonzales 1.0.6.
Protecting copyright notices in CSS: https://github.com/css/csso/issues/92
0s in Firefox for animations: https://github.com/css/csso/issues/100@page inside @media Causes Error: https://github.com/css/csso/issues/90
Your coding agent can read these notes before it upgrades. Set up the MCP server →