Skip to content

Instantly share code, notes, and snippets.

@itspoma
Last active March 7, 2018 06:47
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save itspoma/ee9810b00fd76e9007c1202396b42e58 to your computer and use it in GitHub Desktop.
Save itspoma/ee9810b00fd76e9007c1202396b42e58 to your computer and use it in GitHub Desktop.

Comparison of JavaScript Task Runners & Build Systems

CURSOR Education ©

Comparing via Google Trends:

List of task-runners:

Prepare folder-structure of pet-project

└─ app/
   ├── assets/
   │    ├── scripts/
   │    │    ├── main.js
   │    │    ├── page-index.js
   │    │    └── page-login.js
   │    │    └── page-help.jsx
   │    ├── styles/
   │    │    ├── main.scss
   │    │    ├── mixins.scss
   │    │    ├── const.scss
   │    │    ├── page-index.scss
   │    │    └── page-login.scss
   │    └── images/
   │        ├── icons-social/
   │        │   ├── instagram.png
   │        │   ├── instagram.png
   │        │   └── youtube.png
   │        └── logout.png
   └── build/
        └── index.html

The above folder-structure is created & located on cursor-education/sample-frontend-app repo, so you can clone & use it:

$ git clone https://github.com/cursor-education/sample-frontend-app.git

Setup

$ npm install npm@latest -g
$ npm init --yes
$ npm -v
3.10.5

Sample of package.json

{
  "name": "sample-frontend-app",
  "scripts": {
    "test": "echo \"hello\" && exit 0",
    "build": "echo \"build\""
  }
}

You can additionally read about exit-status and list of exit-codes.

List of reserved script actions - https://docs.npmjs.com/misc/scripts.

  • npm install will run preinstall, install, postinstall actions one-by-one
  • npm test will run pretest, test, posttest actions one-by-one

Usage:

$ npm run-script test
$ npm run test
$ npm test               # only for reserved script names

To compile SASS into CSS (using node-sass):

$ npm i node-sass
$ node_modules/.bin/node-sass --version
node-sass       3.8.0   (Wrapper)       [JavaScript]
libsass         3.3.6   (Sass Compiler) [C/C++]

$ node_modules/.bin/node-sass assets/styles/page-index.scss            # to build specific file & output to console
$ node_modules/.bin/node-sass assets/styles/page-login.scss -o build/  # to build specific file & save output into file
$ node_modules/.bin/node-sass assets/styles/ -o build/                 # to build all files in folder & save outputs into files
$ node_modules/.bin/node-sass assets/styles/ -o build/ --output-style compressed

$ find assets/styles/ -name "page-*.scss" -type f
$ find assets/styles/ -name "page-*.scss" -type f -exec cat {} | node_modules/.bin/node-sass > output \;

# "scripts": {
#   "build:css": "node_modules/.bin/node-sass assets/styles/ -o build/"
# }

$ npm run build:css

To cleanup the build directory, lets add the separate task:

