Electron を試す 2 – パッケージ化におけるプラットフォーム固有処理とコンパイル分岐

2015年10月4日 0 開発 , ,

Electron を試すで残された課題を解決したので、その内容を記録しておく。

更新履歴

  • 2015/10/5 追記
    本記事のはてブにて id:Pasta-Kさんより .ico ファイルを反映させるために wine が必要との指摘があった。試してみたところ OS X 環境でもアイコンとバージョン情報変更を反映した Windows 向けパッケージを生成できたので、その内容を追記した。あと、追記があることに気づかれないかもしれないので、もくじを追加。

もくじ

プラットフォーム固有処理

electron-packager--icon オプションに .ico ファイルを指定すると OS X でエラーになる問題だが、Windows 環境で実行したらパッケージ化に成功した。一方、Windows 環境だと OS X 版のパッケージ化がスキップされる。Linux 版は特別なオプションがないためか、OS X と Windows のどちらでもパッケージ化できた。

この状況から察するに、アイコンの埋め込み処理などでプラットフォーム固有 API を利用している可能性がある。つまり完全なパッケージ化を実行する場合は対象プラットフォームと同じ環境でビルドしなければならない。

以上を踏まえ、リリース用の npm-scripts を以下のように再定義した。

{
  "scripts": {
    "release:css": "stylus -c ./src/stylus/App.styl -o ./dist/src/bundle.css",
    "release:js-main": "browserify -t babelify ./src/js/main/Main.js --im --no-detect-globals | uglifyjs -c warnings=false -d DEBUG=false > ./dist/src/main.js",
    "release:js-renderer": "browserify -t babelify ./src/js/renderer/App.js | uglifyjs -c warnings=false -d DEBUG=false > ./dist/src/bundle.js",
    "release:clean": "rimraf ./dist/src",
    "release:copy": "cpx \"./src/**/{*.html,*.eot,*.svg,*.ttf,*.woff,package.json}\" ./dist/src",
    "release:build": "npm-run-all -s release:clean release:copy -p release:css release:js-main release:js-renderer",
    "release:pack-osx": "electron-packager ./dist/src Starter --out=dist/bin --cache=dist/cache --platform=darwin --arch=x64 --version=0.33.4 --overwrite --asar --icon=res/app.icns",
    "release:pack-win": "electron-packager ./dist/src Starter --out=dist/bin --cache=dist/cache --platform=win32 --arch=x64 --version=0.33.4 --overwrite --asar --icon=res/app.ico --version-string.CompanyName=\"Company\" --version-string.LegalCopyright=\"Copylight (C) USERNAME, All right reserved.\" --version-string.FileDescription=\"Electron application\" --version-string.OriginalFilename=\"Starter.exe\" --version-string.FileVersion=\"1.0.1\" --version-string.ProductVersion=\"1.0.1\" --version-string.ProductName=\"Starter\" --version-string.InternalName=\"Starter\"",
    "release:pack-linux": "electron-packager ./dist/src Starter --out=dist/bin --cache=dist/cache --platform=linux --arch=x64 --version=0.33.4 --overwrite --asar",
    "release:osx": "npm-run-all -s release:build release:pack-osx",
    "release:win": "npm-run-all -s release:build release:pack-win",
    "release:linux": "npm-run-all -s release:build release:pack-linux"
  }
}

従来は release:pack-XXXX 系だけ分岐して release で統合していたが、新しいスクリプトではリリース処理もプラットフォームごとに定義するようにした。実行も対象プラットフォーム上で個別におこなう必要あり。

なお、アイコンのファイル形式自体には問題なかったのだが、OS X 版も透過になっていなかったりしたので作りなおした。Windows 版のアイコン作成は久しぶりに @icon変換 を利用。複数の透過 PNG からマルチ アイコンを作成できる。

Windows 版については、他にバージョン情報も設定している。これらは --version-string.XXXX オプションで指定し、EXE ファイルの詳細タブに表示される。以下は参考画像。ダイアログの内容だけでなく、左上のアイコンも Electron 標準ではなく独自のものに変更されていることを確認できる。

ファイル情報

なおファイルのバージョンは #129 version-string.FileVersion is not working on Windows のためか Electron の 0.33.4.0 になってしまうようだ。

Windows 環境で実行する場合は Windows で node-gyp を使った npm を動かすための環境構築を先に実施しておくこと。これをおこなう前に npm install していた場合、ビルドは成功するがアイコンとバージョン情報の書き変えに失敗する。

