Use Eleventy to compile your Sass files!

I first tried out Eleventy in the glorious year 2020. I quickly started to love it because of various things. Long story short, I felt empowered. There was just one thing that was missing for me. I did not want to add a task-runner to my project for compiling my CSS as I did in the old days. Eleventy seemed potent enough to handle it on its own. Spoiler: that is the case! In this post, you will first ready how two implementations by Max Böck and Mike Riethmuller work. Then, I present my ideal image of handling Sass files and how to implement it in Eleventy.

“Show me the code already!”

here is the example repository with a minimal implementation

Prior work

Max Böck created an eleventastic solution to this problem in his Eleventy Starter Kit. It compiles one Sass entry-file and turns it into a CSS file. It works great. You can split your Sass code, structure it in different folders, share Sass variables and finally import everything in this one file. Now, there are two situations I can think of where this one-entry-file approach is limiting:

  1. You have a complex thing with loads of CSS code somewhere on your website but you don't want to include this code in your main CSS file.
  2. You have an unhealthy obsession with performance and try to shave off as much unneeded code from your main CSS file as possible.

I, for my part, do not qualify for the former though.

There is another solution with multiple entry-files capabilities. Max showed me a very interesting and well-made Eleventy website which inspired his work. The Eleventy boilerplate supermaya by Mike Riethmuller implements a multiple-entry-file functionality. Three different files are involved in the CSS generation in this project. A global data file (which is a file placed in the _data directory) contains the information about the to-be-compiled Sass files. But there is more to it! Eleventy allows us to define global data files in JavaScript where the default export exports the data. That's what makes it possible to execute a function, out-sourced to a utils folder, which handles compiling the Sass files to CSS code. At this point, the Sass code is compiled but no CSS files have been written. This is were the Nunjucks file comes in. By using Eleventy's pagination on global data, it takes one compiled stylesheet at a time and writes it into a file. Awesome! I did not see Mike's solution before implementing it myself. And I am glad that I didn't because I would have shamelessly copied it and I would have never thought about it again. But I did think about it and I want to share with you what I learned. It might improve your workflow.

In a perfect world

Configuring Eleventy feels very natural and streamlined to me. You can start with an empty configuration file and add to it as you go. But you can perfectly create a happy little website without even touching (pun intended) an .eleventy file. The official docs are all you need as they tell you about the default directory structure and out-of-the-box behaviour. And this is what I like a lot. In a perfect world, we could have this for our Sass code as well. There are two conventions I can think of how Sass compilation could be handled:

  1. All Sass files in a certain folder are treated as entry-files. Partials can be placed in a child folder or somewhere else.
  2. All Sass files in a certain folder or it's nested folders are compiled and saved respecting their structure. Sass files starting with an underscore are partials and won't be compiled. As an example, this input folder
    |- components
    |  |- codepen.sass
    |
    |- _typography.scss
    |- _variables.scss
    |- main.scss
    
    should be transformed into this output folder
    |- components
    |  |- codepen.css
    |
    |- main.css
    

I prefer the second convention, as it provides more flexibility in creating a meaningful folder structure. So, that's how I wanted my website to work.

Implementation

My journey began after seeing (or copying) the implementation in Eleventastic, so let's start here. We create a styles.11ty.js file in our styles folder. Files ending with .11ty.js are template files just like .njk. The cool thing about them is that you have the power of JavaScript at your hand. There is one required function which is the render function. A data function is optional. Eleventastic uses the data method to hand over the information regarding the entry file to the render function and to specify the desired location of the output file to Eleventy. The render method, then, receives the information about the entry file, reads it, compiles it and returns the CSS code. Done.

Now, for the second part. We want to assign multiple entry-files. This feature is not covered by Eleventastic. Do you remember how supermaya solved this problem? Pagination in Eleventy can do this. So, we take the styles.11ty.js file we just created and add the pagination settings to the data function. Here, we define the data which is iterated, the size of each batch and an alias for the batch. The permalink (aka the output file's path) is a function which receives an object in which you can access the current batch via the alias. Setting the size to 1 allows us to process each Sass entry-file individually. Now, the permalink function can create a meaningful output path according to the input filename. Also, the render function receives one entry-file after another and can perform the compilation on it. Done again!

One thing is missing. We want it to work automagically. Magic is not real—but globs are and they are the closest we can get to flying brooms and trickle-down theory. So we create a glob which fulfils our vision of a perfect convention (see previous chapter). We might have to adapt our permalink function accordingly, but now we are finally done!

Conclusion

No need to add a task-runner to your project, let Eleventy handle it for you! Both presented solutions—Eleventastic and supermaya—do a great job. Max' solution has the advantage that everything is in one file. Neat and tidy! Mike's solution provides multiple entry-file support. I am quite happy with my solution which combines both approaches, plus adds 0-config for entry-files.