electron-bridge
github链接 求star
Motivition
假如想一套代码同时能跑在web环境和electron环境中,就须要在代码中先推断环境,再离别写对应的逻辑。每次写到electron环境下的逻辑,又要辨别衬着历程和主历程,由于有些事只能衬着历程做,有些事只能主历程做。所以,我愿望能将这些笼统出来,某个要领,只能在electron环境下被挪用,而且不须要体贴在什么历程下,web只需推断环境,调差别的要领就行,不须要体贴和electron的交互。
假如,我须要疾速的开启另一个electron的项目,我愿望我web里的代码能随意马虎的猎取到electron的才能,而不是重新最先编写,这个时刻,我愿望有一层对electron才能的封装。
团队内有些成员对web很熟悉,然则对electron不是很相识,假如到场项目,就须要去进修electron的学问,这个时刻,假如能有一个库列出了一切electron能做的事,你只须要挪用,无需体贴它是怎样完成的,能很大水平进步开辟效力。
Goals
给web注入恰当的环境变量,让web晓得本身的环境
给web注入一个对象,包含一切electron能做的事(包含主历程、衬着历程)
How to do
在load web页面的时刻,有个webPreferences设置,我们在这里预加载一个js文件,就是electron-bridge.js
这个文件具有node的才能,而且它是属于衬着历程的,所以它能做衬着历程里的事, 也能跟主历程通信。
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.js
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
}
});