From f9cfcf94083afb8ededc4542d0a05f6d6457c371 Mon Sep 17 00:00:00 2001 From: xiaohui Date: Fri, 1 Jul 2022 00:33:08 +0800 Subject: [PATCH] feat: restart Electron after `main` or `preload` update. fix: preload config will error when preload config is a function. --- README.md | 29 ++++++++- README.zh-CN.md | 33 ++++++++-- src/config.ts | 2 +- src/server.ts | 160 ++++++++++++++++++++++++++++++++---------------- 4 files changed, 162 insertions(+), 62 deletions(-) diff --git a/README.md b/README.md index a64139e..7879568 100644 --- a/README.md +++ b/README.md @@ -18,6 +18,7 @@ English | [简体中文](./README.zh-CN.md) - 📃Main process, renderer process and preload script Vite configuration combined into one file - 📦Preset optimal build configuration - 🚀HMR for renderer processes +- 🌈Restart Electron after `main` or `preload` update ## Usage @@ -162,7 +163,7 @@ See [vitejs.dev](https://vitejs.dev/config) ### Config presets -#### Build options for `main`: +#### Build options for `main` - **outDir**: `out\main`(relative to project root) - **target**: `node*`, automatically match node target of `Electron`. For example, the node target of Electron 17 is `node16.13` @@ -170,7 +171,7 @@ See [vitejs.dev](https://vitejs.dev/config) - **lib.formats**: `cjs` - **rollupOptions.external**: `electron` and all builtin modules -#### Build options for `preload`: +#### Build options for `preload` - **outDir**: `out\preload`(relative to project root) - **target**: the same as `main` @@ -178,7 +179,7 @@ See [vitejs.dev](https://vitejs.dev/config) - **lib.formats**: `cjs` - **rollupOptions.external**: the same as `main` -#### Build options for `renderer`: +#### Build options for `renderer` - **root**: `src\renderer`(relative to project root) - **outDir**: `out\renderer`(relative to project root) @@ -235,6 +236,28 @@ export default { } ``` +#### How to restart Electron after `main` or `preload` update? + +```js +export default defineConfig({ + main: ({ command }) => ({ + build: { + watch: command === 'serve' ? {} : undefined, + // ... + }, + }), + preload: ({ command }) => ({ + build: { + watch: command === 'serve' ? {} : undefined, + // ... + } + }), + renderer: { + // ... + } +}) +``` + ## CLI options For the full list of CLI options, you can run `npx electron-vite -h` in your project. The flags listed below are only available via the command line interface: diff --git a/README.zh-CN.md b/README.zh-CN.md index 3aafa10..12f61d9 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -18,6 +18,7 @@ - 📃 统一所有配置,合并到一个文件中 - 📦 预设构建配置,无需关注配置 - 🚀 支持渲染进程热更新(HMR) +- 🌈 支持 `main` 更新后 Electron 重启和 `preload` 更新后重新挂载 ## 用法 @@ -162,7 +163,7 @@ export default defineConfig({ ### 配置预设 -#### `主进程`编译项预设: +#### `主进程`编译项预设 - **outDir**:`out\main`(相对于根目录) - **target**:`node*`,自动匹配 `Electron` 的 `node` 构建目标,如 Electron 17 为 `node16.13` @@ -170,7 +171,7 @@ export default defineConfig({ - **lib.formats**:`cjs` - **rollupOptions.external**:`electron` 和所有内置 node 模块(如果用户配置了外部模块 ID,将自动合并) -#### `preload` 脚本编译项预设: +#### `preload` 脚本编译项预设 - **outDir**:`out\preload`(相对于根目录) - **target**:同`主进程` @@ -178,7 +179,7 @@ export default defineConfig({ - **lib.formats**:`cjs` - **rollupOptions.external**:同`主进程` -#### `渲染进程`编译项预设: +#### `渲染进程`编译项预设 - **root**:`src\renderer`(相对于根目录) - **outDir**:`out\renderer`(相对于根目录) @@ -187,7 +188,7 @@ export default defineConfig({ - **polyfillModulePreload**:`false`,不需要为渲染进程 polyfill `Module Preload` - **rollupOptions.external**:同`主进程` -#### `主进程`和 `preload` 脚本的 `define` 项设置: +#### `主进程`和 `preload` 脚本的 `define` 项设置 在 Web 开发中,Vite 会将 `'process.env.'` 替换为 `'({}).'`,这是合理和正确的。但在 nodejs 开发中,我们有时候需要使用 `process.env` ,所以 `electron-vite` 重新预设全局变量替换,恢复其使用,预设如下: @@ -235,6 +236,30 @@ export default { } ``` +#### 如何支持 `main` 更新后 Electron 重启和 `preload` 更新后重新挂载? + +在 `serve` 命令下,`main` 和 `preload` 脚本更新后,你可能想要让 Electron 重启和 `preload` 重新挂载,你可以在配置中这样做: + +```js +export default defineConfig({ + main: ({ command }) => ({ + build: { + watch: command === 'serve' ? {} : undefined, + // ... + }, + }), + preload: ({ command }) => ({ + build: { + watch: command === 'serve' ? {} : undefined, + // ... + } + }), + renderer: { + // ... + } +}) +``` + ## 命令行选项 在项目中,可运行 `npx electron-vite -h` 获得完整的命令行选项列表。下面列出的标志只能通过命令行使用: diff --git a/src/config.ts b/src/config.ts index c9b6078..319ae1d 100644 --- a/src/config.ts +++ b/src/config.ts @@ -287,7 +287,7 @@ export async function loadConfigFromFile( if (config.preload) { const preloadViteConfig = config.preload preloadConfig = await (typeof preloadViteConfig === 'function' ? preloadViteConfig(configEnv) : preloadViteConfig) - if (!isObject(preloadViteConfig)) { + if (!isObject(preloadConfig)) { throw new Error(`preload config must export or return an object`) } } else { diff --git a/src/server.ts b/src/server.ts index 7214636..b937253 100644 --- a/src/server.ts +++ b/src/server.ts @@ -1,70 +1,122 @@ -import { spawn } from 'child_process' -import { createServer as ViteCreateServer, build as viteBuild, createLogger } from 'vite' +import { ChildProcessWithoutNullStreams, spawn } from 'child_process' +import { + createServer as ViteCreateServer, + build as viteBuild, + createLogger, + Logger, + ViteDevServer, + UserConfig, + mergeConfig +} from 'vite' import colors from 'picocolors' import { InlineConfig, resolveConfig } from './config' import { ensureElectronEntryFile, getElectronPath, resolveHostname } from './utils' +import { PluginHooks } from 'rollup' + +export function createElectron(root?: string, logger?: Logger): ChildProcessWithoutNullStreams { + ensureElectronEntryFile(root) + const electronPath = getElectronPath() + const ps = spawn(electronPath, ['.']) + + ps.stdout.on('data', chunk => { + chunk.toString().trim() && logger && logger.info(chunk.toString()) + }) + ps.stderr.on('data', chunk => { + chunk.toString().trim() && logger && logger.error(chunk.toString()) + }) + ps.on('close', process.exit) + + return ps +} + +export function build(config: UserConfig, closeBundle?: PluginHooks['closeBundle']): ReturnType { + const watchConfig: UserConfig = { + // Enable watch through `electron.vite.config.{ js | ts | mjs }` + // build: { watch: {} }, + plugins: [ + { + name: 'vite:electron-serve-build', + closeBundle + } + ] + } + + return viteBuild(mergeConfig(watchConfig, config)) +} + +export async function createRenderServer(config: UserConfig): Promise { + const server = await ViteCreateServer(config) + + if (!server.httpServer) { + throw new Error('HTTP server not available') + } + + await server.listen() + + const conf = server.config.server + const protocol = conf.https ? 'https:' : 'http:' + const host = resolveHostname(conf.host) + const port = conf.port + process.env.ELECTRON_RENDERER_URL = `${protocol}//${host}:${port}` + + const slogger = server.config.logger + + slogger.info(colors.green(`dev server running for the electron renderer process at:\n`), { + clear: !slogger.hasWarned + }) + + server.printUrls() + + return server +} export async function createServer(inlineConfig: InlineConfig = {}): Promise { const config = await resolveConfig(inlineConfig, 'serve', 'development') - if (config.config) { - const logger = createLogger(inlineConfig.logLevel) + const { main, preload, renderer } = config.config || {} + const logger = createLogger(inlineConfig.logLevel) - const mainViteConfig = config.config?.main - if (mainViteConfig) { - await viteBuild(mainViteConfig) + let server: ViteDevServer | undefined + let ps: ChildProcessWithoutNullStreams | undefined + if (main) { + await build(main, () => { logger.info(colors.green(`\nbuild the electron main process successfully`)) - } - const preloadViteConfig = config.config?.preload - if (preloadViteConfig) { - logger.info(colors.gray(`\n-----\n`)) - await viteBuild(preloadViteConfig) + if (ps) { + logger.info(colors.green(`\nwaiting for electron to exit...`)) - logger.info(colors.green(`\nbuild the electron preload files successfully`)) - } + ps.removeAllListeners() + ps.kill() + ps = createElectron(inlineConfig.root, logger) - const rendererViteConfig = config.config?.renderer - if (rendererViteConfig) { - logger.info(colors.gray(`\n-----\n`)) - - const server = await ViteCreateServer(rendererViteConfig) - - if (!server.httpServer) { - throw new Error('HTTP server not available') + logger.info(colors.green(`\n🚀 restart electron app...`)) } - - await server.listen() - - const conf = server.config.server - - const protocol = conf.https ? 'https:' : 'http:' - const host = resolveHostname(conf.host) - const port = conf.port - process.env.ELECTRON_RENDERER_URL = `${protocol}//${host}:${port}` - - const slogger = server.config.logger - - slogger.info(colors.green(`dev server running for the electron renderer process at:\n`), { - clear: !slogger.hasWarned - }) - - 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) - - logger.info(colors.green(`\nstart electron app...`)) } + + if (preload) { + logger.info(colors.gray(`\n-----\n`)) + + await build(preload, () => { + logger.info(colors.green(`\nbuild the electron preload files successfully`)) + + if (server) { + logger.info(colors.green(`\nwaiting for render page to reload...`)) + + server.ws.send({ type: 'full-reload' }) + + logger.info(colors.green(`\nreload render page successfully`)) + } + }) + } + + if (renderer) { + logger.info(colors.gray(`\n-----\n`)) + + server = await createRenderServer(renderer) + } + + ps = createElectron(inlineConfig.root, logger) + + logger.info(colors.green(`\n🚀 start electron app...`)) }