OPEN THE DOOR · 发现 · 加载 · 契约 · 隔离 · 演化 · 一道门的五个问题

开门

O P E N   T H E   D O O R

你用过很多插件。给 VS Code 装过语言支持,给 Chrome 装过广告拦截,在 Mihon 里加过一堆漫画源,也许还给 Obsidian 写过一个小脚本。它们都能凭空往一个已经写好的程序里塞进新功能。

但你有没有想过:这怎么可能?VS Code 的作者不认识那个插件作者,代码没一起编译过,甚至不是同一年写的 —— 凭什么它装上就能用?插件的代码是什么时候、被谁、怎么变成进程里活着的对象的?它想读你的密钥文件时,谁在拦?它把自己写死循环了,为什么 VS Code 不跟着卡死,Obsidian 却会?

这本书的答案是:插件系统是一笔关于信任的交易。你把程序的一部分权力,交给一段你没写过、没审过、随时会变的代码。剩下的所有设计 —— 扩展点、清单、类加载器、沙箱、权限、版本号 —— 都是在给这笔交易定价:让它能做多少事,和它能害你多深。

我们先拆开五个真实系统,看它们各自把价钱定在哪;然后从零造一台自己的。那台宿主是真的 —— 用 JS 写出来,就在这些网页里跑:你现写一段插件代码,点「装载」,它真的被加载、真的注册命令、真的改变宿主的界面;你去掉一个权限,它真的当场被拒绝。

24
6
19个真引擎 Demo
5个被拆开的真实系统
1台你亲手造、当场能跑的宿主

这本书的主线

◆ 一句话

插件系统是一笔关于信任的交易:你把程序的一部分权力,交给一段你没写过、没审过、随时会变的代码。

每一个你听说过的机制 —— 扩展点、manifest、类加载器、独立世界、沙箱、权限清单、semver 范围 —— 都是在给这笔交易定价:让它能做多少事(能力),和它能害你多深(控制)。

五个真实系统之所以长得完全不一样,不是因为技术水平有高下,而是因为它们把价钱定在了不同的地方。看懂了这一点,你再看任何一个插件系统,都能在三分钟内说出它的取舍。

五个系统,一根轴

把它们按「插件能拿到多少能力 ←→ 宿主保留多少控制」排开,是这样的:

系统插件是什么怎么进来隔离换来了什么
Obsidian 一个 main.js 在 Electron 渲染进程里直接 require 没有。官方明说无法可靠限制插件权限 插件能做的事没有上限 —— 长出了别处长不出来的社区
Mihon 一整个独立安装的 APK 用自己的类加载器把它的 DEX 拉进本进程,反射实例化 同进程同权限,靠签名信任把关 一千个漫画站的适配,由一千个人在宿主之外各自维护
IntelliJ 一个 jar + plugin.xml 同一个 JVM,但每个插件一个类加载器 类空间隔离(各带各的库版本),但没有 CPU / 内存隔离 插件能深度改造 IDE 的每一层,代价是它卡死你也卡死
VS Code 一个 npm 包 + contributes 另一个进程(extension host)里 require 进程边界。拿不到 DOM,所有调用过 IPC 插件永远卡不死编辑器;UI 风格统一;代价是几乎全异步
Chrome 扩展 manifest.json + 几段脚本 按清单分派:service worker、注入页面的独立世界 最强:独立世界 + 进程沙箱 + CSP + 权限逐项授予 恶意扩展也得先向用户要到权限;代价是能力被切得很碎

没有哪一头是「对」的。Obsidian 那头换来的是不设上限的创造力,Chrome 那头换来的是可控的风险。你造插件系统时的第一个决定,就是在这根轴上选一个点 —— 后面所有技术选择都由它推导出来。这本书会带你把这根轴走一遍,然后帮你选。

「亲手造一台」是什么意思

市面上讲插件系统的文章,大多停在「画个架构图 + 讲讲概念」。这本书不是。我们要造的宿主是一个真的、能跑的程序,它就嵌在这些网页里,用 JavaScript 写成,从第 6 章起一路长大:

  • 第 6 章,它第一次能加载插件 —— 你在文本框里现写一段插件代码,点「装载」,宿主用 new Function 把这段字符串变成活对象、调它的 activate(),它注册的命令真的出现在命令面板里,点下去真的执行;
  • 第 8 章,它学会读清单和拒绝 —— 你把宿主版本从 1.0.0 改成 2.0.0,原本能装的插件当场全被拒
  • 第 14 章,你会真的看着一个插件逃出沙箱,用三行代码从一个「什么都没给它」的 ctx 里把整个 globalThis 拿回去;
  • 第 15 章,一个插件的死循环会真的把这个页面卡住 0.7 秒 —— 你自己就是那个用户;换进 Worker 之后,同样的重活,心跳一格不漏;
  • 第 17 章,你勾掉一个权限,插件的调用当场抛出 PermissionDenied
  • 第 23 章,所有零件合起来 —— 一个清单里只声明了 notes.read 却偷偷调 fs.read 的「效率助手 Pro」被装上、启用、拦截,而其它三个插件毫发无损。

