gulp+ES6でフロントエンド開発で、ES6で書いたコードをBrowserifyでまとめてuglifyで圧縮する、という開発環境を作ってみたのだが、ソースコードが増えるに従ってビルドの時間が気になるようになってきた。たかだか数秒とはいえ、開発中は何度も修正・ビルド・リロードを繰り返すので少しでも時間が短いほうがよい。
そこで開発時にはES6から変換したJavaScriptのコードをまとめずに個別のままにしておいて、ブラウザ側でそれぞれ個別に読み込んだほうがサイクルとして速くなるんではないかと思って試してみた。
RequireJS
個別に分かれていて、依存関係のあるJavaScriptのスクリプトファイルをブラウザ上で自動的に読み込むには、RequireJSを使ってエントリポイントとなるファイルを指定してやればよい:
<script src="require.js" data-main="main.js"></script>
|
ES6からRequireJSで使える形式(AMD)への変換
BabelでES6からpresets: 2015
で普通に変換すると、CommonJS形式に?変換されてしまい、RequireJSでは使えない。RequireJSで読み込めるようにするにはAMD(Asynchronous Module Definition)という形式にする必要がある。
例:次のようなソースがあったとして:
import Util from './util' Util.log('Hello, ES6')
|
presets:es2015
で変換すると(babel --presets es2015 src/es6/main.js
):
'use strict';
var _util = require('./util');
var _util2 = _interopRequireDefault(_util);
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
_util2.default.log('Hello, ES6');
|
となってしまう。これをAMDという形式に変換するよう指定すると(babel --presets es2015 --plugins transform-es2015-modules-amd src/es6/main.js
):
define(['./util'], function (_util) { 'use strict';
var _util2 = _interopRequireDefault(_util);
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
_util2.default.log('Hello, ES6'); });
|
となって、RequireJSで使える形式に変換されるようになる。
gulpのタスクにする
以上のことをgulpのタスクにする。まずはパッケージのインストール
$ npm install --save-dev gulp-babel babel-plugin-transform-es2015-modules-amd
|
gulpfileにES6を変換する開発用のタスクを追加:
import babel from 'gulp-babel' import browser from 'browser-sync' import plumber from 'gulp-plumber' import sourcemaps from 'gulp-sourcemaps'
const srcEs6Dir = './src/es6' const srcEs6Files = `${srcEs6Dir}/**/*.js` const destJsDevDir = './public/jsdev'
const buildEs6ForDebug = (glob) => { return gulp.src(glob, {base: srcEs6Dir}) .pipe(plumber()) .pipe(sourcemaps.init({loadMaps: true})) .pipe(babel({ plugins: ['transform-es2015-modules-amd'], })) .pipe(sourcemaps.write('./')) .pipe(gulp.dest(destJsDevDir)) .pipe(browser.reload({stream: true})) }
gulp.task('es6', () => { buildEs6ForDebug(srcEs6Files) })
gulp.task('watch-es6', [], () => { gulp.watch(srcEs6Files, (obj) => { if (obj.type === 'changed') buildEs6ForDebug(obj.path) }) })
|
watch-es6
で監視するタスクでは変更があったファイルだけをビルドするようにする
- Lintも同様にする
リリース時には以前の内容と同様に、Browserify+uglifyで1つにまとめてやることにする:
const releaseAssetsDir = 'release/assets'
gulp.task('release', () => { browserify({entries: `${srcEs6Dir}/main.js`}) .transform(babelify) .bundle() .on('error', err => console.log('Error : ' + err.message)) .pipe(source('main.js')) .pipe(buffer()) .pipe(uglify()) .pipe(gulp.dest(releaseAssetsDir)) })
|
リリース時にはRequireJSを使わないようにする
リリース時にはせっかくJavaScriptのファイルを1つにまとめているので、RequireJSを使わないで直接読みこむようにしてやると無駄な読み込みが省けて精神衛生上よい。
HTMLから読み込むファイルを変えるには、HTMLを何らかの方法で変換してやる必要がある。ここではEJSというテンプレートエンジンを使って、分岐で切り替えることにする。開発時には変数buildTarget
に'debug'
を指定して、
gulp.src([srcHtmlFiles, '!' + srcHtmlDir + '/**/_*.html']) .pipe(ejs({buildTarget: 'debug'})) ...
|
HTML内で分岐させる:
<% if (buildTarget === 'debug') { %> <script data-main="jsdev/main.js" src="require.js"></script> <% } else { %> <script src="assets/main.js" type="text/javascript"></script> <% } %>
|
リリース時にはbuildTarget
にrelease
を指定する。
結果
俺用テンプレートリポジトリ (追記:リポジトリはWebPackを使うよう変更されました)