Использование дополнений Node.js в рендере Electron с Webpack - PullRequest
0 голосов
/ 27 мая 2018

У меня есть следующий рендерер:

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)

Как это исправить

1 Ответ

0 голосов
/ 27 мая 2018

Проблема

Первая проблема заключается в том, что 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 для вас, но это много мегабайт, по сути, ничего.

...