Electron Builder: Настройка упакованного приложения с бэкендом и SQLite
Узнайте, как настроить Electron Builder для упаковки вашего приложения с бэкендом Express и базой данных SQLite. Исправьте проблемы с запуском сервера и доступом к базе данных в упакованных приложениях.
Electron Builder: Как настроить упакованное приложение для запуска серверной части и базы данных SQLite?
У меня есть Electron-приложение с:
- React для фронтенда
- Node + Express бэкендом в папке /Server
- Базой данных SQLite в папке /Server
Во время разработки всё работает корректно при запуске npm start. Сервер подключается в main.js через require("./Server/index.js"), и API-запросы работают нормально.
Однако, когда я упаковываю приложение с помощью npm run dist, интерфейс открывается, но бэкенд-сервер не запускается, а все API-запросы остаются в состоянии Pending. База данных SQLite также не находится.
Electron Builder выдает предупреждения вроде:
asar usage is disabled — this is strongly not recommended
И если я включаю asar, база данных SQLite становится недоступной.
Текущая конфигурация сборки в package.json:
"build": {
"appId": "com.yourcompany.matjari",
"files": [
"main.js",
"Server/**",
"client/build/**"
],
"asar": false
}
Какая правильная конфигурация для Electron Builder, чтобы:
- Включить папку /Server и базу данных SQLite в упакованное приложение
- Обеспечить запуск сервера при запуске собранного приложения (.exe)
- Разрешить запись и доступ к базе данных во время выполнения
Стоит ли использовать “asar”: true и asarUnpack? Стоит ли переместить базу данных в другую директорию, например resources/? Какова рекомендуемая структура для Electron-приложений, включающих бэкенд-сервер и SQLite?
Моя цель - создать упакованное приложение (.exe), которое:
- Автоматически запускает Express-сервер
- Нормально загружает и использует базу данных SQLite
- Хранит всё внутри папки установки (без необходимости внешних установок)
Правильная упаковка Electron-приложения с сервером и базой данных SQLite с помощью Electron Builder
Чтобы правильно упаковать ваше Electron-приложение с серверной частью и базой данных SQLite с помощью Electron Builder, необходимо использовать конфигурацию asarUnpack для извлечения файлов сервера и базы данных из доступного только для чтения ASAR-архива. Рекомендуемый подход — установить "asar": true с определенными правилами asarUnpack для файлов сервера и базы данных, а также изменить код инициализации сервера для работы с распакованной файловой структурой.
- Понимание проблемы упаковки ASAR
- Рекомендуемые параметры конфигурации
- Конфигурация структуры файлов и путей
- Модификация кода для упакованного приложения
- Пошаговое руководство по конфигурации
- Тестирование и валидация
- Альтернативные подходы
Понимание проблемы упаковки ASAR
Electron использует ASAR-архивы для упаковки файлов приложения в единый исполняемый формат. Однако это создает файловую систему доступную только для чтения, что препятствует выполнению необходимых операций для серверных приложений:
- Базы данных SQLite нельзя записывать при упаковке внутри ASAR
- Файлы серверной части нельзя выполнять изнутри архива
- Нативные модули, такие как sqlite3, требуют распакованных двоичных файлов для правильной работы
Согласно результатам исследований, “Electron упаковывает файлы в доступный только для чтения ASAR-архив, и любой файл, в который нам нужно записывать (например, базу данных SQLite), не может быть включен”. Это корневая причина ваших проблем, когда база данных не найдена, а сервер не запускается после упаковки.
Сообщение предупреждения, которое вы видите — “использование asar отключено — это настоятельно не рекомендуется” — указывает на то, что полное отключение ASAR не является оптимальным решением, так как это увеличивает размер файла и снижает безопасность.
Рекомендуемые параметры конфигурации
1. Использование конфигурации asarUnpack
Наиболее эффективное решение — использовать asarUnpack для извлечения определенных папок из ASAR-архива, сохраняя при этом включенный ASAR для остальной части приложения:
"build": {
"appId": "com.yourcompany.matjari",
"asar": true,
"asarUnpack": [
"Server/**"
],
"files": [
"main.js",
"client/build/**"
]
}
2. Использование конфигурации extraResources
Для дополнительного контроля над размещением ресурсов можно также использовать свойство extraResources:
"build": {
"appId": "com.yourcompany.matjari",
"asar": true,
"extraResources": [
{
"from": "Server",
"to": "Server",
"filter": ["**/*"]
}
],
"files": [
"main.js",
"client/build/**"
]
}
Ключевые моменты:
asarUnpackавтоматически извлекает указанные папки вresources/asarUnpack/extraResourcesдает больше контроля над размещением файлов- Оба метода гарантируют, что ваши файлы сервера будут исполняемыми, а база данных — доступной для записи
Рекомендация: Используйте
asarUnpackдля папки вашего сервера, так как это проще и прямолинейнее для данного случая использования.
Конфигурация структуры файлов и путей
При использовании asarUnpack Electron Builder создает следующую структуру в вашем упакованном приложении:
YourApp.app/
├── Contents/
│ ├── Resources/
│ │ ├── app.asar (основной код приложения)
│ │ ├── app.asar.unpacked/ (распакованные файлы сервера)
│ │ │ └── Server/ (ваша серверная часть)
│ │ └── ... (другие ресурсы)
Разрешение путей в упакованном приложении
Ваш код инициализации сервера должен обрабатывать как среду разработки, так и упакованную среду. Используйте app.getPath() от Electron для поиска правильного пути к ресурсам:
const path = require('path');
const { app } = require('electron');
function getServerPath() {
if (process.env.NODE_ENV === 'development') {
return path.join(__dirname, 'Server');
} else {
// В продакшене файлы сервера распаковываются в app.asar.unpacked
return path.join(app.getAppPath(), 'app.asar.unpacked', 'Server');
}
}
const serverPath = getServerPath();
Модификация кода для упакованного приложения
1. Инициализация сервера
Измените ваш main.js для правильной инициализации сервера в обеих средах:
const { app, BrowserWindow } = require('electron');
const path = require('path');
const { spawn } = require('child_process');
let serverProcess = null;
function startServer() {
const serverPath = getServerPath();
const serverScript = path.join(serverPath, 'index.js');
// Используйте spawn для лучшего управления процессом
serverProcess = spawn('node', [serverScript], {
cwd: serverPath,
stdio: 'inherit'
});
serverProcess.on('error', (error) => {
console.error('Ошибка запуска сервера:', error);
});
serverProcess.on('exit', (code) => {
console.log(`Сервер завершил работу с кодом ${code}`);
});
}
app.whenReady().then(() => {
// Запускаем сервер при готовности приложения
startServer();
// Создайте главное окно
const mainWindow = new BrowserWindow({
width: 800,
height: 600
});
// Загрузите ваше React-приложение
mainWindow.loadFile('client/build/index.html');
});
app.on('will-quit', () => {
// Очистка процесса сервера при выходе из приложения
if (serverProcess) {
serverProcess.kill();
}
});
2. Конфигурация пути к базе данных
Убедитесь, что ваша база данных SQLite использует доступное для записи расположение:
const path = require('path');
const { app } = require('electron');
function getDatabasePath() {
// Используйте app.getPath('userData') для пользовательских данных
const userDataPath = app.getPath('userData');
return path.join(userDataPath, 'database.sqlite');
}
// В настройках вашего Express-сервера
const dbPath = getDatabasePath();
const db = new sqlite3.Database(dbPath, (err) => {
if (err) {
console.error('Ошибка открытия базы данных:', err);
} else {
console.log('Подключено к базе данных SQLite по пути:', dbPath);
}
});
Пошаговое руководство по конфигурации
1. Обновление конфигурации сборки в package.json
"build": {
"appId": "com.yourcompany.matjari",
"productName": "Название вашего приложения",
"directories": {
"output": "dist"
},
"asar": true,
"asarUnpack": [
"Server/**"
],
"files": [
"main.js",
"client/build/**",
"package.json"
],
"mac": {
"category": "public.app-category.productivity"
},
"win": {
"target": "nsis"
},
"linux": {
"target": "AppImage"
}
}
2. Модификация кода инициализации сервера
Обновите index.js вашего сервера для обработки упакованной среды:
const path = require('path');
const express = require('express');
const sqlite3 = require('sqlite3').verbose();
function getAppPath() {
if (process.env.NODE_ENV === 'development') {
return __dirname;
} else {
// В продакшене файлы сервера находятся в app.asar.unpacked
return path.join(process.resourcesPath, 'app.asar.unpacked', 'Server');
}
}
const app = express();
const port = process.env.PORT || 3000;
// Настройка базы данных
const dbPath = path.join(getAppPath(), 'database.sqlite');
const db = new sqlite3.Database(dbPath, (err) => {
if (err) {
console.error('Ошибка открытия базы данных:', err);
} else {
console.log('Подключено к базе данных SQLite');
// Инициализация таблиц базы данных при необходимости
initializeDatabase();
}
});
function initializeDatabase() {
// Ваш код инициализации базы данных
db.serialize(() => {
db.run(`CREATE TABLE IF NOT EXISTS your_table (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL
)`);
});
}
// Ваши API-маршруты
app.get('/api/data', (req, res) => {
db.all('SELECT * FROM your_table', [], (err, rows) => {
if (err) {
res.status(500).json({ error: err.message });
return;
}
res.json({ data: rows });
});
});
app.listen(port, 'localhost', () => {
console.log(`Сервер запущен на http://localhost:${port}`);
});
3. Обновление основного процесса
Измените ваш main.js для правильного запуска сервера:
const { app, BrowserWindow } = require('electron');
const path = require('path');
const { spawn } = require('child_process');
let mainWindow = null;
let serverProcess = null;
function createWindow() {
mainWindow = new BrowserWindow({
width: 800,
height: 600,
webPreferences: {
nodeIntegration: true,
contextIsolation: false
}
});
// Загрузите ваше React-приложение
mainWindow.loadFile('client/build/index.html');
mainWindow.on('closed', () => {
mainWindow = null;
});
}
function startServer() {
const serverPath = path.join(__dirname, 'Server');
const serverScript = path.join(serverPath, 'index.js');
serverProcess = spawn('node', [serverScript], {
cwd: serverPath,
stdio: 'inherit'
});
serverProcess.on('error', (error) => {
console.error('Ошибка запуска сервера:', error);
});
serverProcess.on('exit', (code) => {
console.log(`Сервер завершил работу с кодом ${code}`);
});
}
app.whenReady().then(() => {
startServer();
createWindow();
app.on('activate', () => {
if (BrowserWindow.getAllWindows().length === 0) {
createWindow();
}
});
});
app.on('window-all-closed', () => {
if (process.platform !== 'darwin') {
app.quit();
}
});
app.on('will-quit', () => {
if (serverProcess) {
serverProcess.kill();
}
});
Тестирование и валидация
1. Тестирование в разработке
Перед сборкой протестируйте вашу конфигурацию в разработке:
npm start
Убедитесь, что:
- Сервер запускается корректно
- API-вызовы работают как ожидается
- Операции с базой данных функционируют нормально
2. Тестирование в продакшене
Соберите и протестируйте ваше упакованное приложение:
npm run dist
Протестируйте следующие сценарии:
- Приложение запускается без ошибок
- Сервер запускается автоматически
- API-вызовы возвращают правильные данные
- Операции с базой данных работают корректно
- Приложение закрывается правильно
3. Распространенные проблемы и решения
Проблема: Сервер не запускается после упаковки
Решение: Убедитесь, что asarUnpack сконфигурирован правильно, а пути к серверу разрешаются корректно
Проблема: База данных не найдена
Решение: Используйте app.getPath('userData') для хранения базы данных вместо путей относительно приложения
Проблема: Ошибки нативных модулей
Решение: Добавьте нативные модули в asarUnpack или используйте extraResources
Проблема: Процесс сервера не очищается
Решение: Добавьте правильную очистку процесса в app.on('will-quit')
Альтернативные подходы
1. Внешнее расположение базы данных
Для приложений, которым нужно обмениваться данными между пользователями или сессиями:
"build": {
"asar": true,
"extraResources": [
{
"from": "Server",
"to": "Server",
"filter": ["**/*"]
}
]
}
И измените путь к базе данных для использования общего расположения:
const path = require('path');
const fs = require('fs');
function getSharedDatabasePath() {
const sharedPath = path.join(app.getPath('userData'), 'shared');
if (!fs.existsSync(sharedPath)) {
fs.mkdirSync(sharedPath, { recursive: true });
}
return path.join(sharedPath, 'database.sqlite');
}
2. Архитектура с отдельными процессами
Для более сложных приложений рассмотрите возможность запуска сервера как отдельного процесса:
// В main.js
const { fork } = require('child_process');
function startServer() {
const serverProcess = fork(path.join(__dirname, 'Server', 'index.js'), {
stdio: 'inherit'
});
serverProcess.on('message', (message) => {
if (message.type === 'server-ready') {
console.log('Сервер готов');
}
});
}
3. Использование better-sqlite3
Для лучшей производительности и совместимости рассмотрите переход на better-sqlite3:
npm install better-sqlite3
Этот нативный модуль часто лучше работает с упаковкой Electron и может быть сконфигурирован с:
"asarUnpack": [
"node_modules/better-sqlite3/**/*"
]
Источники
- Electron Builder Common Configuration - electron.build
- How to Build an Electron Desktop App in JavaScript - freeCodeCamp
- Electron - How to setup db with sqlite in Windows - Stack Overflow
- sqlite3 db-file is not found within app.asar - GitHub Issue
- Native Dependency loading Error for package
sqlite-vec- GitHub Issue - Using Prisma with the Electron Framework - Lightrun
- Distribution Guide - electron-vite
- How to externalise folder with electron? - Stack Overflow
Заключение
Чтобы успешно упаковать ваше Electron-приложение с серверной частью и базой данных SQLite, следуйте этим ключевым рекомендациям:
-
Используйте
asarUnpackдля папки вашего сервера, чтобы извлечь ее из доступного только для чтения ASAR-архива, сохраняя при этом включенный ASAR для безопасности и производительности. -
Измените код инициализации вашего сервера, чтобы правильно разрешать пути как в среде разработки, так и в продакшене, используя
app.getAppPath()иapp.getPath('userData'). -
Храните вашу базу данных SQLite в доступном для записи расположении, таком как
app.getPath('userData'), а не в каталоге приложения, чтобы обеспечить правильные права на запись. -
Реализуйте правильное управление процессами в вашем основном процессе для запуска и остановки сервера при запуске и закрытии приложения.
-
Тщательно тестируйте как в среде разработки, так и в продакшене, чтобы убедиться, что все компоненты правильно работают вместе.
Следуя этому подходу, вы создадите упакованное Electron-приложение, которое автоматически запускает вашу серверную часть, обеспечивает полную функциональность базы данных и сохраняет всю функциональность внутри папки установки без необходимости внешних зависимостей. Конфигурация asarUnpack обеспечивает идеальный баланс между безопасностью, производительностью и функциональностью для приложений, которым необходимо выполнять серверный код и получать доступ к базам данных во время выполнения.