そうなってしまったら環境構築した後に node_modules を削除し、改めて npm install することでビルド環境を正常化できる。

オマケとして OSX と Windows のアイコン作成方法を掲載しておく。

OS X

アイコンの元になる透過 PNG を以下のファイル名とサイズで用意する。ファイル名の @2x というのは Retina 用で、その前につくサイズの倍となる。詳しくは Mac Developer Library の Optimizing for High Resolution を参照のこと。

ファイル名 サイズ
icon_16x16.png 16×16
icon_16x16@2x.png 32×32
icon_32x32.png 32×32
icon_32x32@2x.png 64×64
icon_128x128.png 128×128
icon_128x128@2x.png 256×256
icon_256x256.png 256×256
icon_256x256@2x.png 512×512
icon_512x512.png 512×512
icon_512x512@2x.png 1024×1024

これらを app.iconset というフォルダに格納し、Terminal で app.iconset の置かれた場所まで移動してから以下のコマンドを実行。

$ iconutil -c icns app.iconset

すると app.icns という名前で OS X 用のアイコン ファイルが生成される。フォルダ名の app 部分がファイル名になるので、名前が決まっているならそれで付けてもよい。

Windows

アイコンの元になる透過 PNG を用意する。OS X のものを使いまわしてもよい。必要なサイズは以下となる。

サイズ
16×16
24×24
32×32
48×48
64×64
128×128
256×256

次に @icon変換 を用意する。最終バージョンは 2007/2/4 版と古いが現在でも実用十分なので気にしなくてよい。

アプリを起動して、そのウィンドウに画像ファイルをドラッグ & ドロップすると、画面左のリストに登録される。リストの画像をすべて選択した状態 ( スクリーンショットを参照のこと ) にしておく。

@icon変換

メイン メニューから「ファイル」→「マルチiconとして保存」を選択すると、リストの画像からマルチ アイコン ファイルが生成される。

OS X 環境でアイコンとバージョン情報変更を反映した Windows 向けパッケージを生成する

本記事のはてブにて electron-packager でアイコン変更を含めた Windows 向けパッケージを生成する場合、wine が必要という指摘があったので試してみた。wine は Homebrew からインストール。ただし X11 に依存しているらしく、先にこれを入れよとエラーが出る。

$ brew install wine
wine: XQuartz is required to install this formula.
You can install with Homebrew Cask:
  brew install Caskroom/cask/xquartz

You can download from:
  https://xquartz.macosforge.org
Error: An unsatisfied requirement failed this build.

最新の wine だと X11 なしで動くという話をどこかで聞いた気がするけど、エラーでインストールを促されているので従う。

$ brew install Caskroom/cask/xquartz

次に wine をインストール。

$ brew install wine
==> Installing dependencies for wine: xz, libpng, freetype, jpeg, pkg-config, libtool, libusb, libusb-compat, fontconfig, libtiff, gd, libgphot
... 以下、略

依存パッケージが大量にあるため、すべてインストールするのに 30 分ぐらいかかった。wine を用意したので OS X 環境で Windows 向けパッケージを生成してみる。

$ npm run release:win
... 中略

> electron-starter@1.0.2 release:pack-win .../electron-starter
> electron-packager ./dist/src Starter --out=dist/bin --cache=dist/cache --platform=win32 --arch=x64 --version=0.33.6 --overwrite --asar --icon=res/app.ico --version-string.CompanyName="Company" --version-string.LegalCopyright="Copylight (C) USERNAME, All right reserved." --version-string.FileDescription="Electron application" --version-string.OriginalFilename="Starter.exe" --version-string.FileVersion="1.0.1" --version-string.ProductVersion="1.0.1" --version-string.ProductName="Starter" --version-string.InternalName="Starter"

Downloading electron-v0.33.6-win32-x64.zip
[============================================>] 100.0% of 48.85 MB (857 kB/s)
Packaging app for platform win32 x64 using electron v0.33.6
Wrote new app to dist/bin/Starter-win32-x64

ビルド成功。このパッケージを VMware FUSION 上の Windows 8.1 環境にコピーしてみたところアプリは正常動作した。またアイコン変更とファイルのプロパティからバージョン情報変更が反映されていることも確認できた。

つまり OS X & wine な環境であれば全プラットフォーム向けのパッケージをまとめて実行できる。

コンパイル分岐

