update.electronjs.orgを使って、アプリの自動更新機能をお気楽に組み込む

ElectronのautoUpdater

Electronには、アプリの新しいバージョンをダウンロードして
更新する機能が用意されています。
https://www.electronjs.org/docs/api/auto-updater


Electronアプリからこの機能を使うには本来、
最新版アプリの情報を返すアップデートサーバーが必要になるのですが、


GitHubのReleasesに最新アプリバイナリを置いているアプリ
については、
Electron公式がアップデートサーバーを用意してくれているので、
アプリ側の修正だけで、簡単に自動アップデート機能を組み込めるようになっています。


(ここだよ、ここ)


update.electronjs.org

こちらが公式が用意しているサーバーで、
update.electronjs.org (GitHubにリダイレクト)

このサーバーは、次のパターンのURLを叩くと、GitHubの最新リリースの情報を取得して返します。

https://update.electronjs.org/update/${process.platform}/${app.getVersion()}


例えば、GitHubのレポジトリが"taku-o/myukkurivoice"で、
現在実行中のアプリのバージョンが"0.13.6"だとすれば、次のURLになりますね。
このURLをブラウザで開くと、JSON形式で最新リリースのデータが返ってくるのが分かります。
https://update.electronjs.org/taku-o/myukkurivoice/darwin-x64/0.13.6

{"name":"0.13.7","notes":"- \"MYukkuriVoice について\"ダイアログの表示を修正。\r\n  - ビルド時の...


対応している環境

update.electronjs.orgで対応している環境はLinux環境以外です。
Linuxは駄目かー。そっかー、残念ですね。


macOSのアプリについては、アプリにDeveloper ID Applicationの署名が必要。
実行中のアプリ、そして、ダウンロードする更新アプリ、両方に署名が必要です。
(アプリに署名がされていないと、自動アップデートがエラーになる。)

Error: Code signature at URL file:///Users/user/Library/Caches/jp.nanasi.myukkurivoice.ShipIt/update.TdmmDN0/
MYukkuriVoice-darwin-x64/MYukkuriVoice.app/ did not pass validation: コードオブジェクトがまったく署名されていません
Error: Could not get code signature for running application


Catalinaでは、アップル公証(Notarization)も必要かも?
調べていません。
わざわざ公証の実装を外してまで検証しないよ。
面倒だからね。仕方ないね(^_^)/


更新用のアプリをReleasesに置く

更新用のアプリは、zipで固めてGitHubのReleasesに置いておきます。 https://github.com/taku-o/myukkurivoice/releases/tag/0.13.8

1. バージョン番号のReleasesタグを作る(0.13.8 とか)
2. 更新用のアプリをzip形式にしてアップロードする
3. zipファイルのファイル名は、例えば、AppName-darwin-x64.zip のような形式にする


update.electronjs.orgは、Windows環境とMac環境に対応していますが、
いくつかアップロードされているzipファイルのうち、
どのzipファイルが、どの環境用のものかは、ファイル名から判断されています。
なので、zipファイル名には、mac, darwin, win32-x64など対応環境を示す文字を入れておきましょう。

mac
darwin
osx
win32-ia32
win32-x64


Electronアプリへの組み込み(自動的に更新処理)

Electronアプリのコードにアプリ自動更新の機能を組み込みます。


npmでupdate-electron-appを入れておいて、

npm install update-electron-app

アプリ側で、このコードを呼び出す。

require('update-electron-app')()

簡単!


Electronアプリへの組み込み(任意のタイミングで更新)

上の実装は簡単なのですが、しかし、ドキュメントによると、
10分毎に更新をチェックしにいくようです。
https://www.electronjs.org/docs/tutorial/updates


なんてこった(>_<)!
そんなにアプリを頻繁に更新するわけないぞ(>_<)!
1ヶ月に1回更新できたらマシな方だ(>_<)!


そういう場合は、checkForUpdates()を手動で叩いて、
好きな頻度で更新するか、メニューから更新できるようにしたら良いでしょうな。

autoUpdater.checkForUpdates();
import {app, dialog, autoUpdater} from 'electron';

// register events
autoUpdater.on('update-downloaded', (event, releaseNotes, releaseName) => {
  const dialogOptions = {
    type: 'info',
    buttons: ['Restart', 'Later'],
    title: 'Application Update',
    message: process.platform === 'win32' ? releaseNotes : releaseName,
    detail: '新しいアプリのダウンロードが完了しました。アプリを再起動して更新を適用します。',
  };
  dialog.showMessageBox(dialogOptions)
  .then((result) => {
    const btnId: number = result.response;
    if (btnId === 0) {
      autoUpdater.quitAndInstall();
    }
  });
});

autoUpdater.on('error', (err: Error) => {
  const dialogOptions = {
    type: 'error',
    title: 'Application Version Check Error.',
    message: 'アプリのアップデート中にエラーが発生しました。',
    buttons: ['OK'],
    defaultId: 0,
    cancelId: 0,
  };
  dialog.showMessageBox(dialogOptions);
});

// setup feed url
const server = 'https://update.electronjs.org';
const feed = `${server}/taku-o/myukkurivoice/${process.platform}-${process.arch}/${app.getVersion()}`;
autoUpdater.setFeedURL({
  url: feed,
  serverType: 'json',
});

// update
autoUpdater.checkForUpdates();


いったんcheckForUpdates()を実行すると、
アプリの更新処理をストップ出来ないらしく、
実行前にユーザーに確認ダイアログを出しておいた方が良さそうです。


あるいは、今動作しているアプリのバージョンが最新かどうか確認しておいてから、
アプリを更新しても良いか聞いておきましょう。


autoUpdaterが間違ったファイルをダウンロードする(1敗)

ElectronのautoUpdaterは、おおよそ次の順番で処理しています。
Mac環境)

