Skip to content

Instantly share code, notes, and snippets.

@dmitry-osin
Last active April 28, 2025 23:08
Show Gist options
  • Save dmitry-osin/a5c2eb476b7993575c2325a8225ce718 to your computer and use it in GitHub Desktop.
Save dmitry-osin/a5c2eb476b7993575c2325a8225ce718 to your computer and use it in GitHub Desktop.
Шпаргалка по Electron.js

Шпаргалка по Electron.js

Electron.js - это фреймворк, который позволяет разрабатывать настольные приложения с использованием веб-технологий (HTML, CSS и JavaScript). Давайте создадим полную шпаргалку по Electron.js.

Содержание

Основы Electron.js

Что такое Electron.js?

  • Кроссплатформенный фреймворк для создания десктопных приложений
  • Использует Chromium (для отображения интерфейса) и Node.js (для работы с системой)
  • Позволяет создавать приложения для Windows, macOS и Linux
  • Разработан GitHub (сейчас принадлежит Microsoft)

Архитектура Electron.js

  • Главный процесс (Main Process) - точка входа в приложение, контролирует жизненный цикл
  • Процессы рендеринга (Renderer Processes) - каждое окно работает в отдельном процессе
  • IPC (Inter-Process Communication) - механизм для обмена сообщениями между процессами

Установка и настройка

Требования

  • Node.js (рекомендуется последняя стабильная версия)
  • npm или yarn

Создание нового проекта

# Создание директории проекта
mkdir my-electron-app
cd my-electron-app

# Инициализация npm
npm init -y

# Установка Electron
npm install --save-dev electron

Базовая структура проекта

my-electron-app/
├── package.json
├── main.js         # Главный процесс
├── preload.js      # Скрипт предзагрузки
└── index.html      # Интерфейс приложения

Минимальный package.json

{
  "name": "my-electron-app",
  "version": "1.0.0",
  "description": "Моё первое приложение на Electron",
  "main": "main.js",
  "scripts": {
    "start": "electron ."
  },
  "devDependencies": {
    "electron": "^28.0.0"
  }
}

Создание простого приложения

main.js (Главный процесс)

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

function createWindow() {
  // Создание окна браузера
  const mainWindow = new BrowserWindow({
    width: 800,
    height: 600,
    webPreferences: {
      preload: path.join(__dirname, 'preload.js'),
      nodeIntegration: false,
      contextIsolation: true
    }
  })

  // Загрузка HTML файла
  mainWindow.loadFile('index.html')
  
  // Открыть DevTools (для отладки)
  // mainWindow.webContents.openDevTools()
}

// Создание окна когда приложение готово
app.whenReady().then(() => {
  createWindow()
  
  // На macOS обычно пересоздают окно, когда пользователь кликает на иконку в доке
  app.on('activate', function () {
    if (BrowserWindow.getAllWindows().length === 0) createWindow()
  })
})

// Закрытие приложения, когда все окна закрыты (кроме macOS)
app.on('window-all-closed', function () {
  if (process.platform !== 'darwin') app.quit()
})

preload.js

// Безопасное взаимодействие между рендерером и основным процессом
window.addEventListener('DOMContentLoaded', () => {
  const replaceText = (selector, text) => {
    const element = document.getElementById(selector)
    if (element) element.innerText = text
  }

  for (const dependency of ['chrome', 'node', 'electron']) {
    replaceText(`${dependency}-version`, process.versions[dependency])
  }
})

index.html

<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <title>Моё приложение Electron</title>
  <meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self'">
</head>
<body>
  <h1>Привет, Electron!</h1>
  <p>Версии:</p>
  <ul>
    <li>Chrome: <span id="chrome-version"></span></li>
    <li>Node.js: <span id="node-version"></span></li>
    <li>Electron: <span id="electron-version"></span></li>
  </ul>
</body>
</html>

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

Общение между Main и Renderer процессами

В main.js (Главный процесс)

const { ipcMain } = require('electron')

// Обработка асинхронного сообщения от renderer процесса
ipcMain.on('async-message', (event, arg) => {
  console.log(arg) // выводит "ping"
  event.reply('async-reply', 'pong')
})

