Repository navigation
已安装 bundle 的「原地换代」缺少官方入口:连卸载后再挂载都拿不到新模块(附实测) #7526
HorusJiang
started this conversation in
Ideas
Replies: 0 comments
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Uh oh!
There was an error while loading. Please reload this page.
[Feature] 已安装 bundle 的"原地换代"缺少官方入口:连"卸载后再挂载"都拿不到新模块
TL;DR (English)
Nothing in a running host can make a replaced
node_modulespackage take effect.Install is live (the entry URL was never imported before); updates are not, and
neither is any workaround: I measured that a full unmount → remount of the row
goes through DSH's own
reconcileProfilePatches, really disposes the old fiber andreally creates a new one — and the new fiber still evaluates the cached module
generation, because Node's ESM registry is keyed by URL and only
boot/hmr.partialReload()purges it (via--expose-internals) — and that path isblocked for every npm-installed package by two
url.includes('/node_modules/')exclusions. Please expose that capability (or state the boundary as a documented
product rule).
现状
packages/boot/plugin-manager/src/index.ts:566(hmr 存在 →applied)restart-required:391:if (Object.hasOwn(before, name)) return 'restart-required'两处都很保守,而且没有给插件作者留任何字段:
plugin_manager只读dsh.bundle.patch;dsh.compatibility.dshReleases在整个 DSH 源码里没有任何消费者(按
compatibility?:/dshReleases全仓 grep 零命中)。同一个边界被第三方管理面独立复现了一遍:社区市场
dshmarket自带install.ts/hot.ts/verify.ts管线,并用自己的src/verify.ts:316 activationAfterReplace()得出同样的"更新要重启"。(它甚至因此报出错误的 verdict,见
docs/issue-hot-update-market.md:restart:false / hot:true而进程跑的是旧模块。)实测:卸载-重挂载也拿不到新代际
方法(探针,事后已逐字节还原):
node_modules/dsh-map-tools/lib/index.js顶部插入一段模块作用域的副作用:向临时日志追加一行(不依赖任何 HTTP 路由注册)。
await import('file:///.../lib/index.js')→ 日志出现 1 行。仪器有效:真 import 了就一定会写。
cordis.patch.yml给该行写disabled: true再移除),即reconcileProfilePatches()那条路。GET /dsh-map-tools/config#2排除了"其实没卸载",#3排除了"旧处理器残留"(若旧 fiber 还在,重复注册同一路径会抛错并被
ctx.inject子作用域吞掉,路由会停在上个版本),#4说明换出来的是同一个模块命名空间。根因:URL 级缓存,只有一处能清
Node 的 ESM registry 按 URL 缓存模块命名空间:同一 URL 一旦被求值过,
后续任何
import()都拿回旧的那一份——DSH Loader 重新挂载、市场的Include 热挂载、关掉再打开,全都一样。而
boot/hmr明确处理了这一点:拦住它的只有两处硬编码排除(各一处):
于是
partialReload()的if (!dependencies.some(dep => this.accepted.has(dep))) continue对
node_modules下的包恒成立 → 静默跳过(不报错、不告警)。而 npm 装出来的包必然在
node_modules下:root怎么配都绕不过去。这一条同时解释了四件事,也说明这不是"保守"而是能力缺口:
hmr.rootnode_modules,且 hmr 会清 loadCache建议(任选,按上游成本排序)
Hmr已有的"清loadCache+ 原地重导入"能力暴露成公开 API(例如
hmr.replaceModule(url)),让plugin_manager、dshmarket这类热挂载消费者在更新后调用它,配合各自 fiber 的卸载/重挂载完成代际替换。
或"profile 声明的包名"参与重载判定,而不是按 URL 子串一刀切。
package.json的dsh段加可选字段(如
dsh.reload: 'module-generation'),声明该包支持原地换代;宿主与市场据此放行,未声明的包保持现状。
"更新已安装的 bundle 需要重启 Host;浏览器半边除外"——
好让插件作者能对用户如实说明,而不是各自去猜、各自去造(并各自造错)。
已排除的替代方案
dsh.compatibility.dshReleases:无消费者,无法用来声明"本包可热替换"。dshmarket试过了,结果是restart:false的假 verdict。(
import('./impl/x.js?rev=<mtime>')是新的 ESM 代际,不碰内部 API),但它只能救作者自己的包,且宿主/市场仍会显示"需要重启"——提示与事实不符,
反而更糟。这应当是上游能力,而不是每个作者的重复劳动。
All reactions