ソフトウェア開発において、デバッグとリリースで振るまいを変えたくなることがある。例えばデバッグ時にはログ出力や特殊なメニューを提供し、リリース版ではそれらが顕在化しないようにコードごと削除する。

こうした処理を実現するため、ソースコードのコンパイル時におけるプリプロセスがある。例えば Visual C++ ではコンパイル時の環境変数を定義する機能がある。デバッグ版ではここに _DEBUG を指定して #ifdef _DEBUG から #endif までの区間を固有処理にする。

プロジェクトをデバッグ用ビルドした場合、コンパイルより前に環境変数が定義されるため、区間のコードは有効になる。リリース用ビルドでは環境変数が存在しないため、区間が不要と判定され抹消される。

この設計を JavaScript で実現する場合は watchify と連携させる場合、Source Maps と同時に指定する方法がわからなかった。また、watchify によるコンパイルの都度、uglify を走らせる実行コストも気になる。

そこで以下のようなハックを試みた。

まず、Main プロセスのエントリーポイントあたりで global.DEBUG を true にする。

import App from 'app';

class Main {
  constructor() {
    this._mainWindow  = null;
    this._rendererIPC = null;

    // Compile switch
    global.DEBUG = true;
  }
}

const main = new Main();

次に DEBUG 変数による分岐処理を定義。

class Main {
  constructor() {
    // 初期化処理...
  }

  onReady() {
    if( DEBUG ) { console.log( 'Launched' ); }

    // Window 作成などを実行...
  }

  onWindowAllClosed() {
    if( DEBUG ) { console.log( 'Quit' ); }

    App.quit();
  }
}

const main = new Main();
App.on( 'ready',             main.onReady           );
App.on( 'window-all-closed', main.onWindowAllClosed );

このようにして以下の npm-scripts を実行すると global.DEBUG が有効となり、これを判定している if 文の内容が処理される。開発用コンパイルの変更は不要。従来どおりでよい。

かわりにリリース用コンパイルを以下のように定義する。

{
  "scripts": {
    "release:js-main": "browserify -t babelify ./src/js/main/Main.js --im --no-detect-globals | uglifyjs -c warnings=false -d DEBUG=false > ./dist/src/main.js",
    "release:js-renderer": "browserify -t babelify ./src/js/renderer/App.js | uglifyjs -c warnings=false -d DEBUG=false > ./dist/src/bundle.js"
  }
}

長いので肝要の uglify 部分だけ抜粋。ファイル名が異なるだけで Main/Renderer プロセスで設定は共通である。

uglifyjs -c warnings=false -d DEBUG=false > ./dist/src/main.js

-c は圧縮フラグ。warnings=false は不要箇所の検出による警告を抑止するためのもの。

-d または --define オプションの後に NAME=VALUE を指定すると、その内容でグローバル定数が宣言されているものとしてコンパイルされる。

このスクリプトでは DEBUG=false としているため、これを if 文で判定している部分はすべて無効と扱われる。そう判定された箇所はコンパイル結果から除去されるので、結果としてリリース版では余計なコードが抹消される。

ここで疑問がわくはず。コード上にあった global.DEBUG = true という処理はどう扱われるのだ?と。これがあるならプリプロセス以降にグローバルな DEBUG という名前の変数が true に書き換えられ、結果としてこれを判定している if 文は有効になってしまうのではないか?

実際に uglify されたファイルの当該箇所は以下のようになる。

global.DEBUG=!0

処理自体は残っており代入対象も !0 となっているため判定している部分は true になるだろう。しかしこの変換がおこなわれるのは uglify の処理時である。つまり global.DEBUG 自体の代入は有効な処理だが global 修飾なしに DEBUG を参照している箇所は uglify 側の定数宣言と判定がおこなわれ無効化された。

…上記の内容はコンパイル結果からみた推測である。というのも多分そう動くだろうなぁと思って実装してみたらあっさり成功してしまい、私のほうがビックリしたぐらいだ。そのためハックと表現している。

しかし現時点の uglify なら有効であり、この処理によりデバッグ用メニューをリリース版から除去できたのでよしとする。

なお Main/Rederer ともに同じ方法で分岐できる。DEBUG を有効にする処理はエントリー ポイントになるファイルへ仕込むのがよいだろう。

サンプル プロジェクト

今回の内容に加え。アイコン修正や全プラットフォーム同時生成スクリプトを追加した v1.0.2 をサンプルとして公開する。


REPLY

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