️❗ Проверьте этот пост в блоге на mmazzarolo.com для выделенных блоков кода.
TL;DR: пошаговое руководство, объясняющее, как создать настольное приложение с помощью Create React App (CRA) и Electron. Исходный код проекта вы можете найти на GitHub.
Недавно мне нужно было обернуть приложение React, сгенерированное с помощью Create React App (CRA), с помощью Electron (ну, само приложение использует React Native for Web, но это не имеет значения).
Моя цель заключалась в том, чтобы максимально оставаться в пределах ограничений приложения Create React (без извлечения).
В Интернете нет недостатка в руководствах о том, как это сделать. Тем не менее, я не смог найти тот, который полностью соответствует рекомендациям по безопасности Electron и обеспечивает настройку дистрибутива с использованием Electron-builder.
Итак, вот еще один учебник о том, как обернуть приложение, созданное с помощью Create React App в Electron. — от первоначальных строительных лесов до рабочего процесса дистрибуции.
Скаффолдинг приложения React
Начнем с пустого приложения React, созданного с помощью Create React App.
npx create-react-app my-electron-app
Затем добавьте следующие зависимости (большинство из них здесь только для упрощения процесса разработки):
cd my-electron-app yarn add -D concurrently cross-env electron electron-builder electronmon wait-on
concurrently
: Одновременное выполнение нескольких команд. Мы будем использовать его для запуска процесса Electron и приложения React в режиме просмотра.cross-env
: Запускайте сценарии, устанавливающие и использующие переменные среды на разных платформах. Мы будем использовать его, чтобы сделать наши скрипты совместимыми как с операционными системами Unix, так и с Windows.electron
: основная структура для создания приложения.electron-builder
: Комплексное решение для упаковки и сборки готового к распространению приложения Electron для macOS, Windows и Linux.electronmon
: Вроде , но для процесса Electron. Позволяет просматривать и перезагружать наше приложение Electron.wait-on
: Утилита для ожидания файлов, портов, сокетов и т. д. Мы будем использовать ее для ожидания сборки приложения React, прежде чем открывать приложение Electron (во время разработки).
Основной скрипт Electron
Следующий шаг — создание основного скрипта Electron. Этот скрипт управляет основным процессом, который работает в полной среде Node.js и отвечает за управление жизненным циклом вашего приложения, отображение собственных интерфейсов, выполнение привилегированных операций и управление процессами рендеринга.
Основной скрипт Electron часто называется main.js
и хранится в <project-root>/electron/main.js
, но в нашем случае мы назовем его electron.js
(чтобы устранить неоднозначность) и сохраним его в <project-root>/public/electron.js
(чтобы приложение Create React автоматически скопировало его в каталог сборки).
// public/electron.js
// Module to control the application lifecycle and the native browser window.
const { app, BrowserWindow, protocol } = require("electron");
const path = require("path");
const url = require("url");
// Create the native browser window.
function createWindow() {
const mainWindow = new BrowserWindow({
width: 800,
height: 600,
// Set the path of an additional "preload" script that can be used to
// communicate between node-land and browser-land.
webPreferences: {
preload: path.join(__dirname, "preload.js"),
},
});
// In production, set the initial browser path to the local bundle generated
// by the Create React App build process.
// In development, set it to localhost to allow live/hot-reloading.
const appURL = app.isPackaged
? url.format({
pathname: path.join(__dirname, "index.html"),
protocol: "file:",
slashes: true,
})
: "http://localhost:3000";
mainWindow.loadURL(appURL);
// Automatically open Chrome's DevTools in development mode.
if (!app.isPackaged) {
mainWindow.webContents.openDevTools();
}
}
// Setup a local proxy to adjust the paths of requested files when loading
// them from the local production bundle (e.g.: local fonts, etc...).
function setupLocalFilesNormalizerProxy() {
protocol.registerHttpProtocol(
"file",
(request, callback) => {
const url = request.url.substr(8);
callback({ path: path.normalize(`${__dirname}/${url}`) });
},
(error) => {
if (error) console.error("Failed to register protocol");
}
);
}
// This method will be called when Electron has finished its initialization and
// is ready to create the browser windows.
// Some APIs can only be used after this event occurs.
app.whenReady().then(() => {
createWindow();
setupLocalFilesNormalizerProxy();
app.on("activate", function () {
// On macOS it's common to re-create a window in the app when the
// dock icon is clicked and there are no other windows open.
if (BrowserWindow.getAllWindows().length === 0) {
createWindow();
}
});
});
// Quit when all windows are closed, except on macOS.
// There, it's common for applications and their menu bar to stay active until
// the user quits explicitly with Cmd + Q.
app.on("window-all-closed", function () {
if (process.platform !== "darwin") {
app.quit();
}
});
// If your app has no need to navigate or only needs to navigate to known pages,
// it is a good idea to limit navigation outright to that known scope,
// disallowing any other kinds of navigation.
const allowedNavigationDestinations = "https://my-electron-app.com";
app.on("web-contents-created", (event, contents) => {
contents.on("will-navigate", (event, navigationUrl) => {
const parsedUrl = new URL(navigationUrl);
if (!allowedNavigationDestinations.includes(parsedUrl.origin)) {
event.preventDefault();
}
});
});
// In this file you can include the rest of your app's specific main process
// code. You can also put them in separate files and require them here.
Да, это не минимальная установка electron.js
, но я хотел получить несколько приятных значений по умолчанию и убедился, что мы следуем рекомендациям по безопасности Electron.
Во время выполнения Electron будет искать этот скрипт в поле main
конфигурации приложения package.json
, так что давайте обновим его:
{
"name": "my-electron-app",
"version": "0.1.0",
"private": true,
+ "main": "./public/electron.js",
"dependencies": {
Скрипт предварительной загрузки Electron
По умолчанию процесс, запущенный в вашем браузере, не сможет взаимодействовать с процессом Node.js. Electron решает эту проблему, позволяя использовать сценарий предварительной загрузки: сценарий, который запускается до загрузки процесса средства визуализации и имеет доступ как к глобальным переменным средства визуализации (например, window
и document
), так и к среде Node.js.
В нашем сценарии electron.js
мы уже указали, что ожидаем загрузки сценария предварительной загрузки из <project-root>/public/preload.js
. Итак, давайте создадим его:
// public/preload.js
// All of the Node.js APIs are available in the preload process.
// It has the same sandbox as a Chrome extension.
const { contextBridge } = require("electron");
// As an example, here we use the exposeInMainWorld API to expose the browsers
// and node versions to the main window.
// They'll be accessible at "window.versions".
process.once("loaded", () => {
contextBridge.exposeInMainWorld("versions", process.versions);
});
Приведенный выше код обращается к объекту Node.js process.versions
и предоставляет его в приложении React, делая его доступным по адресу window.versions
.
Создание приложения Create React, совместимого с Electron
Наша цель — оставаться в экосистеме Create React App без извлечения и использовать Electron только для рендеринга приложения React.
Для этого необходимо несколько настроек.
Обновите свойство homepage
Нам нужно принудительно создать приложение React для определения относительного корневого пути в сгенерированном HTML-файле. Это требование, потому что мы не собираемся обслуживать HTML-файл; он будет загружен непосредственно Электроном. Для этого мы можем установить для свойства homepage
элемента package.json
значение ./
(дополнительную информацию см. в разделе Построение относительных путей в документации по созданию приложения React).
{
"name": "my-electron-app",
"version": "0.1.0",
"private": true,
+ "homepage": "./",
"main": "./public/electron.js",
"dependencies": {
Обновить цели browserslist
Обновите раздел browserslist
в package.json
, чтобы он поддерживал только последнюю версию Electron. Это гарантирует, что Webpack/Babel добавят только те полифилы и функции, которые нам строго необходимы, сведя размер пакета к минимуму.
"browserslist": {
"production": [
+ "last 1 electron version",
- ">0.2%",
- "not dead",
- "not op_mini all"
],
"development": [
+ "last 1 electron version",
- "last 1 chrome version",
- "last 1 firefox version",
- "last 1 safari version"
]
},
Определить политику безопасности контента
Политика безопасности контента (CSP) — это дополнительный уровень защиты от атак с использованием межсайтовых сценариев и атак с внедрением данных. Поэтому я настоятельно рекомендую включить его в <project-root>/public/index.html
.
Следующий CSP позволит Electron запускать только встроенные скрипты (те, которые вводятся в файл HTML в процессе сборки приложения Create React).
<meta name="theme-color" content="#000000" />
<meta
name="description"
content="Web site created using create-react-app"
/>
+ <meta
+ http-equiv="Content-Security-Policy"
+ content="script-src 'self' 'unsafe-inline';"
+ />
Имейте в виду, что это всего лишь минимальный пример CSP. Вы можете настроить его дальше, чтобы разрешить список только определенных веб-сайтов, и вы можете сделать его еще более строгим, создав одноразовый номер для загрузки только встроенных сценариев, которые вы создали в процессе сборки. Дополнительные сведения см. в разделе Политика безопасности контента (CSP) в веб-документах MDN.
Определить сценарий запуска/разработки
В своем package.json
определите скрипт для создания приложения Create React и запустите процесс Electron в режиме наблюдения:
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject",
+ "electron:start": "concurrently -k \"cross-env BROWSER=none yarn start\" \"wait-on http://localhost:3000 && electronmon .\""
},
Вот разбивка того, что он делает:
concurrently -k
вызывает последующие команды параллельно и уничтожает их обе, когда процесс останавливается.cross-env BROWSER=none yarn start
устанавливает переменные средыBROWSER=none
(используяcross-env
для совместимости с Windows), чтобы отключить автоматическое открытие браузера, и вызывает скриптstart
, который запускает сборку Create React App в режиме наблюдения.wait-on http://localhost:3000 && electronmon .
ждет, пока сервер разработки Create React App обслужит приложение на локальном хосте: 3000, а затем вызываетelectronmon .
, чтобы запустить дополнение Electron в режиме наблюдения.
Теперь вы можете запустить yarn electron:start
, чтобы запустить приложение React в Electron, а не в окне браузера.
Упакуйте приложение Electron для распространения
Наконец, нам нужно внести несколько незначительных изменений в настройку Create React App, чтобы сгенерировать дистрибутивы для конкретной платформы, чтобы можно было установить наше приложение. Мы будем использовать Electron-builder, основанное на конфигурации решение для упаковки и сборки готовых к распространению приложений Electron для macOS, Windows и Linux.
Electron-builder предлагает тонну параметров конфигурации, но для простоты в этом руководстве мы добавим только самые минимальные параметры для создания рабочих распространяемых файлов.
Установите автора приложения и описание
Electron-builder выводит некоторую информацию по умолчанию, необходимую для сборки распространяемого файла (имя приложения, автора и описание) из package.json
, поэтому давайте укажем их:
"name": "my-electron-app",
"version": "0.1.0",
"private": true,
+ "author": "John Doe",
+ "description": "My fantastic Electron app",
"homepage": "./",
"main": "./public/electron.js",
"dependencies": {
Установите конфигурацию сборки
Добавим минимальную конфигурацию сборщика электронов в package.json
с помощью клавиши build
на верхнем уровне:
appId
: Идентификатор приложения, используемый для идентификации приложения в macOS (как CFBundleIdentifier) и Windows (как Идентификатор модели пользователя приложения).productName
: имя приложения, как показано в исполняемом файле приложения.directories.buildResources
: Путь к корневому каталогу, в котором хранятся ресурсы, не упакованные в приложение.files
: Общие дополнительные файлы (за пределамиdirectories.buildResources
), необходимые приложению для запуска.mac
,win
,linux
: Конфигурации для конкретных платформ.
+ "build": {
+ "appId": "com.electron.myapp",
+ "productName": "My Electron App",
+ "files": ["build/ **/*", "node_modules/** /*"],
+ "directories": {
+ "buildResources": "public"
+ },
+ "mac": {
+ "target": "dmg"
+ },
+ "win": {
+ "target": "nsis"
+ },
+ "linux": {
+ "target": "deb"
+ }
+ }
Добавить значок приложения
По умолчанию Electron-Builder будет искать значок приложения в каталоге <root-project>/build/icon.png
, так что вам должно быть удобно, если вы поместите его в каталог public
(процесс создания приложения React React позаботится о его перемещении в каталог build
).
Дополнительную информацию см. в Документации по иконкам Electronic-Builder.
Добавьте скрипты упаковки
Наконец, чтобы Electron-builder упаковал наше приложение, мы можем добавить скрипт упаковки для каждой целевой платформы в package.json
:
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject",
"electron:start": "concurrently -k \"cross-env BROWSER=none yarn start\" \"wait-on http://localhost:3000 && electronmon .\"",
+ "electron:package:mac": "yarn build && electron-builder -m -c.extraMetadata.main=build/electron.js",
+ "electron:package:win": "yarn build && electron-builder -w -c.extraMetadata.main=build/electron.js",
+ "electron:package:linux": "yarn build && electron-builder -l -c.extraMetadata.main=build/electron.js"
},
Эти команды создадут производственный пакет приложения React и упакуют его в дистрибутивы для Windows, macOS и Linux соответственно. По умолчанию дистрибутивы будут иметь форму NSIS (Windows), dmg (macOS) и deb (Linux).
Сгенерированные дистрибутивные файлы будут размещены в <project-root>/dist
, поэтому обязательно добавьте этот каталог в .gitignore
:
+ /dist
Резюме
Вот и все.
Теперь вы можете запустить yarn electron:start
, чтобы запустить процесс разработки, и yarn electron:package:<platform>
, чтобы создать распространяемый пакет.
Пожалуйста, имейте в виду, что проект, созданный с помощью этого руководства, представляет собой то, что я считаю абсолютным минимумом требований для обертывания приложения React с помощью Electron. Я настоятельно рекомендую потратить некоторое время на чтение официальной документации Electron и Electron-builder, чтобы настроить вашу установку.
Вы можете найти полный код для этого поста в блоге на GitHub.
Первоначально опубликовано на https://mmazzarolo.com.