contextBridge.exposeInMainWorld 和 Electron 应用程序中带有 Typescript 的 IPC:无法读取未定义的 属性 'send'
contextBridge.exposeInMainWorld and IPC with Typescript in Electron app: Cannot read property 'send' of undefined
我定义了 contextBridge ( https://www.electronjs.org/docs/all#contextbridge )
在 preload.js 中如下:
const {
contextBridge,
ipcRenderer
} = require("electron")
contextBridge.exposeInMainWorld(
"api", {
send: (channel, data) => {
ipcRenderer.invoke(channel, data).catch(e => console.log(e))
},
receive: (channel, func) => {
console.log("preload-receive called. args: ");
ipcRenderer.on(channel, (event, ...args) => func(...args));
},
// https://www.electronjs.org/docs/all#ipcrenderersendtowebcontentsid-channel-args
electronIpcSendTo: (window_id: string, channel: string, ...arg: any) => {
ipcRenderer.sendTo(window_id, channel, arg);
},
// https://github.com/frederiksen/angular-electron-boilerplate/blob/master/src/preload
/preload.ts
electronIpcSend: (channel: string, ...arg: any) => {
ipcRenderer.send(channel, arg);
},
electronIpcSendSync: (channel: string, ...arg: any) => {
return ipcRenderer.sendSync(channel, arg);
},
electronIpcOn: (channel: string, listener: (event: any, ...arg: any) => void) => {
ipcRenderer.on(channel, listener);
},
electronIpcOnce: (channel: string, listener: (event: any, ...arg: any) => void) => {
ipcRenderer.once(channel, listener);
},
electronIpcRemoveListener: (channel: string, listener: (event: any, ...arg: any) =>
void) => {
ipcRenderer.removeListener(channel, listener);
},
electronIpcRemoveAllListeners: (channel: string) => {
ipcRenderer.removeAllListeners(channel);
}
}
)
我定义了一个global.ts :
export {}
declare global {
interface Window {
"api": {
send: (channel: string, ...arg: any) => void;
receive: (channel: string, func: (event: any, ...arg: any) => void) => void;
// https://github.com/frederiksen/angular-electron-boilerplate/blob/master/src/preload
/preload.ts
// https://www.electronjs.org/docs/all#ipcrenderersendtowebcontentsid-channel-args
electronIpcSendTo: (window_id: string, channel: string, ...arg: any) => void;
electronIpcSend: (channel: string, ...arg: any) => void;
electronIpcOn: (channel: string, listener: (event: any, ...arg: any) => void) => void;
electronIpcSendSync: (channel: string, ...arg: any) => void;
electronIpcOnce: (channel: string, listener: (event: any, ...arg: any) => void) =>
void;
electronIpcRemoveListener: (channel: string, listener: (event: any, ...arg: any) =>
void) => void;
electronIpcRemoveAllListeners: (channel: string) => void;
}
}
}
在渲染器进程中 App.tsx 我调用 window.api.send :
window.api.send('open-type-A-window', '');
打字稿编译看起来不错:
yarn run dev
yarn run v1.22.5
$ yarn run tsc && rimraf dist && cross-env NODE_ENV=development webpack --watch --progress
--color
$ tsc
95% emitting emit(node:18180) [DEP_WEBPACK_COMPILATION_ASSETS] DeprecationWarning:
Compilation.assets will be frozen in future, all modifications are deprecated.
BREAKING CHANGE: No more changes should happen to Compilation.assets after sealing the
Compilation.
Do changes to assets earlier, e. g. in Compilation.hooks.processAssets.
Make sure to select an appropriate stage from Compilation.PROCESS_ASSETS_STAGE_*.
(Use `node --trace-deprecation ...` to show where the warning was created)
asset main.bundle.js 32.6 KiB [emitted] (name: main) 1 related asset
asset package.json 632 bytes [emitted] [from: package.json] [copied]
cacheable modules 26.2 KiB
modules by path ./node_modules/electron-squirrel-startup/ 18.7 KiB
modules by path ./node_modules/electron-squirrel-startup/node_modules/debug/src/*.js 15
KiB 4 modules
./node_modules/electron-squirrel-startup/index.js 1 KiB [built] [code generated]
./node_modules/electron-squirrel-startup/node_modules/ms/index.js 2.7 KiB [built] [code
generated]
./src/main/main.ts 6.82 KiB [built] [code generated]
./node_modules/file-url/index.js 684 bytes [built] [code generated]
external "path" 42 bytes [built] [code generated]
external "url" 42 bytes [built] [code generated]
external "electron" 42 bytes [built] [code generated]
external "child_process" 42 bytes [built] [code generated]
external "tty" 42 bytes [built] [code generated]
external "util" 42 bytes [built] [code generated]
external "fs" 42 bytes [built] [code generated]
external "net" 42 bytes [built] [code generated]
webpack 5.21.2 compiled successfully in 4313 ms
asset renderer.bundle.js 1000 KiB [emitted] (name: main) 1 related asset
asset index.html 196 bytes [emitted]
runtime modules 937 bytes 4 modules
modules by path ./node_modules/ 990 KiB
modules by path ./node_modules/scheduler/ 31.8 KiB 4 modules
modules by path ./node_modules/react/ 70.6 KiB 2 modules
modules by path ./node_modules/react-dom/ 875 KiB 2 modules
modules by path ./node_modules/css-loader/dist/runtime/*.js 3.78 KiB 2 modules
./node_modules/style-loader/dist/runtime/injectStylesIntoStyleTag.js 6.67 KiB [built]
[code generated]
./node_modules/object-assign/index.js 2.06 KiB [built] [code generated]
modules by path ./src/ 5 KiB
modules by path ./src/app/styles/*.less 3.16 KiB
./src/app/styles/index.less 385 bytes [built] [code generated]
./node_modules/css-loader/dist/cjs.js!./node_modules/less-loader/dist/cjs.js!./src/app
/styles/index.less 2.78 KiB [built] [code generated]
./src/renderer/renderer.tsx 373 bytes [built] [code generated]
./src/app/components/App.tsx 1.48 KiB [built] [code generated]
webpack 5.21.2 compiled successfully in 4039 ms
但是我得到了Cannot read property 'send' of undefined
如果我在 App.tsx 中设置:
const sendProxy = window.api.send;
我得到同样的错误,window 没有呈现:
我在使用 Typescript 和 Electron IPC 时做错了什么?
期待您的帮助
您是否需要 main.ts
中的预加载文件?
webPreferences: {
nodeIntegration: false,
contextIsolation: true,
preload: path.resolve(path.join(__dirname, "preload.js"))
},
你必须把它放在主 window 上。
根据您的 webpack 配置,可能只有一个入口点包,您需要为 preload.js
文件配置一个额外的 webpack 输出。
这里有一个示例答案:
下面是我基于 https://www.electronforge.io 的设置,它还为公开的 api 添加了类型。希望它有所帮助,即使不是一个有针对性的答案。
在 package.json
(使用 @electron-forge package.json setup、webpack + typescript 模板)中,在 entryPoints
下,确保你有:
"preload": {
"js": "./src/preload.ts"
}
在创建 BrowserWindow 的 src/index.ts
中,使用神奇的 webpack 常量引用捆绑的预加载脚本(也许您的预加载脚本没有被捆绑?):
const mainWindow = new BrowserWindow({
webPreferences: {
preload: MAIN_WINDOW_PRELOAD_WEBPACK_ENTRY
}
});
src/preload.ts
的内容:
import { contextBridge } from "electron";
import api from './api'
contextBridge.exposeInMainWorld("api", api);
src/api/index.ts
只是导出 api 的所有特征。示例:
import * as myFeature from "./my-feature";
// api exports functions that make up the frontend api, ie that in turn either do IPC calls to main for db communication or use allowed nodejs features like file i/o.
// Example `my-feature.ts`:
// export const fetchX = async (): Promise<X[]> => { ... }
export default {
...myFeature
}
Typescript 2.9+ 可以通过添加全局声明来识别您的 api 函数,例如 api.fetchX
,例如src/index.d.ts
():
declare const api: typeof import("./api").default;
...您需要参考 tsconfig.json
:
{
...
"files": [
"src/index.d.ts"
]
}
所有这些都完成了,您应该可以在不导入任何内容的情况下从 renderer-side 调用带有输入支持(IDE 的 ymmv)的 api.fetchX
。例子 App.tsx
:
import * as React from 'react'
// do not import api here, it should be globally available
export const App = () => {
useEffect(() => {
(async () => {
const project = await api.fetchX();
...
})();
}, []);
return <h1>My App</h1>
}
我定义了 contextBridge ( https://www.electronjs.org/docs/all#contextbridge ) 在 preload.js 中如下:
const {
contextBridge,
ipcRenderer
} = require("electron")
contextBridge.exposeInMainWorld(
"api", {
send: (channel, data) => {
ipcRenderer.invoke(channel, data).catch(e => console.log(e))
},
receive: (channel, func) => {
console.log("preload-receive called. args: ");
ipcRenderer.on(channel, (event, ...args) => func(...args));
},
// https://www.electronjs.org/docs/all#ipcrenderersendtowebcontentsid-channel-args
electronIpcSendTo: (window_id: string, channel: string, ...arg: any) => {
ipcRenderer.sendTo(window_id, channel, arg);
},
// https://github.com/frederiksen/angular-electron-boilerplate/blob/master/src/preload
/preload.ts
electronIpcSend: (channel: string, ...arg: any) => {
ipcRenderer.send(channel, arg);
},
electronIpcSendSync: (channel: string, ...arg: any) => {
return ipcRenderer.sendSync(channel, arg);
},
electronIpcOn: (channel: string, listener: (event: any, ...arg: any) => void) => {
ipcRenderer.on(channel, listener);
},
electronIpcOnce: (channel: string, listener: (event: any, ...arg: any) => void) => {
ipcRenderer.once(channel, listener);
},
electronIpcRemoveListener: (channel: string, listener: (event: any, ...arg: any) =>
void) => {
ipcRenderer.removeListener(channel, listener);
},
electronIpcRemoveAllListeners: (channel: string) => {
ipcRenderer.removeAllListeners(channel);
}
}
)
我定义了一个global.ts :
export {}
declare global {
interface Window {
"api": {
send: (channel: string, ...arg: any) => void;
receive: (channel: string, func: (event: any, ...arg: any) => void) => void;
// https://github.com/frederiksen/angular-electron-boilerplate/blob/master/src/preload
/preload.ts
// https://www.electronjs.org/docs/all#ipcrenderersendtowebcontentsid-channel-args
electronIpcSendTo: (window_id: string, channel: string, ...arg: any) => void;
electronIpcSend: (channel: string, ...arg: any) => void;
electronIpcOn: (channel: string, listener: (event: any, ...arg: any) => void) => void;
electronIpcSendSync: (channel: string, ...arg: any) => void;
electronIpcOnce: (channel: string, listener: (event: any, ...arg: any) => void) =>
void;
electronIpcRemoveListener: (channel: string, listener: (event: any, ...arg: any) =>
void) => void;
electronIpcRemoveAllListeners: (channel: string) => void;
}
}
}
在渲染器进程中 App.tsx 我调用 window.api.send :
window.api.send('open-type-A-window', '');
打字稿编译看起来不错:
yarn run dev
yarn run v1.22.5
$ yarn run tsc && rimraf dist && cross-env NODE_ENV=development webpack --watch --progress
--color
$ tsc
95% emitting emit(node:18180) [DEP_WEBPACK_COMPILATION_ASSETS] DeprecationWarning:
Compilation.assets will be frozen in future, all modifications are deprecated.
BREAKING CHANGE: No more changes should happen to Compilation.assets after sealing the
Compilation.
Do changes to assets earlier, e. g. in Compilation.hooks.processAssets.
Make sure to select an appropriate stage from Compilation.PROCESS_ASSETS_STAGE_*.
(Use `node --trace-deprecation ...` to show where the warning was created)
asset main.bundle.js 32.6 KiB [emitted] (name: main) 1 related asset
asset package.json 632 bytes [emitted] [from: package.json] [copied]
cacheable modules 26.2 KiB
modules by path ./node_modules/electron-squirrel-startup/ 18.7 KiB
modules by path ./node_modules/electron-squirrel-startup/node_modules/debug/src/*.js 15
KiB 4 modules
./node_modules/electron-squirrel-startup/index.js 1 KiB [built] [code generated]
./node_modules/electron-squirrel-startup/node_modules/ms/index.js 2.7 KiB [built] [code
generated]
./src/main/main.ts 6.82 KiB [built] [code generated]
./node_modules/file-url/index.js 684 bytes [built] [code generated]
external "path" 42 bytes [built] [code generated]
external "url" 42 bytes [built] [code generated]
external "electron" 42 bytes [built] [code generated]
external "child_process" 42 bytes [built] [code generated]
external "tty" 42 bytes [built] [code generated]
external "util" 42 bytes [built] [code generated]
external "fs" 42 bytes [built] [code generated]
external "net" 42 bytes [built] [code generated]
webpack 5.21.2 compiled successfully in 4313 ms
asset renderer.bundle.js 1000 KiB [emitted] (name: main) 1 related asset
asset index.html 196 bytes [emitted]
runtime modules 937 bytes 4 modules
modules by path ./node_modules/ 990 KiB
modules by path ./node_modules/scheduler/ 31.8 KiB 4 modules
modules by path ./node_modules/react/ 70.6 KiB 2 modules
modules by path ./node_modules/react-dom/ 875 KiB 2 modules
modules by path ./node_modules/css-loader/dist/runtime/*.js 3.78 KiB 2 modules
./node_modules/style-loader/dist/runtime/injectStylesIntoStyleTag.js 6.67 KiB [built]
[code generated]
./node_modules/object-assign/index.js 2.06 KiB [built] [code generated]
modules by path ./src/ 5 KiB
modules by path ./src/app/styles/*.less 3.16 KiB
./src/app/styles/index.less 385 bytes [built] [code generated]
./node_modules/css-loader/dist/cjs.js!./node_modules/less-loader/dist/cjs.js!./src/app
/styles/index.less 2.78 KiB [built] [code generated]
./src/renderer/renderer.tsx 373 bytes [built] [code generated]
./src/app/components/App.tsx 1.48 KiB [built] [code generated]
webpack 5.21.2 compiled successfully in 4039 ms
但是我得到了Cannot read property 'send' of undefined
如果我在 App.tsx 中设置:
const sendProxy = window.api.send;
我得到同样的错误,window 没有呈现:
我在使用 Typescript 和 Electron IPC 时做错了什么? 期待您的帮助
您是否需要 main.ts
中的预加载文件?
webPreferences: {
nodeIntegration: false,
contextIsolation: true,
preload: path.resolve(path.join(__dirname, "preload.js"))
},
你必须把它放在主 window 上。
根据您的 webpack 配置,可能只有一个入口点包,您需要为 preload.js
文件配置一个额外的 webpack 输出。
这里有一个示例答案:
下面是我基于 https://www.electronforge.io 的设置,它还为公开的 api 添加了类型。希望它有所帮助,即使不是一个有针对性的答案。
在 package.json
(使用 @electron-forge package.json setup、webpack + typescript 模板)中,在 entryPoints
下,确保你有:
"preload": {
"js": "./src/preload.ts"
}
在创建 BrowserWindow 的 src/index.ts
中,使用神奇的 webpack 常量引用捆绑的预加载脚本(也许您的预加载脚本没有被捆绑?):
const mainWindow = new BrowserWindow({
webPreferences: {
preload: MAIN_WINDOW_PRELOAD_WEBPACK_ENTRY
}
});
src/preload.ts
的内容:
import { contextBridge } from "electron";
import api from './api'
contextBridge.exposeInMainWorld("api", api);
src/api/index.ts
只是导出 api 的所有特征。示例:
import * as myFeature from "./my-feature";
// api exports functions that make up the frontend api, ie that in turn either do IPC calls to main for db communication or use allowed nodejs features like file i/o.
// Example `my-feature.ts`:
// export const fetchX = async (): Promise<X[]> => { ... }
export default {
...myFeature
}
Typescript 2.9+ 可以通过添加全局声明来识别您的 api 函数,例如 api.fetchX
,例如src/index.d.ts
(
declare const api: typeof import("./api").default;
...您需要参考 tsconfig.json
:
{
...
"files": [
"src/index.d.ts"
]
}
所有这些都完成了,您应该可以在不导入任何内容的情况下从 renderer-side 调用带有输入支持(IDE 的 ymmv)的 api.fetchX
。例子 App.tsx
:
import * as React from 'react'
// do not import api here, it should be globally available
export const App = () => {
useEffect(() => {
(async () => {
const project = await api.fetchX();
...
})();
}, []);
return <h1>My App</h1>
}