読者です 読者をやめる 読者になる 読者になる

$shibayu36->blog;

プログラミングの話や自分の考えを色々と書いています。特にperl、emacsや読んだ本の話が多いです。

JSをbrowserifyでビルドし、ライセンスコメントを適切に残す

tech

最近JSを利用するときは、依存モジュールはnpmを利用し、ES6やTypeScriptの仕様を開発には使った上で、ブラウザ用にコンパイルして配信するようになってきている。また同時にネットワークの負荷を下げるためにminifyを行う場合もある。

minifyはライセンスが絡むと少し難しい。例えばコメントを全て削除してしまうとライセンスコメントまで消えてしまう。この問題にはみんながそれぞれの手法で対処しているみたい。1年ほど前の記事でクライアントサイドJavaScriptのライセンス管理 | GREE Engineers' Blog というものがあり、いろんなJSのコンパイルのためのライブラリが独自でライセンスの形式を決めていて、それにマッチしないものは消えてしまう、みたいな辛いことが起きてそうだった。

そこで今回は自分の勉強も兼ねて、npmのモジュールを含めてブラウザ用にコンパイルし、ライセンスコメントも残すための最小のコンパイル環境を作ってみる。

やりたいこと

npmでインストールしたモジュールを使い、ブラウザで動かすようのJSを書き、それをコンパイルしてminifyする。この時に依存モジュールのライセンスコメントを出力する。

利用する技術

依存モジュールをインストールする

プロジェクトのrootでnpm initして適切にpackage.jsonの設定をしたあと、必要なモジュールを入れていく。

# ビルド用
npm install gulp --save-dev
npm install browserify --save-dev
npm install gulp-uglify --save-dev
npm install vinyl-source-stream --save-dev
npm install vinyl-buffer --save-dev
npm install licensify --save-dev

# ブラウザのJSで利用したいもの
npm install jquery --save-dev
npm install lodash --save-dev

JSを用意する

jqueryとlodashを利用するJSを用意する。今回はとりあえずcommonjs形式でrequireしているだけ。

src/js/app.js

var $ = require('jquery');
var _ = require('lodash');

gulpでライセンスを残しながらJSをビルドする環境を作る

以下の様なgulpfile.jsを用意することで、先ほど書いたやりたいことを実現できるbuild-jsタスクを用意できる。

gulpfile.js

var gulp = require('gulp');
var browserify = require('browserify');
var source = require('vinyl-source-stream');
var buffer = require('vinyl-buffer');
var uglify = require('gulp-uglify');

gulp.task('build-js', function () {
    return browserify({
        entries: './src/js/app.js' // どのファイルからビルドするか
    }).plugin('licensify') // licensifyプラグインの有効化
        .bundle() // browserifyの実行
        .pipe(source('app.js'))
        .pipe(buffer())
        .pipe(uglify({
            preserveComments: 'license' // ライセンスコメントを残しつつminify
        }))
        .pipe(gulp.dest('./static/js')); // 出力
});

これを実行することで、src/js/app.jsのファイルがビルドされて最終的にminifyされたJSがstatic/js/app.jsに出力される。

もう少し詳しく解説する。

licensifyプラグインを通しbrowserifyする

ライセンスコメントを確実に残すためにlicensify を利用する。browserifyのAPIでファイルを指定し、pluginメソッドでlicensifyを指定することで、licensifyプラグインを有効にした状態でJSをビルドすることが出来る。

browserifyをつかった後にvinyl-source-streamを使ったり、vinyl-bufferを使ったりしているのは、gulp と browserify と vinyl の話 - <body>gulpとBrowserifyでJSをビルドしてみた - Qiita を参考に。

ライセンスコメントを残す設定でuglifyする

gulp-uglifyのpreserveCommentsにlicenseを指定することでlicenseが記述されたコメントを残すことが出来る。

そもそもlicensifyを使わずにこのオプションを使えばライセンスコメントを残せると思っていたのだが、以下の二つの理由によりlicensifyを通したほうが確実と判断した。

  • 全ての形式のライセンスコメントに対応しているかわからない
  • いろいろ試してみたところなぜか出力されないライセンスコメントがあった
    • それがuglifyの奥底で起こってそうでデバッグにかなり時間が取られそうだったので諦めた

実際に実行してみる

以下のコマンドでビルドが行える。./node_moduels/.bin/gulpを利用しているのはグローバルなgulpコマンドを利用したくないため。

./node_modules/.bin/gulp build-js

するとstatic/js/app.jsにしたのように出力されるのでうまく行っていることが分かる。

/**
 * Modules in this bundle
 * @license
 * 
 * jquery:
 *   licenses: MIT
 *   author: jQuery Foundation and other contributors
 *   maintainers: dmethvin <dave.methvin@gmail.com>, scott.gonzalez <scott.gonzalez@gmail.com>, m_gol <m.goleb@gmail.com>, timmywil <timmywillisn@gmail.com>
 *   homepage: http://jquery.com
 *   version: 2.1.4
 * 
 * lodash:
 *   license: MIT
 *   author: John-David Dalton <john.david.dalton@gmail.com>
 *   maintainers: jdalton <john.david.dalton@gmail.com>, mathias <mathias@qiwi.be>, phated <blaine@iceddev.com>, kitcambridge <github@kitcambridge.be>, d10 <demoneaux@gmail.com>
 *   contributors: John-David Dalton <john.david.dalton@gmail.com>, Benjamin Tan <demoneaux@gmail.com>, Blaine Bublitz <blaine@iceddev.com>, Kit Cambridge <github@kitcambridge.be>, Mathias Bynens <mathias@qiwi.be>
 *   homepage: https://lodash.com/
 *   version: 3.10.1
 * 
 * This header is generated by licensify (https://github.com/twada/licensify)
 */
!function e(t,n,r){function i(u,a){if(!n[u]){if(!t[u]){var s="function"==typeof require&&require;if(!a&&s)return s(u,!0);if(o)return o(u,!0);var c=new Error("Cannot find module '"+u+"'");throw c.code="MODULE_NOT_FOUND",c}var l=n[u]={exports:{}};t[u][0].call(l.exports,function(e){var n=t[u][1][e];return i(n?n:e)},l,l.exports,e,t,n,r)}return n[u].exports}for(var o="function"==typeof require&&require,u=0;u<r.length;u++)i(r[u]);return i}({1:[function(e,t,n){/*!
 * jQuery JavaScript Library v2.1.4
 * http://jquery.com/
 *
 * Includes Sizzle.js
 * http://sizzlejs.com/
 *
 * Copyright 2005, 2014 jQuery Foundation, Inc. and other contributors
 * Released under the MIT license
 * http://jquery.org/license
 *
 * Date: 2015-04-28T16:01Z
 */
!function(e,n){"object"==typeof t&&"object"==typeof t.exports?t.exports=e.document?n(e,!0):function(e){if(!e.document)throw new Error("jQuery requires a window with a document");return n(e)}:n(e)}("undefined"!=typeof window?window:this,function(e,t){function n(e){var t="length"in e&&e.length,n=Z.type(e);return"function"===n||Z.isWindow(e)?!1:1===e.no
// 以下ずっとminifyされたjsが続く

まとめ

今回はnpmのモジュールを含めてブラウザ用にコンパイルし、ライセンスコメントも残すための最小のコンパイル環境を作ってみた。最近のJSはいろいろ進歩していて、昔は非常に面倒だったことが簡単に出来るようになって嬉しい。