webpack plugin のつくりかた

[ラクス Advent Calendar 2016](http://qiita.com/advent-calendar/2016/rakus) の12日目の記事です。
昨日は [@AkiraTamai](http://qiita.com/AkiraTamai) さんによる「 [Erlangで1プロセス作成するのに必要な時間を計ってみた](http://qiita.com/AkiraTamai/items/1f2c12f14f73c2f81b82) 」でした。

Advent Calendarの日取りを失敗してしまい、うっかりベトナム旅行中から投稿している @bakunyo@github です :sweat_smile: 


## webpack とは
突然ですが皆さん [webpack](http://webpack.github.io/) 使ってますか？
まだ知らない、使ったことが無いという方は以下の記事をご参考ください。
[webpack で始めるイマドキのフロントエンド開発](http://qiita.com/yosisa/items/61cfd3ede598e194813b)

[webpack](http://webpack.github.io/) はざっくりいうと、 **JavaScriptモジュールの依存関係を解決しつつ、静的アセットを生成してくれるビルドツール** です。
公式サイトに載っている以下の図がイメージしやすいです。

![image](https://qiita-image-store.s3.amazonaws.com/0/16000/c707c1b6-5136-0c47-cc05-ad65c28ae61f.png)

## webpack plugin とは
[webpack](http://webpack.github.io/) にはプラグイン機能があり、プラグインを追加することで  **ビルド実行時に様々な処理を追加する** ことができます。
例えば [UglifyJsPlugin](http://webpack.github.io/docs/list-of-plugins.html#uglifyjsplugin) は、アセット生成時に [UglifyJs](https://github.com/mishoo/UglifyJS2) を使ってJavaScriptを圧縮してくれます。

ビルトインで提供されているプラグインも多く、以下はその一覧です。
[LIST OF PLUGINS](http://webpack.github.io/docs/list-of-plugins.html)

## webpack plugin のつくりかた
ここからが本題です。
ビルトイン以外にも多くのプラグインが公開されているように、プラグインは**自作することができます**。

webpack ビルド処理の様々な箇所に処理を挿し込むことが可能です。
詳細は [Plugins API](https://github.com/webpack/docs/wiki/plugins) に書かれていますが、 **[Compiler](https://github.com/webpack/docs/wiki/plugins#the-compiler-instance)** と **[Compilation](https://github.com/webpack/docs/wiki/plugins#the-compilation-instance)** に紐付くイベントが大半です。

webpack plugin は、 `compiler` を引数として受け取る **`apply`** メソッドを持つオブジェクトです。
`compiler` や `compilation` は [Tapable](https://github.com/webpack/tapable) を継承していて、 [Plugins API](https://github.com/webpack/docs/wiki/plugins) に記載されている各所に処理を追加することができます。
処理を追加するには、 **`plugin`** メソッドにイベント名とハンドラー関数を渡して、その中に処理を書きます。

言葉ではなかなか伝えづらいので、サンプルコードを用意しました。

```javascript
class MyWebpackPlugin {
  // compiler を引数として受け取る apply メソッド
  apply(compiler) {
    // plugin メソッドにイベント名とハンドラー関数を渡す
    compiler.plugin('compile', (params) => {
      console.log('compile開始時の処理');
    });

    compiler.plugin('compilation', (compilation, params) => {
      // compilation もまた plugin メソッドを持つので、各イベントに処理を追加できる
      compilation.plugin('optimize', () => {
        console.log('optimize(最適化)開始時の処理');
      });
    });

    // 非同期に実行されるイベントは callback を引数で受取り、適宜実行する
    compiler.plugin('emit', (compilation, callback) => {
      console.log('emit(アセット出力)開始時の処理');
      callback();
    });
  }
}

// webpack.config.js
module.exports = {
  entry: {
    // ... 
    // ... webpackの各種設定 ...
    // ... 
  },
  plugins: [
    new MyWebpackPlugin()
  ]  
}
```

上記のサンプルコードを実行した結果、出力は以下のようになりました。
webpack 実行時に、 plugin で追加した処理を通っていることが確認できます。

![webpack.gif](https://qiita-image-store.s3.amazonaws.com/0/16000/76853235-2cb9-7b3e-b804-5b1a2240f19c.gif)

## webpack plugin の開発
サンプルコードで記載している通り、プラグイン開発時には **Compiler** や **Compilation** などのオブジェクト、ハンドラー関数で受け取る引数などを扱う必要が出てきます。
そこで欲しくなるのが **デバッグツール** です。

今回はデバッグツールとして [V8 Inspector](https://nodejs.org/api/debugger.html#debugger_v8_inspector_integration_for_node_js) をご紹介します。
こちらは Node.js に標準で入っており、 Chrome DevTools と連携するため導入は**かなり楽**です。

Node.js の古いバージョンだと入っていないかもしれません。
その場合はアップデートするか、 [node-inspector](https://github.com/node-inspector/node-inspector) や [node-nightly](https://github.com/hemanth/node-nightly) で代用するなどを検討してみてください。

以下、使い方です。

### 下準備
処理を止めたい場所に **`debugger;`** と書きます。
今回は、サンプルコードの `apply` の直後に追加してみます。

```
class MyWebpackPlugin {
  apply(compiler) {
    debugger;            // ← ここに追加
    compiler.plugin('compile', (params) => {
```

### デバッグ実行
以下のコマンドを実行します。
webpack は `node_modules` 配下にインストールしている想定です。

```
$ node --inspect --debug-brk node_modules/webpack/bin/webpack.js
```

実行している様子を以下に貼付けます。

![inspector.gif](https://qiita-image-store.s3.amazonaws.com/0/16000/4fa8f73a-e37b-c4f2-7226-216de96a14aa.gif)

- コマンド実行
- 出力されたURLをChromeで開く
- あとは通常の Chrome DevTools を使っている感じ

慣れ親しんだUIでデバッグできるので、とても楽です！

## webpack についての補足
現在 webpack **バージョン2**がβ版として公開されています。
公式サイトも分かれているようです。
[webpack.js.org](https://webpack.js.org/)

見たところ plugin の機能は引き続き残ります。
多少の設定変更が必要になることはあっても、大きくは変わらないようです。

## 参考
- [plugins](https://github.com/webpack/docs/wiki/plugins)
- [how to write a plugin](https://github.com/webpack/docs/wiki/How-to-write-a-plugin)

## 所感
今回の [ラクス Advent Calendar 2016](http://qiita.com/advent-calendar/2016/rakus) を企画・運営してくれた @oohira さんありがとうございました！
数年前ではこういう企画が成り立つなんてなかなか考えられなかったことなので、感慨深いです。
今後も、こういったオープンな活動に積極的に取り組んでいけたらなと思います :smile: