Проблема
Первая проблема заключается в том, что node-bindings
, который node-serialport
использует для определения пути к своему аддону Node.js, просто не работает в Electron.Для этого есть открытый выпуск , и я не думаю, что связанный PR - это даже полное исправление, так как я выполнил некоторую отладку, и кажется, что fileName
остается undefined
на протяжении всегоgetFileName
.
Вторая проблема: даже если он каким-то образом найдет serialport.node
где-то, он не будет работать после упаковки приложения для распространения, поскольку самого аддона нет в каталоге dist
и Webpack не может просто связать его вместе с основным файлом JS.
Можно попытаться решить эту проблему с помощью node-loader
, учитывая правильно работающий node-bindings
, но это неЭто также не поможет, поскольку node-bindings
использует сложную эвристику, которую Webpack просто не может экстраполировать, пытаясь понять, какие файлы могут потребоваться для require
.Единственное безопасное, что может сделать Webpack, - это включить весь проект «на всякий случай», и это, безусловно, не пойдет, так что node-loader
просто ничего не копирует.
Итак, нам нужнозаменить node-bindings
и скопировать serialport.node
вручную.
Решение
Сначала мы должны взять аддон и поместить его в dist
.Это необходимо сделать в основной сборке Webpack, поскольку средство визуализации служит веб-страницей, возможно, из файловой системы в памяти (поэтому файл *.node
может не быть записан на диск, и Electron никогда его не увидит).Вот как:
import CopyWebpackPlugin from "copy-webpack-plugin";
const config = {
// ...
plugins: [
new CopyWebpackPlugin([
"node_modules/serialport/build/Release/serialport.node",
]),
],
// ...
};
К сожалению, жестко закодировано, но легко исправить, если что-то изменится.
Во-вторых, мы должны заменить node-bindings
нашей собственной прокладкой, 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.
И вот как выполняется замена в конфигурации 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
должен "просто работать".
Альтернативы
Может сойти с рук, добавив node-serialport
в качестве зависимости к сгенерированному dist/package.json
и просто npm i
, установив его там и отметив serialport
как внешнее в Webpack, но это выглядит еще более грязным (несоответствие версий пакета и т. Д.).
Другой способ - просто объявить все как внешние, и electron-packager
просто скопировать всю производственную частьnode_modules
до dist
для вас, но это много мегабайт, по сути, ничего.