How Virga processes PostCSS
“Eleventy is zero-config by default but has flexible configuration options.”, according to Eleventy Documentation, which often means that you will most likely have to set up “configuration options” yourself.
One of the options I always set up is tooling around CSS.
Virga is based on Hylia and one of the reason why I’ve created Virga is to use PostCSS instead of Sass which is what Hylia uses.(I have written about “Differences between Hylia and Virga”.)
In this post, I’ll explain how Virga process PostCSS.
Configurations #
I have already explained how Virga structures CSS.
In short, since Virga has several directories and files inside of them, main.pcss
is there to “glue” them together.
Which is why I use postcss-import
and the process starts from here.
PostCSS can be configured by having .postcssrc.js
file(well, there are several ways). And here is Virga’s one.
// .postcssrc.js
module.exports = (ctx) => ({
map: ctx.env !== 'production' ? {inline: true} : false,
plugins: {
'postcss-import': {},
'postcss-preset-env': {
stage: 1,
},
autoprefixer: {},
cssnano: ctx.env === 'production' ? {} : false,
},
});
Next step of process is to “convert modern CSS into something most browsers can understand, determining the polyfills you need based on your targeted browsers or runtime environments” via PostCSS Preset Env
.
It is like Babel for CSS.
the third step is autoprefixer
which parses CSS and adds vendor prefixes to rule by Can I Use. Even though needs for “vendor prefixes” getting smaller now, I would still include this plugin.
The final step is cssnano
which is a modular minifier, built on top of the PostCSS ecosystem.
cssnano: ctx.env === 'production' ? {} : false;
Are you wondering what’s with this line?
This is a condition to use cssnano
or not based on environment variable. If it is production
then use cssnano
to minify CSS.
Unfortunately having .postcssrc.js
isn’t going to do all the process above so let’s dig deeper.
Development Build #
A “build” process is to have a file(in this case, CSS) which will be load into browsers.
Virga does this at src/_postcss/postcss.11ty.js
for development environment.
const fs = require('fs');
const path = require('path');
const postcss = require('postcss');
const postcssrc = require('postcss-load-config');
const {plugins, options} = postcssrc.sync();
const fileName = {
postcss: 'main.pcss',
css: 'main.css',
};
module.exports = class {
async data() {
const rawFilepath = path.join(__dirname, `./${fileName.postcss}`);
return {
permalink: `css/${fileName.css}`,
eleventyExcludeFromCollections: true,
rawFilepath,
rawCss: await fs.readFileSync(rawFilepath),
};
}
async render({rawCss, rawFilepath}) {
return postcss(plugins)
.process(rawCss, {
...options,
from: rawFilepath,
})
.then((result) => result.css);
}
};
I stole this from EleventyOne by Phil Hawksworth.
This file is processed by Eleventy. Although it doesn’t look like a “template language”, it actually is.
According to Eleventy Documentation, “Eleventy looks for classes that have a render
method and uses render
to return the content of the template.”
Since YAML Front Matter is not supported in JavaScript template types. data
method is there to pass data
to control how this template will render(in other words, build) a file.
So according to this setting, the final css will be generated into dist/css/main.css
.
Finally, you would want to add addWatchTarget
at .eleventy.js
so that when the file or the files in this directory change Eleventy will trigger a build which would look like this:
module.exports = function (config) {
config.addWatchTarget('./src/_postcss/');
};
You can take a look at actual .eleventy.js
file here.
Production Build #
As for production build, Virga utilizes PostCSS CLI
to do the build.
Why do you separate the build process between development and production?
It is a very good question.
Hylia inlines all CSS into <style>
in <head>
and Virga does this too.
In development environment, Virga generates CSS into dist/css/main.css
directly but In production, it is src/_includes/assets/styles/main.css
so CSS file is in Eleventy Layouts.
<style>
{% include "assets/styles/main.css" %}
</style>
By using include
you can “inline” CSS like above.
"build:postcss": "cross-env NODE_ENV=production postcss src/_postcss/main.pcss -o src/_includes/assets/styles/main.css"
When you run npm run build:postcss
, above command will be executed.-o src/_includes/assets/styles/main.css
does the “output” build css into src/_includes/assets/styles/main.css
.
All in all, this is why Virga separate build process.