正文里出现的每一个数字,都是这台宿主当场算的,不是我写死的:那段笔记是 36 个字、来回启停 5 轮之后残留 5 个监听器、宿主升到 2.0.0 时生态存活率从 75% 掉到 20%…… 全部来自真的运行。

写给谁

这本书写给这样的人

  • 一直好奇「插件到底是怎么被装进去的」,但没找到过讲清楚的东西
  • 想给自己的程序开个口子,不知道从哪开、开多大
  • 用过 Mihon / VS Code / Obsidian,甚至写过插件,但对宿主那一侧完全是黑箱
  • 能读 JS 或 Kotlin 其中一门(书里两种都有,互相对照)
  • 想要的是「亲手造一遍」那种理解,而不是背几个名词

这本书不做的事

  • 不是某一个系统的插件开发教程 —— 那些官方文档写得比我好
  • 不教你怎么绕过某个具体软件的限制
  • 不深挖 JVM 字节码、V8 内部实现、操作系统沙箱的底层细节
  • 不假装存在「最佳插件架构」—— 全书都在讲取舍,不在讲答案

六卷

卷 I · 口 OPENING为什么要在自己的程序上开一道门
先把问题立起来:插件系统解决的到底是什么问题,五个真实系统各自怎么答,以及这本书的地图。
  1. 01你写不完所有代码
  2. 02五个系统,一根轴▸ 五系统对照轴
  3. 03所有插件系统都在答同样五个问题▸ 成品预览
卷 II · 框 SLOT宿主先要把自己拆开,才有地方插
插件系统的第一课不是「插件怎么写」,是「宿主怎么改造自己」。扩展点、契约流派、清单,以及第一台能跑的宿主。
  1. 04先有扩展点,才有插件▸ 注册表
  2. 05契约的三种流派:接口、事件、声明
  3. 06动手 ①:起一台宿主▸ 宿主 v1
  4. 07清单:插件的身份证▸ 清单校验器
  5. 08动手 ②:让宿主学会拒绝▸ 宿主 v2
卷 III · 入 LOAD一段陌生代码,怎么变成进程里活着的对象
加载是整件事最像魔法的一步。JS 侧、JVM 侧各有三种做法,Mihon 那套「运行时装 APK」会被完整拆开。
  1. 09加载:把死代码变成活对象▸ 三种加载法
  2. 10JVM 派:类加载器与那个经典异常▸ 类加载器
  3. 11Mihon 深挖:它真的在运行时装 APK▸ 真源码插件
  4. 12JS 派:三种把代码放进来的姿势
  5. 13生命周期:卸载为什么那么难▸ 泄漏计数器
卷 IV · 墙 WALL它崩了、卡了、作恶了,你怎么办
这一卷最硬,也最好玩:一次真的沙箱逃逸、一次真的宿主卡死、以及三种隔离手段的实测对比。
  1. 14不设防会怎样:现场逃逸一次▸ 逃逸演示
  2. 15崩溃、卡死与超时▸ 宿主心跳
  3. 16沙箱的三种真做法▸ 沙箱对比
  4. 17权限:把 Chrome 的模型拆下来装上▸ 权限拦截
  5. 18动手 ③:为什么 VS Code 不给插件 DOM▸ RPC 桥
卷 V · 变 DRIFT门开了就关不上了 —— 演化与生态
插件系统真正的难题不在第一天,在第三年:宿主要升级,插件要互相依赖,启动越来越慢,还有人往里塞恶意代码。
  1. 19版本地狱:宿主升级,一万个插件怎么办▸ 兼容矩阵
  2. 20依赖与冲突:插件之间也要说话▸ 依赖解析
  3. 21一千个插件的冷启动▸ 懒激活
  4. 22分发与信任:商店、签名、供应链
卷 VI · 造 BUILD把所有零件合起来,装到你自己的程序上
完整宿主的全代码走查,以及一张你可以直接抄的决策表:你的程序该开多大的门。
  1. 23合起来:一台完整的插件系统▸ 完整宿主
  2. 24你该开多大的门

关于书里的事实

五个真实系统的机制细节,都来自各家官方文档与源码(Mihon 的加载流程直接读了 ExtensionLoader.kt),并已核对到 2026 年 7 月的现状。凡是带时效的说法(比如 Chrome 商店 2026 年 8 月 31 日下架最后一批 MV2 扩展、Mihon 当前支持的扩展库版本),书里都会标出来源和时间点 —— 因为这类东西一定会过期,你该知道它是什么时候的话。

书里出现的比例类估计(比如「生态里大约多少插件写 ^1.x」)会明确标成假设,不会伪装成实测数据。

◆ 读法建议

卷 I 到卷 II 建议按顺序读,它们在建立整本书的词汇表(扩展点、契约、清单)。之后如果你只关心某一块,可以直接跳:想搞懂加载去卷 III,想搞懂安全去卷 IV,正在被版本兼容折磨去卷 V。

每一章末尾都有一节「装到自己身上」,把这一章的机制翻译成你能立刻用的东西 —— 大多给的是 Kotlin / Android 的落地写法(ServiceLoaderDexClassLoader、版本契约),因为那是最常见的「我想给自己的 App 加插件」场景。