electron-multi-app-kit
v0.2.1
Published
Electron main process lifecycle & module library — setup-style with hook-based customization
Maintainers
Readme
electron-multi-app-kit
Electron 主进程生命周期 & 模块库 — setup 风格,Hook 驱动,按需配置。
为 Electron 应用的主进程提供可复用的脚手架:窗口管理、IPC 事件、系统托盘、自动更新、下载管理、HTTP/WS 服务等模块,全部通过配置驱动,无需重复编写样板代码。
快速开始
安装
npm install electron-multi-app-kit最小示例
用 createElectronApp 替换你的主进程入口:
// electron/main/index.ts
import { createElectronApp } from 'electron-multi-app-kit'
import path from 'node:path'
const __dirname = path.dirname(new URL(import.meta.url).pathname)
createElectronApp({
// 路径配置(必填)
paths: {
rendererDist: path.join(__dirname, '../../dist'),
mainDist: path.join(__dirname, '../../dist-electron'),
publicDir: path.join(__dirname, '../../public'),
preloadScript: path.join(__dirname, '../preload/index.js'),
iconPath: path.join(__dirname, '../../public/icons/icon.ico'),
},
// 窗口定义(必填)
windows: [
{
type: 'main',
options: {
width: 1200,
height: 800,
frame: false,
webPreferences: { webviewTag: true },
},
onCreate(win) {
// 加载页面 —— 适合用 Vite dev server 或本地文件
if (process.env.VITE_DEV_SERVER_URL) {
win.loadURL(process.env.VITE_DEV_SERVER_URL)
} else {
win.loadFile(path.join(__dirname, '../../dist/index.html'))
}
},
},
],
mainWindowType: 'main',
})配置结构
interface ElectronCliConfig {
paths: ElectronCliPaths // 必填 - 路径配置
windows: WindowDefinition[] // 必填 - 窗口定义
mainWindowType: string // 必填 - 主窗口标识
isDev?: boolean // 开发模式
defaultWindowOptions?: BrowserWindowConstructorOptions // 默认窗口选项
lifecycle?: LifecycleHooks // 生命周期钩子
ipc?: IpcOptions // IPC 配置
session?: SessionConfig // Session 拦截
tray?: TrayConfig // 系统托盘
instance?: InstanceConfig // 单实例锁
deepLink?: DeepLinkConfig // 自定义协议与深度链接
updater?: UpdaterConfig // 自动更新
}核心功能
1. 路径配置 paths
解耦构建工具(Vite/Webpack)的路径依赖,所有模块通过此配置获取路径。
paths: {
rendererDist: string // 渲染进程构建产物目录
mainDist: string // 主进程构建产物目录
publicDir: string // 静态资源目录
preloadScript: string // preload 脚本绝对路径
iconPath: string // 应用图标路径
userAgent?: string // 自定义 UA(未注入 node 时使用)
}2. 窗口定义 windows
用声明式配置替代硬编码的 WindowType 枚举和 switch/case。
windows: [
{
type: 'main', // 窗口类型标识
options: { // BrowserWindow 构造选项
width: 1200,
height: 800,
frame: false,
webPreferences: {
preload: '...', // 可选,默认使用 paths.preloadScript
webviewTag: true,
},
},
onCreate(win) {
// 窗口创建后立即执行 —— 加载 URL、设置位置等
win.loadURL('https://example.com')
},
},
]WindowManager 会自动:
- 合并
defaultWindowOptions+ 各窗口的options - 设置菜单隐藏、
ready-to-show、外部链接处理 - 转发
fullscreen-change/maximize/minimize等状态到渲染进程
3. IPC 事件 ipc
内置 7 类 IPC 模块,同时支持三种方式自定义:
ipc: {
// 方式一:结构化定义
extraHandlers: [
{ channel: 'dev-test', type: 'handle', handler: (e, ...args) => { /* ... */ } },
{ channel: 'log', type: 'on', handler: (e, msg) => console.log(msg) },
],
// 方式二:覆盖特定 channel
overrides: {
'window-control': { channel: 'window-control', type: 'handle', handler: myHandler },
},
// 方式三:完全自由的 ipcMain 配置
setup(ipcMain) {
ipcMain.handle('get-ipconfig', async () => await getLocalIP())
ipcMain.on('custom-event', (event, data) => { /* ... */ })
},
// 禁用不需要的内置模块
disabledBuiltins: ['downloader', 'notification'],
}注册时序:内置模块 → extraHandlers → overrides → setup(ipcMain)
setup 最后调用,可以覆盖任何之前注册的 handler。
内置 IPC 模块
| 模块 | Channel | 功能 |
|------|---------|------|
| window-control | window-control | BrowserWindow 控制(最小化/最大化/全屏/置顶/属性查询) |
| fs-api | fs-api | fs 模块 IPC 桥接(readFile/writeFile/readFileAsBase64 等) |
| dialog | open-file-dialog show-message-box | 原生对话框 |
| shell | open-file open-external show-file | 文件/链接打开 |
| downloader | download-file download-controler get-download-tasks | 下载管理 |
| notification | show-notification | 原生通知 |
| window-move | window-move window-move-start window-move-end | 无边框窗口拖拽 |
框架级 handler(不可单独禁用):
| Channel | 功能 |
|---------|------|
| get-preload | 获取 preload 路径 |
| get-renderer-dist | 获取渲染进程 dist 路径 |
| get-main-dist | 获取主进程 dist 路径 |
| open-win | 打开新窗口 |
| open-main-window | 打开主窗口 |
| open-window-page | 打开子窗口/模态窗口 |
| read-file | 读取文件 |
| set-process | 设置窗口进度条 |
| send-to-parent | 向父窗口发送消息 |
| send-to-child | 向子窗口发送消息 |
| set-ignore-mouse-events | 设置鼠标穿透 |
| window-manager-api | 窗口管理器 API 调用 |
4. 生命周期钩子 lifecycle
在 app 启动的各个阶段注入自定义逻辑:
lifecycle: {
async onReady() {
// app.whenReady() 内,窗口创建前
await initDatabase()
},
async afterReady() {
// app.whenReady() 内,窗口创建后
startBackgroundTasks()
},
onAllWindowsClosed() {
// 覆盖默认行为(非 macOS 默认 quit)
// 返回 void 表示使用自定义处理
},
onActivate() {
// macOS dock 点击,默认重新创建主窗口
},
}内部启动流程:
createElectronApp(config)
├─ GPU 配置(Win7 兼容)
├─ Windows app model ID
├─ 单实例锁
├─ IPC 事件注册(内置 + 自定义)
└─ useLifeCycle()
├─ protocol.registerSchemesAsPrivileged('local:')
├─ app.on('window-all-closed')
├─ app.on('activate')
└─ app.whenReady()
├─ lifecycle.onReady()
├─ protocol.registerFileProtocol('local:', ...)
├─ Session 配置
├─ 创建主窗口
├─ 系统托盘(可选)
├─ HTTP/WS 服务(可选)
├─ 自动更新(可选)
└─ lifecycle.afterReady()5. 系统托盘 tray
tray: {
enabled: true,
tooltip: 'My App',
menuItems: [
{ label: '显示主窗口', click: () => windowManager.openMainWindow() },
{ label: '退出', click: () => app.quit() },
],
onDoubleClick: () => { /* 自定义双击行为 */ },
}6. 单实例锁 instance
instance: {
enabled: true,
onSecondInstance() {
// 第二个实例启动时的行为(默认打开主窗口)
},
}7. 自定义协议与深度链接 deepLink
注册一个应用协议,并统一接收首次启动、第二实例和 macOS open-url 事件。启用后会自动使用单实例锁;scheme 不包含 ://。
deepLink: {
scheme: 'rui-edu',
async onOpen(url, context) {
if (url.hostname !== 'course') return
const courseId = url.pathname.slice(1)
windowManager.openMainWindow()
console.log(courseId, context.source)
},
onError(error, rawUrl) {
console.error('Deep link failed:', rawUrl, error)
},
}业务方在 onOpen 中自行校验 hostname、pathname、参数和登录状态。kit 只分发与已配置 scheme 匹配的 URL;应用 ready 前收到的链接会排队处理。
8. 自动更新 updater
基于 electron-updater,使用 generic provider:
updater: {
enabled: true,
feedUrl: 'https://update.example.com/releases',
channel: 'latest',
onUpdateAvailable(info) {
mainWindow.webContents.send('update-available', info)
},
onUpdateError(error) {
console.error('Update error:', error)
},
onUpdateDownloaded() {
// 默认弹窗询问是否重启;返回 false 阻止默认行为
},
}高级用法
批量下载启用 shouldZip 时会在运行时加载 archiver。消费项目应确保打包产物包含该依赖;如果运行时缺少它,kit 会在控制台输出警告,并将已下载内容复制到原 ZIP 位置的同名文件夹。其他压缩错误仍会使任务进入 failed 状态。
独立使用模块
除了 createElectronApp 一键启动,每个模块也可以独立导入使用:
import {
WindowManager, // 窗口管理器类
DownloadManager, // 下载管理器类
useIpcEvents, // IPC 事件注册
useLifeCycle, // 生命周期管理
useSession, // Session 配置
useSingleInstance, // 单实例锁
useTray, // 系统托盘
useUpdater, // 自动更新
useWinDrag, // 透明窗口拖拽
} from 'electron-multi-app-kit'
// 单独使用 WindowManager
const wm = new WindowManager({
windows: [...],
mainWindowType: 'main',
paths: { ... },
})
wm.openMainWindow()独立使用 IPC 内置模块
import {
useWindowMove,
useWindowControl,
useFsApi,
useDialogApi,
useShellApi,
useDownloaderApi,
useNotificationApi,
} from 'electron-multi-app-kit'
// 在自定义 setup 中按需注册
useWindowMove(windowManager)
useFsApi()工具函数
import { merge, debounce, cloneJSON } from 'electron-multi-app-kit'
// 深度合并对象
const opts = merge(defaults, userOptions, { deepMerge: true })
// 防抖
const save = debounce(() => persist(), 1000)
// JSON 深克隆
const copy = cloneJSON(original)WindowManager API
const wm = ctx.windowManager // 从 createElectronApp 返回值获取
wm.create('settings') // 创建窗口
wm.openWindow('settings') // 打开(自动 show/restore/maximize)
wm.openMainWindow() // 打开主窗口
wm.openWindowPage('/hash', { modal: true, parent: mainWin })
wm.openUrl('/some-hash')
wm.getMain() // 获取主窗口
wm.getWindow('settings') // 获取指定窗口
wm.getWindowByWebContents(wc) // 从 webContents 反查窗口
wm.getAllWindows() // 所有窗口
wm.getWindowConfig(win) // 窗口的合并配置
wm.closeWindow('settings') // 关闭窗口
wm.closeAllWindows() // 关闭所有
wm.confirmExit(win) // 弹出退出确认对话框Preload 桥接模块
electron-multi-app-kit/preload 提供一个开箱即用的 IPC 桥接层,通过 contextBridge 将 ipcRenderer 安全地暴露给渲染进程。
通用桥接只应加载到可信的应用页面。远程 <webview> 请使用受限的 safe-preload:
// electron/preload/safe-preload.ts
import 'electron-multi-app-kit/safe-preload'将该文件加入应用的 preload 构建入口,并把构建后的绝对路径传给 <webview preload="...">。安全入口仍暴露 window.ipcRenderer,但只包含 sendToHost、on、once 和 off,不允许直接调用主进程 handler:
window.ipcRenderer.sendToHost('webview:download', { url })宿主 renderer 通过 ipc-message 接收消息,再按业务规则调用主进程 API。fs-api、shell、自动更新和 read-file 仅接受 BrowserWindow 主框架调用,webview 与 iframe 会被拒绝。
使用方法
在你的 preload 入口文件中导入(副作用导入即可):
// electron/preload/index.ts
import 'electron-multi-app-kit/preload' // 自动注册 window.ipcRenderer
// 基于 ipcBridge 构建业务 API
import { ipcBridge } from 'electron-multi-app-kit/preload'
import { contextBridge } from 'electron'
contextBridge.exposeInMainWorld('MyAPI', {
async getVersion() {
return ipcBridge.invoke<string>('get-app-version')
},
onMessage(callback: (data: any) => void) {
ipcBridge.on('main-message', (_event, data) => callback(data))
},
})渲染进程中使用
// 直接调用
const version = await window.ipcRenderer.invoke<string>('get-app-version')
// 监听事件
window.ipcRenderer.on('main-message', (_event, data) => {
console.log(data)
})
// 单向发送
window.ipcRenderer.send('log', 'hello from renderer')
// 向父窗口发送
window.ipcRenderer.sendToParent('child-ready')
// 向子窗口广播
window.ipcRenderer.sendToChild('parent-data', payload)window.ipcRenderer API
| 方法 | 类型 | 说明 |
| ------ | ------ | ------ |
| invoke<T>(channel, ...args) | Promise<T> | 请求-响应 |
| send(channel, ...args) | void | 单向发送 |
| on(channel, listener) | void | 监听事件 |
| once(channel, listener) | void | 一次性监听 |
| off(channel, listener) | void | 移除监听 |
| sendToParent(channel, ...args) | void | 向父窗口发送 |
| sendToChild(channel, ...args) | void | 向子窗口广播 |
类型声明
如需在渲染进程中为 window.ipcRenderer 添加类型:
// src/vite-env.d.ts 或全局声明文件
import type { IpcBridge } from 'electron-multi-app-kit/preload'
declare global {
interface Window {
ipcRenderer: IpcBridge
}
}Renderer 模块
electron-multi-app-kit/renderer 提供 Vue 渲染进程可直接使用的组合式函数和组件。
useIpcRenderer
内存安全的 IPC 封装,自动在组件卸载时清理监听器。
<script setup>
import { useIpcRenderer } from 'electron-multi-app-kit/renderer'
const ipc = useIpcRenderer()
// 监听主进程消息(自动清理)
ipc.on('main-message', (event, data) => {
console.log(data)
})
// 请求-响应
const version = await ipc.invoke<string>('get-app-version')
</script>NavBar — 无边框窗口导航栏
开箱即用的自定义标题栏组件,专为无边框(frame: false)窗口设计。
<template>
<electron-nav-bar title="我的应用" icon="/icon.png">
<button @click="goBack">返回</button>
</electron-nav-bar>
</template>
<script setup>
import { NavBar } from 'electron-multi-app-kit/renderer'
</script>功能:
- 自动检测是否为无边框窗口,标准窗口自动隐藏
- 窗口拖拽(
-webkit-app-region: drag) - 窗口控制按钮:置顶、最小化、最大化/还原、关闭
- 实时同步窗口状态(全屏、最大化、置顶等)
- 通过 CSS 变量自定义样式
Props:
| Prop | 类型 | 说明 |
| ------ | ------ | ------ |
| title | string | 自定义标题(不传则使用 BrowserWindow 的 title) |
| icon | string | 自定义图标 URL(不传则不显示图标) |
Slot: 默认插槽,内容显示在标题和窗口控制按钮之间。
CSS 变量:
| 变量 | 默认值 | 说明 |
| ------ | ------ | ------ |
| --app-nav-bar-size | 40px | 导航栏高度 |
| --app-color-f | #fff | 背景色 |
| --app-color-0 | #333 | 文字颜色 |
| --app-color-e | #e8e8e8 | 按钮 hover 背景 |
| --app-color-c | #d0d0d0 | 按钮 active 背景 |
| --app-color-red | #e81123 | 关闭按钮 hover 背景 |
| --app-color-red-light | #f1707a | 关闭按钮 active 背景 |
完整示例
以 Vite + Vue 项目为例,替换 electron/main/index.ts:
import { createElectronApp } from 'electron-multi-app-kit'
import path from 'node:path'
import { app, shell } from 'electron'
const __dirname = path.dirname(new URL(import.meta.url).pathname)
const isDev = process.env.NODE_ENV === 'development'
const appCtx = createElectronApp({
// ---- 路径 ----
paths: {
rendererDist: path.join(__dirname, '../../dist'),
mainDist: path.join(__dirname, '../../dist-electron'),
publicDir: isDev
? path.join(__dirname, '../../public')
: path.join(__dirname, '../../dist'),
preloadScript: path.join(__dirname, '../preload/index.js'),
iconPath: path.join(__dirname, '../../public/icons/icon.ico'),
userAgent: 'Mozilla/5.0 ...',
},
isDev,
// ---- 默认窗口选项 ----
defaultWindowOptions: {
icon: path.join(__dirname, '../../public/icons/icon.ico'),
webPreferences: {
nodeIntegration: false,
preload: path.join(__dirname, '../preload/index.js'),
},
},
// ---- 窗口定义 ----
windows: [
{
type: 'main',
options: {
width: 1200,
height: 800,
minWidth: 1200,
minHeight: 800,
frame: false,
webPreferences: { webviewTag: true, contextIsolation: true },
},
onCreate(win) {
if (process.env.VITE_DEV_SERVER_URL) {
win.loadURL(process.env.VITE_DEV_SERVER_URL)
} else {
win.loadFile(path.join(__dirname, '../../dist/index.html'))
}
},
},
],
mainWindowType: 'main',
// ---- IPC 自定义 ----
ipc: {
setup(ipcMain) {
ipcMain.handle('dev-test', () => shell.beep())
ipcMain.handle('get-app-version', () => app.getVersion())
},
},
// ---- 启用模块 ----
tray: { enabled: true },
instance: { enabled: true },
// ---- 生命周期 ----
lifecycle: {
async onReady() {
console.log('App is ready, creating window...')
},
},
})
// 返回的 context 可后续使用
export const {
windowManager,
downloadManager,
batchDownloadManager,
} = appCtxdownloadManager 和 batchDownloadManager 由应用上下文统一创建。应用退出前,kit 会自动调用两者的 prepareForQuit();业务代码无需再创建或保存额外实例。
License
MIT
