Electronのproduction環境でasarにある画像などのリソースがnet::ERR_FILE_NOT_FOUNDで読み込めないのはBrowserRouterが原因でした
環境
- Electron 2
- Electron Builder 20
- React: 16
- webpack 4
- React Router 4
- Gentoo Linux
やりたいこと
url-loaderを排除してfile-loaderにしたい.
動機
とある画面を読み込むのがとても遅く, 速くしたかった.
同じ画像を大量に読み込むので, url-loaderを削除してfile-loaderにしてみたらとても速くなった.
パスを変更すると読み込めなくなる問題も
loader: 'file-loader',
options: {
publicPath: '/'
},
すれば解決しました.
問題
development環境ではちゃんと動きます.
しかし何故かproduction向けにビルドして実行ファイルを作ると読み込めません.
Failed to load resource: net::ERR_FILE_NOT_FOUND
になってしまいます.
asar l app.asar
するとちゃんと画像ファイルがバンドルされていることが分かります.
コンソール出力でパスも一致していることが確認できます.
同じパスに存在するJavaScriptファイルは読み込めているのになんで画像は読み込めないんだ?
結論
BrowserRouter
じゃなくてHashRouter
使えば解決です.
以下は作業ログなので纏まってないですし読まなくて良いです.
探索
- Frequent 'webpack-file-loader' Questions - Stack Overflow
- Issues · chentsulin/electron-react-boilerplate
- Issues · electron/electron
- Issues · webpack-contrib/file-loader
- Issues · webpack-contrib/url-loader
- Issues · webpack/webpack
などをものすごく読みましたがジャストで解決する問いと答えはありませんでした.
問題の再現を試みる
これは chentsulin/electron-react-boilerplate: Live editing development on desktop app を少し弄って画像が出力されないことを確認して, それを参考ソースにしてStack Overflowで質問するかと思って, electron-react-boilerplateを少し弄ってfile-loaderを使用するようにして, パッケージングして実行しました.
画像埋め込めてますね… 我々の設定がおかしいのでしょう.
試行
どこかのissueにpackage.jsonのfiles
を設定しろとか書いていた気がします.
electron-react-boilerplateの設定の違いはそこにある気がします.
さっそくelectron-builderのfilesの意味を調べてみましょう.
Application Contents · electron-builder
えっ英語多すぎてつら…
どうも読んだ所ファイルにアクセスできるかには影響しない気がするのですが. 追加するかどうかには影響する気がしますが, 実際にasarには追加されていてアクセスが出来ないので何か違う気がします.
適当にfilesを弄ってもビルドエラーが出ますね.
何もわからないのでとりあえずelectron-react-boilerplateのfilesをコピーしてみることにします.
ビルドエラーが出ます.
どうもfilesにプロパティを追加していくとそれがasarにバンドルされるらしいです. とりあえず全てバンドルしてみましょう.
全てバンドルしてみましたが表示されませんでした. まあ当然ですね. 元からバンドルされていることは確認していてそれでも表示されないのがこの問題なのですから.
ドキュメントにも無視するファイルがない場合は変更する必要ないと書いてありますし.
これが原因だと思った私の勘が間違っていたのでしょう.
node: {
__dirname: false,
__filename: false,
},
が悪いのかなと思って削除してみます.
削除してみたらindex.html
すら読み込めなくなりました.
この設定を共通設定からrendererの設定だけに移してみましょう. これもindex.htmlが読み込めなくなりますね.
file-loaderのソースコードを読んでみます.
んー__dirname
も__pathname
も使ってないですね.
file-loaderのname
オプションでファイル名が固定になったので,
importを使わずに直にパスを打ち込みまくってみます.
全部だめで読み込めない.
そりゃパス名が同じことは既に確認しているんですから当然ですね.
どうやっても出来ない… と思いたいんですが, electron-react-boilerplateは実際出来てるんですから謎なんですよね…
プロトコルの不一致が原因?
win.loadURL(
url.format({
pathname: path.resolve(__dirname, 'index.html'),
protocol: 'file:',
})
を単純な
win.loadURL(`file://${__dirname}/index.html`);
に書き換えましたがダメ. 関係がない.
アップデート処理に失敗して一度404が起きているからそこでロードが止まっている? 一度コメントアウトしてアップデート処理を消してみます. 関係なし.
file-loaderが関係しているのか切り出す必要がありそうです.
CopyWebpackPlugin
でimageをコピーして直パスでアクセスしてみます.
変化なし. まあパスはちゃんと問題なくて, electron-react-boilerplateはfile-loaderでアクセスできているのですから当然ですね.
webpack production url-loader limit path error · Issue #1156 · chentsulin/electron-react-boilerplate を参考にしてoutputにpublicPathを設定してみます.
これはdistに出力する環境でやっているのですからこちらはappに出力するのでappに設定しなければいけないのではないか. ダメっぽい.
electron-react-boilerplateはproduction環境では__dirname
を弄っていないようですね.
こちらもそれでindex.html
が表示されるようにしてみましょう.
元々設定していたのはDevtronが設定しないとバグるというのが原因だったのでproduction環境では問題ないはずです.
Not allowed to load local resource: file://index.html/ after webpacking main.js · Issue #5107 · electron/electron を参考にindex.htmlが読み込まれないのを書き換えていきます.
webPreferences: {
webSecurity: false,
},
してみるとなるほどエラーは消えます.
ウィンドウは何も描画せず何も起きなくなりました.
表示されないのはoutput設定を行っていたからのようですね.
webSecurity: false
にしても画像が読み込まれないのは変わらないけれど.
もう一度file-loaderを疑って, electron-react-boilerplateの真似をすれば良いのではないかと思い, electron-react-boilerplateの出力するファイルパスを真似たものを生で突っ込んでみます.
勿論どちらもnet::ERR_FILE_NOT_FOUND
です.
Importing Images in Electron React Boilerplate - daviseford
を見て絶対パス設定だとダメなのかな…
と思いimport
を相対パス設定に切り替えてみます.
勿論それで結果のパスが変わるはずもなくエラー.
electron-react-boilerplateが./dist/
じゃなくて/dist/
だと画像読み込まないことを確認して,
locationを確認.
Location {href: "file:///tmp/.mount_electrs7TXt2/app/resources/app.asar/app.html#/", ancestorOrigins: DOMStringList, origin: "file://", replace: ƒ, assign: ƒ, …}
私のアプリケーションではlocationはmount以下のhtmlではなく
Location {href: "file:///", ancestorOrigins: DOMStringList, origin: "file://", replace: ƒ, assign: ƒ, …}
になっていることを確認.
これを上に合わせればパスが合うのではないか.
どうやって合わせる?
loadURL
で__dirname
を使う方法は既に失敗したぞ?
false
にするのと合わせて試してはない.
__dirname: false
することでまた何も読み込まれなくなってしまいました.
mainとrendererの間で__dirname
の値が異なったりするのか?
と思ったので確認してみます.
webpackの設定を見るとmain側は__dirname: false
にしていてrendererはしていないように見えますね.
双方のrendererにconsole.log(__dirname);
を仕込んで確かめてみましょう.
どっちもrendererで__dirname
は/
でした.
そこに違いはないんですね…
entryが複数あるからいけないのか? 向こうに合わせて1つにしてappを指すようにしてみます. mainプロセスがwindowオブジェクトを参照するとかでエラーが出てきました. 参照しているはずが無いんですが… winstonを参照しているのが悪いのかなと思って削除してみました. 何も出てこなくなりました… まあ考えてみたらelectron-react-boilerplateではappディレクトリに元ソース入れてるんですから, 我々のwebpackではsrcをentryに指定しないとダメですね. HtmlWebpackPluginが複数ある以上entryは複数ないとダメなのでここは変えられないですね.
publicPath
の指定が関係する?
関係なかったです.
output.path
をjoin
ではなくresolve
で解決しているから正規化された?
関係なかったです.
わからん
locationを/
ではなく,
AppImageがmountされたディレクトリにする必要があることはわかってきましたが,
その方法がわかりません.
React Routerが原因っぽい
会社出て歩き始めてから気がついたんですが,
originがfile://
の状態で/
のルーティングに飛ばしたらそりゃfile:///
になりますよね.
HashRouter使えばパス移動発生しない
HashRouter使いましょう. どうせElectronなのでURLが汚くなるとか関係ないし, 戻る機能すら使っていません.
HashRouter使えばpublicPath
も設定不要.
自決しました
私がこれに気がつくのに12時間ぐらいかかりました. これだけをやっていたわけではないとはいえ… 生産性低すぎませんかね. つらい. 自決したい.
要約
AppImageは実行する時に
file:///tmp/.mount_electrs7TXt2/app/resources/app.asar/app.html
のようにasarのファイルを展開します.
React Routerで/
に飛ばすとoriginはfile://
なので当然file:///
に飛んで行きます.
すると相対パスで展開されたファイルは読み込めなくなります.
ElectronでReact Routerやめませんか
これはただの負け惜しみなんですけど, webサイト作る訳でもないのにURL作っても仕方がなくないですか. コンポーネント遷移は単純にReduxに状態を突っ込んでしまえば良いじゃないですか. 構造的にパラメータも入れられますし.