[[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(); ```