Stu Robson is on a mission to “un-Sass” his CSS. I see articles like this pop up every year, and for good reason as CSS has grown so many new legs in recent years. So much so that much of the core features that may have prompted you to reach for Sass in the past are now baked directly into CSS. In fact, we have Jeff Bridgforth on tap with a related article next week.
What I like about Stu’s stab at this is that it’s an ongoing journey rather than a wholesale switch. In fact, he’s out with a new post that pokes specifically at compiling multiple CSS files into a single file. Splitting and organizing styles into separate files is definitely the reason I continue to Sass-ify my work. I love being able to find exactly what I need in a specific file and updating it without having to dig through a monolith of style rules.
But is that a real reason to keep using Sass? I’ve honestly never questioned it, perhaps due to a lizard brain that doesn’t care as long as something continues to work. Oh, I want partialized style files? Always done that with a Sass-y toolchain that hasn’t let me down yet. I know, not the most proactive path.
Stu outlines two ways to compile multiple CSS files when you aren’t relying on Sass for it:
Using PostCSS
Ah, that’s right, we can use PostCSS both with and without Sass. It’s easy to forget that PostCSS and Sass are compatible, but not dependent on one another.
postcss main.css -o output.css
Stu explains why this could be a nice way to toe-dip into un-Sass’ing your work:
PostCSS can seamlessly integrate with popular build tools like webpack, Gulp, and Rollup, allowing you to incorporate CSS compilation into your existing development workflow without potential, additional configuration headaches.
Custom Script for Compilation
The ultimate thing would be eliminating the need for any dependencies. Stu has a custom Node.js script for that:
const fs = require('fs');
const path = require('path');
// Function to read and compile CSS
function compileCSS(inputFile, outputFile) {
const cssContent = fs.readFileSync(inputFile, 'utf-8');
const imports = cssContent.match(/@import\s+['"]([^'"]+)['"]/g) || [];
let compiledCSS = '';
// Read and append each imported CSS file
imports.forEach(importStatement => {
const filePath = importStatement.match(/['"]([^'"]+)['"]/)[1];
const fullPath = path.resolve(path.dirname(inputFile), filePath);
compiledCSS += fs.readFileSync(fullPath, 'utf-8') + '\n';
});
// Write the compiled CSS to the output file
fs.writeFileSync(outputFile, compiledCSS.trim());
console.log(`Compiled CSS written to ${outputFile}`);
}
// Usage
const inputCSSFile = 'index.css'; // Your main CSS file
const outputCSSFile = 'output.css'; // Output file
compileCSS(inputCSSFile, outputCSSFile);
Not 100% free of dependencies, but geez, what a nice way to reduce the overhead and still combine files:
node compile-css.js
This approach is designed for a flat file directory. If you’re like me and prefer nested subfolders:
With the flat file structure and single-level import strategy I employ, nested imports (you can do with
postcss-import
aren’t necessary for my project setup, simplifying the compilation process while maintaining clean organisation.
Very cool, thanks Stu! And check out the full post because there’s a lot of helpful context behind this, particularly with the custom script.
Article goes the whole way without mentioning why anyone wants to do this. Yes there was a time when ultimately serving just 1 file was more efficient, but does the difference even matter anymore?
I went from SASS to SASS+PostCSS and just PostCSS, too. At some point I was not using much from PostCSS anymore and did not want to mess with plugins and setup so much.
Like Tailwind, I switched to Lightning CSS: It’s simple, transpiles a number of features (syntax lowering and vendor prefixing mostly), handles bundling and minification. Apart from all that it’s also very fast.
For my TS/JS projects I mostly use it with Vite and elsewhere with a CLI invocation which doesn’t even require a config file.
Nothing really wrong with PostCSS, but LightningCSS simplifies things a bit. Assuming Node is installed, this does compile CSS with import handling, custom media query support and a browserslist target spec:
PostCSS probably still offers more features through plugins (even though LightningCSS also supports plugins), but most people are probably not using many additional ones if any at all.
I’ve been using Parcel bundler for over 5 years now to handle all my CSS partials… transforming (PostCSS), bundling and minifying them.
If it wasn’t for the fact I also use TypeScript (which obviously requires bundling), I would probably switch to https://lightningcss.dev/.
I also recently dropped Sass in favour of vanilla CSS. I’d weaned myself off writing complex Sass mixins, functions and loops gradually over a number of years, which made the transition fairly painless!
I am not a fan of PostCSS due to its messy plugin ecosystem and poor DX. Custom syntax that is added via various plugins don’t have any language support in IDEs and it’s a pain to write code which shows as invalid and doesn’t have auto completion. There are a bunch of plugins which overlap in features, some are abandoned and others incomplete. It doesn’t work with stylelint. It fragmented the ecosystem too much. Plugins conflict with each other and have cryptic messages. I prefer lightningCSS.
You don’t really need to do that, do you?
Like most things, I’d say: it depends. I like to split things into super small files on some projects. Others, not so much. It’s just one of the things I’ve leaned on Sass forever and it’s nice to know there’s an alternative.
Unsass your workflow by using another separate tool…
I’ll stick with sass for now and just keep adopting the new native css stuff as I learn about it.
Sass has been a long reliable companion that I have had no issues with.
Really insightful read! Loved how clearly the options for compiling CSS files were explained — especially the balance between PostCSS and a custom script. Super helpful for front-end workflows
The guidance here is a few years outdated.
With HTTP/2 and HTTP/3, multiple smaller CSS files are now better than one monolithic file. Modern protocols eliminate the old bottlenecks through multiplexing (parallel downloads over one connection), header compression, and removal of the 6-connection limit.
Smaller fingerprinted CSS allows for better caching. Updating one component only invalidates that file, not all styles.
Developers can work on isolated stylesheets without affecting others
Critical CSS loads first while deferring non-essential styles
While you shouldn’t go to extremes (hundreds of tiny files), organizing CSS into logical modules (10-30 files) optimizes both performance and developer experience. Modern protocols have freed us to prioritize maintainability and caching efficiency over request minimization.