$ rm -rf build/*.{js,css,png}

# "scripts": {
#   "cleanup": "rm -rf build/*.{js,css,png}"
# }

$ npm run cleanup

Lets add compiling of js-es6 into es5 using Babel CLI

$ npm i babel-cli

$ node_modules/.bin/babel --version
6.11.4 (babel-core 6.11.4)

# http://babeljs.io/docs/usage/cli/

$ node_modules/.bin/babel assets/scripts/page-help.jsx
$ node_modules/.bin/babel assets/scripts/ --out-dir build/
$ node_modules/.bin/babel assets/scripts/ --out-dir build/ --extensions ".jsx"

# "scripts": {
#   "compile-es6": "node_modules/.bin/babel assets/scripts/ --out-dir build/"
# }

$ npm run compile-es6

The images remaining, so we need to add task to create a sprites from sources-images. The css-sprite will be used.

$ npm i css-sprite

$ node_modules/.bin/css-sprite build/ assets/images/icons/*.png

$ node_modules/.bin/css-sprite build/ assets/images/icons/*.png --base64
# src="sprite.css", class="item icon-facebook"

The script with name "build:images" will be the same as above scripts.

Now, lets group above tasks (cleanup, css & js compiling, sprites) into one task "build"

# "scripts": {
#   "build:js": "npm run compile-es6",
#   "build": "npm run cleanup && npm run build:css && npm run build:js && npm run build:images"
# }

$ npm run build

The tasks can be combined into similar, like build:css, build:js, build:images, etc.

On the same way you can add any pre/post processors (that have command line tool), for instance:

More samples of complete scripts section:

  "scripts": {
    "cleanup": "rm -rf dest/*.*",
    "jade": "node node_modules/.bin/jade src/views/ --pretty --out dest/",
    "less": "node node_modules/.bin/less-cli src/styles/ && mv src/styles/*.css dest/",
    "build": "npm run cleanup && npm run jade && npm run less",
    "server": "node node_modules/.bin/http-server dest/",
    "watch": "npm run build && node node_modules/.bin/onchange 'src/**/*.*' -- npm run build"
  },
  • released at 2011
  • is a system for running a sequence of commands/tasks (is Task Runner, not build tool)
  • is the first “second generation” task runner
  • each task is is a plug-in that represents cli tool
  • over 1200 plugins, and you need to read the doc of each of them
  • plugin take an file(s) on input, do some transormation, and save into output
  • each plugin has configuration located on Gruntfile.js
  • configurations are over js objects (configuration over code)
  • the "wrapper" function on Gruntfile.js is module.exports = function(grunt) { ... }

Setup

$ npm install grunt-cli grunt
$ node_modules/.bin/grunt --version
grunt-cli v1.2.0
grunt v1.0.1

Sample of Gruntfile.js

module.exports = function(grunt) {
   grunt.initConfig({
      pkg: grunt.file.readJSON('package.json'),
      uglify: { all: { /* ... */ } }
   });
   
   grunt.loadNpmTasks('grunt-contrib-uglify');
   
   grunt.registerTask('default', ['uglify']);
}

Lets add jshint task:

$ npm i grunt-contrib-jshint

$ npm init --yes
$ touch Gruntfile.js

module.exports = function(grunt) {
  // project configuration
  grunt.initConfig({
    pkg: grunt.file.readJSON('package.json'),
    jshint: {
      all: ['Gruntfile.js', 'assets/scripts/**/*.js']
    }
  });
  grunt.loadNpmTasks('grunt-contrib-jshint');
  // default task
  grunt.registerTask('default', ['jshint']);
};

$ node_modules/.bin/grunt

Other plugins:

  • released at 2013
  • configuration located on gulpfile.js
  • using the streams (example >1000 sass \w autoprefixer)
    • no temporary files
    • stream instances are basically Unix pipes
    • Unix pipes (and so nodejs’ streams) improve performance on I/O operations
    • you can plug the output of one stream to the input of another
    • Grunt’s way of doing things is more disk heavy resulting in lower performance than Gulp
  • based on pipes (use a series of connected tubes instead of a big truck)
  • single responsibility (one action) for each plugin ("task" means sequence of actions)
  • less config, more code

Setup

$ npm i gulp
$ node_modules/.bin/gulp --version
[00:46:32] CLI version 3.9.1
[00:46:32] Local version 3.9.1

API

  • gulps.task(name, fn) define a task by passing its name and a function.
  • gulp.run(tasks…) runs all tasks
  • gulp.watch(glob, fn) runs a function when a file that matches the glob changes
  • gulp.src(glob) this returns a readable stream which can then be piped to other streams (plugins)
  • gulp.dest(folder) returns a writable stream. Objects piped into this are saved to the file system.

Sample of gulpfile.js

Compile sass to css using grunt-sass.

$ npm i grunt-sass
var gulp = require('gulp')
  , sass = require('gulp-sass');

gulp.task('sass', function () {
  return gulp.src('./assets/styles/**/*.scss')
    .pipe(sass().on('error', sass.logError))
    .pipe(gulp.dest('./build'));
});

