Compiling Sass with Gulp

When I would do theming for Drupal 7 sites, I used Compass to compile the Sass files (in this case, scss) to CSS. With Drupal 8, you could do that as well (Sass doesn't care about the CMS) and while it is certainly possible to set up Compass under Lando, it is not really recommended as there can be conflicts with how Lando works. (Disclaimer: I have installed Compass for one project in Lando, and didn't have a problem.) Rather, it is recommended to use node-sass, and many prefer using Gulp to drive it as it's fairly simple.

While Gulp is simple, I had a terrible time getting it up-and-running with Lando. Most of that was with my expections, though. First a little background on what caused my headaches, and then how I overcame them and got Gulp working like a charm.

For some reason, I figured my .lando.yml file would contain everything I needed to start using Gulp in my custom themes. I was trying various searches on "lando gulp" or "lando gulp scss" and, to be honest, there just weren't very many articles out there. A couple that kept popping up were the "Frontend Tooling" and "How do I wire up Browsersync and Gulp in my Lando app?" articles on Lando's official documentation site. While I wasn't looking for the Browsersync aspect (though I hope to eventually have that implemented), I hoped to follow along with the Gulp instructions and start compiling. Unfortunately, that was not how this works. Adding the supplied steps to my .lando.yml file and adding a gulp.js file caused Lando to download the node_modules directory (containing some standard plugins for the node library), it would always complain about missing items when I tried to run Gulp. No matter how I searched, I just wasn't finding the solution.

Finally it dawned on me: developing the theme doesn't need to rely on Lando. Sure, Lando needs to have the tools available to get the theme started, but getting the sass to compile is part of the theme, not the overall container. Now I started looking for how to setup and use Gulp independently of Lando, and things started making more sense and I was making progress at getting it to actually work!

First, we need to make sure we can use npm in our container. (You could also use Yarn, but I'll be using npm.) In case you're curious, npm (Node Package Manager) allows for installing Javascript packages and dependencies. We will first adjust our .lando.yml file to setup a node service and we will add tooling for npm and gulp:

services:
  node:
    type: node
    globals:
      gulp-cli: "latest"

tooling:
  npm:
    service: node
  node:
    service: node
  gulp:
    service: node
  yarn:
    service: node

What this is doing is adding a node service and installing the latest Gulp CLI. We then make the tooling available on the command line. I'm showing the tooling for yarn, but we won't need it here.

This gives us the ability to run npm (via lando npm), so now we can get our theme set up. We'll come back to the .lando.yml file later.

Next we go into our custom theme directory (e.g. themes/custom/demo) and run lando npm init. You can basically accept the default values, though you might wish to add a description or author information.

$ lando npm init
This utility will walk you through creating a package.json file.
It only covers the most common items, and tries to guess sensible defaults.

See `npm help init` for definitive documentation on these fields
and exactly what they do.

Use `npm install <pkg>` afterwards to install a package and
save it as a dependency in the package.json file.

Press ^C at any time to quit.
package name: (demo2) 
version: (1.0.0) 
description: 
entry point: (index.js) 
test command: 
git repository: 
keywords: 
author: 
license: (ISC) 
About to write to /app/themes/custom/demo2/package.json:

{
  "name": "demo2",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "ISC"
}


Is this OK? (yes) 

This creates the package.json file used by npm to track your packages. Now we'll need to add some for compiling SASS, so issue the following command (we can install multiple packages in a single command):

$ lando npm install gulp gulp-sass gulp-plumber

Next we need to add a gulpfile.js file. At this point we are keeping it very simple and only compiling the scss files into css. We could also compress javascript files or images, but I'm usually happy letting Drupal take care of compression and aggregation.

Create a new file in the theme's directory named gulpfile.js and add the following code to it:

// Include commands from the packages
var   gulp    = require('gulp'),
      watch   = require('gulp'),
      plumber = require('gulp-plumber'),
      sass    = require('gulp-sass');

// CSS task
function css() {
  return gulp
    .src('./scss/**/*.scss')
    .pipe(plumber())
    .pipe(sass({
      sourceComments: 'map',    // Add comments to the compiles css file
      sourceMap: 'sass',        // The comments will be mapped to the source scss file
      outputStyle: "expanded"   // Makes the css more human-readable. Setting to "nested" would make the file a bit smaller.
    }))
    .pipe(gulp.dest('./css'));  // Output the css files in the css directory
}

// Watch files
function watchFiles() {
  // Monitor the scss directory for changes in the scss files.
  //  If there was a change, compile it into css
  gulp.watch('./scss/**/*.scss', css);
}


// Export the functions so we can call them from the command line
exports.watch = watchFiles;
exports.css = css;

// By setting the default, we can simply run 'lando gulp' to start watching for changes.
exports.default = watchFiles;

At this point, if we were to run lando gulp or lando gulp watch, it would start watching for changes in the scss directory. If we ran lando gulp css, it would perform a one-time compilation of the css files. Go ahead and add/edit some scss files and give it a try.

Source control and rebuilding

When you deploy your code, you don't need to include the node_modules directory in the theme directory. Additionally, you could also leave the css directory out of your git repository and use continuous integration to build it before deploying to your server.

To make sure we have the node_modules directory and packages in our Lando app, we will add a build step in the .lando.yml file (remember, I said we'd revisit that). All we need to do is add a build command to the node service to change into the theme directory and perform an npm install to pull the packages as defined in the package.json file. We can also compile our CSS files to make sure they are up to date.

    build:
      - cd themes/custom/demo && npm install && gulp css

Now, whenever we start or rebuild our Lando app, it will update the npm packages and build the CSS.

Summary

We should now be able to use Gulp to compile our SASS files into CSS, either by doing a one-time compile (lando gulp css) or by initiating the watch to monitor for changes (lando gulp or lando gulp watch). At a bare minimum, we should have the following in our .lando.yml and gulpfile.js files:

.lando.yml

name: my-lando-app  # Name of your lando app
recipe: drupal8     # Whatever recipe you are using
config:
  webroot: .        # Where your Drupal code is located
services:
  node:
    type: node
    globals:
      gulp-cli: "latest"
    build:
      - cd themes/custom/demo && npm install && gulp css
tooling:
  npm:
    service: node
  node:
    service: node
  gulp:
    service: node

gulpfile.js

// Include commands from the packages
var   gulp    = require('gulp'),
      watch   = require('gulp'),
      plumber = require('gulp-plumber'),
      sass    = require('gulp-sass');

// CSS task
function css() {
  return gulp
    .src('./scss/**/*.scss')
    .pipe(plumber())
    .pipe(sass({
      sourceComments: 'map',    // Add comments to the compiles css file
      sourceMap: 'sass',        // The comments will be mapped to the source scss file
      outputStyle: "expanded"   // Makes the css more human-readable. Setting to "nested" would make the file a bit smaller.
    }))
    .pipe(gulp.dest('./css'));  // Output the css files in the css directory
}

// Watch files
function watchFiles() {
  // Monitor the scss directory for changes in the scss files.
  //  If there was a change, compile it into css
  gulp.watch('./scss/**/*.scss', css);
}


// Export the functions so we can call them from the command line
exports.watch = watchFiles;
exports.css = css;

// By setting the default, we can simply run 'lando gulp' to start watching for changes.
exports.default = watchFiles;