Serving Jekyll with Grunt
If you are running a Jekyll blog, you might find that simply running
grunt watch and
jekyll serve parallel is really slow, since a change in any grunt-processed file (e.g. your
What’s gonna be in the Gruntfile
Grunt is a task runner that let’s you automate all kinds of actions especially for front-end development. It can be configured very flexibly through plugins. I am currently using the following services on this site:
- grunt-contrib-sass: compile Sass files
- grunt-postcss: autoprefix vendor prefixes and minify CSS
- grunt-contrib-copy: copy files (into the Jekyll build folder)
- grunt-shell: run shell commands
- grunt-contrib-watch: watch files and execute all the other scripts on change
- grunt-contrib-connect: serve a static site
The configuration is done in a file named
Gruntfile.coffee — I much prefer the CoffeScript syntax, which avoids the annoying issues with trailing
, for JSON like files. The Gruntfile has to first include all needed modules, which can be simplified with the load-grunt-tasks plugin, so we start with:
1 2 3
module.exports = (grunt) -> require('load-grunt-tasks')(grunt) grunt.initConfig
Configuring the CSS and JS Processors
Next in the Gruntfile we will configure the Sass compiler to compile all
.scss files in the
assets/css directory and save them in files of the same name with the extension
.min.css (we really don’t care about anything but the final files). In most cases this will only compile the single
main.scss file, files with a
_ at the start are ignored like expected. Than we will use PostCSS to run autoprefixer and csswring (a minifyer) on the created CSS files and simply overwrite them.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
# CSS Processors sass: options: compress: false sourcemap: 'none' scss: files: [ expand: true cwd: 'assets/css/' src: ['*.scss'] dest: 'assets/css/' ext: '.min.css' ] postcss: options: processors: [ require 'autoprefixer-core' require 'csswring' ] mincss: files: [ expand: true cwd: 'assets/css/' src: ['*.min.css'] dest: 'assets/css/' ]
@import "path/to/file"; statements. This one will only run on
.js files not
.min.js files, but change their extension to
.min.js in the process. Than I also run a minifyer/uglifyer on all
.min.js files. You might also want to do something like compile CoffeeScript or transpile ES6 syntax if you’re usig something like that.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
# JS Processors import: js: expand: true cwd: 'assets/js/' src: ['*.js', '!*.min.js'] dest: 'assets/js/' ext: '.min.js' uglify: minjs: files: [ expand: true cwd: 'assets/js/' src: ['*.min.js'] dest: 'assets/js/' ext: '.min.js' ]
Building the Jekyll Site
_site/ directory to be available to our local server. We will use the copy task for that and just copy all
.min.css (read the
main.min.css) file and all
.min.js files over into the
_site/ directory. Easy enough:
1 2 3 4 5 6 7 8 9 10 11
copy: mincss: expand: true cwd: 'assets/css/' src: ['*.min.css'] dest: '_site/assets/css/' minjs: expand: true cwd: 'assets/js/' src: ['*.min.js'] dest: '_site/assets/js/'
If any other file (mainly
md files) change, we will just trigger a
jekyll build via the shell plugin, since that’s what Jekyll is there fore after all. In production I usually want to compile with the
--drafts flag, so I include one task for that here and I also have another ruby script that I run which generates my archive pages without any plugins — read more about it in this post. There also is a dedicated grunt-jekyll plugin, but that didn’t work for me with drafts, is significantly slower, and not maintained well anymore, so the shell script seemed like the better option for now.
1 2 3 4 5 6 7
shell: jekyll_drafts: command: 'jekyll build --drafts' jekyll: command: 'jekyll build' archive: command: 'ruby archive/_page_generator.ruby'
Now we just need to make sure that the appropriate task are run. For that we configure the watch task. If any
.scss file changes, we will compile it and run PostCss on it and than copy it. If a
.js file is changed, we will run the import and uglify tasks and than copy the files. If any other file changes we will run the Jekyll build task and the ruby script. I specify all other files here manually, you have to make sure changes in the
_site/ directory are excluded and all other CSS/JS related files as well (this is not an if-else statement).
1 2 3 4 5 6 7 8 9 10 11 12
watch: css: files: ['assets/css/*.scss'] tasks: ['sass', 'postcss', 'copy:mincss'] js: files: ['assets/js/*.js', '!assets/js/*.min.js'] tasks: ['import', 'uglify', 'copy:minjs'] jekyll: files: ['*.html', '*.md', '*.yml', '*.png', '*.icon', '*.xml', '_drafts/*', '_includes/**', '_layouts/*', '_posts/*', 'archive/*', 'assets/img/**', 'assets/lib/*', 'assets/svg/*'] tasks: ['shell:jekyll_drafts', 'shell:archive', ]
Serving the site locally
No that we can automatically build everything neatly, we just have to also serve it loally, since we are not running our Jekyll server anymore. Luckily there is the grunt-contrib-connect plugin that allows us to do that from inside Grunt. We just have to specify a base directory and a port and that’s it.
1 2 3 4 5 6
connect: server: options: livereload: true base: '_site/' port: 4000
Lastly we configure two commands to be run from the console. The
grunt build just compiles everything once without drafts (so no need for any copying or watching here). This can be used before a commit instead of
jekyll build now. And than the
grunt serve command, which will use everything we just build to build and serve the site, just like
jekyll serve does, but with all the sweetness and configurability.
1 2 3 4 5 6 7 8 9 10 11
grunt.registerTask 'build', [ 'sass' 'postcss' 'import' 'uglify' 'shell:jekyll' ] grunt.registerTask 'serve', [ 'connect:server' 'watch' ]
Some other things to take care of
make sure to exclude the Grunt files by adding
exclude: [node_modules, Gruntfile.js, package.json] to your
_config.yml, if not the whole
node_modules folder will be copied each time. You don’t want that! Unfortunately it’s not possible to avoid this by just wrapping the whole source code in another directory, because GitHub Pages doesn’t allow it. Also make sure to delete all
--- front matter from you
.scss/.sass files to make sure the Jekyll-internal compilers don’t process these files, since Grunt is already taking care of that now.