// Обработка синхронного сообщения от renderer процесса
ipcMain.on('sync-message', (event, arg) => {
  console.log(arg) // выводит "ping"
  event.returnValue = 'pong'
})

// Обработка запроса с ответом
ipcMain.handle('invoke-message', async (event, arg) => {
  console.log(arg) // выводит "ping"
  return 'pong'
})

В preload.js

const { contextBridge, ipcRenderer } = require('electron')

// Экспортируем API в глобальный объект window
contextBridge.exposeInMainWorld('electronAPI', {
  // Асинхронное сообщение
  sendMessage: (message) => ipcRenderer.send('async-message', message),
  
  // Получение ответа на асинхронное сообщение
  onReply: (callback) => ipcRenderer.on('async-reply', (event, ...args) => callback(...args)),
  
  // Синхронное сообщение
  sendSyncMessage: (message) => ipcRenderer.sendSync('sync-message', message),
  
  // Запрос с Promise-ответом
  invokeMessage: (message) => ipcRenderer.invoke('invoke-message', message)
})

В renderer.js (скрипт в HTML)

// Асинхронное сообщение
window.electronAPI.sendMessage('ping')
window.electronAPI.onReply((response) => {
  console.log(response) // выводит "pong"
})

// Синхронное сообщение
const response = window.electronAPI.sendSyncMessage('ping')
console.log(response) // выводит "pong"

// Запрос с Promise-ответом
window.electronAPI.invokeMessage('ping')
  .then((response) => {
    console.log(response) // выводит "pong"
  })

Работа с Меню

Создание меню приложения

const { Menu } = require('electron')

const template = [
  {
    label: 'Файл',
    submenu: [
      { label: 'Новый', accelerator: 'CmdOrCtrl+N', click: () => { /* действие */ } },
      { type: 'separator' },
      { label: 'Выход', role: 'quit' }
    ]
  },
  {
    label: 'Правка',
    submenu: [
      { role: 'undo' },
      { role: 'redo' },
      { type: 'separator' },
      { role: 'cut' },
      { role: 'copy' },
      { role: 'paste' }
    ]
  },
  {
    label: 'Вид',
    submenu: [
      { role: 'reload' },
      { role: 'forceReload' },
      { role: 'toggleDevTools' },
      { type: 'separator' },
      { role: 'resetZoom' },
      { role: 'zoomIn' },
      { role: 'zoomOut' },
      { type: 'separator' },
      { role: 'togglefullscreen' }
    ]
  }
]

const menu = Menu.buildFromTemplate(template)
Menu.setApplicationMenu(menu)

Контекстное меню

const { Menu, MenuItem } = require('electron')

const contextMenu = new Menu()
contextMenu.append(new MenuItem({ label: 'Копировать', role: 'copy' }))
contextMenu.append(new MenuItem({ label: 'Вставить', role: 'paste' }))

// В главном процессе
mainWindow.webContents.on('context-menu', (e, params) => {
  contextMenu.popup(mainWindow, params.x, params.y)
})

// Или в renderer процессе через preload
window.addEventListener('contextmenu', (e) => {
  e.preventDefault()
  window.electronAPI.showContextMenu()
})

Диалоговые окна

Открытие диалоговых окон

const { dialog } = require('electron')

// Открыть диалог выбора файла
async function openFile() {
  const { canceled, filePaths } = await dialog.showOpenDialog({
    properties: ['openFile'],
    filters: [
      { name: 'Текстовые файлы', extensions: ['txt', 'md'] },
      { name: 'Все файлы', extensions: ['*'] }
    ]
  })
  
  if (!canceled) {
    return filePaths[0]
  }
}

// Открыть диалог сохранения файла
async function saveFile() {
  const { canceled, filePath } = await dialog.showSaveDialog({
    filters: [
      { name: 'Текстовые файлы', extensions: ['txt'] }
    ]
  })
  
  if (!canceled) {
    return filePath
  }
}

// Показать сообщение
function showMessage() {
  dialog.showMessageBox({
    type: 'info',
    title: 'Информация',
    message: 'Это информационное сообщение',
    detail: 'Дополнительная информация здесь',
    buttons: ['OK', 'Отмена']
  }).then(result => {
    console.log(`Нажата кнопка с индексом ${result.response}`)
  })
}

Уведомления

Нативные уведомления

// В renderer процессе
function showNotification() {
  const notification = new Notification('Заголовок', {
    body: 'Текст уведомления'
  })
  
  notification.onclick = () => {
    console.log('Уведомление было нажато')
  }
}

// Проверка поддержки
if (Notification.permission === 'granted') {
  showNotification()
} else if (Notification.permission !== 'denied') {
  Notification.requestPermission().then(permission => {
    if (permission === 'granted') {
      showNotification()
    }
  })
}

Работа с файловой системой

Чтение и запись файлов

const fs = require('fs')
const path = require('path')

// Чтение файла
function readFile(filePath) {
  try {
    return fs.readFileSync(filePath, 'utf8')
  } catch (error) {
    console.error('Ошибка при чтении файла:', error)
    return null
  }
}

// Запись в файл
function writeFile(filePath, content) {
  try {
    fs.writeFileSync(filePath, content, 'utf8')
    return true
  } catch (error) {
    console.error('Ошибка при записи файла:', error)
    return false
  }
}

// Получение пути к пользовательским данным
const userDataPath = app.getPath('userData')
const configPath = path.join(userDataPath, 'config.json')

Упаковка и дистрибуция

Electron Forge

# Установка Electron Forge
npm install --save-dev @electron-forge/cli
npx electron-forge import

# Создание дистрибутива
npm run make

Конфигурация в package.json

{
  "name": "my-app",
  "version": "1.0.0",
  "main": "main.js",
  "scripts": {
    "start": "electron-forge start",
    "package": "electron-forge package",
    "make": "electron-forge make"
  },
  "config": {
    "forge": {
      "packagerConfig": {
        "icon": "./assets/icon"
      },
      "makers": [
        {
          "name": "@electron-forge/maker-squirrel",
          "config": {
            "name": "my_app"
          }
        },
        {
          "name": "@electron-forge/maker-zip",
          "platforms": ["darwin"]
        },
        {
          "name": "@electron-forge/maker-deb",
          "config": {}
        },
        {
          "name": "@electron-forge/maker-rpm",
          "config": {}
        }
      ]
    }
  },
  "devDependencies": {
    "@electron-forge/cli": "^6.0.0",
    "@electron-forge/maker-deb": "^6.0.0",
    "@electron-forge/maker-rpm": "^6.0.0",
    "@electron-forge/maker-squirrel": "^6.0.0",
    "@electron-forge/maker-zip": "^6.0.0",
    "electron": "^28.0.0"
  }
}

Безопасность

Лучшие практики безопасности

  1. Всегда используйте contextIsolation: true

    webPreferences: {
      contextIsolation: true,
      nodeIntegration: false
    }
  2. Используйте preload скрипты для безопасного API

    webPreferences: {
      preload: path.join(__dirname, 'preload.js')
    }
  3. Настройка CSP (Content Security Policy)

    <meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self'">
  4. Проверка источников при загрузке удаленного содержимого

    // Безопасно
    mainWindow.loadURL('https://trusted-domain.com')
    
    // Небезопасно
    mainWindow.loadURL(userProvidedUrl) // Не делайте так!
  5. Ограничение доступа к функциям Node.js

    // В preload.js экспортируйте только необходимые API
    contextBridge.exposeInMainWorld('safeAPI', {
      // Ограниченный набор функций
      readConfig: () => fs.readFileSync(configPath, 'utf8'),
      // НЕ экспортируйте весь fs модуль!
    })

Отладка

Инструменты разработчика

// Открытие DevTools
mainWindow.webContents.openDevTools()

// Отладка main процесса
// Запустите приложение с флагом --inspect:
// electron --inspect=5858 .

Логирование

// В main процессе
console.log('Информация')
console.error('Ошибка')

// В renderer процессе
console.log('Информация в рендерере')

Горячие клавиши

Регистрация глобальных горячих клавиш

const { globalShortcut } = require('electron')

// Регистрация горячей клавиши
app.whenReady().then(() => {
  globalShortcut.register('CommandOrControl+Shift+I', () => {
    console.log('CommandOrControl+Shift+I нажата')
    // Выполнить действие
  })
})

// Проверка регистрации
const isRegistered = globalShortcut.isRegistered('CommandOrControl+Shift+I')

// Отмена регистрации
app.on('will-quit', () => {
  // Отменить регистрацию отдельной клавиши
  globalShortcut.unregister('CommandOrControl+Shift+I')
  
  // Отменить регистрацию всех клавиш
  globalShortcut.unregisterAll()
})

Обновление приложения

Автообновление с помощью electron-updater

const { autoUpdater } = require('electron-updater')

// Настройка логирования
autoUpdater.logger = require('electron-log')
autoUpdater.logger.transports.file.level = 'info'

// Проверка обновлений
app.on('ready', () => {
  autoUpdater.checkForUpdatesAndNotify()
})

// События обновления
autoUpdater.on('update-available', () => {
  // Обновление доступно
})

autoUpdater.on('update-downloaded', () => {
  // Обновление загружено и готово к установке
  dialog.showMessageBox({
    type: 'info',
    title: 'Обновление',
    message: 'Обновление загружено',
    buttons: ['Перезапустить сейчас', 'Позже']
  }).then((result) => {
    if (result.response === 0) {
      autoUpdater.quitAndInstall()
    }
  })
})

Полезные API Electron

BrowserWindow

// Создание окна с дополнительными опциями
const win = new BrowserWindow({
  width: 800,
  height: 600,
  minWidth: 400,
  minHeight: 300,
  frame: false, // Безрамочное окно
  transparent: true, // Прозрачное окно
  alwaysOnTop: true, // Всегда поверх других окон
  titleBarStyle: 'hidden', // Скрыть заголовок (macOS)
  webPreferences: {
    preload: path.join(__dirname, 'preload.js'),
    contextIsolation: true
  }
})

// События окна
win.on('closed', () => {
  // Окно закрыто
})

win.on('focus', () => {
  // Окно получило фокус
})

win.on('blur', () => {
  // Окно потеряло фокус
})

// Методы окна
win.maximize() // Развернуть
win.minimize() // Свернуть
win.restore() // Восстановить
win.setFullScreen(true) // Полноэкранный режим
win.setTitle('Новый заголовок') // Изменить заголовок
win.center() // Центрировать окно

Tray (иконка в системном трее)

const { Tray, Menu } = require('electron')
const path = require('path')

let tray = null

app.whenReady().then(() => {
  tray = new Tray(path.join(__dirname, 'icon.png'))
  
  const contextMenu = Menu.buildFromTemplate([
    { label: 'Показать', click: () => mainWindow.show() },
    { label: 'Скрыть', click: () => mainWindow.hide() },
    { type: 'separator' },
    { label: 'Выход', click: () => app.quit() }
  ])
  
  tray.setToolTip('Моё приложение Electron')
  tray.setContextMenu(contextMenu)
  
  tray.on('click', () => {
    mainWindow.isVisible() ? mainWindow.hide() : mainWindow.show()
  })
})

Clipboard (буфер обмена)

const { clipboard } = require('electron')

// Запись в буфер обмена
clipboard.writeText('Текст в буфере обмена')

// Чтение из буфера обмена
const text = clipboard.readText()

// Работа с изображениями
const image = clipboard.readImage()
clipboard.writeImage(image)

// Работа с HTML
clipboard.writeHTML('<b>HTML в буфере обмена</b>')
const html = clipboard.readHTML()

powerMonitor (мониторинг питания)

const { powerMonitor } = require('electron')

powerMonitor.on('suspend', () => {
  console.log('Система переходит в режим сна')
  // Сохранить данные перед сном
})

powerMonitor.on('resume', () => {
  console.log('Система возобновила работу')
  // Восстановить соединения и т.д.
})

powerMonitor.on('on-ac', () => {
  console.log('Система подключена к сети')
})

powerMonitor.on('on-battery', () => {
  console.log('Система работает от батареи')
  // Включить режим экономии энергии
})

Продвинутые темы

Использование нативных модулей

// package.json
{
  "dependencies": {
    "sqlite3": "^5.0.0"
  }
}

// Пересборка модулей для Electron
// npm install --save-dev electron-rebuild
// npx electron-rebuild

// Использование в коде
const sqlite3 = require('sqlite3')
const db = new sqlite3.Database('./database.sqlite')

Многооконные приложения

function createWindow(url, options = {}) {
  const win = new BrowserWindow({
    width: 800,
    height: 600,
    ...options,
    webPreferences: {
      preload: path.join(__dirname, 'preload.js'),
      contextIsolation: true,
      ...(options.webPreferences || {})
    }
  })
  
  win.loadFile(url)
  return win
}

// Создание нескольких окон
const mainWindow = createWindow('index.html')
const settingsWindow = createWindow('settings.html', { 
  parent: mainWindow,
  modal: true,
  width: 400,
  height: 300
})

Интеграция с веб-фреймворками

React

# Создание React приложения
npx create-react-app my-electron-react-app
cd my-electron-react-app

# Установка Electron
npm install --save-dev electron electron-builder concurrently wait-on

package.json для React

{
  "main": "public/electron.js",
  "homepage": "./",
  "scripts": {
    "start": "react-scripts start",
    "build": "react-scripts build",
    "electron:dev": "concurrently \"BROWSER=none npm start\" \"wait-on http://localhost:3000 && electron .\"",
    "electron:build": "npm run build && electron-builder"
  }
}

public/electron.js

const { app, BrowserWindow } = require('electron')
const path = require('path')
const isDev = require('electron-is-dev')

function createWindow() {
  const mainWindow = new BrowserWindow({
    width: 800,
    height: 600,
    webPreferences: {
      preload: path.join(__dirname, 'preload.js'),
      contextIsolation: true
    }
  })

  mainWindow.loadURL(
    isDev
      ? 'http://localhost:3000'
      : `file://${path.join(__dirname, '../build/index.html')}`
  )
}

app.whenReady().then(createWindow)

Vue.js

# Создание Vue приложения
vue create my-electron-vue-app
cd my-electron-vue-app

# Установка Vue CLI Plugin Electron Builder
vue add electron-builder

Советы и трюки

Отключение меню в окне

mainWindow.setMenu(null)

Создание прозрачного окна

const win = new BrowserWindow({
  transparent: true,
  frame: false,
  backgroundColor: '#00000000'
})

Создание перетаскиваемой области в безрамочном окне

/* В CSS */
.titlebar {
  -webkit-app-region: drag;
}

.non-draggable {
  -webkit-app-region: no-drag;
}

Сохранение размера и позиции окна

const Store = require('electron-store')
const store = new Store()

// Сохранение при закрытии
mainWindow.on('close', () => {
  store.set('windowBounds', mainWindow.getBounds())
})

// Восстановление при создании
function createWindow() {
  const bounds = store.get('windowBounds') || { width: 800, height: 600 }
  const mainWindow = new BrowserWindow({
    ...bounds,
    webPreferences: {
      preload: path.join(__dirname, 'preload.js'),
      contextIsolation: true
    }
  })
}

Темная/светлая тема

// Определение темы системы
const { nativeTheme } = require('electron')

// Проверка текущей темы
const isDarkMode = nativeTheme.shouldUseDarkColors

// Принудительная установка темы
nativeTheme.themeSource = 'dark' // 'light' или 'system'

// Отслеживание изменений темы
nativeTheme.on('updated', () => {
  console.log('Тема изменена:', nativeTheme.shouldUseDarkColors ? 'темная' : 'светлая')
})

Полезные пакеты

  • electron-store - простое хранение настроек
  • electron-updater - автоматическое обновление
  • electron-builder - упаковка и дистрибуция
  • electron-log - логирование
  • electron-reload - автоматическая перезагрузка в режиме разработки
  • electron-devtools-installer - установка DevTools расширений
  • electron-context-menu - контекстное меню
  • electron-window-state - сохранение состояния окна
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment