Tabby 源码构建与插件开发指南:从依赖安装、打包安装程序到插件 Provider 扩展机制
本文基于 Tabby 仓库的 HACKING.md 开发指南,完整覆盖环境准备、源码构建、跨平台安装包生成、项目与插件目录结构,以及 Tabby 插件系统的加载与扩展机制;并结合 app/src/plugins.ts、app/src/entry.ts 等源码实现,说明插件发现、tabby-plugin 关键字过滤与 NgModule 注入的底层原理,帮助你在本地跑起 Tabby 源码并开发出自己的插件。
项目背景与技术栈
Tabby 是一个 Electron 桌面应用(README 定位为 "A terminal for a modern age"),前端使用 TypeScript 配合 Angular 框架编写,整体由 Webpack 构建。理解这一点对开发至关重要,因为整个代码库被拆分为两类部分:
- 宿主应用(app/ 目录):Electron 壳子,只包含"最基本的骨架",负责主进程启动、窗口管理、插件发现与加载、配置与错误处理;
- 插件(
tabby-*目录):核心 UI、标签页管理、本地 Shell、终端、SSH、串口等功能全部以插件形式实现,通过 Angular 的NgModule注入机制拼装进应用根模块。
根目录的 package.json 中,electron、webpack、typescript、pug(模板语言)、sass、@angular/* 等依赖印证了上述技术栈;app/package.json 的 peerDependencies 则列出了所有随发行版一起分发的内置插件包(tabby-core、tabby-local、tabby-terminal 等)。
环境准备与依赖安装
开发环境需要 Node.js 15 或更新版本 以及 Yarn 包管理器。克隆仓库后,在 tabby 根目录执行安装:
macOS 与 Windows:
yarn
Linux(以 Debian/Ubuntu 为例,需先安装 Electron 运行所需的系统库与编译工具):
# Linux (Debian/Ubuntu here as an example)
sudo apt install libfontconfig-dev libsecret-1-1-dev libarchive-tools libnss3 libatk1.0-0 libatk-bridge2.0-0 libgdk-pixbuf2.0-0 libgtk-3-0 libgbm1 cmake
yarn
HACKING.md 中有一个针对 fork 场景的重要提示:如果你 fork 了该仓库,在安装 node modules 之前可能需要先从上游仓库拉取 tags,否则依赖安装可能失败:
git pull --tags upstream master
从源码结构看,yarn 安装完成后会自动触发根 package.json 中的 postinstall 钩子:
"postinstall": "patch-package && node ./scripts/install-deps.mjs && node ./scripts/build-native.mjs"
即依次执行:用 patch-package 应用 patches/ 目录下的补丁、运行 scripts/install-deps.mjs 处理各插件子包的依赖、再运行 scripts/build-native.mjs 编译 node-pty、serialport 等原生模块。这也是 Linux 上必须先装好 cmake 和 C++ 工具链的原因。
开发模式构建与启动
完成依赖安装后,构建 Tabby:
yarn run build
该命令实际执行 npm run build:typings && node scripts/build-modules.mjs(见 package.json 的 scripts.build),先编译各插件的 typings,再由 scripts/build-modules.mjs 构建所有插件模块。
然后启动开发模式:
yarn start
start 脚本定义为 cross-env TABBY_DEV=1 electron app -d --inspect(package.json),有两个值得注意的点:
TABBY_DEV=1环境变量决定了运行模式。从 app/src/plugins.ts 可以看到,开发模式下内置插件的查找路径是源码检出目录本身(path.dirname(remote.app.getAppPath())),而不是生产模式下resources/builtin-plugins目录——这正是 HACKING.md 所说"开发模式下应用会从源码检出加载全部插件"的底层实现;-d --inspect参数开启 Electron 调试与 Node inspector,方便断点调试主进程代码。
app/package.json 中 "main": "dist/main.js" 说明主进程入口是 Webpack 打包产物,对应 app/webpack.config.main.mjs 将 app/lib/ 下的主进程 TypeScript 代码(index.ts、plugins.ts 除外,见 app/tsconfig.main.json)打包为 dist/main.js;渲染进程入口则由 app/src/entry.ts 引导。
构建安装程序
要构建发行安装包,先完成上述"正常构建",然后执行:
node scripts/prepackage-plugins.mjs
node scripts/build-windows.mjs
# 或
node scripts/build-linux.mjs
# 或
node scripts/build-macos.mjs
构建产物输出在 dist 文件夹中。scripts/prepackage-plugins.mjs 负责预先打包各内置插件,而三个平台脚本对应 scripts/build-windows.mjs、scripts/build-linux.mjs、scripts/build-macos.mjs,底层使用 electron-builder,其目标平台、产物格式等参数由根目录的 electron-builder.yml 配置。
项目结构与插件结构
HACKING.md 给出的项目布局如下(注释来自原文档):
tabby
├─ app # Electron app, just the bare essentials
| ├─ src # Electron renderer code
| └─ main.js # Electron main entry point
├─ build
├─ clink # Clink distribution, for Windows
├─ scripts # Maintenance scripts
├─ tabby-community-color-schemes # Plugin that provides color schemes
├─ tabby-core # Plugin that provides base UI and tab management
├─ tabby-electron # Plugin that provides Electron-specific functions
├─ tabby-local # Plugin that provides local shells and profiles
├─ tabby-plugin-manager # Plugin that installs other plugins
├─ tabby-settings # Plugin that provides the settings tab
├─ tabby-terminal # Plugin that provides terminal tabs
└─ tabby-web # Plugin that provides web-specific functions
对照当前仓库,文档中的布局依然是骨架,只是插件数量有所增长:除文档所列之外,当前仓库还包含 tabby-ssh/(SSH 会话)、tabby-serial/(串口)、tabby-telnet/、tabby-linkifier/(链接识别)、tabby-web-demo/ 等插件;Windows 的 Clink 发行版对应 extras/clink/ 目录,本地化翻译文件位于 locale/。
单插件的标准目录结构(文档原文定义):
tabby-pluginname
├─ src # Typescript code
| ├─ components # Angular components
| | ├─ foo.component.ts # Code
| | ├─ foo.component.scss # Styles
| └─ foo.component.pug # Template
| ├─ services # Angular services
| | └─ foo.service.ts
| ├─ api.ts # Publicly exported API
| └─ index.ts # Module entry point
├─ package.json
├─ tsconfig.json
└─ webpack.config.js
以 tabby-local/ 为例可以核对这一定义:其 src/components/ 下有 *.component.ts、*.component.scss、*.component.pug 三件套,src/services/terminal.service.ts 提供 Angular service,src/api.ts 导出公共 API,src/index.ts 是模块入口,根目录配套 package.json、tsconfig.json、webpack.config.mjs(当前仓库中 webpack 配置已统一为 .mjs 后缀)。
插件加载机制:三个来源与关键字过滤
Tabby 会从以下位置加载插件(HACKING.md 原文归纳):
- 开发模式下从源码检出目录加载(依赖
TABBY_DEV环境变量,见前文 app/src/plugins.ts 的路径选择逻辑); - 始终从用户插件目录加载(在应用内
Settings>Plugins页面点击Open Plugins Directory可打开该目录); - 始终从
TABBY_PLUGINS环境变量指定的目录加载。
app/src/plugins.ts 的 initModuleLookup() 展示了具体实现:把用户插件目录的 node_modules、应用自身路径,以及 TABBY_PLUGINS 中用 : 分隔的多个路径全部压入 Node 模块查找路径(NODE_PATH),从而让渲染进程可以通过 require 找到插件包。
只有 package.json 的 keywords 中包含 tabby-plugin 的模块才会被加载。 源码中该判断位于 app/src/plugins.ts:
if (!info.keywords || !(info.keywords.includes('terminus-plugin') || info.keywords.includes('terminus-builtin-plugin') || info.keywords.includes('tabby-plugin') || info.keywords.includes('tabby-builtin-plugin'))) {
return null
}
可以推断 terminus-* 关键字是为兼容 Tabby 更名前(Terminus)时代发布的旧插件而保留的。此外,插件目录名需以 tabby- 或旧前缀 terminus- 开头(app/src/plugins.ts 中的 PLUGIN_PREFIX / LEGACY_PLUGIN_PREFIX),同名插件按"内置优先、非旧版覆盖旧版"的规则去重(app/src/plugins.ts),并支持通过 config.pluginBlacklist 配置项禁用特定插件(app/src/entry.ts)。
如果你正在自己的插件目录中开发插件,推荐按文档给出的方式启动:
TABBY_PLUGINS=$(pwd) tabby --debug
在 Linux/macOS 上,也可以用源码模式等效运行:TABBY_PLUGINS=$(pwd) yarn start(yarn start 已自动附加 TABBY_DEV=1 与调试参数)。
插件模块契约:NgModule 默认导出
一个插件只应提供一个默认导出,它应是一个 NgModule 类(在适用场景下可以是 NgModuleWithDependencies)。该模块会作为依赖注入到应用根模块中:
import { NgModule } from '@angular/core'
@NgModule()
export default class MyModule {
constructor () {
console.log('Angular engaged, cap\'n.')
}
}
加载链路在源码中清晰可查:app/src/entry.ts 在收到主进程的 start IPC 消息后调用 findPlugins() 与 loadPlugins(),随后以插件模块列表构建根模块并 bootstrapModule。app/src/plugins.ts 中的这一行解释了 NgModuleWithDependencies 的含义——若默认导出带有 forRoot() 静态方法,则会先调用它:
const pluginModule = packageModule.default.forRoot ? packageModule.default.forRoot() : packageModule.default
而 app/src/app.module.ts 的 getRootModule() 把所有插件模块加入 imports,并要求至少一个插件提供 bootstrap 组件(否则抛出 "Did not find any bootstrap components" 错误);渲染进程若 bootstrap 失败会自动降级为 safe mode,只加载 isBuiltin 的内置插件(app/src/entry.ts),这对排查第三方插件导致的崩溃很有用。
通过 Provider 扩展功能
插件通过导出单个或多实例(multi: true)provider 来提供功能。以下是 HACKING.md 给出的完整示例——向工具栏添加一个按钮,通过继承 tabby-core 的 ToolbarButtonProvider 实现:
import { NgModule, Injectable } from '@angular/core'
import { ToolbarButtonProvider, ToolbarButton } from 'tabby-core'
@Injectable()
export class MyButtonProvider extends ToolbarButtonProvider {
provide (): ToolbarButton[] {
return [{
icon: 'star',
title: 'Foobar',
weight: 10,
click: () => {
alert('Woohoo!')
}
}]
}
}
@NgModule({
providers: [
{ provide: ToolbarButtonProvider, useClass: MyButtonProvider, multi: true },
],
})
export default class MyModule { }
可用的扩展点(provider 基类)在各插件的 API 模块中定义。HACKING.md 指引查看 tabby-core/src/api.ts、tabby-settings/src/api.ts、tabby-local/src/api.ts 与 tabby-terminal/src/api.ts;在当前仓库中,tabby-core 的扩展点已组织为 tabby-core/src/api/ 目录,包含 profileProvider.ts(自定义 Profile)、toolbarButtonProvider.ts(工具栏按钮)、hotkeyProvider.ts(快捷键)、menu.ts、commands.ts、configProvider.ts、tabContextMenuProvider.ts、theme.ts 等;tabby-settings、tabby-local 对应 tabby-settings/src/api.ts、tabby-local/src/api.ts,终端相关扩展点在 tabby-terminal/src/api/。编写插件前通读这些文件,是找到正确 provider 基类最快的方式。
发布插件到 NPM 与插件管理器
最后一步:将插件发布到 NPM,并在 package.json 的 keywords 中加入 tabby-plugin 关键字,该插件就会出现在 Tabby 的插件管理器(Plugin Manager)中供用户安装。
从源码看,插件管理器的安装/卸载实现在 app/lib/pluginManager.ts:它在进程内直接使用 npm 官方的 Arborist 安装引擎(new Arborist({ path: targetPath, ... }).reify({ add: [name@version] }))解析并安装完整依赖树,避免了打包 18 MB 的 npm CLI;tabby-plugin-manager/ 插件则负责管理器的前端界面(tabby-plugin-manager/src/services/pluginManager.service.ts)。
小结
按 HACKING.md 的流程走一遍:安装 Node 15+ 与 Yarn → yarn(Linux 先装系统库)→ yarn run build → yarn start 即可进入开发模式;出包则执行 scripts/prepackage-plugins.mjs 加对应平台的 build-*.mjs 脚本,产物位于 dist。开发插件时,牢记三个关键点:目录名以 tabby- 开头且 package.json 含 tabby-plugin 关键字、唯一默认导出为 NgModule(可带 forRoot)、用 multi: true provider 挂载到 tabby-core 等插件定义的扩展点上,再用 TABBY_PLUGINS=$(pwd) tabby --debug 完成本地联调。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0622
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00