CLOVER🍀

That was when it all began.

Babel(ES2015)+Browserifyでのビルド設定を、npm runのみとgulpを使って書く

最近Babelを始めて、それからテストやライブラリといったところの勉強へ進めていきたいのですが、いったんここらでビルド定義について自分なりにまとめておくことにしました。

たまに立ち止まって振り返らないと、よくわからなくなるので…。

今回、やりたいテーマは以下とします。

  • Babel(ES2015)を使ってビルド
  • Browserifyで、スクリプトはまとめる
  • 通常はSource Mapsを作成する
  • Watchifyを使用した継続ビルドをできるようにする
  • リリースビルド時は、Source Mapsは作成せずMinifyする
  • Webサーバーを起動する(ポートは8000)
  • Webサーバーの起動と継続ビルドを組み合わせることができるようにする
  • ビルド成果物のクリーンアップができるようにする

上記を、単純に「npm run」での設定と、gulpを使った両方で考えたいと思います。

ただ、gulpを使う場合もあくまで「npm run」経由での起動とし、gulpをグローバルにインストールするのは避けるものとします。

前提

npm initは済んでいるものとします。

また、Browserifyでまとめるコードは、以下の2つとします。
src/calc.js

export class Calc {
    constructor() {
    }

    add(a, b) {
        return a + b;
    }

    multiply(a, b) {
        return a * b;
    }
}

エントリポイントとなるコードは、こちら。
src/main.js

import "babel-polyfill";

import { Calc } from "./calc";

let calc = new Calc();
console.log("calc.add(1, 3) = " + calc.add(1, 3));
console.log("calc.multiply(2, 3) = " + calc.multiply(2, 3));

また、起動するコマンドは、単純に「npm run」する場合でもgulpを利用する場合でも、以下とします。

  • npm run build … Source Maps付きでビルド
  • npm run build:continuous … 変更を監視し、継続ビルド
  • npm run build:release … Source Mapsなしのリリース用ビルド
  • npm run server … Webサーバー起動
  • npm run server:continuous … build:continuousとserverを同時に実行

NODE_ENVとか見た方がいいのかも?とかある気がしますが、とりあえずこの方針で…。

単純な「npm run」編

まずは、単純に「npm run」で頑張る方を。

必要なモジュールをインストール。

## Babel
$ npm install --save-dev babelify babel-preset-es2015
$ npm install --save babel-polyfill

## Browserify and Minify
$ npm install --save-dev browserify watchify exorcist uglify-js

## clean
$ npm install --save-dev del-cli

## WebServer
$ npm install --save-dev browser-sync

Babelはさておき、Source Mapsの作成ではexorcistを使い、MinifyにはUglifyJs2を使います。

npm runでクリーンアップをするために、「del-cli」をインストール。

Webサーバー代わりのものとしては、設定がある程度できそうなBrowser Syncを使用することにします。

ES2015用のBabelの設定ファイルを作成。

$ echo '{ "presets": ["es2015"] }' > .babelrc

ソースコード配置用のディレクトリと、成果物出力用のディレクトリ(dist)を作成。

$ mkdir src dist

で、ソースコードは実装済みとします。

そして、タスクを定義するpackage.jsonのscriptsの部分は、このようにしました。

  "scripts": {
    "build": "browserify src/main.js --debug -t babelify | exorcist dist/app.js.map > dist/app.js",
    "build:continuous": "watchify src/main.js -v --debug -t babelify -o 'exorcist dist/app.js.map > dist/app.js'",
    "build:release": "browserify src/main.js -t babelify | uglifyjs --compress --output dist/app.js",
    "server": "browser-sync start --port 8000 --server . --no-open",
    "server:continuous": "npm run build:continuous & npm run server",
    "clean": "del dist/*"
  },

それぞれの説明は、前提のところに記載済みです。

なお、buildとbuild:continuousではMinifyは行われません…。

gulpç·¨

続いて、gulpを利用する場合。

モジュールのインストールは、こちら。

## Babel
$ npm install --save-dev babelify babel-preset-es2015
$ npm install --save babel-polyfill

## Browserify
$ npm install --save-dev browserify watchify

## gulp, minify, sourcemaps, WebServer
$ npm install --save-dev gulp gulp-load-plugins gulp-uglify gulp-sourcemaps gulp-plumber gulp-util vinyl-source-stream vinyl-buffer gulp-webserver

## clean
$ npm install --save-dev del

Source Mapsの作成は、gulpのものを使うことにします。

ES2015用のBabelの設定ファイルを作成。

$ echo '{ "presets": ["es2015"] }' > .babelrc

ソースコード配置用のディレクトリを作成。

$ mkdir src

gulpの設定は、こんな感じにしました。
gulpfile.js

"use strict";

const gulp = require("gulp");
const $ = require("gulp-load-plugins")();
const browserify = require("browserify");
const watchify = require("watchify");
const babelify = require("babelify");
const source = require("vinyl-source-stream");
const buffer = require("vinyl-buffer");
const del = require("del");

let createBundler = (plugins, debugBuild) => {
    return browserify({ entries: ["src/main.js"],
                 debug: debugBuild,
                 plugin: plugins
               })
    .transform(babelify);
};

let bundle = (bundler, debugBuild) => {
    let b = bundler
        .bundle()
        .on("error", $.util.log)
        .on("end", () => $.util.log("browserify compile success."))
        .pipe(source("app.js"))
        .pipe(buffer())
        .pipe($.uglify());

    if (debugBuild) {
        b = b
            .pipe($.sourcemaps.init({ loadMaps: true }))
            .pipe($.sourcemaps.write("./"));
    }
    
    return b.pipe(gulp.dest("dist"));
};

gulp.task("build", () => {
    let debug = true;
    bundle(createBundler([], debug), debug);
});

gulp.task("build:continuous", () => {
    let debug = true;
    let bundler = createBundler([watchify], debug);
    let rebundle = () => bundle(bundler, debug);

    bundler.on("update", rebundle);
    rebundle(bundler);
});

gulp.task("build:release", () => {
    bundle(createBundler([]));
});

gulp.task("server", () => {
    gulp
        .src("./")
        .pipe($.webserver({
            port: 8000,
            livereload: true  // enable reload
        }));
});

gulp.task("clean", () => del.sync("dist/*"));

…この設定だと、通常のビルド時でもMinifyしちゃいますけど、まあいいかぁと。

package.json側のscript定義は、単にgulpのタスクのラッパーとしました。

  "scripts": {
    "build": "gulp build",
    "build:continuous": "gulp build:continuous",
    "build:release": "gulp build:release",
    "server": "gulp server",
    "server:continuous": "npm run build:continuous & npm run server",
    "clean": "gulp clean"
  },

「server:continuous」は、gulp側の設定として「build:continous」を依存タスクとして定義してもよかったのですが、今回はnpm run側でコントロールすることにしました。

まあ、交換は簡単ですし。

まとめ

少し自分の頭の整理をするという意味で、ちょっとまとめてみました。

実現方法を2つ取った理由ですが、いろいろツールやその設定、モジュールの組み合わせをやっているうちに「gulpって必要なの?npmだけでもいいんじゃ?」と思い始めたのがきっかけです。

実行結果が完全に等価となるわけではありませんが、通常のビルドで似たようなことはだいたいできそうなので、いいかなぁと思います。