Frontend Build Tools

with NPM Scripts

Divya Sasidharan

Web Developer @KnightLab

 

Building story telling tools and apps for journalists and newsrooms

Storytime!

Legacy Build system

“fablib” is a small collection of helper functions for the fabric deployment and system administration tool.

 

You can download a model copy of a typical font CSS file from our CDN. You may want to put it through a CSS formatter before you start to edit it. If you are familiar with LESS, you may prefer to work from our LESS files on GitHub.

Mission:

Implement Build system

Confession:

I hate build tools

/src

/js/first.js

/js/second.js

/scss/first.scss

/scss/second.scss

/css/main.css

/assets/image.jpg

/assets/logo.jpg

/templates/partial.hbs

/templates/layout.hbs

Compile to CSS

Copy Assets

Compile to HTML

Compile JS

/dist

/assets/image.jpg

/assets/logo.jpg

/templates/index.html

/templates/index.hbs

/js/main.js

Compile + Transpile JS (ES2015)

Compile Templates (Jade/Handlebars/Mustache)

CSS Preprocessers (Sass/LESS)

Style transforms (Modernizr)

File minification 

Source Maps

CommonJS/AMD compatibility 

Handle Dependencies

Supports testing functionality (regression + integration)

Local Server that supports live reload

Specific Requirements:

Build system == Task Runner

Dependence on plugins and plugin maintenance

Unnecessary layer of abstraction

Tools are opinionated

Fragmented and inadequate documentation

Debugging build tools is pain

Problems with build tools

*ahem* Gulp and Grunt *ahem*

#goals

A build tool that just works

(Enter, pad left)

Why NPM Scripts?

We already use Node + NPM

CLI 💖 

grunt-sass

grunt-browserify

grunt-webpack

grunt-contrib-jasmine

grunt-contrib-watch

etc.

node-sass

browserify

webpack

jasmine

live-reload

etc.

{
    "scripts": {
        "test": "jasmine spec/*.js"
    }
}

package.json

npm run test

npm run

$Path Config

{
    scripts: {
        "test": "./node_modules/.bin/jasmine spec/*.js"
    }
}
{
    scripts: {
        "test": "jasmine spec/*.js"
    }
}

vs.

🎉🎉 🎉  

😭😭 😭  

package.json

package.json

Pre + post Hooks

{
    scripts: {
        "test": "jasmine spec/*.js",
        "pretest": "npm run lint",

        "lint": "jslint spec/*.js"
    }
}
npm run pretest
npm run test
npm run posttest

package.json

&&: Running Multiple Tasks

{
    "scripts": {
        "compile": "npm run templates && npm run sass && npm run copy",

        "templates": "node tasks/compile-hbs.js",
        "sass": "node-sass -o dist/css --output-style compact src/scss",
        "copy": "npm src/assets dist/assets"
    }
}

package.json

| : Streaming Tasks

{
    devDependencies: {
        "cssmin": "^0.4.3"
    },

    scripts: {
        "prod:css": "node-sass -o dist/css --output-style compact src/scss > 
                    dist/css/base.css | cssmin > dist/css/base.min.css"
    }
}

src/scss/*.scss => dist/css/base.css => dist/css/base.min.css

package.json

Decompose Tasks

var cssmin = require('cssmin'),
    fs = require('fs'),

    css = fs.readFileSync('dist/css/base.css')
    minify = css.min(css);


fs.outputFileSync('dist/css/main.min.css', min, 'utf8');
    

tasks/minify-css.js

{
    scripts: {
        "sass:min": "node tasks/minify-css.js"
    }
}

package.json

npm run sass:min

Write Less code

var source = require('vinyl-source-stream')
var streamify = require('gulp-streamify')
var browserify = require('browserify')
var uglify = require('gulp-uglify')
var gulpify = require('gulpify')
var rename = require('gulp-rename')
var gulp = require('gulp')

// using gulpify:
gulp.task('gulpify', function() {
  gulp.src('index.js')
    .pipe(gulpify())
    .pipe(uglify())
    .pipe(rename('bundle.js'))
    .pipe(gulp.dest('./'))
})

// using vinyl-source-stream:
gulp.task('browserify', function() {
  var bundleStream = browserify('./index.js')
                       .bundle()

  bundleStream
    .pipe(source('index.js'))
    .pipe(streamify(uglify()))
    .pipe(rename('bundle.js'))
    .pipe(gulp.dest('./'))
})
{
    "devDependencies": {
        "browserify": "^13.1.0",
        "uglifyjs": "^2.4.10"
    },

    "scripts": {        
        "browserify": "browserify index.js | 
        uglifyjs > bundle.js"
    }
}

gulpfile.js

package.json

But.....

what about Cross platform compatibility?

Solution 1: ShellJS

Portable unix shell commands for Node.js 

loadMethods = (function() {
    
    var shell = require('shelljs'),
        lodash = require('lodash-cli'),

        methodString = "",
        // add lodash methods used here //
        methods = [ 'assign',
                    'forEach'
                  ],
        outputFile = './src/js/lib/lodash.js';

    methods.forEach(function(method) {
        methodString += "," + method;
    })

    lodashCommand = 'lodash -d -o ' + outputFile + ' include=' + methodString

    shell.exec(lodashCommand, function() {
        console.log("Compiling lodash methods...")
    }); 
})();

tasks/loaderdash.js

Solution 2: Npm Packages

rm-rf

cp -r

&

mkdir

rim-raf

ncp

parallelshell

mkdirp

/bash

/node

Solution 3: &&, &, <, >, |

&&   :   Chaining tasks

&      :   Run Parallel Tasks

<      :   STdIn

>      :   Stdout

|        :   Pipe

Less Dependencies = Less Maintenance = Dev Happiness

Thank you.

@shortdiv

https://github.com/NUKnightLab/frontend-buildkit

Frontend Build Tools with NPM Scripts

By shortdiv

Frontend Build Tools with NPM Scripts

  • 577