问题描述
我有以下渲染器:
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)
我该如何解决这个问题?
How do I fix this?
推荐答案
问题
第一个问题是 node-bindings
,node-serialport
依赖于解析其 Node.js 插件的路径,在 Electron 中根本不起作用.有一个 未决问题 ,我认为相关的 PR 不是甚至是一个完整的修复,因为我已经进行了一些调试,并且似乎 fileName
在整个 getFileName
中仍然是 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.
其次,我们必须用我们自己的 shim 替换 node-bindings
,src/bindings.js
:
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
electron dist
应该正常工作".
将 node-serialport
作为对生成的 dist/package.json
的依赖项添加并且只需 npm i
nstalling 可能会成功在那里,并将 serialport
标记为 Webpack 中的外部,但这感觉更脏(包版本不匹配等).
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 插件的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持跟版网!