實現electron-bridge

electron-bridge

github連接 求starjavascript

Motivition

  • 若是想一套代碼同時能跑在web環境和electron環境中,就須要在代碼中先判斷環境,再分別寫對應的邏輯。每次寫到electron環境下的邏輯,又要區分渲染進程和主進程,由於有些事只能渲染進程作,有些事只能主進程作。因此,我但願能將這些抽象出來,某個方法,只能在electron環境下被調用,而且不須要關心在什麼進程下,web只要判斷環境,調不一樣的方法就行,不須要關心和electron的交互。java

  • 若是,我須要快速的開啓另外一個electron的項目,我但願我web裏的代碼能輕易的獲取到electron的能力,而不是從新開始編寫,這個時候,我但願有一層對electron能力的封裝。node

  • 團隊內有些成員對web很熟悉,可是對electron不是很瞭解,若是加入項目,就須要去學習electron的知識,這個時候,若是能有一個庫列出了全部electron能作的事,你只須要調用,無需關心它是怎麼實現的,能很大程度提升開發效率。git

Goals

  1. 給web注入適當的環境變量,讓web知道本身的環境github

  2. 給web注入一個對象,包含全部electron能作的事(包括主進程、渲染進程)web

How to do

在load web頁面的時候,有個webPreferences配置,咱們在這裏預加載一個js文件,就是electron-bridge.jspromise

這個文件擁有node的能力,而且它是屬於渲染進程的,因此它能作渲染進程裏的事, 也能跟主進程通信。app

st=>start: start
op0=>operation: index.js去調用bridge.js暴露出來的方法, ElectronBridge.setFullScreen()
op1=>operation: bridge.js經過ipcRender告訴ipacMain作什麼,並把回調暫存起來
op2=>operation:  主進程作完告訴bridge.js作完了,發送數據
op4=>operation:  bridge.js帶上收到的數據,執行暫存的回調
op3=>operation:  bridge.js直接作完,觸發回調
cond=>condition: bridge.js判斷是否是主進程作的事?
e=>end: end

st->op0->cond
cond(yes)->op1->op2->op4->e
cond(no)->op3->e

Let's do it

給web注入適當的環境變量

加載bridge.jselectron

win = new BrowserWindow({
  width: 800,
  height: 600,
  show: false,
  webPreferences: {
    preload: path.join(__dirname, '../bridge/bridge.js'),
    plugins: true
  }
});

當咱們啓動electron的時候,主進程開始通知這個渲染進程,給渲染進程注入主進程的環境變量,再有渲染進程掛載到window對象上,這樣web就能獲取本身的環境信息函數

//bridge.js

const {ipcRenderer} = require('electron');

//監聽主進程,設置環境變量
ipcRenderer.on('set-env', (event, msg) => {
  for (const key in msg) {
    window[key] = msg[key];
  }
});
//main.js
const {BrowserWindow, ipcMain} = require('electron');

const win = new BrowserWindow({...});

//獲取建立好的window對象發送消息
win.webContents.on('did-finish-load', function() {
  win.webContents.send('set-env', { //設置web環境變量
    __ELECTRON__: true,
    __DEV__: true,
    __PRO__: false,
    __SERVER__: false,
    windowLoaded: true
  });
});

經過bridge.js 來調用主進程的方法

咱們經過ipcRender給主進程發送一系列消息,包括作什麼事情(eventName), 根據哪些參數(params),對外根據不一樣的事件暴露不一樣的方法,接受參數,和回調函數。

  • 先將回調函數放在 eventsMap上暫存起來,由於ipcRender不能發送函數,全部的信息會被序列化後再發送給主進程,因此,咱們先生成一個時間戳,讓 eventsMap[時間戳] = cb 並把時間戳一同發送過去,等一下子,主進程通知渲染進程調用哪一個時間戳函數

  • 經過'resist-event'頻道, 發送參數,包括 eventName、params、timeStamp

//bridge.js
const {ipcRenderer} = require('electron');

const eventsMap = {};

//調用原生事件
function registEvent(eventName, params, cb) {
  //容許只傳兩個數據
  if (!cb) {
    cb = params;
    params = {};
  }

  //若是win還未ready
  if (!windowLoaded) {
    cb(new Error('window not ready'));
    return;
  }

  const stamp = String(new Date().getTime());
  const opts = Object.assign({eventName}, params, {stamp});
  eventsMap[stamp] = cb; //註冊惟一函數
  ipcRenderer.send('regist-event', opts); //發送事件
}

//進入全屏
function setFullScreen(cb) {
  registEvent(SET_FULL_SCREEN, cb);
}

window.ElectronBridge = {
  setFullScreen
};

主進程監聽‘resist-event’頻道,作對應的事。咱們會將全部主進程能作的事,放在eventsList對象下,當接受到渲染進程的通知,去eventsList找有沒有對應的事能作,有,作完經過promise,或者經過回調函數,去在‘fire-event’頻道通知,渲染進程,事情已經作完,並把數據傳回去,包括 stamp(以前渲染進程傳過來的,如今傳回去,告訴渲染進程執行哪一個回調函數) 、 payload(返回數據) 、err (錯誤信息)

//main.js
const {ipcMain} = require('electron');

//監聽對原生的調用
ipcMain.on('regist-event', (event, arg) => {
  const nativeEvent = eventsList[arg.eventName];
  if (nativeEvent) {
    const result =  nativeEvent(app, win, arg.params);
    if (isPromise(result)) {
      result.then(res => {
        event.sender.send('fire-event', {
          stamp: arg.stamp,
          payload: res
        });
      }).catch(err => {
        event.sender.send('fire-event', {
          stamp: arg.stamp,
          err
        });
      });
    } else {
      event.sender.send('fire-event', {
        stamp: arg.stamp,
        payload: result
      });
    }
  } else {
    event.sender.send('fire-event', {
      stamp: arg.stamp,
      err: new Error('event not support')
    });
  }
});

渲染進程監聽‘fire-event’執行對應時間戳回調函數,並把主進程傳過來的數據傳給回調函數。觸發完成後,刪掉該回調函數。

//bridge.js

//觸發事件回調
ipcRenderer.on('fire-event', (event, arg) => {
  const cb = eventsMap[arg.stamp];
  if (cb) {
    if (arg.err) {
      cb(arg.err, arg.payload);
    } else {
      cb(false, arg.payload);
    }
    delete eventsMap[arg.stamp];
  }
});

若是是渲染進程能作的事,就不須要再和主進程通信,能夠直接完成觸發回調

//bridge.js
const {webFrame} = require('electron');
//設置縮放比,只能在渲染進程中實現
function setZoomFactor(params, cb) {
  webFrame.setZoomFactor(params);
  cb && cb();
}

window.ElectronBridge = {
  setZoomFactor
};

最終web中的js代碼去調用bridge.js暴露出來的方法

// ../web/index.js

$btn1.addEventListener('click', function() {
  if (__ELECTRON__ && ElectronBridge) { //electron 環境
    ElectronBridge.setFullScreen((err) => {
      if (err) return;
      console.log('done');
    });
  } else { //web 環境
    alert('不能設置全屏')
    //do something else
  }
});
相關文章
相關標籤/搜索