feat: main process and preload scripts support hot reloading (#7)
This commit is contained in:
parent
006418ab14
commit
1bd15486ea
|
@ -21,6 +21,8 @@ interface GlobalCLIOptions {
|
|||
mode?: string
|
||||
ignoreConfigWarning?: boolean
|
||||
sourcemap?: boolean
|
||||
w?: boolean
|
||||
watch?: boolean
|
||||
outDir?: string
|
||||
}
|
||||
|
||||
|
@ -34,7 +36,8 @@ function createInlineConfig(root: string, options: GlobalCLIOptions): InlineConf
|
|||
ignoreConfigWarning: options.ignoreConfigWarning,
|
||||
build: {
|
||||
sourcemap: options.sourcemap,
|
||||
outDir: options.outDir
|
||||
outDir: options.outDir,
|
||||
...(options.w || options.watch ? { watch: {} } : null)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -55,6 +58,7 @@ cli
|
|||
.command('[root]', 'start dev server and electron app')
|
||||
.alias('serve')
|
||||
.alias('dev')
|
||||
.option('-w, --watch', `[boolean] rebuilds when main process or preload script modules have changed on disk`)
|
||||
.action(async (root: string, options: GlobalCLIOptions) => {
|
||||
const { createServer } = await import('./server')
|
||||
const inlineConfig = createInlineConfig(root, options)
|
||||
|
|
52
src/electron.ts
Normal file
52
src/electron.ts
Normal file
|
@ -0,0 +1,52 @@
|
|||
import path from 'node:path'
|
||||
import fs from 'node:fs'
|
||||
import { type ChildProcessWithoutNullStreams, spawn } from 'child_process'
|
||||
import { type Logger } from 'vite'
|
||||
|
||||
const ensureElectronEntryFile = (root = process.cwd()): void => {
|
||||
const pkg = path.join(root, 'package.json')
|
||||
if (fs.existsSync(pkg)) {
|
||||
const main = require(pkg).main
|
||||
if (!main) {
|
||||
throw new Error('not found an entry point to electorn app, please add main field for your package.json')
|
||||
} else {
|
||||
const entryPath = path.resolve(root, main)
|
||||
if (!fs.existsSync(entryPath)) {
|
||||
throw new Error(`not found the electorn app entry file: ${entryPath}`)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
throw new Error('no package.json')
|
||||
}
|
||||
}
|
||||
|
||||
const getElectronPath = (): string => {
|
||||
const electronModulePath = path.resolve(process.cwd(), 'node_modules', 'electron')
|
||||
const pathFile = path.join(electronModulePath, 'path.txt')
|
||||
let executablePath
|
||||
if (fs.existsSync(pathFile)) {
|
||||
executablePath = fs.readFileSync(pathFile, 'utf-8')
|
||||
}
|
||||
if (executablePath) {
|
||||
return path.join(electronModulePath, 'dist', executablePath)
|
||||
} else {
|
||||
throw new Error('Electron uninstall')
|
||||
}
|
||||
}
|
||||
|
||||
export function startElectron(root: string | undefined, logger: Logger): ChildProcessWithoutNullStreams {
|
||||
ensureElectronEntryFile(root)
|
||||
|
||||
const electronPath = getElectronPath()
|
||||
|
||||
const ps = spawn(electronPath, ['.'])
|
||||
ps.stdout.on('data', chunk => {
|
||||
chunk.toString().trim() && logger.info(chunk.toString())
|
||||
})
|
||||
ps.stderr.on('data', chunk => {
|
||||
chunk.toString().trim() && logger.error(chunk.toString())
|
||||
})
|
||||
ps.on('close', process.exit)
|
||||
|
||||
return ps
|
||||
}
|
|
@ -1,8 +1,7 @@
|
|||
import { spawn } from 'child_process'
|
||||
import colors from 'picocolors'
|
||||
import { createLogger } from 'vite'
|
||||
import { InlineConfig } from './config'
|
||||
import { ensureElectronEntryFile, getElectronPath } from './utils'
|
||||
import type { InlineConfig } from './config'
|
||||
import { startElectron } from './electron'
|
||||
import { build } from './build'
|
||||
|
||||
export async function preview(inlineConfig: InlineConfig = {}): Promise<void> {
|
||||
|
@ -10,18 +9,7 @@ export async function preview(inlineConfig: InlineConfig = {}): Promise<void> {
|
|||
|
||||
const logger = createLogger(inlineConfig.logLevel)
|
||||
|
||||
ensureElectronEntryFile(inlineConfig.root)
|
||||
|
||||
const electronPath = getElectronPath()
|
||||
|
||||
const ps = spawn(electronPath, ['.'])
|
||||
ps.stdout.on('data', chunk => {
|
||||
chunk.toString().trim() && logger.info(chunk.toString())
|
||||
})
|
||||
ps.stderr.on('data', chunk => {
|
||||
chunk.toString().trim() && logger.error(chunk.toString())
|
||||
})
|
||||
ps.on('close', process.exit)
|
||||
startElectron(inlineConfig.root, logger)
|
||||
|
||||
logger.info(colors.green(`\nstart electron app...`))
|
||||
}
|
||||
|
|
|
@ -1,17 +1,42 @@
|
|||
import { spawn } from 'child_process'
|
||||
import { createServer as ViteCreateServer, build as viteBuild, createLogger } from 'vite'
|
||||
import type { ChildProcessWithoutNullStreams } from 'child_process'
|
||||
import {
|
||||
type UserConfig as ViteConfig,
|
||||
type ViteDevServer,
|
||||
createServer as ViteCreateServer,
|
||||
build as viteBuild,
|
||||
createLogger,
|
||||
mergeConfig
|
||||
} from 'vite'
|
||||
import colors from 'picocolors'
|
||||
import { InlineConfig, resolveConfig } from './config'
|
||||
import { ensureElectronEntryFile, getElectronPath, resolveHostname } from './utils'
|
||||
import { type InlineConfig, resolveConfig } from './config'
|
||||
import { resolveHostname } from './utils'
|
||||
import { startElectron } from './electron'
|
||||
|
||||
export async function createServer(inlineConfig: InlineConfig = {}): Promise<void> {
|
||||
const config = await resolveConfig(inlineConfig, 'serve', 'development')
|
||||
if (config.config) {
|
||||
const logger = createLogger(inlineConfig.logLevel)
|
||||
|
||||
let server: ViteDevServer | undefined
|
||||
let ps: ChildProcessWithoutNullStreams | undefined
|
||||
|
||||
const mainViteConfig = config.config?.main
|
||||
if (mainViteConfig) {
|
||||
await viteBuild(mainViteConfig)
|
||||
const watchHook = (): void => {
|
||||
logger.info(colors.green(`\nrebuild the electron main process successfully`))
|
||||
|
||||
if (ps) {
|
||||
logger.info(colors.cyan(`\n waiting for electron to exit...`))
|
||||
|
||||
ps.removeAllListeners()
|
||||
ps.kill()
|
||||
ps = startElectron(inlineConfig.root, logger)
|
||||
|
||||
logger.info(colors.green(`\nrestart electron app...`))
|
||||
}
|
||||
}
|
||||
|
||||
await doBuild(mainViteConfig, watchHook)
|
||||
|
||||
logger.info(colors.green(`\nbuild the electron main process successfully`))
|
||||
}
|
||||
|
@ -19,7 +44,18 @@ export async function createServer(inlineConfig: InlineConfig = {}): Promise<voi
|
|||
const preloadViteConfig = config.config?.preload
|
||||
if (preloadViteConfig) {
|
||||
logger.info(colors.gray(`\n-----\n`))
|
||||
await viteBuild(preloadViteConfig)
|
||||
|
||||
const watchHook = (): void => {
|
||||
logger.info(colors.green(`\nrebuild the electron preload files successfully`))
|
||||
|
||||
if (server) {
|
||||
logger.info(colors.cyan(`\n trigger renderer reload`))
|
||||
|
||||
server.ws.send({ type: 'full-reload' })
|
||||
}
|
||||
}
|
||||
|
||||
await doBuild(preloadViteConfig, watchHook)
|
||||
|
||||
logger.info(colors.green(`\nbuild the electron preload files successfully`))
|
||||
}
|
||||
|
@ -28,7 +64,7 @@ export async function createServer(inlineConfig: InlineConfig = {}): Promise<voi
|
|||
if (rendererViteConfig) {
|
||||
logger.info(colors.gray(`\n-----\n`))
|
||||
|
||||
const server = await ViteCreateServer(rendererViteConfig)
|
||||
server = await ViteCreateServer(rendererViteConfig)
|
||||
|
||||
if (!server.httpServer) {
|
||||
throw new Error('HTTP server not available')
|
||||
|
@ -52,19 +88,41 @@ export async function createServer(inlineConfig: InlineConfig = {}): Promise<voi
|
|||
server.printUrls()
|
||||
}
|
||||
|
||||
ensureElectronEntryFile(inlineConfig.root)
|
||||
|
||||
const electronPath = getElectronPath()
|
||||
|
||||
const ps = spawn(electronPath, ['.'])
|
||||
ps.stdout.on('data', chunk => {
|
||||
chunk.toString().trim() && logger.info(chunk.toString())
|
||||
})
|
||||
ps.stderr.on('data', chunk => {
|
||||
chunk.toString().trim() && logger.error(chunk.toString())
|
||||
})
|
||||
ps.on('close', process.exit)
|
||||
ps = startElectron(inlineConfig.root, logger)
|
||||
|
||||
logger.info(colors.green(`\nstart electron app...`))
|
||||
}
|
||||
}
|
||||
|
||||
type UserConfig = ViteConfig & { configFile?: string | false }
|
||||
|
||||
async function doBuild(config: UserConfig, watchHook: () => void): Promise<void> {
|
||||
return new Promise(resolve => {
|
||||
if (config.build?.watch) {
|
||||
let firstBundle = true
|
||||
const closeBundle = (): void => {
|
||||
if (firstBundle) {
|
||||
firstBundle = false
|
||||
resolve()
|
||||
} else {
|
||||
watchHook()
|
||||
}
|
||||
}
|
||||
|
||||
config = mergeConfig(config, {
|
||||
plugins: [
|
||||
{
|
||||
name: 'vite:electron-watcher',
|
||||
closeBundle
|
||||
}
|
||||
]
|
||||
})
|
||||
}
|
||||
|
||||
viteBuild(config).then(() => {
|
||||
if (!config.build?.watch) {
|
||||
resolve()
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
|
34
src/utils.ts
34
src/utils.ts
|
@ -1,43 +1,9 @@
|
|||
import * as path from 'path'
|
||||
import * as fs from 'fs'
|
||||
|
||||
export function isObject(value: unknown): value is Record<string, unknown> {
|
||||
return Object.prototype.toString.call(value) === '[object Object]'
|
||||
}
|
||||
|
||||
export const dynamicImport = new Function('file', 'return import(file)')
|
||||
|
||||
export function ensureElectronEntryFile(root = process.cwd()): void {
|
||||
const pkg = path.join(root, 'package.json')
|
||||
if (fs.existsSync(pkg)) {
|
||||
const main = require(pkg).main
|
||||
if (!main) {
|
||||
throw new Error('not found an entry point to electorn app, please add main field for your package.json')
|
||||
} else {
|
||||
const entryPath = path.resolve(root, main)
|
||||
if (!fs.existsSync(entryPath)) {
|
||||
throw new Error(`not found the electorn app entry file: ${entryPath}`)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
throw new Error('no package.json')
|
||||
}
|
||||
}
|
||||
|
||||
export function getElectronPath(): string {
|
||||
const electronModulePath = path.resolve(process.cwd(), 'node_modules', 'electron')
|
||||
const pathFile = path.join(electronModulePath, 'path.txt')
|
||||
let executablePath
|
||||
if (fs.existsSync(pathFile)) {
|
||||
executablePath = fs.readFileSync(pathFile, 'utf-8')
|
||||
}
|
||||
if (executablePath) {
|
||||
return path.join(electronModulePath, 'dist', executablePath)
|
||||
} else {
|
||||
throw new Error('Electron uninstall')
|
||||
}
|
||||
}
|
||||
|
||||
export const wildcardHosts = new Set(['0.0.0.0', '::', '0000:0000:0000:0000:0000:0000:0000:0000'])
|
||||
|
||||
export function resolveHostname(optionsHost: string | boolean | undefined): string {
|
||||
|
|
Loading…
Reference in a new issue