@splicetree/plugin-lazy-load 0.0.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/CHANGELOG.md ADDED
@@ -0,0 +1,25 @@
1
+ # @splicetree/plugin-lazy-load
2
+
3
+ ## 0.0.1
4
+
5
+ ### Patch Changes
6
+
7
+ - chore: 搭建 SpliceTree 框架基础架构与工程体系
8
+
9
+ 框架架构:
10
+ - 建立无头树(Headless Tree)核心模型与数据结构
11
+ - 定义节点操作 API、事件系统和状态管理机制
12
+ - 设计并实现插件体系(生命周期、注册机制、能力扩展)
13
+ - 引入模块化架构,明确 core / plugins / adapters 的边界
14
+ - 预留扩展点与内部协议,构建可插拔式架构基础
15
+
16
+ 适配层与生态:
17
+ - 添加 Vue 3 适配层(渲染无关、纯接口绑定)
18
+ - 设计独立的 UI 层解耦策略,确保跨框架可迁移
19
+ - 预留未来 React/Svelte/WebComponents 的适配接口
20
+
21
+ 工程化与工具链:
22
+ - 初始化 monorepo 工程结构(packages + docs)
23
+ - 配置构建工具链 tsdown(多包构建、类型输出)
24
+ - 构建文档系统(VitePress)与基础导航结构
25
+ - 设置开发环境、代码规范(ESLint)、格式化流程
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Michael Cocova
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,48 @@
1
+ # @splicetree/plugin-lazy-load
2
+
3
+ [![version](https://img.shields.io/npm/v/@splicetree/plugin-lazy-load.svg?label=version)](https://www.npmjs.com/package/@splicetree/plugin-lazy-load)
4
+ [![downloads](https://img.shields.io/npm/dm/@splicetree/plugin-lazy-load.svg)](https://npmcharts.com/compare/%40splicetree%2Fplugin-lazy-load?minimal=true)
5
+ [![license](https://img.shields.io/npm/l/@splicetree/plugin-lazy-load.svg)](https://www.npmjs.com/package/@splicetree/plugin-lazy-load)
6
+ [![Website](https://img.shields.io/static/v1?label=Website&message=splicetree.dev&color=blue)](https://www.splicetree.dev)
7
+ [![GitHub](https://img.shields.io/static/v1?label=GitHub&message=splicetree%2Fsplicetree&logo=github)](https://github.com/michaelcocova/splicetree)
8
+
9
+ 提供懒加载子节点能力,在首次展开时动态加载并追加到树。
10
+
11
+ ## 安装
12
+
13
+ `pnpm add @splicetree/plugin-lazy-load`
14
+
15
+ ## 使用
16
+
17
+ ```ts
18
+ import { createSpliceTree } from '@splicetree/core'
19
+ import lazyLoad from '@splicetree/plugin-lazy-load'
20
+
21
+ const tree = createSpliceTree(data, {
22
+ plugins: [lazyLoad],
23
+ loadChildren: async (node) => {
24
+ const resp = await fetch(`/api/children?parent=${node.id}`)
25
+ const children = await resp.json()
26
+ return children
27
+ },
28
+ })
29
+ ```
30
+
31
+ ## Api
32
+
33
+ ### 实例方法
34
+
35
+ | 名称 | 参数 | 说明 |
36
+ | ------------------- | ------------------------- | -------------------------- |
37
+ | `loadedKeys` | `无` | 已加载集合 |
38
+ | `isLoaded(id)` | `id: string` | 是否已加载 |
39
+ | `load(id)` | `id: string` | 手动加载 |
40
+ | `expand(ids)` | `ids: string \| string[]` | 覆盖:未加载时先加载再展开 |
41
+ | `toggleExpand(ids)` | `ids: string \| string[]` | 覆盖:未加载时先加载再切换 |
42
+
43
+ ### 节点方法
44
+
45
+ | 名称 | 参数 | 说明 |
46
+ | --------------- | ---- | --------------------------------------------- |
47
+ | `isLoaded()` | `无` | 当前节点是否已加载 |
48
+ | `hasChildren()` | `无` | 重写:未加载返回 `true`,加载后根据子节点判断 |
@@ -0,0 +1,57 @@
1
+ import { SpliceTreePlugin } from "@splicetree/core";
2
+
3
+ //#region src/index.d.ts
4
+ declare module '@splicetree/core' {
5
+ /**
6
+ * 选项扩展(Lazy-Load)
7
+ * - loadChildren:按需加载指定节点的子节点
8
+ */
9
+ interface UseSpliceTreeOptions {
10
+ /**
11
+ * 加载指定节点的子节点
12
+ * 返回子节点数据数组,加载完成后将自动追加到树中
13
+ * @param node 要加载子节点的目标节点
14
+ */
15
+ loadChildren?: (node: SpliceTreeNode<any>) => Promise<any[]>;
16
+ }
17
+ /**
18
+ * 事件扩展(Lazy-Load)
19
+ * - lazyload:派发已加载节点集合
20
+ */
21
+ interface SpliceTreeEventPayloadMap {
22
+ lazyload: {
23
+ keys: string[];
24
+ };
25
+ }
26
+ /**
27
+ * 实例扩展(Lazy-Load)
28
+ * - loadedKeys:已加载集合
29
+ * - isLoaded:查询是否已加载
30
+ * - load:加载指定节点的子节点
31
+ */
32
+ interface SpliceTreeInstance {
33
+ /**
34
+ * 已完成加载的节点 id 集合
35
+ */
36
+ loadedKeys: Set<string>;
37
+ /**
38
+ * 查询指定节点是否已完成子节点加载
39
+ */
40
+ isLoaded: (id: string) => boolean;
41
+ /**
42
+ * 加载指定节点的子节点并追加到树中
43
+ * @param id 目标节点 id
44
+ */
45
+ load: (id: string) => Promise<void>;
46
+ }
47
+ /**
48
+ * 节点扩展(Lazy-Load)
49
+ * - isLoaded:当前节点是否已加载
50
+ */
51
+ interface SpliceTreeNode {
52
+ isLoaded: () => boolean;
53
+ }
54
+ }
55
+ declare const lazyLoad: SpliceTreePlugin;
56
+ //#endregion
57
+ export { lazyLoad as default, lazyLoad };
package/dist/index.js ADDED
@@ -0,0 +1,90 @@
1
+ //#region src/index.ts
2
+ const lazyLoad = {
3
+ name: "lazy-load",
4
+ setup(ctx) {
5
+ const { loadChildren } = ctx.options;
6
+ const loadedKeys = /* @__PURE__ */ new Set();
7
+ const isLoaded = (id) => loadedKeys.has(id);
8
+ /**
9
+ * 为节点应用懒加载相关的覆盖方法
10
+ * - isLoaded:读取实例的已加载状态
11
+ * - hasChildren:未加载时返回 true,避免展开箭头消失
12
+ */
13
+ const applyLazyOverrides = (node) => {
14
+ node.isLoaded = () => ctx.tree.isLoaded?.(node.id);
15
+ node.hasChildren = () => {
16
+ if (!ctx.tree.isLoaded?.(node.id)) return true;
17
+ return !!node.getChildren()?.length;
18
+ };
19
+ };
20
+ /**
21
+ * 加载指定节点的子节点,并将其追加到树结构
22
+ * - 若已加载或未提供加载器则跳过
23
+ * - 追加后为新增节点应用覆盖方法
24
+ * - 完成后记录 loadedKeys 并派发 lazyload 事件
25
+ * @param id 目标节点 id
26
+ */
27
+ const load = async (id) => {
28
+ if (!loadChildren) return;
29
+ if (loadedKeys.has(id)) return;
30
+ const node = ctx.tree.getNode(id);
31
+ if (!node) return;
32
+ const children = await loadChildren(node);
33
+ if (children?.length) {
34
+ ctx.tree.appendChildren(id, children);
35
+ for (const c of children) {
36
+ const childId = String(Reflect.get(c, ctx.tree.options?.keyField ?? "id"));
37
+ const childNode = ctx.tree.getNode(childId);
38
+ if (childNode) applyLazyOverrides(childNode);
39
+ }
40
+ }
41
+ loadedKeys.add(id);
42
+ ctx.events.emit({
43
+ name: "lazyload",
44
+ keys: Array.from(loadedKeys)
45
+ });
46
+ };
47
+ const origExpand = ctx.tree.expand.bind(ctx.tree);
48
+ const origToggle = ctx.tree.toggleExpand.bind(ctx.tree);
49
+ /**
50
+ * 覆盖 expand:在展开前确保子节点已加载
51
+ * @param ids 要展开的节点 id 或 id 数组
52
+ */
53
+ const expand = async (ids) => {
54
+ const list = Array.isArray(ids) ? ids : [ids];
55
+ if (loadChildren) {
56
+ for (const id of list) if (!isLoaded(id)) await load(id);
57
+ }
58
+ origExpand(ids);
59
+ };
60
+ /**
61
+ * 覆盖 toggleExpand:在切换前确保子节点已加载
62
+ * @param ids 要切换的节点 id 或 id 数组
63
+ */
64
+ const toggleExpand = async (ids) => {
65
+ const list = Array.isArray(ids) ? ids : [ids];
66
+ if (loadChildren) {
67
+ for (const id of list) if (!isLoaded(id)) await load(id);
68
+ }
69
+ origToggle(ids);
70
+ };
71
+ return {
72
+ loadedKeys,
73
+ isLoaded,
74
+ load,
75
+ expand,
76
+ toggleExpand
77
+ };
78
+ },
79
+ extendNode(node, ctx) {
80
+ node.isLoaded = () => ctx.tree.isLoaded?.(node.id);
81
+ node.hasChildren = () => {
82
+ if (!ctx.tree.isLoaded?.(node.id)) return true;
83
+ return !!node.getChildren()?.length;
84
+ };
85
+ }
86
+ };
87
+ var src_default = lazyLoad;
88
+
89
+ //#endregion
90
+ export { src_default as default, lazyLoad };
package/package.json ADDED
@@ -0,0 +1,32 @@
1
+ {
2
+ "name": "@splicetree/plugin-lazy-load",
3
+ "type": "module",
4
+ "version": "0.0.1",
5
+ "author": {
6
+ "email": "michael.cocova@gmail.com",
7
+ "name": "Michael Cocova"
8
+ },
9
+ "license": "MIT",
10
+ "homepage": "https://www.splicetree.dev",
11
+ "repository": {
12
+ "type": "git",
13
+ "url": "https://github.com/michaelcocova/splicetree"
14
+ },
15
+ "exports": {
16
+ ".": "./dist/index.js",
17
+ "./package.json": "./package.json"
18
+ },
19
+ "main": "./dist/index.js",
20
+ "module": "./dist/index.js",
21
+ "types": "./dist/index.d.ts",
22
+ "publishConfig": {
23
+ "access": "public"
24
+ },
25
+ "devDependencies": {
26
+ "@splicetree/core": "0.0.1"
27
+ },
28
+ "scripts": {
29
+ "dev": "tsdown --watch",
30
+ "build": "tsdown"
31
+ }
32
+ }
package/src/index.ts ADDED
@@ -0,0 +1,177 @@
1
+ import type { SpliceTreePlugin, SpliceTreePluginContext } from '@splicetree/core'
2
+ import '@splicetree/core'
3
+
4
+ declare module '@splicetree/core' {
5
+ /**
6
+ * 选项扩展(Lazy-Load)
7
+ * - loadChildren:按需加载指定节点的子节点
8
+ */
9
+ interface UseSpliceTreeOptions {
10
+ /**
11
+ * 加载指定节点的子节点
12
+ * 返回子节点数据数组,加载完成后将自动追加到树中
13
+ * @param node 要加载子节点的目标节点
14
+ */
15
+ loadChildren?: (node: SpliceTreeNode<any>) => Promise<any[]>
16
+ }
17
+
18
+ /**
19
+ * 事件扩展(Lazy-Load)
20
+ * - lazyload:派发已加载节点集合
21
+ */
22
+ interface SpliceTreeEventPayloadMap {
23
+ lazyload: { keys: string[] }
24
+ }
25
+
26
+ /**
27
+ * 实例扩展(Lazy-Load)
28
+ * - loadedKeys:已加载集合
29
+ * - isLoaded:查询是否已加载
30
+ * - load:加载指定节点的子节点
31
+ */
32
+ interface SpliceTreeInstance {
33
+ /**
34
+ * 已完成加载的节点 id 集合
35
+ */
36
+ loadedKeys: Set<string>
37
+ /**
38
+ * 查询指定节点是否已完成子节点加载
39
+ */
40
+ isLoaded: (id: string) => boolean
41
+ /**
42
+ * 加载指定节点的子节点并追加到树中
43
+ * @param id 目标节点 id
44
+ */
45
+ load: (id: string) => Promise<void>
46
+ }
47
+
48
+ /**
49
+ * 节点扩展(Lazy-Load)
50
+ * - isLoaded:当前节点是否已加载
51
+ */
52
+ interface SpliceTreeNode {
53
+ isLoaded: () => boolean
54
+ }
55
+ }
56
+
57
+ export const lazyLoad: SpliceTreePlugin = {
58
+ name: 'lazy-load',
59
+ /**
60
+ * 懒加载插件
61
+ * - 首次展开时按需加载子节点
62
+ * - 覆盖 hasChildren/expand/toggleExpand 以接入加载流程
63
+ * - 通过 loadedKeys 标记已加载节点
64
+ */
65
+ setup(ctx: SpliceTreePluginContext) {
66
+ const { loadChildren } = ctx.options
67
+ const loadedKeys = new Set<string>()
68
+ const isLoaded = (id: string) => loadedKeys.has(id)
69
+
70
+ /**
71
+ * 为节点应用懒加载相关的覆盖方法
72
+ * - isLoaded:读取实例的已加载状态
73
+ * - hasChildren:未加载时返回 true,避免展开箭头消失
74
+ */
75
+ const applyLazyOverrides = (node: any) => {
76
+ node.isLoaded = () => ctx.tree.isLoaded?.(node.id)
77
+ node.hasChildren = () => {
78
+ const loaded = ctx.tree.isLoaded?.(node.id)
79
+ if (!loaded) {
80
+ return true
81
+ }
82
+ return !!node.getChildren()?.length
83
+ }
84
+ }
85
+
86
+ /**
87
+ * 加载指定节点的子节点,并将其追加到树结构
88
+ * - 若已加载或未提供加载器则跳过
89
+ * - 追加后为新增节点应用覆盖方法
90
+ * - 完成后记录 loadedKeys 并派发 lazyload 事件
91
+ * @param id 目标节点 id
92
+ */
93
+ const load = async (id: string) => {
94
+ if (!loadChildren) {
95
+ return
96
+ }
97
+ if (loadedKeys.has(id)) {
98
+ return
99
+ }
100
+ const node = ctx.tree.getNode(id)
101
+ if (!node) {
102
+ return
103
+ }
104
+ const children = await loadChildren(node)
105
+ if (children?.length) {
106
+ ctx.tree.appendChildren(id, children)
107
+ for (const c of children) {
108
+ const childId = String(Reflect.get(c as any, ctx.tree.options?.keyField ?? 'id'))
109
+ const childNode = ctx.tree.getNode(childId)
110
+ if (childNode) {
111
+ applyLazyOverrides(childNode)
112
+ }
113
+ }
114
+ }
115
+ loadedKeys.add(id)
116
+ ctx.events.emit({ name: 'lazyload', keys: Array.from(loadedKeys) } as any)
117
+ }
118
+
119
+ const origExpand = ctx.tree.expand.bind(ctx.tree)
120
+ const origToggle = ctx.tree.toggleExpand.bind(ctx.tree)
121
+
122
+ /**
123
+ * 覆盖 expand:在展开前确保子节点已加载
124
+ * @param ids 要展开的节点 id 或 id 数组
125
+ */
126
+ const expand = async (ids: string | string[]) => {
127
+ const list = Array.isArray(ids) ? ids : [ids]
128
+ if (loadChildren) {
129
+ for (const id of list) {
130
+ if (!isLoaded(id))
131
+ await load(id)
132
+ }
133
+ }
134
+ origExpand(ids as any)
135
+ }
136
+ /**
137
+ * 覆盖 toggleExpand:在切换前确保子节点已加载
138
+ * @param ids 要切换的节点 id 或 id 数组
139
+ */
140
+ const toggleExpand = async (ids: string | string[]) => {
141
+ const list = Array.isArray(ids) ? ids : [ids]
142
+ if (loadChildren) {
143
+ for (const id of list) {
144
+ if (!isLoaded(id))
145
+ await load(id)
146
+ }
147
+ }
148
+ origToggle(ids as any)
149
+ }
150
+
151
+ return {
152
+ loadedKeys,
153
+ isLoaded,
154
+ load,
155
+ // 覆盖核心方法以接入懒加载
156
+ expand: expand as any,
157
+ toggleExpand: toggleExpand as any,
158
+ }
159
+ },
160
+ /**
161
+ * 为节点扩展懒加载相关方法与行为覆盖
162
+ * - isLoaded:查询当前节点是否已加载
163
+ * - hasChildren:未加载时返回 true,已加载后按实际子节点判断
164
+ */
165
+ extendNode(node, ctx) {
166
+ ;(node as any).isLoaded = () => ctx.tree.isLoaded?.(node.id)
167
+ node.hasChildren = () => {
168
+ const loaded = ctx.tree.isLoaded?.(node.id)
169
+ if (!loaded) {
170
+ return true
171
+ }
172
+ return !!node.getChildren()?.length
173
+ }
174
+ },
175
+ }
176
+
177
+ export default lazyLoad
package/tsconfig.json ADDED
@@ -0,0 +1,9 @@
1
+ {
2
+ "extends": "../../../tsconfig.base.json",
3
+ "compilerOptions": {
4
+ "declaration": true,
5
+ "outDir": "dist"
6
+ },
7
+ "include": ["src"],
8
+ "exclude": ["dist", "node_modules"]
9
+ }
@@ -0,0 +1,11 @@
1
+ import { defineConfig } from 'tsdown'
2
+
3
+ export default defineConfig({
4
+ clean: true,
5
+ sourcemap: false,
6
+ treeshake: true,
7
+ unused: true,
8
+ unbundle: false,
9
+ platform: 'neutral',
10
+ entry: ['./src/index.ts'],
11
+ })