[[Electron]]バージョンアップの際に、[[webpack]]などで発生するエラーのハマリポイントと対処方法について。
## Before/Afterバージョン
| パッケージ | 現バージョン | 対応後バージョンアップ |
| -------------------- | ------------ | ---------------------- |
| [[Node.js]] | 12 | 16.11.36 |
| [[Electron]] | 7 | 19.0.1 |
| [[electron-builder]] | 21 | 23.0.3 |
| [[electron-store]] | 5 | 8.0.1 |
| [[TypeScript]] | 3.7 | 4.7.2 |
| [[Nuxt TypeScript]] | 0.5 | 2.15.8 |
| [[Nuxt.js]] | 2.11 | 2.15.8 |
| [[node-sqlite3]] | 4 | 5.0.8 |
本当は[[Node.js]]のバージョンだけをまず上げたかったが、他がv16に対応していなかったため全てをアップデートすることになった。
## npm run devに関するビルドエラー
具体的には以下のコマンド。
```console
tsc main.ts && electron main.js
```
`main.ts`では開発用のlocalhostサーバを立ち上げている。
### [[webpack]]関連のパッケージで大量の型エラーが出る
一部抜粋。
```
node_modules/@types/file-loader/index.d.ts:7:10 - error TS2305: Module '"webpack"' has no exported member 'loader'.
7 import { loader } from 'webpack';
~~~~~~
node_modules/@types/terser-webpack-plugin/index.d.ts:7:10 - error TS2305: Module '"webpack"' has no exported member 'Plugin'.
7 import { Plugin } from 'webpack';
~~~~~~
node_modules/@types/terser-webpack-plugin/index.d.ts:40:40 - error TS2724: 'exports' has no exported member named 'compilation'. Did you mean
40 chunkFilter?: ((chunk: webpack.compilation.Chunk) => boolean) | undefined;
~~~~~~~~~~~~
node_modules/@types/webpack-bundle-analyzer/index.d.ts:7:10 - error TS2305: Module '"webpack"' has no exported member 'Plugin'.
7 import { Plugin, Compiler, Stats } from 'webpack';
~~~~~~~
node_modules/@types/webpack-bundle-analyzer/index.d.ts:72:31 - error TS2702: 'Stats' only refers to a type, but is being used as a namespace here.
72 statsOptions?: null | Stats.ToJsonOptionsObject;
```
`npm ls webpack`を確認すると、v4とv5の[[webpack]]が混ざっている。v4の[[webpack]]を明示的にインストールすることでv4に統一され解決。
```console
npm i -D webpack@4
```
### [[Electron]]のremote周りでエラー
```
Uncaught TypeError: Cannot read properties of undefined (reading 'members')
at setObjectPrototype (remote.js:201:1)
at metaToValue (remote.js:303:1)
at push../node_modules/@electron/remote/dist/src/renderer/remote.js.exports.require (remote.js:354:1)
```
[[@electron-remote]]の[Migration](https://github.com/electron/remote#migrating-from-remote)が抜けていた。
`main.ts`で初期化する。
```ts
import { initialize } from '@electron/remote/main';
initialize();
```
## npm run devに関するランタイムエラー
### 起動直後に404エラー
```
GET http://localhost:55125/_nuxt/runtime.js net::ERR_ABORTED 404 (Not Found)
GET http://localhost:55125/_nuxt/vendors/app.js net::ERR_ABORTED 404 (Not Found)
GET http://localhost:55125/_nuxt/commons/app.js net::ERR_ABORTED 404 (Not Found)
GET http://localhost:55125/_nuxt/app.js net::ERR_ABORTED 404 (Not Found)
```
[[nuxt.config]]の`build.extend`にて、`config.output.publicPath = './_nuxt/'`と書いていたところを削除したら動いた。
### requireが定義されていないエラー
```
Uncaught ReferenceError: require is not defined
```
`BrowserWindow`に`contextIsolation: true`を指定する。
```ts
const mainWindow = new Browser({
webPreferences: {
nodeIntegration: true,
contextIsolation: false,
},
})
```
[Electron v12でcontextIsolationのデフォルトがtrueになった](https://www.electronjs.org/blog/electron-12-0#highlight-features)影響。明示的に`false`にする。
> [!caution]
> セキュリティ的に`contextIsolation`の推奨設定は`true`。詳しくは [コンテキストの分離 \| Electron](https://www.electronjs.org/ja/docs/latest/tutorial/context-isolation) を参照。
### WebContentsでremoteが無効になる
```
Uncaught Error: @electron/remote is disabled for this WebContents. Call require("@electron/remote/main").enable(webContents) to enable it.
```
[[@electron-remote]]のREADMEに答えがある。
> **Note:** In `electron >= 14.0.0` the remote module is disabled by default for any `WebContents` instance and is only enabled for specified `WebContents` after explicitly calling `require("@electron/remote/main").enable(webContents)`.
[[Electron]] v14からは明示的な`WebContents`の許可設定が必要。`loadURL`の前に`enable`を呼び出しておく。
```
import { initialize, enable } from '@electron/remote/main';
async function setUpWindow(win: BrowserWindow): Promise<void> {
enable(win.webContents);
return win.loadURL(_NUXT_URL_);
}
```
### [[SQLite]]が読み込めない
```
Uncaught Error: Loading non-context-aware native module in renderer: '\\?\C:\Users\tadashi-aikawa\git\github\repo\node_modules\sqlite3\lib\binding\electron-v19.0-win32-x64\node_sqlite3.node'. See https://github.com/electron/electron/issues/18397.
```
[[node-sqlite3]]をv4からv5にバージョンアップしたら消えた。
```console
npm i sqlite3@latest
```
## npm run buildに関するランタイムエラー
### index.htmlからjsファイルが404になる
具体的なコマンド。
```bash
# .nuxtを作成
nuxt build
# distを作成
nuxt generate
```
`dist`配下の`index.html`から`*.js`が読み込めない。`index.html`から`*.js`が絶対パスになっているのが原因。
```html
<script src="/_nuxt/9fcf46f.js">
```
相対パスになるよう[[nuxt.config]]の`build`に設定を追加する。
```javascript
if (!isDev) {
config.output.publicPath = '_nuxt/';
}
```
これで相対パスになるので`script`タグのパスは読み込めるようになる。
```diff
- <script src="/_nuxt/9fcf46f.js">
+ <script src="_nuxt/9fcf46f.js">
```
一方、preloadの方は絶対パスのままになっている。
```html
<link rel="preload" href="/_nuxt/9fcf46f.js" as="script">
```
preloadが先に読み込まれてエラーになるため、`script`タグの`*.js`が正しく読み込まれても画面が表示されない。
```
WebContents #1 called ipcRenderer.sendSync() with 'electron-store-get-data' channel without listeners.
(node:21196) UnhandledPromiseRejectionWarning: Error: ERR_ABORTED (-3) loading 'file:///C:/Users/tadashi-aikawa/git/repo/dist/index.html#/'
at rejectAndCleanup (node:electron/js2c/browser_init:161:7647)
at EventEmitter.navigationListener (node:electron/js2c/browser_init:161:7953)
at EventEmitter.emit (node:events:526:28)
(Use `electron --trace-warnings ...` to show where the warning was created)
(node:21196) UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). To terminate the node process on unhandled promise rejection, use the CLI flag `--unhandled-rejections=strict` (see https://nodejs.org/api/cli.html#cli_unhandled_rejections_mode). (rejection id: 1)
WebContents #1 called ipcRenderer.sendSync() with 'electron-store-get-data' channel without listeners.
```
[[nuxt.config]]の`router.base: './'`を指定する。これでpreloadのhrefが相対パスになる。
```diff
- <link rel="preload" href="/_nuxt/9fcf46f.js" as="script">
+ <link rel="preload" href="./_nuxt/9fcf46f.js" as="script">
```
## npm run packageに関するランタイムエラー
`npm run build`のあとに`electron-builder`コマンドを実行する。
### local resourceが読み込めないエラー
起動すると以下のエラーになる。
```
Not allowed to load local resource: file:///C:/Users/tadashi-aikawa/AppData/Local/Programs/repo/resources/app.asar/dist/index.html
```
`webPreferences.webSecurity: false`を指定すると解消される。
```ts
win = new BrowserWindow({
webPreferences: {
// ...
webSecurity: false,
},
// ...
});
```
> [!caution]
> [webSecurityを無効にすることはリスクがあり](https://www.electronjs.org/ja/docs/latest/tutorial/security#6-websecurity-%E3%82%92%E7%84%A1%E5%8A%B9%E3%81%AB%E3%81%97%E3%81%AA%E3%81%84)、[[Electron]]の推奨に反する。[[同一オリジンポリシー]]が無効になっても問題ないことを承知の上、実施すること。
### `projectName`が取得できない
インストーラーからインストールした場合のみ、起動後以下のエラーになる。
```
Uncaught Error: Project name could not be inferred. Please specify the `projectName` option.
```
[[electron-store]]の[[conf]]に関するエラー。
<div class="link-card-v2">
<div class="link-card-v2-site">
<img class="link-card-v2-site-icon" src="https://github.githubassets.com/favicons/favicon.svg" />
<span class="link-card-v2-site-name">GitHub</span>
</div>
<div class="link-card-v2-title">
Remove automatic `projectName` / `projectVersion` inference · Issue #150 · sindresorhus/conf
</div>
<div class="link-card-v2-content">
conf/source/index.ts Lines 29 to 36 in 8329e55 let parentDir = ''; try { // Prevent caching of this module so mo ...
</div>
<img class="link-card-v2-image" src="https://opengraph.githubassets.com/bd7f5cdb598a77cd425d5bdfe389b1b008700b157ce63c2143643f6282c8c5bf/sindresorhus/conf/issues/150" />
<a href="https://github.com/sindresorhus/conf/issues/150"></a>
</div>
[[トランスパイル]]された[[JavaScript]]のコードを確認。
```ts
class Conf {
constructor(partialOptions = {}) {
var _a;
_Conf_validator.set(this, void 0);
_Conf_encryptionKey.set(this, void 0);
_Conf_options.set(this, void 0);
_Conf_defaultValues.set(this, {});
this._deserialize = value => JSON.parse(value);
this._serialize = value => JSON.stringify(value, undefined, '\t');
const options = {
configName: 'config',
fileExtension: 'json',
projectSuffix: 'nodejs',
clearInvalidConfig: false,
accessPropertiesByDotNotation: true,
configFileMode: 0o666,
...partialOptions
};
const getPackageData = onetime(() => {
const packagePath = pkgUp.sync({ cwd: parentDir });
// Can't use `require` because of Webpack being annoying:
// [https://github.com/webpack/webpack/issues/196](https://github.com/webpack/webpack/issues/196)
const packageData = packagePath && JSON.parse(fs.readFileSync(packagePath, 'utf8'));
return packageData !== null && packageData !== void 0 ? packageData : {};
});
if (!options.cwd) {
if (!options.projectName) {
options.projectName = getPackageData().name;
}
if (!options.projectName) {
// ココ
throw new Error('Project name could not be inferred. Please specify the `projectName` option.');
}
```
```ts
constructor(options) {
let defaultCwd;
let appVersion;
// If we are in the renderer process, we communicate with the main process
// to get the required data for the module otherwise, we pull from the main process.
if (ipcRenderer) {
const appData = ipcRenderer.sendSync('electron-store-get-data');
if (!appData) {
throw new Error('Electron Store: You need to call `.initRenderer()` from the main process.');
}
({defaultCwd, appVersion} = appData);
} else if (ipcMain && app) {
({defaultCwd, appVersion} = initDataListener());
}
options = {
name: 'config',
...options
};
if (!options.projectVersion) {
options.projectVersion = appVersion;
}
if (options.cwd) {
options.cwd = path.isAbsolute(options.cwd) ? options.cwd : path.join(defaultCwd, options.cwd);
} else {
options.cwd = defaultCwd;
}
options.configName = options.name;
delete options.name;
super(options);
}
```
[[electron-store]]のインスタンス生成時に`projectName`を指定すればいけそうだが、型定義からそれは除外されている。今回はここだけ通せばいいので無理やり流し込む。
```ts
const configStore = new EStore({ projectName: 'repo' } as any) as ElectronStore<Config>;
```
### リロードすると真っ白になる
間接的には以下のようなエラーも出る。
```
WebContents #1 called ipcRenderer.sendSync() with 'electron-store-get-data' channel without listeners.
```
[[electron-store]]のREADMEに以下の記述がある。
> You can use this module directly in both the main and renderer process. For use in the renderer process only, you need to call `Store.initRenderer()` in the main process, or create a new Store instance (`new Store()`) in the main process.
mainプロセスで`initRenderer`を呼び出す必要がある。
```ts
import * as ElectronStore from 'electron-store';
ElectronStore.initRenderer();
```
ただ、[[Private class features]]が使われているためアクセスできないエラーとなる。
```
node_modules/conf/dist/source/index.d.ts:5:5 - error TS18028: Private identifiers are only available when targeting ECMAScript 2015 and higher.
5 #private;
~~~~~~~~
Found 1 error in node_modules/conf/dist/source/index.d.ts:5
```
[[ESモジュール (JavaScript)|ESモジュール]]ではなく[[CommonJS]]形式だと動作した。
```ts
const ElectronStore = require('electron-store');
ElectronStore.initRenderer();
```