2016-02-12

Angular.jsでBrowserify

Angular.jsでBrowserify対応するときに色々ハマったので、その備忘録。

Angular.jsの1.3.14以降、1.4以降はCommonJS形式でのライブラリのロードがサポートされており、基本的にはnpmで取ってきたangularに対して以下のようなコードを書いてBrowserifyを通します

// js/app.js
var angular = require('angular'); // npm install angular --save
var router = require('angular-ui-router'); // npm install angular-ui-router --save
var fuga = require('./fuga');

angular.module('hoge', [
  'fuga',
  'ui.router',
]);

fugaモジュールはこんな感じで書く↓(分割したモジュールの書き方は色々あります)

// js/fuga.js
var angular = require('angular');
module.exports = angular.module('fuga', [])
    .controller('FugaCtrl', ['$scope', '$http', function($scope, $http) {
      // something
    }]);

index.html↓

<html ng-app="hoge">
<head>
  <script src="dist/bundle.js"></script>
</head>
<body ng-controller="FugaCtrl" ng-cloak>
  <div>{{aaa}}</div>
  <ui-view></ui-view>
</body>
</html>

あとはbrowserifyで変換するだけ

$ browserify js/app.js -o dist/bundle.js

gulp↓

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

//browserify
gulp.task('build', function(){
  browserify({
    entries: ['js/app.js'],
  })
  .bundle()
  .pipe(source('bundle.js'))
  .pipe(gulp.dest('dist'));
});

bowerのライブラリも併用する場合

browserifyでbower経由で取得したライブラリも併用する場合は、debowerifyをtransformに指定すればOK

インストール↓

$ npm install debowerify --save

コマンド↓

$ browserify -t debowerify js/app.js -o dist/bundle.js

gulpfileはこんな感じでdebowerifyのtransformを追加すればOK

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

//browserify + debowerify
gulp.task('build', function(){
  browserify({
    entries: ['js/app.js'],
  })
  .transform("debowerify")
  .bundle()
  .pipe(source('bundle.js'))
  .pipe(gulp.dest('dist'));
});

debowerifyを使うときの注意

bowerのみで配布されておりangular.jsと依存関係にあるライブラリを利用する場合には、bower installでangular.jsもbower_components直下にインストールされてしまいます。この状態でdebowerifyを実行するとbower側のangular.jsが優先されてロードされてしまいます。

そうすると、bower側はCommonJS形式に非対応なので、requireの戻り値はundefinedになり

var angular = require('angular');

といったロードしたモジュールを変数に格納するような利用方法は出来ません。bowerのangularでrequire('angular')を呼ぶとグローバルにangularが定義され、通常のscriptタグを読み込むのと同じ動作になります。(npmバージョンも同様にグローバルに定義されるものの、戻り値としてもangularオブジェクトを返してくれます)

かと言って、bower_components/angularディレクトリを削除すると、browserify起動時に以下のエラーが発生します。(おそらくコンパイル前に依存関係とか調べているのだと思われます)

Error: could not resolve dependency angular : bower returns the module as known but not found (did you forget to run bower install ?) (/Users/hoge/js/app.js) while parsing file: /Users/hoge/js/app.js

そこで、debowerifyにはnpmをbowerよりも優先して読み込むオプションがあるので、これを利用します。

npmの最新バージョン(2016/02/07時点)ではpreferNPMの機能追加がされていないので、githubからインストールします。

$ npm install https://github.com/eugeneware/debowerify.git

コマンド↓

$ browserify -t [ debowerify --preferNPM ] js/app.js -o dist/bundle.js

gulp↓

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

//browserify + debowerify(preferNPM=true)
gulp.task('build', function(){
  browserify({
    entries: ['js/app.js'],
  })
  .transform("debowerify", {preferNPM: true})
  .bundle()
  .pipe(source('bundle.js'))
  .pipe(gulp.dest('dist'));
});

これでbowerとnpmに同一のライブラリが有ってもnpm側の設定を優先的に読み込むことが出来ます。まぁそもそも、こんなことしなくてもrequire("node_modules/angular")の直接パス指定で解決しちゃうんですが…。

その他

preferNPMとか使って無理やりnpmバージョン使わなくても、browserify-shimを使えばCommonJSスタイルに書き換えてくれます。(node_modules/angular/index.jsと同じようにmodule.exportsしてくれます)

package.json↓

{
  ...
  "browserify-shim": {
    "./bower_components/angular/angular.js": "angular"
  },
  ...
}

gulpfile.js↓

gulp.task('build', function(){
  browserify({
    entries: ['js/app.js'],
  })
  .transform("browserify-shim")
  .transform("debowerify")
  .bundle()
  .pipe(source('bundle.js'))
  .pipe(gulp.dest('dist'));
});

debowerify使わない場合はpackage.jsonのbrowserフィールドにエイリアスを書かないと動かないので注意。

あと、browserifyのコマンドラインでオプション指定するところでハマりました…minimistというライブラリを利用しているっぽいので、オプションの指定方法はminimistを調べると良いです。

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