为什么 DSH 升级后插件会失效
别名:插件失效、升级炸插件、DSH 升级后插件没了、dsh-loader 挡不住什么
从层栈与补丁的绑定关系解释升级为什么会打断已装插件:命中行 id 的补丁、被整段替换的 config、客户端入口图、以及 home 层压过 profile 层这四类断裂,并说清兼容层 dsh-loader 能扛哪一段、扛不住哪一段。
升级为什么能「炸」插件
照直觉,升级 DSH 像是在换软件版本,插件是独立装的 npm 包,两边应该互不打扰。实际不是:插件不是挂在 DSH 上的功能开关,而是一层补丁。启动时 DSH 把 profile 里那串 bundle 的补丁依次叠到一棵空树上(层序与覆盖规则见 concept/1),插件那一层要么 insert 新行、要么按 id 改已有行的顶层键。于是「插件能不能用」取决于补丁能不能命中它要改的那些东西——而行的 id、行上的键、插件注入的服务,全都是 DSH 自己的实现细节,会随版本移动。
失效因此有两副面孔:看得见的(启动报错、插件整行不见了)与看不见的(插件还在,配置丢了、界面没出现)。下面四类断裂分别对应它们。
一、按 id 命中的补丁,目标不见了
补丁列表里只有两类操作:id + insert(往某个分组里插行)与 id + 其它键(改已有行的顶层键)。两者都靠 id 找目标:
- 找不到目标的补丁只告警、不报错,被跳过。这条设计对启动稳定性是好事,对插件是坏事——升级把某个行 id 改掉之后,插件那层会被安静地跳过,界面上什么都不显示,日志里只有一行告警。
- 列在
dsh.profile.bundles里、却没有dsh.bundle声明的包会直接抛错(declares no dsh.bundle in its package.json),这是配置错误而不是兼容问题:多半是你手改过 profile 的package.json。
要做的事:升级后养成看启动输出的习惯;dsh --profile <name> --dump-config 会打印组合后的树,相对 --dump-default-config(不叠用户层与 --patch)就能看出「包带的」与「你或插件改的」差在哪。
二、config 是整段替换,不是深合并
这是最容易在升级后以「插件在、功能没了」的形式暴露的一类。补丁写 config: {...} 时,目标行的整个 config 被换掉,不是逐键合并。旧版本里这样写没事,是因为当时该行的 config 只有那么几个键;升级后新版本给这一行补了必需的键,插件那层那份「只写自己关心的键」的 config 就把它们一并抹掉了。
要做的事:看到配置项莫名回到默认值或功能半残,先怀疑它。--dump-config 打出来的就是替换后的结果,照着新版默认值把要保留的键补全。
三、客户端半端是另一条链,宿主修好了它也不一定在
一个插件可以两半,宿主侧在 Node 进程里、客户端侧在浏览器里(见 concept/4)。客户端半端不是自己跑起来的:服务端扫描已经加载的插件行,把每个客户端半端变成入口图里的一行,序列化进页面 <head>;某个包解析不出 ./client 产物时聚合报错会连带说出它在找哪个路径。
两条推论:
- 宿主那一行没挂上(第一类断裂),客户端入口就不会出现在图里——你在界面上看不到任何东西,但宿主日志里可能只有一个告警。排查看两处,不要只看一处。
- 声明里的
inject是一串包名,升级改了客户端包的拆分或命名,组合阶段就会直接抛错。这类修复点是package.json的dsh.client,不是插件逻辑。
四、home 层压过 profile 层,改了却像没改
层序是固定的:bundle 层(按 dsh.profile.bundles 顺序)→ profile 的 cordis.patch.yml → $DSH_HOME/cordis.patch.yml → --patch 覆盖 → 遥测开关。注意第三个:机器级的 home 层比每个 profile 自己的层更高,这是有意的设计(机器偏好对所有 profile 生效)。
升级后最典型的踩法是:为了修插件,你在某个 profile 的 cordis.patch.yml 里改了配置,结果没生效——因为 $DSH_HOME/cordis.patch.yml 里有一条更早留下的同名补丁压在上面。「我改了没用」在 DSH 里通常不是缓存问题,而是层序问题。
dsh-loader 能挡哪一段、挡不住哪一段
dsh-loader 的定位是版本感知的运行时兼容层:让第三方插件在 DSH 本体升级、内部包版本变动之后,不改源码继续挂上。把它当作第一道缓冲是对的,但要清楚边界:
| 失效类型 | dsh-loader 管到吗 | 为什么 |
|---|---|---|
| 内部包版本变动、老签名继续被调用 | 管得到(它的本职) | 这正是「兼容 shim」要吸收的差异 |
| 补丁命中的行 id 变了 | 管不到 | 补丁是数据,shim 不改写别人的补丁文件;命不中就是命不中 |
config 整段替换把新键抹掉 | 管不到 | 这是补丁语义,不是加载器能修的 |
客户端入口图与 inject 声明断裂 | 管不到 | 客户端半端的解析与签名在另一个包里 |
| home 层压过 profile 层 | 管不到 | 层序是契约,不是兼容问题 |
| dsh-loader 自己没跟上新版 DSH | 管不到自己 | 它同样是装在 profile 里的一个包(见 concept/2),也一样要等更新 |
它自己的可靠性也有实况可看:市场账本里唯一声明了它的整合包是「更好的侧边栏」(hxh230802.better-sidebar,manifest v4),dependencies 里写的是 "@dsh-plugin/dsh-loader": "1.3.3-dev.33218986978"——-dev.<一串数字> 这种版本形态说明上游在发开发版。把「长期稳定」押在一个发开发版的兼容层上,是有风险的;它支持的 DSH 版本区间本轮也没能核实(见 dsh-loader 的 npm 页面 的「未核实」一节)。
一个具体的插件长什么样
拿 plugin/2 当样本:它的 cordis.patch.yml 只有一行 insert,把宿主插件按 id dspack-host 插进树里;它的客户端半端靠 package.json 的 dsh.client 声明与 exports["./client"] 挂进页面。看懂了这个结构,就明白升级要动的是什么:不是它的 lib/host.js,而是那行 id: dspack-host 所依赖的服务名(connection / webServer / skills)与它写的 config 键。这层结构与自写插件一模一样,写法见 tutorial/2。
升级之后按什么顺序查
- 看启动输出:被跳过的补丁只告警,别漏掉。
- 对比两棵树:
--dump-default-config与--dump-config,定位差异落在哪一层。 - 最小化实验:把可疑改动单独写成一个
--patch文件叠上去。它按 argv 顺序排在最后,能立刻确认「是补丁本身的问题」还是「被更高的层压住了」。 - 最后才动插件源码:上面三类断裂大多不需要改插件;真要改,改的也多半是声明(
dsh.bundle/dsh.client/ patch 文件),不是逻辑。
「装插件时到底发生了什么」——依赖怎么解析、落点在哪、为什么 GitHub 来源要先授权构建——见 tutorial/3;层序与 config 替换语义的权威出处见 concept/1。
相关教程
谁引用了这一条
信息表
- appliesTo
- dsh 0.1.0-rc.6(实测,本机 @deepseek-ai/dsh 版本);更高版本未核实
- category
- ops.upgrade
- difficulty
- intermediate
- origin
- original
- plugins
- [object Object]、[object Object]
- prereq
- concept/1、concept/2
- related
- concept/4、tutorial/3
- updatedAt
- 2026-10-02
标签:兼容性、排错、升级、patch 层