СоХабр закрыт.

С 13.05.2019 изменения постов больше не отслеживаются, и новые посты не сохраняются.

H Создание десктопного приложения с помощью Webix и Electron в черновиках Из песочницы Tutorial

Статья представляет собой пошаговое описание моего опыта создания кроссплатформенного десктопного приложения с помощью Webix, Electron и Node.js.


image

Однажды мне пришла в голову светлая мысль создать десктопное приложение на базе стэка веб-технологий, который мне хорошо знаком. Знаю, что программисты, пишущие под десктоп, обычно используют C++, Java, C#, а на стэк веб-технологий для этих целей смотрят свысока. Но, поскольку я писал приложение для себя, то справедливо решил, что использование знакомых инструментов ускорит процесс. Ну и конечно захотелось «скрестить ужа с ежом» и посмотреть что получится. Если вкратце, то получившийся результат можно запускать и как обычное веб-приложение, и как десктоп.

Код приложения можно скачать с GitHub.

Что будет делать наше приложение… Это TODO-list (а как же иначе...), в который мы сможем добавлять события, редактировать их и удалять. Событие будет иметь заголовок, содержание, место проведения, дату и приоритет. Также будет доступна возможность перевода интерфейса на русский и английский языки. Назовем его «Data master».

Для создания веб-приложения я использовал Webix. Он представляет собой кроссплатформенную и кроссбраузерную UI библиотеку, использующие компоненты для быстрого построения приложения с использованием JavaScript синтаксиса. Для компиляции веб-приложения в десктоп использовался Electron. Это кроссплатформенный инструмент, работающий на базе Node.js и позволяющий компилировать веб-приложение для запуска на различных платформах различной разрядности: Windows, Linux, Mac. Для всяких вспомогательных вещей используются инструменты на базе Node.js.

Начнем со структуры папок. В корне проекта я создал ее в таком виде:

  • css — стили
  • data — бэкенд
  • img — изображения
  • js — скрипты JavaScript

После установки модулей Node.js добавится папка «node_modules», для Webix будет использоваться папка «codebase», в папке "~/release/DataMaster" будут версии десктопного приложения для различных платформ.

В итоге структура проекта у меня получилась такая:
image


Корневая папка проекта должна быть расположена на сервере. В моем случае это Apache.
Итак, для начала я зашел на страницу загрузки Webix и нажал «Скачать Webix Standard». Это бесплатная версия библиотеки (лицензия «GNU GPLV3»), которая вполне подойдет для наших нужд. Имеется еще коммерческая версия «Webix PRO», которая отличается главным образом расширенной библиотекой виджетов, а также возможностями техподдержки. Из полученного архива «webix.zip» копируем папку «codebase» в корень нашего проекта. Внутри папки «codebase» обратите внимание на файлы webix.js и webix.css. Подключение этих файлов в приложении позволяет работать с Webix. В папке «skins» содержатся css-файлы с темами.

Создадим в корне проекта файл index.html.
index.html
<!DOCTYPE HTML>
<html>
    <head>
    	<link rel="stylesheet" href="codebase/skins/contrast.css" type="text/css">
    	<link rel="stylesheet" href="css/main.css" type="text/css">
    	<script src="codebase/webix.js" type="text/javascript"></script>
    	<script src="codebase/i18n/en.js" type="text/javascript"></script>
    	<script src="codebase/i18n/ru.js" type="text/javascript"></script>
    </head>
    <body>
    	<script src="bundle.js" type="text/javascript"></script>
    </body>
</html>


Добавим webix.js. Подключение webix.css дает нам возможность использовать стандартную тему. Я же решил подключить симпатичную темненькую тему, которая лежит в «codebase/skins/contrast.css». Также мы подключили файлы из папки «codebase/i18n» для использования встроенной возможности локализации Webix. В индексного файла подключаем файл «bundle.js». Там будет находиться сборка всего нашего js-кода. Для сборки нам понадобится Node.js и Gulp.

Если у вас еще не установлена Node.js, то сделать это можно отсюда. Командами $ node -v и $ npm -v проверьте корректность установки Node.js и пакетного менеджера платформы — NPM.

