在Webpack的Electron渲染器中使用Node.js插件 [英] Using Node.js addons in Electron's renderer with Webpack
问题描述
我有以下渲染器:
import SerialPort from "serialport";
new SerialPort("/dev/tty-usbserial1", { baudRate: 57600 });
它是由Webpack构建的,具有以下配置(为简洁起见进行了修整):
It's built by Webpack, with the following config (trimmed for brevity):
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
一起提供服务,并由主进程作为网页加载(开发过程中热模块更换需要此页面).
It's served by a development server, along with an index.html
, and is loaded by the main process as a web page (this is needed for hot module replacement during development).
主要流程是由Webpack构建的,并也发送到 dist
.Webpack插件还生成以下 dist/package.json
:
The main process is built by Webpack and emitted to dist
too. A Webpack plugin also generates the following dist/package.json
:
{
"name": "my-app",
"main": "main.js"
}
当我运行 electron dist
时,渲染器进程崩溃,并出现以下错误:
When I run electron dist
, the renderer process crashes with the following error:
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-serialport
用来解析其Node.js插件路径的 node-bindings
根本在Electron中不起作用.为此存在一个公开问题,我不认为相关的PR是甚至是一个完整的修复程序,因为我已经进行了一些调试,并且在整个 getFileName
中, fileName
似乎仍然是 undefined
.
Problem
The first problem is that node-bindings
, which node-serialport
relies on to resolve the path to its Node.js addon, simply doesn't work in Electron. There's an open issue for this, and I don't think the associated PR is even a complete fix, since I've done some debugging, and it appears that fileName
remains undefined
throughout the whole getFileName
.
第二个问题:即使它以某种方式在某个地方找到了 serialport.node
,在打包要分发的应用程序后它也无法工作,因为插件本身不在 dist中
目录,Webpack无法将其与主要JS文件捆绑在一起.
The second problem: even if it somehow found a serialport.node
somewhere, it wouldn't work after packaging the application for distribution, since the addon itself isn't in the dist
directory, and Webpack can't just bundle it together with the main JS file.
可以尝试使用 node-loader
,并给出了正确的 node-bindings
,但也无济于事,因为 node-bindings
使用了精心设计的启发式方法,Webpack根本无法从中推断出试图了解其 require
可能需要哪些文件.Webpack唯一可以做的安全的事情就是包括整个项目,以防万一,这显然是不行的,所以 node-loader
只是不复制任何内容.
One could attempt to solve this with node-loader
, given a correctly working node-bindings
, but that wouldn't help either, since node-bindings
uses elaborate heuristics, which Webpack simply can't extrapolate from, when trying to understand what files could be required by its require
. The only safe thing Webpack could do is include the whole project, "just in case", and that's a certain no-go, obviously, so node-loader
just doesn't copy anything.
因此,我们需要替换 node-bindings
并手动复制 serialport.node
.
So, we need to replace node-bindings
and copy serialport.node
manually.
首先,我们必须获取插件并将其放入 dist
中.这需要在main的Webpack构建中完成,因为渲染器是作为网页提供的,可能是通过内存中的文件系统提供的(因此 *.node
文件可能不会发射到磁盘上,而Electron永远不会看到它).方法如下:
First, we must grab the addon and put it in dist
. This needs to be done in main's Webpack build, since the renderer is served as web page, potentially from an in-memory file system (so the *.node
file may not be emitted to disk, and Electron will never see it). Here's how:
import CopyWebpackPlugin from "copy-webpack-plugin";
const config = {
// ...
plugins: [
new CopyWebpackPlugin([
"node_modules/serialport/build/Release/serialport.node",
]),
],
// ...
};
不幸的是,经过硬编码,但是如果发生更改,很容易修复.
Hardcoded, unfortunately, but easy to fix if something changes.
第二,我们必须用自己的垫片 src/bindings.js
替换 node-bindings
:
Second, we must substitute node-bindings
with our own shim, src/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内部的某个目录中.
__non_webpack_require__
is self-explanatory (yes, plain require
won't work, without some trickery, as it's handled by Webpack), and the require("electron").remote.app.getAppPath()
is necessary because __dirname
doesn't actually resolve to what one would expect - an absolute path to dist
- but rather to some directory buried deep inside Electron.
这是在渲染器的Webpack配置中完成替换的方法:
And here's how the replacement is done, in renderer's Webpack config:
import { NormalModuleReplacementPlugin } from "webpack";
const config = {
// ...
plugins: [
new NormalModuleReplacementPlugin(
/^bindings$/,
`${__dirname}/src/bindings`
),
],
// ...
};
就是这样!完成上述步骤后, index.html
+ renderer.js
将由某些服务器(或您采用的任何方法)以及 dist
看起来像这样:
And that's it! Once the above is done, and index.html
+ renderer.js
are being served by some server (or whatever your approach is), and the dist
looks something like this:
dist/
main.js
package.json
serialport.node
电子距离
应该正常工作".
通过添加 node-serialport
作为对所生成的 dist/package.json
的依赖项,并可能只是 npm i
安装它,可能会避免在其中,并在Webpack中将 serialport
标记为外部,但这感觉甚至更脏(软件包版本不匹配等).
Could potentially get away with adding node-serialport
as a dependency to the generated dist/package.json
and just npm i
nstalling it in there, and marking serialport
as an external in Webpack, but that feels even dirtier (package version mismatches, etc.).
另一种方法是将所有内容声明为外部对象,并使用 electron-packager
将 node_modules
的整个生产部分复制到 dist
您,但实际上根本就是一整兆.
Another way is to just declare everything as externals, and have electron-packager
just copy the whole production part of node_modules
to dist
for you, but that's a whole lot of megabytes for basically nothing.
这篇关于在Webpack的Electron渲染器中使用Node.js插件的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!