BetterSettings 插件源码解析:加速设置页打开、消除过渡动画与齿轮菜单分类

原创2026-10-01 23:52:491,752 阅读
文章标签:即时通讯桌面应用前端插件系统

BetterSettings 插件源码解析:加速设置页打开、消除过渡动画与齿轮菜单分类

导读

BetterSettings 是 Vencord 中一个专注改善 Discord 设置页体验的插件:它通过三项可独立开关的改动,让设置页打开更快、去掉生硬的过渡动画、并把设置齿轮的右键菜单按分类重新组织。本文以 BetterSettings README 为骨架,结合 index.tsx 的完整实现,逐条拆解三个开关背后的 Patch 机制、CSS 注入与菜单构建逻辑,让你既能照着配置,也能理解 Vencord 插件如何修改 Discord 内部 UI。

一、插件定位与三项核心能力

官方 README 用一句话概括了插件职责:通过多项可独立开关的改动改善 Discord 的设置(Settings)体验,并明确列出三项改进:

  1. 让打开设置的速度明显变快;
  2. 移除生硬的过渡(crossfade)动画;
  3. 把设置齿轮的右键菜单按分类整理。

这些能力并非绑定死,而是由三个独立的布尔开关控制,全部默认开启、且都需要重启 Discord 后生效,定义位于 index.tsx:

配置键 说明 类型 默认值 需要重启
disableFade 禁用设置页的 crossfade 过渡动画 BOOLEAN true 是
organizeMenu 将设置齿轮右键菜单按分类整理 BOOLEAN true 是
eagerLoad 消除首次打开设置菜单时的加载延迟 BOOLEAN true 是

插件元信息(作者 Devs.Kyuuhachi、标签 Appearance / Customisation / Organisation)同样在 index.tsx 中定义。下文分别对应 README 的"更快打开"“移除动画”“菜单分类”三条主线展开。

二、更快打开设置:eagerLoad 的预加载策略

Discord 的设置页内容并非一次性打包进主 bundle,而是按需惰性加载的:首次点击齿轮时,客户端才去解析对应的模块 chunk 和目录(TOC)数据,这就是用户感知到的"首次打开卡顿"。

BetterSettings 针对两个惰性加载点做了两处 Patch,均由 settings.store.eagerLoad 门控:

  • 预加载设置内容 chunk(index.tsx):匹配 createPromise:()=>...webpackId:"..." 的模块创建模式,把原本延后执行的 promise 构造函数额外触发一次(replace: "$&,_:$1"),让设置页内容在后台提前解析,并刻意跳过 CollectiblesShop 商城模块避免无关开销。
  • 预加载菜单目录(TOC)(index.tsx):匹配 handleOpenSettingsContextMenu= 句柄,用 _vencordBetterSettingsEagerLoad=(async ()=>$1)() 把原本在用户点击后才执行的 await 调用提前跑一遍,使目录数据在菜单真正弹出前就绪。

两处都使用 predicate: () => settings.store.eagerLoad 做运行时门控:只有开关打开时该替换才被注入,关闭后代码保持 Discord 原样,互不影响。

三、消除生硬过渡动画:disableFade 的四处联动

"过渡动画"在 Discord 设置 UI 里体现在多个层面,README 所说的"scuffed transition"实际由四个独立 Patch 协同移除,全部受 disableFade 控制:

  1. 外层 Layer 层替换(index.tsx):匹配 this.renderArtisanalHack() 所在模块,把负责淡入淡出的旧 FadeLayer 类替换为插件自定义的 Layer 组件(replace: "var $1=$self.Layer;...")。
  2. 侧边栏的轻微淡入(index.tsx):standardSidebarView 也有一个小的 fade-in,Patch 将 animated.div 替换为普通 "div",并跳过 from:{position:"absolute"} 的入场逻辑。
  3. 模态层的淡入淡出(index.tsx):针对 "data-mana-component":"layer-modal" 组件,同样把 animated.div 换成 "div",并将注入的 style: 替换为空对象 style:{},直接抹掉动画样式。
  4. 进出场延迟归零(index.tsx):匹配 headerId:void 0,headerIdIsManaged:!1 模块,把初始化延迟常量 let $1=300 改为 0,连 300ms 的等待窗口一并取消。

自定义 Layer 组件:接管后仍保持完整性

被替换进来的 Layer 组件定义在 index.tsx:它保留 SHOWN / HIDDEN 两种 mode 与 baseLayer 标记,隐藏时通过 aria-hidden 和 opacity: 0 快速隐藏(stop-animations 类名),并在卸载时向 ComponentDispatch 派发 LAYER_POP_START / LAYER_POP_COMPLETE,维持 Discord 对图层弹出事件的既有预期——这正是"移除动画但不破坏 UI 状态机"的关键。

