Кросс-платформенная разработка десктопного ПО на базе JavaScript

Раздел: Разработка -> Кросс-платформенная разработка

Создание десктопных приложений на JavaScript

Какое решение считается основным для десктопных приложений на JavaScript?

Основным и наиболее популярным инструментом является Electron от GitHub. Он позволяет создавать кросс-платформенные приложения на базе Chromium и Node.js. Многие известные программы, такие как Visual Studio Code, Slack, Discord, используют Electron.

Установка Electron выполняется через npm. Для инициализации проекта создается папка, затем выполняется npm init и npm install electron --save-dev.

Пример минимальной структуры:

package.json
main.js
index.html

Js desktop (десктопные приложения на javascript (electron, nw.js))

Файл package.json содержит точку входа:

{
  "name": "my-app",
  "version": "1.0.0",
  "main": "main.js",
  "scripts": {
    "start": "electron ."
  }
}

Файл main.js создает окно браузера:

const { app, BrowserWindow } = require('electron');

function createWindow () {
  const win = new BrowserWindow({
    width: 800,
    height: 600,
    webPreferences: {
      nodeIntegration: true
    }
  });
  win.loadFile('index.html');
}

app.whenReady().then(createWindow);

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

Файл index.html содержит простой интерфейс:

<!DOCTYPE html>
<html>
<head><title>Приложение</title></head>
<body><h1>Привет, мир!</h1></body>
</html>

Запуск приложения осуществляется командой npm start.

Какие проблемы возникают при использовании Electron?

Основная проблема - значительный размер итогового приложения (более 100 МБ) из-за встроенного Chromium. Также возможны утечки памяти, если не управлять окнами корректно. При установке могут возникать ошибки загрузки нативных модулей. Рекомендуется использовать yarn или настроить зеркало для electron.

Если при сборке возникает ошибка, связанная с отсутствием нативного модуля, помогает команда electron-rebuild для пересборки модулей под версию Electron.

Какой альтернативой Electron является NW.js?

NW.js (ранее node-webkit) также позволяет запускать веб-приложения как настольные. В отличие от Electron, в NW.js можно напрямую вызывать Node.js API из DOM без необходимости в IPC, что упрощает код для простых приложений. Однако NW.js менее популярен, обновляется реже, и для сложных проектов может быть менее стабилен.

Пример создания приложения на NW.js. Структура та же: package.json, index.html. Package.json содержит поле "main" указывающее на HTML файл напрямую, а также поле "node-remote" для разрешения Node.js в окне. Пример:

{
  "name": "nw-app",
  "main": "index.html",
  "node-remote": "*",
  "scripts": {
    "start": "nw ."
  }
}

index.html:

<!DOCTYPE html>
<html>
<body>
<h1>NW.js приложение</h1>
<script>
  const fs = require('fs');
  document.body.innerHTML += '<p>Содержимое файла: ' + fs.readFileSync('data.txt', 'utf8') + '</p>';
</script>
</body>
</html>

Для установки NW.js используется npm: npm install nw --save-dev или загрузка предварительно собранного пакета.

Какие типичные ошибки в NW.js?

Возможна несовместимость модулей из-за устаревших версий Chromium. Также отсутствует централизованная система обновлений. Может потребоваться настройка nw-gyp для сборки нативных модулей. Если не установлен node-remote, Node.js API в окне будет недоступно.

Расширенные примеры использования Electron и NW.js

Создание окна с меню (Electron)

Пример
// main.js
const { app, BrowserWindow, Menu } = require('electron');

function createWindow() {
  const win = new BrowserWindow({ width: 800, height: 600 });
  const menu = Menu.buildFromTemplate([
    {
      label: 'Файл',
      submenu: [
        { label: 'Открыть', click: () => console.log('Открыть нажато') },
        { type: 'separator' },
        { label: 'Выход', role: 'quit' }
      ]
    },
    {
      label: 'Помощь',
      submenu: [
        { label: 'О программе', click: () => console.log('О программе') }
      ]
    }
  ]);
  Menu.setApplicationMenu(menu);
  win.loadFile('index.html');
}
app.whenReady().then(createWindow);
Результат: окно с панелью меню. При выборе пунктов меню в консоль выводятся сообщения. Приложение завершается по команде 'Выход'.

Межпроцессное взаимодействие через IPC (Electron)

Пример
// main.js
const { app, BrowserWindow, ipcMain } = require('electron');

function createWindow() {
  const win = new BrowserWindow({
    webPreferences: {
      preload: __dirname + '/preload.js'
    }
  });
  win.loadFile('index.html');
}

ipcMain.handle('get-user-data', async (event, arg) => {
  return { name: 'Пользователь', age: 25 };
});

app.whenReady().then(createWindow);
Пример
// preload.js
const { contextBridge, ipcRenderer } = require('electron');

contextBridge.exposeInMainWorld('electronAPI', {
  getUserData: () => ipcRenderer.invoke('get-user-data')
});
Пример
// index.html
<!DOCTYPE html>
<html>
<body>
<button id="btn">Получить данные</button>
<div id="output"></div>
<script>
document.getElementById('btn').addEventListener('click', async () => {
  const data = await window.electronAPI.getUserData();
  document.getElementById('output').textContent = JSON.stringify(data);
});
</script>
</body>
</html>
Результат: при нажатии на кнопку в окне отображается JSON-строка с данными {"name":"Пользователь","age":25}, переданная из главного процесса через IPC.

Системный трей (Electron)

Пример
const { app, Tray, Menu, nativeImage } = require('electron');

let tray = null;
app.whenReady().then(() => {
  const icon = nativeImage.createFromPath('icon.png'); // иконка 16x16
  tray = new Tray(icon);
  const contextMenu = Menu.buildFromTemplate([
    { label: 'Показать окно', click: () => { /* код показа окна */ } },
    { label: 'Выход', role: 'quit' }
  ]);
  tray.setToolTip('Моё приложение');
  tray.setContextMenu(contextMenu);
});
Результат: иконка появляется в системном трее. Правый клик открывает контекстное меню.

Запуск внешнего процесса (Electron)

Пример
const { app, BrowserWindow } = require('electron');
const { spawn } = require('child_process');

app.whenReady().then(() => {
  const win = new BrowserWindow({ width: 800, height: 600 });
  win.loadURL('data:text/html,<h1>Запуск Python скрипта</h1><script>fetch("/run").then(r=>r.text()).then(t=>document.body.innerHTML+=t)</script>');
  win.webContents.session.protocol.registerHttpProtocol('run', (req, cb) => {
    const child = spawn('python3', ['-c', 'print("Hello from Python")']);
    child.stdout.on('data', (data) => {
      win.webContents.send('script-output', data.toString());
    });
  });
});
Результат: приложение запускает Python скрипт и выводит его вывод в окне. (Пример упрощенный, для работы требуется дополнительная настройка протокола.)

Использование нативных модулей (NW.js)

Пример
<!-- В index.html -->
<script>
  const os = require('os');
  document.body.innerHTML = '<p>Платформа: ' + os.platform() + '</p>';
  // Использование нативного модуля (например, serialport) - требует установки
  // const SerialPort = require('serialport');
  // const port = new SerialPort('/dev/ttyUSB0', { baudRate: 9600 });
</script>
Результат: отображение информации о платформе. Если нативный модуль установлен, возможно его использование.

Десктопные приложения на JavaScript (Electron, NW.js) - comments

En
Js desktop (javascript)