webpackの静的解析で余計なモジュールをバンドルさせない

webpack はビルドの際にデフォルトで簡単な静的解析を行い、副作用のない置換を行ってくれる部分があります。

全ては把握していないのですが、例として特定の条件を満たす if の条件を計算済み bool に書き換えてくれます。このエントリではこの件について紹介します。

(この記事の動作確認: webpack@3.4.1, node@6.11.1)

webpack がifの条件を事前計算してくれる

前にも書いたよう、webpack は if 文の条件式を静的解析してくれます。例えば DefinePlugin を利用して if (process.env.NODE_ENV !== 'production' ) としてデバッグ用コードを仕込んだり、逆に本番専用のコードを仕込んだりするのに使いますが、これを UglifyJSPlugin でその部分を消してくれるのも有名です。

例として、 if (process.env.NODE_ENV !== 'production' )は DefinePluginにより if ("production" !== 'production' ) のように置換され、静的解析により if (false) に、UglifyJSにより if (false) な部分を取っ払う、と言った流れが発生しているようです。

if (false) 内ではrequireされない

なぜ、webpackが静的解析を行うのでしょうか。

webpackは if (false) 内の require() や import() などを無視します。1 そのため、上記のdefine, uglifyと併せ、開発時のみ (or 本番でのみ) 使用するモジュールがあれば不要な際はバンドルすらしなくて済みます。

場合によっては大変な容量削減が見込めそうですし、UglifyJS単体ではwebpackでバンドルしているのにの使っていないモジュールを外すことは出来ないので重要な役割です。

Ex: truthy な場合

Ex: falsyな場合

静的解析可能な条件

上記のようなif条件のbool化にはいくつかの条件を満たす必要があるようです。

プリミティブ値の同リテラル同士 の == / === / != / !==

左辺と右辺が同じ型の所謂プリミティブ値のリテラル同士の同値比較演算子が主な対象のようです。ここで言うプリミティブ値、リテラルとは

  • string型
    • ' ~ ',  " ~ " 文字列リテラル
    • ` ~ ` テンプレートリテラル
  • number型
    • 5 整数リテラル
    • 5.3 浮動小数点数リテラル
  • boolean 型
    • false / true 真偽値リテラル

を指します。一度でも変数を経由してはダメです。DefinePlugineで置換される場合を除いて。

同型の異種リテラル同士は可、型が異なる場合はダメ

上記のリテラル同士の比較について、文字列型同士であれば

  • 'fuga' == "fuga"
  • `fuga` == 'fuga'
  • `fuga` == `fuga`
  • 5. == 5
  • 5.0 == 5

などはいずれも true に置換されますが、'5' == 5, 1 == true のようなあからさまに解決できそうなものも対象外になります。

リテラル単体なら変換されずにtruthy / falsy

上記のリテラルをif文に単体で放り込んだ場合、false, 0, ''はfalsy、それ以外のtruthyな値はtruthyとして機能しますが置換はされません。

null は単体なら falsy

null はfalsyな値として解釈されます。そのため、if(null) 内ではrequireなどは変換されない他、後述の論理演算でもfalsyとして機能します。

ただし、null == null のような比較演算は静的解析の対象外のようです。

あと undefined はそもそも予約語ではなく変数なので関係ないです。

上記を満たす値、式同士の論理演算

if 文の条件に前述の条件を満たす値と || && のような論理演算を組み合わせ論理演算を行った場合も true / falseに変換されます。

例えば

'' || 'hoge' => true

false && 'fugafuga' => false

のように。

あと ! については  !nullは変換されず(もちろんtruthy扱い)、null以外の値については !'' => true のように変換される模様。

その他の演算子

  • 数値リテラル同士の単純算術演算
    数値リテラル同士であれば四則演算の静的解析ができそう。いつ役に立つっていうんだ……
  • 文字列リテラル同士の +
    ''+'' => false となってくれる。もちろん減算は数値キャストが起こるのでやってくれないみたい。
  • それ以外の算術演算:ダメっぽい
    カンマ演算子:ダメっぽい
  • ビット演算子:ダメっぽい
  • 上記条件を満たす三項演算子
    なんか行けた。パターンが複雑にならない限り世話にはならないな
  • 同値以外の比較演算子:同型であってもだめ。まあねw

正直気にする機会はなさそう。

特別な変数

__dirname ビルド前ファイルのディレクトリまでのフルパス

__filename ビルド前ファイルのフルパス

他の場所に埋め込むと / だとか /index.js だとかになる厄介なやつ。何故かif内で使うと上記のように解決されるので `${__dirname}/hogehoge.js` == __filename (=>true/false) のように使うことは出来るが読み込み元で変わるわけでもないのでものすごく微妙。


(網羅できていないパターンがあれば教えてください。)

まとめ

上記の条件を満たしてfalsyになるような書き方をしておけば巨大になりがちなwebpackの吐き出すバンドルのサイズが少しでも抑えられるかもしれません。SPAの読み込みが遅い?まずはこのあたりからも見直してみませんか?


  1. ES6 の import from はブロック内なので使用できません。(トップレベルでのみ使用可のため) 

コメントを残す