简单写了一个 Electron 使用 webview 嵌入外部页面的代码
// App.tsx
import React, { useEffect, useRef } from 'react'
const style = { width: '100%', height: '100vh' }
const App: React.FC = () => {
const webview = useRef<Electron.WebviewTag>(null)
const handleDomReady = (): void => {
const element = webview.current as Electron.WebviewTag
element.openDevTools()
}
useEffect(() => {
const element = webview.current
element?.addEventListener('dom-ready', handleDomReady)
return (): void => {
element?.removeEventListener('dom-ready', handleDomReady)
}
}, [])
return (
<webview
ref={webview}
preload="file://C:/Users/MissGwen/Desktop/electron-app/preload.cjs"
src="https://www.github.com"
style={style}
/>
)
}
export default App
preload: 预加载脚本的本地路径,必须为file协议 + 本地绝对路径Electron提供了 webview DOM 的类型为Electron.WebviewTag(可能有一部分小伙伴不知道,随口一提)
进程通信
Electron 渲染进程 👉 Webview 预加载脚本
// App.tsx
const App: React.FC = () => {
...
const webview = useRef<Electron.WebviewTag>(null)
const handleDomReady = (): void => {
const element = webview.current as Electron.WebviewTag
element.openDevTools()
// ✨✨✨
element.send('to-webview:preload', 'ping')
}
...
return (
<webview
ref={webview}
preload="file://C:/Users/MissGwen/Desktop/electron-app/preload.cjs"
/>
)
}
export default App
// preload.cjs
const { ipcRenderer } = require('electron')
ipcRenderer.on('to-webview:preload', (_, data) => {
console.log(data) // ping
})
Webview 预加载脚本 👉 Electron 渲染进程
// preload.cjs
const { ipcRenderer } = require('electron')
ipcRenderer.sendToHost('to-electron:render', 'ping')
// App.tsx
const App: React.FC = () => {
const webview = useRef<Electron.WebviewTag>(null)
// ✨✨✨
const handleIpcMessage = (event: Electron.IpcMessageEvent): void => {
console.log(event.args) // ['ping']
}
useEffect(() => {
const element = webview.current
// ✨✨✨
element?.addEventListener('ipc-message', handleIpcMessage)
return (): void => {
element?.removeEventListener('ipc-message', handleIpcMessage)
}
}, [])
return (
<webview
ref={webview}
preload="file://C:/Users/MissGwen/Desktop/electron-app/preload.cjs"
/>
)
}
export default App
接收的
event.args的值,实际为一个数组,这样就可以传递和接收多个值
Webview 渲染进程 👈👉 Webview 预加载脚本
Electron给webview提供了两种注入JavaScript代码的方式-
- 就是上面提到的 使用
preload脚本
- 就是上面提到的 使用
-
- 就是使用
executeJavaScript注入
- 就是使用
const webview = useRef<Electron.WebviewTag>(null)
webview.current?.executeJavaScript(`console.log(window)`)
这样注入的 js 就可以在嵌入页面的内部执行
executeJavaScript只能在 'dom-ready' 之后进行调用,注意先后顺序
但是也随之遇到了一个问题,我们使用 executeJavaScript 去访问 window 对象,和在 preload 文件中去访问 window 对象,得到的结果其实并不相同。这说明,虽然都是 window 对象,都可以去操作 DOM ,都可以在嵌入页面运行,但其实两者的环境是相互独立的!
- 通过
executeJavaScript注入的 js 代码,可以去访问嵌入页面本身在window上挂载的方法,而无法访问node api - 使用
preload文件注入的 js 代码,可以访问node api而无法访问嵌入页面本身在window上挂在的方法 - 这个概念相当于
electron文档介绍的 上下文隔离
如何通信
- 在
preload使用contextBridge向window中暴露注册处理函数,为了方便双向通信,我们将第二个参数设置为回调
// preload.cjs
const { contextBridge } = require('electron')
contextBridge.exposeInMainWorld('preload', {
// 定义自己的处理函数
doAThing(data, cb) {
console.log(data, 'preload') // ping
cb('pong')
}
})
executeJavaScript环境中,就有了访问preload的能力,我们使用element.executeJavaScript('window.preload.doAThing("ping")')调用定义的处理函数
// App.tsx
import React, { useEffect, useRef } from 'react'
const style = { width: '100%', height: '100vh' }
const App: React.FC = () => {
const webview = useRef<Electron.WebviewTag>(null)
const handleDomReady = (): void => {
const element = webview.current as Electron.WebviewTag
element.openDevTools()
// ✨✨✨
element.executeJavaScript(`
window.preload.doAThing('ping', (data) => {
console.log(data, 'executeJavaScript') // pong
})
`)
}
useEffect(() => {
const element = webview.current
element?.addEventListener('dom-ready', handleDomReady)
return (): void => {
element?.removeEventListener('dom-ready', handleDomReady)
}
}, [])
return (
<webview
ref={webview}
preload="file://C:/Users/MissGwen/Desktop/electron-app/preload.cjs"
src="https://www.github.com"
style={style}
/>
)
}
export default App
我们使用 webview.openDevTools() 打开嵌入页面的控制台,就可以成功看到输出了,虽然在一个控制台,但是是属于两个环境打印出来的日志