Теперь в папке «js» мы будем создавать основную логику приложения. Файл internalization.js содержит объект для интернационализации интерфейса приложения. По аналогии с уже имеющимися языками (русский, английский) вы можете добавить туда другие языки в случае необходимости.
internalization.js
var translations = {
	// English
	"en-US": {
		localeName: "en-US",
		headerTitle: "Data master",
		resetFilters: "Reset filters",
		changeLocale: "Change language:",
		loadData: "Load data",
		addRow: "Add row",
		clearSelection: "Clear selection",
		deleteRow: "Delete row",
		saveData: "Save data",
		title: "Title",
		noItemSelected: "No item selected",
		dataSaved: "Data saved",
		reservedButton: "Reserved botton"
	},

	// Russian
	"ru-RU": {
		localeName: "ru-RU",
		headerTitle: "Мастер данных",
		resetFilters: "Сбросить фильтры",
		changeLocale: "Сменить язык:",
		loadData: "Загрузить данные",
		addRow: "Добавить ряд",
		clearSelection: "Снять выделение",
		deleteRow: "Удалить ряд",
		saveData: "Сохранить",
		title: "Название",
		noItemSelected: "Нет выбранных рядов",
		dataSaved: "Данные сохранены",
		reservedButton: "Зарезервировано..."
	}
};


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

var defaultLocale = "en-US";

// object from translations.js
var localizator = translations[defaultLocale];

/**
 * Get data from backend and fill datatable grid
 */
function getData() {
    $$("dataFromBackend").clearAll();
    $$("dataFromBackend").load("http://localhost/data_master/data/data.php");
}

/**
 * Add new row to datatable
 */
function addRow() {
    $$("dataFromBackend").add(
        {
            title: "-----",
            content: "-----",
            place: "-----"
            //date: "-----",
            //priority: "-----"
        }
    );
}

/**
 * Reset selection in datatable grid
 */
function clearSelection() {
    $$("dataFromBackend").unselectAll();
}

/**
 * Delete selected row
 */
function deleteRow() {
    if (!$$("dataFromBackend").getSelectedId()) {
        webix.alert(localizator.noItemSelected);
        return;
    }

    //removes the selected item
    $$("dataFromBackend").remove($$("dataFromBackend").getSelectedId());
}

/**
 * Save data to backend from datatable grid
 */
function saveData() {
    var grid = $$("dataFromBackend");
    var serializedData = grid.serialize();
    webix.ajax().post("http://localhost/data_master/data/save.php", {data: serializedData});
    webix.alert(localizator.dataSaved);
}

/**
 * Reset filters settings
 */
function resetFilters() {
    $$("dataFromBackend").getFilter("title").value = null;
    $$("dataFromBackend").getFilter("content").value = null;
    $$("dataFromBackend").getFilter("place").value = null;
    $$("dataFromBackend").getFilter("date").value = null;
    $$("dataFromBackend").getFilter("priority").value = null;
    
    // reload grid
    $$("dataFromBackend").clearAll();
    $$("dataFromBackend").load("http://localhost/data_master/data/data.php"); 
}

/**
 * Change translation to selected
 */
function changeLocale(locale) {
    localizator = translations[locale];
    
    $$("headerContainer").define("template", localizator.headerTitle);
    $$("headerContainer").refresh();

    $$("resetFiltersContainer").define("value", localizator.resetFilters);
    $$("resetFiltersContainer").refresh();

    $$("changeLocale").define("label", localizator.changeLocale);
    $$("changeLocale").refresh();

    $$("loadData").define("value", localizator.loadData);
    $$("loadData").refresh();

    $$("addRow").define("value", localizator.addRow);
    $$("addRow").refresh();

    $$("clearSelection").define("value", localizator.clearSelection);
    $$("clearSelection").refresh();

    $$("deleteRow").define("value", localizator.deleteRow);
    $$("deleteRow").refresh();

    $$("saveData").define("value", localizator.saveData);
    $$("saveData").refresh();

    $$("reservedButton").define("value", localizator.reservedButton);
    $$("reservedButton").refresh();

    webix.i18n.setLocale(locale);
}

/**
 * Function for reserved button
 */
function reservedButton() {
    // your code...
}


Большинство функций являются обработчиками события «onclick» кнопок. Код функций в основном представляет собой способы работы с Webix-элементами. В общих чертах он интуитивно понятен, если нужна более подродная информация — добро пожаловать на страницу документации Webix.

В файле objects.js планировалось хранить функции-конструкторы, которые являются обертками над стандартными компонентами Webix. Я планировал поместить туда часто используемые в приложении виджеты, но ограничился лишь одним — наиболее повторяющимся — элементом Button. Чуть ниже я поясню его использование.

objects.js

/**
 * Create object with type "Button"
 *
 * @constructor
 */
