@deepseek-ai/dsh-client-hmr 0.1.5-rc.2 → 0.1.6-alpha.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.i18n.yaml +2 -2
- package/README.md +2 -2
- package/README.zh.md +9 -9
- package/lib/client.js +19 -7
- package/lib/index.js +2 -2
- package/lib/types/client/index.d.ts +11 -0
- package/lib/types/index.d.ts +1 -1
- package/package.json +7 -5
package/README.i18n.yaml
CHANGED
|
@@ -2,5 +2,5 @@
|
|
|
2
2
|
# side as of the last confirmed-consistent state. Both languages carry equal authority;
|
|
3
3
|
# after editing either side, bring the other along and re-record with:
|
|
4
4
|
# pnpm run verify-translation-pairing --write packages/client/hmr/README.md
|
|
5
|
-
README.md:
|
|
6
|
-
README.zh.md:
|
|
5
|
+
README.md: 26bc640c6cdd213fbc78de920e12444589901990
|
|
6
|
+
README.zh.md: 72d941f678e7e29a56bca678a79c4740667e7e6b
|
package/README.md
CHANGED
|
@@ -63,11 +63,11 @@ The chain is two halves with one contract: the node half owns bundle detection a
|
|
|
63
63
|
|
|
64
64
|
### The browser swap
|
|
65
65
|
|
|
66
|
-
On a `rebuilt` frame the revision makes `invalidate` select that plugin's immutable one-resource combo URL instead of its initial multi-resource URL. `prefetch` loads and registers the new factory while the old fiber still serves. The remaining order is registry-first teardown (`registry.delete` before the fiber's disposer emits `internal/plugin`, or the vendored Loader flags the entry disabled), drain the old fiber's unload, delete `entry.fiber`, remove owned `<style data-plugin>` tags, then `entry.refresh()` re-imports and remounts, and `fiber.await()` rethrows startup failures
|
|
66
|
+
On a `rebuilt` frame the revision makes `invalidate` select that plugin's immutable one-resource combo URL instead of its initial multi-resource URL. `prefetch` loads and registers the new factory while the old fiber still serves. The remaining order is registry-first teardown (`registry.delete` before the fiber's disposer emits `internal/plugin`, or the vendored Loader flags the entry disabled), drain the old fiber's unload, delete `entry.fiber`, remove owned `<style data-plugin>` tags, then `entry.refresh()` re-imports and remounts, and `fiber.await()` rethrows startup failures loudly. The swap is safe because execution is pure registration under the lazy-CJS model: every module side effect lives in the factory closure and runs at materialization.
|
|
67
67
|
|
|
68
68
|
### Cascade and self-reload
|
|
69
69
|
|
|
70
|
-
A fiber's activation epoch strings its service providers' uids, so replacing a provider's fiber re-cascades every dependent through
|
|
70
|
+
A fiber's activation epoch strings its service providers' uids, so replacing a provider's fiber re-cascades every dependent through Cordis itself with zero HMR-side bookkeeping. This plugin is itself a graph entry, so a rebuilt frame may name it; the in-flight reload keeps running in the old bundle's closure and the new bundle's apply opens a fresh channel.
|
|
71
71
|
|
|
72
72
|
### Failure policy
|
|
73
73
|
|
package/README.zh.md
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
---
|
|
2
|
-
description: "
|
|
2
|
+
description: "仅用于开发环境的浏览器客户端插件热重载:重建插件 bundle 后原地替换运行中的插件,供开发者迭代 web GUI。"
|
|
3
3
|
kind: "package-reference"
|
|
4
4
|
---
|
|
5
5
|
|
|
@@ -9,7 +9,7 @@ kind: "package-reference"
|
|
|
9
9
|
|
|
10
10
|
## 概述
|
|
11
11
|
|
|
12
|
-
`dsh-client-hmr` 会在浏览器客户端插件的 bundle 重建后原地重载该插件,让编辑插件源码的开发者无需整页刷新即可看到变更。如果没有重建 watcher,整条链路保持空闲:只有 `pnpm run dev:web` 之类的进程重写客户端 bundle
|
|
12
|
+
`dsh-client-hmr` 会在浏览器客户端插件的 bundle 重建后原地重载该插件,让编辑插件源码的开发者无需整页刷新即可看到变更。如果没有重建 watcher,整条链路保持空闲:只有 `pnpm run dev:web` 之类的进程重写客户端 bundle 时才会产生它所响应的重建。每次重载只替换一个插件并携带全新组件状态,而数据层(连接、运行时与 Session 对象)保持不变。这里的一切都是浏览器侧的开发机制;模型永远看不到它。
|
|
13
13
|
|
|
14
14
|
## 目录
|
|
15
15
|
|
|
@@ -33,7 +33,7 @@ kind: "package-reference"
|
|
|
33
33
|
|
|
34
34
|
### 一次重载做什么
|
|
35
35
|
|
|
36
|
-
每次重载都会重新执行插件 bundle
|
|
36
|
+
每次重载都会重新执行插件 bundle,并用全新状态重新挂载插件。依赖被重载插件的插件会随之自动重载。失败的重载会以可见方式报告,并在下一次重建时从头重试。
|
|
37
37
|
|
|
38
38
|
### 配置
|
|
39
39
|
|
|
@@ -41,7 +41,7 @@ kind: "package-reference"
|
|
|
41
41
|
|---|---|---|
|
|
42
42
|
| `pollIntervalMs` | `500` | bundle stat 轮询间隔,单位为毫秒 |
|
|
43
43
|
|
|
44
|
-
生成的[配置目录](../../../docs/config-catalog.zh.md#deepseek-aidsh-client-hmr)
|
|
44
|
+
生成的[配置目录](../../../docs/config-catalog.zh.md#deepseek-aidsh-client-hmr)是所有受支持字段及其 JSDoc 的完整真源。
|
|
45
45
|
|
|
46
46
|
### 观察成功
|
|
47
47
|
|
|
@@ -59,19 +59,19 @@ kind: "package-reference"
|
|
|
59
59
|
|
|
60
60
|
### 设计理念
|
|
61
61
|
|
|
62
|
-
链路分为两半,共用一份约定:node 半侧负责 bundle 检测与通知,浏览器半侧负责替换。node 半侧运行一个 interval,从 module host 读取文件前的基线开始 stat 轮询每个图 bundle。未变化的启动 row 无需读取内容或求 hash 即可开始监视;发生变化的 row,或产物恢复后的 dirty row,会进入 `rebuilt()`,且只广播真实 revision 变更。`rebuilt()` 会把当前 source map 与已变化的 bundle 一起读取;仅写入 map 不会重载可执行代码。node 半侧还提供 `/plugins/events`,一个广播 `graph` 与 `rebuilt` 帧的 SSE
|
|
62
|
+
链路分为两半,共用一份约定:node 半侧负责 bundle 检测与通知,浏览器半侧负责替换。node 半侧运行一个 interval,从 module host 读取文件前的基线开始 stat 轮询每个图 bundle。未变化的启动 row 无需读取内容或求 hash 即可开始监视;发生变化的 row,或产物恢复后的 dirty row,会进入 `rebuilt()`,且只广播真实 revision 变更。`rebuilt()` 会把当前 source map 与已变化的 bundle 一起读取;仅写入 map 不会重载可执行代码。node 半侧还提供 `/plugins/events`,一个广播 `graph` 与 `rebuilt` 帧的 SSE(Server-Sent Events)通道。
|
|
63
63
|
|
|
64
64
|
### 浏览器侧替换
|
|
65
65
|
|
|
66
|
-
收到 `rebuilt` 帧后,帧内 revision 会让 `invalidate` 选择该插件不可变的单资源 combo URL,而不是初始多资源 URL。`prefetch` 在旧 fiber 仍在服务时加载并注册新 factory
|
|
66
|
+
收到 `rebuilt` 帧后,帧内 revision 会让 `invalidate` 选择该插件不可变的单资源 combo URL,而不是初始多资源 URL。`prefetch` 在旧 fiber 仍在服务时加载并注册新 factory。其余顺序是:先从注册表删除,再拆卸(在 fiber 的 disposer 发出 `internal/plugin` 之前执行 `registry.delete`,否则 vendored Loader 会把该 entry 标为禁用)、等待旧 fiber 卸载完成、删除 `entry.fiber`、移除自身拥有的 `<style data-plugin>` 标签,然后 `entry.refresh()` 重新导入并挂载,`fiber.await()` 直接把启动失败重新抛出。替换之所以安全,是因为在惰性 CJS 模型下执行只是注册:每个模块副作用都位于 factory 闭包中,在物化时运行。
|
|
67
67
|
|
|
68
68
|
### 级联与自重载
|
|
69
69
|
|
|
70
|
-
fiber 的激活 epoch 会串联其服务提供方的 uid,因此替换提供方 fiber 会通过
|
|
70
|
+
fiber 的激活 epoch 会串联其服务提供方的 uid,因此替换提供方 fiber 会通过 Cordis 自身级联重载所有依赖方,无需 HMR(热模块替换)侧维护任何簿记信息。本插件本身也是一个图 entry,因此 `rebuilt` 帧可能点名它;进行中的重载在旧 bundle 的闭包中继续运行,新 bundle 的 apply 会打开全新通道。
|
|
71
71
|
|
|
72
72
|
### 失败策略
|
|
73
73
|
|
|
74
|
-
不回滚:导入失败会让 entry 失去 fiber(下一个 `rebuilt` 帧从头重试),apply 失败则会在外壳的状态投影中留下 FAILED fiber
|
|
74
|
+
不回滚:导入失败会让 entry 失去 fiber(下一个 `rebuilt` 帧从头重试),apply 失败则会在外壳的状态投影中留下 FAILED fiber。两者都会输出醒目的错误日志。
|
|
75
75
|
|
|
76
76
|
### 源码地图
|
|
77
77
|
|
|
@@ -113,7 +113,7 @@ fiber 的激活 epoch 会串联其服务提供方的 uid,因此替换提供方
|
|
|
113
113
|
|
|
114
114
|
这些限制说明重载驱动器不会保留或恢复什么。它们是当前包约束,不是任务积压。
|
|
115
115
|
|
|
116
|
-
- **重载有意保持粗粒度**——全新 fiber 与全新组件;被重载插件内的 React
|
|
116
|
+
- **重载有意保持粗粒度**——全新 fiber 与全新组件;被重载插件内的 React 状态会丢失,而数据层(连接 fiber、运行时 fiber、Session 对象)不受影响。react-refresh 级状态保留与重新执行 bundle 冲突,因此有意排除。
|
|
117
117
|
- **失败时不回滚**——失败的重载会让该 entry 保持 FAILED 并在 loader 状态投影中可见;系统不会自动恢复先前 bundle。
|
|
118
118
|
- **重建帧不会替换启动图**——每个帧都携带单资源 combo 重载所需的插件产物 revision;页面重载时才接收重新组合的启动图。
|
|
119
119
|
|
package/lib/client.js
CHANGED
|
@@ -46,6 +46,23 @@ window.__ModuleLoader__.load({
|
|
|
46
46
|
const name = "client-hmr";
|
|
47
47
|
/** Required services: the vendored Loader (entry governance) and the client module system (boot provide, service name `modules`). */
|
|
48
48
|
const inject = ["loader", "modules"];
|
|
49
|
+
/**
|
|
50
|
+
* Registry-first teardown of an entry's running fiber so `entry.refresh()`
|
|
51
|
+
* rebuilds it (see the module comment): delete the runtime record before the
|
|
52
|
+
* fiber's disposer emits `internal/plugin` (or the Loader flags the entry
|
|
53
|
+
* disabled), drain the unload so effect disposers finish before a new apply
|
|
54
|
+
* re-registers, then clear `entry.fiber` so `refresh()` re-imports instead of
|
|
55
|
+
* no-oping. A fiberless entry is left untouched.
|
|
56
|
+
* @param entry - the Loader entry to tear down.
|
|
57
|
+
*/
|
|
58
|
+
async function tearDownEntryFiber(entry) {
|
|
59
|
+
const oldFiber = entry.fiber;
|
|
60
|
+
if (oldFiber === void 0) return;
|
|
61
|
+
const runtime = oldFiber.runtime;
|
|
62
|
+
if (runtime !== null) entry.ctx.registry.delete(runtime.callback);
|
|
63
|
+
while (oldFiber.inertia !== void 0) await oldFiber.inertia;
|
|
64
|
+
delete entry.fiber;
|
|
65
|
+
}
|
|
49
66
|
/** Find the loader entry whose module specifier is `id` (entry tree ids are random; the package name lives in `options.name`). */
|
|
50
67
|
function findEntry(loader, id) {
|
|
51
68
|
for (const entry of loader.entries()) if (entry.options.name === id) return entry;
|
|
@@ -70,13 +87,7 @@ window.__ModuleLoader__.load({
|
|
|
70
87
|
}
|
|
71
88
|
modLoader.invalidate(id, rev);
|
|
72
89
|
await modLoader.prefetch(id);
|
|
73
|
-
|
|
74
|
-
if (oldFiber !== void 0) {
|
|
75
|
-
const runtime = oldFiber.runtime;
|
|
76
|
-
if (runtime !== null) entry.ctx.registry.delete(runtime.callback);
|
|
77
|
-
while (oldFiber.inertia !== void 0) await oldFiber.inertia;
|
|
78
|
-
delete entry.fiber;
|
|
79
|
-
}
|
|
90
|
+
await tearDownEntryFiber(entry);
|
|
80
91
|
removeOwnedStyles(id);
|
|
81
92
|
await entry.refresh();
|
|
82
93
|
await entry.fiber?.await();
|
|
@@ -118,6 +129,7 @@ window.__ModuleLoader__.load({
|
|
|
118
129
|
exports.apply = apply;
|
|
119
130
|
exports.inject = inject;
|
|
120
131
|
exports.name = name;
|
|
132
|
+
exports.tearDownEntryFiber = tearDownEntryFiber;
|
|
121
133
|
return module.exports;
|
|
122
134
|
}
|
|
123
135
|
});
|
package/lib/index.js
CHANGED
|
@@ -9,7 +9,7 @@ const EVENTS_ENDPOINT = "/plugins/events";
|
|
|
9
9
|
* HMR plugin, node half: the host end of the dev reload chain. One interval
|
|
10
10
|
* stat-polls every graph row's client bundle (polling by design: network mounts
|
|
11
11
|
* deliver no inotify events), reports changes through
|
|
12
|
-
* `
|
|
12
|
+
* `clientModules.rebuilt(id)`, and serves the `/plugins/events` SSE channel
|
|
13
13
|
* broadcasting graph/rebuilt frames to the browser half (src/client/).
|
|
14
14
|
* The web bundle mounts this row unconditionally: without a rebuild
|
|
15
15
|
* watcher rewriting client bundles, the poll observes no changes and the
|
|
@@ -38,7 +38,7 @@ function sameBundleStat(left, right) {
|
|
|
38
38
|
}
|
|
39
39
|
/**
|
|
40
40
|
* Mount the dev chain: bundle watches, rebuilt reporting, and the SSE channel.
|
|
41
|
-
* @param ctx - host plugin context carrying
|
|
41
|
+
* @param ctx - host plugin context carrying clientModules and webServer.
|
|
42
42
|
* @param config - validated {@link Config}.
|
|
43
43
|
*/
|
|
44
44
|
function apply(ctx, config) {
|
|
@@ -62,12 +62,23 @@
|
|
|
62
62
|
* leaves a FAILED fiber for the shell's status projection. Both log loudly.
|
|
63
63
|
*/
|
|
64
64
|
import type { Context } from '@deepseek-ai/cordis';
|
|
65
|
+
import type { Entry } from '@deepseek-ai/cordis-plugin-loader';
|
|
65
66
|
export type { PluginsEventFrame } from '../events.ts';
|
|
66
67
|
export { EVENTS_ENDPOINT } from '../events.ts';
|
|
67
68
|
/** Cordis plugin name. */
|
|
68
69
|
export declare const name = "client-hmr";
|
|
69
70
|
/** Required services: the vendored Loader (entry governance) and the client module system (boot provide, service name `modules`). */
|
|
70
71
|
export declare const inject: string[];
|
|
72
|
+
/**
|
|
73
|
+
* Registry-first teardown of an entry's running fiber so `entry.refresh()`
|
|
74
|
+
* rebuilds it (see the module comment): delete the runtime record before the
|
|
75
|
+
* fiber's disposer emits `internal/plugin` (or the Loader flags the entry
|
|
76
|
+
* disabled), drain the unload so effect disposers finish before a new apply
|
|
77
|
+
* re-registers, then clear `entry.fiber` so `refresh()` re-imports instead of
|
|
78
|
+
* no-oping. A fiberless entry is left untouched.
|
|
79
|
+
* @param entry - the Loader entry to tear down.
|
|
80
|
+
*/
|
|
81
|
+
export declare function tearDownEntryFiber(entry: Entry): Promise<void>;
|
|
71
82
|
/**
|
|
72
83
|
* Mount the HMR driver: subscribe to the system SSE channel and hot-swap
|
|
73
84
|
* rebuilt entries.
|
package/lib/types/index.d.ts
CHANGED
|
@@ -14,7 +14,7 @@ export interface Config {
|
|
|
14
14
|
export declare const Config: z<Config>;
|
|
15
15
|
/**
|
|
16
16
|
* Mount the dev chain: bundle watches, rebuilt reporting, and the SSE channel.
|
|
17
|
-
* @param ctx - host plugin context carrying
|
|
17
|
+
* @param ctx - host plugin context carrying clientModules and webServer.
|
|
18
18
|
* @param config - validated {@link Config}.
|
|
19
19
|
*/
|
|
20
20
|
export declare function apply(ctx: Context, config: Config): void;
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@deepseek-ai/dsh-client-hmr",
|
|
3
3
|
"description": "Dev-only hot-reload driver for script-loaded client entries: SSE rebuilt frames → invalidate/prefetch → fiber swap through the vendored Loader entry",
|
|
4
|
-
"version": "0.1.
|
|
4
|
+
"version": "0.1.6-alpha.1",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
7
7
|
},
|
|
@@ -31,7 +31,9 @@
|
|
|
31
31
|
},
|
|
32
32
|
"dsh": {
|
|
33
33
|
"client": {
|
|
34
|
-
"inject": [
|
|
34
|
+
"inject": [
|
|
35
|
+
"@deepseek-ai/dsh-client-modules"
|
|
36
|
+
],
|
|
35
37
|
"platform": "web",
|
|
36
38
|
"immediately": true
|
|
37
39
|
}
|
|
@@ -45,9 +47,9 @@
|
|
|
45
47
|
},
|
|
46
48
|
"devDependencies": {
|
|
47
49
|
"@deepseek-ai/cordis-plugin-loader": "^1.0.3",
|
|
48
|
-
"@deepseek-ai/dsh-
|
|
49
|
-
"@deepseek-ai/dsh-
|
|
50
|
-
"@deepseek-ai/dsh-invariants": "^0.1.
|
|
50
|
+
"@deepseek-ai/dsh-client-modules": "^0.1.6-alpha.1",
|
|
51
|
+
"@deepseek-ai/dsh-host-webserver": "^0.1.6-alpha.1",
|
|
52
|
+
"@deepseek-ai/dsh-invariants": "^0.1.6-alpha.1",
|
|
51
53
|
"@deepseek-ai/cordis": "^4.0.2"
|
|
52
54
|
},
|
|
53
55
|
"files": [
|