Infinite | Squares

Art of code and more

Getting Started With Gulp

Gulp is a build system implemented using stream. You can use gulp to hinting and linting scripts, concatenate and minify them, automate testing and many others. I have been wanted to use Gulp in my Nodejs project for a while (I have tried Grunt once, but that not work well for me). This is my working with Gulp notes.

Installation

To start, install Gulp globally

1
npm install -g gulp

Next, install gulp as development module in your Nodejs project

1
npm install gulp --save-dev

Make gulpfile.js in your root project folder

1
2
3
4
5
var gulp = require('gulp');

gulp.task('default', function() {
  // gulp tasks here
});

Then run gulp from command line

1
2
3
4
$ gulp
[10:18:23] Using gulpfile ~\app\gulpfile.js
[10:18:23] Starting 'default'...
[10:18:23] Finished 'default' after 72 ยตs

Development style

Now that we have Gulp ready, we can start looking for flow that we want to use. I think we should customized our gulp workflow to our developments style. There are many tutorials available on how you can setup your Gulp flow. For example here and here

My stack now is Nodejs for backend, with MongoDB on database and standard HTML, CSS, Javascript trio on the front end. For Nodejs I use Expressjs and Swig rendering engine. So naturally I look for Gulp flow that suitable for my development approach.

For example, I usually start with default base template for other swig file to inherit. This will contains all commons Javascript and CSS files.

Swig base template
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<!DOCTYPE html>
<html lang="en">
  <head>

    <link rel="stylesheet" href="/css/font-awesome.min.css" />
    <link rel="stylesheet" href="/css/bootstrap.min.css">

    {% block styles %}{% endblock %}

  </head>

  <body>

    {% block content %}{% endblock %}

    <script src="/js/modernizr.custom.js"></script>
    <script src="/js/jquery-1.9.1.min.js"></script>
    <script src="/js/bootstrap.min.js"></script>
    <script src="/js/bootbox.min.js"></script>

    {% block scripts %}{% endblock %}

  </body>
</html>

If I want to make login page, I would do

Login page
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
{% extends "../template/centerbox.swig" %}

{% block styles %}
<link rel="stylesheet" href="/css/login.css" />
<link rel="stylesheet" href="/css/other.css" />
{% endblock %}

{% block content %}
<div class="content-area">
  <div class="row">
    <!-- Login form -->
  </div>
</div>
{% endblock %}

{% block scripts %}
<script src="/js/login.js"></script>
<script src="/js/other.js"></script>
{% endblock %}

My aim to to group assets in different swig file into different concatenated, minified and uglified scripts. If login page is rendered, it would be

Swig base template
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<!DOCTYPE html>
<html lang="en">
  <head>
    <link rel="stylesheet" href="/css/base.min.css" />
    <link rel="stylesheet" href="/css/login.min.css">
  </head>

  <body>

    <div class="content-area">
      <div class="row">
        <!-- Login form -->
      </div>
    </div>

    <script src="/js/base.min.js"></script>
    <script src="/js/login.min.js"></script>

  </body>
</html>

I think this approach is better than combining all files into one big min.js or min.css file. It provide separate file for asset that might be changes frequently. With that in mind I start looking available Gulp plugin for such task (someone must have thought that too!).

Gulp tasks

I found gulp-useref which do exactly as I wanted. and I also add gulp-rev to add hash of content into filename.

So head to your console and install the followings:

1
npm install gulp-useref gulp-if gulp-rev gulp-uglify gulp-minify-css gulp-rev gulp-rev-replace gulp-sequence del --save-dev

Then update your swig files

Swig base template updated with useref syntax
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
27
28
<!DOCTYPE html>
<html lang="en">
  <head>

    <!-- build:css /css/base.min.css -->
    <link rel="stylesheet" href="/css/font-awesome.min.css" />
    <link rel="stylesheet" href="/css/bootstrap.min.css">
    <!-- endbuild -->

    {% block styles %}{% endblock %}

  </head>

  <body>

    {% block content %}{% endblock %}

    <!-- build:js /css/base.min.js -->
    <script src="/js/modernizr.custom.js"></script>
    <script src="/js/jquery-1.9.1.min.js"></script>
    <script src="/js/bootstrap.min.js"></script>
    <script src="/js/bootbox.min.js"></script>
    <!-- endbuild -->

    {% block scripts %}{% endblock %}

  </body>
