Terser で minify するときに ascii_only オプションを true にしないと Unicode Escape Sequence が展開されてしまう

Webpack + Babel + core-js な環境でビルドしたJavaScriptPerlサーバーで配信するプロジェクトで、あるときPerlサーバーで配信しようとしたときに Internal Server Error がでてしまった。

ログを確認すると、Wide character in subroutine entry at /path/to/Compress/Zlib.pm というエラーがでていた。どうやら、配信しようとしていたJavaScriptUnicodeが含まれていたため、Perlの内部文字列のutf8フラグが立ってしまい、そのままgzip圧縮をする Compress::Zlib モジュールに渡ってしまったのが原因のようだ。そのため、$content = encode("UTF-8", $content); としてUTF-8バイト文字列に変換すればよいのだが、そもそも元のJavaScriptにはUnicodeは含まれていない。しかし、ビルドされたJavaScriptを確認すると、たしかにUnicodeが含まれている。

いろいろ調査したところ、どうやら Terser は ascii_only オプションが false の時(デフォルト)は、"\u2028" といった Unicode Escape Sequence を展開してしまうことがあるらしいということがわかった。そして、core-js によって挿入された whitespaces.js というモジュールには Unicode Escape Sequence が含まれており、Terser がこれを展開してしまったため、Perlで読み込まれたときにutf8フラグが立ってしまい問題が引き起こされたようだ。

そのため、webpack.config.js で以下のように Terser の ascii_only オプションを true とすることで問題を解決した。

const TerserPlugin = require('terser-webpack-plugin');

module.exports = {
  ...
  optimization: {
    minimizer: [new TerserPlugin({
      terserOptions: {
        format: {
          ascii_only: true
        }
      }
    })]
  }
}

今回のような状況下でなくても、Unicode Escape Sequence が勝手に展開されるのはバグの温床になるので、ascii_only オプションは常に true としておいたほうがよさそうである。

なお、このときの環境は以下の通り。

  • Node.js: 16.13.2
  • Webpack: 5.51.1
  • Babel: 7.15.0
  • core-js: 3.16.3
  • terser: 5.7.1
  • terser-webpack-plugin: 5.1.4

参考