function Button(id, value, type, width, onClickFunction) {
	this.view = "button";
	this.id = id;
	this.value = value;
	this.type = type;
	this.width = width;
	this.on = {
		"onItemClick": function(){ 
	      onClickFunction();
	    }
	}
}


Как это работает… В метод webix.ui() передается объект, имеющий многоуровневую структуру. Свойство view определяет тип виджета Webix: в нашем случае «layout». Этих типов очень много, каждый из них имеет свои методы и свойства. Кроме того, мы можем расширять стандартные компоненты Webix с помощью метода webix.protoUI(), добавляя или переопределяя необходимую нам функциональность. Как видите, работа с Webix осуществляется с помощью Javascript, поэтому весь код работы с этой библиотекой мы помещаем в теги <script>. В методе webix.ui() мы задали последовательность из рядов и колонок, некоторые из которых, в свою очередь, имеют вложенные ряды и колонки, образуя сетку, параметры элементов которой мы можем задать, например, с помощью свойств «width» и «height». В колонки и ряды мы «вкладываем» элементы, настраивая их. Например, вот так можно определить кнопку:

{
	view: "button", 
	id: "loadData", 
	value: "Load data", 
	type: "form", 
	width: 150,
	on: {
	    "onItemClick": function(id, e, trg){ 
	      getData();
	    }
}


Свойство «id» — это свойство Webix «view_id», через которое мы можем получить доступ к элементу с помощью метода $$(). Например, $$(«loadData») вернет нам объект кнопки, описанной в коде выше. Свойство «value» определяет надпись на кнопке, «type» — тип, «width» — ширину. В объекте «on» можно задать обработчики событий для элемента. В примере выше — он один («onItemClick») и соответствует событию «onclick», которое вызывает функцию getData().

Вместо описанной выше структуры для создания элемента Button (в файле «objects.js») я использовал функцию-конструктор. Она создает и возвращает объект Button в соответствии с переданными параметрами. Это позволяет устранить дублирование кода и создавать объект таким образом: new Button(«loadData», «Load data», «form», 150, getData). Кстати, я добавил зарезервированную кнопку для лучшего UX в скомпилированном приложении. Функциональности для нее я не придумал, поэтому можете использовать ее, как вам вздумается.

В нижней части файла components.js имеется код вида $$(«buttonContainer»).define(«css», «buttonContainerClass»). Таким способом мы можем определять и изменять свойства элементов (в примере: добавление атрибута класс со значением «buttonContainerClass»). Способ, приведенный здесь, указан для наглядности. Мы можем изначально инициализировать объект каким либо классом, присвоив значение свойству «css».

Webix имеет различные способы загрузки данных в приложение и в отдельные элементы. В функции getData() я использовал метод load() для загрузки данных в грид. Метод убращается к нашему бэкенду по URL «data/data.php».

Бэкенд нашего приложения до неприличия прост. Я решил не использовать базы данных для такого маленького приложения. Данные хранятся в файле data.json, читаются оттуда с помощью data.php, и сохраняются туда же с помощью save.php.
data.php
<?php
$dataFromFile = json_decode(file_get_contents("data.json"));
echo json_encode($dataFromFile);
/*$example_json_data = array(
  array (title => "My Fair Lady", year => 1964, votes => 533848, rating => 8.9, rank => 5),
  array (title => "Film 1", year => 1984, votes => 933848, rating => 6.9, rank => 4),
  array (title => "Film 2", year => 1966, votes => 53848, rating => 4.3, rank => 5),
  array (title => "Film 3", year => 1975, votes => 567848, rating => 2.9, rank => 2),
  array (title => "Film 4", year => 1981, votes => 433788, rating => 6.3, rank => 1)
);*/
//echo json_encode($example_json_data);


save.php
<?php
$data = $_POST["data"];
file_put_contents("data.json", $data);


В коммерческом проекте, конечно, следовало бы сделать различные проверки данных и обработку ошибок, но для наглядности я их опустил. В файл data-example.json я поместил образец структуры данных для загрузки в Webix элемент «datatable», взятый с сайта документации.
data-example.json
[
  {"title":"My Fair Lady", "year":1964, "votes":533848, "rating":8.9, "rank":5},
  {"title":"Film 1", "year":1984, "votes":933848, "rating":6.9, "rank":4},
  {"title":"Film 2", "year":1966, "votes":53848, "rating":4.3, "rank":5},
  {"title":"Film 3", "year":1975, "votes":567848, "rating":2.9, "rank":2},
  {"title":"Film 4", "year":1981, "votes":433788, "rating":6.3, "rank":1}
]


Сохранение данных осуществляется в функции saveData() с помощью AJAX-метода webix.ajax().post(), которому передается URL на бэкенде и объект с данными. Вообще Webix может работать с данными по-разному, принимая и отдавая, например, json или xml. Кстати, в скачанном архиве с версией Webix, кроме папки codebase есть папка samples, в которой можно глянуть примеры работы с различными компонентами системы. В папке «samples/common/connector» есть «родная» основа для работы с бэкендом.

Таким образом, в общих чертах работа нашего приложения выполняется так… Создается сетка с рядами и колонками, в которые помещаются элементы. При взаимодействии с элементами происходят события, и выполняются обработчики, определенные для этих событий. Некоторые из обработчиков используют методы для общения с бэкендом для получения и сохранения данных. Итого мы имеем SPA-приложение, где получение и обработка данных не требуют перезагрузки страницы. Перевод интерфейса приложения осуществляется за счет взятия свойств объекта translations в соответствии с выбранной локалью, задания нового значения свойств «value» элементов и обновления этих элементов. Логика висит на событии «onChange» комбобокса и вызывает нашу функцию changeLocale(). В этой функции мы, кстати, встроенный метод webix.i18n.setLocale(locale), куда передаем локаль из комбобокса. Подробнее можно глянуть здесь.

Затем нам нужно собрать весь js код в бандл. Но сначала проделаем небольшую подготовительную работу. Создадим в корне проекта файл package.json с основными настройками приложения.
package.json
{
    "name": "data_master",
    "description": "Simple ToDo list with desktop building",
    "version": "0.0.1",
    "homepage": "https://github.com/paratagas/data_master",
    "repository": {
        "type": "git",
        "url": "git+https://github.com/paratagas/data_master.git"
    },
    "author": {
        "name": "Yauheni Svirydzenka",
        "email": "partagas@mail.ru",
        "url": "https://github.com/paratagas"
    },
    "tags": [
        "node.js",
        "webix",
        "electron",
        "ToDo list"
    ],
    "main": "main.js",
    "scripts": {
        "start": "electron .",
        "package": "electron-packager ./ DataMaster --all --out ~/release/DataMaster --overwrite"
    },
    "dependencies": {
        "electron-prebuilt": "^0.35.6",
        "electron-packager": "^8.4.0"
    },
    "devDependencies": {
        "gulp": "^3.9.0",
        "gulp-concat": "^2.6.0",
        "gulp-uglify": "^1.2.0",
        "gulp-sourcemaps": "^1.5.2"
    },
    "license": "GPL-3.0"
}


Затем выполним команду $ npm install для загрузки необходимых компонентов. В файле gulpfile.js в корне проекта зададим настройки нашей сборки.

gulpfile.js

var gulp = require('gulp'),
    uglify = require('gulp-uglify'),
    concat = require('gulp-concat');
    // to create source mapping
    sourcemaps = require('gulp-sourcemaps');

/*
 * Collect all js files to one bundle script
 * Command: "gulp bundle"
 */
gulp.task('bundle', function() {
    // choose any files in directories and it's subfolders
    return gulp.src('js/**/*.js')
        .pipe(sourcemaps.init())
        .pipe(concat('bundle.js'))
        .pipe(sourcemaps.write('./'))
        //.pipe(uglify())
        // output result to current directory
        .pipe(gulp.dest('./'));
});

/*
 * Watch js files changing and run task
 * Command: "gulp watch"
 */
gulp.task('watch', function () {
	gulp.watch('./js/**/*.js', ['bundle']);
});


Я закомментировал выполнение минификации, чтобы можно было посмотреть как в итоге выглядит bindle.js со всем нашим кодом. Кроме того, я не использовал минификацию CSS, так как у нас только один файл с небольшим количеством стилей. Вы можете изменить это поведение, если захотите. Теперь мы можем собрать проект, выполнив команду $ gulp bundle в корне проекта. В процессе разработки команда $ gulp watch позволяет отслеживать изменения js файлов и при наличии таковых выполнять команду $ gulp bundle.

Наше веб-приложение уже готово и мы можем запустить его на рабочем сервере. У меня получилось что-то вроде:
image

Теперь давайте сделаем из него десктоп с помощью Electron. Выбрать и скачать свежую версию можно здесь. Внутри страницы каждого релиза есть список версий для различных платформ. В нашем «package.json» определены два модуля, которые позволят нам сделать основную работу. Модуль «electron-prebuilt» отвечает за предварительную сборку и запуск приложения. Отдельно модуль можно установить командой $ npm install --save-dev electron-prebuilt. В свою очередь, модуль «electron-packager» позволяет компилировать приложения для целевой платформы или для всех возможных платформ. Отдельно устанавливается командой $ npm install --save-dev electron-packager.

Обратите внимание на секцию:
"scripts": {
	"start": "electron .",
	"package": "electron-packager ./ DataMaster --all --out ~/release/DataMaster --overwrite"
},


Определив ее, вы можем запускать предсборку приложения командой $ npm start, а компиляцию — командой $ npm run-script package. Кстати, если мы изменим команду package, например, на "package": "electron-packager ./ DataMaster --win32-x64 --out ~/release/DataMaster --overwrite" то приложение будет скомпилировано для целевой платформы — в нашем случае Windows x64. На данный момент Electron поддерживает платформы: Windows x32/x64, Linux x32/x64/armv7, OS X/x64. Для более полного понимания можно глянуть документацию.

Создадим в корне проекта файл main.js. Он нужен для настроек Electron.
main.js

/*
 * Commands:
 * npm init - initialize npm in current directory
 * npm install - install modules
 * npm install --save-dev electron-prebuilt - install module for pred-build
 * npm install --save-dev electron-packager - install module for build
 * npm start - to start app
 * npm run-script package - to compile app
 */

const electron = require('electron');
// lifecycle of our app
const app = electron.app;
// create window for our app
const BrowserWindow = electron.BrowserWindow;

// To send crash reports to Electron support
// electron.crashReporter.start();

// set global link
// if not, the window will be closed after garbage collection
var mainWindow = null;

/**
 * Check that all windows are closed before quiting app
 */
app.on('window-all-closed', function() {
    // OS X apps are active before "Cmd + Q" command. Close app
    if (process.platform != 'darwin') {
        app.quit();
    }
});

/**
 * Create main window menu
 */
function createMenu() {
    var Menu = electron.Menu;
    var menuTemplate = [
        {
            label: 'File',
            submenu: [
                {
                    label: 'New window',
                    click: function() {
                        createSubWindow();
                    }
                },
                {type: "separator"},
                {
                    label: 'Exit',
                    click: function() {
                        app.quit();
                    }
                }
            ]
        },
        {
            label: 'Edit',
            submenu: [
                {
                    label: 'Cut',
                    role: 'cut'
                },
                {
                    label: 'Copy',
                    role: 'copy'
                },
                {
                    label: 'Paste',
                    role: 'paste'
                }
            ]
        },
        {
            label: 'About',
            submenu: [
                {
                    label: 'Name',
                    click: function() {
                        console.log(app.getName());
                    }
                },
                {
                    label: 'Version',
                    click: function() {
                        console.log(app.getVersion());
                    }
                },
                {
                    label: 'About',
                    click: function() {
                        console.log('ToDo list');
                    }
                }
            ]
        },
        {
            label: 'Help',
            submenu: [
                {
                    label: 'Node.js docs',
                    click: function() {
                        require('electron').shell.openExternal("https://nodejs.org/api/");
                    }
                },
                {
                    label: 'Webix docs',
                    click: function() {
                        require('electron').shell.openExternal("http://docs.webix.com/");
                    }
                },
                {
                    label: 'Electron docs',
                    click: function() {
                        require('electron').shell.openExternal("http://electron.atom.io/docs/all");
                    }
                }
            ]
        }
    ];

    var menuItems = Menu.buildFromTemplate(menuTemplate);
    Menu.setApplicationMenu(menuItems);
}

/**
 * Create main window
 */
function createMainWindow() {
    mainWindow = new BrowserWindow({
        title: "Data master",
        resizable: false,
        width: 910,
        height: 800,
        // set path to icon for compiled app
        icon: 'resources/app/img/icon.png',
        // set path to icon for launched app
        //icon: 'img/icon.png'
        center: true
        // to open dev console: The first way
        //devTools: true
    });

    createMenu();

    // load entry point for desktop app
    mainWindow.loadURL('file://' + __dirname + '/index.html');
    
    // to open dev console: The second way
    //mainWindow.webContents.openDevTools();

    // Close all windows when main window is closed
    mainWindow.on('closed', function() {
        mainWindow = null;
        newWindow = null;
    });
}

/**
 * Create sub menu window
 */
function createSubWindow() {
    newWindow = new BrowserWindow({
        title: "Go to GitHub",
        resizable: false,
        // imitate mobile device
        width: 360,
        height: 640,
        icon: 'resources/app/img/mobile.png',
        center: true
    });
    
    newWindow.loadURL("https://github.com/");

    newWindow.on('closed', function() {
        newWindow = null;
    });
}

/**
 * When Electron finish initialization and is ready to create browser window
 */
app.on('ready', function() {
    createMainWindow();
});


В комментариях в файле описывается назначение некоторых шагов. В общих чертах мы создаем объект electron, затем окно приложения, после чего настраиваем его. После этого в окно передается основной URL приложения, например, так: mainWindow.loadURL('file://' + __dirname + '/index.html'). В нашем случае это файл «index.html» в корне проекта. В конце выражением mainWindow = null удаляем ссылку на окно, так как если приложение поддерживает несколько окон, то нужно ловить момент когда следует удалить соответствующий элемент. Закрытие основного окна приложения в нашем случае закрывает (присваивает null) дочернее окно. В настройках также можно задать иконку готового десктоп-приложения. Для этого указываем icon: 'resources/app/img/icon.png', где «resources/app» — место, где хранится исходный код в уже скомпилированном варианте приложения.

Electron также позволяет создавать кастомизированное меню окон приложения. В демонстрационных целях я добавил несколько пунктов меню, чтобы показать, как это делается. Хорошая инфа на эту тему есть вот тут и в официальной документации. В пункте меню File > New window я добавил новое окно. Оно имитирует просмотр контента на мобильном устройстве и открывает страницу GitHub. Можно задать стартовый URL для нового окна и в нашем веб-приложении, создав таким образом еще одну точка входа, если, например, требуется обособить какой-либо функционал.

В режиме разработки можно активировать Chrome Dev Tools. В комментариях файла «main.js» указана пара способов сделать это.
Выполняем команду $ npm run-script package и в "~/release/DataMaster" появляются готовые приложения под различные платформы.
С открытым дополнительным окном вид такой:
image

В итоге у нас получилось вполне работоспособное приложение, которое может кому-нибудь пригодиться. Код проекта не претендует на лучшие практики разработки (хотя я и старался), но, возможно, кому-то покажутся интересными использованные технологии и их взаимодействие. Собственно, для этого я и написал эту статью. Ведь именно из таких вот статей на Хабре я в свое время узнал об этих инструментах и теперь с удовольствием их использую. Отмечу, что в приложении используется лишь небольшая часть возможностей Webix и Electron. На самом деле эти инструменты обладают довольно обширным функционалом, владение которым позволяет создавать солидные кроссплатформенные приложения.

комментарии (34)

–1
+3 –4
vlreshet ,  
Таких постов на хабре (не с webix, правда, но тут не суть) уже с десяток. Зачем ещё один? Что принципиально нового можно узнать?
0
+2 –2
paratagas ,  
То, что именно эти инструменты можно использовать друг с другом — на примере конкретного рабочего решения.
+1
vlreshet ,  
Webix — JS библиотека. Electron использует самый обычный js движок от хрома (кое-чего нового добавлено, но стандартные апи идентичные) + HTML5, который и в африке HTML5. Следовательно, всё что работает в обычном хром браузере — всё будет работать и в електроне (но не наоборот). В чём тогда суть?
+4
+5 –1
paratagas ,   * (был изменён)
Суть в том, что в теории так оно и есть. Но на практике всегда появляются какие-то подводные камни и нюансы, на которых можно споткнуться. Например, даже такая простая вещь, как задание иконки для приложения Electron может вызвать трудности, так как в документации не указано, как одновременно задать иконку для запускаемого под Node.js приложения и скомпилированного приложения. В целом я насчитал четыре статьи про создание приложений на Electron на Хабре за последние 2 года. Не вижу ничего плохого, в том, что я поделился своим опытом работы с ним. API развивается, версии становятся более свежими, появляются новые возможности. Кроме того, возможно, кто-то просто пропустил предыдущие статьи, а из более свежих сможет узнать, что такие инструменты существуют, попробовать их и, возможно, использовать.
0
hardex ,  
Не указано, потому что компиляция — прерогатива electron-packager/electron-builder — разработок третьей стороны — а не электорна.
0
MrGobus ,  
Вы говорите как человек прочитавший и помнящий весь хабр за все года =)
В целом неплохой пост, я вот про Webix узнал (знал раньше но сейчас задумался о его пользе), да и даблы постов это неплохо если их разделяет определенный период времени. Кто-то освежит информацию, а кто-то откроет для себя что-то новое, не все же старые посты читают.
0
Zenitchik ,  
Тот, кто будет гуглить webix найдёт старые и новые посты с равной вероятностью.
0
paratagas ,  
Да, если человек знает, что искать нужно библиотеку с таким названием. Я, например, узнал о существовании Electron из комментариев к статье на Хабре про какой-то из аналогичных инструментов (точно не помню, скорее всего это NW.js).
0
Zenitchik ,  
В любом случае, если человек захочет разобраться в вопросе, он за полдня нагуглит очень многое. И среди этого многого будут посты с хабра за четыре последних года.
+3
+4 –1
max1gu ,  
скажите, сколько памяти занимает ваше трехстрочное приложение поле получаса работы?
Сам попробовал электрон, но на второй день снес — дешевле в пкстую форму приложения на дельфи7 поставить браузер на всю форму, а страницы и всё остальное зашить в ресурсы (ИЕ он и так умеет)
Дешево и сердито.
0
+1 –1
paratagas ,  
Честно говоря, не замерял. Но каких-то особых тормозов не заметил. Хотя приложение небольшое. Насчет Delphi (равно, как и других традиционных инструментов для десктопа) согласен с Вами. Но суть конкретно это приложения — больше эксперимент — освоение новых возможностей платформы Node.js. Отмечу, что в скомпилированном приложении под Win x64 один только exe-шник имеет размер около 70 Мб. Аналогичный exe-шник, сделанный, например, с помощью Windows Forms и C#, точно имел бы в разы меньший объем.
+5
paratagas ,  
Стало любопытно и я замерил (для Windows 7 x64).
Для скомпилированного приложения:
Сразу после запуска — около 26Мб, после получаса работы — около 28Мб. Разница между активным режимом (изменение данных, открытие окон и ссылок на документацию) и пассивным режимом — около 1Мб.
Для приложения, запускаемого с помощью
$ npm start
все показатели примерно на 1Мб меньше
+4
paratagas ,  
Прошу прощения — не сразу заметил все процессы приложения. Итого результат таков: основное приложение — 3 процесса примерно по 26Мб (всего около 80Мб). Плюс еще примерно по 35Мб на каждое дополнительно открытое окно Electron из меню.
0
+3 –3
herr_kaizer ,  
Жесть.
0
vSLY ,  
Спасибо за статью!
А подскажите пожалуйста, если я захочу затем портировать это приложение на мобильные устройства (желательно в виде исполняемого файла со страницей в google play/app store) какими инструментами можно воспользоваться, чтобы не менять код сильно?
Стоит задача кросс-платформенной разработки из одного исходника, пока что смотрю в сторону Delphi 10.1 Berlin, но рассматриваю и другие варианты.
0
igormatyushkin1 ,   (был удалён)
Задача сводится к тому, чтобы положить статичный веб-сайт в application bundle и отобразить его через WebView в Android, либо через WKWebView в iOS. Лучше всего создать по одному проекту для каждой платформы, используя стандартные инструменты этой самой платформы: Android Studio и Xcode для Android и iOS соответственно.
0
Leopotam ,   * (был изменён)
Если в рамках js / electron, то можно сразу смотреть в сторону reactjs, чтобы потом максимально безболезненно переехать на react-native.
0
max1gu ,  
тогда сразу на cordova
–1
paratagas ,  
Слышал о своего рода эмуляторах, например, DOSBox. Такой инструмент устанавливается на Android, после чего с его помощью можно запускать, например, exe-файлы. Сам такого рода эмуляторы не использовал, поэтому про их эффективность ничего не скажу. С другой стороны, возможно, лучше будет конвертировать для целевой платформы веб-приложение (которое в нашем случае написано на Webix), например, с помощью, PhoneGap.
+2
Goodkat ,  
Вы храните данные приложения с помощью php — у вас в скомпиллированном десктопном приложении, получается, будут браузер, вебсервер с php и node.js?
0
paratagas ,  
Да. При такой архитектуре без любого из этих компонентов (кроме браузера — он уже не нужен) скомпиллированное десктопное приложение работать не будет. В будущем планирую сделать и бэкенд и сервер на Node.js. Тогда можно будет обойтись и без отдельного веб-сервера, и без PHP.
0
Goodkat ,  
А как работает этот вебсервер? Он остаётся внутри приложения или виден извне для всех?
Что будет, если запустить три копии приложения? Они начнут конфликтовать?
Можете подробней осветить этот вопрос?
0
paratagas ,  
Сервер остается снаружи десктопа и виден извне. Если кто-то еще имеет к нему доступ (в том числе другие копии приложения), то они получат равные права на модификацию данных. Изменения данных в одной копии после обновления отражаются на других копиях. С другой стороны, если сделать бэкенд с использованием транзакций, то можно запускать несколько копий десктопного приложения на разных машинах и использовать общую базу данных.
0
Goodkat ,  
Получается, что Electron — это просто лончер для вебсервера и браузера.
И если в системе вдруг окажется другое Electron-приложение, а в списке их много, то последствия будут непредсказуемыми, они могут испортить друг другу данные.
0
paratagas ,   * (был изменён)
«Получается, что Electron — это просто лончер для вебсервера и браузера»:
В минимальном виде — да. Но Electron обладает теми же возможностями доступа, например, к ОС или файловой системе, что и Node.js. Это делает его более мощным инструментом, чем просто launcher.