gulp.task('default', ['sass']);

Adding sass:watch task.

$ node_modules/.bin/gulp sass:watch
gulp.task('sass:watch', function () {
  gulp.watch('./assets/styles/**/*.scss', ['sass']);
});

Adding gulp-sourcemaps

var sourcemaps = require('gulp-sourcemaps');

gulp.task('sass', function () {
  return gulp.src('./assets/styles/**/*.scss')
    .pipe(sourcemaps.init())
    .pipe(sass({ outputStyle: 'compressed' }).on('error', sass.logError))
    .pipe(sourcemaps.write())
    .pipe(gulp.dest('./build'));
});
gulp.task('sass:watch', function () {
  gulp.watch('./assets/styles/**/*.scss', ['sass']);
});

Adding autoprefixer Interactive demo.

var autoprefixer = require('gulp-autoprefixer');

  .pipe(autoprefixer({
    browsers: ['last 5 versions'],
    cascade: false
  }))

Adding simple static server to serve static

$ npm i npm install connect@2.X.X
var connect = require('connect');

gulp.task('server', function() {
  connect()
    .use(require('connect-livereload')())
    .use(connect.static('./build'))
    .listen('9000');

  console.log('Server listening on http://localhost:9000');
});

http://localhost:9000/index.html

<script type="text/javascript" src="/base.css"></script>

Adding livereload

$ npm i connect-livereload tiny-lr gulp-livereload
tiny-lr

Adding linting & minification for js.

  • supports a module system (AMD, CommonJS, ES6)
  • supports Babel, ReactJS, CommonJS, among others

Setup

$ npm i webpack
$ node_modules/.bin/webpack --version
webpack 1.13.1

$ node_modules/.bin/webpack ./app.js bundle.js

Sample of webpack.config.js file

var webpack = require('webpack');

module.exports = {
  entry: './entry.js',
  output: {
    path: __dirname,
    filename: 'bundle.js'
  },
  module: {
    loaders: [
      {
        test: /\.css$/,
        loaders: ['style', 'css']
      }
    ]
  },
  plugins: [
    new webpack.optimize.UglifyJsPlugin()
  ]
};
  • webpack --watch
  • npm i webpack-dev-server
  • webpack-dev-server
  • loaders

Setup

$ npm install browserify
$ node_modules/.bin/browserify --version
13.1.0

Sample of usage

$ npm i underscore
$ node_modules/.bin/browserify assets/scripts/test-browserify.js -o build/test-browserify.js

The "Hello World" sample

$ echo 'console.log("Hello world.")' > hello.js

$ node hello.js
Hello world.

$ node_modules/.bin/browserify hello.js -o hello-bundle.js
$ cat hello-bundle.js

(function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
console.log("Hello world.")},{}]},{},[1]);

$ node hello-bundle.js
Hello world.

Btw, you can beautify your js here.

Sample of using exports

exports.beep = function (n) { return n * 1000 };
exports.boop = 555;

var beep = require('./beep.js').beep;
var boop = require('./boop.js').boop;
console.log(beep(5), boop);

To Read

  • is shipped with Coffeescript and is not supported without it
  • almost everything is described through code logic (opposite of Grunt)
  • the recipes is on Cakefile
  • no plugin system
  • npm/cake

Setup

$ npm install -g coffee-script cacke
$ cake -- version
  • released at 2013
  • tasks based on code logic and plugin system for helpers
  • "will figure out by itself which files to watch, and only rebuild those that need rebuilding"
  • caching system built on top of its core abstraction of file trees
  • comparison with other build systems

Setup

$ npm install -g broccoli
$ brocoli --version
  • acts more as framework by providing you with project template
  • about 382 plugins

Setup

$ npm i brunch
$ node_modules/.bin/brunch --version
2.8.2

$ node_modules/.bin/brunch new testAppBrunch
$ cd testAppBrunch/ && node_modules/.bin/brunch build

Sample

  • the new action

Setup

$ npm i mimosa
$ node_modules/.bin/mimosa --version
2.3.32

$ node_modules/.bin/mimosa new mySampleApp
$ tree -d mySampleApp

mySampleApp
├── assets
│   ├── javascripts
│   │   ├── app
│   │   │   └── template
│   │   └── vendor
│   └── stylesheets
├── node_modules
│   ├── mimosa-ractive
│   │   └── src
│   │       └── client
│   ├── mimosa-sass
│   │   └── src
│   ├── mimosa-typescript
│   │   └── src
│   ├── ractive
│   ├── typescript
│   │   └── bin
│   │       └── resources ...
│   └── typescript-api
│       └── bower_components
│           └── dt-node
└── views

$ node_modules/.bin/mimosa skel:list     # list of skeletons
$ node_modules/.bin/mimosa skel:new angular-node myAngularSampleApp
$ node_modules/.bin/mimosa skel:list

Setup

$ npm install pint
$ node_modules/pint/bin/pint --version
0.2.1

Sample of Pintfile.js

'use strict';

module.exports = {
  jobs: [
    require('./build/test.js'),
  ]
};
  • based in co-routines, generators and promises
  • cascading tasks

Setup

$ npm install fly fly-esnext
$ node_modules/.bin/fly --version
fly, 1.3.1

Sample of flyfile.js

const paths = {
  scripts: ['src/**/*.js', '!src/ignore/**/*.js']
}

export default async function () {
  await this.watch(paths.scripts, 'build')
}

export async function build() {
  await this.source(paths.scripts)
    .eslint({
      rules: {'no-extra-semi': 0}
    })

  await this.source(paths.scripts)
    .babel({
      presets: ['es2015', 'stage-0']
    })
    .concat('app.js')
    .target('dist')
}
  • operates with nodes (source, transformation, merging, etc), that reprensents contents of an input folder
  • about 89 plugins

Setup

$ npm install -g gobble gobble-cli
$ node_modules/.bin/gobble --version
gobble-cli version 0.7.0

$ npm i gobble-sass
  • node = gobble('foo') represents foo/ directory
  • node = gobble([node1, node2, etc]) merging the input nodes
  • node2 = node1.transform(transformer, options) applying transformer to node1
  • node.include(patterns) filter files with match to pattern
var gobble = require('gobble');

module.exports = gobble([
    gobble('build/'),
    gobble('assets/styles').transform('sass', {
      src: 'page-index.scss',
      dest: 'page-index.css'
    })
])

and navigate your browser to http://localhost:4567.

$ node_modules/.bin/gobble                 # to build & server via built-in server
$ node_modules/.bin/gobble build dest/     # to build & save outputs into dest/ folder

Setup

$ npm install plumber plumber-cli
$ node_modules/.bin/plumber --version
plumber-cli v0.4.5
plumber v0.4.8

$ npm i plumber-libsass

Sample of Plumbing.js

var all   = require('plumber-all')
  , glob  = require('plumber-glob')
  , sass  = require('plumber-libsass')
  , write = require('plumber-write');

module.exports = function(pipelines) {
    var sources = glob.within('assets');
    var writeToDist = write('buildx');

    // compile stylesheets
    pipelines['compile:css'] = [
        all(
            sources('styles/page-index.css')
        ),
        sass()
        writeToDist
    ];
}
  • scaffold (to automatically build the folder-structure)
  • set of generators, more than 4000+

Setup

$ npm install yo
$ node_modules/.bin/yo --version
1.8.4

$ node_modules/.bin/yo

Sample of material-ui genetator

$ npm i generator-material-react
$ node_modules/.bin/yo material-react
$ cd app && start

Sample of angular genetator

$ npm i generator-angular
$ node_modules/.bin/yo angular
$ node_modules/.bin/yo angular --help    # to list generators
$ npm i                                  # to install generated nodejs dependencies
$ node_modules/.bin/bower install        # to install generated bower dependencies
$ node_modules/.bin/grunt serve
$ node_modules/.bin/yo angular:route about
$ node_modules/.bin/yo angular:route contact
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment