Если вы занимаетесь веб-разработками, то, вероятно, наслышаны об Electron. Для тех же, кто не в курсе, скажу, что это достаточно противоречивый фреймворк для создания приложений, совместимых с Windows, macOS и Linux, на основе веб-технологий и единой базы кода. Несмотря на то, что его приложения могут уступать нативным в скорости и легковесности, Electron позволяет веб-разработчикам развертывать их на 3 главных PC платформах, а, значит, экономит наше время и деньги. Фактически ряд широко распространенных настольных приложений, включая Visual Studio Code, Slack и Discord, созданы с помощью Electron.
В данном руководстве мы создадим простое приложение Electron. Предварительно вам необходимо ознакомиться с Node.js, лежащей в основе рассматриваемого фреймворка. Помимо этого не обойтись без первоначальных знаний о библиотеке Express. И в качестве бонуса используем TypeScript вместо JavaScript, после чего вы, вряд ли, захотите вернуться к JS.
При желании можете обратиться к моему репозиторию за более подробным материалом о приложении Electron с фронтендом Angular.
Введение
По сути, приложения Electron работают по той же фронтенд/бэкенд модели, что и большинство веб-приложений. Отличие же в том, что фронтенд-часть выполняется не в браузере, а в своем отдельном окне, при этом бэкенд-компонент является приложением Node, выполняющимся локально, а не на сервере.
Процесс создания приложения можно разбить на несколько основных этапов:
- Настройка среды
- Настройка TypeScript
- Создание бэкенда
- Создание фронтенда
- Тестирование приложения
- Сборка приложения
О безопасности: внашем примере для обеспечения взаимодействия фронтенда и бэкенда послужит сервер Express, что позволит нам оптимизировать процесс разработки и легко перенести в Electron уже имеющиеся у вас веб-приложения. Однако в производственной среде с этой целью всегда применяется Electron IPC.
Итак, приступим.
1. Настройка среды
Сначала убедитесь, что на вашем компьютере установлен Node.
Затем создайте новый каталог для приложения Electron и, запустив терминал, перейдите в него. Теперь для инициализации нового проекта Node выполните npm init
. Следуйте указаниям, выбирая варианты по умолчанию, если не уверены в ответе.
Примечание: по желанию на данном этапе вы также можете настроить систему контроля версий Git. Сделав это, убедитесь, что она игнорирует папки /dist
и /build
, поскольку в них будет находиться транслированный код и скомпилированные исполняемые файлы.
Установка зависимостей
Для установки необходимых зависимостей выполните из корневого каталога проекта следующие команды:
npm i --save-dev electron electron-packager typescript ts-node-dev
npm i express @types/express
2. Настройка TypeScript
В папке проекта создайте файл с именем tsconfig.json
и скопируйте в него следующее:
{
"compilerOptions": {
"target": "es6",
"module": "commonjs",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"outDir": "dist",
"experimentalDecorators": true
},
"include": [
"src/**/*.ts"
],
"exclude": [
"node_modules"
]
}
compilerOptions
— это набор опций, благодаря которым TypeScript понимает, как интерпретировать код и компилировать его на JavaScript. Однако, признавая TypeScript отличным языком программирования, не стоит забывать, что его необходимо компилировать на старый добрый JavaScript перед применением в производственной среде. Мы не будем подробно останавливаться на каждой опции, обратим лишь внимание на одну из них — outDir
. Именно она сообщает место сохранения окончательного кода JS. В данном руководстве TS будет размещать код в директории dist
, расположенной в корневом каталоге проекта.
Опция include
информирует транспилятор, где искать файлы с кодом TS, а опция exclude
явно указывает, какие компоненты исключить.
Исходные директории
Обратите внимание, что опция include
в tsconfig.json
ссылается на директорию src
, которую нам еще предстоит создать в каталоге проекта. Именно здесь будет размещаться весь бэкенд-код. Также нам понадобится директория frontend
:
Преобразования package.json
Нам потребуется изменить файл package.json
. Для этого откройте его и замените содержимое "scripts"
на следующее:
"scripts": {
"transpile": "tsc",
"watch": "ts-node-dev --respawn --poll ./src/main.ts",
"app": "electron .",
"build": "electron-packager . --out=build --overwrite"
},
Эти скрипты определяют команды, которые мы можем выполнять в проекте. Команда transpile
транспилирует код TypeScript в JavaScript.
watch
осуществляет постоянный контроль кода TypeScript и в случае внесения изменений автоматически выполняет его напрямую без предварительной транспиляции в JavaScript. Во время тестирования данная процедура может оказаться очень полезной.
Команда app
запускает приложение Electron для тестирования, а build
компилирует его окончательную выполняемую версию для текущей платформы, сохраняя ее в директории build
.
Все три команды можно выполнить в терминале в следующем формате: npm run <command>
, например npm run transpile
.
Наконец, изменим значение свойства "main"
на "dist/main.js"
, благодаря чему Electron запустит файл main.js
в своем главном процессе. Это не означает, что мы будем самостоятельно писать данный файл — он будет чуть позже транспилирован из кода TypeScript.
3. Создание бэкенда
Обычный бэкенд Node содержит лишь код для обработки HTTP-запросов от клиента. Помимо этого, бэкенд Electron содержит еще и код для обработки самого процесса Electron.
Создадим новый файл main.ts
в директории src
, скопировав в нее следующий код:
// Импортируем модули
import { app, BrowserWindow } from 'electron'
import express from 'express'
import path from 'path'
// Произвольный порт для Electron
const PORT = 7259
// Окно приложения Electron
let win: BrowserWindow | null
// Функция для создания нового окна приложения
const createWindow = () => {
win = new BrowserWindow({
width: 600,
height: 600,
backgroundColor: '#ffffff',
autoHideMenuBar: true,
})
// Загружаем файл HTML из каталога frontend
win.title = 'Hello World | Electron'
win.loadURL(`http://localhost:${PORT}`)
win.on('closed', () => {
win = null
})
}
// Настройка обратных вызовов GUI для разных событий
// Код помещен в конструкцию if, позволяя проводить тестирование в браузере во время разработки
if (app) {
// В момент инициализации приложения вызываем createWindow
app.on('ready', () => {
createWindow()
})
// При закрытии всех окон приложения выходим из него (не касается //MacOS)
app.on('window-all-closed', () => {
// Даже когда все окна закрыты, приложения MacOS обычно продолжают выполняться в фоновом режиме, пока пользователь явно не выйдет из приложения через панель Dock.
if (process.platform !== 'darwin') {
app.quit()
}
})
// Когда приложение, выполнявшееся в фоновом режиме, "активировано", вызываем createWindow
// Необходимость этого объясняется вышеупомянутой особенностью MacOS выполнять приложения при закрытых окнах 'window-all-closed'
app.on('activate', () => {
if (win === null) {
createWindow()
}
})
}
// Инициализируем сервер Express
const expressApp: express.Application = express()
// Проверяем доступность каталога frontend для клиентов
expressApp.use(express.static(path.join(__dirname, "../frontend")))
// Конечная точка Hello World
expressApp.get("/api/hello", async (req, res) => {
res.status(200).send('Hi! Electron is pretty cool.')
})
// Добавляем при необходимости другие конечные точки
// Перенаправляем все другие точки во frontend
expressApp.all("*", async (req, res) => {
res.sendFile(path.join(__dirname, "../frontend/index.html"))
})
// Запускаем сервер Express
expressApp.listen(process.env.PORT || PORT, () => {
console.log(`Running (port ${process.env.PORT || PORT})`)
})
Давайте разберем этот код. Сначала мы импортируем необходимые модули:
app
иBrowserWindow
изelectron
. Первый — для взаимодействия с приложением, а второй — для управления GUI.express
иpath
необходимы для обслуживания фронтенда.
Далее устанавливаем переменную const PORT
для настройки порта, который будет прослушиваться сервером. Это может быть любое произвольное число. Однако советую выбирать числа в диапазоне 49152–65535 во избежание конфликтов с зарезервированными или часто используемыми портами.
На этом этапе уже начинается работа с Electron. Сначала определяем переменную win
, которая будет содержать объект, представляющий окно приложения. В случае отсутствия окон приложения ее значением также может быть null
.
Затем определяем функцию createWindow
для создания нового окна приложения и сохраняем ее в переменной win
. Применяя разные опции, задаем размер и имя окна, а функция win.loadURL
дает окну команду загрузить и отобразить localhost:7259
, откуда и предоставляется HTML-файл фронтенда, который мы вскоре создадим. В завершении этого этапа определяем в этой функции обратный вызов win.on
, который при закрытии окна устанавливает значение объекта win
обратно на null
.
До настоящего момента наш код сообщал Electron, что и как нужно делать с окном приложения, но ничего не говорил про реализацию работы самого приложения. Этой цели послужат строки с 30 по 50. С помощью app.on
они определяют 3 функции обратных вызовов, которые срабатывают при определенных событиях.
При начальной инициализации процесса приложения Electron запускается событие ready
, вслед за чем мы создаем новое окно приложения и отображаем его, используя функцию createWindow
.
Как только закрываются все ранее открытые окна, запускается событие window-all-closed
. В Windows или Linux это говорит о том, что пользователь намерен окончательно выйти из приложения, поэтому мы вызываем app.quit
для завершения его работы. Однако в macOS приложения могут выполняться в фоновом режиме, пока пользователь не закроет их напрямую через панель Dock, поэтому этот особый случай учитывается в конструкции if
.
В завершении этапа происходит обработка события activate
, которое вызывается при переходе приложения из фонового режима на передний план. Очевидно, что это характерно только для macOS, поскольку только данная платформа позволяет приложению изначально выполняться в фоновом режиме. При запуске этого события функцией createWindow
еще раз создается новое окно приложения.
Поскольку вы знакомы с Node и Express, мы не будем рассматривать код после 52 строки.
Итак, у нас есть необходимый код для запуска приложения Electron и сервера Express в фоновом режиме. Но нам по-прежнему требуется фронтенд для взаимодействия с сервером.
4. Создание фронтенда
Обратите внимание, что в ранее рассмотренном коде упоминается файл index.html
. Вы, конечно, можете применять любые фронтенд-технологии на свой вкус, однако для целей данного руководства достаточно одного самого простого HTML-файла.
Скопируйте следующий код в файл index.html
, расположенный в корневом каталоге проекта.
<!DOCTYPE html>
<html lang="en">
<head>
<meta http-equiv="content-type" content="text/html; charset=utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Electron Hello World</title>
<style>
@media (prefers-color-scheme: dark) {
html {
background-color: #222222;
color: white;
}
}
</style>
</head>
<body style="font-family: Arial, Helvetica, sans-serif;">
<div>
<button id="messageButton" type='button' onclick='loadDoc()'>Get Message</button>
<h1 id='heading'></h1>
</div>
<script>
const loadDoc = () => {
var xhttp = new XMLHttpRequest();
document.getElementById('messageButton').disabled = true
xhttp.onreadystatechange = (res) => {
if (res.target.readyState == 4) {
document.getElementById('messageButton').disabled = false
}
if (res.target.readyState == 4 && res.target.status == 200) {
document.getElementById('messageButton').disabled = true
document.getElementById('heading').innerHTML = res.target.responseText;
setTimeout(() => {
document.getElementById('messageButton').disabled = false
document.getElementById('heading').innerHTML = ''
}, 5000);
}
};
xhttp.open('GET', 'http://localhost:7259/api/hello', true);
xhttp.send();
}
</script>
</body>
</html>
Здесь нет ничего особенного — обычная HTML-страница с простым стилем для взаимодействия с бэкендом. Отметим лишь, что любой HTTP-запрос, осуществляемый фронтендом, должен иметь правильный номер порта.
5. Тестирование приложения
На этом этапе приложение готово для сборки. Приступайте к генерации исполняемого файла, только убедившись, что все работает, как нужно.
Запускаем код командой npm run watch
и внимательно следим за его изменением. Затем открываем в браузере файл index.html
. Несмотря на то, что приложения Electron обычно в браузере не выполняются, по своей сути, они являются веб-приложениями, поэтому в целях удобства тестирования этот вариант вполне допустим. Electron основан на движке Chromium, в связи с чем желательно использовать Chrome, Edge или другие браузеры на базе Chromium.
Протестируйте приложение и по мере необходимости вносите любые изменения. Не забывайте обновлять окно браузера после корректировки фронтенд-кода.
Технически запуск приложения в браузере ничем не отличается от аналогичного процесса в контейнере Electron, в чем вы сами можете убедиться, запустив приложение в контейнере командой в npm run app
.
Убедившись, что все работает, переходите к следующему разделу.
6. Сборка приложения
Итак, все части пазла на своих местах, и осталось только скомпилировать приложение для конечных пользователей. Выполняем npm run transpile
, чтобы убедиться, что последняя версия кода преобразована в JavaScript. После этого с помощью npm run build
собираем окончательную выполняемую версию приложения. Обратите внимание, что таким образом вы создаете приложение для текущей версии ОС, но, изучив опции electron-packager
, вы узнаете и способы компиляции для других платформ, что позволит внести соответствующие корректировки в скрипт package.json
.
Скомпилированное приложение должно находиться в новой директории build
.
Заключение
Надеюсь, что благодаря этому руководству вы научились разрабатывать настольные приложения в Electron.
Благодарю за внимание!
Читайте также:
- Создание настольных приложений с помощью Electron. Руководство для начинающих
- Создаём «ToDo» приложение с помощью Electron
- 3 совета, как стать мастером Йода по JavaScript
Читайте нас в Telegram, VK и Яндекс.Дзен
Перевод статьи Imran Remtulla: Dive Into Electron