Skip to content

Releases: css/csso

2.0.0 Drop legacy API and new AST format

06 Apr 02:35
Compare
Choose a tag to compare

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() and minifyBlock() 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 via options
    • Supported contexts now: stylesheet, atrule, atruleExpression, ruleset, selector, simpleSelector, block, declaration and value
    • Drop needPositions option, positions option should be used instead
    • Drop needInfo option, info object is attaching to nodes when some information is requested by options
    • options should be an object, otherwise it treats as empty object
  • 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

30 Mar 21:02
Compare
Choose a tag to compare
  • Don't remove spaces after function/braces/urls since unsafe (#289)

1.8.0 Usage data support, rules merge improvements and minifyBlock() function

24 Mar 12:42
Compare
Choose a tag to compare

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

16 Mar 21:03
Compare
Choose a tag to compare
  • pass block mode to tokenizer for correct parsing of declarations properties with // hack
  • fix wrongly @import and @charset removal on double exclamation comment

1.7.0

10 Mar 22:00
Compare
Choose a tag to compare
  • 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 for compress() and minify()
    • make possible to set initial line and column for parser

1.6.4

01 Mar 17:18
Compare
Choose a tag to compare
  • npm publish issue (#276)

1.6.3

29 Feb 21:40
Compare
Choose a tag to compare
  • add file to generated source map since other tools can relay on it in source map transform chain

1.6.2

29 Feb 20:40
Compare
Choose a tag to compare
  • 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 if positions option omitted)
  • expose internal AST API as csso.internal.*
  • minify() adds sourcesContent by default when source map is generated
  • bring back support for node.js 0.10 until major release (#275)

1.6.1

28 Feb 13:20
Compare
Choose a tag to compare
  • fix exception on zero length dimension compress outside declaration (#273)

1.6.0 Source maps, verbose error output and performance boost

27 Feb 16:09
Compare
Choose a tag to compare

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:
    • various parsing issues fixed
    • fix unicode sequence processing in ident (#191)
    • support for flags in attribute selector (#270)
    • position (line and column) of parse error (#109)
    • 4x performance boost, less memory consumption
  • compressor refactoring
    • internal AST is using doubly linked lists (with safe transformation support during iteration) instead of arrays
    • rename restructuring to restructure option for minify()/compress() (restructuring is alias for restructure now, with lower priority)
    • unquote urls when possible (#141, #60)
  • setup code coverage and a number of related fixes