四、齿轮右键菜单分类:organizeMenu 与 transformSettingsEntries

4.1 Patch 入口

最后一项改动针对设置齿轮的右键菜单。Patch 以 #{intl::USER_SETTINGS_ACTIONS_MENU_LABEL} 为锚点定位菜单渲染代码(index.tsx),把原始 children:[...] 的数组替换为 children:[$self.transformSettingsEntries($1),,即把每个菜单条目交给插件的转换函数重新组织,同样以 organizeMenu 作门控。

4.2 transformSettingsEntries 的分组逻辑

核心转换函数 transformSettingsEntries 遍历原始菜单条目,按 key 分三类处理:

  • Vencord 专属入口:key 为 vencord_plugins / vencord_themes 时,分别用 vencordToolbox/menu.tsx 导出的 buildPluginMenuEntries() / buildThemeMenuEntries() 替换子菜单,让插件与主题开关直接在齿轮菜单里可操作;
  • 用户信息等分段:key 为 user_section(或任何以 _section 结尾且有 label 的条目)时,包成带 label 的二级 Menu.MenuItem,并递归调用 transformSettingsEntries 处理其子项,实现按分类层层分组;user_section 的标题通过 getIntlMessage("USER_SETTINGS") 获取本地化文案(见 discord.tsx,内部经 runtimeHashMessageKey 解析为运行时哈希键);
  • 其余条目:原样透传,不破坏 Discord 原有菜单项。

4.3 子菜单内容从哪来

buildPluginMenuEntries(menu.tsx)会列出所有已启用且非 API 的插件(isPluginEnabled 过滤 + p.name.endsWith("API") 排除),按名称排序,并针对每个插件遍历其 settings.def 自动生成控件:布尔项生成 MenuCheckboxItem,下拉项生成 MenuRadioItem,滑块项生成 MenuSliderItem(遇到 stickToMarkers / componentProps 等菜单不支持的滑块配置会跳过),并附"Open Settings"入口;改动 restartNeeded 的选项时还会弹出 "Restart to apply the change" 提示。

buildThemeMenuEntries(menu.tsx)则提供 QuickCSS 开关/编辑、管理主题入口,以及通过 VencordNative.themes.getThemesList 拉取的已安装主题勾选列表。

4.4 配套 CSS:解除菜单高度上限

菜单分类后子菜单可能变长,但 Discord 对底部弹出的上下文菜单子菜单有高度上限逻辑(--reference-position-layer-max-height)。为此插件在 fullHeightContext.css 中针对 #user-settings-cog 及 aria-activedescendant^="user-settings-cog" 选择器把该变量重置为 initial,让子菜单可以自由向上/向下生长。该样式仅在 start() 中 organizeMenu 开启时通过 enableStyle 注入,stop() 时由 disableStyle 移除(index.tsx)。

五、配置方式与生效前提

安装并启用 BetterSettings 后,可在 Vencord 设置 → Plugins 中找到它,三个开关(disableFade / organizeMenu / eagerLoad)的默认值均为开启;由于改动都发生在启动时的 Patch 注入阶段,任何开关变更都需要重启 Discord 才会生效(restartNeeded: true)。若只想保留部分体验,例如仅需要更快的打开速度,可关闭 disableFade 与 organizeMenu,只留 eagerLoad。

六、实现机制背后的 Vencord 基础设施

读懂本插件即可顺带掌握 Vencord 修改 Discord 的通用三件套:

  • Patch 三元组:find(模块锚点)→ match(正则/字符串,可用 \i 通配标识符)→ replace($1... 引用捕获组,$self 在编译期被替换为插件路径),配合 predicate 做运行时开关,见 patches.ts 的规范化逻辑(含 #{intl::...} 消息键哈希展开);
  • 懒加载 CSS 类名:findCssClassesLazy("animating","baseLayer","bg","layer","layers") 在 webpack.ts 中实现,通过代理在首次访问时才去 webpack 缓存里定位压缩后的类名,避免启动期开销;
  • 容错处理:插件作者特意说明,由于 Layer 是整个 UI 的最外层,不能直接包 ErrorBoundary(会误捕获子组件无关错误),因此在静态方法 Layer 中对 FocusLock、ComponentDispatch、Classes.layer 做 sanity check,任一缺失即回退返回原始 children 并记日志(index.tsx)。

七、相关源码索引

登录后查看全文
Vencord