Skip to content

Commit 6921364

Browse files
committed
feat: enhance DevTools support for Inula 2.0 with new hooks and event notifications
1 parent 35bbd5c commit 6921364

File tree

17 files changed

+2255
-29
lines changed

17 files changed

+2255
-29
lines changed

next-packages/runtime/src/index.ts

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,30 @@
1+
// DevTools 支持:在模块加载时就设置标记并通知 DevTools
2+
if (typeof window !== 'undefined') {
3+
const win = window as any;
4+
5+
// 标记为 Inula 2.0(用于 DevTools 检测)
6+
win.__INULA_V2__ = true;
7+
win.__INULA_NEXT__ = true;
8+
9+
console.log('[Inula Next] Runtime loaded, version 2.0');
10+
11+
// 主动通知 DevTools:2.0 运行时已加载
12+
// 发送自定义事件
13+
const event = new CustomEvent('__INULA_V2_LOADED__', {
14+
detail: { version: '2.0', timestamp: Date.now() }
15+
});
16+
window.dispatchEvent(event);
17+
18+
// 也通过自定义事件通知(不需要用 DevTools 消息格式)
19+
setTimeout(() => {
20+
window.postMessage({
21+
type: '__INULA_V2_READY__', // 这是自定义事件,不需要改
22+
version: '2.0',
23+
from: 'inula-runtime'
24+
}, '*');
25+
}, 0);
26+
}
27+
128
export * from './render';
229
export * from './Nodes';
330
export * from './types';

