Optimzing Sass Compilation with Rollup

Posted by Nathan Cahill on

Sass lends itself well to modular design. Many features of the extension are designed to do just that, with @import, @extend, @[email protected]. The structure of Sass code tends to mirror the structure of component-based JS frameworks like React. I’ve seen a lot of codebases structured like this:

project
│   index.js
└───sass
│   │   _variables.scss
│   │   header.scss
└───components
    │   Header.js

You can picture what the Header component looks like:

import '../sass/header.scss'
export default Header = () => <MyAwesomeHeader />

This approach works fine, until the Sass files start importing dependencies of their own. Maybe header.scss imports framework/button.scss, but so does footer.scss. Now, we’re bundling duplicate code, that we have no way of detecting, since each import is compiled in it’s own context.

The more popular way to solve this is to setup a single entrypoint for the Sass code. Maybe a file like main.scss:

@import 'framework/button.scss';
@import 'header.scss';
@import 'footer.scss';

This solves the duplication issue, but since the Sass imports from the individual components are now gone, we’re stuck with maintaining a second entrypoint for Sass, probably imported in index.js:

import './main.scss'

Now, when we get rid of the <Header> component, we have to remember to go back and remove the now unused imports in main.scss. This problem is exacerbated in a component library: CSS must either be inlined for each component (see Material UI), the entire CSS must be included as a single file (see Reactstrap), or the user must be responsible for separately compiling the required CSS (unsure if any examples exist).

Solution

There might be a better way to import Sass modules alongside components, while maintaining the benefits of a single Sass entrypoint. I built rollup-plugin-collect-sass as a Rollup plugin to handle this scenario, and it works as a two-pass compiler:

  • First pass: It collects all of the Sass imports, normalizing all @import statements to absolute paths (after the first pass, current parent directory state is lost for the import).
  • Second pass: It compiles all collected Sass in a single context. Since the second pass has knowledge of the entirety of the imported Sass, it can deduplicate imported code. It also means that every component can be a standalone component, without inlining CSS.

I believe that the Rollup paradigm, with the bulk of efficiency being gained through intelligent compilers rather than in the initial writing of the code, is a great approach. Hopefully, this brings similar benefits to projects that combine JS and Sass.

Results

As an experiment, this blog’s CSS is built using Github’s Primer CSS toolkit, imported as React components, and then compiled with Rollup and rollup-plugin-collect-sass into static CSS and HTML. This drastically reduced the weight of the included CSS from 15kb down to 2kb. Some time soon, I’ll release that component framework as a lightweight compiler for static and dynamic components. Subscribe below for updates.


Join the mailing list

I send infrequent updates about upcoming projects, and links to interesting mapping and data visualization tools.

Thanks for subscribing.
Something went wrong, double-check that your email address is corret.