Около года назад на Canonium вышла статья, в которой рассказывалось о построении node-webkit приложения. Эта статья стала одной из самых популярных у меня в блоге и даже пару раз мелькала в крупных пабликах в ВК, приводя всё больше и больше читателей.

С того времени изменилось многое: от Node.js отделился io.js, node-webkit сменил имя на nw.js, GitHub выпустил стабильную версию своего редактора кода Atom, а вскоре случилось явление Electron широкой публике.

Если говорить кратко, то Electron — это альтернатива NW.js, представляющая собой среду выполнения для построения настольных приложений, написанных, разумеется, на JavaScript.

Нужно заметить, что Electron предоставляет практически равные с NW.js возможности, кроме некоторых особенностей, о которых в рамках этой серии статей я говорить не буду. Следует сказать, что Electron обладает более сдержанной и качественной документацией, которую хочется читать, в отличие от того же NW.js.

Основные отличия

Далее Electron я буду называть «Электроном».

Ни для кого не секрет, что io.js вернулся в состав Node.js, который теперь регулируется Linux Foundation. Дело в том, что io.js сыграл злую шутку с NW.js, поманив его создателя на свою сторону. Создатели Электрона тоже не оставались в стороне, но, к счастью, уже успели вернуться на Node.js и успешно его поддерживают, в то время как NW.js 12-ой и 13-ой версии всё ещё базируются на io.js, причем не первой свежести.

В NW.js главный файл — это html-файл, указанный в package.json, в то же время в Электроне главным файлом является сценарий, написанный на JavaScript. Это позволяет писать приложения, скажем так, на более низком уровне, не прибегая лишний раз к скриптам в самом браузере. На самом деле, говорить так не совсем корректно, но таким образом упрощается маршрутизация по страницам внутри приложения. Как говорится в документации, в какой-то мере Электрон здесь представляет собой подобие Node.js.

GitHub создал Atom. Вместе с Atom родился Atom Shell. Atom Shell переродился в Электрон. В этой небольшой цепочке событий есть ключевое слово — GitHub. Да, Intel поддерживает NW.js, да, NW.js медленно, но развивается. Однако, GitHub будет поддерживать свою разработку столько, сколько будет существовать редактор Atom.

На самом деле, это не все отличия Электрона от NW.js, есть и другие — более технические. С полным списком можно ознакомиться в документации проекта.

Принцип работы

Принцип работы Электрона основан на двух типах процессов:

Первый тип — основной процесс, который отвечает за интеграцию и взаимодействие с GUI операционной системы. Под этим понятием скрывается интеграция в док на OS X или панель задач на Windows, а также сворачивание в трей, полноэкранный режим и прочие обыденные и нативные для ОС штуки. Такой процесс может быть запущен только один раз на всю жизнь приложения.

Второй тип — процесс рендеринга, отвечающий за отображение окна браузера, в котором с помощью одной магической строчки может быть открыта страница приложения или любая другая веб-страница. Таких процессов может быть произвольное число. За создание процесса рендеринга отвечает основной процесс.

Для того, чтобы породить основной процесс используется следующая схема рождения приложения:

  • Электрон читает package.json и ищет в нём секцию main, в которой определен основной файл приложения. Этот файл далее в статье я буду называть «точкой входа».
  • Затем происходит обработка «точки входа» и создаётся основной процесс, который в свою очередь, при желании разработчика, открывает какую-либо страницу или страницы, то есть создаёт окно браузера. А если говорить точнее, то порождает процесс или процессы рендеринга.

Если попытаться графически изобразить жизнь приложения, построенного на Электроне, то она будет иметь следующий вид:

Electron жизненный цикл приложения

Возможности Electron

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

Для компактности статьи я вынес описание доступных API Электрона версии 0.33.3 в отдельный Gist.

Настройка окружения

Думаю, не стоит упоминать, что для работы с Электроном вам нужен Node.js и пакетный менеджер npm актуальной версии. Кроме этого, я рекомендую глобально установить пакет electron-prebuilt, который позволит автоматически загружать актуальную версию Электрона и запускать приложения без необходимости предварительной сборки.

$ npm i -g electron-prebuilt

После установки пакет автоматически загрузит текущую стабильную версию Электрона. Теперь, для того, чтобы запустить приложение, необходимо всего лишь выполнить команду electron . в директории проекта. Для последующей автоматизации процесса разработки я советую добавить эту команду в секцию скриптов в package.json:

...
"scripts": {
  "start": "electron ."
},
...

Эта запись даст возможность инициировать запуск приложения по команде npm start.

При желании можно загружать последнюю или любую другую версию Электрона вручную. Подробнее об этом написано в документации.

К слову, вы можете установить этот пакет локально, но в этом случае команда electron . преобразуется в страшный и некрасивый набор символов:

./node_modules/.bin/electron .

Кроме самого Электрона я также советую установить пакет XO, который является оберткой над ESLint:

