From c58fbf9c674f08b020455f01c97b99d3d7b562a9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Leo=20Wang=28=E8=8D=89=E9=9E=8B=E6=B2=A1=E5=8F=B7=29?= <308487730@qq.com> Date: Sat, 16 Nov 2024 18:50:38 +0800 Subject: [PATCH 1/3] feat: mock index.html for support use the main process only --- src/index.ts | 11 +++++++++ src/utils.ts | 63 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 74 insertions(+) diff --git a/src/index.ts b/src/index.ts index 72b56a6..46b3f22 100644 --- a/src/index.ts +++ b/src/index.ts @@ -7,6 +7,8 @@ import { import { resolveServerUrl, resolveViteConfig, + resolveInput, + mockIndexHtml, withExternalBuiltins, treeKillSync, } from './utils' @@ -51,6 +53,7 @@ export default function electron(options: ElectronOptions | ElectronOptions[]): const optionsArray = Array.isArray(options) ? options : [options] let userConfig: UserConfig let configEnv: ConfigEnv + let mockdInput: Awaited> | undefined return [ { @@ -120,7 +123,15 @@ export default function electron(options: ElectronOptions | ElectronOptions[]): // Make sure that Electron can be loaded into the local file using `loadFile` after packaging. config.base ??= './' }, + async configResolved(config) { + const input = resolveInput(config) + if (input == null) { + mockdInput = await mockIndexHtml(config) + } + }, async closeBundle() { + mockdInput?.remove() + for (const options of optionsArray) { options.vite ??= {} options.vite.mode ??= configEnv.mode diff --git a/src/utils.ts b/src/utils.ts index 2776938..ae6f404 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -5,6 +5,7 @@ import type { AddressInfo } from 'node:net' import { builtinModules } from 'node:module' import { type InlineConfig, + type ResolvedConfig, type ViteDevServer, mergeConfig, } from 'vite' @@ -137,6 +138,68 @@ export function resolvePackageJson(root = process.cwd()): { } } +/** @see https://github.com/vitejs/vite/blob/v5.4.9/packages/vite/src/node/build.ts#L489-L504 */ +export function resolveInput(config: ResolvedConfig) { + const options = config.build + const { root } = config + const libOptions = options.lib + + const resolve = (p: string) => path.resolve(root, p) + const input = libOptions + ? options.rollupOptions?.input || + (typeof libOptions.entry === 'string' + ? resolve(libOptions.entry) + : Array.isArray(libOptions.entry) + ? libOptions.entry.map(resolve) + : Object.fromEntries( + Object.entries(libOptions.entry).map(([alias, file]) => [ + alias, + resolve(file), + ]), + )) + : options.rollupOptions?.input + + if (input) return input + + const indexHtml = resolve('index.html') + return fs.existsSync(indexHtml) ? indexHtml : undefined +} + +/** + * When run the `vite build` command, there must be an entry file. + * If the user does not need Renderer, we need to create a temporary entry file to avoid Vite throw error. + * @inspired https://github.com/vitejs/vite/blob/v5.4.9/packages/vite/src/node/config.ts#L1234-L1236 + */ +export async function mockIndexHtml(config: ResolvedConfig) { + const { root, build } = config + const output = path.resolve(root, build.outDir) + const content = ` + + + + vite-plugin-electron + + +
An entry file for electron renderer process.
+ + +`.trim() + const index = 'index.html' + const filepath = path.join(root, index) + const distpath = path.join(output, index) + + await fs.promises.writeFile(filepath, content) + + return { + async remove() { + await fs.promises.unlink(filepath) + await fs.promises.unlink(distpath) + }, + filepath, + distpath, + } +} + /** * Inspired `tree-kill`, implemented based on sync-api. #168 * @see https://github.com/pkrumins/node-tree-kill/blob/v1.2.2/index.js From 638d0f331bb8d24557c30b55137f1af756a216e0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Leo=20Wang=28=E8=8D=89=E9=9E=8B=E6=B2=A1=E5=8F=B7=29?= <308487730@qq.com> Date: Sat, 16 Nov 2024 20:25:27 +0800 Subject: [PATCH 2/3] feat: support main process control hot-reload --- src/index.ts | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/src/index.ts b/src/index.ts index 46b3f22..1fe907b 100644 --- a/src/index.ts +++ b/src/index.ts @@ -92,11 +92,14 @@ export default function electron(options: ElectronOptions | ElectronOptions[]): options.onstart.call(this, { startup, // Why not use Vite's built-in `/@vite/client` to implement Hot reload? - // Because Vite only inserts `/@vite/client` into the `*.html` entry file. + // Because Vite only inserts `/@vite/client` into the `*.html` entry file, the preload scripts are usually a `*.js` file. // @see - https://github.com/vitejs/vite/blob/v5.2.11/packages/vite/src/node/server/middlewares/indexHtml.ts#L399 reload() { if (process.electronApp) { (server.hot || server.ws).send({ type: 'full-reload' }) + + // For Electron apps that don't need to use the renderer process. + startup.send('electron-vite&type=hot-reload') } else { startup() } @@ -165,7 +168,10 @@ export async function startup( await startup.exit() // Start Electron.app - process.electronApp = spawn(electronPath, argv, { stdio: 'inherit', ...options }) + process.electronApp = spawn(electronPath, argv, { + stdio: ['inherit', 'inherit', 'inherit', 'ipc'], + ...options, + }) // Exit command after Electron.app exits process.electronApp.once('exit', process.exit) @@ -175,6 +181,14 @@ export async function startup( process.once('exit', startup.exit) } } + +startup.send = (message: string) => { + if (process.electronApp) { + // Based on { stdio: [,,, 'ipc'] } + process.electronApp.send?.(message) + } +} + startup.hookedProcessExit = false startup.exit = async () => { if (process.electronApp) { From e6787c7f1c242cdd255cff1594ec160d97ee9656 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Leo=20Wang=28=E8=8D=89=E9=9E=8B=E6=B2=A1=E5=8F=B7=29?= <308487730@qq.com> Date: Sat, 16 Nov 2024 20:56:07 +0800 Subject: [PATCH 3/3] v0.29.0 --- CHANGELOG.md | 23 +++++++++++++++++++++++ README.md | 18 ++++++++++++++++++ package.json | 4 ++-- 3 files changed, 43 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ec4dc5e..bfe3d33 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,26 @@ +## 0.29.0 (2024-11-16) + +- 638d0f3 feat: support main process control hot-reload +- c58fbf9 feat: mock index.html for support use the main process only + +**Hot Reload** + +Since `v0.29.0`, when preload scripts are rebuilt, they will send an `electron-vite&type=hot-reload` event to the main process. +If your App doesn't need a renderer process, this will give you **hot-reload**. + +```js +// electron/main.ts + +process.on('message', (msg) => { + if (msg === 'electron-vite&type=hot-reload') { + for (const win of BrowserWindow.getAllWindows()) { + // Hot reload preload scripts + win.webContents.reload() + } + } +}) +``` + ## 0.28.8 (2024-09-19) - 3239718 fix: better exit app #251 diff --git a/README.md b/README.md index 5f05266..f313aac 100644 --- a/README.md +++ b/README.md @@ -246,6 +246,24 @@ build({ }) ``` +**Hot Reload** + +Since `v0.29.0`, when preload scripts are rebuilt, they will send an `electron-vite&type=hot-reload` event to the main process. +If your App doesn't need a renderer process, this will give you **hot-reload**. + +```js +// electron/main.ts + +process.on('message', (msg) => { + if (msg === 'electron-vite&type=hot-reload') { + for (const win of BrowserWindow.getAllWindows()) { + // Hot reload preload scripts + win.webContents.reload() + } + } +}) +``` + ## How to work It just executes the `electron .` command in the Vite build completion hook and then starts or restarts the Electron App. diff --git a/package.json b/package.json index 0548910..3356f49 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "vite-plugin-electron", - "version": "0.28.8", + "version": "0.29.0", "description": "Electron 🔗 Vite", "main": "./dist/index.js", "types": "./dist/index.d.ts", @@ -26,7 +26,7 @@ "type": "git", "url": "git+https://github.com/electron-vite/vite-plugin-electron.git" }, - "author": "草鞋没号 <308487730@qq.com>", + "author": "Leo Wang(草鞋没号) <308487730@qq.com>", "license": "MIT", "packageManager": "pnpm@8.0.0", "scripts": {