Releases: css/csso
2.0.0 Drop legacy API and new AST format
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.
Changes
- No more
gonzales
AST format and related code minify()
andminifyBlock()
is always return an object as result now (i.e.{ css: String, map: SourceMapGenerator or null }
)parse()
- Returns AST in new format (so called
internal
) - Dynamic scanner implemented
- New AST format + dynamic scanner = performance boost and less memory consumption
- No more
context
argument, context should be specified viaoptions
- Supported contexts now:
stylesheet
,atrule
,atruleExpression
,ruleset
,selector
,simpleSelector
,block
,declaration
andvalue
- Drop
needPositions
option,positions
option should be used instead - Drop
needInfo
option,info
object is attaching to nodes when some information is requested byoptions
options
should be an object, otherwise it treats as empty object
- Returns AST in new format (so called
compress()
- No more AST converting (performance boost and less memory consumption)
- Drop
outputAst
option - Returns an object as result instead of AST (i.e.
{ ast: Object }
)
- Drop methods:
justDoIt()
,stringify()
,cleanInfo()
1.8.1
1.8.0 Usage data support, rules merge improvements and minifyBlock() function
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.
Filtering
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}
Scopes
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.
minifyBlock()
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'
Changes
- Usage data support:
- Filter rulesets by tag names, class names and ids white lists.
- More aggressive ruleset moving using class name scopes information.
- New CLI option
--usage
to pass usage data file.
- Improve initial ruleset merge
- Change order of ruleset processing, now it's left to right. Previously unmerged rulesets may prevent lookup and other rulesets merge.
- Difference in pseudo signature just prevents ruleset merging, but don't stop lookup.
- Simplify block comparison (performance).
- New method
csso.minifyBlock()
for css block compression (e.g.style
attribute content). - Ruleset merge improvement: at-rules with block (like
@media
or@supports
) now can be skipped during ruleset merge lookup if doesn't contain something prevents it. - FIX: Add negation (
:not()
) to pseudo signature to avoid unsafe merge (old browsers doesn't support it). - FIX: Check nested parts of value when compute compatibility. It fixes unsafe property merging.
1.7.1
1.7.0
- support for CSS Custom Properties (#279)
- rework RTBL properties merge – better merge for values with special units and don't merge values with CSS-wide keywords (#255)
- remove redundant universal selectors (#178)
- take in account
!important
when check for property overriding (#280) - don't merge
text-align
declarations with some values (#281) - add spaces around
/deep/
combinator on translate, since it together with universal selector can produce a comment - better keyword and property name resolving (tolerant to hacks and so on)
- integration improvements
- compression log function could be customized by
logger
option forcompress()
andminify()
- make possible to set initial line and column for parser
- compression log function could be customized by
1.6.4
1.6.3
1.6.2
- tweak some parse error messages and their positions
- fix
: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 ifpositions
option omitted)- expose internal AST API as
csso.internal.*
minify()
addssourcesContent
by default when source map is generated- bring back support for node.js
0.10
until major release (#275)
1.6.1
1.6.0 Source maps, verbose error output and performance boost
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.
Changes
- source maps support
- parser remake:
- compressor refactoring
- setup code coverage and a number of related fixes