1. 実行中のアプリの署名の確認
2. 実行中のアプリが最新版かどうかチェック
3. 最新アプリのダウンロード
4. zipファイルの解凍
5. 最新アプリの署名の確認
6. アプリの入れ替え


update.electronjs.orgはこんな実装(↓)になっているので、
データファイルなどの名前に"mac"の文字が入っていると、
更新アプリでなく、間違ったファイルをダウンロードすることがありますね。
(1敗)

# https://github.com/electron/update.electronjs.org/blob/ed32d4fedca459fb734a5d898d8573f57b605bd4/index.js#L236

const assetPlatform = fileName => {
  if (/.*(mac|darwin|osx).*\.zip/i.test(fileName)) return 'darwin-x64'
  if (/win32-ia32/.test(fileName)) return 'win32-ia32'
  if (/win32-x64|(\.exe$)/.test(fileName)) return 'win32-x64'
  return false
}


ダウンロードされたzipファイルは、 次のような場所に保存された後、解凍され、
その後、アプリの署名の検証が行われます。

/Users/user/Library/Caches/xx.xx.bundleid.ShipIt/update.QCBggxR


なので、誤ったファイルをダウンロードしていて、 署名エラーが発生している時は、
このディレクトリを監視していれば、問題を追えます。
(エラーになった時、すぐ消されちゃうみたいですけど。)


if (/win32-x64|(\.exe$)/.test(fileName)) return 'win32-x64'

試していませんけれど、このコードを見る限り、
Windows環境はexe形式もいけるみたいですね。
自己解凍形式がサポートされているのかな?


終わり

MacのElectronアプリだったらさ、
自動更新機能を組み込まなくても、Mac App Storeに出せば良いじゃない?


そんなふうに考えていた時期が俺にもありました


どうも駄目なレビュアーに当たった気がします。
審査が通らない。
話が通じない。


そろそろ審査アプリのバンドルIDを変更して、
レビューする人を切り替えようかと考え始めています。
(あくまでも自分は悪くないと決めつけるスタイル)

2020年2月最新のElectronアプリ、アップル公証(Notarization)対応

アップル公証(Notarization)

Macにはアップル公証という、Macの野良アプリを署名する仕組みがあり、
このアップル公証を通しておかないと、 macOS Catalinaではアプリを起動する際、そのアプリの起動がブロックされます。
(起動する術はある、らしい)


少し前、これに対応して、自前のElectronアプリで アップル公証を通していたのですが、
実は、その時は、公証の条件が少し緩くなっていたらしく・・・

アップル、macOS野良アプリの公証要件を一時的に緩和。Catalinaへのスムーズな移行のため https://japanese.engadget.com/2019/09/05/macos-catalina/