$ npm i -g xo

ESLint является самым передовым линтером js-кода, разрабатываемый Николасом Закасом.

Настройка WebStorm

Из коробки WebStorm не умеет автоматически дополнять, предлагать методы, да и вообще работать с Electron. Чтобы исправить эту несправедливость нужно выполнить пару нехитрых операций.

Сначала необходимо перейти к настройке библиотек и фреймворков и, после этого, выбрать там наш любимый JavaScript:

File → Settings → Languages & Frameworks → JavaScript → Libraries

Нажимаем кнопку Download и выбираем слева вверху канал пакетов TypeScript community stubs. Затем ищем в большом списке всякой всячины запись github-electron, выбираем её и кликаем на кнопку Download and install. Готово!

Подсказка

Для поиска по списку кликните на любом элементе списка и начните вводить интересующее вас слово.

Первое приложение

Первым шагом в начале разработки любого Node.js приложения будет создание файла package.json. Вы можете создать его в полуавтоматическом режиме с помощью команды npm init, отвечая на вопросы, или же вручную, добавив лишь самое нужное:

{
  "name": "app",
  "version": "0.1.0",
  "main": "./main.js",
  "scripts": {
    "start": "electron ."
  }
}

Так как я ещё не решил, что мы будем разрабатывать в течении всей серии статей, пока что приложение будет иметь имя app.

Теперь самое время выполнить команду, добавляющую в package.json поддержу js-линтера XO:

