Atomic CSS classes and smaller bundles ⚡️ 📦

The DSS compiler converts every CSS declaration to an atomic CSS classes and returns a JSON object that contains mappings to the source rules.

Since we are using atomic CSS classes, declarations are deduped and the final bundle size should be small. With atomic CSS classes the file size growth is logarithmic since DSS produces rules only for new declarations. This strategy also makes critical CSS extraction unnecessary.

How it works

Given some CSS:

/* index.css */

.foo {
  display: flex;
  flex-direction: column;
  color: red;
}

.bar {
  display: flex;
  color: green;
}

the DSS compiler converts everything to atomic CSS classes and returns a Promise that resolves with an object that looks like the following:

{
  locals,
  css,
  flush,
}

locals

locals is an object where each selector is mapped to an array of atomic CSS classes:

{
 "foo": [
    "dss_14e3233-fkmc3a",
    "dss_1uacqdt-m23pbg",
    "dss_rfc3hq-169mlyl"
  ],
  "bar": [
    "dss_14e3233-fkmc3a",
    "dss_rfc3hq-5rjgso"
  ]
}

Locals should be written to disk as json as each CSS file is processed by DSS.

css

css is a function that returns the generated CSS.

/* css() */

.dss_14e3233-fkmc3a{display:flex}
.dss_1uacqdt-m23pbg{flex-direction:column}
.dss_rfc3hq-169mlyl{color:red}
.dss_rfc3hq-5rjgso{color:green}

When compiling multiple files css should be called at the end, only after all the files have been processed. This is because DSS collects rules as the files are processed.

flush

flush is like css except that it resets the internal collection of styles. When calling css multiple times you always get the latest styles. Instead if you call flush subsequent calls of either css or flush will return an empty string.

css()
// .dss_14e3233-fkmc3a{display:flex}
css()
// .dss_14e3233-fkmc3a{display:flex}

flush()
// .dss_14e3233-fkmc3a{display:flex}

css()
// ''

flush()
// ''

Putting everything together

const fs = require('fs')
const dss = require('dss-compiler').singleton

let getCSS

const source1 = fs.readFileSync('./component1/styles.css')

const first = dss(source).then(({ locals }) => {
  // locals contains the JSON above
  fs.writeFileSync('./component1/styles.css.json', JSON.stringify(locals))
})

const source2 = fs.readFileSync('./component2/styles.css')

const second = dss(source).then(({ locals, css, flush }) => {
  fs.writeFileSync('./component2/styles.css.json', JSON.stringify(locals))

  getCSS = flush
})

Promise.all([first, second]).then(() => {
  fs.writeFileSync('./bundle.css', getCSS())
})

When compiling multiple files, the JSON for each file should be written to disk and at the end of the compilation the CSS generated by DSS is available via a dss' css() call. The string returned by css contains the entire app CSS.

Note that DSS comes with a CLI and a Webpack loader/plugin that automates the process above, so that you can chill and just focus on writing styles!