使用electron時間不是很久,隨着使用的深入慢慢的也遇到一些問題,下面總結一下遇到的問題與大家分享,避免趟坑。
主要問題匯總如下:
- webview與渲染進程renderer間通信
- BrowserWindow加載第三方網站,集成node模塊時導致第三方模塊不可用
- 預加載腳本preload的問題
- 渲染線程renderer中引入Electron報錯
- 渲染進程使用require報Uncaught ReferenceError: require is not defined錯
1、webview與渲染進程renderer間通信
與渲染進程之間的通信不同,渲染進程與webview之間的通信,在webview層通過調用sendToHost
方法來向渲染進程通信;而在渲染進程測通過webview提供的ipc-message
事件來向webview通信。具體如下面代碼所示:
// renderer環境,獲取webview,然后注冊事件
webview.addEventListener('ipc-message', (event) => {
// 通過event.channel的值來判斷webview發送的事件名
if (event.channel === 'webview_event_name') {
console.log(event.args[0]) // 123
}
})
webview.send('renderer_event_name', '456')
// webview環境
const {ipcRenderer} = require('electron')
ipcRenderer.on('renderer_event_name', (e, message) => {
console.log(message); // 456
ipcRenderer.sendToHost('webview_event_name', '123')
})
2、BrowserWindow加載第三方網站,集成node模塊時導致第三方模塊不可用
具體來說,就是在使用new BrowserWindow
時,配置其webPreferences選項的nodeIntegration
值為true,即:
new BrowserWindow({
webPreferences: {
nodeIntegration: true // 注入node模塊
}
})
這樣,第三方網站按照CMD格式加載前端模塊時如下所示,
!function(a, b) {
"object" == typeof module && "object" == typeof module.exports ? module.exports = a.document ? b(a, !0) : function(a) {
if (!a.document)
throw new Error("jQuery requires a window with a document");
return b(a)
}
: b(a)
}(this, fn);
可以看出,若electron窗口配置集成node模塊的話,前端模塊占用了node關鍵字module
,導致前端頁面的模塊成了node的模塊,以jQuery為例,依賴jQuery的模塊會出現如下錯誤信息:
Uncaught ReferenceError: $ is not defined
知道問題所在,解決問題有兩種思路:
-
不啟用node功能,即設置
nodeIntegration: false
。這種方式比較粗暴,不能更好的拓展electron應用 -
在頁面加載模塊依賴之前改變
module
,之后恢復module指向node模塊。
針對第二種方法,github上有人提出解決方案。我們在不可控的加載第三方網站時,利用BrowserWindow的前置注入腳本preload
來提供修改module指向,可參考代碼如下:
// renderer
var win = new BrowserWindow({
...
webPreferences: {
nodeIntegration: true,
preload: process.cwd() + '/app/resource/preload.js'
}
});
// preload.js
// electron的BrowserWindow設置nodeIntegration為true時,導致頁面可以訪問node的module影響頁面正常模塊引入功能,如jQuery
document.addEventListener('DOMNodeInserted', function(event){
// 頁面內容加載之前需要引入的一些代碼
if (document.head && !document.getElementById('module')) {
var script = document.createElement('script');
script.setAttribute('id', 'module');
script.innerHTML = "if (typeof module === 'object') {window.module = module; module = undefined;}"
document.head.appendChild(script);
}
});
document.addEventListener('DOMContentLoaded', function(event) {
// 頁面內容加載之后需要引入的一些操作
var script = document.createElement('script');
script.innerHTML = `if (window.module) module = window.module;`
document.body.appendChild(script);
})
3、預加載腳本preload的問題
BrowserWindow
提供的preload
的配置是為了在頁面第一次加載文檔之前預先加載js腳本文件,其需要注意3個問題:
- preload配置值不能直接為腳本字符串,否則不會執行
- preload配置的腳本文件路徑,只能為本地文件,其協議必須是file:、asar:二者之一
- preload腳本仍然有能力去訪問所有的 Node APIs, 即使配置nodeIntegration: false。但是當這個腳本執行執行完成之后,通過Node 注入的全局對象(global objects)將會被刪除。
preload是在腳本加載之前執行,其有三個階段如下,具體可以參考#217 Electron 深度實踐總結:
// ---------------------------------------------------
// 第一階段:在頁面加載之前需要執行的相關代碼
// ...
// -------------------------------------------------------
document.addEventListener('DOMNodeInserted', (event) => {
// 第二階段:頁面內容加載之前需要引入的一些代碼
// ...
})
// -------------------------------------------------------
document.addEventListener('DOMContentLoaded', (event) => {
// 第三階段:頁面內容加載之后需要引入的一些操作
// ...
})
// -------------------------------------------------------
可以看出:
preload環境
可以使用Node API,又能訪問DOM、BOM的特殊環境,即使dom文檔還未形成之前。
4、渲染線程renderer中引入Electron報錯
在使用webpack打包編譯的renderer進程中,使用如下語句引入electron:
const { shell, BrowserWindow } = require('electron').remote;
打包完成后報fs.existsSync is not a function
的錯誤,詳細信息如下圖所示:
即,electron/index.js文件中引入fs.existsSync語句造成的。
6 | function getElectronPath () {
7 | if (fs.existsSync(pathFile)) {
8 | var executablePath = fs.readFileSync(pathFile, 'utf-8')
主要原因還是因為webpack默認產出目標是web平台的js,其混淆了nodejs的標准模塊系統,導致引入nodejs的模塊時出現問題。對此github有對應的解決辦法:
- Error while importing electron in react | import { ipcRenderer } from 'electron'
- Requiring electron outside of main.js causes a TypeError
即通過使用window.require
代替require
來引入electron,因為前者不會被webpack編譯,在渲染進程require
關鍵字就是表示node模塊的系統;
其實,更佳的解決方案是通過webpack自身來幫我們解決,即修改webpack提供的target產出目標為electron
,即:
- 修改electron主線程webpack的target配置項為
electron-main
- 修改electron渲染線程的webpack的target配置項為
electron-renderer
這樣就可以更好的解決上面的問題。
5、渲染進程使用require報Uncaught ReferenceError: require is not defined錯
在將webpack打包輸出目標為electron對應環境后,在主線程中使用BrowserWindow加載renderer線程頁面:
var win = new BrowserWindow({
width: 600,
height: 800
})
win.loadURL(url)
因為渲染進程使用require引入node的模塊,這時會報錯Uncaught ReferenceError: require is not defined
。原因應該是加載渲染進程的窗口沒有集成node環境。
奇怪的是在electron3.x
中並沒有任何配置就集成了node環境,而升級到electron5.x
就不行了,查詢資料發現:
electron5.x的node集成環境默認是關閉的,這之前的版本是默認開啟的。
所以,為了保持前后兼容,建議顯示配置集成node環境,即nodeIntegration
為true。
var win = new BrowserWindow({
width: 600,
height: 800,
webPreferences: {
nodeIntegration: true
}
})