JavaScriptライブラリ/プロジェクトのファイルサイズの問題点を見つける方法

ブラウザ向けのJavaScriptだとファイルサイズはある程度気になると思います。

この記事では、ファイルサイズの計測方法やボトルネックとなってるライブラリの見つけ方についてできるだけ簡単な方法をまとめます。

## ファイルサイズ

ファイルサイズと一言にいっても、ブラウザでは大体minifyしてから配布するのでminify後のファイルサイズも重要です。

- ソースコード自体のファイルサイズ
- minifyしたファイルサイズ
- minify + gzipしたファイルサイズ

コメントの多いソースコードはminifyするとかなり小さくなったりすることが多いので、ソースコード自体のファイルサイズでは比較しにくいです。
また、ソースコードにおいて `assert` モジュールを使っているとファイルサイズが10kb弱程度minify後で変わります。
`assert`モジュールは通常外しても問題ないので、[unassert](https://github.com/twada/unassert "unassert")でビルド時に取り除きます。
色々ライブラリを使っているプロジェクトだと[unassert](https://github.com/twada/unassert "unassert")するだけで5%程度ファイルサイズが減りました。
何がいいたいかというとminifyやビルドの方法でもサイズが異なります。
そのため、プロジェクトにおいてのファイルサイズは計測が結構面倒くさいという話を後半します。

## minify後のファイルサイズを取得

minify後のファイルを得るには[bundle-size](https://github.com/mkwtys/bundle-size "bundle-size")というツールを使うのが簡単です。

[bundle-size](https://github.com/mkwtys/bundle-size "bundle-size")では指定したパッケージやentry pointをminifyしたファイルサイズを環境変数毎に一覧できます。

```
# bundle-size lib/index.js みたいなentry pointもいける
❯ bundle-size almin -e development -e production
almin@0.9.0

env          bundle  minify   gzip
--           116 kB  60.5 kB  16.6 kB
development  116 kB  60.5 kB  16.6 kB
production   116 kB  60.5 kB  16.6 kB
```

[bundle-size](https://github.com/mkwtys/bundle-size "bundle-size")はbundle後のファイルサイズのみで、依存ツリーを見ることはできません。(いまのところ)

一方、[package-size-analyzer](https://github.com/adventure-yunfei/package-size-analyzer "package-size-analyzer")を使うと、パッケージや指定したentry pointでビルドした結果の依存ツリーを見ることができます。(逆にこちらはminifyした結果が見られない)

```
# -e でentry point 、 -j でstats.json
❯ package-size-analyzer -p almin
__ROOT__: 111.46 KB
  <self>: 17 B
  almin: 57.81 KB
    <self>: 55.86 KB
    object-assign: 1.95 KB
      <self>: 1.95 KB
  package-size-analyzer: 5.17 KB
    <self>: 0 B
    process: 5.17 KB
      <self>: 5.17 KB
  assert: 11.43 KB
    <self>: 11.43 KB
  util: 15.4 KB
    <self>: 15.4 KB
  inherits: 672 B
    <self>: 672 B
  events: 8 KB
    <self>: 8 KB
  lru-map-like: 7.84 KB
    <self>: 7.84 KB
  map-like: 5.13 KB
    <self>: 5.13 KB
```

ファイルサイズは本家だと単位付けてくれないので、PRを出しています。
(READMEもなくてアレなので、forkするかもしれないです)

- [show file size with unit by azu · Pull Request #1 · adventure-yunfei/package-size-analyzer](https://github.com/adventure-yunfei/package-size-analyzer/pull/1 "show file size with unit by azu · Pull Request #1 · adventure-yunfei/package-size-analyzer")

[bundle-size](https://github.com/mkwtys/bundle-size "bundle-size")はBrowserifyを内部的に使いますが、[package-size-analyzer](https://github.com/adventure-yunfei/package-size-analyzer "package-size-analyzer")は内部的にwebpackを使っています。

これらは、どちらかというライブラリのサイズを分析する向けとなっています。
実際のプロジェクトだと複雑なビルドが絡まっていたりするため、設定なしで使えるツールには限界がありそうです。

## プロジェクトのライブラリ依存ツリーを見る

プロジェクトでは色々なライブラリを使うので、無駄にファイルサイズの大きいものがうっかり混ざっている事があります。dedupeされてないとかで二重に入ってしまうなどもありえます。
そういう場合は実際にbundleしているツールを使って、その結果から分析してみると分かりやすいです。

### Browserifyを使っているプロジェクトで依存ツリーを見る

Browserifyで依存ツリーをみるには[disc](https://github.com/hughsk/disc "disc")を使うのが簡単です。

- [hughsk/disc: Visualise the module tree of browserify project bundles and track down bloat.](https://github.com/hughsk/disc "hughsk/disc: Visualise the module tree of browserify project bundles and track down bloat.")

```
$(npm bin)/browserify --full-paths src/index.js | discify --open
```

で依存ツリーを可視化して見ることができます。
## webpackを使っているプロジェクトで依存ツリーを見る

webpackはプロジェクトの依存ツリーを分析するためのファイルをJSONで入手できます。
具体的に依存してるモジュールでどれが大きいのかを調べるには、webpackのstats.jsonを使った方法が分かりやすいです。

webpackでは`--json`でbundleしたモジュールの解析結果をJSONとして取得できます。

```
$(npm bin)/webpack --json --profile > stats.json
```

この結果取得できる `stats.json` を以下のツールなどに投げて見ると依存ツリーを可視化してみることができます。

- [upload](http://webpack.github.io/analyse/)
- [Webpack Chart](https://alexkuz.github.io/webpack-chart/)
- [Webpack Visualizer](https://chrisbateman.github.io/webpack-visualizer/)

詳しくは[SurviveJS - Analyzing Build Statistics](http://survivejs.com/webpack/building-with-webpack/analyzing-build-statistics/)を見ると良いです。

毎回jsonを吐いて、ブラウザで見るのは大変なのでコマンドラインで依存ツリーを見る方法として[webpack-bundle-size-analyzer](https://github.com/robertknight/webpack-bundle-size-analyzer "webpack-bundle-size-analyzer")を使う方法が有名です。

次のようにパイプで`stats.json`として吐いていたものを渡すだけで依存ツリーのファイルサイズを出してくれます。(ただしminify前のソースコードとしてのサイズです)

```
❯ $(npm bin)/webpack -p --json | webpack-bundle-size-analyzer
react: 581.8 KB (78.8%)
almin: 57.81 KB (7.83%)
  object-assign: 1.95 KB (3.37%)
  <self>: 55.86 KB (96.6%)
fbjs: 32.32 KB (4.38%)
util: 15.4 KB (2.09%)
assert: 11.43 KB (1.55%)
events: 8 KB (1.08%)
lru-map-like: 7.84 KB (1.06%)
map-like: 5.13 KB (0.695%)
process: 2.01 KB (0.272%)
object-assign: 896 B (0.119%)
inherits: 672 B (0.0889%)
react-dom: 63 B (0.00833%)
<self>: 14.85 KB (2.01%)
```

## おわりに

[webpack-bundle-size-analyzer](https://github.com/robertknight/webpack-bundle-size-analyzer "webpack-bundle-size-analyzer")のリポジトリにも書いてありますが、webpackのstats.jsonを使った方法は、minify後ではなくminify前のファイルサイズが表示されます。

> Consequently the stats shown by webpack-bundle-size-analyzer will report sizes before minification. This should still give a pretty good idea of what contributes to your bundle size but some libraries will compress better than others, so they can be misleading.
> -- [webpack-bundle-size-analyzer](https://github.com/robertknight/webpack-bundle-size-analyzer "webpack-bundle-size-analyzer")

そのため、minify後のファイルサイズと依存ツリーを得る方法が今のところよく分かっていません。(stats.json自体にも入らない)

minify後の依存ツリーをみる方法やツールがあるならお知らせください。

この辺のツールは[Almin](https://github.com/almin/almin "Almin")のファイルサイズをビルド後 gzipで10KB以下にするため、計測する方法を調べてやりました。

- [perf(StoreGroup): use lru-map-like insteadof lru-cache by azu · Pull Request #57 · almin/almin](https://github.com/almin/almin/pull/57)
- [Reduce file size · Issue #56 · almin/almin](https://github.com/almin/almin/issues/56)

追記(2019-02-06): 現在は`webpack-bundle-analyzer`という優れたビジュアライズツールがあるので、最初はこれを利用することをおすすめします。
もっと詳細なデータをみたい、フィルターしたい場合はwebpackの`stats.json`ファイルを見ていくと良さそうです。

- [webpack-contrib/webpack-bundle-analyzer: Webpack plugin and CLI utility that represents bundle content as convenient interactive zoomable treemap](https://github.com/webpack-contrib/webpack-bundle-analyzer)
- [Vendor and code splitting in webpack 2 – Adam Rackis – Medium](https://medium.com/@adamrackis/vendor-and-code-splitting-in-webpack-2-6376358f1923)

## おまけ

GitHubリポジトリの指定したファイルのサイズを表示してくれるbadgeサービスもあります。

 Badge       | URL
:------------|:---------------------------------------------------------------------------------|
Normal size  | ![](http://img.badgesize.io/ngryman/badge-size/master/index.js.svg)
Gzipped size | ![](http://img.badgesize.io/ngryman/badge-size/master/index.js.svg?compression=gzip)

- [ngryman/badge-size: Displays the size of a given file in your repository.](https://github.com/ngryman/badge-size "ngryman/badge-size: Displays the size of a given file in your repository.")