Другое

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:

json
"build": {
  "appId": "com.yourcompany.matjari",
  "files": [
    "main.js",
    "Server/**",
    "client/build/**"
  ],
  "asar": false
}

Какая правильная конфигурация для Electron Builder, чтобы:

  1. Включить папку /Server и базу данных SQLite в упакованное приложение
  2. Обеспечить запуск сервера при запуске собранного приложения (.exe)
  3. Разрешить запись и доступ к базе данных во время выполнения

Стоит ли использовать “asar”: true и asarUnpack? Стоит ли переместить базу данных в другую директорию, например resources/? Какова рекомендуемая структура для Electron-приложений, включающих бэкенд-сервер и SQLite?

Моя цель - создать упакованное приложение (.exe), которое:

  • Автоматически запускает Express-сервер
  • Нормально загружает и использует базу данных SQLite
  • Хранит всё внутри папки установки (без необходимости внешних установок)

Правильная упаковка Electron-приложения с сервером и базой данных SQLite с помощью Electron Builder

Чтобы правильно упаковать ваше Electron-приложение с серверной частью и базой данных SQLite с помощью Electron Builder, необходимо использовать конфигурацию asarUnpack для извлечения файлов сервера и базы данных из доступного только для чтения ASAR-архива. Рекомендуемый подход — установить "asar": true с определенными правилами asarUnpack для файлов сервера и базы данных, а также изменить код инициализации сервера для работы с распакованной файловой структурой.

Понимание проблемы упаковки ASAR

Electron использует ASAR-архивы для упаковки файлов приложения в единый исполняемый формат. Однако это создает файловую систему доступную только для чтения, что препятствует выполнению необходимых операций для серверных приложений:

  • Базы данных SQLite нельзя записывать при упаковке внутри ASAR
  • Файлы серверной части нельзя выполнять изнутри архива
  • Нативные модули, такие как sqlite3, требуют распакованных двоичных файлов для правильной работы

Согласно результатам исследований, “Electron упаковывает файлы в доступный только для чтения ASAR-архив, и любой файл, в который нам нужно записывать (например, базу данных SQLite), не может быть включен”. Это корневая причина ваших проблем, когда база данных не найдена, а сервер не запускается после упаковки.

Сообщение предупреждения, которое вы видите — “использование asar отключено — это настоятельно не рекомендуется” — указывает на то, что полное отключение ASAR не является оптимальным решением, так как это увеличивает размер файла и снижает безопасность.

Рекомендуемые параметры конфигурации

1. Использование конфигурации asarUnpack

Наиболее эффективное решение — использовать asarUnpack для извлечения определенных папок из ASAR-архива, сохраняя при этом включенный ASAR для остальной части приложения:

json
"build": {
  "appId": "com.yourcompany.matjari",
  "asar": true,
  "asarUnpack": [
    "Server/**"
  ],
  "files": [
    "main.js",
    "client/build/**"
  ]
}

2. Использование конфигурации extraResources

Для дополнительного контроля над размещением ресурсов можно также использовать свойство extraResources:

json
"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 для поиска правильного пути к ресурсам:

javascript
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 для правильной инициализации сервера в обеих средах:

javascript
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 использует доступное для записи расположение:

javascript
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

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 вашего сервера для обработки упакованной среды:

javascript
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 для правильного запуска сервера:

javascript
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. Тестирование в разработке

Перед сборкой протестируйте вашу конфигурацию в разработке:

bash
npm start

Убедитесь, что:

  • Сервер запускается корректно
  • API-вызовы работают как ожидается
  • Операции с базой данных функционируют нормально

2. Тестирование в продакшене

Соберите и протестируйте ваше упакованное приложение:

bash
npm run dist

Протестируйте следующие сценарии:

  • Приложение запускается без ошибок
  • Сервер запускается автоматически
  • API-вызовы возвращают правильные данные
  • Операции с базой данных работают корректно
  • Приложение закрывается правильно

3. Распространенные проблемы и решения

Проблема: Сервер не запускается после упаковки
Решение: Убедитесь, что asarUnpack сконфигурирован правильно, а пути к серверу разрешаются корректно

Проблема: База данных не найдена
Решение: Используйте app.getPath('userData') для хранения базы данных вместо путей относительно приложения

Проблема: Ошибки нативных модулей
Решение: Добавьте нативные модули в asarUnpack или используйте extraResources

Проблема: Процесс сервера не очищается
Решение: Добавьте правильную очистку процесса в app.on('will-quit')

Альтернативные подходы

1. Внешнее расположение базы данных

Для приложений, которым нужно обмениваться данными между пользователями или сессиями:

json
"build": {
  "asar": true,
  "extraResources": [
    {
      "from": "Server",
      "to": "Server",
      "filter": ["**/*"]
    }
  ]
}

И измените путь к базе данных для использования общего расположения:

javascript
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. Архитектура с отдельными процессами

Для более сложных приложений рассмотрите возможность запуска сервера как отдельного процесса:

javascript
// В 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:

bash
npm install better-sqlite3

Этот нативный модуль часто лучше работает с упаковкой Electron и может быть сконфигурирован с:

json
"asarUnpack": [
  "node_modules/better-sqlite3/**/*"
]

Источники

  1. Electron Builder Common Configuration - electron.build
  2. How to Build an Electron Desktop App in JavaScript - freeCodeCamp
  3. Electron - How to setup db with sqlite in Windows - Stack Overflow
  4. sqlite3 db-file is not found within app.asar - GitHub Issue
  5. Native Dependency loading Error for package sqlite-vec - GitHub Issue
  6. Using Prisma with the Electron Framework - Lightrun
  7. Distribution Guide - electron-vite
  8. How to externalise folder with electron? - Stack Overflow

Заключение

Чтобы успешно упаковать ваше Electron-приложение с серверной частью и базой данных SQLite, следуйте этим ключевым рекомендациям:

  1. Используйте asarUnpack для папки вашего сервера, чтобы извлечь ее из доступного только для чтения ASAR-архива, сохраняя при этом включенный ASAR для безопасности и производительности.

  2. Измените код инициализации вашего сервера, чтобы правильно разрешать пути как в среде разработки, так и в продакшене, используя app.getAppPath() и app.getPath('userData').

  3. Храните вашу базу данных SQLite в доступном для записи расположении, таком как app.getPath('userData'), а не в каталоге приложения, чтобы обеспечить правильные права на запись.

  4. Реализуйте правильное управление процессами в вашем основном процессе для запуска и остановки сервера при запуске и закрытии приложения.

  5. Тщательно тестируйте как в среде разработки, так и в продакшене, чтобы убедиться, что все компоненты правильно работают вместе.

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

Авторы
Проверено модерацией
Модерация