next-packages/runtime/src/render.ts

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,49 @@ export const render = (compNode: CompNode, container: HTMLElement) => {
1212
if (container == null) {
1313
throw new Error('Render target is empty. Please provide a valid DOM element.');
1414
}
15+
16+
// DevTools 支持:设置全局标记
17+
if (typeof window !== 'undefined') {
18+
const win = window as any;
19+
20+
// 标记为 Inula 2.0
21+
if (!win.__INULA_V2__) {
22+
win.__INULA_V2__ = true;
23+
win.__INULA_NEXT__ = true;
24+
}
25+
26+
// 设置 render 函数到 window,以便 DevTools 拦截
27+
if (!win.render) {
28+
win.render = render;
29+
}
30+
31+
// 通知 DevTools 有新的渲染
32+
if (win.__INULA_DEV_TOOL_V2_HELPER__) {
33+
win.__INULA_DEV_TOOL_V2_HELPER__.registerCompNode(compNode);
34+
win.__INULA_DEV_TOOL_V2_HELPER__.rootCompNodes.push(compNode);
35+
}
36+
}
37+
1538
container.innerHTML = '';
1639
insertNode(container as InulaHTMLNode, compNode, 0);
1740
runDidMount();
41+
42+
// 渲染完成后通知 DevTools
43+
if (typeof window !== 'undefined') {
44+
const win = window as any;
45+
if (win.__INULA_DEV_TOOL_V2_HELPER__) {
46+
// 延迟发送,确保 DOM 已更新
47+
setTimeout(() => {
48+
const tree = win.__INULA_DEV_TOOL_V2_HELPER__.collectComponentTree();
49+
window.postMessage({
50+
type: 'DevTool_Msg_Label', // 必须使用这个标签
51+
payload: {
52+
type: 'vNode trees infos',
53+
data: [tree],
54+
},
55+
from: 'dev tool hook',
56+
}, '*');
57+
}, 0);
58+
}
59+
}
1860
};
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
/**
2+
* 适配器工厂
3+
* 根据版本自动选择合适的适配器
4+
*/
5+
6+
import { InulaVersion, ComponentTreeNode, ComponentDetail } from '../comm/types';
7+
import { V1Adapter } from './v1Adapter';
8+
import { V2Adapter } from './v2Adapter';
9+
10+
export class AdapterFactory {
11+
private v1Adapter: V1Adapter;
12+
private v2Adapter: V2Adapter;
13+
14+
constructor() {
15+
this.v1Adapter = new V1Adapter();
16+
this.v2Adapter = new V2Adapter();
17+
}
18+
19+
/**
20+
* 获取适配器
21+
*/
22+
getAdapter(version: InulaVersion) {
23+
return version === InulaVersion.V1 ? this.v1Adapter : this.v2Adapter;
24+
}
25+
26+
/**
27+
* 解析组件树
28+
*/
29+
parseComponentTree(data: any, version: InulaVersion): ComponentTreeNode[] {
30+
const adapter = this.getAdapter(version);
31+
return adapter.parseComponentTree(data);
32+
}
33+
34+
/**
35+
* 构建树形结构
36+
*/
37+
buildTree(nodes: ComponentTreeNode[], version: InulaVersion): ComponentTreeNode[] {
38+
const adapter = this.getAdapter(version);
39+
return adapter.buildTree(nodes);
40+
}
41+
42+
/**
43+
* 解析组件详情
44+
*/
45+
parseComponentDetail(data: any, id: string | number, version: InulaVersion): ComponentDetail {
46+
const adapter = this.getAdapter(version);
47+
return adapter.parseComponentDetail(data, id);
48+
}
49+
50+
/**
51+
* 格式化更新数据
52+
*/
53+
formatUpdateData(id: string | number, ...args: any[]): any {
54+
// 根据参数数量判断版本
55+
if (args.length === 3) {
56+
// V1: attrName, path, value
57+
return this.v1Adapter.formatUpdateData(id, args[0], args[1], args[2]);
58+
} else {
59+
// V2: propName, value
60+
return this.v2Adapter.formatUpdateData(id, args[0], args[1]);
61+
}
62+
}
63+
}
64+
65+
export const adapterFactory = new AdapterFactory();
66+
67+
export { V1Adapter } from './v1Adapter';
68+
export { V2Adapter } from './v2Adapter';
Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
/**
2+
* Inula 1.0 版本适配器
3+
* 将 1.0 版本的数据格式转换为统一格式
4+
*/
5+
6+
import { ComponentTreeNode, ComponentDetail, InulaVersion } from '../comm/types';
7+
8+
export class V1Adapter {
9+
/**
10+
* 将 1.0 版本的 VNode 树转换为统一的组件树格式
11+
*/
12+
parseComponentTree(vNodeData: any[]): ComponentTreeNode[] {
13+
const result: ComponentTreeNode[] = [];
14+
15+
// 1.0 版本的数据格式:[id, nameObj, parentId, key, ...]
16+
for (let i = 0; i < vNodeData.length; i += 4) {
17+
const id = vNodeData[i];
18+
const nameObj = vNodeData[i + 1];
19+
const parentId = vNodeData[i + 2];
20+
const key = vNodeData[i + 3];
21+
22+
const node: ComponentTreeNode = {
23+
id,
24+
name: typeof nameObj === 'object' ? nameObj.itemName : nameObj,
25+
type: typeof nameObj === 'object' && nameObj.badge?.length > 0
26+
? nameObj.badge[0]
27+
: 'Component',
28+
parentId: parentId || undefined,
29+
version: InulaVersion.V1,
30+
rawData: {
31+
id,
32+
nameObj,
33+
parentId,
34+
key,
35+
},
36+
};
37+
38+
result.push(node);
39+
}
40+
41+
return result;
42+
}
43+
44+
/**
45+
* 构建树形结构
46+
*/
47+
buildTree(nodes: ComponentTreeNode[]): ComponentTreeNode[] {
48+
const nodeMap = new Map<string | number, ComponentTreeNode>();
49+
const roots: ComponentTreeNode[] = [];
50+
51+
// 创建节点映射
52+
nodes.forEach(node => {
53+
nodeMap.set(node.id, { ...node, children: [] });
54+
});
55+
56+
// 构建父子关系
57+
nodes.forEach(node => {
58+
const currentNode = nodeMap.get(node.id)!;
59+
if (node.parentId) {
60+
const parent = nodeMap.get(node.parentId);
61+
if (parent) {
62+
if (!parent.children) {
63+
parent.children = [];
64+
}
65+
parent.children.push(currentNode);
66+
} else {
67+
roots.push(currentNode);
68+
}
69+
} else {
70+
roots.push(currentNode);
71+
}
72+
});
73+
74+
return roots;
75+
}
76+
77+
/**
78+
* 将 1.0 版本的组件属性转换为统一格式
79+
*/
80+
parseComponentDetail(attrs: any, id: string | number): ComponentDetail {
81+
return {
82+
id,
83+
name: attrs.name || 'Unknown',
84+
type: attrs.type || 'Component',
85+
props: this.parseAttrs(attrs.parsedProps),
86+
state: this.parseAttrs(attrs.parsedState),
87+
hooks: attrs.parsedHooks,
88+
version: InulaVersion.V1,
89+
rawData: attrs,
90+
};
91+
}
92+
93+
/**
94+
* 解析属性数组
95+
*/
96+
private parseAttrs(attrs: any[]): Record<string, any> {
97+
if (!attrs || !Array.isArray(attrs)) {
98+
return {};
99+
}
100+
101+
const result: Record<string, any> = {};
102+
attrs.forEach(attr => {
103+
if (attr && attr.itemName) {
104+
result[attr.itemName] = attr.value;
105+
}
106+
});
107+
108+
return result;
109+
}
110+
111+
/**
112+
* 将统一格式的属性值转换回 1.0 格式用于更新
113+
*/
114+
formatUpdateData(id: string | number, attrName: string, path: string[], value: any): any {
115+
return {
116+
id,
117+
itemName: attrName,
118+
path,
119+
value,
120+
};
121+
}
122+
}

0 commit comments

Comments
 (0)