またもや、自家製Electronアプリで、 アップル公証が通らなくなってしまったのでありました。
(´・ω・`)


この記事は、Electron製アプリでアップル公証(Notarization)を通す、
その手順についてのお話となります。


Xcodeのバージョン

まず最初に、アップル公証を通すには、新しめのXcodeが必要です。 バージョンが低い場合はXcode 10以降にバージョンを上げておきましょう。
App Storeからインストールできない場合は、Apple Developerからダウンロードしてきて、インストールします。
https://developer.apple.com/jp/


作ったアプリに署名をつける

作ったアプリに署名をつけます。
electron-osx-signというライブラリを使うか、
codesignコマンドで、Electronアプリに署名をつけられます。
https://github.com/electron/electron-osx-sign


おそらく、electron-osx-signを使うか、
electron-builder、electron-packagerのコマンドで、 オプションを追加して署名をつける方が楽でしょう。


が、自分の場合は、
ElectronアプリでDynamicLibraryを使っていたり、
アプリに同梱している別の実行ファイルを叩いていたりしていまして、
アプリの構造が少し独特なので、
codesignコマンドを直に叩いて署名をつけています。


codesign

codesignコマンドでアップル公証用の署名をつける場合は、
次の2つのパラメータをつけて、codesignを実行します。

--options runtime
--timestamp


下のコードは、仮想コードです。

仮想コードですが、
"どのファイルに対して、どのパラメータでcodesignを実行するか"
というのが大事な情報なので、 きっとこの記事を読みに来た人にはこれでもわかるはず・・・
アプリ名とかIDは適当に読み替えてください。

APP='MYukkuriVoice'
identity='Developer ID Application: Foo Bar (99QXXXXXXX)'
PARENT_PLIST='build/darwin.app.plist'
CHILD_PLIST='build/darwin.app-child.plist'

/usr/bin/codesign --options runtime --timestamp -s "${identity}" -f --entitlements "${CHILD_PLIST}"
    `${APP}-darwin-x64/${APP}.app/Contents/Frameworks/Electron Framework.framework/Versions/A/Electron Framework`
    `${APP}-darwin-x64/${APP}.app/Contents/Frameworks/Electron Framework.framework/Versions/A/Libraries/libEGL.dylib`
    `${APP}-darwin-x64/${APP}.app/Contents/Frameworks/Electron Framework.framework/Versions/A/Libraries/libswiftshader_libEGL.dylib`
    `${APP}-darwin-x64/${APP}.app/Contents/Frameworks/Electron Framework.framework/Versions/A/Libraries/libGLESv2.dylib`
    `${APP}-darwin-x64/${APP}.app/Contents/Frameworks/Electron Framework.framework/Versions/A/Libraries/libswiftshader_libGLESv2.dylib`
    `${APP}-darwin-x64/${APP}.app/Contents/Frameworks/Electron Framework.framework/Versions/A/Libraries/libffmpeg.dylib`
    `${APP}-darwin-x64/${APP}.app/Contents/Frameworks/Electron Framework.framework/Versions/A/Resources/crashpad_handler`
    `${APP}-darwin-x64/${APP}.app/Contents/Frameworks/Electron Framework.framework`
    `${APP}-darwin-x64/${APP}.app/Contents/Frameworks/${APP} Helper (GPU).app/Contents/MacOS/${APP} Helper (GPU)`
    `${APP}-darwin-x64/${APP}.app/Contents/Frameworks/${APP} Helper (GPU).app`
    `${APP}-darwin-x64/${APP}.app/Contents/Frameworks/${APP} Helper (Plugin).app/Contents/MacOS/${APP} Helper (Plugin)`
    `${APP}-darwin-x64/${APP}.app/Contents/Frameworks/${APP} Helper (Plugin).app`
    `${APP}-darwin-x64/${APP}.app/Contents/Frameworks/${APP} Helper (Renderer).app/Contents/MacOS/${APP} Helper (Renderer)`
    `${APP}-darwin-x64/${APP}.app/Contents/Frameworks/${APP} Helper (Renderer).app`
    `${APP}-darwin-x64/${APP}.app/Contents/Frameworks/${APP} Helper.app/Contents/MacOS/${APP} Helper`
    `${APP}-darwin-x64/${APP}.app/Contents/Frameworks/${APP} Helper.app`

/usr/bin/codesign --deep --options runtime --timestamp -s "${identity}" -f --entitlements "${CHILD_PLIST}"
    `${APP}-darwin-x64/${APP}.app/Contents/Frameworks/Mantle.framework/Mantle`
    `${APP}-darwin-x64/${APP}.app/Contents/Frameworks/Mantle.framework/Versions/A`
    `${APP}-darwin-x64/${APP}.app/Contents/Frameworks/ReactiveCocoa.framework/ReactiveCocoa`
    `${APP}-darwin-x64/${APP}.app/Contents/Frameworks/ReactiveCocoa.framework/Versions/A`
    `${APP}-darwin-x64/${APP}.app/Contents/Frameworks/Squirrel.framework/Versions/A/Resources/ShipIt`
    `${APP}-darwin-x64/${APP}.app/Contents/Frameworks/Squirrel.framework/Squirrel`
    `${APP}-darwin-x64/${APP}.app/Contents/Frameworks/Squirrel.framework/Versions/A`

# アプリから読み込んでいるDynamicLibrary
# (このアプリ固有)
/usr/bin/codesign --options runtime --timestamp -s "${identity}" -f --entitlements "${CHILD_PLIST}"
    `${APP}-darwin-x64/${APP}.app/Contents/Resources/app.asar.unpacked/vendor/AqKanji2Koe.framework/Versions/A/AqKanji2Koe`
    `${APP}-darwin-x64/${APP}.app/Contents/Resources/app.asar.unpacked/vendor/AqKanji2Koe.framework`
    `${APP}-darwin-x64/${APP}.app/Contents/Resources/app.asar.unpacked/vendor/AqUsrDic.framework/Versions/A/AqUsrDic`
    `${APP}-darwin-x64/${APP}.app/Contents/Resources/app.asar.unpacked/vendor/AqUsrDic.framework`
    `${APP}-darwin-x64/${APP}.app/Contents/Resources/app.asar.unpacked/vendor/AquesTalk10.framework/Versions/A/AquesTalk`
    `${APP}-darwin-x64/${APP}.app/Contents/Resources/app.asar.unpacked/vendor/AquesTalk10.framework`
    `${APP}-darwin-x64/${APP}.app/Contents/Resources/app.asar.unpacked/vendor/AquesTalk2.framework/Versions/A/AquesTalk2`
    `${APP}-darwin-x64/${APP}.app/Contents/Resources/app.asar.unpacked/vendor/AquesTalk2.framework`

# アプリから実行している外部実行ファイル。とそこから読んでいるDynamicLibrary
# (このアプリ固有)
/usr/bin/codesign --options runtime --timestamp -s "${identity}" -f
    `${APP}-darwin-x64/${APP}.app/Contents/Resources/app.asar.unpacked/vendor/maquestalk1-ios`
    `${APP}-darwin-x64/${APP}.app/Contents/Resources/app.asar.unpacked/vendor/secret`
    `${APP}-darwin-x64/${APP}.app/Contents/Resources/app.asar.unpacked/vendor/maquestalk1`
    `${APP}-darwin-x64/${APP}.app/Contents/Resources/app.asar.unpacked/vendor/AquesTalk.framework/Versions/A/AquesTalk`
    `${APP}-darwin-x64/${APP}.app/Contents/Resources/app.asar.unpacked/vendor/AquesTalk.framework`

/usr/bin/codesign --options runtime --timestamp -s "${identity}" -f --entitlements "${CHILD_PLIST}"
    `${APP}-darwin-x64/${APP}.app/Contents/MacOS/${APP}`

/usr/bin/codesign --options runtime --timestamp -s "${identity}" -f --entitlements "${PARENT_PLIST}"
    `${APP}-darwin-x64/${APP}.app`


codesignコマンドで直接、Electronアプリを署名しようとすると、
対象のファイルがとても多くなりますね。
コマンドの実行順序は数カ所ほど、入れ替わると駄目な場所があります。


次の2つのバイナリは、
2020年1月まではcodesignで指定しなくてもアップル公証が通っていたのですが、
今月(2月)になったら、個別にcodesignしておかないとアップル公証が通らなくなってしまっていました。

Electron Framework.framework/Versions/A/Resources/crashpad_handler
Squirrel.framework/Versions/A/Resources/ShipIt


entitlements

codesignコマンドで指定するentitlementsは、 Electronの場合、
com.apple.security.cs.allow-unsigned-executable-memory が必須になります。


それから、このアプリの場合はDynamicLibraryを使っているので、 それとは別に、
com.apple.security.cs.disable-library-validation を追加しています。
アプリによっては、これら以外にも必要となる指定があるでしょう。

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
  <dict>
    <key>com.apple.security.cs.allow-unsigned-executable-memory</key>
    <true/>
    <key>com.apple.security.cs.disable-library-validation</key>
    <true/>
  </dict>
</plist>


古いOSで作った実行ファイル

古いOSでビルドした実行ファイルが含まれていると、アップル公証がエラーになります。 これを解消するには新しく実行ファイルを作り直す必要があり、回避方法は無さそうです。

# このように怒られる
The binary uses an SDK older than the 10.9 SDK.


アプリによっては、この条件のせいで、アップル公証を通したくても通せなくなってしまうこともありそうです。

実行ファイルでなく、古いOSでビルドしたライブラリは、アップル公証でエラーにはなりませんでした。
助かった(>_<)


notarize

codesignで署名したアプリをアップル公証(notarization)に通します。
調べれば分かりますが手順がとてもとてもとても面倒くさい。


electron-notarizeライブラリを使うと、さくっと公証を通して、 完結まで処理してくれるので、
Electronアプリのアップル公証は、基本、これを使うべきだと思いました。
https://github.com/electron/electron-notarize


次のコードはgulpで、notarizeを実行するコードです。
実行結果が返ってくるまでは、毎回毎回少し時間がかかります。
5分、10分くらい。

const gulp = require('gulp');
const notarize = require('electron-notarize').notarize;

gulp.task('_notarize', () => {
    const APP = 'MYukkuriVoice';
    const platform = 'darwin';
    const APP_PATH = `${APP}-${platform}-x64/${APP}.app`;
    const bundleId = 'jp.nanasi.foobar';
    const teamId = '99QXXXXXXX';

    const appleId = DEVELOPER_APPLE_ID;
    const env = process.env;
    const password = env.AC_PASSWORD;

    return notarize({
      appBundleId: bundleId,
      appPath: APP_PATH,
      appleId: appleId,
      appleIdPassword: password,
      ascProvider: teamId,
    });
});


verify notarization

アップル公証を通したら、
本当にアップル公証が通っているか、次のコマンドで確認します。

# アップル公証の実行履歴の表示
xcrun altool --notarization-history 0 -u 'hoge@example.jp' -p 'xxxx-xxxx-xxxx-xxxx'

# 指定のアプリがアップル公証を通っているか
xcrun stapler validate -v MYukkuriVoice-darwin-x64/MYukkuriVoice.app


もちろんこれで確認が取れてもアプリが動かないことがあります。
その場合は、単純にアプリのバグでなければ、

  • アップル公証を通した後にアプリが変更された
    (一度、アンチウィルスソフトにやられました)
  • entitlementsの指定が足りない

のどちらかが怪しそうです。


おわり

終わりでありますよ。


自分はまだmacOS Catalinaに移行していないので、
動作確認が少し面倒です。(´・ω・`)

