UglifyJSでコードの書き換えはいろいろやれるけどちょっと分かりにくい(define, compress, source map編)

今まで UglifyJS は単に圧縮としてしか使ってなくて、あまり細かく設定を見たことがなかったが、いざ触ってみたらいくつか発見があったのでそのメモを残しておく。

UglifyJS — JavaScript parser, compressor, minifier written in JS

まとめ

  • 外部から define で値を埋め込むことができる
  • UglifyJS の基本はコードの圧縮だが、その際に dead code の除去も行える
  • source map の扱いにはややクセがある

define による外部からの設定の埋め込み

UglifyJS 自体は Node.js で動くので Node.js から環境変数を取得することはできるが、UglifyJS は Browserify ではないので、Browserify のように環境変数にアクセスしている部分をいい具合に置換してくれるわけではない。

そこで利用できるのが define.

  • オプション –define でコード内の定数をセットできる
    • literal に展開されるので文字列を埋め込むなどは難しい
    • boolean だけに留めておくのが正解っぽい
  • shell script の機能を呼び出して分岐させることはできる
uglifyjs  --define PRODUCTION=`[ "$NODE_ENV" = "production" ] && echo "true" || echo "false"`

とやると JavaScript のコードの中では

if ( PRODUCTION ) {
  ...
} else {
  ...
}

のように参照して分岐させることができる。

compress

結構細かく制御できるし、default では割と積極的にコードが削除される。

例えばデフォルトの動作で dead_code を remove できるので、上のように if を書いておくと PRODUCTION が true の場合は then の中身だけが出力コードに残り、false の場合は else の中身だけが出力コードに残る。

他の assets や API の JSON を取得する処理はよくあると思うが、その URL は development, staging 環境と production で差し替えたくなるだろう。上のようなコードにしておくと UglifyJS の compress だけでそれが実現できる。(UglifyJS 後には if はなくなって差し替え後のコードだけが残る。)

source map

  • source map オプションは output オプションの後に置かないと機能しない
    • output オプションのパスを絶対に参照するみたい(中身は見てない)
  • option の解釈が自身の生成向けオプションと読み込むソース向けのオプションと両方入っていて分かりにくい。これは UglifyJS がトランスパイルの最終工程に置かれることが多いゆえなのか、設計が古いのか、もうちょっと工夫してほしかった。

例えば uglifyjs 3.2.2 で確認したところ出力先のコードに source map を含めるには以下のようにする必要があり、

uglifyjs -o ./output/application.js \
         --source-map url=inline,includeSources

さらにこの際、./output/application.js.map の生成は抑止できない。(ファイルの生成と inline への埋め込みがトグルするものと思っていたので指定に失敗しているのかと思ってしばらく悩んだ。)

ちなみに uglifyjs の前工程で source map を inline に生成している場合は以下のように content=inline を追加する必要がある。まぁその場合は最終工程の uglifyjs を省略すればいいだけじゃね?と思わなくもない。

uglifyjs -o ./output/application.js \
         --source-map content=inline,url=inline,includeSources

cf. Emit inline source maps with UglifyJS v3 · Issue #2711 · mishoo/UglifyJS2

More