«И если в системе вдруг окажется другое Electron-приложение… то… они могут испортить друг другу данные»:
Если они будут обращаться к одним и тем же данным по одному и тому же URL на сервере — то да. Но это же верно и для обычных веб-приложений при определенных условиях.

Если приложение небольшое, то, поскольку внутри Electron создается окно браузера, можно использовать localStorage. Кроме того, существуют различные инструменты и техники для доступа к БД (SQL/NoSQL), адаптированные для Electron или аналогичных ему инструментов: linvodb3, MySQL, Couchbase.
0
Goodkat ,  
Если они будут обращаться к одним и тем же данным по одному и тому же URL на сервере — то да. Но это же верно и для обычных веб-приложений при определенных условиях.
У вебприложений обычно разные серверы, а если они запущены на одном сервере, то разработчик знает о наличии других вебприложений на том же сервере.

А тут могут быть приложения от разных разработчиков, которые не зная друг о друге используют Electron и сохраняют данные используя локальный вебсервер.
Да и просто запустив такое приложение вы открываете дыру на своей машине.

Фигня какая-то получается.

Я думал, что Electron использует модифицированные вебсервер и браузер, которые контактируют как-нибудь напрямую, без открытия доступных извне сокетов.
+1
paratagas ,  
В приведенном Вами выше списке есть одно из приложений, которое требует доступ к URL
http://localhost:8000