$ xo --init --space=2 --env=node --env=browser --ignore=dist/**

Эта команда автоматически добавит в package.json всё самое необходимое, а также установит локальную копию XO. Остаётся лишь добавить вызов XO в скрипт start, чтобы перед запуском приложения, написанный ранее js-код, всегда оставался чистым и хорошо пахнул:

{
  "name": "app",
  "version": "0.1.0",
  "main": "./main.js",
  "scripts": {
    "start": "xo && electron .",
    "test": "xo"
  },
  "xo": {
    "space": 2,
    "rules": {
      "object-curly-spacing": [2, "always"],
      "space-before-function-paren": 0
    },
    "envs": [
      "node",
      "browser"
    ],
    "ignores": [
      "dist/**"
    ]
  },
  "devDependencies": {
    "xo": "^0.9.0"
  }
}

Я также добавил и изменил несколько правил проверки кода, чтобы линтер соответствовал моим личным требованиям и был удобен в использовании.

Самое время создать какой-нибудь простенький html-файл, который в последствии будет загружаться при старте приложения. Как мне кажется, нет ничего лучше, чем вывести на экран версию Node.js, Electron и Chrome. Для этого обратимся к модулю process, который был расширен Electron. Теперь, помимо информации о текущем процессе, он также может предоставлять информацию о типе процесса (основной или рендер), версию Chrome и Electron, а также путь до исполняемого js-файла.

<!DOCTYPE html>
<html>
  <head>
    <title>Hello World!</title>
  </head>
  <body>
    <script>
      var versions = {
        'node': process.version,
        'electron': process.versions['electron'],
        'chrome': process.versions['chrome']
      };

      document.write(`Node: ${versions.node} | Electron: ${versions.electron} | Chrome: ${versions.chrome}`);
    </script>
  </body>
</html>

Забыл сказать, что при написании приложения я буду по возможности и желанию использовать некоторые фишки из ES2015 (ES6).

Файл main.js является «точкой входа» для всего приложения. В нём будут создаваться окна и обрабатываться системные события.

Сначала необходимо подключить модуль app, отвечающий за управление жизненным циклом приложения и browser-window, создающий новое окно браузера.

var app = require('app');
var BrowserWindow = require('browser-window');
var path = require('path');

Далее создаётся ссылка на объект Window. Это делается для того, чтобы окно не закрывалось автоматически, когда объект будет обработан сборщиком мусора.

var mainWindow = null;

При закрытии всех окон приложения следует из него выйти. В OS X это событие является общим для приложений и их баров меню, поэтому здесь присутствует условие, отбрасывающее эту платформу.

app.on('window-all-closed', function() {
  if (process.platform !== 'darwin') {
    app.quit();
  }
});

После того, как Электрон полностью будет инициализирован, станет доступным API управления приложением. Ниже представлен код, который создаёт новое окно браузера размерами 800 на 600 пикселей. Затем в этом окне загружается ранее созданный нами html-файл.

Важно заметить, что даже если вы не загрузите ни один из html-файлов, процесс рендеринга всё равно будет запущен, так как ранее уже было создано окно браузера.

app.on('ready', function() {
  mainWindow = new BrowserWindow({
    width: 800,
    height: 600
  });
  mainWindow.loadUrl(path.join('file://', __dirname, '/index.html'));
  mainWindow.on('closed', function() {
    mainWindow = null;
  });
});

В конце вешается обработчик события closed, которое всплывает тогда, когда окно браузера уже было закрыто. После того, как вы получили данное событие, необходимо удалить ссылку на окно и больше его не использовать.

К слову, не стоит путать событие closed с похожим на него событием close, которое посылается, когда окно будет закрыто, то есть перед closed.

Вообще, тема событий в Электроне достойна отдельной статьи, но в силу моей лени, желающие могут обратиться к документации. Скажу лишь, что жизненный цикл приложения в событиях будет выглядеть следующим образом:

  • will-finish-launching
  • ready
  • browser-window-created
  • page-title-updated
  • close
  • before-quit
  • will-quit
  • quit
  • window-all-closed
  • closed

События, событиями, но вернемся к нашему приложению — самое время запустить его, используя команду npm start.

Electron js приложение

В итоге мы имеем окно, в котором отображается текущая версия Node.js, Electron и Chrome. Кроме того, окно приложениия имеет служебное меню с небольшим набором стандартных пунктов. Служебным оно называется из-за того, что после сборки проекта, как вы увидите позднее, оно исчезает.

Распространение приложения

В отличие от статьи про NW.js я дам самый необходимый материал сразу же в первой статье, чтобы самые активные читатели уже имели возможность начать изучать этот инструмент самостоятельно.

Для сборки приложения я предлагаю воспользоваться пакетом electron-packager. Сначала установим его глобально:

$ npm i -g electron-packager

В этот раз мы обойдемся без планировщика задач, наподобие Grunt или Gulp. Мы же взрослые люди? — думаю, да!

Просто создадим новый скрипт build в package.json с таким содержанием:

...
  "scripts": {
    "start": "xo && electron .",
    "test": "xo",
    "build": "electron-packager . myApp --platform=win32 --arch=x64 --version=0.33.3 --app-version=0.1.0 --out=dist --ignore=dist --prune"
  },
...

Теперь, выполнив команду npm run build вы получите сборку своего приложения под операционную систему Windows. Если же вам необходима сборка под все три платформы, то команда будет иметь вид:

electron-packager . myApp --all --version=0.33.3 --app-version=0.1.0 --out=dist --ignore=dist --prune

После сборки приложения под все три платформы будет создано, как это ни странно, пять директорий:

  • app-darwin-x64
  • app-linux-ia32
  • app-linux-x64
  • app-win32-ia32
  • app-win32-x64

Но самое забавное, что полностью собранный «комплект» весит всего лишь каких-то жалких 500Мб.

Кроме явного указания имени приложения и версии Электрона в команде, можно воспользоваться данными из package.json. Создадим два поля, которые будут содержать имя публикуемого приложения и версию, с помощью которой необходимо будет собрать его:

{
  ...
  "publishName": "myApp",
  "electronVersion": "0.33.3",
  ...
}

После этого в команде станут доступны переменные:

  • $npm_package_publishName
  • $npm_package_electronVersion

Разумеется, что вам никто не запрещает использовать поле name как имя приложения:

  • $npm_package_name
  • $npm_package_version

В итоге команда может принять вид:

$ electron-packager . $npm_package_publishName --all --version=$npm_package_electronVersion --app-version=$npm_package_version --out=dist --ignore=dist --prune

К сожалению, на Windows я так и не смог заставить electron-packager использовать $npm_package_*. Зато на OS X это замечательно работает.

Сейчас я предлагаю немного подробнее остановиться на аргументах, передаваемых в пакет electron-packager. Шаблон команды выглядит следующим образом:

$ electron-packager <sourcedir> <appname> --platform=<platform> --arch=<arch> --version=<version>

Доступные для пользователя аргументы:

Обязательные

  • platform — платформа (all или win32, linux, darwin)
  • arch — разрядность (all или ia32, x64)
  • version — версия Электрона для сборки

Опциональные

  • all — эквивалент --platform=all --arch=all
  • out — директория, в которую будут помещены сборки
  • icon — иконка приложения (.icns или .ico)
  • app-bundle-id — идентификатор приложения в plist
  • app-version — версия приложения
  • build-version — версия сборки приложения для OS X
  • cache — директория, в которой будет располагаться кэш приложения
  • helper-bundle-id — идентификатор приложения для помощника plist
  • ignore — исключение файлов из сборки
  • prune — запуск команды npm prune --production в приложении
  • overwrite — перезапись уже созданных сборок
  • asar — упаковка исходников приложения в asar-архив
  • asar-unpack — распаковка указанных файлов в директорию app.asar.unpacked
  • sign — идентификатор для входа в codesign (OS X)
  • version-string — ключи для сборки (Windows). Список ключей смотрите в документации пакета

О том, как добавлять иконки, что вообще следует делать перед распространением приложения и как создать установщик для сборки, я расскажу в заключительной части этой серии статей.

Список литературы

По традиции оставляю ссылки на то, что можно почитать, чтобы расширить свой кругозор в рамках этой статьи. На русском языке ничего нет, так как у нас Электрон как-то не особо популярен.

Статьи

Модули