Chromeエクステンションを作ろう：gulp編


説明で使用した拡張機能は[こちら](https://chrome.google.com/webstore/detail/redmine-lazy-applicant/dofmgcleeckgckfapnhookpocglpbmgp)からインストールできます. 一人でも多くの方に利用いただければ幸いです.
ソースコードは[こちら](https://bitbucket.org/dhun-h/lazy-applicant)から閲覧できます.


## gulpとは

Node.jsのStreamAPIを利用したビルドシステム、およびタスクランナーです.
gulpを利用することで、定型的な作業を自動化することができます.

[公式サイト](https://github.com/gulpjs/gulp/blob/master/docs/getting-started.md)

## 開発環境の構築

Node.jsやgulpを開発環境にインストールします.
なおWindows版は十分な実機検証していないため、うまくいかないかもしれません.

### nvmのインストール

Node.jsは頻繁に更新されるため、Node.jsのバージョン管理ツールをインストールします.

* [Mac](https://github.com/creationix/nvm#node-version-manager-)
* [Windows](https://github.com/hakobera/nvmw#node-version-manager-for-windows)

インストールスクリプトも頻繁に更新されるようですので、ここには記載しません.
前述したサイトのインストール手順を参照してください.

### Node.jsのインストール

nvmを使って、Node.jsをインストールします.

#### Mac

```shell
nvm install v0.12.7     # 最新を指定する
nvm alias default current
```

#### Windows

```bat
nvmw install v0.12.7     # 最新を指定する
```

Windows版のnvmwでは`alias default`ができないため、[こちら](http://qiita.com/dhun/items/004936ede4d290caa0d0)の運用で対処します.

### gulpのインストール

続いて、gulpをインストールします.
`-g`オプションを指定することで、OSにgulpをインストールします.

```shell
npm install -g gulp
```


## npmの準備

今回はgulpパッケージへの依存性はnpmで解決させたため、npmを利用できるようにします.

`npm init`を実行すると、いくつかの設問があります.
適当に回答(回答しない設問には空エンター)すると、回答結果が`package.json`として出力されます.

```shell
$ npm init

name: (tmp) lazy-applicant
version: (1.0.0)
description:
entry point: (index.js)
test command:
git repository:
keywords:
author:
license: (ISC) No License
About to write to /Users/dhun/tmp/package.json:

{
  "name": "lazy-applicant",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "No License"
}


Is this ok? (yes) yes
```

これを編集して、次のようにしました.

```json:package.json
  "name": "lazy-applicant",
  "license" : "No License"
```


## 依存パッケージのインストール

次のようなコマンドで、依存するパッケージをインストールします.

`--save-dev`オプションを指定することで、`package.json`に依存性が記録されます.

```shell
npm install --save-dev gulp
npm install --save-dev gulp-eslint
npm install --save-dev gulp-uglify
    :  ＜省略＞
```

package.jsonをgitなどのバージョン管理システムに登録すれば、依存性をすべての開発者で共有することができます.
共同開発者は、次のコマンドでpackage.jsonに記述されたパッケージをローカル環境にインストールできます.

```shell
npm install
```

## gulpタスクの定義

定型的な処理を、gulpタスクとして定義します.
この際に[gulpパッケージの一括ロード](http://qiita.com/dhun/items/daa207fc05c67f255c23)を利用したり、[gulpタスクの分割](http://qiita.com/dhun/items/c8633800097f1e7ecf70)を意識すると、可読性の高い状態を維持できます.

```js:clean.js
var gulp = require('gulp');
var del = require('del');
var config = require('../config').simple;

gulp.task('clean', function() {
    del.sync([config.versionDir
            , config.versionCrxFile + '*'
            , config.latestCrxFile + '*']);
});
```

### gulpタスクの同期実行

gulpタスクは基本的に非同期で実行されます.
gulpが高速に動作する理由の一つがこの非同期性らしいのですが、ビルド系処理などでは順序性が重要になります.（というか、非同期で実行できる処理の方が少ない気がするのだが、、、）

gulpタスクを同期処理として実行するには、次のいずれかの方法をとります.

* タスクの最後の処理で、ストリームを返却する
* タスクの関数にcallback引数を定義して、最後の処理でcallback引数を実行する
* `run-sequence`を利用する
* `del.sync`のような同期処理を利用する

```js:gen-zip.js(ストリームを返却する例)
var gulp = require('gulp');
var $ = require('gulp-load-plugins')();
var config = require('../config').simple;

gulp.task('gen-zip', function () {
    return gulp.src(config.versionDir + '/**')
        .pipe($.zip(config.versionZipName))
        .pipe(gulp.dest(config.zipDir));
});
```

```js:switch-env.js(callback引数の例)
var gulp = require('gulp');
var runSequence = require('run-sequence');
var config = require('../config').simple;

gulp.task('switch-env', function(callback) {
    if (config.env.profile == 'release') {
        runSequence('switch-release');
    } else {
        runSequence('switch-develop');
    }
    callback();
});
```

```js:copy.js(runSequenceを利用する例)
var gulp = require('gulp');
var runSequence = require('run-sequence');

gulp.task('copy', function(callback) {
    runSequence(
        ['copy-src', 'copy-lib', 'copy-license'],
        callback
    );
});
```


## プラグインで実装したこと
* JSとHTMLを監視して、変更されたらLintによる構文チェックを実行する
* ソースファイルをビルドして、配布ファイルを生成する

すべて説明できないため、詳細は[ソースコード](https://bitbucket.org/dhun-h/lazy-applicant)を参照してください.

### ファイル構成

```shell
.
+-- package.json
+-- gulpfile.js
+-- gulp/
    +-- config.js
    +-- tasks/
        +-- clean.js
        +-- copy.js
        +-- lint.js
              :
```

[gulpfile.js](http://qiita.com/dhun/items/c8633800097f1e7ecf70#gulpfilejs)や[config.js](http://qiita.com/dhun/items/c8633800097f1e7ecf70#configjs)については、別記事を参照してください.

### gulpタスク

gulp/tasks/に配置しました.

| タスク | 実行コマンド | 概要 |
|---|---|---|
|{ default }|gulp|JSとHTMLを監視して、ファイルが変更されたらLintする|
|build|gulp build|以下の一連のタスクを実行して.crxと.zipファイルを生成する|
|assert.js|&nbsp;|ビルドに必要な定義が揃っているかを検証する. 検証エラーがあればbuildタスクを中断する|
|lint-js.js|&nbsp;|JSファイルをLintする. 検証エラーがあればbuildタスクを中断する|
|lint-html.js|&nbsp;|HTMLファイルをLintする. 検証エラーがあればbuildタスクを中断する|
|clean.js|&nbsp;|publish/build/${version}/を削除する|
|copy.js|&nbsp;|publish/build/${version}/へファイルをコピーする|
|switch-env.js|&nbsp;|src/js/{env.js,env-release.js}を切り替える. config-env.jsに従う|
|package.js|&nbsp;|publish/build/XXX.crxファイルを生成する|
|move-crx.js|&nbsp;|publish/crx/にXXX.crxファイルを移動する|
|gen-latest.js|&nbsp;|publish/crx/にlatest.crxを生成する|
|gen-digest.js|&nbsp;|publish/crx/にXXX.md5を生成する|
|gen-zip.js|&nbsp;|publish/zip/にXXX.zipを生成する|
|complete.js|&nbsp;|build処理が正常終了したことをデスクトップ通知する|

### 利用したgulpパッケージ

最後に利用したgulpパッケージを記載します.
興味のあるパッケージがあれば、[ソースコード](https://bitbucket.org/dhun-h/lazy-applicant)のどこかで利用しているので参照してみてください.

```json:package.json(抜粋)
  "devDependencies": {
    "del": "^1.2.1",
    "event-stream": "^3.3.1",
    "gulp": "^3.9.0",
    "gulp-cache": "^0.2.10",
    "gulp-debug": "^2.1.0",
    "gulp-eslint": "^1.0.0",
    "gulp-hashsum": "^1.0.0",
    "gulp-htmlhint": "^0.3.0",
    "gulp-if": "^1.2.5",
    "gulp-imagemin": "^2.3.0",
    "gulp-intercept": "^0.1.0",
    "gulp-load-plugins": "^0.10.0",
    "gulp-minify-css": "^1.2.0",
    "gulp-minify-html": "^1.0.4",
    "gulp-notify": "^2.2.0",
    "gulp-rename": "^1.2.2",
    "gulp-shell": "^0.4.2",
    "gulp-uglify": "^1.2.0",
    "gulp-util": "^3.0.6",
    "gulp-zip": "^3.0.2",
    "require-dir": "^0.3.0",
    "run-sequence": "^1.1.2"
  }
```


## 説明で紹介したプラグインのソースコード

* [package.json](https://bitbucket.org/dhun-h/lazy-applicant/src/f371e1be0e9d66311bf81555e098b71c25886eb7/package.json?at=master&fileviewer=file-view-default)
* [gulpfile.js](https://bitbucket.org/dhun-h/lazy-applicant/src/f371e1be0e9d66311bf81555e098b71c25886eb7/gulpfile.js?at=master&fileviewer=file-view-default)
* [gulp/config.js](https://bitbucket.org/dhun-h/lazy-applicant/src/f371e1be0e9d66311bf81555e098b71c25886eb7/gulp/config.js?at=master&fileviewer=file-view-default)