И есть как минимум еще одно приложение, которое требует этот же URL.
В этом случае действительно возможен конфликт нескольких приложений.
Спасибо за Ваши комментарии. Поднятая Вами проблема достаточно актуальна и интересна. В свободное время попробую изучить ее детально.
–2
kostus1974 ,  
зачем это нужно? примитивный туду на 50 строк и общий размер под 50 МБ?
когда это всё кончится уже?
ну сделали вы туду на жээс — ну и что? что вам мешает просто молча запускать это просто на вашем любимом хомячном веб-сервере? зачем это публичное запихивание ноджээс во все дыры с объявлением десктопности этого? зачем это на хабре?
0
paratagas ,  
Платформа Node.js и язык Javascript развиваются. Как минимум, кому-то на Хабре будет интересно узнать про их новые возможности и существующие недостатки.
0
Zenitchik ,  
Если бы этого не было на Хабре, как бы я узнал, что это — дрянь? Самому бы шишки набивать пришлось, время тратить.
0
kostus1974 ,  
то есть слить в кучку локальный веб-сервер и браузер, и потом назвать это десктопным приложением — это надо какой-то путь пройти, как-то поумнеть? какой вы тут опыт увидели? где тут можно «шишки набивать»?
0
Zenitchik ,   * (был изменён)
слить в кучку локальный веб-сервер и браузер, и потом назвать это десктопным приложением

А почему бы нет? Не будь под капотом 50 метров мёртвой массы — всё нормально было бы.
0
justboris ,  

Насколько я понял из статьи, php вам нужен ради написания 4 строк кода.


Вот эквивалентный код на node.js. И Apache ставить не надо.


const express = require('express');
const bodyParser = require('body-parser');

const app = express();

app.use(bodyParser.json());
app.get('/data', (req, res) => {
  res.sendFile('data.json');
});
app.post('/data', (req, res) => {
   fs.writeFile('data.json', JSON.stringify(req.body), (err) => {
      if(err) {
        return res.status(500).send(`Error: ${err.message}`)
      }
      res.status(200).send(req.body);
   })
});

app.listen(3000);
0
paratagas ,  
Спасибо! Изначально я планировал более сложную систему с категориями и подкатегориями дел, думал использовать PHP-фреймворк. Но потом решил не перегружать проект, от идеи фреймворка отказался, а PHP по инерции оставил. Согласен, что использование Node.js сервера здесь намного уместнее.