electron-builderがnode_modulesのディレクトリをapp.asarにパッケージングしない原因はapp/package.jsonに依存を書いていないからでした
問題解決を行おうと思ったのですが徐々に行き詰まってきてしまったので, 問題点と調べた内容を整理するためにメモを取ります.
問題
Electronアプリケーションをelectron-builderでパッケージングすると, Semantic UI LESS のアイコンが表示されません.
原因
ビルドとパッケージの手順は, webpackでsrcからappにデータをビルド, appの内容をdistにパッケージするようになっています.
ビルドしたときはきちんと
app/node_modules/semantic-ui-less/themes/default/assets/fonts
にアイコンフォントはコピーされるのですが,
electron-builderでパッケージングすると,
node_modules
ディレクトリはapp.asarにパッケージングされずに除外されてしまいます.
副産物としてビルド組み込みを改善できました
Semantic UI LESSをアプリケーションに組み込むにあたって,
postinstallのたびにnode_modules
の内部を書き換えるようになっていました.
しかし本来バージョンが同じな場合,
パッケージ管理システムの外にあるnode_modules
の内容は常に同じになっているのが望ましいので,
あまり好ましい方法とは思っていませんでした.
しかし他の方法で組み込む方法がわからなかったので異論は挟みませんでした.
しかしこの問題を追いかけていく過程で常に書き換えなくてもビルドに組み込むことが出来るとわかったので, そのように書き換えました.
theme.configを上書きしないと自分のテーマが作れない問題
Using semantic-ui in webpack project - Stack Overflow を参考にしました.
webpack.configに以下のようにresolve.alias
を設定すれば良いです.
resolve: {
alias: {
'../../theme.config': path.join(
__dirname,
'src',
'theme.config'
),
},
},
imageとfontのパスがズレてしまう問題
Erroneous Icon Font URL · Issue #4174 · Semantic-Org/Semantic-UI を参考にしました.
要するに変数@imagePath
と@fontPath
の内容を上書きすれば良いので,
site/site.variables
に以下のように書けば良いです.
@imagePath : '../../themes/default/assets/images';
@fontPath : '../../default/assets/fonts';
なんでデフォルトのテーマを使うために変数を上書き的に上書きしないといけないのか, 読み込みパスの初期位置が違うのか全くわかりません.
こういうことがあるのでSemantic UIはあまり信用できない…
Selenium(Spectron)によるwebフォントを含めた404エラーの検知自動化
直す前にまずテストを自動化しないと, 直ったかを調べるために一々手動で確認しないといけないのでストレスですし, 直してもリグレッションバグが発生する可能性も高いです.
しかし「コンソールにエラーログが出ているか」を調べる方法が1メソッドで用意されていなかったので, 自分で書く必要がありました.
404エラーの検知などのワードで検索しましたが,
- imgタグを全部見てheightをチェックする
- titleに404を含むかチェックする
といったものしか出なくて, 私の要望を満たすものではありませんでした.
読み込みに失敗しているのはアイコンフォントであって, タイトルにもエラーを示す文章は無いからです.
Webdriverにはログを取るlogメソッドがあったので, これを使えばログをJSONで取得できます. WebdriverIO - log
jestに素直にcallbackでデータをチェックする述語(hspecで言うshouldSatisfy
)がなかったので,
extendで述語を書く必要がありました.
// ログに致命的なエラーデータを含むことのテスト
expect.extend({
toHaveSevereLog(received) {
if (received.value.some(v => v.level === 'SEVERE')) {
return {
message: () =>
`${this.utils.printReceived(received)}が致命的なエラーを含みます`,
pass: true,
};
}
return {
message: () =>
`${this.utils.printReceived(received)}が致命的なエラーを含みません`,
pass: false,
};
},
});
これを以下のように呼び出せばテスト作成完了です.
expect(await client.log('browser')).not.toHaveSevereLog();
electron-builderのpackage.jsonに書ける設定を1つずつ見ていきます
filesオプションは一見ピッタシに見えますが,
これはデフォルトで何も無視することは無いのですよね.
semantic-ui-lessはdevDependencies
ではなくdependencies
に存在しますし.
一応試しに"files": "**/*"
と書いてみましたが変化なしです.
本当にこれ設定が反映されているのか?
と思ったので"files": "!**/*"
と書いてみました.
そうしたらパッケージに失敗したので効いてはいるのでしょう.
"files": "node_modules"
と書いてみたらpackage.json
しかasarに含まれませんでした.
LESSとかがdevDependenciesだからなのでは?
全部dependencies
に移してみましたがダメでした.
extraFilesに書いてみては?
これapp以下のリレーションじゃないからダメだと思いますがやってみます. extraFilesだとasarにパッケージングされないのでダメですね. extraResourcesもパッケージしないからダメでしょう.
node_modules
にインストールしない形式には出来ないの?
これはかなり難しいです.
何故ならカスタムしたlessファイル達はみんな
@import "~semantic-ui-less/definitions/globals/reset";
のようにチルダ~
を使ってimportしているからです.
node_modulesではない別ディレクトリにインストールする方法に切り替えると,
これを全て修正するだけではなく,
開発版とパッケージ版でコードを切り替える必要があるからです.
issueを検索すると未解決問題だと思えてきました
ドキュメントの情報が足りなかったのでissueをnode_modules
というワードで掘ってみることにしました.
すると以下のissueが出てきました.
node_modules
がコピーされないという私の状況と似た問題です.
これに7月12日に解決方法はないのでafterPack
hookでどうにかすると書かれています…
とりあえずafterPackをどう書けば良いのかわからないので,
呼び出してみてcontextをconsole.log
で出力してみました.
lazy getterばかりで何もわからない. Nodeのデバッガーを呼び出してみましょう. hookでdebuggerを呼び出す方法がわかりません. 内部でrequireしているようでnodeコマンドを実行させることが出来ないです.
まあデバッガが使えないからconsole.log
するにしてもちょっと頭使えば楽になります.
Object.entries
を使えば一気にconsole.log
出来ます.
Object.entries(context.packager.info._configuration).forEach(([key, value]) =>
console.log(key, ":", value)
);
一気にlazy getterをforceする簡単な方法は無いものでしょうか.
asarにディレクトリをねじ込めば良いのでは?
afterPackのcontextの中身はさっぱりわかりませんが,
afterPackということは既にパッケージングは終わっているはずで,
そこでapp.asar
にファイルを追加してしまえば解決するのではと思いつきました.
最悪に強引な手段なのでやりたくないのですが…
asar読み込み専用でした.
というか読み込み専用という時点で, どうcontextをこねくり回してもasarにディレクトリを追加するのは出来無いことがわかってきました.
ソースコードを読むしかない
electron-builderのソースコードをnode_modules
で検索して全件読みました.
すると electron-builder/packager.ts at 6f8e4ec6c65d3a951f774276b5943c0e66704fb4 · electron-userland/electron-builder が気になりました.
ここのソースコードは
private get productionDeps(): Lazy<Array<Dependency>> {
let result = this._productionDeps
if (result == null) {
// https://github.com/electron-userland/electron-builder/issues/2551
result = new Lazy(async () => {
if (this.config.beforeBuild == null || (await exists(path.join(this.appDir, "node_modules")))) {
return await getProductionDependencies(this.appDir)
}
else {
return []
}
})
this._productionDeps = result
}
return result
}
となっていてプロダクションの依存関係をapp
ディレクトリで見ています.
そして我々はelectron-builderの推奨する,
rootとappの両方にpackage.json
を置くアーキテクチャを採用しています.
Two package.json Structure - electron-builder
よってapp/package.json
のdependencies
にsemantic-ui-less
を追加してやれば解決です.
解決に時間がかかった原因
私はwebpackを使ってほとんどのファイルをバンドルしていました.
よってelectron-builderのプロダクション限定での,
node_modules
のパッケージ挿入に左右されることがありませんでした.
よってpackage.json
のプロダクション判断基準を把握せずに運用していたので,
コピーされない原因がなかなかわかりませんでした.
もしwebpackを使わずに,
全てasarにJSファイルなどもバンドルして生でrequire
していれば,
必然的にelectron-builder install-app-deps
を実行することになっていたので,
同期されていて気が付かなかったことでしょう.
他のnode_modules
のコピーが必要ない環境を作ってしまったのがあだになってしまいましたね.
本当に疲れました… もしかしたら最初からソースコードを見ていれば2時間で解決する話だったのかもしれないと思うと, 徒労感が酷いです.