今では、JavaScriptを書いててrequire()
やimport
を使わないことはありえない。そしてクライアントサイドではそのためにbrowserifyやwebpackを使うことになる。
browserify-shim
やみくもにimport
していると、1つのJavaScriptファイルのサイズが大きくなってしまう。また、エントリポイント(実際に<script>
で読み込まれるであろう入り口となるJavaScript)が複数になった場合、複数のエントリポイントに同じコードが含まれることになる。こういう場合にbrowserify-shimを使う。
browserify-shimを使うと、特定の名前に対するrequire()
を、別の変数やメンバーへのアクセスに置き換えることができる。実際に書いたコードが
var React = require('react');
だとして、これを
var React = window.React;
に置き換えたりができるようになる。これを活用すると、ファイルサイズの大きいライブラリ、たとえばReactやAngularを使っている場合などになかなか利点がある。
- HTTPはたいてい6つまで並列にリソースをダウンロードできるので、
<script>
ごと分けておいて、並列にダウンロードした方がいい場合がある。 - ライブラリの中のコードの変更はそう頻繁に行うことはない(ライブラリのバージョンアップくらいでしか変更しない)ので、
<script>
ごと分けておけばブラウザキャッシュを活用できる。
使い方は、browserifyのtransform
として使う。
browserify('./path/to/script.js')
.transform('browserify-shim')
.bundle()
どのrequire()
をどういうアクセスに置き換えるかは、package.json
内で記述する。たとえば、以下のように書いておくと、
{
...
"browserify-shim": {
"react": "global:React"
}
...
}
require('react')
をwindow.React
に置き換えるという意味になる。
Reactにおいて、react-reduxやreact-routerなどを使う場合は、それぞれのライブラリの中からrequire('react')
があるので、これも置き換えてやる必要がある。この場合は、transform指定時に、以下のようにglobal transformとして適用させる必要がある(これはuglifyifyと同様)。
browserify(...)
.transform('browserify', { global: true })
gulpとbrowserify/watchify
@shuheikagawaさんに教えられて気付かされた話。
gulpはgulp.src()
によってgulp.pipe()
を適用させるファイルをglobで指定することができる。gulp.src()
を起点としたgulp tasksを書くと、そのファイルの読み込みはgulp.src()
によって行われる。
僕はこれまで何も考えずgulp-watchifyを使ってきていたけど、あれはgulp.src()
を起点とした.pipe()
中で(watchifiedな)browserifyを普通に動かしてるので、ファイルへのアクセスはgulpによるものと、browserifyによるものの2つが走ることになる。
たまにログが2回出たりして、「うーん?!」って思ってたけど納得した。これからはbrowserify()
起点のgulp tasksを書くようにする。
たとえば、以下のような感じ。
gulp.task('transpile:js', () => {
const b = browserify(Object.assign({}, watchify.args, {
entries: ['./app/app.es6'],
extensions: ['.es6', '.json'],
debug: true,
}));
const onError = err => {
gutil.log(
gutil.colors.magenta('Error occured by browserify:'),
gutil.colors.red(err.message)
);
gutil.log('\n' + err.codeFrame);
notifier.notify({
title: 'Gulp Error: transpile:js',
message: err.message,
icon: path.resolve(
__dirname, './node_modules/gulp-notify/assets/gulp-error.png'
),
});
};
const bundle = () => {
b
.transform('babelify')
.transform('browserify-shim', { global: true })
.transform('uglifyify', { global: true })
.bundle()
.on('error', onError)
.pipe(source('bundle.js'))
.pipe(buffer()) // transform to vinyl stream (for gulp)
.pipe(sourcemaps.init({ loadMaps: true }))
.pipe(sourcemaps.write('./'))
.pipe(gulp.dest('./public/javascripts'));
gutil.log(gutil.colors.magenta('Bundling:'), 'app.es6');
};
bundle();
if (isWatching) {
const w = watchify(b);
w.on('update', bundle);
w.on('log', (...args) => {
gutil.log(gutil.colors.magenta('Rebundled:'), ...args)
});
}
});
sourcemapsについても、browserifyでJavaScriptに関するトランスパイルをすべて済ませ、それをsourcemaps.init({ loadMaps: true })
で読み込んで吐き出させるようにすればうまくいく。browserify後に.pipe(uglify()).pipe(sourcemaps.init(...))
ってすると上手くいかない。
gulpfile.babel.js
ってファイル名にするとBabelなES6でgulpfile.jsが書ける。babel-coreかbabelをgulpが読みにいくので、installを忘れずに。