続・Electron製アプリが Mac App Storeで non-public APIの利用で rejectされる問題。解決しそう

AppStoreのレビュー通ったって

前回
Electron製アプリが Mac App Storeへの申請で non-public APIの利用で rejectされる問題
https://taku-o.hatenablog.jp/entry/2019/11/03/103422


Electronがnon-publicなAPIを利用していたので、
Electron製のアプリがみんなrejectされている問題があったのだけれど、
最新コードでAppStoreのレビューが通ったそうで、解決のメドがたったみたい。
https://github.com/electron/electron/issues/20027


もうちょっとで修正が利用できるようになるのでしょう。
https://github.com/electron/electron/pull/20965


でも、Electron 5以降が対象なんだ

Electronのバージョン4以下は、サポート打ち切られています。
当然修正は用意されない!


Electron 4用のPull Requestも作られたけど、
一瞬でcloseされたよ!!
https://github.com/electron/electron/pull/21003


私が使ってたのは3.1.9だったので、 このままでは修正が用意されないのですね。
Electron 3、Electron 4使っている人は、
Electronのバージョン上げましょうね。


というわけでElectron 5までバージョン上げたよ

というわけで、ここ数日は、
アプリで使っている Electronのバージョンアップに励んでいました。


Electronを追っている人は知っていると思うけど、
Electronはバージョン上げて、すんなり動いたりしないのですよ。


