- 桌面应用
- 开发工具
【免费下载链接】menubar
➖ high level way to create menubar desktop applications with electron
导读
menubar 是一个基于 Electron 的高层封装库,让开发者可以用极少的样板代码创建常驻系统托盘(菜单栏)的桌面应用:只需指向你的index.html,剩下的 Tray 图标、窗口定位、显隐切换等都由它处理。本文以仓库中的 CHANGELOG.md 为骨架,系统梳理 menubar 从 6.0.0 到 9.5.3 的版本演进脉络,完整覆盖各版本新增的特性(Features)、修复的缺陷(Bug Fixes)与破坏性变更(BREAKING CHANGES),并结合 src/Menubar.ts 等源码说明这些变更的底层实现,帮助你理解 API 演进的来龙去脉,并在升级时做出正确的迁移决策。
读完本文,你将掌握:menubar 各项核心选项与事件的生命周期及来源版本、browserWindow字段的引入背景、activateWithApp与before-load等新能力的用法、Electron 版本与 menubar 版本的对应关系,以及 7.0.0 / 6.0.0 两次破坏性变更的具体迁移写法。
版本总览:一条通向 9.5.x 的演进线
当前仓库的版本为9.5.3(见 package.json),CHANGELOG 记录的演进时间线如下:
| 版本 | 发布日期 | 类型 | 核心要点 |
|---|---|---|---|
| 9.5.3 | 2026-07-29 | Patch | 例行维护 |
| 9.5.2 | 2025-10-10 | Patch | 例行维护 |
| 9.5.1 | 2024-10-11 | Patch | 例行维护 |
| 9.5.0 | 2024-06-25 | Minor | 扩大 Electron 支持范围;修复 Win32 定位与原生菜单示例 |
| 9.4.0 | 2023-11-28 | Minor | 升级至 Electron 27;修复setVisibleOnAllWorkspaces破坏 Dock 状态的问题 |
| 9.3.0 | 2023-02-13 | Minor | 新增 Electron 22 支持 |
| 9.2.3 | 2022-10-05 | Minor | 新增 Electron 21 支持 |
| 9.2.2 | 2022-09-05 | Minor | 将 Electron 20 加入 peerDependency;修复 arrow 示例 |
| 9.2.1 | 2022-07-11 | Patch | 新增 Electron 19 支持 |
| 9.2.0 | 2022-04-11 | Minor | 新增before-load事件 |
| 9.1.2 | 2022-03-29 | Patch | 新增 Electron 17 支持 |
| 9.1.1 | 2021-12-09 | Patch | 新增 Electron 16 支持 |
| 9.1.0 | 2021-10-27 | Minor | 新增activateWithApp选项 |
| 9.0.6 | 2021-10-15 | Patch | 新增 Electron 14/15 支持 |
| 9.0.5 | 2021-06-28 | Patch | 新增 Electron 13 支持;修复 Linux/Windows 任务栏在左侧时的窗口定位 |
| 9.0.4 | 2021-05-03 | Patch | 新增 Electron 12 支持 |
| 9.0.3 | 2021-02-24 | Patch | 新增 Electron 10/11 支持 |
| 9.0.2 | 2021-01-20 | Patch | 改进 Windows 任务栏位置检测 |
| 9.0.1 | 2021-05-28 | Patch | 修复showWindow()阻止窗口关闭的问题 |
| 9.0.0 | 2020-05-27 | Major | 要求 Electron 9 |
| 8.0.x | 2020 | Minor/Patch | Electron 8 支持;修复 Dock 图标点击显示窗口、Windows 闪烁 |
| 8.0.0 | 2020-02-10 | Major | 推荐 peer dependency 为electron@^8.0.0 |
| 7.2.0 | 2020-01-16 | Minor | 新增loadUrlOptions选项 |
| 7.1.0 | 2019-11-25 | Minor | 允许跳过loadUrl(index: false) |
| 7.0.0 | 2019-10-23 | Major | 破坏性变更:放弃 Electron 4/5/6;menubar参数改为dir字段;窗口尺寸参数迁入browserWindow |
| 6.0.x | 2019 | 系列修复 | Windows 定位、after-hide重复触发、Menubar.window访问等 |
| 6.0.0 | 2019-05-31 | Major | 破坏性变更:代码库全部迁移至 TypeScript;导出方式改为具名导出menubar;新增browserWindow字段;支持NativeImage图标 |
从这张表中可以提炼出两条清晰的演进主线:一是跟随 Electron 大版本不断扩宽兼容性(9.x 系列几乎每个版本都对应一个新 Electron 大版本);二是 API 的收敛与现代化(字符串参数 →dir字段、散落的窗口参数 →browserWindow对象、CommonJS 默认导出 → 具名导出)。
一、6.0.0:TypeScript 重写与第一次破坏性变更
6.0.0 是 menubar 历史上的分水岭,它同时完成了三件事:全量 TypeScript 重写、导出方式变更、窗口配置收敛到browserWindow。
1.1 导出方式:从默认导出到具名导出
6.0.0 之前的写法是 CommonJS 默认导出:
var menubar = require('menubar');6.0.0 之后必须使用具名导出:
- var menubar = require('menubar'); + var { menubar } = require('menubar');使用 ES6/TypeScript 语法时等价于:
import { menubar } from 'menubar';这一变更与源码的模块组织方式直接对应:入口文件 src/index.ts 中menubar是一个具名导出的工厂函数,同时导出了Menubar类与getWindowPosition工具函数。工厂函数内部会创建一个Menubar实例:
export function menubar(options?: Partial<Options>): Menubar { return new Menubar(app, options); }1.2 新增browserWindow字段,废弃散落的窗口参数
6.0.0 引入了options.browserWindow字段,用于统一承载所有 BrowserWindow 构造参数,取代原先直接挂在menubar()参数表上的height、width、x、y、alwaysOnTop等字段。这一设计延续至今:在 src/types.ts 中,browserWindow的类型就是 Electron 官方的BrowserWindowConstructorOptions,这意味着 Electron 支持的所有窗口选项都可以原样透传。
底层实现上,src/Menubar.ts 的createWindow()方法会把默认行为与用户配置合并后交给new BrowserWindow():
const defaults = { show: false, // 创建时不显示,等待第一次点击托盘图标 frame: false, // 移除窗口边框,使其看起来像菜单栏 }; this._browserWindow = new BrowserWindow({ ...defaults, ...this._options.browserWindow, });其中show: false与frame: false是 menubar 强加的默认行为,用户传入的browserWindow配置会覆盖它们。
1.3 图标支持NativeImage
6.0.0 同时让icon选项支持Electron.NativeImage类型(见 src/types.ts 中icon?: string | Electron.NativeImage),不再局限于 PNG 路径字符串,这为动态生成托盘图标提供了可能。
1.4 6.0.x 系列的关键修复
6.0.x 期间修复了若干影响实际使用的缺陷,其中与窗口生命周期相关的几个值得关注:
- 修复访问
Menubar.window的问题(6.0.3):此前在部分时机访问.window可能拿不到 BrowserWindow 实例。当前实现中window是一个 getter,返回this._browserWindow,在窗口被关闭(after-close触发)后会被置为undefined(见 src/Menubar.ts 的windowClear())。 - 修复 Windows 定位崩溃(6.0.6):位置计算在 Windows 上的崩溃问题,与下方定位逻辑的迭代有关。
- 修复
after-hide事件重复触发(6.0.4):当前hideWindow()实现中先emit('hide')、再调用this._browserWindow.hide()、随后emit('after-hide'),配合_isVisible标志位避免了重复触发(见 src/Menubar.ts 的hideWindow())。 - 无图标时使用内置猫图标兜底(6.0.2):当用户未提供
icon且默认路径的IconTemplate.png也不存在时,回退到仓库 assets/IconTemplate.png。对应逻辑见 src/Menubar.ts 的appReady():
let trayImage = this._options.icon || path.join(this._options.dir, 'IconTemplate.png'); if (typeof trayImage === 'string' && !fs.existsSync(trayImage)) { trayImage = path.join(__dirname, '..', 'assets', 'IconTemplate.png'); // Default cat icon }- 修正 Windows 多任务栏下的窗口位置(6.0.4):这与下面将要详述的
taskbarLocation检测逻辑一脉相承。
二、7.0.0:API 收敛的破坏性变更
7.0.0 引入了 CHANGELOG 中标注最详细的破坏性变更,主要体现在放弃对 Electron 4/5/6 的支持以及两处参数形态的迁移。这些迁移规则至今仍有效,理解它们有助于避免在老教程中看到过时写法。
2.1 字符串参数迁移为dir字段
旧版本允许把应用目录以字符串形式直接传给menubar(),7.0.0 起必须改为dir字段:
- menubar('/home/me/MY_ABSOLUTE_PATH'); + menubar({ dir: '/home/me/MY_ABSOLUTE_PATH' });在 src/util/cleanOptions.ts 中,dir会经过两项预处理:若未提供则取app.getAppPath(),若为相对路径则path.resolve()转为绝对路径。而默认的index则基于dir拼接生成file://协议的地址,指向dir/index.html:
options.index = url.format({ pathname: path.join(options.dir, 'index.html'), protocol: 'file:', slashes: true, });2.2 窗口参数迁移进browserWindow
旧版本将x、y、height、width、alwaysOnTop直接挂在menubar()参数上,7.0.0 要求把它们放进browserWindow字段:
- menubar({ - x: 12, - y: 34, - height: 500, - width: 320, - alwaysOnTop: true - }); + menubar({ + browserWindow: { + x: 12, + y: 34, + height: 500, + width: 320, + alwaysOnTop: true + } + });注意alwaysOnTop与窗口显隐行为的关联:在 src/Menubar.ts 的createWindow()中,窗口失焦(blur)行为取决于是否设置了alwaysOnTop—— 若窗口始终置顶,失焦时仅触发focus-lost事件而不自动隐藏;否则在 100ms 延时后自动调用hideWindow()。
2.3 7.1.0 与 7.2.0:加载行为的新选项
在 7.x 的后续小版本中,加载行为获得两个重要选项:
index: false跳过loadUrl(7.1.0):当应用希望自行控制加载(例如加载特定 URL 或使用@electron/remote等预加载场景)时,可以将index显式设为false。在 src/Menubar.ts 的createWindow()中,仅当this._options.index !== false时才调用loadURL。注意index的类型是string | false,不能用undefined代替,否则会走默认的dir/index.html拼接逻辑。loadUrlOptions(7.2.0):加载index时透传给browserWindow.loadURL(url, options)的附加选项,类型为 Electron 的LoadURLOptions(见 src/types.ts),默认值为{}(由 src/util/cleanOptions.ts 归一化)。
三、8.x:Electron 8 支持与平台行为修复
8.0.0 将推荐的 peer dependency 更新为electron@^8.0.0。8.x 期间的修复主要集中在平台行为一致性上:
- Windows 上 Dock 图标点击显示窗口(8.0.2):确保点击任务栏 Dock 图标时窗口能正确显示,避免菜单栏应用在 Windows 上“唤不醒”的体验问题。
- 修复 Windows 上的闪烁(8.0.2,对应 issue #274):通过调整窗口创建与显示的时机减少打开瞬间的闪烁。这与 src/Menubar.ts 中
show: false默认值 +showWindow()显式控制显示的机制相关:窗口创建时不直接显示,而是在托盘事件触发后才setPosition()并show()。 - 安全依赖升级(8.0.1):将
acorn从 6.1.1 升至 6.4.1,属于常规安全维护。
四、9.0.x:定位系统大修与 Electron 兼容性扩张
9.0.0 是 Electron 9 时代的起点(CHANGELOG 明确要求 "Please use Electron 9 with this menubar version")。9.0.x 系列除了逐版本跟进 Electron 大版本外,还密集修复了跨平台窗口定位问题,这正是 menubar 在 Linux/Windows 上体验的核心。
4.1 平台定位逻辑的源码实现
从 9.0.2(改进 Windows 任务栏位置检测)、9.0.5(修复 Linux/Windows 任务栏在左侧时的定位)到 9.5.0(win32 位置修复),背后是 src/util/getWindowPosition.ts 中一套完整的任务栏方位推断算法。
核心函数taskbarLocation(tray)通过对比**屏幕工作区(workArea)与显示器边界(screenBounds)**的相对关系来判断任务栏方位:
// TASKBAR LEFT: 工作区起点 x > 0,说明左侧被任务栏占用 if (workArea.x > 0) { if (isLinux && workArea.y > 0) return 'top'; return 'left'; } // TASKBAR TOP: 工作区起点 y > 0 if (workArea.y > 0) { return 'top'; } // TASKBAR RIGHT: 工作区宽度小于显示器宽度,且左侧情形已被排除 if (workArea.width < screenBounds.width) { return 'right'; } // 其余情况都是底部任务栏 return 'bottom';在此基础上,getWindowPosition(tray)按平台映射出最终的窗口锚点:
- macOS(darwin):固定返回
trayCenter,托盘图标居中弹出; - Linux / Windows:根据任务栏方位返回
topRight(任务栏在顶部且为 Linux 时)、bottomRight(底部/右侧)、bottomLeft(左侧)等; - 无法判断时兜底为
topRight。
4.2 显示前的实时同步
在 src/Menubar.ts 的showWindow()中,Windows 与 Linux 平台会在每次显示前重新计算窗口位置,以应对任务栏位置或分辨率变化:
if (['win32', 'linux'].includes(process.platform)) { this._options.windowPosition = getWindowPosition(this.tray); }随后通过electron-positioner的calculate()计算坐标,并优先使用用户在browserWindow.x/y中显式指定的值覆盖计算结果(注意源码中特意不用||而是用!== undefined判断,以允许 x/y 为 0 的合法值),最后Math.round()后调用setPosition()—— 这一 round 处理正是为了避免setPosition传入非整数而崩溃(对应 6.0.6 的修复背景)。
4.3 9.0.1:showWindow()与窗口关闭
9.0.1 修复了调用showWindow()导致菜单栏窗口无法关闭的问题(issue #287)。从当前实现看,showWindow()会先确保 Tray 与 BrowserWindow 存在(必要时调用createWindow()),再走“缓存 bounds → 计算位置 → show”的流程,配合_isVisible状态与blur自动隐藏机制,保证窗口既能被点击打开、也能正常失焦关闭。
五、9.1.x - 9.2.x:事件与激活行为的精细化
这一阶段以功能新增为主,全部沉淀为当前稳定 API。
5.1activateWithApp:控制 macOS 激活行为(9.1.0)
macOS 下点击 Dock 图标会触发app.on('activate')。默认情况下 menubar 会监听该事件并在没有可见窗口时打开菜单栏窗口;新增的activateWithApp选项让你可以关闭这一行为(见 src/types.ts,默认true)。
底层实现在 src/Menubar.ts 的appReady()中:
if (this._options.activateWithApp) { this.app.on('activate', (_event, hasVisibleWindows) => { if (!hasVisibleWindows) { this.showWindow().catch(console.error); } }); }该选项与showDockIcon(macOS 专属,控制是否调用app.dock.hide()隐藏 Dock 图标)配合,可精细控制菜单栏应用在 macOS 上的“存在感”。注意 src/Menubar.ts 中仅在this.app.dock存在且showDockIcon未开启时才隐藏 Dock 图标。
5.2before-load事件:在加载页面之前介入(9.2.0)
before-load是 9.2.0 新增的事件,位于create-window之后、loadURL之前。从 src/Menubar.ts 的createWindow()可以看到完整的事件序列:
this.emit('create-window'); // ... 创建 BrowserWindow、Positioner、blur 监听 ... this.emit('before-load'); if (this._options.index !== false) { await this._browserWindow.loadURL(this._options.index, this._options.loadUrlOptions); } this.emit('after-create-window');before-load的典型用途是启用@electron/remote等需要抢在页面加载前完成的设置。README 中列举的完整事件生命周期如下:
| 事件 | 触发时机 |
|---|---|
ready | Tray 图标已创建并初始化完成(注意区别于 Electron 的app.on('ready')) |
create-window | 调用new BrowserWindow()的前一行 |
before-load | 窗口创建后、loadURL前 |
after-create-window | 所有窗口初始化代码完成且 URL 加载完成后 |
show | window.show()被调用的前一行 |
after-show | window.show()被调用后 |
hide | window.hide()被调用的前一行(窗口失焦时) |
after-hide | window.hide()被调用后 |
after-close | .window(BrowserWindow)属性被置空后 |
focus-lost | 设置alwaysOnTop时用户点击窗口外部触发 |
5.3 Electron 兼容性的连续跟进(9.1.x - 9.2.x)
这一阶段逐版本跟进 Electron:9.1.1(Electron 16)、9.1.2(Electron 17)、9.2.1(Electron 19)、9.2.2(Electron 20 进入 peerDependency,并修复 arrow 示例)、9.2.3(Electron 21)。当前 package.json 中的 peerDependencies 为electron >= 9.0.0,devDependencies 使用electron ^43.0.0,README 的兼容性表也说明 9.x 在 CI 中持续对 Electron 35.x - 43.x 做类型检查。
六、9.3.x - 9.5.x:近期的平台修复与兼容性扩张
6.1 9.4.0:Electron 27 与 Dock 状态保护
9.4.0 将 Electron 升级到 27,并修复了一个隐蔽的 macOS 问题:setVisibleOnAllWorkspaces会破坏app.dock原本的可见状态(issue #442)。修复后的实现(见 src/Menubar.ts 的createWindow())为setVisibleOnAllWorkspaces增加了skipTransformProcessType选项:
if (this._options.showOnAllWorkspaces !== false) { this._browserWindow.setVisibleOnAllWorkspaces(true, { skipTransformProcessType: true, // Avoid damaging the original visible state of app.dock }); }这里也体现了选项的默认值:showOnAllWorkspaces默认开启(仅当显式设为false时跳过),让窗口在所有 macOS 工作区(Space)中可见。
6.2 9.5.0:Electron 支持扩张与示例修复
9.5.0 完成了三项工作:
- 扩大 Electron 支持范围(issue #473):与整个 9.x 系列“持续扩宽 peerDependency 兼容面”的路线一致;
- 修复原生菜单示例(issue #471):对应 examples/native-menu/main.js。该示例展示了如何先创建带右键菜单的
Tray,再通过menubar({ tray })传入(此时icon选项会被忽略),并在ready后调用tray.removeAllListeners()以规避 macOS 上的白屏问题; - 修复 win32 位置(issue #479):继续完善 Windows 平台下的窗口定位,最终沉淀为上文所述的
getWindowPosition算法。
6.3 9.5.1 - 9.5.3:稳定期
9.5.1、9.5.2、9.5.3 均为无破坏性的例行维护版本,标志着 9.x 系列进入稳定期。
七、升级迁移实战:把旧代码一次性迁移到现代 API
综合两次破坏性变更(6.0.0 与 7.0.0),把一份“上古”代码迁移到当前 9.5.x 版本需要同时完成三处改写。以下是一个完整的迁移对照:
迁移前(v5/v6 早期风格):
var menubar = require('menubar'); var mb = menubar('/home/me/MY_ABSOLUTE_PATH');迁移后(9.x 风格):
const { menubar } = require('menubar'); const mb = menubar({ dir: '/home/me/MY_ABSOLUTE_PATH', browserWindow: { width: 400, height: 400, alwaysOnTop: true, }, }); mb.on('ready', () => { console.log('Menubar app is ready.'); });迁移要点归纳:
- 导出方式:
require('menubar')改为const { menubar } = require('menubar'),或使用import { menubar } from 'menubar'; - 目录参数:字符串参数改为
{ dir: ... }对象字段; - 窗口参数:
x/y/width/height/alwaysOnTop全部迁入browserWindow对象; - Electron 版本:确保项目使用 Electron 9 及以上(建议跟随 README 兼容表选择当前 CI 持续验证的 Electron 版本范围)。
验证迁移结果:跑通测试
迁移后可以用仓库自带的 Jest 测试验证基础对象契约。当前 src/Menubar.spec.ts 验证了四个核心不变量:
mb.app始终存在(Electron App 实例);- 在
after-create-window事件触发前访问mb.positioner会抛出异常,触发后可正常访问; - 在
ready事件触发前访问mb.tray会抛出异常,触发后为Tray实例; mb.window在ready前为undefined,ready后为BrowserWindow实例。
这些断言与 src/Menubar.ts 中positioner、traygetter 的“未就绪即抛错”设计一一对应,也印证了 CHANGELOG 中各版本围绕事件时序所做修复的最终形态。
结语:从 CHANGELOG 看 menubar 的演进哲学
回看这份 CHANGELOG,menubar 的演进路径非常清晰:每解锁一个新 Electron 大版本就发布一个小版本(9.0.x - 9.5.x 几乎与 Electron 12 - 43 一一对应),功能新增始终围绕菜单栏场景的核心体验(定位、显隐、事件时序、激活行为),而两次破坏性变更都服务于 API 的收敛与类型安全(TypeScript 化、参数归入browserWindow、具名导出)。对使用方而言,掌握 7.0.0 的迁移规则与 9.x 的 Electron 兼容矩阵,就能让项目长期稳定地跟随这个库的演进节奏。
如需查阅完整的 API 参考,可继续阅读仓库内的 API 文档、Menubar 类文档 与 Options 接口文档;动手实验可参考 hello-world、arrow、icon-animation 与 native-menu 四个示例。
- 桌面应用
- 开发工具
【免费下载链接】menubar
➖ high level way to create menubar desktop applications with electron
相关推荐
ScreenPipe 完整上手:本地屏幕记忆喂给 AI,怎么装、目录怎么读
ScreenPipe 完整上手:本地屏幕记忆喂给 AI,怎么装、目录怎么读 ScreenPipe 在本地持续记录你的屏幕和音频,把它们变成能用自然语言搜索的"电
AI 应用大模型本地部署AI AgentMCP 服务屏幕录制语音ArchiSteamFarm跨版本兼容性:API变更与迁移指南
ArchiSteamFarm跨版本兼容性:API变更与迁移指南 引言 你是否在升级ArchiSteamFarm(ASF)时遇到过API兼容性问题?本文将详细介绍
后端Termshark跨版本兼容性:API变更与迁移指南
Termshark跨版本兼容性:API变更与迁移指南 你是否在升级Termshark时遇到过配置失效、命令行参数错误或功能异常?本文将系统梳理Termshark
开发工具网络安全
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考