0%

[Electron.js] window.require is not a function 的原因及解決方法

前言

使用前端框架 (React/Vue/Angular 等) 搭配 Electron 開發桌面應用程式時,最容易在呼叫 Node 套件時遇到問題,此時若查詢相關文獻,會發現大多數人提供一種方法,也就是將 require 改為 window.require 來引用套件。卻伴隨 TypeError: window.require is not a function 錯誤訊息出現,且通常不會提到此問題的解法。因此這裡記錄下個人對於該問題的想法與解決方法,供未來的自己或同好參考。

環境

  • node 14.16.1
  • electron 13.1.8
  • react 17.0.2

問題描述

前端框架預設是以瀏覽器 (Browser) 來執行的,我們可以用其作為桌面應用程式的 UI 開發工具,因此若在前端框架中使用 Browser 預設不支援的套件,就會出現如下的錯誤訊息,告訴我們某些被使用的套件不被 Browser 所支援

1
Error: Workbook.fromFileAsync is not supported in the browser

其對應的原始碼為

1
2
3
// App.js
const XlsxPopulate = require('xlsx-populate');
XlsxPopulate.fromFileAsync(...).then(...);

將上方錯誤訊息拿去餵 Google,大部分的解決方案是將 require 修改為 window.require,若真的照著做,很大的機率會噴出另一種錯誤訊息

1
TypeError: window.require is not a function

根本原因

Electron 將應用程式的運作切割為兩種 Process,分別為 Main Process 與 Render Process,兩個 Process 的環境是隔離開來的,且前端框架都是運作在 Render Process 中。若想在前端中引用 Node 的套件 (該套件預設只允許運作在 Main Process),必須先在 preload.js 明確定義要讓 Render Process 能使用甚麼樣的套件,這樣才能於前端框架中使用。

我們可以將 preload.js 想像是 Main Process 釋放出來的 API,供 Render Process 呼叫使用。通常會以 window 物件來作為介面,例如想在前端使用 remote 模組,就需要先在 preload.js 引入該模組並存放在 window.remote

1
2
// preload.js
window.remote = require('electron').remote; // 開放 remote 接口給 Render Process

若前端需要使用 remote 模組內的模組,例如 dialog,在前端就可以這樣指定

1
2
// App.js
const { dialog } = window.remote;

但這樣還是不夠的,Electron 對於安全性設定非常嚴格,我們需要在 main.js 中建立 BrowserWindow 時,加上 enableRemoteModulecontextIsolation 兩個安全性選項

1
2
3
4
5
6
7
8
9
10
11
//main.js
const mainWindow = new BrowserWindow({
width: 1024,
height: 768,
icon: path.join(__dirname, 'icon.png'),
webPreferences: {
preload: path.join(__dirname, 'preload.js'),
enableRemoteModule: true, // 允許在 Render Process 使用 Remote Module
contextIsolation: false, // 讓在 preload.js 的定義可以傳遞到 Render Process (React)
}
});

解決方法

理解根本原因後,可以推理出 window.require 應該是已經在 preload.js 中先定義好的介面,但我們 preload.js 中沒有相關定義,所以才會噴出錯誤訊息 (TypeError: window.require is not a function)。解決方法就是只需要在 preload.js 加上下方程式即可

1
2
// preload.js
window.require = require;

這裡我們沒有使用到 remote 模組,因此只需確認 contextIsolation 設為 false,重新運行 Electron 就能在 Render Process 中使用 window.require 了!

結語

個人非常不建議直接將 Main Process 的 require 開放給 Render Process 使用,這是非常危險的事情。取而代之的是,先明確知道會使用到甚麼套件,再針對該套件來做介面開放,例如

1
2
3
4
5
6
7
// preload.js
window.process_browser = undefined;
window.remote = require('electron').remote;
window.xlsxPopulate = require('xlsx-populate');
window.node_rdp = require('node-rdp');
window.open = require('open');
window.clipboard = require('electron').clipboard;
weirenxue/ap-displayer
很高興能在這裡幫助到您,歡迎登入 Liker 為我鼓掌 5 次,或者成為我的讚賞公民,鼓勵我繼續創造優質文章。
以最優質的內容回應您的鼓勵