Прошло несколько дней с тех пор, как я наткнулся на проект по созданию настольного приложения. В прошлом я работал над веб-приложениями и мобильными приложениями, используя React и React-Native, но это было что-то новое. Давным-давно я слышал о фреймворке, который позволяет нам создавать мультиплатформенные настольные приложения на JavaScript, и подумал про себя, что его следует использовать для этого проекта. Этим фреймворком был Электрон!

Приложения Электрон работают в два потока, основной и рендерер. Основной поток использует API-интерфейсы Electron для связи с ОС для выполнения собственных операций настольных приложений. Поток рендеринга отвечает за рендеринг DOM в окне приложения. Эти два потока взаимодействуют друг с другом, используя IPC (межпроцессное взаимодействие) Электронный API.

На домашней странице Electron четко сказано, что это проще, чем вы думаете, и это верно для большинства из них, но установка была не такой уж простой. Я собирался использовать React для проекта, поэтому я начал с использования electron-react-boilerplate, который также упоминался в Документации Electron. Проблема, с которой я столкнулся с этим шаблоном, заключалась в том, что он был слишком громоздким и в нем было установлено много модулей, которые не имели отношения к делу. Мне требовался шаблон, который имел только минимальные зависимости для создания приложения Electron на основе React. После обширных исследований я решил, что должен создать его для себя 😬.

Внедряем программирование:

Я начал с простого стартового кода, который нашел в документации. Он создает окно и загружает в него файл HTML, что довольно просто. Затем загрузите приложение React внутри этого окна. У меня уже был шаблон приложения React, который я создал некоторое время назад. Сначала полегче: я связал приложение React, попытался загрузить выходной HTML-файл в окно, но это не сработало. Я понял, что мне нужно внести некоторые изменения в конфигурацию Webpack. Я использую "/" вместо publicPath в выходных данных Webpack, потому что это требуется, когда приложение обслуживается с сервера. В случае с Electron файлы будут браться из локальной системы, поэтому я его удалил. Затем я удалил имена хеш-файлов, удалил оптимизацию разделения фрагментов и использовал одно имя файла, то есть renderer.js, и удалил полифилл, потому что он не требуется. После всех этих изменений я снова запустил бандл в приложении Electron и ЭВРИКА! это сработало.

Нужен режим разработки

Следующая задача — запустить приложение в режиме разработки с помощью HMR (горячая замена модуля). Для веб-приложения React я использую webpack-dev-server для обслуживания файлов и использования HMR. Чтобы использовать то же самое в Electron, я наткнулся на функцию BrowserWindow.loadURL, которая может загружать URL-адрес внутри окна приложения Electron. Теперь я обслуживал приложение React с помощью сервера разработки, передал https://localhost:3000/ внутри функции loadURL, и теперь мое приложение работало в среде разработки. Но вскоре я понял, что HMR не работает, и начал искать решение. В electron-react-boilerplate, упомянутом в документации, я заметил, что они также использовали пакет react-hot-loader для HMR. Я использовал то же самое и убедился, что он используется только в сборке разработки с использованием переменных среды, установленных в конфигурации Webpack, и теперь все заработало гладко.

Потоковое общение

Далее, использование API-интерфейсов IPC для связи между потоками. Я попытался импортировать модуль ipcRenderer в приложение React из пакета electron, но это вызвало ошибку. По-видимому, electron — это модуль узла, и любое приложение, использующее его, должно работать в среде узла, но наш renderer.js на самом деле работает в среде браузера. Изучив эту проблему, я обнаружил, что могу установить флаг nodeIntegration в параметрах BrowserWindow на true, чтобы предоставить приложение рендерера со средой узла. Благодаря этому я смог использовать модуль ipcRenderer, но позже я обнаружил, что приложение рендерера не должно отображаться в среде узла, потому что среда узла предоставляет доступ к файловой системе приложению, и любой внешний внедренный скрипт может получить доступ к файлу. система 🤷🏻‍♂️.

Я нашел несколько решений, которые не работали. Наконец, я вернулся к громоздкому electron-react-boilerplate ипроверил, как они справились с этой проблемой, и обнаружил, что они передали JS-приложение React в комплекте с предварительной загрузкой BrowserWindow options. На самом деле это дает два преимущества: во-первых, сценарии предварительной загрузки имеют доступ к среде узла, а во-вторых, это ускоряет загрузку приложения React в окне приложения. И снова ЭВРИКА! это работает отлично. Я мог отправлять и получать сообщения в и из основного потока и потока рендерера. Я также создал файл common.js, где я могу хранить имена каналов связи, используемые как основным потоком, так и потоком рендерера.

Подготовка к производству

Читая больше документации, я узнал, что Webpack предоставляет определенные цели для связывания JS-кода для Electron. Для связывания кода рендерера electron-renderer и для связывания скрипта предварительной загрузки electron-preload. Я использую electron-renderer в режиме разработки, потому что в этом случае я не предварительно загружаю JS своего приложения React и устанавливаю nodeIntegration в true для разработки. Для производства я использую electron-preload и устанавливаю nodeIntegration на false, а также обязательно удаляю ссылку на файл renderer.js в index.html, что можно сделать в конфигурации Webpack, потому что он будет предварительно загружен. Вот как выглядит моя конфигурация главного окна:

const mainWindow = new BrowserWindow({
  show: false,
  backgroundColor: "#000000",
  width: 1024,
  height: 768,
  webPreferences: {
    nodeIntegration: isDev,
    contextIsolation: !isDev,
    enableRemoteModule: false,
    preload: isDev ? null : path.join(__dirname, "build/renderer.js"),
  },
});

Мне также нужно убедиться, что при разработке HTML-код загружается с сервера разработки, работающего на локальном хосте, а при производстве он должен загружаться в виде файла.

const APP_URL = isDev ? "http://localhost:3000" : `file://${__dirname}/build/index.html`;
mainWindow.loadURL(APP_URL);

Советы по безопасности

У Электрона есть полная страница со всеми советами по безопасности, которым мы, как разработчики, должны следовать. Я потратил много времени на этот документ, и почти все рекомендации по безопасности были обработаны в шаблоне, который я создал. Существуют надлежащие проверки для включения и отключения некоторых свойств в зависимости от среды, в которой запускается приложение. Вы можете прочитать больше об этих рекомендациях по безопасности здесь: https://www.electronjs.org/docs/tutorial/security.

Упаковка и доставка

Для упаковки и доставки приложения я не смог найти более простой инструмент, чем electron-builder. Этот инструмент может упаковать приложение для всех платформ одной простой командой. Все, что вам нужно, это создать файл electron-builder.yml, указать некоторые параметры, а затем запустить команду electron-builder build -mwl, и она создаст установщики приложений для MacOS, Windows и Linux 🙌🏼. Вот файл electron-builder.yml, который я использую:

productName: "ElectronReact"
appId: "com.electron.ElectronReact"
files:
  - "build/"
  - "main.js"
  - "common.js"

Установить значок для приложения тоже просто. Вам просто нужно изображение PNG размером 512px x 512px, назовите его как icon.png и убедитесь, что оно включено в список files в файле electron-builder.yml.

Вывод

https://github.com/skb1129/electron-react-шаблон

Создание этого шаблона приложения с нуля было непростым делом, и, честно говоря, начинать что-то новое никогда не бывает легко. Я надеюсь, что это сэкономит много времени людям, использующим этот шаблон, для начала новых проектов.

Спасибо за чтение этого!