自分の場合、3.1.9 → 5.0.12で、次のような問題があった。
(Electron 6への移行も検討してたので、Electron5用の対応と、Electron6用の対応が混在してるよ)


node-ffi が Node 12 でビルドできない

もっとも古いElectron 5でも、Node 12.0.0です。

# リリースノートから
Upgraded to Chromium 73.0.3683.119, Node.js 12.0.0, and V8 7.3.492.27.

node-ffiというネイティブライブラリを実行できるモジュールがあり、 今までこれを利用していたのですが、
このライブラリはNode 12ではビルドできない。
開発が止まっている。
https://github.com/node-ffi/node-ffi


自分はいろいろ検討した結果、node-ffi-napiに移行しました。
とても良い感じ。
https://github.com/node-ffi-napi/node-ffi-napi


menu item role

Electronのメニュー定義のrole定数が大文字小文字で変わっていたり。
こんな感じであちこち。
Typescriptでコンパイル時にエラーになってた。

# 抜粋
- {role: 'zoomin'},
- {role: 'zoomout'},
- {role: 'resetzoom'},
+ {role: 'zoomIn'},
+ {role: 'zoomOut'},
+ {role: 'resetZoom'}


Spectron

remote-debugging-portを指定しないと、
Spectronのテストコードが動作しなくなった。

