Live-updating Sass in Eleventy and WordPress with Gulp and BrowserSync

I love Sass. Not SCSS, mind you. I'm talking about the original Syntactically Awesome Stylesheet syntax, AKA The Indented Syntax. It's not that I'm particularly old-school, or that I don't see that value in SCSS being a superset of CSS. It's just that I hate looking at extraneous pointy shapes in my code, and Sass gets rid of the sharpest, pointiest ones — {, }, and of course ;. It might seem a weird thing to care about, but being a visual designer is, among some wonderful things, a bit of a curse.

Needless to say, I'm always on the lookout for the best way to integrate Sass in whatever environment I happen to be working. The best DX, in my opinion, it when style changes are injected on to the page without needing to reload. It's the kind of thing you get with HMR-capable build tools (Vue CLI, Vite, etc.), and the result is instant feedback with minimal interruption.

Environment

When I'm building with non-JavaScript-centric tooling, Gulp has been my trusty go-to for years. I use it to orchestrates the bundling of my CSS and JavaScript, and every time I think to myself "Yeah, I could probably do that in Node scripts", I end up finding a new reason to stick with it.

One tool I consistently use with Gulp is BrowserSync. If you're not familiar, BrowserSync is a nodeJS utility that sets up a local server. It's the engine behind Eleventy's watch mode, and can do a ton of cool things to speed up development. Critically, the watch option allows BrowserSync to update the browser, whenever it detects a change to files. An amazing sub-feature of this watcher is that it can inject CSS changes without reloading, very similar to HMR.

Eleventy

The setup for Eleventy was a bit tricky to figure out, but works very well. The gist of it is to send the bundled files directly to the output folder (Elevnety's default being _site), and tell Eleventy's BrowserSync instance to watch for changes. It looks like this:

// gulpfile.js

const gulpSass = require('gulp-sass')(require('sass'))

const sass = () => gulp.src('path/to/sass/files')
  .pipe(gulpSass.sync({ outputStyle: 'compressed' }))
  // ... fun gulp stuff
  .pipe(dest('./_site/assets/css'))
// .eleventy.js

module.exports = (eleventyConfig) => {
  // ... fun eleventy stuff
  eleventyConfig.setBrowserSyncConfig({
    watch: true,
  })
}

This extends Eleventy's BrowserSync settings, adding the watch option on the project's folder. And the wonderful thing about this is that, by default, BrowserSync will inject CSS changes rather than reload the page.

It took me while to get to this because I was previously using Eleventy's addPassthroughCopy to send bundled assets to the _site folder. I thought that would be the only way to have the page update every time a change was made. But it turns out all I needed to do was tell the BrowserSync instance itself to watch for changes.

WordPress

Another great feature of BrowserSync is the ability to proxy existing local servers and watch their underlying folder for changes. Assuming my local WordPress server is at http://website.local (I use Local for all my WordPress sites), I can add the following to Gulp:

// gulpfile.js

const browserSync = require('browser-sync').create()

exports.serve = () => {
  browserSync.init({
    proxy: 'http://website.local',
  })
  gulp.watch('path/to/sass/files', sass)
}

const sass = () => gulp.src('path/to/sass/files')
  .pipe(gulpSass.sync({ outputStyle: 'compressed' }))
  // ... fun gulp stuff
  .pipe(dest('output/path/for/css/files'))
  .pipe(browserSync.stream())

First I initialize a BrowserSync instance at the top of my serve function, and then I pipe the stream method to the sass stream. Now, instead of pointing my browser at http://website.local, I can run gulp serve and go to http://localhost:3000 (the address and port are configurable, but I tend to stick with the default on this). Any time Gulp processes my Sass, it will stream (i.e. inject) the changes automatically in the browser.