Electron 内嵌 IFrame 的数据通信
-
背景:项目A打包成网页,然后套壳在
Electron
(下面成为项目B)和其他平台,平台差异化的代码逻辑在各自平台实现。 -
环境:
Vue2
+electron-builder
-
遇到问题:使用 Electron 自带的
webview
,同样的代码,webview
无法加载到preload.js
,网上找了很久都没法实现,只能切换成iframe
,也不知道正常是否是用此逻辑来处理通信。
主要步骤
Electron
配置preload.js
,在preload.js
配置可供iframe
调用的方法。iframe
里按需调用,ipcMain
接受事件,并转发给ipcRenderer
ipcRenderer
收到消息,通过postMessage
发送给iframe
iframe
收到回调消息,处理数据
具体实现
- 配置
preload.js
,监听事件
// B/src/background.ts
const win = new BrowserWindow({
//...其他配置
webPreferences: {
webviewTag: true,
nodeIntegration: true,
contextIsolation: false,
enableRemoteModule: true,
nodeIntegrationInSubFrames: true,
// 对应的文件在 public/repload.js
preload: path.join(__static, "preload.js"),
}
});
// 加载 Electron 项目里的 app.vue,后续会用到这个 win
const containerHtml = process.env.WEBPACK_DEV_SERVER_URL as string;
if (containerHtml) {
await win.loadURL(containerHtml);
} else {
createProtocol("app");
await win.loadURL("app://./index.html");
}
// 这里监听来自 iframe 传回的消息,并转发给 win.webContents,即 Electron 项目里的 app.vue
ipcMain.on("event_from_iframe", (evt, data) => {
win.webContents.send("event_from_iframe", data);
});
// B/public/preload.js
const { ipcRenderer } = require("electron");
window.isElectron = true;
window.$bridge = {
ipcRenderer,
};
iframe
里通过调用NativeElectron.send
按需传递信息,ipcMain
接受事件,并转发给ipcRenderer
<!-- A/src/index.vue -->
<!-- 项目A 里的代码,假设最后打包的地址是 http://a.com , 后续会使用到 -->
<script lang="ts">
import { onMounted, onUnmounted } from "@vue/composition-api";
import {NativeElectron} from "@/utils/native/native.electron";
onMounted(() => {
NativeElectron.addMessageListener({
xxx: (res) => {
console.log("xxx response: ", res);;
}
})
})
onUnmounted(() => {
NativeElectron.removeMessageListener();
})
handleXXX() {
NativeElectron.send("xxx", {data: "data"});
}
</script>
// A/src/utils/native/native.electron.ts
interface NativeElectronCallback {
[key: string]: Function;
}
// 和 Electron 外壳约定好的数据方式,其他不做响应
interface NativeElectronData {
from: "electron" | any;
name: string; // 回调方法名
payload: any;
}
export class NativeElectron {
static callbacks: NativeElectronCallback = {}; // 注册回调函数
static isElectron(): boolean {
const is = window && window.isElectron;
console.log("isElectron : ", is);
return is;
}
static addMessageListener (callbacks: NativeElectronCallback) {
if (!NativeElectron.isElectron()) {
return;
}
window.addEventListener("message", NativeElectron.messageHandler)
NativeElectron.callbacks = callbacks;
}
static removeMessageListener() {
if (!NativeElectron.isElectron()) {
return;
}
window.removeEventListener("message", NativeElectron.messageHandler)
}
// 接收 Electron 里通过的 postMessage
static messageHandler = (e) => {
const data = e.data as NativeElectronData;
if (typeof data !== "object") {
return;
}
const { from = "", name = "", payload } = data;
if (from !== "electron") {
return;
}
const callback = NativeElectron.callbacks[name];
callback && callback(payload);
}
// iframe 里传消息给 ipcMain
static send(type: string, payload: any) {
if (!NativeElectron.isElectron()) {
return;
}
// @ts-ignore
window.$bridge.ipcRenderer.send("event_from_iframe", {
type,
payload
});
}
}
ipcRenderer
收到消息,通过postMessage
发送给iframe
<!-- B/src/app.vue -->
<template>
<div>这是 Electron 项目,下面使用 iframe 标签包裹的 A 项目</div>
<iframe src="http://a.com"></iframe>
</template>
<script>
mounted() {
const webView = document.querySelector("iframe");
// 监听从 ipcMain 传来的消息,并转发给 iframe
ipcRenderer.on("event_from_iframe", (e, data) => {
webView.contentWindow.postMessage(
{ from: "electron", name: "xxx", payload: data },
"http://a.com" // postMessage 的第二个参数是 iframe 里加载的源地址
);
});
}
</script>
具体流程
文件加载流程:
B/src/background.ts
里创建 win,加载B/public/preload.js
和B/src/app.vue
B/src/app.vue
里通过iframe
加载打包之后的A/src/index.vue
A/src/index.vue
在window
对象上,能拿到B/public/preload.js
上挂载的函数。
数据流转过程(具体看文章开头的流程图)
A/src/index.vue
调用handleHelloWorld
把消息{data: "data"}
传给B/src/background.ts
里的ipcMain.on("event-from-iframe")
ipcMain.on("event-from-iframe")
把数据转发给B/src/app.vue
里的ipcRenderer.on("event_from_iframe")
ipcRenderer.on("event_from_iframe")
通过webView.contentWindow.postMessage
传给iframe
iframe
通过window.addEventListener("message")
的回调拿到数据。