mirror of
https://github.com/alex8088/electron-vite.git
synced 2026-04-30 01:38:14 +08:00
Spawn the Electron binary directly in dev/preview mode but forward SIGINT/SIGTERM/SIGHUP from the electron-vite parent to the child, matching Electron's own CLI wrapper (node_modules/electron/cli.js). Without this, Ctrl-C sends SIGINT to the foreground process group once: the Node parent exits immediately (no handler) and the Electron child gets only a single SIGINT. On macOS, an Electron app whose before-quit handler calls preventDefault() and later calls app.quit() can stall in AppKit's event loop in that path. Under a direct 'electron .' launch the CLI shim forwards SIGINT as well, so Electron gets it twice and exits cleanly. Matching that behavior here fixes the hang. Fixes #899
193 lines
5.1 KiB
TypeScript
193 lines
5.1 KiB
TypeScript
import path from 'node:path'
|
|
import fs from 'node:fs'
|
|
import { createRequire } from 'node:module'
|
|
import { type ChildProcess, spawn } from 'node:child_process'
|
|
import { loadPackageData } from './utils'
|
|
|
|
const _require = createRequire(import.meta.url)
|
|
|
|
const ensureElectronEntryFile = (root = process.cwd()): void => {
|
|
if (process.env.ELECTRON_ENTRY) return
|
|
const pkg = loadPackageData()
|
|
if (pkg) {
|
|
if (!pkg.main) {
|
|
throw new Error('No entry point found for electron app, please add a "main" field to package.json')
|
|
} else {
|
|
const entryPath = path.resolve(root, pkg.main)
|
|
if (!fs.existsSync(entryPath)) {
|
|
throw new Error(`No electron app entry file found: ${entryPath}`)
|
|
}
|
|
}
|
|
} else {
|
|
throw new Error('Not found: package.json')
|
|
}
|
|
}
|
|
|
|
const getElectronMajorVer = (): string => {
|
|
let majorVer = process.env.ELECTRON_MAJOR_VER || ''
|
|
if (!majorVer) {
|
|
const pkg = _require.resolve('electron/package.json')
|
|
if (fs.existsSync(pkg)) {
|
|
const version = _require(pkg).version
|
|
majorVer = version.split('.')[0]
|
|
process.env.ELECTRON_MAJOR_VER = majorVer
|
|
}
|
|
}
|
|
return majorVer
|
|
}
|
|
|
|
export function supportESM(): boolean {
|
|
const majorVer = getElectronMajorVer()
|
|
return parseInt(majorVer) >= 28
|
|
}
|
|
|
|
export function supportImportMetaPaths(): boolean {
|
|
const majorVer = getElectronMajorVer()
|
|
return parseInt(majorVer) >= 30
|
|
}
|
|
|
|
export function getElectronPath(): string {
|
|
let electronExecPath = process.env.ELECTRON_EXEC_PATH || ''
|
|
if (!electronExecPath) {
|
|
const electronModulePath = path.dirname(_require.resolve('electron'))
|
|
const pathFile = path.join(electronModulePath, 'path.txt')
|
|
let executablePath
|
|
if (fs.existsSync(pathFile)) {
|
|
executablePath = fs.readFileSync(pathFile, 'utf-8')
|
|
}
|
|
if (executablePath) {
|
|
electronExecPath = path.join(electronModulePath, 'dist', executablePath)
|
|
process.env.ELECTRON_EXEC_PATH = electronExecPath
|
|
} else {
|
|
throw new Error('Electron uninstall')
|
|
}
|
|
}
|
|
return electronExecPath
|
|
}
|
|
|
|
export function getElectronNodeTarget(): string {
|
|
const electronVer = getElectronMajorVer()
|
|
|
|
const nodeVer = {
|
|
'41': '24.14',
|
|
'40': '24.14',
|
|
'39': '22.20',
|
|
'38': '22.19',
|
|
'37': '22.16',
|
|
'36': '22.14',
|
|
'35': '22.14',
|
|
'34': '20.18',
|
|
'33': '20.18',
|
|
'32': '20.16',
|
|
'31': '20.14',
|
|
'30': '20.11',
|
|
'29': '20.9',
|
|
'28': '18.18',
|
|
'27': '18.17',
|
|
'26': '18.16',
|
|
'25': '18.15',
|
|
'24': '18.14',
|
|
'23': '18.12',
|
|
'22': '16.17'
|
|
}
|
|
if (electronVer && parseInt(electronVer) > 10) {
|
|
let target = nodeVer[electronVer]
|
|
if (!target) target = Object.values(nodeVer).reverse()[0]
|
|
return 'node' + target
|
|
}
|
|
return ''
|
|
}
|
|
|
|
export function getElectronChromeTarget(): string {
|
|
const electronVer = getElectronMajorVer()
|
|
|
|
const chromeVer = {
|
|
'41': '146',
|
|
'40': '144',
|
|
'39': '142',
|
|
'38': '140',
|
|
'37': '138',
|
|
'36': '136',
|
|
'35': '134',
|
|
'34': '132',
|
|
'33': '130',
|
|
'32': '128',
|
|
'31': '126',
|
|
'30': '124',
|
|
'29': '122',
|
|
'28': '120',
|
|
'27': '118',
|
|
'26': '116',
|
|
'25': '114',
|
|
'24': '112',
|
|
'23': '110',
|
|
'22': '108'
|
|
}
|
|
if (electronVer && parseInt(electronVer) > 10) {
|
|
let target = chromeVer[electronVer]
|
|
if (!target) target = Object.values(chromeVer).reverse()[0]
|
|
return 'chrome' + target
|
|
}
|
|
return ''
|
|
}
|
|
|
|
export function startElectron(root: string | undefined): ChildProcess {
|
|
ensureElectronEntryFile(root)
|
|
|
|
const electronPath = getElectronPath()
|
|
|
|
const isDev = process.env.NODE_ENV_ELECTRON_VITE === 'development'
|
|
|
|
const args: string[] = process.env.ELECTRON_CLI_ARGS ? JSON.parse(process.env.ELECTRON_CLI_ARGS) : []
|
|
|
|
if (!!process.env.REMOTE_DEBUGGING_PORT && isDev) {
|
|
args.push(`--remote-debugging-port=${process.env.REMOTE_DEBUGGING_PORT}`)
|
|
}
|
|
|
|
if (!!process.env.V8_INSPECTOR_PORT && isDev) {
|
|
args.push(`--inspect=${process.env.V8_INSPECTOR_PORT}`)
|
|
}
|
|
|
|
if (!!process.env.V8_INSPECTOR_BRK_PORT && isDev) {
|
|
args.push(`--inspect-brk=${process.env.V8_INSPECTOR_BRK_PORT}`)
|
|
}
|
|
|
|
if (process.env.NO_SANDBOX === '1') {
|
|
args.push('--no-sandbox')
|
|
}
|
|
|
|
const entry = process.env.ELECTRON_ENTRY || '.'
|
|
|
|
const ps = spawn(electronPath, [entry].concat(args), { stdio: 'inherit' })
|
|
ps.on('close', process.exit)
|
|
|
|
forwardTerminationSignals(ps)
|
|
|
|
return ps
|
|
}
|
|
|
|
let signalHandlersInstalled = false
|
|
let currentElectronPs: ChildProcess | undefined
|
|
|
|
// Forward termination signals to the Electron child, matching electron's own
|
|
// CLI wrapper (node_modules/electron/cli.js). Without this, Ctrl-C kills the
|
|
// parent immediately and the Electron child can get stuck on macOS when a
|
|
// before-quit handler calls preventDefault then later app.quit(). See #899.
|
|
function forwardTerminationSignals(ps: ChildProcess): void {
|
|
currentElectronPs = ps
|
|
if (signalHandlersInstalled) return
|
|
signalHandlersInstalled = true
|
|
for (const signal of ['SIGINT', 'SIGTERM', 'SIGHUP'] as const) {
|
|
process.on(signal, () => {
|
|
const target = currentElectronPs
|
|
if (target && target.exitCode === null && !target.killed) {
|
|
try {
|
|
target.kill(signal)
|
|
} catch {
|
|
// already gone
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|