皆さん、今日は虚な話はしません。この2日間私が踏んだ大きな落とし穴と、そこからどう這い上がったかについて話します。事の発端は、本番環境の古いプロジェクトが今まで問題なく動いていたのに、最近ユーザーからページがたまに白画面になるとの報告があったことです。エラー率は高くありませんでしたが、ベース数が多いため、毎日大量のエラーログが私のメールに流れ込んできました。最初はバックエンドAPIの問題かと思い、調べても問題は見つかりませんでした。その後、フロントエンドの監視を見て、驚きました。すべてJSエラーで、しかも圧縮後のバンドルファイルに集中していました。
ご存知の通り、本番環境のJSは圧縮されていて、1行に数万文字あり、エラーメッセージは全く読めません。私はその「Unexpected token」をじっと見つめながら、まるで嘲笑されているかのようでした。仕方なく、ソースマップを有効にして位置を特定すると、エラー箇所は多種多様でしたが、共通点がありました。それは、すべて条件分岐内のコードか、ES6+の新しい構文を使っていることでした。
この時になって、問題は圧縮設定にあるかもしれないと気づきました。私たちのプロジェクトで使っている圧縮ツールは古く、当時は手間を省くためにデフォルト設定をそのまま使っていて、細かく見ていませんでした。デフォルト設定には「drop_debugger」や「compress」内の様々な最適化項目があり、古いコードにとってはまさに災難でした。
最も典型的な例を挙げると、圧縮ツールはデフォルトで「常に偽」と判断した条件分岐を直接削除します。例えば、コードに if (typeof window !== 'undefined') のような判断を書いたとします。これはSSR対応や特定の特殊環境でのエラー防止を目的としています。しかし、圧縮器から見れば、この判断は無意味です。なぜなら、コンテキストを分析してwindowが必ず存在すると判断し、ifブロック全体を削除してしまうからです。その結果、実機の一部のWebView環境では、windowの特定のプロパティにアクセスできず、コードが直接クラッシュします。
さらに厄介なのは、圧縮器が関数内の変数名を極端に短く変更することです。例えば、userName を a に、orderList を b に変えます。これはほとんどの場合問題ありませんが、コード内で eval や new Function を使っていて、その中で外部変数名を参照している場合、圧縮後は変数名が一致せず、直接ReferenceErrorが発生します。本番環境には、これを使っている古いモジュールがいくつかあり、普段は問題ないのに、圧縮すると壊れます。
そこで、この2日間、痛みを伴いつつも、圧縮設定を研究するために午後を費やしました。変更後、どうなったと思いますか?本番エラーが直接8割減りました!決してオカルトではなく、設定が正しくなかっただけです。
具体的にいくつか変更した点があるので、メモを取ってください。いつか役立つかもしれません:
第一に、compress オプション内の conditionals と dead_code をオフにしました。つまり、圧縮器が勝手にコード分岐を削除するのを防ぎます。これにより圧縮率は少し下がり、数KB増えるかもしれませんが、安定性が得られるので価値があります。特に、長い歴史を持ち、様々な互換性判断が書かれた古いプロジェクトにとって、これらのオプションは時限爆弾です。
第二に、mangle 内の eval パラメータを true に設定しました。これは、コード内で eval や new Function が検出された場合、内部の変数名を変更しない、または関数内の変数を短縮しないことを意味します。これにより圧縮効果は低下しますが、本番でクラッシュするよりはましです。
第三に、compress 内の unused もオフにしました。このオプションは「定義されているが使用されていない」パラメータを削除します。しかし、意図的にプレースホルダーとしてパラメータを残す場合があります。例えば、コールバック関数で3番目のパラメータが有効で、最初の2つは固定シグネチャです。圧縮器はそれを知らず、最初の2つが使用されていないと見て直接削除し、コールバックのパラメータがずれて、呼び出し時にundefinedが渡され、ロジックが完全に壊れます。
第四に、最も見落とされがちなのが、output の ascii_only 設定です。コード内に中国語や絵文字の文字列がある場合、この設定を false にすることをお勧めします。そうしないと、すべて \uXXXX のようなエスケープシーケンスに変換されます。機能は同じですが、文字列の長さが爆発的に増え、一部の古いブラウザでは長すぎる文字列の解析に問題があり、フリーズやエラーが発生する可能性があります。
とにかく、今の私の設定は「余計なことをしない」モードです。圧縮の目的は元々サイズを減らすことですが、サイズを減らすために機能を壊してしまったら、節約したトラフィックは残業代にもなりません。計算してみると、設定変更後、バンドルされたファイルサイズは約15%増加しましたが、本番エラー率は以前の1日数百件から、現在は1日数件に減少しました。そのサイズ増加は大した問題ではありません。
最後に注意点ですが、圧縮設定を変更した後は、必ず完全な回帰テストを実行してください。特にルートの遅延ロードや動的importに関わる部分です。なぜ私が知っているのか聞かないでください。涙なしには語れません。とにかく、今は本番が安定し、私もぐっすり眠れています。皆さんも同様の問題があれば、コードロジックを疑う前に、まず圧縮設定が「逆効果」になっていないか確認してみてください。