babel-preset-env と minify

2017年3月31日 0 開発 , ,

babel-preset-env を試す で試した Electron 用の設定を実際に akabekobeko/examples-electron へ適用してみたところ、いろいろと問題があったので記録しておく。

JavaScript のビルド設定

この記事を読むための前提条件として必要な情報を先にまとめておく。

私は Electron 用 JavaScript ビルドを package.json へ定義している。必要最小の npm は以下。

npm 用途
browserify Bundler。JavaScript 間のファイル参照を解決して単一ファイルを出力する。
babelify Transpiler である Babel の Browserify 用プラグイン。ES.next で書かれた JavaScript を ES5 などに変換してくれる。
babel-preset-env Babel が ES.next を解析するためのプリセット。対象環境を設定することで、それにあわせた変換を実行してくれる。従来、環境を指定せず最新 ES.next を常に ES5 化する場合は babel-preset-latest を使用していたが、現在これをインストールすると deprecated と共に env を使用せよと警告される。
babel-preset-react Babel が React の JSX を解析するためのプリセット。
cross-env npm-scripts 上で環境変数 NODE_ENV の値を設定するツール。npm の中にはこの値によって処理を分岐するものがあり、例えば React は production だとリリース用となりログ出力処理などが無効化される。
exorcist Web ブラウザーの開発者ツールで変換前後の JavaScript を紐付けて解析するための Source Maps を生成するツール。これがあると実行は変換後、デバッガー表示は変換前のファイルという運用ができて便利。
uglify-js JavaScript を minify するツール。非常に高機能でインデント削除だけでなく参照を解析したうえで変数名などを短縮するなどの最適化や難読化もおこなえる。

これらを使用した package.json 定義は以下。私は Babel の設定も独立したファイルではなく npm-scripts などと共に package.json へ記述する派。

{
  "babel": {
    "presets": [
      [
        "env",
        {
          "targets": {
            "electron": 1.6
          }
        }
      ],
      "react"
    ]
  },
  "scripts": {
    "build:js-main": "browserify -t [ babelify ] ./src/js/main/Main.js --exclude electron --im --no-detect-globals --node -d | exorcist ./src/assets/main.js.map > ./src/assets/main.js",
    "build:js-renderer": "browserify -t [ babelify ] ./src/js/renderer/App.js --exclude electron -d | exorcist ./src/assets/renderer.js.map > ./src/assets/renderer.js",
    "release:js-main": "cross-env NODE_ENV=production browserify -t [ babelify ] ./src/js/main/Main.js --exclude electron --im --no-detect-globals --node | uglifyjs -c warnings=false -m -d DEBUG=false > ./dist/src/assets/main.js",
    "release:js-renderer": "cross-env NODE_ENV=production browserify -t [ babelify ] ./src/js/renderer/App.js --exclude electron | uglifyjs -c warnings=false -m -d DEBUG=false > ./dist/src/assets/renderer.js",
  },
  "devDependencies": {
    "babel-preset-env": "^1.3.2",
    "babel-preset-react": "^6.23.0",
    "babelify": "^7.3.0",
    "browserify": "^14.1.0",
    "cross-env": "^4.0.0",
    "exorcist": "^0.4.0",
    "uglify-js": "^2.8.19"
  }
}

Electron は Main/Renderer プロセスのエントリー ポイントが別れているため、個別にビルドする。build:js-* は開発用、release:js-* がリリース用の定義。babel-preset-env の設定は現時点で最新の Electron v1.6 を対象としている。

この状態で開発用ビルドを実行すると Electron v1.6 は Chromium 56.0.2924.87 を採用しているため、ES2015 Classes など対応されているものはそのままに、Modules など未対応のものだけ変換される。もちろん、変換されたコードはちゃんと Electron アプリとして実行可能。

しかしリリース用ビルドは問題がある。開発用と同様に Browserify + babelify 部分まではよいのだが、uglify-js は ES2015 以降に対応していないためエラーになる。というわけで、この問題へ対応してみる。

uglify-js の harmony 版

ES2015 が標準化されてひさしい。そのため当然 uglify-js にも対応が要望されている。ただし ES2015 を含む ES.next へ対応するのはかなり難しい。そのため uglify-js は実験的な ES.next 対応として harmony 版を提供、README の Harmony 欄にはこう書かれている。

If you wish to use the experimental harmony branch to minify ES2015+ (ES6+) code please use the following in your package.json file:

"uglify-js": "git+https://github.com/mishoo/UglifyJS2.git#harmony"

or to directly install the experimental harmony version of uglify:

npm install --save-dev uglify-js@github:mishoo/UglifyJS2#harmony

See #448 for additional details.

これを踏まえて uglify-js を harmony 版へ差し替える。npm un -D uglify-js してから npm i -D uglify-js@github:mishoo/UglifyJS2#harmony して devDependencies 上のバージョン表記が npm 管轄から特別なホストへ切り替わることを確認。

{
  "devDependencies": {
    "uglify-js": "github:mishoo/UglifyJS2#harmony"
  }
}

この状態で再びリリース用ビルドを実行すると正常に終了した。出力されたファイルを見ると class などはそのままに uglify-js へ指定された設定どおりの minify が実行されている。ただし問題もある。

harmony は実験的なものでありバグも多い。uglify-js の issue を ES6ES2015 で検索すると対応に苦慮しているようだ。これまでの流れと現状を把握するには README にも掲載されている issue Harmony support を読むとよい。2014 年から始まり今も活況である。

Releases についても harmony は Pre-release となり npmjs としてのバージョン管理下にはない。よって現時点ではプロダクトに採用するのを避けたほうがよいだろう。

babel-preset-babili

uglify-js がダメなら ES.next へ正式対応している minify ツールを使えばよい。というわけで Babel ファミリーの babel-preset-babili を試す。npm i -D babel-preset-babili してから Babel と npm-scripts を書き換える。

{
  "babel": {
    "presets": [
      [
        "env",
        {
          "targets": {
            "electron": 1.6
          }
        }
      ],
      "react"
    ],
    "env": {
      "production": {
        "presets": [
          "babili"
        ]
      }
    }
  },
  "scripts": {
    "release:js-main": "cross-env NODE_ENV=production browserify -t [ babelify ] ./src/js/main/Main.js --exclude electron --im --no-detect-globals --node -o ./dist/src/assets/main.js",
    "release:js-renderer": "cross-env NODE_ENV=production browserify -t [ babelify ] ./src/js/renderer/App.js --exclude electron -o ./dist/src/assets/renderer.js",
  }
}

リリース版だけ minify したいので env.production.presets に babili を指定する。この状態でリリース用ビルドを実行すると確かに env も考慮しつつ ES.next 部分が minify された。しかし標準では dependencies の npm は minify されず、細かなカスタマイズはプリセットを構成する各種プラグインについて学習する必要がある。

uglify-js と同等の minify を再現できるかは分からないが babel-preset-env と組み合わせるなら、同じ Babel ファミリーであり ES.next 対応の知見も共有されそうな babili がよさそう。

というわけで babili を掘り下げようと思ったのだが README をざっと読んだだけでも重量級っぽい雰囲気がプンプンただよってるため、後で独立した記事としてまとめる予定。


REPLY

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です