electron初探問題總結

wonyun發表於2019-06-10

使用electron時間不是很久,隨著使用的深入慢慢的也遇到一些問題,下面總結一下遇到的問題與大家分享,避免趟坑。

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文件還未形成之前。

參考文獻