feat: add Electron hot-reload development mode

- Add electron-dev.mjs script for hot-reload development
- Support automatic Electron dependency building
- Start Remix dev server and Electron app concurrently
- Add proper process management and cleanup
- Fix preload script path for development mode
- Add electron:dev and electron:dev:inspect npm scripts

This enables developers to run 'pnpm electron:dev' for a complete
hot-reload development experience with automatic rebuilding and
process management.
This commit is contained in:
zhaomenghuan02
2025-09-13 00:19:42 +08:00
parent 4ca535b9d1
commit 33725102e2
3 changed files with 187 additions and 1 deletions

View File

@@ -9,6 +9,9 @@ export function createWindow(rendererURL: string) {
const bounds = store.get('bounds');
console.log('restored bounds:', bounds);
// preload path
const preloadPath = path.join(isDev ? process.cwd() : app.getAppPath(), 'build', 'electron', 'preload', 'index.cjs');
const win = new BrowserWindow({
...{
width: 1200,
@@ -18,7 +21,7 @@ export function createWindow(rendererURL: string) {
vibrancy: 'under-window',
visualEffectState: 'active',
webPreferences: {
preload: path.join(app.getAppPath(), 'build', 'electron', 'preload', 'index.cjs'),
preload: preloadPath,
},
});

View File

@@ -30,6 +30,8 @@
"preview": "pnpm run build && pnpm run start",
"prepare": "husky",
"clean": "node scripts/clean.js",
"electron:dev": "node scripts/electron-dev.mjs",
"electron:dev:inspect": "NODE_ENV=development electron --inspect=9229 build/electron/main/index.mjs",
"electron:build:deps": "concurrently \"pnpm electron:build:main\" \"pnpm electron:build:preload\" --kill-others-on-fail",
"electron:build:main": "vite build --config ./electron/main/vite.config.ts",
"electron:build:preload": "vite build --config ./electron/preload/vite.config.ts",

181
scripts/electron-dev.mjs Normal file
View File

@@ -0,0 +1,181 @@
#!/usr/bin/env node
/**
* Electron Development Script
*
* This script provides hot-reload development mode for Electron applications.
* It automatically builds Electron dependencies, starts the Remix development server,
* and launches the Electron application with hot-reload capabilities.
*/
import { spawn, exec } from 'node:child_process';
import path from 'node:path';
import fs from 'node:fs';
import { fileURLToPath } from 'node:url';
// Get current file directory
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
// Constants
const REMIX_PORT = 5173;
const CHECK_INTERVAL = 1000;
const MAX_RETRIES = 30;
// Set environment variables
process.env.NODE_ENV = 'development';
console.log('🚀 Starting Electron hot-reload development mode...');
console.log('🔧 Environment:', process.env.NODE_ENV);
let electronProcess = null;
let remixProcess = null;
/**
* Cleanup function to gracefully shutdown all processes
*/
function cleanup() {
console.log('\n🛑 Shutting down all processes...');
if (electronProcess) {
electronProcess.kill('SIGTERM');
}
if (remixProcess) {
remixProcess.kill('SIGTERM');
}
process.exit(0);
}
// Handle process exit signals
process.on('SIGINT', cleanup);
process.on('SIGTERM', cleanup);
/**
* Wait for a server to start on the specified port
* @param {number} port - Port number to check
* @param {string} serverName - Name of the server for logging
* @returns {Promise<void>}
*/
async function waitForServer(port, serverName) {
return new Promise((resolve, reject) => {
let retries = 0;
const checkServer = () => {
exec(`lsof -i :${port}`, (error, stdout) => {
if (stdout) {
console.log(`${serverName} started`);
resolve();
} else if (retries >= MAX_RETRIES) {
reject(new Error(`Timeout waiting for ${serverName} to start`));
} else {
retries++;
setTimeout(checkServer, CHECK_INTERVAL);
}
});
};
checkServer();
});
}
/**
* Build Electron dependencies
* @returns {Promise<void>}
*/
async function buildElectronDeps() {
return new Promise((resolve, reject) => {
const buildProcess = spawn('pnpm', ['electron:build:deps'], {
stdio: 'inherit',
env: { ...process.env },
});
buildProcess.on('close', (code) => {
if (code === 0) {
console.log('✅ Electron dependencies built successfully');
resolve();
} else {
reject(new Error(`Build failed with exit code: ${code}`));
}
});
buildProcess.on('error', (error) => {
reject(new Error(`Build process error: ${error.message}`));
});
});
}
/**
* Main function to start Electron development mode
* @returns {Promise<void>}
*/
async function startElectronDev() {
try {
// 1. Build Electron dependencies first
console.log('📦 Building Electron dependencies...');
await buildElectronDeps();
// 2. Start Remix development server
console.log('🌐 Starting Remix development server...');
remixProcess = spawn('pnpm', ['dev'], {
stdio: 'pipe',
env: { ...process.env },
});
remixProcess.stdout.on('data', (data) => {
const output = data.toString();
console.log(`[Remix] ${output.trim()}`);
});
remixProcess.stderr.on('data', (data) => {
console.error(`[Remix Error] ${data.toString().trim()}`);
});
// Wait for Remix server to start
await waitForServer(REMIX_PORT, 'Remix development server');
// 3. Start Electron application
console.log('⚡ Starting Electron application...');
const electronPath = path.join(__dirname, '..', 'node_modules', '.bin', 'electron');
const mainPath = path.join(__dirname, '..', 'build', 'electron', 'main', 'index.mjs');
// Check if main process file exists
if (!fs.existsSync(mainPath)) {
throw new Error(`Main process file not found: ${mainPath}`);
}
electronProcess = spawn(electronPath, [mainPath], {
stdio: 'inherit',
env: {
...process.env,
NODE_ENV: 'development',
ELECTRON_IS_DEV: '1',
},
});
electronProcess.on('error', (error) => {
console.error('❌ Failed to start Electron:', error);
cleanup();
});
electronProcess.on('exit', (code) => {
console.log(`📱 Electron process exited with code: ${code}`);
if (code !== 0) {
cleanup();
}
});
console.log('🎉 Electron hot-reload development mode started!');
console.log('💡 Code changes will automatically reload');
console.log('🛑 Press Ctrl+C to exit');
} catch (error) {
console.error('❌ Startup failed:', error.message);
cleanup();
}
}
// Start development mode
startElectronDev();