</html>

Do the same to the login page swig file.

Now, for my gulpfile.js

gulpfile.js
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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
var gulp = require('gulp');
var useref = require('gulp-useref');
var gulpif = require('gulp-if');
var uglify = require('gulp-uglify');
var minifyCss = require('gulp-minify-css');
var rev = require('gulp-rev');
var revReplace = require('gulp-rev-replace');
var del = require('del');
var gulpSequence = require('gulp-sequence');

// 1
gulp.task('swig', function () {
  var assets = useref.assets({
    searchPath: 'app/public'
  });

  return gulp.src('app/views/**/*.swig')
    .pipe(assets)
    .pipe(gulpif('*.js', uglify()))
    .pipe(gulpif('*.css', minifyCss()))
    .pipe(rev())
    .pipe(assets.restore())
    .pipe(useref())
    .pipe(revReplace({replaceInExtensions: ['.swig']}))
    .pipe(gulp.dest('dist/views'));
});

// 2
gulp.task('move:font', function () {
  return gulp.src(['./app/public/fonts/*'], { base: './app/public/' })
    .pipe(gulp.dest('dist'));
});

// 3
gulp.task('move:css', function () {
  return gulp.src(['./dist/views/css/*'], { base: './dist/views/' })
    .pipe(gulp.dest('dist'));
});

gulp.task('move:js', function () {
  return gulp.src(['./dist/views/js/*'], { base: './dist/views/' })
    .pipe(gulp.dest('dist'));
});

// 4
gulp.task('delete:cssjs', function (cb) {
  del(['dist/views/css/**', 'dist/views/js/**'], cb);
});

// 5
gulp.task('default', gulpSequence('clean','swig','move:font','move:css','move:js','delete:cssjs'));

// 6
gulp.task('clean', function (cb) {
  del(['dist/views/**','dist/js/**','dist/css/**','dist/fonts/**'], cb);
});

Now for the commentary

  1. This is where useref will scans all swig files, collect js and css inside useref tags, concat, minify, copy to destination then writing it back into swig files. It also use gulp-rev to add checksum.
  2. Copy font files into destination too so we have all assets in one place
  3. Somehow, minified js and css are copied into the same destination as output swig files. Move them into other location.
  4. Delete moved js and css files
  5. Default task. I use gulp-sequence because I found that moving and deleting runs in parallel and that can mess the whole things.
  6. Clean destination folder before updating the content.

If all running correctly, you will have

Swig base template
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<!DOCTYPE html>
<html lang="en">
  <head>
    <link rel="stylesheet" href="/css/base-d810cb7f.min.css" />
    <link rel="stylesheet" href="/css/login-632b05b7.min.css">
  </head>

  <body>

    <div class="content-area">
      <div class="row">
        <!-- Login form -->
      </div>
    </div>

    <script src="/js/base-784b8098.min.js"></script>
    <script src="/js/login-e410da1c.min.js"></script>

  </body>
</html>

Update on Express

You also need to update express to use your update swig files. My preference is to use config file.

1
2
3
app.engine('swig', swig.renderFile);
app.set('views', path.join(__dirname, config.useDist ? 'dist/views': 'app/views'));
app.set('view engine', 'swig');

Also because the asset is now has revisions, you don’t have to worry if you set long time for it to expire on browser assets (this will reduce request to server). Here I set to 1 year.

1
2
3
4
5
6
app.use(serveStatic(path.join(__dirname, 'dist'), {
  setHeaders: function setDistCacheControl(res, path) {
    res.setHeader('Cache-Control', 'public, max-age=' + (365*86400))
    res.setHeader("Expires", moment().add(1,'y').toDate().toUTCString());
  }
}))

That’s it. I hope this notes is useful for you. Let me know if you have any comment or feedback.

Comments