本セッションの登壇者
セッション動画(YouTubeチャンネル登録もお願いします。)
mizchiです。バンドル最適化マニアクスというテーマで話したいと思います。
ahomuさん的には、CDN Edge Workersの話をしてほしかったらしいですが、ガン無視してManglingの話をします。
フロントエンド改善とはビルド改善!?
突然なんですけど、JavaScriptでかいと辛いですよね。
なんで辛いかというと、開発時だとビルドサイクルが遅いとか、npm installが自然と肥大化しててCIが遅いとかがあります。ユーザー体験としても、あたりまえなんですけどダウンロードが遅い。あと、モバイルだととくにCPUをエバリュエーションで酷使して、バッテリーが辛いです。
個人的には、フロントエンド改善ってほぼビルド改善だと思っていて、ただ誰が何を改善するのかというと、アプリケーション開発者はちょっとマクロなチューニングをする - 何をインポートして何をインポートしないか。ライブラリ開発者はマイクロチューニング - TreeshakeできるAPIとかManglingとかの話をするという感じですね。
今日は、利用者が知っておくべきTreeshakeと、マイクロチューニングの話をします。
基礎編 - TreeshakeとDCE
TreeshakeとDCEなんですけど、Treeshakeはバンドラが未使用のimportされているモジュール、というか変数などを削除する機能ですね。impor{a,b}で、aしか使ってないときはbが消えます。
ただTreeshakeには発動する条件があって、トップレベルで副作用を発生させてはいけない。この例だと「.getTimezoneOffset();」というのが副作用判定されます。
この「agadoo」はリッチ・ハリス(Rich Harris)、Rollupの作者によるコードです。ライブラリでチェックできます。
DCEは未使用のコードを削除する機能です。見たらだいたい直感的にわかると思うんですけど、if(fallse)とか、return文のあととかですね。あと三項演算子もそう。
これは、上のコードをrollupでbuildすると、下のコードになります。X=1;だけ残る感じですね。
Treeshake + DCEの実践
TreeshakeとDCEをやるとどうなるか、このrollup.config.jsは後で見直してくださいという感じなんですが、process.envで定数を定義してるとこだけ見ておいてください。
こういうコードが、prodとdevで、productionのときはprod、そうじゃないときはdevということをやったとします。
最初は定数展開で、”production”===”production”が作られます。
それがterserによって評価されてtrueに置換されて、
三項演算子でprodだけ残って
rollupがprodだけ残して展開して
terserが圧縮して、正確には順番が違うんですけど、だいたいこういう流れです。こういう流れで最終的なものができる。
DCEの使い方なんですけど、とにかくif(false)を作って開発環境のツールを落とす、というのが使う側の発想なんですが、ライブラリ作者としてはこれが可能なAPIを提供するという話ですね。
サイズ視点だとjQueryみたいなメソッドチェーンはかなり相性が悪いです。ライブラリ作者としては、必要なだけimportするんですけど、とくにimport*asは避けた方が良いです。
あと一部のライブリReactみたいなのは、NODE_ENV=productionって入れるだけで勝手に読み取ってbuildに最適化したりします。
マイクロチューニング上級編
こっからちょっと上級編です。
○terserと仲良くなる
terserと仲良くなることがもうとにかく大事。最近はterser以外もあるんですけど、terserが一番ノウハウがたまっているので使ってます。
で、try.terser.orgをひたすら叩いて確認しながら動かしましょう。公開API以外は全部1 - 2文字にします。
○compress - 何が短くなるか?
そもそも何が短くなるかというと、ローカル変数は外に出ない分、短くなるんですけど、exportされるシンボルはあたりまえですけどその名前がキープされないといけないので、最終的な出力に残っちゃいます。
○compress - メンバアクセスは鬼門
ここから先がちょっと直感に反するやつなんですけど、メンバアクセスが鬼門で、x.f.関数でプライベートの値は1個だけ返すという構図なんですが、これはほぼManglingされません。
というのはこれ、アクセスしたときに実質scorpの参照などが発生するので、何が必要かわからない、つまり必要かどうかがterserレベルではわからないってことですね。
これを仮にオブジェクトやめてフラットに展開すると、さもうほぼ理想的なコードが出ます。だからオブジェクトはちょっとやめたほうがいい。
○発展編 - 複数回minify
これはいろいろと実験して見つけたおもしろい挙動で、6回minifyすると定数になるというコードです。これ、でかいオブジェクトの中に2、4、6という値が入っていて、それをコンソールログでアクセスするコードなんですけど、6回やってやっとこれです。
つまりterserはアクセス先の定数判定をめちゃくちゃ浅くしかやってない。これ、compress:{pass:6}でやっとこの動きをします。
ただこんなふうに定数化するなんて耐えられない人がいることはもちろんわかっていて、そういう人にはこのterser({mangle:{properties:{regex:”^_”}という手段があります。これは正規表現を満たしたプロパティをmangle対象にします。
これは、アンスコから始まるものがMangling対象になるんですけど、本当にそうかどうかっていうのはAPI定義とかちゃんと見て人間が確認する必要がある。
○マイクロチューニング - TS編
マイクロチューニングの話でTypeScript使ったときの話は、今日は時間がないのでスキップするんですが、ひとつだけ覚えてほしいこととして、enumが生成するコードがやばいのでconst enumを使ってください。
実践結果の紹介
こういうのを実践して、結果どういうのができるかというと、これが3カ月くらい前に作った自作のTypeScriptコンパイラです。gzipで8.1Kbです。TypeScriptのコードをいろいろコンパイルできます。
どうやって小さくしたかというと、自作のパーサコンビネータでnamed tupleの構文定義を吐いて、それをバイナリに圧縮して、ランタイムではそれをインライン化して展開しました。ただし、ASI(セミコロン自動挿入)は未対応です。
こんな感じですね。ちょっとfuncsとか残ってるんですけど、予約語以外はほぼ消えて1文字になっているのが見えると思います。
このほかにShakerphobiaという自分で作ったツールがあります。bundlephobiaだとちょっとわからないTreeshake後のサイズを計測するツールです。これはブラウザ内でビルドしてます。
まとめ
まとめとしては、とにかくTreeshake、ライブラリを使う方も提供する方もTreeshakeが大事で、terserはメンバアクセスに弱いので、できるだけオブジェクト内定数を避けましょう。最終手段としてmangle.properties.regexがあって、ライブラリ作者は頑張ったほうがいいと思います。
終わりです。お疲れ様でした。