# 抜粋
describe('dictWindow', function() {
  this.timeout(10000);

  before(function() {
    this.app = new Application({
      path: 'App-darwin-x64/App.app/Contents/MacOS/App',
      chromeDriverArgs: ['remote-debugging-port=9222'], ←←←←←← これ!
      env: {DEBUG: 1, NODE_ENV: 'test'},
    });
    return this.app.start();
  });


electron-packager

electron-packagerでアプリパッケージを作っているのですが、
Electron 6あたりで、Electronの作りが結構変わっているようですね。
古いバージョンのelectron-packagerを使っている人はアップデートが必要でしょう。
https://github.com/electron/electron-packager


一部ファンクションが非同期になってた

例えば、

dialog.showMessageBox

これが同期から非同期処理になってたり。


そういう変更は、リリースノートの
Breaking Changesの項に置いといて欲しい。


ばっちこい

準備完了だ。
はやくこいこいアップデート。

Electronアプリの起動時間短縮に挑む

アプリが起動するまでの時間が短いと気持ちいい

アプリの起動は最初の一回しか発生しないのに、
その後はアプリは起動しっぱなしなのに、
この最初の起動までの時間が短いと、アプリの使用感が非常に良くなるような気がする。 気がしない?


この記事はElectronアプリの起動速度を速くするために
いろいろやったことと、その結果の記録です。
環境はMacのElectronです。


なお計測はしていたが、記録はない。
各仕組みの導入の前後で結構コードが変わってしまって、
計測の仕方も変わってしまって、
あとバイナリは残っているから調査可能だけど、面倒くさいからであります。


初回起動時の記録は捨てとこう

ところでElectronのアプリって、
ビルド後初回の起動より、2回目・3回目の方が早く起動しますね。
計測して起動時パフォーマンスチューニングするなら、初回の記録は捨てときましょ。


やったことリスト

  • require文の書き換え・遅延ローディング
  • ファイルの統合・分割・minify
  • コールバック・Promise
  • 設定ファイルを同期・非同期で読み込む
  • 設定ファイルの読み込みのタイミングをずらす
  • Electronのバージョンを変更する
  • アプリ起動時の見た目をごまかす


require文の書き換え・遅延ローディング

require文を書き換えて、ライブラリを遅延ローディングするように変更する。
すごく効く。効いた。

var log = require('electron-log');
log.error('error found.');

↓例えば、こう書き換える

var _log, log = () => { _log = _log || require('electron-log'); return _log; };
log().error('error found.');


ファイルの分割・統合・minify

Electronアプリで使用しているファイルを、

  • Webpackなどで1つにまとめたり、
  • 逆に細かく分割してファイル1つあたりを軽量にしたり、
  • ファイルをminifyしてファイルのサイズを小さくしたり。

asarに固めたElectronアプリでは効かない

全然全然本当にまったく効果なかった。
まったく効果がないので、余計な手間がビルド時に発生する分、マイナスまである。

何度となく諦めきれずに試したが、そのたびにまったく効果がないと知ることとなった。
でも、私はきっとまた試すよ。


コールバック・Promise

コールバックとPromiseは、コールバックの方が早い。
世間の流れと逆方向でありますね。

でも、仕方ない。
アプリの起動時には、ほんの僅かでも速度が欲しいのであります。
効く

// Promise
hoge().then((data1) => {
  foo(data1).then(data2) => {
    // 何かやる
  });
});
// コールバック
hoge((data1) => {
  foo(data1, (data2) => {
    // 何かやる
  });
});


設定ファイルを同期・非同期で読み込む

設定ファイルを読み込む処理があるとして、

  • 非同期で設定ファイルを読み込むより、
  • 同期で設定ファイルを読み込む方がかなり早い


早いが、もちろんデメリットあり。
アプリの処理によっては、処理がブロックされて逆に遅くなるでしょう。

更に更に、
例えば、設定ファイルがJSONであるなら、
こんな感じにrequire文を使って読み込むと、
同期での設定ファイル読み込みより更に早かったのですけど、
これはセキュリティ的な問題があるでしょうな。

var dataJson = null;
try {
  // data.jsonの読み込み
  dataJson = require(`${app.getPath('userData')}/data.json`);
} catch (e) {
  dataJson = [];
}
// 読み込み終わったらキャッシュ消しておく
delete require.cache[`${app.getPath('userData')}/data.json`];


設定ファイルの読み込みのタイミングをずらす

私はAngularを使用しているのですけど、
Renderer Processで画面を描画中に設定ファイルを読み込もうとすると、
同期であろうと非同期であろうと、ものすごく遅くなる。

その描画中の範囲を外して設定ファイルを読み込むようにすると
めちゃくちゃ早くなる。
とても良く効く


自分の環境では、同じ処理が実行タイミングの違いで、これくらい効いた。

1秒 → 0.002秒


Electronのバージョンを変更する

少し効く
Electronのバージョンを3から4にする。
何度も計測した結果、ちょっぴり早くなっていると思う。
ちょっぴり。

でも、Electronのバージョンは、あまり選択可能な要素ではないですね。

私はまだElectron4で安定動作させられなかったので、
Electron3に戻しちゃいましたけど、
いろいろ問題解決したら、もちろんElectronのバージョンを上げておきたい。

もうElectronの5が出てる?気のせいでしょ。


アプリ起動時の見た目をごまかす

設定ファイルからデータを読み込んで、 読み込み終わった順にそのデータでビューを更新すると、
画面がちらちら更新されて、体感、アプリの起動が遅く感じる。

一括でパッと表示した方が早く感じる。操作できるまでの時間変わらなくても。

設定ファイルの読み込み順と、読み込みタイミングの考慮は要るけれど、
手間暇かけると、アプリの起動時間の体感は良くなる。


おわり

おわりだよー。

MacのElectronで"safeDialogs"の機能が動作するようになった

要約

動作しないと自分の中で評判だった、
Electronの無限アラート防止機能"safeDialogs"の問題が修正。
Electronバージョン5から動作するようになった。


BrowserWindowのsafeDialogsオプション

ElectronのBrowserWindowに、"safeDialogs"というオプションがあります。
このオプションは、例えば、
アラートダイアログが無限に繰り返し表示されるような処理が見つかった時、
そのループを停止してくれる機能です。

safeDialogs Boolean (optional)
- Whether to enable browser style consecutive dialog protection. Default is false.

https://electronjs.org/docs/api/browser-window


動作サンプル


こんな感じの無限アラートを、
Electronのrendererプロセスに仕込んで、

while (true) alert('!')


BrowserWindowのパラメータで、"safeDialogs"を指定しておくと、

function createWindow () {
  mainWindow = new BrowserWindow({
    width: 800,
    height: 600,
    webPreferences: {
      safeDialogs: true, // ←←←←←←←←←←←←
      safeDialogsMessage: 'stop dialog',
      nodeIntegration: true
    }
  })


無限アラートを検知した時に、
ループを停止するチェックボックスが表示されます。

ただし、Macでは長らく動作していなかった。

この機能、オプション導入直後から、
MacのElectronでは長らく動作していませんでした。
誰も試してなかったのかな。

無限アラートがあっても、スルーされてました。
https://github.com/electron/electron/issues/17543


これで安心

たとえ、兵庫県に住んでいてもね!!

AquesTalkの機能をJavaScriptから呼び出す機能をライブラリに切り出した

概要

Mac用の動画作成用ゆっくりボイスアプリ MYukkuriVoice で使用している AquesTalk呼び出し機能を、ライブラリに切り出した。
たぶんMacで、Electronで、AquesTalkな読み上げアプリを作るなら役に立つでしょう。
めちゃくちゃレアケースだな!!!

※ MYukkuriVoice
www.nicovideo.jp


でも、ごめん使うのは大変

  • Macでしか動かないのであります
  • お試しするのにも、たくさん準備がいります
    • AquesTalkライブラリが必要
  • これを使って、アプリを作るなら更に準備がいります
    • AquesTalkライブラリの開発ライセンス、使用ライセンスが必要
  • たいへん(^_^;)


使い方

ライブラリの使い方の説明は https://github.com/taku-o/aquestalk-mac に書いてあります。

けれど、このaquestalk-macライブラリを使うにあたって大変なのは、
ライブラリの使い方より、導入までだと思うので、この記事では、その話をする。
おそらく、ライブラリそのものではなく、このサンプルアプリを動かすまでの手順が、このライブラリで一番価値があるものです。


サンプルアプリの動かし方

1. Pythonのバージョンを確認する

Macのデフォルトのsystemバージョンが望ましい。
他のPython環境を入れていてそれを使っていると、npm installで失敗することが多い。

2. npm installで必要なライブラリをインストール

git clone https://github.com/taku-o/aquestalk-player-mac.git
cd aquestalk-player-mac
npm install

3. electron-rebuildでnative系のライブラリを作り直す

electron-rebuildしないと動かない。

./node_modules/.bin/electron-rebuild

4. vendorEvaディレクトリに評価版ライブラリを入れる

  • AquesTalk2 Mac SDK評価版
  • AqKanji2Koe Mac SDK
    • この2つを入手して、"vendorEva"ディレクトリに次のようにファイルを配置する
|-- vendorEva
    |-- AqKanji2Koe.framework
    |-- AquesTalk2Eva.framework
    |-- aq_dic_large/aq_user.dic
    |-- aq_dic_large/aqdic.bin
    `-- phont/aq_f1c.phont

5. サンプルアプリを動かす

これでElectronのサンプルアプリが起動すると思います。
サンプルアプリでは入力した文字列を、AquesTalkを使って音声として再生したり、wavファイルとして保存したりできます。
お疲れ様でした。

cd sample
npm install
npm run start


このライブラリはファイルアクセスが必要?

Q. このライブラリを使用するにはファイルアクセスが必要?
    A. 必要

Q. つまり、Webブラウザでは動作しない?
    A. 基本的には動作しない

Q. 音声をJavaScriptで再生するには、Web Audio APIか、HTML5 Audioクラスあたりが妥当?
    A. 他のプログラムに作成した音声データを渡さないなら妥当

Q. Web Audio APIは、Webブラウザで動作しますよね?
    A. ・・・そうですね

Q. このライブラリはどういう場面で使えるの?
    A. ・・・え、Electronとか・






おまけ

ニコニコのランキングシステム変わるし、
SoftalkとかCeVIOとか、
動画の音声作成ソフトのタグには"テキスピ"使おうぜ、って話が出ている。

www.nicovideo.jp

Electronのrenderer processで、consoleのログ出力をターミナルに吐かせる

さいしょに

Electronアプリの開発中はelectronコマンドで直接アプリを起動すること多いと思う。

electron .
  • この時、main processでconsole.logると、ターミナルにログが出力される。
  • でも、renderer processでconsole.logしたら、ブラウザ側にログが出力される。


見る場所が分散していると面倒ですね。
だから、一カ所にまとめちゃおう。
両方ともターミナルにログ出力して貰おう、という話。

consoleの差し替え

renderer process内で、consoleを差し替えれば良い。

console = require('electron').remote.require('console');

console.log('log from renderer process');

おわり

おわり。
本番アプリではconsoleのコードは取り除かれるだろうから、開発中の話。
global変数のconsoleを差し替えると、eslintの推奨設定だと怒られちゃうけどね!