在 Electron 中,如何使用多个模块安全地构建我的渲染器脚本?
In Electron, how can I securely build my renderer script out of multiple modules?
我明白为什么这是最佳做法 not to expose Node or Electron's full API in renderer processes。而且我了解如何使用预加载脚本仅公开这些 API 的必要子集。
但我不明白的是如何使用该模型编写渲染器逻辑。
我的主要 BrowswerWindow
的 renderer.js
脚本必须包含几乎所有 UI 逻辑,侦听所有 window
事件并修改页面的 DOM 相应地。
通常,我想以模块化的方式构建这个庞大的脚本,但 Electron 的独立上下文意味着 Node's module system won't be available。 Electron 文档建议使用 webpack
或 parcel
等打包工具可能是解决方案。但是没有提供例子。
(我注意到在 VSCode and Electron's own Fiddle 应用程序的源代码中,他们似乎只是耸了耸肩并启用了 Node 集成。)
但在理想情况下,这实际上意味着如何工作?
例如,每次我想测试-运行 应用程序时,我是否应该有一个脚本来将我所有的 src
内容缩小为 3 个脚本 - main.min.js
,rederer.min.js
和 preload.min.js
?将它们放在 app
目录中?然后从那里复制 运行ning electron 之前的静态内容?
这是我的想法,还是我没听懂?
您不需要使用 webpack or parcel 来构建安全的 Electron 应用程序。这些用于捆绑您的代码,而不是保护它。
很难为 Electron 找到好的、最佳实践的设计文档。正如您已经发现的那样,将 nodeIntegration
设置为 false
而不自己实施的建议可能会造成混淆。
如果您不在渲染中加载任何远程内容,则 nodeIntegration
可以设置为 true
。参考:Do not enable Node.js integration for remote content。我总是将我的设置为 false
并利用我的 preload.js
脚本在主进程和渲染进程之间通信/传输数据。
关于“如何使用此模型编写渲染器逻辑”,当您退后一步并从更广阔的角度看时,它有望变得更加清晰。
processes (though I tend to use the term threads). The main process and the render process你一定会想到Electron。
如您所知,您使用 IPC 进行进程间通信。在主进程和渲染进程中,您可以使用事件。在您的主进程中使用 Node 的 events 时,模块变得紧密耦合的问题已被消除。
现在,preload.js
脚本...
我看到很多人试图直接访问硬编码到他们的 preload.js
脚本中的具体实现。这可能会在短时间内变得非常复杂和混乱 space。
我采取了一种非常不同的方法。我的整个项目只使用 one preload.js
脚本。我的预加载脚本的目的不是定义/实现具体模型,而是将 preload.js
脚本纯粹用作通信渠道。 IE:用于在主进程和渲染进程之间来回发送“事件”的通道名称(和可选数据)。让主进程中的脚本处理具体的 Electron/Node 实现,让渲染进程中的脚本处理 html 交互性。
主进程
主进程包含Electron的核心和Node.js
您可以将代码拆分成逻辑块并使用 Node 的 require
函数导入它们。请记住,一旦调用它们,它们就会被缓存。他们也有效地拥有自己的范围,这很棒。
为了减轻尝试计算路径的负担,只需使用 Node 的 path.join(...) 模块。
使用 Node 的事件在您的应用程序模块之间进行通信。
使用 Electron 的 ipcMain.on(...) and ipcMain.handle(...) to listen for IPC events from the render process and contents.send(...) 将 IPC 事件发送到您的特定渲染进程。
渲染过程
渲染进程包含您的 html 视图,Javascript 使您的 html 视图具有交互性,当然,html 相关视图 CSS.
在此处使用 ES6 模块来分隔您的代码。 import
将它们放入您的 html Javascript 文件中,记住仅 export
您的公开可用函数。
您可以将所有路径引用为相对路径。如果设置正确,您可能使用的任何构建工具都应该能够处理此问题。
使用 Electron 的 ipcRender.on(...) to listen for IPC events from the main process and ipcRender.send(...) and ipcRender.invoke(...) 将 IPC 事件发送到主进程。
要轻松使用上述命令,请像这样设置您的 preload.js
脚本。
preload.js
(主进程)
// Import the necessary Electron components.
const contextBridge = require('electron').contextBridge;
const ipcRenderer = require('electron').ipcRenderer;
// White-listed channels.
const ipc = {
'render': {
// From render to main.
'send': [
'message:fromRender' // Example channel name
],
// From main to render.
'receive': [
'message:fromMain' // Example channel name
],
// From render to main and back again.
'sendReceive': [
'message:fromRenderAndBackAgain' // Example channel name
]
}
};
// Exposed protected methods in the render process.
contextBridge.exposeInMainWorld(
// Allowed 'ipcRenderer' methods.
'ipcRender', {
// From render to main.
send: (channel, args) => {
let validChannels = ipc.render.send;
if (validChannels.includes(channel)) {
ipcRenderer.send(channel, args);
}
},
// From main to render.
receive: (channel, listener) => {
let validChannels = ipc.render.receive;
if (validChannels.includes(channel)) {
// Deliberately strip event as it includes `sender`.
ipcRenderer.on(channel, (event, ...args) => listener(...args));
}
},
// From render to main and back again.
invoke: (channel, args) => {
let validChannels = ipc.render.sendReceive;
if (validChannels.includes(channel)) {
return ipcRenderer.invoke(channel, args);
}
}
}
);
在 preload.js
脚本中构建频道名称 whitelist 提供了一个真实来源。在每个创建的 window 中包含相同的(唯一的)preload.js
脚本变得容易且无错误。
如果您需要我在这个答案中添加在主进程和渲染进程中使用 preload.js
脚本,请告诉我。
我明白为什么这是最佳做法 not to expose Node or Electron's full API in renderer processes。而且我了解如何使用预加载脚本仅公开这些 API 的必要子集。
但我不明白的是如何使用该模型编写渲染器逻辑。
我的主要 BrowswerWindow
的 renderer.js
脚本必须包含几乎所有 UI 逻辑,侦听所有 window
事件并修改页面的 DOM 相应地。
通常,我想以模块化的方式构建这个庞大的脚本,但 Electron 的独立上下文意味着 Node's module system won't be available。 Electron 文档建议使用 webpack
或 parcel
等打包工具可能是解决方案。但是没有提供例子。
(我注意到在 VSCode and Electron's own Fiddle 应用程序的源代码中,他们似乎只是耸了耸肩并启用了 Node 集成。)
但在理想情况下,这实际上意味着如何工作?
例如,每次我想测试-运行 应用程序时,我是否应该有一个脚本来将我所有的 src
内容缩小为 3 个脚本 - main.min.js
,rederer.min.js
和 preload.min.js
?将它们放在 app
目录中?然后从那里复制 运行ning electron 之前的静态内容?
这是我的想法,还是我没听懂?
您不需要使用 webpack or parcel 来构建安全的 Electron 应用程序。这些用于捆绑您的代码,而不是保护它。
很难为 Electron 找到好的、最佳实践的设计文档。正如您已经发现的那样,将 nodeIntegration
设置为 false
而不自己实施的建议可能会造成混淆。
nodeIntegration
可以设置为 true
。参考:Do not enable Node.js integration for remote content。我总是将我的设置为 false
并利用我的 preload.js
脚本在主进程和渲染进程之间通信/传输数据。
关于“如何使用此模型编写渲染器逻辑”,当您退后一步并从更广阔的角度看时,它有望变得更加清晰。
processes (though I tend to use the term threads). The main process and the render process你一定会想到Electron。
如您所知,您使用 IPC 进行进程间通信。在主进程和渲染进程中,您可以使用事件。在您的主进程中使用 Node 的 events 时,模块变得紧密耦合的问题已被消除。
现在,preload.js
脚本...
我看到很多人试图直接访问硬编码到他们的 preload.js
脚本中的具体实现。这可能会在短时间内变得非常复杂和混乱 space。
我采取了一种非常不同的方法。我的整个项目只使用 one preload.js
脚本。我的预加载脚本的目的不是定义/实现具体模型,而是将 preload.js
脚本纯粹用作通信渠道。 IE:用于在主进程和渲染进程之间来回发送“事件”的通道名称(和可选数据)。让主进程中的脚本处理具体的 Electron/Node 实现,让渲染进程中的脚本处理 html 交互性。
主进程
主进程包含Electron的核心和Node.js
您可以将代码拆分成逻辑块并使用 Node 的 require
函数导入它们。请记住,一旦调用它们,它们就会被缓存。他们也有效地拥有自己的范围,这很棒。
为了减轻尝试计算路径的负担,只需使用 Node 的 path.join(...) 模块。
使用 Node 的事件在您的应用程序模块之间进行通信。
使用 Electron 的 ipcMain.on(...) and ipcMain.handle(...) to listen for IPC events from the render process and contents.send(...) 将 IPC 事件发送到您的特定渲染进程。
渲染过程
渲染进程包含您的 html 视图,Javascript 使您的 html 视图具有交互性,当然,html 相关视图 CSS.
在此处使用 ES6 模块来分隔您的代码。 import
将它们放入您的 html Javascript 文件中,记住仅 export
您的公开可用函数。
您可以将所有路径引用为相对路径。如果设置正确,您可能使用的任何构建工具都应该能够处理此问题。
使用 Electron 的 ipcRender.on(...) to listen for IPC events from the main process and ipcRender.send(...) and ipcRender.invoke(...) 将 IPC 事件发送到主进程。
要轻松使用上述命令,请像这样设置您的 preload.js
脚本。
preload.js
(主进程)
// Import the necessary Electron components.
const contextBridge = require('electron').contextBridge;
const ipcRenderer = require('electron').ipcRenderer;
// White-listed channels.
const ipc = {
'render': {
// From render to main.
'send': [
'message:fromRender' // Example channel name
],
// From main to render.
'receive': [
'message:fromMain' // Example channel name
],
// From render to main and back again.
'sendReceive': [
'message:fromRenderAndBackAgain' // Example channel name
]
}
};
// Exposed protected methods in the render process.
contextBridge.exposeInMainWorld(
// Allowed 'ipcRenderer' methods.
'ipcRender', {
// From render to main.
send: (channel, args) => {
let validChannels = ipc.render.send;
if (validChannels.includes(channel)) {
ipcRenderer.send(channel, args);
}
},
// From main to render.
receive: (channel, listener) => {
let validChannels = ipc.render.receive;
if (validChannels.includes(channel)) {
// Deliberately strip event as it includes `sender`.
ipcRenderer.on(channel, (event, ...args) => listener(...args));
}
},
// From render to main and back again.
invoke: (channel, args) => {
let validChannels = ipc.render.sendReceive;
if (validChannels.includes(channel)) {
return ipcRenderer.invoke(channel, args);
}
}
}
);
在 preload.js
脚本中构建频道名称 whitelist 提供了一个真实来源。在每个创建的 window 中包含相同的(唯一的)preload.js
脚本变得容易且无错误。
如果您需要我在这个答案中添加在主进程和渲染进程中使用 preload.js
脚本,请告诉我。