Reusing Design Systems with Yarn and Gulp

April 26, 2017

Throughout my years of working with design systems, I’ve developed a different perspective on front-end development. I no longer view projects as their own enclosed ecosystems, but rather as the result of my growth within my field. My hope is that with every completed project I walk away with a refined skill set that I can carry on to the next. But what happens when I go back to maintain a project that I’ve “outgrown”? Often times it’s simply not worth the time investment to keep an isolated design system updated.

In an ideal world, we should be able to let our projects grow along with us. Even if that’s not perfectly realistic, I think it’s valuable to develop a process that allows us to propagate our design systems across multiple projects as easily as possible. So as an attempt towards that goal, I’ve started using Sass framework packages as a place where my projects inherit their design systems. This allows me to focus on project specific styles while also giving me a hook to manage these systems across a wide range of projects while I grow as a developer. Here’s how it works:

The important thing to note here is that anything explicitly defined in the project specific styles act as an override to our base design system. This allows us to update our base system without fear of losing any customization that our specific project requires.

I’m going to walk through the way I accomplish this using BaseWeb, Yarn and Gulp. BaseWeb is my personal front-end development framework, but this workflow can be used with any framework such as Bootstrap, Foundation or even a personal framework. As long as the project is published on NPM, you can use this method.

Lets install our framework using Yarn. If you’re using your own personal framework, simply publish it to NPM and replace any reference to BaseWeb with your project’s package name:

$ yarn add baseweb --dev

Next, we’ll create our project specific SCSS directory (scss/) and a directory for storing our final CSS output (css/):

$ mkdir scss css

This part is specific to BaseWeb; we’ll copy over a few files from the package. We’ll only need three: baseweb.scss which contains all the import declarations, then _overrides.scss and _custom.scss partials—the former is where we override variables and the latter is a starter file for our project specific styles. Both of these are located in the custom directory so we’ll just copy the whole thing. For brevity, lets also quickly define an environment variable with the path to the BaseWeb package’s SCSS.

$ bw=node_modules/baseweb/src/scss/
$ cp -i $bw'baseweb.scss' scss
$ cp -R $bw'custom' scss

Note that the only real requirement here is a file to store our @import declarations. For more information on how BaseWeb overrides variable maps, check out the documentation here.

We can comment out or delete any imports we don’t need for our specific project from baseweb.scss. The only required imports in BaseWeb are the settings and core files, but these don’t actually output any styles when compiled. They simply provide variables and mixins that are used in other element or block components.

Next, we’ll need to put together some build scripts to compile our SCSS into production ready CSS. Lets add gulp and gulp-sass as development dependencies and create gulpfile.js for writing our tasks:

$ yarn add gulp gulp-sass --dev
$ touch gulpfile.js

Next, lets open up gulpfile.js with your favorite text editor, in my case that’s Atom. We’ll be defining a few variables and initiate our CSS task for processing SCSS.

var
  // Modules
  gulp = require('gulp'),
  sass = require('gulp-sass'),

  // Directories
  dir = {
    src: 'scss/',
    dest: 'css/',
    // This part is important. We're saving the path to our framework package.
    bw: 'node_modules/baseweb/src/scss/'
  }
;

gulp.task('css', function() {
  // Our CSS task goes here...
});

One important thing to note is that we’re storing the path to our framework package—we’ll be using that in a pretty cool way next. So lets put together our css task.

gulp.task('css', function() {
  return gulp.src(dir.src + 'baseweb.scss')
    .pipe(sass({
      outputStyle: 'compressed',
      includePaths: [
        dir.src, // 1. Check our project specific files
        dir.bw   // 2. Check our framework package
      ]
    })
    .on('error', sass.logError))
    .pipe(gulp.dest(dir.dest));
});

The real magic happens in the options we’re passing to gulp-sass which accepts the same arguments as node-sass. Specifically includePaths, where we include two paths; our project specific directory and the framework package. What this does is it will search in these two locations for a file that we call using @import. If a file can’t be found in our project specific path, Sass will look for that file in the framework package. Let’s give it a test run:

$ gulp css

We’ve just compiled our SCSS into a production ready CSS file: css/baseweb.css. Even though most of our imports aren’t located in the project itself, those get inherited through our framework package. Now we can work on our project specific styles and not worry about our base design system getting out dated or diverge with custom edits. Since these are separated, the latest updates to BaseWeb can be leveraged by running:

$ yarn upgrade baseweb

That’s it! For further reading on design systems, I highly recommend Brad Frost’s book “Atomic Design”. If you have any suggestions or tips on this method, let me know! I’d also love to know how you maintain your projects and the solutions you’ve come up with. You can find the source to this example in this repository.