With the recent release of Jekyll 4.0, it was a good time to revisit how I manage my blog’s assets. The web and its tools continue to move in a break-neck speed and new versions like to break things so finding up to date best practices and code samples was a bit difficult. This is one reason why I’m writing this post.
My previous pipeline was built on Ruby’s Rake, so the tasks were defined in a
- I had previously used Bootstrap as the framework for styling so one task compiled the LESS files.
- Jekyll’s own build process would resize and transform images using bespoke plugins1.
- Another task ran
pngcrushon the destination (
_site/images) to optimize the transformations.
- Finally, one task deployed the built site using
The tools were varied and scattered all around the place. I wasn’t looking forward to update my plugins to take advantage of the new version of Jekyll2. However, there’s no real disadvantage in using Rake if you’re using Jekyll - both are Ruby so there is not that much new to learn.
So, one option was to build everything in Jekyll and continue use Rake tasks. I had already started to move to use SCSS, which was supported in Jekyll so this was at least in the realm of possibility.
However, why maintain your own tools when there are much better options available3.
The other option was to move fully to use a Node based build flow, of which there are many4. For no particular reason I went with Gulp. In fact, I first made sure that Gulp worked with the tools I wanted to use: Sass5, PostCSS and Imagemin.
The general idea of the architecture I was bulding was that
- Tasks are defined in Gulp’s
- Gulp compiles and minifes Sass to
- Gulp resizes and optimizes from
- Jekyll builds only the HTML content
- Other Rake tasks are ported to a Gulp tasks (like
First thing to note that normally Jekyll destroys the destination folder before building, so here Jekyll’s configuration needs to be updated so that it leaves
_site/css untouched in
You might also want to exclude build related files from your site.
The whole Gulpfile
Here is the full Gulpfile I’m using to build this site in its Gulp 4.0 style syntax (exported normal functions instead of
One could use this in development by calling
gulp watch and
gulp serve (or better yet, make a parallel task of these two). Building for deployment can be achived with
gulp build && gulp deploy (or again, by making an additional task that runs these as a series).
As for the file structure, it’s possible to split tasks to different files, and many examples do this. One neat approach to this is to create a folder called
gulpfile.js and have a main file
index.js and tasks as their own
.js files. I don’t think my file is large or complex enough to warrant this. However, the above file will work identically if saved as
Another improvement would be to add variables for sources and destinations.
The other tasks are quite self-explanatory but CSS and image handling have a bit more complex flows that I will explain next.
In my case, the source files are
.scss files in
Above, I have added an example (commented out) how to add
gulp-sourcemaps for development purposes.
In my case, my main stylsheet imports from sanitize.css which I bring in as an npm package.
For Sass compiler to find this packaged file, I need to tell it to look for it in
includePaths. For reference, if you are using Jekyll’s built-in Sass compiler, you can achieve similar thing with configuration option:
The above is irrelevant if you are using
gulp-sass to compile your CSS.
Another way to import
normalize.css) file would be to bring it in using Gulp and as the PostCSS plugin
After compilation, the scripts runs the stylesheet through PostCSS plugins. Here through CSS nano for minification7 and Autoprefixer for automatic vendor prefixing of CSS rules8. There are various other PostCSS plugins that you can add to the array, for example the beforementioned
scope parameter, Kramdown’s default HTML converter adds
.highlighter-rouge class which is different from Rouge’s default
My images in
_images are in various sizes and formats. The above piece of code makes resized versions of the images that fit the layout of my site and optimizes the resulting files with
pngquant. Note that both of these are lossy,
imagemin defaults to lossless algorithms.
The resizing and variants are accomplished with
gulp-scale-images. To inject variants to the stream, I use
flatMap (as instructed in
gulp-scale-images documentation). If you only need one version of your images, you can remove the
.pipe(flatMap(retinaVersions)) row and the
Note that you will in any case need a function that adds the
file.scale map for
gulp-image-scale to instruct how to process the file. This can be achieved for example using
through2 as shown in
gulp-scale-images documentation and clarifying issue.9
Conversely, you can also add further variants for thumbnails and such in the function called by
flatMap. Note that you would need to add support in your Jekyll to actually use the retina or thumbnail variant (through a Liquid filter or other plugin). In my case, my Kramdown converter scans for a retina version and adds a
srcset to the
<img> tag if one is found10.
In the end, what did I actually get from bringing in npm and it’s thousands of packages into my project? Most importantly, I get access to the best practices tools like Sass, PostCSS and Imagemin. I also get a clean separation of duties where I can more easily change from Jekyll to something else if need be.
Everyone’s workflow is a bit different to scratch their own personal itch. The same applies to this. I would recommend to use it as starting point for your own tasks.
It is also possible that by the time you read this, a breaking update has made the syntax or parameters invalid. I recommend checking each plugin’s and tool’s up to date documentation for reference. That is how I ended up with the above, I found outdated snippets from various blogs and attempted to bring them up to date by referencing the source documentation.