gulpとかwatchifyとかbrowserify-shimとかの話 はてなブックマーク - gulpとかwatchifyとかbrowserify-shimとかの話

今では、JavaScriptを書いててrequire()importを使わないことはありえない。そしてクライアントサイドではそのためにbrowserifywebpackを使うことになる。

browserify-shim

やみくもにimportしていると、1つのJavaScriptファイルのサイズが大きくなってしまう。また、エントリポイント(実際に<script>で読み込まれるであろう入り口となるJavaScript)が複数になった場合、複数のエントリポイントに同じコードが含まれることになる。こういう場合にbrowserify-shimを使う。

browserify-shimを使うと、特定の名前に対するrequire()を、別の変数やメンバーへのアクセスに置き換えることができる。実際に書いたコードが

var React = require('react');  

だとして、これを

var React = window.React;  

に置き換えたりができるようになる。これを活用すると、ファイルサイズの大きいライブラリ、たとえばReactAngularを使っている場合などになかなか利点がある。

  • 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-reduxreact-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を忘れずに。

このエントリーをはてなブックマークに追加

Hot Articles