Собираем грабли Electron.js или десктопные JS-приложения на практике

от автора

image

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

Нужно отметить, что разработка на Электроне очень во многом отличается от обычного браузерно-серверного приложения на Node. О чем и будет эта статья.

За подробным введением в Электрон добро пожаловать сюда и туда.

Около полугода назад я узнал об Электроне. Тогда я реализовал простенький платформер на базе Phaser. Js, и почувствовал полный восторг от того насколько быстро и удобно эта платформа позволяет создавать приложения. Впрочем, этот опыт был отложен до лучших времен.

Тогда мне пришлось работать лишь с Canvas, а вся основная логика создавалась средствами Phaser и подключением скриптов. Никакой особой архитектуры приложению не требовалось. Однако неделю назад возникла потребность в небольшой десктопной утилите, и выбор пал на Электрон как на платформу для разработки. И тут заверте…

Да, для тех кто заинтересуется разработкой чего-нибудь играбельного на Электроне: WebGL приложение работает быстрее чем в обычном браузере. Как ни странно, хуже всех себя показывает Chrome, на котором Электрон и основан. Впрочем, разница не намного велика.

Вот график по кадрам в секунду (бенчмарком был этот пример, с отключенным удалением объектов):

image

Что ж, вперед. Попробуем написать что-нибудь толковое.

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

Как я говорил, Electron — это не то же самое что Chrome и отвечающий ему локально запущенный Node.

Если уходить чуть глубже в строение Электрона, то это скорее обрезанный по части браузерных особенностей Chromium и встроенный в него Node.js. (не путать с «голым» V8 в обычной поставке браузера).

image

Выполнение приложения начинается с такого кода:

//main.js //Основная конфигуация для старта приложения const electron = require('electron');  const app = electron.app;  const BrowserWindow = electron.BrowserWindow;  let mainWindow;   function createWindow () {   // Create the browser window.   mainWindow = new BrowserWindow({     width: 800,     height: 600,     //fullscreen:true,     frame:false,     resizable:false     }); //основная конфигуация     mainWindow.loadURL('file://' + __dirname + '/views_html/startscreen/index.html'); //загрузка html файла      mainWindow.on('closed', function() {     mainWindow = null;   }); } //закрытие главного окна  app.on('ready', createWindow); //создание окна при готовности приложения  app.on('window-all-closed', function () {   if (process.platform !== 'darwin') {     app.quit();   } });  //закрытие окна и сворачивание в док если это OS X  app.on('activate', function () { .   if (mainWindow === null) {     createWindow();    } }); //восстановление окна 

Более подробный разбор по ссылкам в начале статьи.

Этот код выполняется в Node, со всеми полагающимися недостатками и преимуществами. На основе его работы порождается окно с WebKit и средой для выполнения JavaScript. Средой является тот же самый Node, запустивший приложение, однако инициализирующий код и код вашего приложения между собой взаимодействовать не смогут.

Проще говоря, main.js создает конфигуацию окна и отвечает за интеграцию приложения в систему. Index.html — это точка входа уже непосредственно в разработанное Вами приложение. Взаимодействовать между собой напрямую они не могут. Передать переменную из main в скрипт подключенный к загруженной html не получится, несмотря на то что они выполняются друг за другом на одной виртуальной машине.

Но не напрямую возможно. Существует целых два способа передачи данных из инициализиующего кода в само приложение. Первый довольно очевиден, регистрация глобального объекта. Одним из таких является process, и он содержит в себе всю информацию о среде в которой приложение выполняется:

!DOCTYPE html> <html>   <head>     <meta charset="UTF-8">     <title>Hello World!</title>   </head>   <body>     <h1>Hello World!</h1>     Мы используем Node <script>document.write(process.versions.node)</script>,     Chrome <script>document.write(process.versions.chrome)</script>,     и Electron <script>document.write(process.versions.electron)</script>.   </body> </html> 

Выполнение данного кода выведет на экран версию Node, Cromium и Electron.

Естественно можно добавить свой объект и выводить таким же образом.

И второй способ. Выполнение кода внутри Вашего приложения, своеобразный RPC. Это будет выглядеть так:

  mainWindow.webContents.executeJavaScript(                    //указываем окно где скрипт должен выполниться   `alert("Hello from runtime!");`);                                              //код на выполнение  

Здесь стоит учитывать, что:

Код, указанный в executeJavaScript(), выполнится только после загрузки страницы. Поэтому нельзя таким образом инициализировать переменные критичные для старта приложения. Есть, конечно, вариант и само приложение таким образом запускать, обернув стартовый скрипт в функцию и:

  mainWindow.webContents.executeJavaScript(                    //указываем окно где скрипт должен выполниться   `alert("Hello from runtime!");`;   //код на выполнение    start_up();  //Пуск     );                                              

Это явно не лучшее решение.

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

Однако я недаром упомянул что «браузерная» часть приложения также выполняется внутри Node, следовательно вы имеете полное право использовать модули, как установленные при помощи npm:

npm install backbone npm install jquery 

var Backbone = require('backbone'); var $ = require('jquery'); 

Так и подключать собственные части приложения при помощи:

require("path_to_file"); 

Но здесь поджидает гигантский подводный камень.

При обычной разработке на Node.js мы можем подключить файл таким образом:

require("./app"); 

Это код укажет приложению что нужно включить файл app лежащий в той же директории что и выполняющийся скрипт. Но в «браузерной» части Электрона не работают пути, которые начинаются со слэша или «./».

По умолчанию он ищет в папке node_modules в корне приложения, и если вы укажете:

require("modules/app"); //сработает при условии что папка modules находится в node_modules  require("/modules/app"); // => Error  require("./modules/app"); // => Error, даже если папка рядом 

В тоже время:

 require("modules/../app"); // если app лежит в node_modules 

отработает как надо.

Будьте внимательны.

Естественно, меня такое положение не устроило. Раскидывать приложение по папочкам в директории для модулей рантайма решительно не хотелось. Я вышел из ситуации так:

function require_file(file) {   require("modules/../../js/app/modules/"+file); //вместо modules может быть абсолютно любая папка в node_modules } 

Костыль? Однозначно. Зато теперь я могу включать необходимые мне части приложения указав:

require_file("show"); //где show - необходимый модуль 

Вернемся к нашей структуре. Я выбрал такой подход:

<!-- index.html --> <script type="text/javascript" src="../../js/app/system_init.js"></script> 

Файл system_init единственный подключаемый скрипт в html-файле.

В нем находится:

var Backbone = require('backbone'); //инициализация библиотек var $ = require('jquery'); 

Ряд других функций-хелперов вроде загрузки файлов конфигурации и запуск приложения:

 var router = new APP.SystemRouter({       menus: new APP.SystemCollection()     }); 

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

require_file("/path/module");  

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

ссылка на оригинал статьи https://habrahabr.ru/post/278951/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *