Electron.js - это фреймворк, который позволяет разрабатывать настольные приложения с использованием веб-технологий (HTML, CSS и JavaScript). Давайте создадим полную шпаргалку по Electron.js.
- Основы Electron.js
- Установка и настройка
- Создание простого приложения
- Межпроцессное взаимодействие (IPC)
- Работа с Меню
- Диалоговые окна
- Уведомления
- Работа с файловой системой
- Упаковка и дистрибуция
- Безопасность
- Отладка
- Горячие клавиши
- Обновление приложения
- Полезные API Electron
- Продвинутые темы
- Советы и трюки
- Полезные пакеты
- Кроссплатформенный фреймворк для создания десктопных приложений
- Использует Chromium (для отображения интерфейса) и Node.js (для работы с системой)
- Позволяет создавать приложения для Windows, macOS и Linux
- Разработан GitHub (сейчас принадлежит Microsoft)
- Главный процесс (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 # Интерфейс приложения
{
"name": "my-electron-app",
"version": "1.0.0",
"description": "Моё первое приложение на Electron",
"main": "main.js",
"scripts": {
"start": "electron ."
},
"devDependencies": {
"electron": "^28.0.0"
}
}
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()
})
// Безопасное взаимодействие между рендерером и основным процессом
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])
}
})
<!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>
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'
})
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)
})
// Асинхронное сообщение
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
npm install --save-dev @electron-forge/cli
npx electron-forge import
# Создание дистрибутива
npm run make
{
"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"
}
}
-
Всегда используйте contextIsolation: true
webPreferences: { contextIsolation: true, nodeIntegration: false }
-
Используйте preload скрипты для безопасного API
webPreferences: { preload: path.join(__dirname, 'preload.js') }
-
Настройка CSP (Content Security Policy)
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self'">
-
Проверка источников при загрузке удаленного содержимого
// Безопасно mainWindow.loadURL('https://trusted-domain.com') // Небезопасно mainWindow.loadURL(userProvidedUrl) // Не делайте так!
-
Ограничение доступа к функциям 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()
})
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()
}
})
})
// Создание окна с дополнительными опциями
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() // Центрировать окно
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()
})
})
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()
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 приложения
npx create-react-app my-electron-react-app
cd my-electron-react-app
# Установка Electron
npm install --save-dev electron electron-builder concurrently wait-on
{
"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"
}
}
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 приложения
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 - сохранение состояния окна