@zhin.js/dependency 1.0.1 → 1.0.2

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.md CHANGED
@@ -1,769 +1,570 @@
1
- # 🌲 @zhin.js/dependency
2
-
3
- 一个强大的依赖树分析工具,支持动态导入、热重载、生命周期管理和可扩展的 Hook 系统。
4
-
5
- ## 📋 目录
6
-
7
- - [主要特性](#-主要特性)
8
- - [安装](#-安装)
9
- - [快速开始](#-快速开始)
10
- - [基本用法](#基本用法)
11
- - [在插件中使用 Hooks](#在插件中使用-hooks)
12
- - [继承 Dependency 类](#继承-dependency-类)
13
- - [配置](#-配置)
14
- - [环境变量](#环境变量)
15
- - [运行时配置](#运行时配置)
16
- - [可扩展 Hook 系统](#-可扩展-hook-系统)
17
- - [热重载](#-热重载)
18
- - [类继承指南](#-类继承指南)
19
- - [插件生态系统](#-插件生态系统)
20
- - [API 文档](#-api-文档)
21
- - [生命周期](#-生命周期)
22
-
23
- ## ✨ 主要特性
24
-
25
- - 🌲 **依赖树构建** - 自动构建模块依赖关系树
26
- - 🔄 **热重载支持** - 文件变更时自动重载,保留子依赖树
27
- - 🎯 **原生 import 支持** - 使用标准 ES 模块语法,无需自定义函数
28
- - 🪝 **可扩展 Hook 系统** - 注册自定义 hooks,支持自动类型推断
29
- - 📦 **跨运行时支持** - Node.js / tsx / Bun
30
- - 🎨 **生命周期管理** - `start`, `mount`, `dispose`, `stop` 生命周期方法
31
- - 🔔 **EventEmitter 集成** - 标准的事件系统
32
- - 🎯 **TypeScript 类型支持** - 完整的类型定义和类型推断
33
- - 🧬 **继承支持** - 完全支持类继承,创建自定义插件类
1
+ # @zhin.js/dependency
34
2
 
35
- ## 📦 安装
3
+ 基于运行时依赖树分析的模块管理系统,提供自动依赖去重、热重载和生命周期管理功能。
36
4
 
37
- ```bash
38
- pnpm add @zhin.js/dependency
39
- ```
5
+ ## ✨ 核心特性
40
6
 
41
- ## 🚀 快速开始
7
+ ### 1. **自动依赖去重**
8
+ - 全局唯一实例:同一文件路径在整个依赖树中只有一个实例
9
+ - 引用计数:通过 `refs` 集合追踪所有引用者
10
+ - 智能共享:自动识别共享依赖并显示引用计数
42
11
 
43
- ### 基本用法
12
+ ### 2. **热重载 (Hot Reload)**
13
+ - 原地重载:根节点重载时保持引用不变
14
+ - 智能 Diff:自动比较新旧子依赖,保留未变化的子树
15
+ - 状态保持:共享依赖在重载时保持状态和监听器
44
16
 
45
- ```typescript
46
- import { Dependency } from '@zhin.js/dependency';
17
+ ### 3. **生命周期管理**
18
+ - 细粒度生命周期钩子
19
+ - 事件冒泡机制:从叶子节点向根节点传播
20
+ - 自动清理:停止时自动清理子依赖和副作用
47
21
 
48
- // 创建并启动依赖树
49
- const root = new Dependency('./entry.js');
50
- await root.start();
22
+ ### 4. **副作用自动管理**
23
+ - 自动包装全局副作用函数(`setInterval`, `setTimeout`, `setImmediate`)
24
+ - 模块卸载时自动清理副作用
25
+ - 通过环境变量 `DEPENDENCY_WRAP_EFFECTS` 控制开关
51
26
 
52
- // 打印依赖树
53
- console.log(root.printTree('', true, true));
27
+ ## 📦 安装
54
28
 
55
- // 停止依赖树
56
- await root.stop();
29
+ ```bash
30
+ pnpm add @zhin.js/dependency
57
31
  ```
58
32
 
59
- ### 在插件中使用 Hooks
33
+ ## 🚀 快速开始
34
+
35
+ ### 基础用法
60
36
 
61
37
  ```typescript
62
- // plugins/my-plugin.ts
63
- import { onMount, onDispose, addListener } from '@zhin.js/dependency';
38
+ import { Dependency, onMount, onDispose, getCurrentDependency } from '@zhin.js/dependency';
64
39
 
65
- // 挂载钩子
66
- onMount(() => {
67
- console.log('插件已挂载!');
68
- });
40
+ // 插件代码 (plugin.ts)
41
+ export const name = 'my-plugin';
69
42
 
70
- // 添加事件监听器
71
- const unsubscribe = addListener('my-event', () => {
72
- console.log('事件触发');
43
+ onMount(() => {
44
+ console.log('插件已挂载');
73
45
  });
74
46
 
75
- // 卸载钩子
76
47
  onDispose(() => {
77
- unsubscribe();
78
48
  console.log('插件已卸载');
79
49
  });
80
50
 
81
- // 使用原生 import 导入子模块
82
- import './child-plugin';
51
+ // 导入子依赖
52
+ const dep = getCurrentDependency();
53
+ if (dep) {
54
+ await dep.importChild('./child-plugin');
55
+ }
83
56
 
84
- export default {};
85
- ```
57
+ // 主程序
58
+ const root = new Dependency('./plugin.ts');
59
+ await root.start();
60
+
61
+ console.log(root.printTree('', true, true));
62
+ // my-plugin (0 listeners)
63
+ // └── child-plugin (0 listeners)
86
64
 
87
- ### 继承 Dependency 类
65
+ await root.stop();
66
+ ```
88
67
 
89
- 完全支持类继承,创建自定义插件类:
68
+ ### 热重载
90
69
 
91
70
  ```typescript
92
71
  import { Dependency } from '@zhin.js/dependency';
72
+ import chokidar from 'chokidar';
93
73
 
94
- class Plugin extends Dependency {
95
- public version: string = '1.0.0';
96
-
97
- constructor(filePath: string) {
98
- super(filePath);
99
- }
100
-
101
- getInfo(): string {
102
- return `${this.name} v${this.version}`;
103
- }
104
- }
105
-
106
- // 使用自定义类
107
- const root = new Plugin('./entry.js');
74
+ const root = new Dependency('./plugin.ts');
108
75
  await root.start();
109
76
 
110
- // 所有子节点也是 Plugin 实例!
111
- console.log(root.children[0] instanceof Plugin); // true
112
- console.log(root.children[0].getInfo()); // 可以使用自定义方法
77
+ // 监听文件变化
78
+ chokidar.watch('./plugin.ts').on('change', async () => {
79
+ console.log('🔄 检测到文件变化,重载中...');
80
+ await root.reload();
81
+ console.log('✅ 重载完成');
82
+ });
113
83
  ```
114
84
 
115
- ## 🔧 配置
116
-
117
- ### 环境变量
118
-
119
- #### `DEPENDENCY_TREE_INCLUDE`
120
-
121
- 指定需要处理的路径(优先级最高,即使在 `node_modules` 中也会处理)。
122
-
123
- **使用场景:**
124
-
125
- 1. **包含 npm 包中的插件** ⭐
126
-
127
- ```bash
128
- # 场景:你的插件发布为 npm 包,用户安装后需要被依赖树系统处理
129
- DEPENDENCY_TREE_INCLUDE=node_modules/@my-org/my-plugin
130
- ```
85
+ ## 🔧 核心 API
131
86
 
132
- 2. **混合本地和 npm 插件**
87
+ ### Dependency
133
88
 
134
- ```bash
135
- # 同时包含本地插件和多个 npm 包
136
- DEPENDENCY_TREE_INCLUDE=src/plugins,node_modules/@org/plugin1,node_modules/@org/plugin2
89
+ #### 构造函数
90
+ ```typescript
91
+ constructor(filePath: string)
92
+ ```
93
+
94
+ #### 生命周期方法
95
+
96
+ | 方法 | 说明 | 返回值 |
97
+ |------|------|--------|
98
+ | `start()` | 启动依赖:初始化模块、挂载、启动子依赖 | `Promise<void>` |
99
+ | `mount()` | 挂载:执行 `onMount` 钩子 | `Promise<void>` |
100
+ | `dispose()` | 卸载:执行 `onDispose` 钩子和副作用清理 | `Promise<void>` |
101
+ | `stop()` | 停止:卸载、清理缓存、递归停止子依赖 | `Promise<void>` |
102
+ | `reload()` | 重载:卸载、清理、重新导入、Diff 子依赖 | `Promise<Dependency>` |
103
+
104
+ #### 依赖管理方法
105
+
106
+ | 方法 | 说明 | 返回值 |
107
+ |------|------|--------|
108
+ | `importChild(path)` | 导入子依赖(自动去重) | `Promise<P>` |
109
+ | `removeChild(child)` | 移除子依赖(引用计数减 1) | `Promise<void>` |
110
+ | `init()` | 初始化模块(导入代码并注册到全局池) | `Promise<void>` |
111
+
112
+ #### 属性 & Getter
113
+
114
+ | 属性 | 类型 | 说明 |
115
+ |------|------|------|
116
+ | `name` | `string` | 依赖名称(从文件名提取) |
117
+ | `filePath` | `string` | 文件绝对路径 |
118
+ | `children` | `P[]` | 子依赖列表(通过 Symbol 实现) |
119
+ | `refs` | `Set<string>` | 引用者文件路径集合 |
120
+ | `parent` | `P \| null` | 父依赖(`refs` 的第一个元素) |
121
+ | `root` | `P` | 根依赖 |
122
+ | `isRoot` | `boolean` | 是否为根节点(`refs.size === 0`) |
123
+ | `started` | `boolean` | 是否已启动 |
124
+ | `mounted` | `boolean` | 是否已挂载 |
125
+ | `reloading` | `boolean` | 是否正在重载 |
126
+
127
+ #### 工具方法
128
+
129
+ | 方法 | 说明 | 返回值 |
130
+ |------|------|--------|
131
+ | `getPath()` | 获取从根到当前节点的路径 | `Dependency[]` |
132
+ | `getDepth()` | 获取当前节点深度 | `number` |
133
+ | `printTree()` | 打印依赖树 | `string` |
134
+ | `toJSON()` | 序列化为 JSON | `object` |
135
+
136
+ ### Hooks API
137
+
138
+ #### onMount
139
+ 在依赖挂载时执行
140
+ ```typescript
141
+ onMount(() => {
142
+ console.log('已挂载');
143
+ });
137
144
  ```
138
145
 
139
- 3. **包含包内特定目录**
146
+ #### onDispose
147
+ 在依赖卸载时执行
148
+ ```typescript
149
+ onDispose(() => {
150
+ console.log('已卸载');
151
+ });
140
152
 
141
- ```bash
142
- # 只处理包内的 plugins 目录
143
- DEPENDENCY_TREE_INCLUDE=node_modules/@my-org/my-plugin/plugins
153
+ // 内部清理(在生命周期监听器清理前执行)
154
+ onDispose(() => {
155
+ console.log('内部清理');
156
+ }, true);
144
157
  ```
145
158
 
146
- 4. **支持插件生态系统**(社区插件 + 官方插件)⭐
147
-
148
- ```bash
149
- # 同时支持社区插件 (zhin.js-*) 和官方插件 (@zhin.js/*)
150
- DEPENDENCY_TREE_INCLUDE=node_modules/zhin.js-,node_modules/@zhin.js/
159
+ #### getCurrentDependency
160
+ 获取当前模块的 Dependency 实例
161
+ ```typescript
162
+ const dep = getCurrentDependency();
163
+ if (dep) {
164
+ await dep.importChild('./child');
165
+ }
151
166
  ```
152
167
 
153
- #### `DEPENDENCY_TREE_EXCLUDE`
168
+ ## 📋 生命周期详解
154
169
 
155
- 指定需要排除的路径(优先级第二)。
170
+ ### 启动流程 (start)
156
171
 
157
- ```bash
158
- # 排除测试文件
159
- DEPENDENCY_TREE_EXCLUDE=plugins/__tests__,plugins/**/*.test.ts
160
172
  ```
161
-
162
- ### 运行时配置
163
-
164
- #### Bun
165
-
166
- ```json
167
- {
168
- "scripts": {
169
- "start": "bun --preload @zhin.js/dependency/bun-preload.ts src/index.ts"
170
- }
171
- }
173
+ 1. before-start ──▶ 向上冒泡
174
+ 2. self.start ──▶ 本节点监听器
175
+ 3. init() ──▶ 导入模块代码(如果未初始化)
176
+ 4. mount() ──▶ 挂载钩子
177
+ ├─ before-mount ──▶ 向上冒泡
178
+ ├─ self.mounted ──▶ onMount 钩子
179
+ └─ mounted ──▶ 向上冒泡
180
+ 5. children.start() ──▶ 递归启动子依赖
181
+ 6. started ──▶ 向上冒泡
172
182
  ```
173
183
 
174
- #### tsx
184
+ ### 卸载流程 (dispose)
175
185
 
176
- ```json
177
- {
178
- "scripts": {
179
- "start": "tsx --import @zhin.js/dependency/register.mjs src/index.ts"
180
- }
181
- }
182
186
  ```
183
-
184
- #### Node.js(编译后)
185
-
186
- ```json
187
- {
188
- "scripts": {
189
- "build": "tsc",
190
- "start": "node --import @zhin.js/dependency/register.mjs dist/index.js"
191
- }
192
- }
187
+ 1. before-dispose ──▶ 向上冒泡
188
+ 2. self.dispose ──▶ onDispose 钩子
189
+ 3. #onSelfDispose ──▶ 内部副作用清理
190
+ 4. disposed ──▶ 向上冒泡
193
191
  ```
194
192
 
195
- ## 🪝 可扩展 Hook 系统
196
-
197
- ### 自动类型推断
193
+ ### 停止流程 (stop)
198
194
 
199
- 通过 **Module Augmentation** 扩展 `Hooks` interface,实现自动类型推断:
200
-
201
- ```typescript
202
- import { registerHook, useHook } from '@zhin.js/dependency';
203
-
204
- // 1️⃣ 扩展类型定义
205
- declare module '@zhin.js/dependency' {
206
- interface Hooks {
207
- logger: (message: string, level?: 'info' | 'warn' | 'error') => void;
208
- onBeforeMount: (callback: () => void) => void;
209
- }
210
- }
211
-
212
- // 2️⃣ 注册 hook
213
- registerHook({
214
- name: 'logger',
215
- handler: (dep, message, level = 'info') => {
216
- console[level](`[${dep.name}] ${message}`);
217
- }
218
- });
219
-
220
- // 3️⃣ 使用(类型自动推断!)
221
- export const logger = useHook('logger'); // (message: string, level?: 'info' | 'warn' | 'error') => void
222
-
223
- // ✅ TypeScript 提供完整的类型检查和智能提示
224
- logger('Hello', 'info');
195
+ ```
196
+ 检查: refs.size > 0 ? 返回(还有引用者)
197
+ ├─ before-stop ──▶ 向上冒泡
198
+ ├─ self.stop ──▶ 本节点监听器
199
+ ├─ dispose() ──▶ 卸载
200
+ ├─ 清理全局池 ──▶ globalDepMap.delete()
201
+ ├─ 清理模块缓存 ──▶ removeModuleCache()
202
+ ├─ removeChild() ──▶ 递归移除子依赖(refs-1)
203
+ ├─ stopped ──▶ 向上冒泡
204
+ └─ started = false
225
205
  ```
226
206
 
227
- ### 内置 Hooks
207
+ ### 重载流程 (reload)
228
208
 
229
- - `addListener(event, listener)` - 添加事件监听器
230
- - `onMount(hook)` - 添加挂载钩子
231
- - `onDispose(hook)` - 添加卸载钩子
232
- - `importModule(path)` - 导入子模块
209
+ ```
210
+ 1. before-reload ──▶ 向上冒泡
211
+ 2. self.reload ──▶ 本节点监听器
212
+ 3. reloading ──▶ 向上冒泡
213
+ 4. 保存子依赖 ──▶ savedChildren = [...this.children]
233
214
 
234
- ### 自定义 Hooks API
215
+ 5. #cleanupBeforeReload()
216
+ ├─ dispose() ──▶ 卸载
217
+ ├─ parent?.removeChild(this) ──▶ 从父节点移除
218
+ └─ removeModuleCache() ──▶ 清理缓存
235
219
 
236
- - `registerHook(config)` - 注册自定义 hook
237
- - `unregisterHook(name)` - 取消注册 hook
238
- - `useHook(name)` - 创建 hook 函数(支持类型推断)
239
- - `hasHook(name)` - 检查 hook 是否存在
240
- - `getAllHooks()` - 获取所有已注册 hooks
220
+ 6. #reloadNode()
221
+ ├─ 如果是根节点:
222
+ │ ├─ this.#cleanLifecycleListeners() ──▶ 清理生命周期监听器
223
+ │ ├─ this[childrenKey].clear() ──▶ 清空子依赖
224
+ │ ├─ await this.init() ──▶ 重新导入模块
225
+ │ └─ return this ──▶ 返回自己
226
+ └─ 如果有父节点:
227
+ └─ return await this.parent.importChild(path) ──▶ 父节点重新导入
241
228
 
242
- ## 🔥 热重载
229
+ 7. #updateChildren(newNode, savedChildren)
230
+ ├─ #diffChildren() ──▶ 比较新旧子依赖
231
+ │ ├─ removedChildren ──▶ 旧的但新的没有
232
+ │ └─ addedChildren ──▶ 新的但旧的没有
233
+ ├─ #removeChildren() ──▶ 移除已删除的子依赖
234
+ ├─ #addChildren() ──▶ 添加新增的子依赖
235
+ └─ 更新 childrenKey ──▶ 用 savedChildren 覆盖
243
236
 
244
- `Dependency` 提供了 `reload()` 方法来支持热重载。你可以使用 `chokidar` 监听文件变化,然后调用 `reload()` 来重新加载模块。
237
+ 8. await newNode.start() ──▶ 启动新节点
238
+ 9. reloaded ──▶ 向上冒泡
239
+ ```
245
240
 
246
- ### 基本示例
241
+ ## 🎯 核心机制详解
247
242
 
248
- 使用**事件驱动**的方式动态收集文件路径:
243
+ ### 1. 依赖去重机制
249
244
 
245
+ #### 全局依赖池
250
246
  ```typescript
251
- import { Dependency } from '@zhin.js/dependency';
252
- import chokidar from 'chokidar';
253
-
254
- // 1. 创建依赖树和文件监听器
255
- const root = new Dependency('./entry.js');
256
- const watchedFiles = new Map<string, Dependency>();
257
-
258
- // 2. 创建空的 watcher,准备动态添加文件
259
- const watcher = chokidar.watch([], {
260
- persistent: true,
261
- ignoreInitial: true,
262
- awaitWriteFinish: {
263
- stabilityThreshold: 100,
264
- pollInterval: 100
265
- }
266
- });
267
-
268
- // 3. 监听 afterStart 事件,动态收集文件路径
269
- root.on('afterStart', (dep: Dependency) => {
270
- watchedFiles.set(dep.filePath, dep);
271
- watcher.add(dep.filePath);
272
- });
273
-
274
- // 4. 启动依赖树(会触发 afterStart 事件)
275
- await root.start();
276
-
277
- // 5. 监听文件变化
278
- watcher.on('change', async (changedPath: string) => {
279
- const dep = watchedFiles.get(changedPath);
280
- if (dep) {
281
- console.log(`📝 文件变更: ${dep.name}`);
282
- console.time('reload');
283
-
284
- try {
285
- const newDep = await dep.reload();
286
- watchedFiles.set(newDep.filePath, newDep);
287
- } catch (error) {
288
- console.error(`❌ [${dep.name}] 重载失败:`, error);
289
- }
290
-
291
- console.timeEnd('reload');
292
- }
293
- });
247
+ private static globalDepMap = new Map<string, Dependency>();
294
248
  ```
249
+ - **Key**: 文件绝对路径(标准化后)
250
+ - **Value**: Dependency 实例
251
+ - **作用**: 确保同一文件只有一个实例
295
252
 
296
- ### 热重载工作原理
297
-
298
- 当调用 `dep.reload()` 时,会自动:
299
-
300
- 1. **暂存子依赖** - 保存当前节点的 children
301
- 2. **卸载当前节点** - 调用 `dispose()`
302
- 3. **清除模块缓存** - 清除 require/import 缓存
303
- 4. **重新导入** - 父节点重新 import 该文件(或根节点重新 start)
304
- 5. **恢复子依赖** - 将暂存的 children 赋值给新节点
305
- 6. **重新挂载** - 调用新节点的 `mount()`
306
- 7. **返回新实例** - `reload()` 返回新的 `Dependency` 实例
307
-
308
- ### 关键特性
309
-
310
- - ✅ **支持根节点热重载** - 即使没有 parent 也能 reload
311
- - ✅ **返回新实例** - `reload()` 返回 `Promise<Dependency>`
312
- - ✅ **事件驱动** - 使用 `afterStart` 事件动态收集依赖
313
- - ✅ **保留子树** - 子依赖会自动迁移到新实例
314
- - ✅ **灵活可控** - 完全控制监听策略和重载时机
315
-
316
- ### 优势
317
-
318
- - 🚀 **性能优化** - 只监听实际需要的文件
319
- - 🎯 **精确控制** - 可以根据需求定制监听策略
320
- - 🔄 **增量更新** - 无需重新收集所有文件
321
- - 💾 **内存友好** - 及时更新监听映射,避免内存泄漏
322
- - 🛠️ **可扩展** - 可以结合其他工具(如 nodemon、pm2)
323
-
324
- ## 🧬 类继承指南
325
-
326
- ### 核心特性
327
-
328
- - ✅ **完整继承支持** - 子节点自动使用父节点的类
329
- - ✅ **类型安全** - 完整的 TypeScript 类型支持
330
- - ✅ **生命周期保留** - 所有生命周期方法正常工作
331
- - ✅ **热重载兼容** - 重载后的节点保持相同类型
332
-
333
- ### 基本继承
334
-
253
+ #### 引用计数 (refs)
335
254
  ```typescript
336
- import { Dependency } from '@zhin.js/dependency';
337
-
338
- class Plugin extends Dependency {
339
- public version: string = '1.0.0';
340
- public author: string = 'unknown';
341
-
342
- constructor(filePath: string) {
343
- super(filePath);
344
- }
345
-
346
- // 添加自定义方法
347
- getInfo(): string {
348
- return `${this.name} v${this.version} by ${this.author}`;
349
- }
350
- }
351
-
352
- // 使用自定义类
353
- const root = new Plugin('./entry.js');
354
- await root.start();
355
-
356
- // 所有子节点也是 Plugin 实例!
357
- console.log(root.children[0] instanceof Plugin); // true
255
+ public refs: Set<string> = new Set();
358
256
  ```
257
+ - 存储所有引用者的文件路径
258
+ - 首次导入者也在 `refs` 中
259
+ - `refs.size` 即为总引用计数
359
260
 
360
- ### 实际示例
361
-
362
- #### 示例 1:添加配置系统
363
-
261
+ #### parent (Getter)
364
262
  ```typescript
365
- interface PluginConfig {
366
- enabled: boolean;
367
- priority: number;
368
- dependencies?: string[];
263
+ get parent(): P | null {
264
+ return this.refs.size > 0
265
+ ? Dependency.globalDepMap.get(this.refs.values().next().value!) as P
266
+ : null;
369
267
  }
268
+ ```
269
+ - 动态计算,返回 `refs` 的第一个元素
270
+ - 代表首次导入者
271
+ - Set 迭代顺序稳定,保证 `parent` 始终是第一个
370
272
 
371
- class ConfigurablePlugin extends Dependency {
372
- private config: PluginConfig = {
373
- enabled: true,
374
- priority: 0
375
- };
376
-
377
- constructor(filePath: string, config?: Partial<PluginConfig>) {
378
- super(filePath);
379
- if (config) {
380
- this.config = { ...this.config, ...config };
381
- }
382
- }
383
-
384
- getConfig(): PluginConfig {
385
- return { ...this.config };
386
- }
387
-
388
- updateConfig(config: Partial<PluginConfig>): void {
389
- this.config = { ...this.config, ...config };
390
- }
391
-
392
- isEnabled(): boolean {
393
- return this.config.enabled;
273
+ #### importChild 逻辑
274
+ ```typescript
275
+ async importChild(importPath: string): Promise<P> {
276
+ const normalizedPath = this.resolveFilePath(absolutePath);
277
+ let child = Dependency.globalDepMap.get(normalizedPath);
278
+
279
+ if (!child) {
280
+ // 首次导入:创建实例并初始化
281
+ child = new (this.constructor as Constructor<P>)(normalizedPath);
282
+ await child.init(); // 导入模块代码
394
283
  }
284
+
285
+ // 建立引用关系
286
+ child.refs.add(this.#filePath);
287
+ this[childrenKey].add(child.filePath);
288
+
289
+ return child;
395
290
  }
396
291
  ```
397
292
 
398
- #### 示例 2:添加性能监控
399
-
293
+ #### removeChild 逻辑
400
294
  ```typescript
401
- class MonitoredPlugin extends Dependency {
402
- private metrics = {
403
- loadTime: 0,
404
- mountTime: 0,
405
- childCount: 0
406
- };
407
-
408
- async start(): Promise<void> {
409
- const startTime = Date.now();
410
- await super.start();
411
- this.metrics.loadTime = Date.now() - startTime;
295
+ async removeChild(child: P): Promise<void> {
296
+ child.refs.delete(this.#filePath);
297
+ this[childrenKey].delete(child.filePath);
298
+
299
+ if (!child.refs.size) {
300
+ await child.stop(); // 引用计数归零,停止
412
301
  }
302
+ }
303
+ ```
413
304
 
414
- async mount(): Promise<void> {
415
- const startTime = Date.now();
416
- await super.mount();
417
- this.metrics.mountTime = Date.now() - startTime;
418
- this.metrics.childCount = this.children.length;
419
- }
305
+ ### 2. 热重载机制
420
306
 
421
- getMetrics() {
422
- return { ...this.metrics };
423
- }
307
+ #### Clone-Diff-Merge 策略
424
308
 
425
- printMetrics(): void {
426
- console.log(`📊 ${this.name} 性能指标:`);
427
- console.log(` 加载时间: ${this.metrics.loadTime}ms`);
428
- console.log(` 挂载时间: ${this.metrics.mountTime}ms`);
429
- console.log(` 子节点数: ${this.metrics.childCount}`);
430
- }
431
- }
309
+ **根节点重载**:
310
+ ```
311
+ 保存 children → dispose → 清理 → 重新 init → diff → 恢复 children → start
432
312
  ```
433
313
 
434
- ### 工作原理
435
-
436
- 当父节点导入子模块时,`importChild()` 方法会自动使用 `this.constructor` 来创建子节点:
314
+ **非根节点重载**:
315
+ ```
316
+ 保存 children → dispose → 父节点重新 importChild diff → 恢复 children → start
317
+ ```
437
318
 
319
+ #### Diff 算法
438
320
  ```typescript
439
- async importChild(importPath: string): Promise<Dependency> {
440
- const absolutePath = this.resolveImportPath(this.#filePath, importPath);
321
+ #diffChildren(newNode, savedChildren) {
322
+ // Removed: saved 中但不在 new 中
323
+ const removedChildren = savedChildren.filter(
324
+ child => !newNode.children.find(c => c.filePath === child.filePath)
325
+ );
441
326
 
442
- // 使用父节点的构造函数创建子节点
443
- const child = new (this.constructor as typeof Dependency)(absolutePath);
327
+ // Added: 在 new 中但不在 saved 中
328
+ const addedChildren = newNode.children.filter(
329
+ child => !savedChildren.find(c => c.filePath === child.filePath)
330
+ );
444
331
 
445
- child.parent = this;
446
- this.children.push(child);
447
- await child.start();
448
-
449
- return child;
332
+ // Kept: 都存在的会被自动保留(不在 removed 中)
333
+ return { removedChildren, addedChildren };
450
334
  }
451
335
  ```
452
336
 
453
- 这确保了:
454
- - 子节点使用与父节点相同的类
455
- - 整个依赖树保持类型一致
456
- - 自定义属性和方法在所有节点上可用
457
-
458
- ### 注意事项
459
-
460
- #### 1. 构造函数参数
337
+ #### 状态保持
338
+ - **Kept 子依赖**: 不重新创建,保持原实例
339
+ - **Added 子依赖**: 新创建或从全局池复用
340
+ - **Removed 子依赖**: 调用 `removeChild`,引用计数减 1
461
341
 
462
- 如果你的自定义类需要额外的构造函数参数,需要确保只使用 `filePath` 作为必需参数:
342
+ ### 3. children 的 Symbol 实现
463
343
 
464
344
  ```typescript
465
- // 正确:额外参数都是可选的
466
- class MyPlugin extends Dependency {
467
- constructor(filePath: string, config?: MyConfig) {
468
- super(filePath);
469
- // ...
470
- }
471
- }
345
+ const childrenKey = Symbol('children');
472
346
 
473
- // ❌ 错误:必需的额外参数会导致子节点创建失败
474
- class MyPlugin extends Dependency {
475
- constructor(filePath: string, config: MyConfig) { // config 是必需的
476
- super(filePath);
477
- // ...
478
- }
347
+ [childrenKey]: Set<string> = new Set(); // 存储子依赖的文件路径
348
+
349
+ get children(): P[] {
350
+ return Array.from(this[childrenKey])
351
+ .map(filePath => Dependency.globalDepMap.get(filePath) as P);
479
352
  }
480
353
  ```
481
354
 
482
- **解决方案:** 使用默认值或可选参数:
355
+ **优势**:
356
+ - 通过文件路径间接引用,避免循环引用
357
+ - 从全局池动态获取,保证始终是最新实例
358
+ - 支持 Diff 和更新操作
483
359
 
360
+ ### 4. 事件系统
361
+
362
+ #### 事件冒泡
484
363
  ```typescript
485
- class MyPlugin extends Dependency {
486
- constructor(
487
- filePath: string,
488
- config: MyConfig = { /* 默认值 */ }
489
- ) {
490
- super(filePath);
491
- // ...
492
- }
364
+ async dispatchAsync(event: string, ...args: any[]): Promise<void> {
365
+ if (this.parent)
366
+ await this.parent.dispatchAsync(event, ...args);
367
+ else
368
+ await this.broadcastAsync(event, ...args);
493
369
  }
494
370
  ```
371
+ - 有父节点:向父节点传播
372
+ - 无父节点(根节点):广播到整个子树
495
373
 
496
- #### 2. 异步初始化
497
-
498
- 如果需要异步初始化,使用生命周期方法而不是构造函数:
499
-
374
+ #### 事件广播
500
375
  ```typescript
501
- class AsyncPlugin extends Dependency {
502
- private initialized: boolean = false;
503
-
504
- // ✅ 使用 start 方法
505
- async start(): Promise<void> {
506
- await this.initialize();
507
- await super.start();
508
- }
509
-
510
- private async initialize(): Promise<void> {
511
- // 异步初始化逻辑
512
- this.initialized = true;
376
+ async broadcastAsync(event: string, ...args: any[]): Promise<void> {
377
+ await this.emitAsync(event, ...args); // 触发自己的监听器
378
+ for (const child of this.children) {
379
+ await child.broadcastAsync(event, ...args); // 递归广播
513
380
  }
514
381
  }
515
382
  ```
516
383
 
517
- ## 🔌 插件生态系统
518
-
519
- ### 支持多种命名规范
384
+ ## 🔌 Loader 使用
520
385
 
521
- #### 1. 社区插件(前缀命名)
386
+ ### Tsx (Node.js)
522
387
 
523
- ```
524
- zhin.js-plugin1
525
- zhin.js-my-plugin
526
- zhin.js-awesome-feature
388
+ ```bash
389
+ tsx --import @zhin.js/dependency/register.mjs index.ts
527
390
  ```
528
391
 
529
- #### 2. 官方插件(组织命名)
530
-
531
- ```
532
- @zhin.js/core
533
- @zhin.js/plugin1
534
- @zhin.js/database
392
+ 或在 `package.json` 中:
393
+ ```json
394
+ {
395
+ "scripts": {
396
+ "dev": "tsx --import @zhin.js/dependency/register.mjs src/index.ts"
397
+ }
398
+ }
535
399
  ```
536
400
 
537
- ### 配置方法
538
-
539
- #### 方法 1:环境变量(推荐)
540
-
541
- 在 `.env` 文件或启动脚本中设置:
401
+ ### Bun
542
402
 
543
403
  ```bash
544
- # 同时支持两种插件
545
- DEPENDENCY_TREE_INCLUDE=node_modules/zhin.js-,node_modules/@zhin.js/
404
+ bun --preload @zhin.js/dependency/bun-preload.ts index.ts
546
405
  ```
547
406
 
548
- #### 方法 2:package.json 脚本
549
-
407
+ 或在 `package.json` 中:
550
408
  ```json
551
409
  {
552
410
  "scripts": {
553
- "dev": "DEPENDENCY_TREE_INCLUDE=node_modules/zhin.js-,node_modules/@zhin.js/ bun src/index.ts",
554
- "start": "DEPENDENCY_TREE_INCLUDE=node_modules/zhin.js-,node_modules/@zhin.js/ tsx src/index.ts"
411
+ "dev": "bun --preload @zhin.js/dependency/bun-preload.ts src/index.ts"
555
412
  }
556
413
  }
557
414
  ```
558
415
 
559
- #### 方法 3:使用 dotenv
416
+ ### 环境变量
560
417
 
561
418
  ```bash
562
- # .env
563
- DEPENDENCY_TREE_INCLUDE=node_modules/zhin.js-,node_modules/@zhin.js/
564
- ```
565
-
419
+ # 禁用副作用自动管理
420
+ DEPENDENCY_WRAP_EFFECTS=false tsx --import @zhin.js/dependency/register.mjs index.ts
421
+ ```
422
+
423
+ ## 📊 生命周期事件完整列表
424
+
425
+ ### 本节点事件 (self.*)
426
+ - `self.start`: 启动开始
427
+ - `self.mounted`: 挂载完成(onMount 钩子)
428
+ - `self.dispose`: 卸载开始(onDispose 钩子)
429
+ - `self.stop`: 停止开始
430
+ - `self.reload`: 重载开始
431
+
432
+ ### 冒泡事件
433
+ - `before-start`: 启动前
434
+ - `started`: 启动完成
435
+ - `before-mount`: 挂载前
436
+ - `mounted`: 挂载完成
437
+ - `before-dispose`: 卸载前
438
+ - `disposed`: 卸载完成
439
+ - `before-stop`: 停止前
440
+ - `stopped`: 停止完成
441
+ - `before-reload`: 重载前
442
+ - `reloading`: 重载中
443
+ - `reloaded`: 重载完成
444
+
445
+ ### 错误事件
446
+ - `error`: 通用错误
447
+ - `reload.error`: 重载错误
448
+
449
+ ## 🎨 实用场景
450
+
451
+ ### 插件系统
566
452
  ```typescript
567
- // index.ts
568
- import 'dotenv/config';
569
- import { Dependency } from '@zhin.js/dependency';
570
-
571
- const root = new Dependency('./entry.js');
572
- await root.start();
573
- ```
574
-
575
- ### 实际场景
576
-
577
- #### 场景 1:纯官方插件生态
578
-
579
- 如果你的项目只使用官方插件(如 `@zhin.js/*`):
453
+ class PluginDependency extends Dependency {
454
+ async enable() {
455
+ await this.start();
456
+ }
457
+
458
+ async disable() {
459
+ await this.stop();
460
+ }
461
+
462
+ async reload() {
463
+ return super.reload() as Promise<PluginDependency>;
464
+ }
465
+ }
580
466
 
581
- ```bash
582
- DEPENDENCY_TREE_INCLUDE=node_modules/@zhin.js/
467
+ const plugin = new PluginDependency('./plugin.ts');
468
+ await plugin.enable();
583
469
  ```
584
470
 
585
- #### 场景 2:纯社区插件生态
471
+ ### 微服务热重载
472
+ ```typescript
473
+ import { Dependency } from '@zhin.js/dependency';
474
+ import chokidar from 'chokidar';
586
475
 
587
- 如果你的项目只使用社区插件(如 `zhin.js-*`):
476
+ const services = new Map<string, Dependency>();
588
477
 
589
- ```bash
590
- DEPENDENCY_TREE_INCLUDE=node_modules/zhin.js-
478
+ async function loadService(servicePath: string) {
479
+ const service = new Dependency(servicePath);
480
+ await service.start();
481
+ services.set(servicePath, service);
482
+
483
+ chokidar.watch(servicePath).on('change', async () => {
484
+ await service.reload();
485
+ });
486
+ }
591
487
  ```
592
488
 
593
- #### 场景 3:混合生态 ⭐
594
-
595
- 同时支持官方和社区插件(推荐):
489
+ ### 依赖树可视化
490
+ ```typescript
491
+ const root = new Dependency('./main.ts');
492
+ await root.start();
596
493
 
597
- ```bash
598
- DEPENDENCY_TREE_INCLUDE=node_modules/zhin.js-,node_modules/@zhin.js/
494
+ console.log(root.printTree('', true, true));
495
+ // main (3 listeners)
496
+ // ├── logger (1 listeners) [shared ×2]
497
+ // ├── child (2 listeners)
498
+ // │ └── timer (1 listeners)
499
+ // └── parent (2 listeners)
500
+ // └── child (2 listeners) [shared ×2]
599
501
  ```
600
502
 
601
- #### 场景 4:本地插件 + npm 插件
602
-
603
- 同时支持本地开发和 npm 安装的插件:
503
+ ## 🔍 调试技巧
604
504
 
605
- ```bash
606
- DEPENDENCY_TREE_INCLUDE=src/plugins,node_modules/zhin.js-,node_modules/@zhin.js/
505
+ ### 打印依赖树
506
+ ```typescript
507
+ console.log(dep.printTree('', true, true));
607
508
  ```
608
509
 
609
- #### 场景 5:选择性包含
610
-
611
- 只包含特定的插件:
612
-
613
- ```bash
614
- DEPENDENCY_TREE_INCLUDE=node_modules/@zhin.js/core,node_modules/zhin.js-auth,node_modules/zhin.js-database
510
+ ### 监听所有事件
511
+ ```typescript
512
+ const events = [
513
+ 'before-start', 'started', 'before-mount', 'mounted',
514
+ 'before-dispose', 'disposed', 'before-stop', 'stopped',
515
+ 'before-reload', 'reloading', 'reloaded', 'error'
516
+ ];
517
+
518
+ events.forEach(event => {
519
+ root.on(event, (dep) => {
520
+ console.log(`[${event}] ${dep.name}`);
521
+ });
522
+ });
615
523
  ```
616
524
 
617
- ### 排除特定插件
618
-
619
- 使用 `DEPENDENCY_TREE_EXCLUDE` 排除不需要的插件:
620
-
621
- ```bash
622
- # 包含所有 zhin.js 插件,但排除测试和开发插件
623
- DEPENDENCY_TREE_INCLUDE=node_modules/zhin.js-,node_modules/@zhin.js/
624
- DEPENDENCY_TREE_EXCLUDE=node_modules/zhin.js-dev,node_modules/@zhin.js/testing
525
+ ### 查看引用关系
526
+ ```typescript
527
+ console.log('引用者数量:', dep.refs.size);
528
+ console.log('引用者路径:', Array.from(dep.refs));
529
+ console.log('父节点:', dep.parent?.name);
530
+ console.log('子节点:', dep.children.map(c => c.name));
625
531
  ```
626
532
 
627
- ### 发布插件为 npm 包
628
-
629
- #### 插件包作者(发布方)
630
-
631
- 在你的插件包 README 中说明:
632
-
633
- ```markdown
634
- ## 使用方法
533
+ ## ⚙️ 高级配置
635
534
 
636
- 安装插件:
637
-
638
- \`\`\`bash
639
- npm install @your-org/your-plugin
640
- \`\`\`
641
-
642
- 配置环境变量以启用依赖树转换:
643
-
644
- \`\`\`bash
645
- DEPENDENCY_TREE_INCLUDE=node_modules/@your-org/your-plugin
646
- \`\`\`
647
-
648
- 或者在 `package.json` 中:
649
-
650
- \`\`\`json
651
- {
652
- "scripts": {
653
- "start": "DEPENDENCY_TREE_INCLUDE=node_modules/@your-org/your-plugin tsx src/index.ts"
535
+ ### 自定义路径解析
536
+ ```typescript
537
+ class CustomDependency extends Dependency {
538
+ protected resolveFilePath(filePath: string): string {
539
+ // 自定义路径解析逻辑
540
+ return super.resolveFilePath(filePath);
654
541
  }
655
542
  }
656
- \`\`\`
657
543
  ```
658
544
 
659
- #### 插件使用者
660
-
661
- ```bash
662
- # .env 文件
663
- DEPENDENCY_TREE_INCLUDE=node_modules/@my-org/plugin1,node_modules/@my-org/plugin2
664
- ```
665
-
666
- 或者在启动命令中:
667
-
668
- ```json
669
- {
670
- "scripts": {
671
- "start": "DEPENDENCY_TREE_INCLUDE=node_modules/@my-org/my-plugin tsx src/index.ts"
545
+ ### 自定义模块初始化
546
+ ```typescript
547
+ class CustomDependency extends Dependency {
548
+ async init() {
549
+ // 自定义初始化逻辑
550
+ await super.init();
551
+ // 额外处理
672
552
  }
673
553
  }
674
554
  ```
675
555
 
676
- ## 📚 API 文档
677
-
678
- ### `Dependency` 类
679
-
680
- #### 构造函数
681
-
682
- ```typescript
683
- new Dependency(filePath: string)
684
- ```
685
-
686
- #### 方法
556
+ ## 📝 注意事项
687
557
 
688
- - `async start()` - 启动依赖(导入模块并构建树)
689
- - `async mount()` - 挂载(执行 onMount hooks)
690
- - `async dispose()` - 卸载(执行 onDispose hooks)
691
- - `async stop()` - 停止(dispose 并级联卸载子节点)
692
- - `async reload(): Promise<Dependency>` - 热重载,返回新的 Dependency 实例(支持根节点)
693
- - `printTree(prefix?, showListeners?, showPaths?)` - 打印树结构
694
- - `toJSON()` - 导出为 JSON
695
- - `dispatch(event, ...args)` - 触发当前节点的事件
696
- - `broadcast(event, ...args)` - 广播事件到整个子树
558
+ 1. **循环依赖**: 自动处理,通过全局池去重
559
+ 2. **内存泄漏**: `stop()` 时自动清理缓存和副作用
560
+ 3. **热重载**: 根节点重载保持引用,非根节点创建新实例
561
+ 4. **共享依赖**: 通过 `refs.size` 追踪,引用计数归零时才停止
562
+ 5. **Symbol children**: 通过文件路径间接引用,避免实例循环引用
697
563
 
698
- #### 属性
564
+ ## 📄 License
699
565
 
700
- - `name` - 依赖名称
701
- - `filePath` - 文件路径
702
- - `parent` - 父依赖
703
- - `children` - 子依赖数组
704
-
705
- #### 继承自 EventEmitter
706
-
707
- ```typescript
708
- // 监听事件
709
- dep.on('afterMount', (dep) => console.log('挂载完成'));
710
-
711
- // 触发事件
712
- dep.emit('custom-event', data);
713
-
714
- // 其他 EventEmitter 方法
715
- dep.once(event, listener);
716
- dep.off(event, listener);
717
- dep.removeAllListeners(event);
718
- ```
719
-
720
- ## 🎨 生命周期
721
-
722
- ```
723
- ┌─────────────┐
724
- │ create │ new Dependency()
725
- └──────┬──────┘
726
-
727
- ┌──────▼──────┐
728
- │ start │ 导入模块,构建树
729
- └──────┬──────┘
730
-
731
- ┌──────▼──────┐
732
- │ mount │ 执行 onMount hooks
733
- └──────┬──────┘
734
-
735
- ┌───▼────┐
736
- │ active │ 运行中...
737
- └───┬────┘
738
-
739
- ┌──────▼──────┐
740
- │ dispose │ 执行 onDispose hooks
741
- └──────┬──────┘
742
-
743
- ┌──────▼──────┐
744
- │ stop │ 级联卸载子节点
745
- └─────────────┘
746
- ```
747
-
748
- ### 生命周期事件
749
-
750
- Dependency 类继承自 EventEmitter,在生命周期的各个阶段会触发相应事件:
751
-
752
- - `beforeStart` - 开始启动前
753
- - `afterStart` - 启动完成后
754
- - `beforeMount` - 开始挂载前
755
- - `afterMount` - 挂载完成后
756
- - `beforeDispose` - 开始卸载前
757
- - `afterDispose` - 卸载完成后
758
- - `beforeReload` - 开始重载前
759
- - `afterReload` - 重载完成后
760
- - `fileChange` - 文件变更时
761
- - `error` - 发生错误时
566
+ MIT
762
567
 
763
568
  ## 🤝 贡献
764
569
 
765
- 欢迎提交 Issue 和 Pull Request
766
-
767
- ## 📄 许可证
768
-
769
- MIT
570
+ 欢迎提交 Issue 和 PR