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.
Change log
Parser
- attach minus to number
Compressor
- split code base into small modules and related refactoring
- introduce internal AST format for compressor (
gonzales
→internal
andinternal
→gonzales
convertors, walkers, translator) - various optimizations: no snapshots, using caches and indexes
- sort selectors, merge selectors in alphabet order
- compute selector's specificity
- better ruleset restructuring, improve compression of partially equal blocks
- better ruleset merge – not only closest but also disjoined by other rulesets when safe
- join
@media
with same query outputAst
– new option to specify output AST format (gonzales
by default for backward compatibility)- remove quotes surrounding attribute values in attribute selectors when possible (issue #73)
- replace
from
→0%
and100%
→to
at@keyframes
(#205) - prevent partial merge of rulesets at
@keyframes
(#80, #197)
API
- walker for
gonzales
AST was implemented
CLI
- new option
--stat
(output stat instderr
) - new optional parameter
level
for--debug
option