babel-plugin-lodashで依存パッケージから未使用のコードを削除する


概要
------
* [babel-plugin-lodash](https://github.com/lodash/babel-plugin-lodash)は、babelのトランスパイル時にlodashのimportを個別の関数のimportに変換してくれるプラグインです。個別の関数のimportにすることで、スクリプトバンドルのサイズを抑えることができます。
* 変換対象としてlodash以外のパッケージを指定することができ、条件を満たしたパッケージであれば、lodashと同様に必要なオブジェクトだけをバンドルに含めるように変換することができます。これにより、大幅にバンドルのサイズを削減できる場合があります。
* 実際にサンプルを作って試してみたところ、依存パッケージで使用していないコードをバンドルから削減することができ、大きくバンドルサイズを減らすことができました。
* Tree Shakingが適用できないパッケージにも適用できるため、Tree Shakingの代替として役立ちそうです。

記事中で利用した主なパッケージのバージョン
------
* babel-plugin-lodash: 3.2.11
* reactstrap: 4.2.0

babel-plugin-lodashとは
------
みんな大好き[lodash](https://lodash.com/)は、様々なユーティリティ関数をまとめたパッケージです。
全部をスクリプトバンドルに含めてしまうとかなりサイズが大きいので、必要な部分だけ含めるようにすることが推奨されています。
個人的には、lodashはやっぱりアンダーバーにdefault importして(`import _ from 'lodash'`)使うのがいちばん便利かつ美しいかなー、と思うのですが、これをやってしまうと全体がバンドルに含まれてしまいます。
そこで、default importしつつも実際に呼び出している関数だけを含めるようにする手段として、[babel-plugin-lodash](https://github.com/lodash/babel-plugin-lodash)を使うという方法があります。
これは、babelのトランスパイル時に、一括import(default importや名前付きimportなど)を個別関数のimportに変換してくれるbabelプラグインです。
このプラグインを利用すれば、default importでコードを書きつつ、必要な個別関数だけをバンドルに含ませられるというわけです。

実はlodash以外にも使える
------
[babel-plugin-lodashのREADME](https://github.com/lodash/babel-plugin-lodash/blob/master/README.md)にもありますが、プラグインのオプションでid属性にパッケージ名を指定することで、lodash以外のパッケージも変換対象にすることができます。
つまり、lodash以外のパッケージでも、このプラグインを使えば実際に利用しているオブジェクトだけをスクリプトに含ませることができるというわけです。
ただし、残念ながらどんなパッケージでも適用できるというわけではなく、以下の条件が揃っている場合のみ適用できます。

1. package.jsonに`main`が書かれている
1. importするオブジェクトが、同名のファイルに定義されている

1の条件はプラグインが`main`で指定されているファイルのパスを基準にファイルを探していくためで、指定されていないとエラーになります。ほとんどのパッケージは指定されているのであまり気にする必要はないかと思いますが、まれに指定されていないもの（例: [react-routerのv4 alpha](https://github.com/ReactTraining/react-router/blob/v4.0.0-alpha.6/package.json)）があるので、もしエラーになる場合は疑ってみるとよいかもしれません。
2の条件がこのプラグインが適用できるかのポイントで、1オブジェクト1ファイルで定義されているようなパッケージでないと変換がエラーになります。利用するオブジェクトを選択的にimportするような使い方をするパッケージであっても、この条件を満たしていない場合（例: react-dom）があるので注意が必要です。
私が知っている限りでは、Reactまわりでは以下のパッケージには適用できました。

* reactstrap
* react-i18next
* react-router-dom

実際に試してみた
------
サンプルアプリを作って、babel-plugin-lodashでバンドルサイズが削減できることを確認してみました。
サンプルアプリの全てのソースコードは https://github.com/ucho/reduce-bundle-size-sample にあります（[この記事](http://qiita.com/ucho/items/54670fe1e7213c3b7b3f)で使ったものと同一です）。
このアプリでは、[reactstrap](https://github.com/reactstrap/reactstrap)のいくつかのコンポーネントだけをimportしているので、プラグインが効果を発揮すればimportしていないコンポーネントはバンドルから削除されるはずです。
バンドルを生成してファイルサイズを調べた結果は以下です。

* プラグインなし: 403KB
* プラグインあり: 250KB

期待通り、プラグインを適用した場合では、reactstrapのimportしていないコンポーネントが削除され、大幅にファイルサイズを減らすことができました。実際にどれくらいサイズが減るかは何をimportしたのか（していないのか）によりますが、大きなパッケージの一部しか使っていないような場合は、劇的な効果が期待できるのではないでしょうか。

Tree Shakingとの関係
------
Tree Shakingはwebpackやrollupが提供している、importしていないコードをバンドルから削除してくれる機能です。babel-plugin-lodashの効果はTree Shakingの効果とよく似ていますが、何が異なるのか以下にまとめてみました。

* コード形式
  * Tree Shaking: 対象コードが全てES2015モジュール形式でなければならない
  * babel-plugin-lodash: 呼び出し側はES2015 importを使う必要があるが、呼ばれる側はES2015でなくてもよい
* 適用対象
  * Tree Shaking: import対象がアプリ自身か依存パッケージかに関係なく適用される
  * babel-plugin-lodash: 適用できるのは依存パッケージのみ。加えていくつかの条件を満たしている必要あり

Tree Shakingは適用範囲が広く強力ですが、依存パッケージに適用するには、そのパッケージがES2015モジュール形式のコードを提供している必要があり、そしてそのようなパッケージは現時点(2017/03時点)ではまだ数が多くないため、[期待したほど効果が出ないかもしれません](http://qiita.com/ucho/items/54670fe1e7213c3b7b3f)。
babel-plugin-lodashは、適用範囲は限定されますが、Tree Shakingが効かないパッケージにも適用できるので、そのパッケージがTree Shakingできるようになるまでの代替手段としては、十分に役に立つのではないでしょうか。