在 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 的必要子集。

但我不明白的是如何使用该模型编写渲染器逻辑。

我的主要 BrowswerWindowrenderer.js 脚本必须包含几乎所有 UI 逻辑,侦听所有 window 事件并修改页面的 DOM 相应地。

通常,我想以模块化的方式构建这个庞大的脚本,但 Electron 的独立上下文意味着 Node's module system won't be available。 Electron 文档建议使用 webpackparcel 等打包工具可能是解决方案。但是没有提供例子。

(我注意到在 VSCode and Electron's own Fiddle 应用程序的源代码中,他们似乎只是耸了耸肩并启用了 Node 集成。)

但在理想情况下,这实际上意味着如何工作?

例如,每次我想测试-运行 应用程序时,我是否应该有一个脚本来将我所有的 src 内容缩小为 3 个脚本 - main.min.jsrederer.min.jspreload.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 脚本,请告诉我。