在 Electron 的渲染器中使用 Node.js 插件和 Webpack

Using Node.js addons in Electron's renderer with Webpack

我有以下渲染器:

import SerialPort from "serialport";

new SerialPort("/dev/tty-usbserial1", { baudRate: 57600 });

它由 Webpack 构建,具有以下配置(为简洁起见进行了删减):

const config = {
  entry: { renderer: ["./src/renderer"] }
  output: {
    path: `${__dirname}/dist`,
    filename: "[name].js",
  },
  target: "electron-renderer",
  node: false, // Disables __dirname mocking and such
};

它由开发服务器和 index.html 一起提供,并由主进程作为网页加载(这是开发期间热模块替换所必需的)。

主进程由 Webpack 构建并发送到 dist。 Webpack 插件还会生成以下 dist/package.json:

{
  "name": "my-app",
  "main": "main.js"
}

当我 运行 electron dist 时,渲染器进程崩溃并出现以下错误:

Uncaught TypeError: Path must be a string. Received undefined
    at assertPath (path.js:28)
    at dirname (path.js:1364)
    at Function.getRoot (bindings.js?dfc1:151)
    at bindings (bindings.js?dfc1:60)
    at eval (linux.js?d488:2)
    at Object../node_modules/serialport/lib/bindings/linux.js (renderer.js:12686)
    at __webpack_require__ (renderer.js:712)
    at fn (renderer.js:95)
    at eval (auto-detect.js?3cc7:16)
    at Object../node_modules/serialport/lib/bindings/auto-detect.js (renderer.js:12638)

我该如何解决这个问题?

问题

第一个问题是 node-bindingsnode-serialport 依赖它来解析其 Node.js 插件的路径,但在 Electron 中根本不起作用。为此有一个 open issue,我认为相关的 PR 甚至都不是一个完整的修复程序,因为我已经进行了一些调试,并且看起来 fileName 在整个过程中仍然是 undefined整个getFileName.

第二个问题:即使它以某种方式在某处找到了 serialport.node,在打包应用程序进行分发后它也不会工作,因为插件本身不在 dist 目录中,而且 Webpack 不能将它与主 JS 文件捆绑在一起。

人们可以尝试用 node-loader 来解决这个问题,给定一个正常工作的 node-bindings,但这也无济于事,因为 node-bindings 使用了复杂的启发式算法,Webpack 可以简单地做到这一点当试图了解其 require 可能需要哪些文件时,不要从中推断。 Webpack 可以做的唯一安全的事情是包含整个项目,"just in case",显然,这是绝对不行的,所以 node-loader 不会复制任何东西。

所以,我们需要手动替换node-bindings和复制serialport.node

解决方案

首先,我们必须抓取插件并将其放入 dist。这需要在 main 的 Webpack 构建中完成,因为渲染器作为网页提供,可能来自内存文件系统(因此 *.node 文件可能不会发送到磁盘,Electron 永远不会看到它) .方法如下:

import CopyWebpackPlugin from "copy-webpack-plugin";

const config = {
  // ...
  plugins: [
    new CopyWebpackPlugin([
      "node_modules/serialport/build/Release/serialport.node",
    ]),
  ],
  // ...
};

不幸的是,它是硬编码的,但是如果有什么变化很容易修复。

其次,我们必须用我们自己的垫片替换node-bindingssrc/bindings.js:

module.exports = x =>
  __non_webpack_require__(
    `${require("electron").remote.app.getAppPath()}/${x}`
  );

__non_webpack_require__ 是不言自明的(是的,简单的 require 不会工作,没有一些技巧,因为它由 Webpack 处理),并且 require("electron").remote.app.getAppPath() 是必要的,因为 __dirname 实际上并没有解析到人们所期望的 - 一个到 dist 的绝对路径 - 而是指向深埋在 Electron 中的某个目录。

这是在渲染器的 Webpack 配置中完成替换的方法:

import { NormalModuleReplacementPlugin } from "webpack";

const config = {
  // ...
  plugins: [
    new NormalModuleReplacementPlugin(
      /^bindings$/,
      `${__dirname}/src/bindings`
    ),
  ],
  // ...
};

就是这样!完成上述操作后,index.html + renderer.js 将由某个服务器(或任何您的方法)提供服务,并且 dist 看起来像这样:

dist/
  main.js
  package.json
  serialport.node

electron dist 应该 "just work".

备选方案

可能会添加 node-serialport 作为对生成的 dist/package.json 的依赖项,然后 npm i 将其安装在那里,并将 serialport 标记为外部Webpack,但感觉更脏(包版本不匹配等)。

另一种方法是将所有内容声明为外部,让 electron-packager 为您将 node_modules 的整个生产部分复制到 dist,但这会占用很多兆字节基本上没有。

我要感谢@Alec Mev,因为他实际上是整个互联网上唯一真正有效且内含真正知识的答案。我花了几天时间解决 webpack 和电子本机模块的问题,直到我尝试了 Alec 的答案并且它完美运行。

只是一个小的补充,如果在主进程中使用本机代码而不是渲染器(最常见的情况是应用程序安全性更强),则需要按以下方式调整 shim:

module.exports = x =>
  __non_webpack_require__(
    `${require('electron').app.getAppPath()}/${x}`
  );

写的时候觉得@Alec Mev回答很精彩

就我而言,所有互联网建议的黑客攻击(如复制、加载程序等)都没有成功,而且真的不清楚。

我想通过电子渲染器调用我自己的本机节点插件,但正如 Alec Webpack 所提到的那样,它没有按要求处理 .node 文件。所以就我而言:

文件夹结构

Electron project dir
│   electron.main.js
│   package.json
│
├───Addon
│   │   addon.js
│   │   package.json
│   │
│   └───dist
│           addon.node
│
└───render-site
    │   index.html
    │   js.js
    │   webpack.config.js
    │
    └───build
  • 这是演示 - render-site 是您的 webpack 站点,dist 是您的插件二进制文件位置。

非常重要的是将 webpack 目标设置为 'electron-renderer',它允许您更轻松地使用 electron 和其他节点模块 fs 的 require。

在您的站点中调用插件,就像 docs 建议的那样

const addon = require('electron').remote.require('./addon/dist/addon.node');

就是这样。您可以 运行 webpack 并在使用插件的同时为站点提供服务。

生产非常简单,您只需确保将插件复制到 electron 的构建(我正在使用 electron-builder 复制系统),相对路径为 electron.main.js.

希望对您有所帮助。

我在 electron-renderer 中尝试使用本机模块时遇到了类似的错误。
虽然在我的例子中错误信息是这样的:

node-loader: The specified module could not be found

我相信这都与节点加载器有关。对我来说,native-addon-loader 是运行良好的加载器。
另外,请确保您的 Webpack 目标是 nodeelectron-mainelectron-renderer,否则它将无法工作(无法在客户端使用本机模块)。