From 772917e9e5a642d378e53a9ed52a583c0f96a530 Mon Sep 17 00:00:00 2001
From: * <8>
Date: Sun, 24 Apr 2022 16:51:00 +0800
Subject: [PATCH 01/26] Match-id-62193e3466a6412806c2877cea02375f7d9c374c
---
libs/extension/src/components/VList/VList.tsx | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/libs/extension/src/components/VList/VList.tsx b/libs/extension/src/components/VList/VList.tsx
index 0879fbf4..edf4b16b 100644
--- a/libs/extension/src/components/VList/VList.tsx
+++ b/libs/extension/src/components/VList/VList.tsx
@@ -2,7 +2,7 @@
// data 数组更新后不修改滚动位置,
// 只有修改scrollToItem才会修改滚动位置
-import { useState, useRef, useEffect, useMemo } from 'libs/extension/src/components/VList/node_modules/horizon';
+import { useState, useRef, useEffect, useMemo } from 'horizon';
import styles from './VList.less';
import ItemMap from './ItemMap';
From 05ceadb72857aa4cb2048e7298eefc553113fba5 Mon Sep 17 00:00:00 2001
From: * <8>
Date: Mon, 25 Apr 2022 19:49:43 +0800
Subject: [PATCH 02/26] Match-id-d7957147dbf4d7b41c362465a12aa8fb5e9d7bd3
---
.../src/panelConnection/PanelConnection.ts | 61 +++++++++++++++++++
libs/extension/src/panelConnection/index.ts | 1 +
2 files changed, 62 insertions(+)
create mode 100644 libs/extension/src/panelConnection/PanelConnection.ts
create mode 100644 libs/extension/src/panelConnection/index.ts
diff --git a/libs/extension/src/panelConnection/PanelConnection.ts b/libs/extension/src/panelConnection/PanelConnection.ts
new file mode 100644
index 00000000..88dae8bb
--- /dev/null
+++ b/libs/extension/src/panelConnection/PanelConnection.ts
@@ -0,0 +1,61 @@
+import { packagePayload } from '../utils/transferTool';
+import { DevToolPanel, InitDevToolPageConnection } from '../utils/constants';
+
+let connection;
+const callbacks = [];
+
+export function addBackgroundMessageListener(fun: (message) => void) {
+ callbacks.push(fun);
+}
+
+export function removeBackgroundMessageListener(fun: (message) => void) {
+ const index = callbacks.indexOf(fun);
+ if (index !== -1) {
+ callbacks.splice(index, 1);
+ }
+}
+
+export function initBackgroundConnection() {
+ console.log(!isDev);
+ if (!isDev) {
+ try {
+ connection = chrome.runtime.connect({ name: 'panel' });
+ const notice = message => {
+ callbacks.forEach(fun => {
+ fun(message);
+ });
+ };
+ // TODO: 我们需要删除 notice 吗?如果需要,在什么时候删除
+ // 监听 background 消息
+ connection.onMessage.addListener(notice);
+ // 页面打开后发送初始化请求
+ postMessageToBackground(InitDevToolPageConnection, chrome.devtools.inspectedWindow.tabId);
+ } catch (e) {
+ console.error('create connection failed');
+ console.error(e);
+ }
+ }
+}
+
+let reconnectTimes = 0;
+export function postMessageToBackground(type: string, data: any) {
+ try{
+ connection.postMessage(packagePayload({
+ type: type,
+ data: data,
+ }, DevToolPanel));
+ } catch(err) {
+ // 可能出现 port 关闭的场景,需要重新建立连接,增加可靠性
+ if (reconnectTimes === 20) {
+ reconnectTimes = 0;
+ console.error('reconnect failed');
+ return;
+ }
+ console.error(err);
+ reconnectTimes++;
+ // 重建连接
+ initBackgroundConnection();
+ // 初始化成功后才会重新发送消息
+ postMessage(type, data);
+ }
+}
diff --git a/libs/extension/src/panelConnection/index.ts b/libs/extension/src/panelConnection/index.ts
new file mode 100644
index 00000000..b2d3e64d
--- /dev/null
+++ b/libs/extension/src/panelConnection/index.ts
@@ -0,0 +1 @@
+export * from './PanelConnection';
From 0237a0b72c74d0fc70ae057f79a5e54229223102 Mon Sep 17 00:00:00 2001
From: * <8>
Date: Mon, 25 Apr 2022 19:50:33 +0800
Subject: [PATCH 03/26] Match-id-8b842212d10a14844c0f8995a7beb81f5700de91
---
libs/extension/src/components/VList/index.ts | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/libs/extension/src/components/VList/index.ts b/libs/extension/src/components/VList/index.ts
index d0154206..f30db06a 100644
--- a/libs/extension/src/components/VList/index.ts
+++ b/libs/extension/src/components/VList/index.ts
@@ -1 +1,2 @@
-export { VList, renderInfoType } from './VList';
+export { VList } from './VList';
+export type { renderInfoType } from './VList';
From 58d5869a581b282fac5e69fca509b3882015b145 Mon Sep 17 00:00:00 2001
From: * <8>
Date: Mon, 25 Apr 2022 19:55:21 +0800
Subject: [PATCH 04/26] Match-id-e56b18c31b44e4091ce6cf98af6502f369a02364
---
libs/extension/src/background/index.ts | 2 +-
.../src/components/ComponentInfo.tsx | 75 +++++++++++-----
libs/extension/src/injector/index.ts | 30 ++-----
libs/extension/src/panel/App.tsx | 85 +++++++------------
libs/extension/src/parser/parseAttr.ts | 65 +++++++++++++-
libs/extension/src/utils/constants.ts | 10 ++-
6 files changed, 165 insertions(+), 102 deletions(-)
diff --git a/libs/extension/src/background/index.ts b/libs/extension/src/background/index.ts
index ed9698fb..cc8bd778 100644
--- a/libs/extension/src/background/index.ts
+++ b/libs/extension/src/background/index.ts
@@ -1,6 +1,6 @@
import { checkMessage, packagePayload, changeSource } from '../utils/transferTool';
import { RequestAllVNodeTreeInfos, InitDevToolPageConnection, DevToolBackground } from '../utils/constants';
-import { DevToolPanel, DevToolContentScript } from './../utils/constants';
+import { DevToolPanel, DevToolContentScript } from '../utils/constants';
// 多个页面、tab页共享一个 background,需要建立连接池,给每个tab建立连接
const connections = {};
diff --git a/libs/extension/src/components/ComponentInfo.tsx b/libs/extension/src/components/ComponentInfo.tsx
index f5ff0051..328642bd 100644
--- a/libs/extension/src/components/ComponentInfo.tsx
+++ b/libs/extension/src/components/ComponentInfo.tsx
@@ -5,17 +5,19 @@ import Copy from '../svgs/Copy';
import Triangle from '../svgs/Triangle';
import { useState, useEffect } from 'horizon';
import { IData } from './VTree';
-import { IAttr } from '../parser/parseAttr';
+import { buildAttrModifyData, IAttr } from '../parser/parseAttr';
+import { postMessageToBackground } from '../panelConnection';
+import { ModifyAttrs } from '../utils/constants';
type IComponentInfo = {
name: string;
attrs: {
- props?: IAttr[];
- context?: IAttr[];
- state?: IAttr[];
- hooks?: IAttr[];
+ parsedProps?: IAttr[],
+ parsedState?: IAttr[],
+ parsedHooks?: IAttr[],
};
parents: IData[];
+ id: number;
onClickParent: (item: IData) => void;
};
@@ -26,11 +28,18 @@ function collapseAllNodes(attrs: IAttr[]) {
});
}
-function ComponentAttr({ name, attrs }: { name: string, attrs: IAttr[] }) {
+function ComponentAttr({ attrsName, attrsType, attrs, id }: {
+ attrsName: string,
+ attrsType: string,
+ attrs: IAttr[],
+ id: number}) {
const [collapsedNode, setCollapsedNode] = useState(collapseAllNodes(attrs));
+ const [editableAttrs, setEditableAttrs] = useState(attrs);
useEffect(() => {
setCollapsedNode(collapseAllNodes(attrs));
+ setEditableAttrs(attrs);
}, [attrs]);
+
const handleCollapse = (item: IAttr) => {
const nodes = [...collapsedNode];
const i = nodes.indexOf(item);
@@ -44,7 +53,7 @@ function ComponentAttr({ name, attrs }: { name: string, attrs: IAttr[] }) {
const showAttr = [];
let currentIndentation = null;
- attrs.forEach((item, index) => {
+ editableAttrs.forEach((item, index) => {
const indentation = item.indentation;
if (currentIndentation !== null) {
if (indentation > currentIndentation) {
@@ -53,17 +62,40 @@ function ComponentAttr({ name, attrs }: { name: string, attrs: IAttr[] }) {
currentIndentation = null;
}
}
- const nextItem = attrs[index + 1];
+ const nextItem = editableAttrs[index + 1];
const hasChild = nextItem ? nextItem.indentation - item.indentation > 0 : false;
const isCollapsed = collapsedNode.includes(item);
showAttr.push(
-
(handleCollapse(item))}>
+
handleCollapse(item)}>
{hasChild && }
{`${item.name}`}
{' :'}
- {item.type === 'string' || item.type === 'number'
- ? {item.value}
- : {item.value}}
+ {item.type === 'string' || item.type === 'number' ? (
+ {
+ const nextAttrs = [...editableAttrs];
+ const nextItem = {...item};
+ nextItem.value = event.target.value;
+ nextAttrs[index] = nextItem;
+ setEditableAttrs(nextAttrs);
+ }}
+ onKeyUp={(event) => {
+ const value = (event.target as HTMLInputElement).value;
+ if (event.key === 'Enter') {
+ if(isDev) {
+ console.log('post attr change', value);
+ } else {
+ const data = buildAttrModifyData(attrsType,attrs, value,item, index, id);
+ postMessageToBackground(ModifyAttrs, data);
+ }
+ }
+ }}
+ />
+ ) : (
+ {item.value}
+ )}
);
if (isCollapsed) {
@@ -74,7 +106,7 @@ function ComponentAttr({ name, attrs }: { name: string, attrs: IAttr[] }) {
return (
-
{name}
+
{attrsName}
@@ -86,8 +118,7 @@ function ComponentAttr({ name, attrs }: { name: string, attrs: IAttr[] }) {
);
}
-export default function ComponentInfo({ name, attrs, parents, onClickParent }: IComponentInfo) {
- const { state, props, context, hooks } = attrs;
+export default function ComponentInfo({ name, attrs, parents, id, onClickParent }: IComponentInfo) {
return (
@@ -104,10 +135,14 @@ export default function ComponentInfo({ name, attrs, parents, onClickParent }: I
>}
- {context &&
}
- {props && props.length !== 0 &&
}
- {state && state.length !== 0 &&
}
- {hooks && hooks.length !== 0 &&
}
+ {Object.keys(attrs).map(attrsType => {
+ const parsedAttrs = attrs[attrsType];
+ if (parsedAttrs && parsedAttrs.length !== 0) {
+ const attrsName = attrsType.slice(6); // parsedState => State
+ return
;
+ }
+ return null;
+ })}
{name &&
parents: {
@@ -122,4 +157,4 @@ export default function ComponentInfo({ name, attrs, parents, onClickParent }: I
);
-}
\ No newline at end of file
+}
diff --git a/libs/extension/src/injector/index.ts b/libs/extension/src/injector/index.ts
index e0372d10..17725af9 100644
--- a/libs/extension/src/injector/index.ts
+++ b/libs/extension/src/injector/index.ts
@@ -1,5 +1,5 @@
import parseTreeRoot, { clearVNode, queryVNode } from '../parser/parseVNode';
-import { packagePayload, checkMessage } from './../utils/transferTool';
+import { packagePayload, checkMessage } from '../utils/transferTool';
import {
RequestAllVNodeTreeInfos,
AllVNodeTreesInfos,
@@ -7,11 +7,9 @@ import {
ComponentAttrs,
DevToolHook,
DevToolContentScript
-} from './../utils/constants';
-import { VNode } from './../../../horizon/src/renderer/vnode/VNode';
-import { ClassComponent } from '../../../horizon/src/renderer/vnode/VNodeTags';
-import { parseAttr, parseHooks } from '../parser/parseAttr';
-import { FunctionComponent } from './../../../horizon/src/renderer/vnode/VNodeTags';
+} from '../utils/constants';
+import { VNode } from '../../../horizon/src/renderer/vnode/VNode';
+import { parseVNodeAttrs } from '../parser/parseAttr';
const roots = [];
@@ -48,24 +46,8 @@ function postMessage(type: string, data) {
function parseCompAttrs(id: number) {
const vNode: VNode = queryVNode(id);
- const tag = vNode.tag;
- if (tag === ClassComponent) {
- const { props, state } = vNode;
- const parsedProps = parseAttr(props);
- const parsedState = parseAttr(state);
- postMessage(ComponentAttrs, {
- parsedProps,
- parsedState,
- });
- } else if (tag === FunctionComponent) {
- const { props, hooks } = vNode;
- const parsedProps = parseAttr(props);
- const parsedHooks = parseHooks(hooks);
- postMessage(ComponentAttrs, {
- parsedProps,
- parsedHooks,
- });
- }
+ const parsedAttrs = parseVNodeAttrs(vNode);
+ postMessage(ComponentAttrs, parsedAttrs);
}
function injectHook() {
diff --git a/libs/extension/src/panel/App.tsx b/libs/extension/src/panel/App.tsx
index af6e5d9a..993c3a66 100644
--- a/libs/extension/src/panel/App.tsx
+++ b/libs/extension/src/panel/App.tsx
@@ -9,13 +9,16 @@ import { FilterTree } from '../hooks/FilterTree';
import Close from '../svgs/Close';
import Arrow from './../svgs/Arrow';
import {
- InitDevToolPageConnection,
AllVNodeTreesInfos,
RequestComponentAttrs,
ComponentAttrs,
- DevToolPanel,
-} from './../utils/constants';
-import { packagePayload } from './../utils/transferTool';
+} from '../utils/constants';
+import {
+ addBackgroundMessageListener,
+ initBackgroundConnection,
+ postMessageToBackground, removeBackgroundMessageListener,
+} from '../panelConnection';
+import { IAttr } from '../parser/parseAttr';
const parseVNodeData = (rawData) => {
const idIndentationMap: {
@@ -59,45 +62,13 @@ const getParents = (item: IData | null, parsedVNodeData: IData[]) => {
return parents;
};
-let connection;
-if (!isDev) {
- // 与 background 的唯一连接
- connection = chrome.runtime.connect({
- name: 'panel'
- });
-}
-
-let reconnectTimes = 0;
-
-function postMessage(type: string, data: any) {
- try {
- connection.postMessage(packagePayload({
- type: type,
- data: data,
- }, DevToolPanel));
- } catch(err) {
- // 可能出现 port 关闭的场景,需要重新建立连接,增加可靠性
- if (reconnectTimes === 20) {
- reconnectTimes = 0;
- console.error('reconnect failed');
- return;
- }
- console.error(err);
- reconnectTimes++;
- // 重建连接
- connection = chrome.runtime.connect({
- name: 'panel'
- });
- // 重新发送初始化消息
- postMessage(InitDevToolPageConnection, chrome.devtools.inspectedWindow.tabId);
- // 初始化成功后才会重新发送消息
- postMessage(type, data);
- }
-}
-
function App() {
const [parsedVNodeData, setParsedVNodeData] = useState([]);
- const [componentAttrs, setComponentAttrs] = useState({});
+ const [componentAttrs, setComponentAttrs] = useState<{
+ parsedProps?: IAttr[],
+ parsedState?: IAttr[],
+ parsedHooks?: IAttr[],
+ }>();
const [selectComp, setSelectComp] = useState(null);
const treeRootInfos = useRef<{id: number, length: number}[]>([]); // 记录保存的根节点 id 和长度,
@@ -119,14 +90,11 @@ function App() {
const parsedData = parseVNodeData(mockParsedVNodeData);
setParsedVNodeData(parsedData);
setComponentAttrs({
- state: parsedMockState,
- props: parsedMockState,
+ parsedProps: parsedMockState,
+ parsedState: parsedMockState,
});
} else {
- // 页面打开后发送初始化请求
- postMessage(InitDevToolPageConnection, chrome.devtools.inspectedWindow.tabId);
- // 监听 background消息
- connection.onMessage.addListener(function (message) {
+ const handleBackgroundMessage = (message) => {
const { payload } = message;
if (payload) {
const { type, data } = payload;
@@ -145,13 +113,21 @@ function App() {
} else if (type === ComponentAttrs) {
const {parsedProps, parsedState, parsedHooks} = data;
setComponentAttrs({
- props: parsedProps,
- state: parsedState,
- hooks: parsedHooks,
+ parsedProps,
+ parsedState,
+ parsedHooks,
});
}
}
- });
+ };
+ console.log('handle connection');
+ // 在页面渲染后初始化连接
+ initBackgroundConnection();
+ // 监听 background消息
+ addBackgroundMessageListener(handleBackgroundMessage);
+ return () => {
+ removeBackgroundMessageListener(handleBackgroundMessage);
+ };
}
}, []);
@@ -162,11 +138,11 @@ function App() {
const handleSelectComp = (item: IData) => {
if (isDev) {
setComponentAttrs({
- state: parsedMockState,
- props: parsedMockState,
+ parsedProps: parsedMockState,
+ parsedState: parsedMockState,
});
} else {
- postMessage(RequestComponentAttrs, item.id);
+ postMessageToBackground(RequestComponentAttrs, item.id);
}
setSelectComp(item);
};
@@ -216,6 +192,7 @@ function App() {
name={selectComp ? selectComp.name : null}
attrs={selectComp ? componentAttrs : {}}
parents={parents}
+ id={selectComp ? selectComp.id : null}
onClickParent={handleClickParent} />
diff --git a/libs/extension/src/parser/parseAttr.ts b/libs/extension/src/parser/parseAttr.ts
index e18f9bad..38238eda 100644
--- a/libs/extension/src/parser/parseAttr.ts
+++ b/libs/extension/src/parser/parseAttr.ts
@@ -1,5 +1,8 @@
-import { Hook, Reducer, Ref } from './../../../horizon/src/renderer/hooks/HookType';
+import { Hook, Reducer, Ref } from '../../../horizon/src/renderer/hooks/HookType';
+import { ModifyHooks, ModifyProps, ModifyState } from '../utils/constants';
+import { VNode } from '../../../horizon/src/renderer/vnode/VNode';
+import { ClassComponent, FunctionComponent } from '../../../horizon/src/renderer/vnode/VNodeTags';
// 展示值为 string 的可编辑类型
type editableStringType = 'string' | 'number' | 'undefined' | 'null';
@@ -12,7 +15,7 @@ type showAsStringType = editableStringType | unEditableStringType;
export type IAttr = {
- name: string;
+ name: string | number;
indentation: number;
hIndex?: number; // 用于记录 hook 的 hIndex 值
} & ({
@@ -131,3 +134,61 @@ export function parseHooks(hooks: Hook
[]) {
});
return result;
}
+
+export function parseVNodeAttrs(vNode: VNode) {
+ const tag = vNode.tag;
+ if (tag === ClassComponent) {
+ const { props, state } = vNode;
+ const parsedProps = parseAttr(props);
+ const parsedState = parseAttr(state);
+ return {
+ parsedProps,
+ parsedState,
+ };
+ } else if (tag === FunctionComponent) {
+ const { props, hooks } = vNode;
+ const parsedProps = parseAttr(props);
+ const parsedHooks = parseHooks(hooks);
+ return {
+ parsedProps,
+ parsedHooks,
+ };
+ }
+}
+
+// 计算属性的访问顺序
+function calculateAttrAccessPath(item: IAttr, index: number, attrs: IAttr[]) {
+ let currentIndentation = item.indentation;
+ const path = [item.name];
+ for(let i = index - 1; i >= 0; i--) {
+ const lastItem = attrs[i];
+ const lastIndentation = lastItem.indentation;
+ if (lastIndentation < currentIndentation) {
+ path.push(lastItem.name);
+ currentIndentation = lastIndentation;
+ }
+ }
+ path.reverse();
+ return path;
+}
+
+export function buildAttrModifyData(parsedAttrsType: string, attrs: IAttr[], value, item: IAttr, index: number, id: number) {
+ const path = calculateAttrAccessPath(item, index, attrs);
+ let type;
+ if (parsedAttrsType === 'parsedProps') {
+ type = ModifyProps;
+ } else if (parsedAttrsType === 'parsedState') {
+ type = ModifyState;
+ path[0] = item.hIndex;
+ } else if (parsedAttrsType === 'parsedHooks') {
+ type = ModifyHooks;
+ } else {
+ return null;
+ }
+ return {
+ id: id,
+ type: type,
+ value: value,
+ path: path,
+ };
+}
diff --git a/libs/extension/src/utils/constants.ts b/libs/extension/src/utils/constants.ts
index e779a143..1be5c8dc 100644
--- a/libs/extension/src/utils/constants.ts
+++ b/libs/extension/src/utils/constants.ts
@@ -11,6 +11,14 @@ export const RequestComponentAttrs = 'get component attrs';
// 返回组件属性
export const ComponentAttrs = 'component attrs';
+export const ModifyAttrs = 'modify attrs';
+
+export const ModifyProps = 'modify props';
+
+export const ModifyState = 'modify state';
+
+export const ModifyHooks = 'modify hooks';
+
// 传递消息来源标志
export const DevToolPanel = 'dev tool panel';
@@ -19,4 +27,4 @@ export const DevToolBackground = 'dev tool background';
export const DevToolContentScript = 'dev tool content script';
-export const DevToolHook = 'dev tool hook';
\ No newline at end of file
+export const DevToolHook = 'dev tool hook';
From 004376160984c5700b25a9b46bd5a1a7a7490fb3 Mon Sep 17 00:00:00 2001
From: * <8>
Date: Wed, 27 Apr 2022 10:32:19 +0800
Subject: [PATCH 05/26] Match-id-06a032e63a37a939ac11e0d3f94647bd2122360c
---
libs/extension/readme.md | 22 ++++++++++++++++++----
libs/extension/src/parser/parseAttr.ts | 21 +++++++++++++--------
2 files changed, 31 insertions(+), 12 deletions(-)
diff --git a/libs/extension/readme.md b/libs/extension/readme.md
index b54fecf2..d725a5f8 100644
--- a/libs/extension/readme.md
+++ b/libs/extension/readme.md
@@ -47,6 +47,9 @@ sequenceDiagram
```
## 传输数据结构
+**限制:chrome.runtime.sendMessage只能传递 JSON-serializable 数据**
+
+
```ts
type passData = {
type: 'HORIZON_DEV_TOOLS',
@@ -58,11 +61,22 @@ type passData = {
```
## horizon和devTools的主要交互
-- 页面初始渲染
-- 页面更新
-- 页面销毁
+- App初始渲染
+- App更新
+- App销毁
+- 整个页面刷新
- devTools触发组件属性更新
+## 对 hook 类型的判断和值的获取
+Horizon 是一个底层框架,在 Horizon 与插件的交互过程中,我们不希望 Horizon 额外的增加一些代码和接口给插件使用,这可能会影响到 Horizon 的性能。
+所以我们决定直接感知 hook 的属性值,通过其属性值判断 hook 类型,并直接调用 Reducer 的 trigger 函数触发更新。
+
+## 触发组件更新方式
+- 类组件的state:调用实例的 setState 函数触发更新
+- 类组件的props:浅复制props后更新props值并调用 forceUpdate 触发更新
+- 函数组件的props:
+- 函数组件的state:调用 useState 函数触发更新
+
## VNode的清理
全局 hook 中保存了root VNode,在解析 VNode 树的时候也会保存 VNode 的引用,在清理VNode的时候这些 VNode 的引用也需要删除。
@@ -73,7 +87,7 @@ type passData = {
- 通过解析 path 值可以分析出组件树的结构
## 组件props/state/hook等数据的传输和解析
-将数据格式进行转换后进行传递。对于 props 和 类组件的 state,他们都是对象,可以将对象进行解析然后以 k-v 的形式,树的结构显示。函数组件的 Hooks 是以数组的形式存储在 vNode 的属性中的,每个 hook 的唯一标识符是 hIndex 属性值,在对象展示的时候不能展示该属性值,需要根据 hook 类型展示一个 state/ref/effect 等值。hook 中存储的值也可能不是对象,只是一个简单的字符串,他们的解析和 props/state 的解析同样存在差异。
+将数据格式进行转换后进行传递。对于 props 和 类组件的 state,他们都是对象,可以将对象进行解析然后以 k-v 的形式,树的结构显示。函数组件的 Hooks 是以数组的形式存储在 vNode 的属性中的,每个 hook 的唯一标识符是 hIndex 属性值,在对象展示的时候不能展示该属性值,需要根据 hook 类型展示一个 state/ref/effect 等值。hook 中存储的值也可能不是对象,只是一个简单的字符串或者 dom 元素,他们的解析和 props/state 的解析同样存在差异,需要单独处理。
## 滚动动态渲染 Tree
diff --git a/libs/extension/src/parser/parseAttr.ts b/libs/extension/src/parser/parseAttr.ts
index 38238eda..652593db 100644
--- a/libs/extension/src/parser/parseAttr.ts
+++ b/libs/extension/src/parser/parseAttr.ts
@@ -99,7 +99,7 @@ const parseSubAttr = (
value,
indentation: parentIndentation + 1,
};
- if (hIndex) {
+ if (hIndex !== undefined) {
item.hIndex = hIndex;
}
result.push(item);
@@ -123,13 +123,18 @@ export function parseHooks(hooks: Hook[]) {
const result: IAttr[] = [];
const indentation = 0;
hooks.forEach(hook => {
- const { hIndex, state ,type } = hook;
- if (type === 'useState') {
- parseSubAttr((state as Reducer).stateValue, indentation, 'state', result, hIndex);
- } else if (type === 'useRef') {
+ const { hIndex, state } = hook;
+ // 不同 hook 的 state 有不同属性,根据是否存在该属性判断 hook 类型
+ // 采用这种方式是因为要拿到需要的属性值,和后续触发更新,必然要感知 hook 的属性值
+ // 既然已经感知了属性,就不额外添加属性进行类型判断了
+ if ((state as Reducer).stateValue) {
+ if ((state as Reducer).isUseState) {
+ parseSubAttr((state as Reducer).stateValue, indentation, 'state', result, hIndex);
+ } else {
+ parseSubAttr((state as Reducer).stateValue, indentation, 'reducer', result, hIndex);
+ }
+ } else if ((state as Ref).current) {
parseSubAttr((state as Ref).current, indentation, 'ref', result, hIndex);
- } else if (type === 'useReducer') {
- parseSubAttr((state as Reducer).stateValue, indentation, 'reducer', result, hIndex);
}
});
return result;
@@ -179,9 +184,9 @@ export function buildAttrModifyData(parsedAttrsType: string, attrs: IAttr[], val
type = ModifyProps;
} else if (parsedAttrsType === 'parsedState') {
type = ModifyState;
- path[0] = item.hIndex;
} else if (parsedAttrsType === 'parsedHooks') {
type = ModifyHooks;
+ path[0] = item.hIndex;
} else {
return null;
}
From 580beb2ca6ed277df853886d5a0f99703bddf06f Mon Sep 17 00:00:00 2001
From: * <8>
Date: Wed, 27 Apr 2022 15:48:51 +0800
Subject: [PATCH 06/26] Match-id-3eb8b4cc0e16ccf9c3931acedad4ed9abe2b52d9
---
libs/extension/src/parser/parseVNode.ts | 14 +++++++++-----
1 file changed, 9 insertions(+), 5 deletions(-)
diff --git a/libs/extension/src/parser/parseVNode.ts b/libs/extension/src/parser/parseVNode.ts
index fbf4b0bd..b62eacc6 100644
--- a/libs/extension/src/parser/parseVNode.ts
+++ b/libs/extension/src/parser/parseVNode.ts
@@ -1,13 +1,17 @@
-import { travelVNodeTree } from '../../../../libs/horizon/src/renderer/vnode/VNodeUtils';
-import { VNode } from '../../../../libs/horizon/src/renderer/Types';
-import { ClassComponent, FunctionComponent } from '../../../../libs/horizon/src/renderer/vnode/VNodeTags';
+import { travelVNodeTree } from '../../../horizon/src/renderer/vnode/VNodeUtils';
+import { VNode } from '../../../horizon/src/renderer/vnode/VNode';
+import { ClassComponent, FunctionComponent } from '../../../horizon/src/renderer/vnode/VNodeTags';
// 建立双向映射关系,当用户在修改属性值后,可以找到对应的 VNode
const VNodeToIdMap = new Map();
const IdToVNodeMap = new Map();
let uid = 0;
-function generateUid () {
+function generateUid (vNode: VNode) {
+ const id = VNodeToIdMap.get(vNode);
+ if (id !== undefined) {
+ return id;
+ }
uid++;
return uid;
}
@@ -33,7 +37,7 @@ function parseTreeRoot(treeRoot: VNode) {
travelVNodeTree(treeRoot, (node: VNode) => {
const tag = node.tag;
if (isUserComponent(tag)) {
- const id = generateUid();
+ const id = generateUid(node);
result.push(id);
const name = node.type.name;
result.push(name);
From 90ab25303d837cf5ef4f641b209b3d2953b82612 Mon Sep 17 00:00:00 2001
From: * <8>
Date: Wed, 27 Apr 2022 15:57:44 +0800
Subject: [PATCH 07/26] Match-id-dd619e8d054f2fd3bac30ca85925d001505434ee
---
libs/horizon/src/renderer/hooks/HookType.ts | 1 -
libs/horizon/src/renderer/hooks/UseReducerHook.ts | 1 -
libs/horizon/src/renderer/hooks/UseRefHook.ts | 1 -
3 files changed, 3 deletions(-)
diff --git a/libs/horizon/src/renderer/hooks/HookType.ts b/libs/horizon/src/renderer/hooks/HookType.ts
index cb8be892..e965fdf1 100644
--- a/libs/horizon/src/renderer/hooks/HookType.ts
+++ b/libs/horizon/src/renderer/hooks/HookType.ts
@@ -3,7 +3,6 @@ import {EffectConstant} from './EffectConstant';
export interface Hook {
state: Reducer | Effect | Memo | CallBack | Ref;
hIndex: number;
- type?: 'useState' | 'useRef' | 'useReducer';
}
export interface Reducer {
diff --git a/libs/horizon/src/renderer/hooks/UseReducerHook.ts b/libs/horizon/src/renderer/hooks/UseReducerHook.ts
index 480f43bb..52399713 100644
--- a/libs/horizon/src/renderer/hooks/UseReducerHook.ts
+++ b/libs/horizon/src/renderer/hooks/UseReducerHook.ts
@@ -87,7 +87,6 @@ export function useReducerForInit(reducer, initArg, init, isUseState?: boo
}
const hook = createHook();
- hook.type = isUseState ? 'useState' : 'useReducer';
// 为hook.state赋值{状态值, 触发函数, reducer, updates更新数组, 是否是useState}
hook.state = {
stateValue: stateValue,
diff --git a/libs/horizon/src/renderer/hooks/UseRefHook.ts b/libs/horizon/src/renderer/hooks/UseRefHook.ts
index 381ef61e..754a16d2 100644
--- a/libs/horizon/src/renderer/hooks/UseRefHook.ts
+++ b/libs/horizon/src/renderer/hooks/UseRefHook.ts
@@ -12,7 +12,6 @@ export function useRefImpl(value: V): Ref {
if (stage === HookStage.Init) {
hook = createHook();
hook.state = {current: value};
- hook.type = 'useRef';
} else if (stage === HookStage.Update) {
hook = getCurrentHook();
}
From 1b6652bb5ad8c0d03f618703db1ed3e8491ab419 Mon Sep 17 00:00:00 2001
From: * <8>
Date: Wed, 27 Apr 2022 16:00:33 +0800
Subject: [PATCH 08/26] Match-id-873a6fdcfff71a095adeec6085ceac6928f62d4d
---
libs/horizon/src/renderer/vnode/VNodeUtils.ts | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/libs/horizon/src/renderer/vnode/VNodeUtils.ts b/libs/horizon/src/renderer/vnode/VNodeUtils.ts
index 2875a125..ef60b566 100644
--- a/libs/horizon/src/renderer/vnode/VNodeUtils.ts
+++ b/libs/horizon/src/renderer/vnode/VNodeUtils.ts
@@ -97,6 +97,10 @@ export function clearVNode(vNode: VNode) {
vNode.toUpdateNodes = null;
vNode.belongClassVNode = null;
+ if (window.__HORIZON_DEV_HOOK__) {
+ const hook = window.__HORIZON_DEV_HOOK__;
+ hook.delete(vNode);
+ }
}
// 是dom类型的vNode
From d201a000f551dbbb499239e58a4610c5f92435cb Mon Sep 17 00:00:00 2001
From: * <8>
Date: Wed, 27 Apr 2022 20:14:41 +0800
Subject: [PATCH 09/26] Match-id-28c4a62743944f5c0ea9f7fe8481f641238170f4
---
libs/extension/src/panelConnection/PanelConnection.ts | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/libs/extension/src/panelConnection/PanelConnection.ts b/libs/extension/src/panelConnection/PanelConnection.ts
index 88dae8bb..3a3b18ce 100644
--- a/libs/extension/src/panelConnection/PanelConnection.ts
+++ b/libs/extension/src/panelConnection/PanelConnection.ts
@@ -56,6 +56,6 @@ export function postMessageToBackground(type: string, data: any) {
// 重建连接
initBackgroundConnection();
// 初始化成功后才会重新发送消息
- postMessage(type, data);
+ postMessageToBackground(type, data);
}
}
From c744bf3e0eef396e054af792010a63e87ed785d8 Mon Sep 17 00:00:00 2001
From: * <8>
Date: Wed, 27 Apr 2022 20:16:37 +0800
Subject: [PATCH 10/26] Match-id-8d1a9a57310598b5ac6a4c9f5bca7609c8a56b35
---
libs/extension/src/utils/logUtil.ts | 19 +++++++++++++++++++
1 file changed, 19 insertions(+)
create mode 100644 libs/extension/src/utils/logUtil.ts
diff --git a/libs/extension/src/utils/logUtil.ts b/libs/extension/src/utils/logUtil.ts
new file mode 100644
index 00000000..d57e8bfe
--- /dev/null
+++ b/libs/extension/src/utils/logUtil.ts
@@ -0,0 +1,19 @@
+// chrome 通过 iframe 的方式将 panel 页面嵌入到开发者工具中,如果有报错是无法感知到的
+// 同时也无法在运行时打断点,需要适当的日志辅助开发和问题定位
+
+interface loggerType {
+ error: typeof console.error,
+ info: typeof console.info,
+ log: typeof console.log,
+ warn: typeof console.warn,
+}
+
+export function createLogger(id: string): loggerType {
+ return ['error', 'info', 'log', 'warn'].reduce((pre, current) => {
+ const prefix = `[horizon_dev_tool][${id}] `;
+ pre[current] = (...data) => {
+ console[current](prefix, ...data);
+ };
+ return pre;
+ }, {} as loggerType);
+}
From f76a09544dae17b406525f64165cc30369e33037 Mon Sep 17 00:00:00 2001
From: * <8>
Date: Wed, 27 Apr 2022 20:17:34 +0800
Subject: [PATCH 11/26] Match-id-63a8d97173b3615bf3b6cf153df0aeefc8fe6fb1
---
libs/extension/src/background/index.ts | 7 +++----
1 file changed, 3 insertions(+), 4 deletions(-)
diff --git a/libs/extension/src/background/index.ts b/libs/extension/src/background/index.ts
index cc8bd778..b72197f6 100644
--- a/libs/extension/src/background/index.ts
+++ b/libs/extension/src/background/index.ts
@@ -14,10 +14,9 @@ chrome.runtime.onConnect.addListener(function (port) {
const { type, data } = payload;
let passMessage;
if (type === InitDevToolPageConnection) {
- if (!connections[data]) {
- // 获取 panel 所在 tab 页的tabId
- connections[data] = port;
- }
+ // 记录 panel 所在 tab 页的tabId,如果已经记录了,覆盖原有port,因为原有port可能关闭了
+ // 可能这次是 panel 发起的重新建立请求
+ connections[data] = port;
passMessage = packagePayload({ type: RequestAllVNodeTreeInfos }, DevToolBackground);
} else {
passMessage = message;
From d7a211a45a12f73693c7e84a4fda4d5a5efa47d0 Mon Sep 17 00:00:00 2001
From: * <8>
Date: Wed, 27 Apr 2022 20:23:42 +0800
Subject: [PATCH 12/26] Match-id-093ef93954199cd2cfffed7fd140bbac91c716d9
---
libs/extension/src/panel/App.tsx | 47 +++++++++++++++++++++-----------
1 file changed, 31 insertions(+), 16 deletions(-)
diff --git a/libs/extension/src/panel/App.tsx b/libs/extension/src/panel/App.tsx
index 993c3a66..9ae632cc 100644
--- a/libs/extension/src/panel/App.tsx
+++ b/libs/extension/src/panel/App.tsx
@@ -19,8 +19,11 @@ import {
postMessageToBackground, removeBackgroundMessageListener,
} from '../panelConnection';
import { IAttr } from '../parser/parseAttr';
+import { createLogger } from '../utils/logUtil';
-const parseVNodeData = (rawData) => {
+const logger = createLogger('panelApp');
+
+const parseVNodeData = (rawData, idToTreeNodeMap , nextIdToTreeNodeMap) => {
const idIndentationMap: {
[id: string]: number;
} = {};
@@ -37,10 +40,18 @@ const parseVNodeData = (rawData) => {
i++;
const indentation = parentId === '' ? 0 : idIndentationMap[parentId] + 1;
idIndentationMap[id] = indentation;
- const item = {
- id, name, indentation, userKey
- };
- data.push(item);
+ const lastItem = idToTreeNodeMap[id];
+ if (lastItem) {
+ // 由于 diff 算法限制,一个 vNode 的 name,userKey,indentation 属性不会发生变化
+ nextIdToTreeNodeMap[id] = lastItem;
+ data.push(lastItem);
+ } else {
+ const item = {
+ id, name, indentation, userKey
+ };
+ nextIdToTreeNodeMap[id] = item;
+ data.push(item);
+ }
}
return data;
};
@@ -62,15 +73,19 @@ const getParents = (item: IData | null, parsedVNodeData: IData[]) => {
return parents;
};
+interface IIdToNodeMap {
+ [id: number]: IData;
+}
+
function App() {
const [parsedVNodeData, setParsedVNodeData] = useState([]);
const [componentAttrs, setComponentAttrs] = useState<{
parsedProps?: IAttr[],
parsedState?: IAttr[],
parsedHooks?: IAttr[],
- }>();
+ }>({});
const [selectComp, setSelectComp] = useState(null);
- const treeRootInfos = useRef<{id: number, length: number}[]>([]); // 记录保存的根节点 id 和长度,
+ const idToTreeNodeMapRef = useRef({});
const {
filterValue,
@@ -87,7 +102,9 @@ function App() {
useEffect(() => {
if (isDev) {
- const parsedData = parseVNodeData(mockParsedVNodeData);
+ const nextIdToTreeNodeMap: IIdToNodeMap = {};
+ const parsedData = parseVNodeData(mockParsedVNodeData, idToTreeNodeMapRef.current, nextIdToTreeNodeMap);
+ idToTreeNodeMapRef.current = nextIdToTreeNodeMap;
setParsedVNodeData(parsedData);
setComponentAttrs({
parsedProps: parsedMockState,
@@ -96,19 +113,18 @@ function App() {
} else {
const handleBackgroundMessage = (message) => {
const { payload } = message;
+ // 对象数据只是记录了引用,内容可能在后续被修改,打印字符串可以获取当前真正内容,不被后续修改影响
+ logger.info(JSON.stringify(payload));
if (payload) {
const { type, data } = payload;
if (type === AllVNodeTreesInfos) {
+ const idToTreeNodeMap = idToTreeNodeMapRef.current;
+ const nextIdToTreeNodeMap: IIdToNodeMap = {};
const allTreeData = data.reduce((pre, current) => {
- const parsedTreeData = parseVNodeData(current);
- const length = parsedTreeData.length;
- treeRootInfos.current.length = 0;
- if (length) {
- const treeRoot = parsedTreeData[0];
- treeRootInfos.current.push({id: treeRoot.id, length: length});
- }
+ const parsedTreeData = parseVNodeData(current, idToTreeNodeMap, nextIdToTreeNodeMap);
return pre.concat(parsedTreeData);
}, []);
+ idToTreeNodeMapRef.current = nextIdToTreeNodeMap;
setParsedVNodeData(allTreeData);
} else if (type === ComponentAttrs) {
const {parsedProps, parsedState, parsedHooks} = data;
@@ -120,7 +136,6 @@ function App() {
}
}
};
- console.log('handle connection');
// 在页面渲染后初始化连接
initBackgroundConnection();
// 监听 background消息
From 559038fe6729735bf6a34750de95fe9615f73f68 Mon Sep 17 00:00:00 2001
From: * <8>
Date: Thu, 28 Apr 2022 10:20:00 +0800
Subject: [PATCH 13/26] Match-id-b60858a00107b694e16612e9b5cb64237a31e501
---
libs/extension/src/panel/App.tsx | 5 +++++
1 file changed, 5 insertions(+)
diff --git a/libs/extension/src/panel/App.tsx b/libs/extension/src/panel/App.tsx
index 9ae632cc..22994dcc 100644
--- a/libs/extension/src/panel/App.tsx
+++ b/libs/extension/src/panel/App.tsx
@@ -43,7 +43,12 @@ const parseVNodeData = (rawData, idToTreeNodeMap , nextIdToTreeNodeMap) => {
const lastItem = idToTreeNodeMap[id];
if (lastItem) {
// 由于 diff 算法限制,一个 vNode 的 name,userKey,indentation 属性不会发生变化
+ // 但是在跳转到新页面时,id 值重置,此时原有 id 对应的节点都发生了变化,需要更新
+ // 为了让架构尽可能简单,我们不区分是否是页面跳转,所以每次都需要重新赋值
nextIdToTreeNodeMap[id] = lastItem;
+ lastItem.name = name;
+ lastItem.indentation = indentation;
+ lastItem.userKey = userKey;
data.push(lastItem);
} else {
const item = {
From a54d90bc58f9bddadafa818522c15a4e256066c7 Mon Sep 17 00:00:00 2001
From: * <8>
Date: Thu, 28 Apr 2022 10:20:35 +0800
Subject: [PATCH 14/26] Match-id-b1a480e5c4932daff22eb8e301cc4434240bfdc5
---
libs/extension/src/background/index.ts | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/libs/extension/src/background/index.ts b/libs/extension/src/background/index.ts
index b72197f6..6913fb0f 100644
--- a/libs/extension/src/background/index.ts
+++ b/libs/extension/src/background/index.ts
@@ -16,7 +16,7 @@ chrome.runtime.onConnect.addListener(function (port) {
if (type === InitDevToolPageConnection) {
// 记录 panel 所在 tab 页的tabId,如果已经记录了,覆盖原有port,因为原有port可能关闭了
// 可能这次是 panel 发起的重新建立请求
- connections[data] = port;
+ connections[data] = port; // data 是 tabId 值,该值指当前浏览器分配给 web_page 的 id 值。是panel页面查询得到
passMessage = packagePayload({ type: RequestAllVNodeTreeInfos }, DevToolBackground);
} else {
passMessage = message;
@@ -56,6 +56,7 @@ chrome.runtime.onMessage.addListener(function (message, sender, sendResponse) {
// Messages from content scripts should have sender.tab set
if (sender.tab) {
const tabId = sender.tab.id;
+ // 和 InitDevToolPageConnection 时得到的 tabId 值一致时,向指定的 panel 页面 port 发送消息
if (tabId in connections && checkMessage(message, DevToolContentScript)) {
changeSource(message, DevToolBackground);
connections[tabId].postMessage(message);
From 1f3c35e6ba1eb0c4ed6c1bfda0573f018f158a94 Mon Sep 17 00:00:00 2001
From: * <8>
Date: Thu, 28 Apr 2022 11:33:09 +0800
Subject: [PATCH 15/26] Match-id-7581e825580013d22069f66123ef649787bb1a1e
---
libs/extension/src/background/index.ts | 20 +++++--------------
.../src/panelConnection/PanelConnection.ts | 12 +++++------
2 files changed, 11 insertions(+), 21 deletions(-)
diff --git a/libs/extension/src/background/index.ts b/libs/extension/src/background/index.ts
index 6913fb0f..6a8c4591 100644
--- a/libs/extension/src/background/index.ts
+++ b/libs/extension/src/background/index.ts
@@ -11,28 +11,18 @@ chrome.runtime.onConnect.addListener(function (port) {
const isHorizonMessage = checkMessage(message, DevToolPanel);
if (isHorizonMessage) {
const { payload } = message;
- const { type, data } = payload;
+ // tabId 值指当前浏览器分配给 web_page 的 id 值。是panel页面查询得到,指定向该页面发送消息
+ const { type, data, tabId } = payload;
let passMessage;
if (type === InitDevToolPageConnection) {
// 记录 panel 所在 tab 页的tabId,如果已经记录了,覆盖原有port,因为原有port可能关闭了
// 可能这次是 panel 发起的重新建立请求
- connections[data] = port; // data 是 tabId 值,该值指当前浏览器分配给 web_page 的 id 值。是panel页面查询得到
+ connections[tabId] = port;
passMessage = packagePayload({ type: RequestAllVNodeTreeInfos }, DevToolBackground);
} else {
- passMessage = message;
- changeSource(passMessage, DevToolBackground);
+ passMessage = packagePayload({type, data}, DevToolBackground);
}
- // 查询参数有 active 和 currentWindow, 如果开发者工具与页面分离,会导致currentWindow为false才能找到
- // 所以只用 active 参数查找,但不确定这么写是否会引发查询错误的情况
- // 或许需要用不同的查询参数查找两次
- chrome.tabs.query({ active: true }, function (tabs) {
- if (tabs.length) {
- chrome.tabs.sendMessage(tabs[0].id, passMessage);
- console.log('post message end');
- } else {
- console.log('do not find message');
- }
- });
+ chrome.tabs.sendMessage(tabId, passMessage);
}
}
// Listen to messages sent from the DevTools page
diff --git a/libs/extension/src/panelConnection/PanelConnection.ts b/libs/extension/src/panelConnection/PanelConnection.ts
index 3a3b18ce..27bcc4ac 100644
--- a/libs/extension/src/panelConnection/PanelConnection.ts
+++ b/libs/extension/src/panelConnection/PanelConnection.ts
@@ -29,7 +29,7 @@ export function initBackgroundConnection() {
// 监听 background 消息
connection.onMessage.addListener(notice);
// 页面打开后发送初始化请求
- postMessageToBackground(InitDevToolPageConnection, chrome.devtools.inspectedWindow.tabId);
+ postMessageToBackground(InitDevToolPageConnection);
} catch (e) {
console.error('create connection failed');
console.error(e);
@@ -38,12 +38,12 @@ export function initBackgroundConnection() {
}
let reconnectTimes = 0;
-export function postMessageToBackground(type: string, data: any) {
+export function postMessageToBackground(type: string, data?: any) {
try{
- connection.postMessage(packagePayload({
- type: type,
- data: data,
- }, DevToolPanel));
+ const payLoad = data
+ ? { type, tabId: chrome.devtools.inspectedWindow.tabId, data }
+ : { type, tabId: chrome.devtools.inspectedWindow.tabId };
+ connection.postMessage(packagePayload(payLoad, DevToolPanel));
} catch(err) {
// 可能出现 port 关闭的场景,需要重新建立连接,增加可靠性
if (reconnectTimes === 20) {
From 8fd3295ba2d0bcb78d08c14d6df74e6aaf4930dd Mon Sep 17 00:00:00 2001
From: * <8>
Date: Thu, 5 May 2022 16:05:10 +0800
Subject: [PATCH 16/26] Match-id-210cbd61873caacc32857203dac50d925672c6a5
---
libs/extension/src/injector/index.ts | 6 +++++-
libs/extension/src/parser/parseVNode.ts | 2 +-
2 files changed, 6 insertions(+), 2 deletions(-)
diff --git a/libs/extension/src/injector/index.ts b/libs/extension/src/injector/index.ts
index 17725af9..58575844 100644
--- a/libs/extension/src/injector/index.ts
+++ b/libs/extension/src/injector/index.ts
@@ -45,7 +45,11 @@ function postMessage(type: string, data) {
}
function parseCompAttrs(id: number) {
- const vNode: VNode = queryVNode(id);
+ const vNode = queryVNode(id);
+ if (!vNode) {
+ console.error('Do not find match vNode, this is a bug, please report us');
+ return;
+ }
const parsedAttrs = parseVNodeAttrs(vNode);
postMessage(ComponentAttrs, parsedAttrs);
}
diff --git a/libs/extension/src/parser/parseVNode.ts b/libs/extension/src/parser/parseVNode.ts
index b62eacc6..314e80e4 100644
--- a/libs/extension/src/parser/parseVNode.ts
+++ b/libs/extension/src/parser/parseVNode.ts
@@ -61,7 +61,7 @@ function parseTreeRoot(treeRoot: VNode) {
return result;
}
-export function queryVNode(id: number) {
+export function queryVNode(id: number): VNode|undefined {
return IdToVNodeMap.get(id);
}
From a58338f11daf614b2d940d4426f832afab64265b Mon Sep 17 00:00:00 2001
From: * <8>
Date: Thu, 5 May 2022 20:20:42 +0800
Subject: [PATCH 17/26] Match-id-0c9d4e2a3b570ab8cbf7ef261c41166f30d3e32e
---
libs/extension/src/injector/index.ts | 55 +++++++++++++++++++++++++-
libs/extension/src/parser/parseAttr.ts | 18 ++++++---
2 files changed, 66 insertions(+), 7 deletions(-)
diff --git a/libs/extension/src/injector/index.ts b/libs/extension/src/injector/index.ts
index 58575844..ad8c5714 100644
--- a/libs/extension/src/injector/index.ts
+++ b/libs/extension/src/injector/index.ts
@@ -6,10 +6,11 @@ import {
RequestComponentAttrs,
ComponentAttrs,
DevToolHook,
- DevToolContentScript
+ DevToolContentScript, ModifyAttrs, ModifyHooks, ModifyState,
} from '../utils/constants';
import { VNode } from '../../../horizon/src/renderer/vnode/VNode';
import { parseVNodeAttrs } from '../parser/parseAttr';
+import { Reducer } from '../../../horizon/src/renderer/hooks/HookType';
const roots = [];
@@ -54,6 +55,56 @@ function parseCompAttrs(id: number) {
postMessage(ComponentAttrs, parsedAttrs);
}
+function modifyVNodeAttrs(data) {
+ const {type, id, value, path} = data;
+ const vNode = queryVNode(id);
+ if (!vNode) {
+ console.error('Do not find match vNode, this is a bug, please report us');
+ return;
+ }
+ if (type === ModifyHooks) {
+ const hooks = vNode.hooks;
+ const editHook = hooks[path[0]];
+ if ((editHook.state as Reducer).trigger) {
+ const editState = editHook.state as Reducer;
+ const editValue = editState.stateValue;
+ const editValueType = typeof editValue;
+ if (editValueType === 'string') {
+ editState.trigger(value);
+ } else if (editValueType === 'number') {
+ const numValue = Number(value);
+ const targetValue = isNaN(numValue) ? value : numValue; // 如果能转为数字,转数字,不能转数字,用原值
+ editState.trigger(targetValue);
+ } else if(editValueType === 'object') {
+ if (editValue === null) {
+ editState.trigger(value);
+ } else {
+ const newValue = {...editValue};
+ // 遍历读取到直接指向需要修改值的对象
+ const attrPath = path.slice(1);
+ let attr = newValue;
+ for(let i = 0; i < attrPath.length - 1; i++) {
+ attr = attr[attrPath[i]];
+ }
+ // 修改对象上的值
+ attr[attrPath[attrPath.length - 1]] = value;
+ editState.trigger(newValue);
+ }
+ }
+ }
+ } else if (type === ModifyState) {
+ const instance = vNode.realNode;
+ const oldState = instance.state || {};
+ const nextState = Object.assign({}, oldState);
+ let accessRef = nextState;
+ for(let i = 0; i < path.length - 1; i++) {
+ accessRef = accessRef[path[i]];
+ }
+ accessRef[path[path.length - 1]] = value;
+ instance.setState(nextState);
+ }
+}
+
function injectHook() {
if (window.__HORIZON_DEV_HOOK__) {
return;
@@ -79,6 +130,8 @@ function injectHook() {
send();
} else if (type === RequestComponentAttrs) {
parseCompAttrs(data);
+ } else if (type === ModifyAttrs) {
+ modifyVNodeAttrs(data);
}
}
});
diff --git a/libs/extension/src/parser/parseAttr.ts b/libs/extension/src/parser/parseAttr.ts
index 652593db..34f6d78e 100644
--- a/libs/extension/src/parser/parseAttr.ts
+++ b/libs/extension/src/parser/parseAttr.ts
@@ -127,11 +127,9 @@ export function parseHooks(hooks: Hook[]) {
// 不同 hook 的 state 有不同属性,根据是否存在该属性判断 hook 类型
// 采用这种方式是因为要拿到需要的属性值,和后续触发更新,必然要感知 hook 的属性值
// 既然已经感知了属性,就不额外添加属性进行类型判断了
- if ((state as Reducer).stateValue) {
+ if ((state as Reducer).trigger) {
if ((state as Reducer).isUseState) {
parseSubAttr((state as Reducer).stateValue, indentation, 'state', result, hIndex);
- } else {
- parseSubAttr((state as Reducer).stateValue, indentation, 'reducer', result, hIndex);
}
} else if ((state as Ref).current) {
parseSubAttr((state as Ref).current, indentation, 'ref', result, hIndex);
@@ -162,23 +160,31 @@ export function parseVNodeAttrs(vNode: VNode) {
}
// 计算属性的访问顺序
-function calculateAttrAccessPath(item: IAttr, index: number, attrs: IAttr[]) {
+function calculateAttrAccessPath(item: IAttr, index: number, attrs: IAttr[], isHook: boolean) {
let currentIndentation = item.indentation;
const path = [item.name];
+ let hookRootItem: IAttr = item;
for(let i = index - 1; i >= 0; i--) {
const lastItem = attrs[i];
const lastIndentation = lastItem.indentation;
if (lastIndentation < currentIndentation) {
+ hookRootItem = lastItem;
path.push(lastItem.name);
currentIndentation = lastIndentation;
}
}
path.reverse();
+ if (isHook) {
+ if (hookRootItem) {
+ path[0] = hookRootItem.hIndex;
+ } else {
+ console.error('There is a bug, please report');
+ }
+ }
return path;
}
export function buildAttrModifyData(parsedAttrsType: string, attrs: IAttr[], value, item: IAttr, index: number, id: number) {
- const path = calculateAttrAccessPath(item, index, attrs);
let type;
if (parsedAttrsType === 'parsedProps') {
type = ModifyProps;
@@ -186,10 +192,10 @@ export function buildAttrModifyData(parsedAttrsType: string, attrs: IAttr[], val
type = ModifyState;
} else if (parsedAttrsType === 'parsedHooks') {
type = ModifyHooks;
- path[0] = item.hIndex;
} else {
return null;
}
+ const path = calculateAttrAccessPath(item, index, attrs, parsedAttrsType === 'parsedHooks');
return {
id: id,
type: type,
From a0a5f1470a290d0baff1f485a4298956adcf2373 Mon Sep 17 00:00:00 2001
From: * <8>
Date: Mon, 9 May 2022 15:41:30 +0800
Subject: [PATCH 18/26] Match-id-58a32cd0f1a9a827dc84ad9a30326c16ae0d7417
---
libs/extension/src/injector/index.ts | 90 +++++++++++++++---------
libs/extension/src/parser/parseVNode.ts | 5 +-
libs/horizon/index.ts | 1 +
libs/horizon/src/external/devtools.ts | 53 ++++++++++++++
libs/horizon/src/renderer/TreeBuilder.ts | 6 +-
libs/horizon/src/renderer/vnode/VNode.ts | 2 +-
6 files changed, 118 insertions(+), 39 deletions(-)
create mode 100644 libs/horizon/src/external/devtools.ts
diff --git a/libs/extension/src/injector/index.ts b/libs/extension/src/injector/index.ts
index ad8c5714..09f3b6de 100644
--- a/libs/extension/src/injector/index.ts
+++ b/libs/extension/src/injector/index.ts
@@ -6,11 +6,14 @@ import {
RequestComponentAttrs,
ComponentAttrs,
DevToolHook,
- DevToolContentScript, ModifyAttrs, ModifyHooks, ModifyState,
+ DevToolContentScript,
+ ModifyAttrs,
+ ModifyHooks,
+ ModifyState,
+ ModifyProps,
} from '../utils/constants';
import { VNode } from '../../../horizon/src/renderer/vnode/VNode';
import { parseVNodeAttrs } from '../parser/parseAttr';
-import { Reducer } from '../../../horizon/src/renderer/hooks/HookType';
const roots = [];
@@ -22,7 +25,7 @@ function addIfNotInclude(treeRoot: VNode) {
function send() {
const result = roots.reduce((pre, current) => {
- const info = parseTreeRoot(current);
+ const info = parseTreeRoot(helper.travelVNodeTree ,current);
pre.push(info);
return pre;
}, []);
@@ -55,6 +58,34 @@ function parseCompAttrs(id: number) {
postMessage(ComponentAttrs, parsedAttrs);
}
+function calculateNextValue(editValue, value, attrPath) {
+ let nextState;
+ const editValueType = typeof editValue;
+ if (editValueType === 'string' || editValueType === 'undefined' || editValueType === 'boolean') {
+ nextState = value;
+ } else if (editValueType === 'number') {
+ const numValue = Number(value);
+ nextState = isNaN(numValue) ? value : numValue; // 如果能转为数字,转数字,不能转数字,用原值
+ } else if(editValueType === 'object') {
+ if (editValue === null) {
+ nextState = value;
+ } else {
+ const newValue = Array.isArray(editValue) ? [...editValue] : {...editValue};
+ // 遍历读取到直接指向需要修改值的对象
+ let attr = newValue;
+ for(let i = 0; i < attrPath.length - 1; i++) {
+ attr = attr[attrPath[i]];
+ }
+ // 修改对象上的值
+ attr[attrPath[attrPath.length - 1]] = value;
+ nextState = newValue;
+ }
+ } else {
+ console.error('The devTool tried to edit a non-editable value, this is a bug, please report', editValue);
+ }
+ return nextState;
+}
+
function modifyVNodeAttrs(data) {
const {type, id, value, path} = data;
const vNode = queryVNode(id);
@@ -62,49 +93,39 @@ function modifyVNodeAttrs(data) {
console.error('Do not find match vNode, this is a bug, please report us');
return;
}
- if (type === ModifyHooks) {
+ if (type === ModifyProps) {
+ const nextProps = calculateNextValue(vNode.props, value, path);
+ helper.updateProps(vNode, nextProps);
+ } else if (type === ModifyHooks) {
const hooks = vNode.hooks;
const editHook = hooks[path[0]];
- if ((editHook.state as Reducer).trigger) {
- const editState = editHook.state as Reducer;
- const editValue = editState.stateValue;
- const editValueType = typeof editValue;
- if (editValueType === 'string') {
- editState.trigger(value);
- } else if (editValueType === 'number') {
- const numValue = Number(value);
- const targetValue = isNaN(numValue) ? value : numValue; // 如果能转为数字,转数字,不能转数字,用原值
- editState.trigger(targetValue);
- } else if(editValueType === 'object') {
- if (editValue === null) {
- editState.trigger(value);
- } else {
- const newValue = {...editValue};
- // 遍历读取到直接指向需要修改值的对象
- const attrPath = path.slice(1);
- let attr = newValue;
- for(let i = 0; i < attrPath.length - 1; i++) {
- attr = attr[attrPath[i]];
- }
- // 修改对象上的值
- attr[attrPath[attrPath.length - 1]] = value;
- editState.trigger(newValue);
- }
- }
+ const hookInfo = helper.getHookInfo(editHook);
+ if (hookInfo) {
+ const editValue = hookInfo.value;
+ // path 的第一个值指向 hIndex,从第二个值才开始指向具体属性访问路径
+ const nextState = calculateNextValue(editValue, value, path.slice(1));
+ helper.updateHooks(vNode, path[0], nextState);
+ } else {
+ console.error('The devTool tried to edit a non-editable hook, this is a bug, please report', hooks);
}
} else if (type === ModifyState) {
- const instance = vNode.realNode;
- const oldState = instance.state || {};
- const nextState = Object.assign({}, oldState);
+ const oldState = vNode.state || {};
+ const nextState = {...oldState};
let accessRef = nextState;
for(let i = 0; i < path.length - 1; i++) {
accessRef = accessRef[path[i]];
}
accessRef[path[path.length - 1]] = value;
- instance.setState(nextState);
+ helper.updateState(vNode, nextState);
}
}
+let helper;
+
+function init(horizonHelper) {
+ helper = horizonHelper;
+}
+
function injectHook() {
if (window.__HORIZON_DEV_HOOK__) {
return;
@@ -112,6 +133,7 @@ function injectHook() {
Object.defineProperty(window, '__HORIZON_DEV_HOOK__', {
enumerable: false,
value: {
+ init,
addIfNotInclude,
send,
deleteVNode,
diff --git a/libs/extension/src/parser/parseVNode.ts b/libs/extension/src/parser/parseVNode.ts
index 314e80e4..a70e7189 100644
--- a/libs/extension/src/parser/parseVNode.ts
+++ b/libs/extension/src/parser/parseVNode.ts
@@ -1,4 +1,3 @@
-import { travelVNodeTree } from '../../../horizon/src/renderer/vnode/VNodeUtils';
import { VNode } from '../../../horizon/src/renderer/vnode/VNode';
import { ClassComponent, FunctionComponent } from '../../../horizon/src/renderer/vnode/VNodeTags';
@@ -32,7 +31,7 @@ function getParentUserComponent(node: VNode) {
return parent;
}
-function parseTreeRoot(treeRoot: VNode) {
+function parseTreeRoot(travelVNodeTree, treeRoot: VNode) {
const result: any[] = [];
travelVNodeTree(treeRoot, (node: VNode) => {
const tag = node.tag;
@@ -57,7 +56,7 @@ function parseTreeRoot(treeRoot: VNode) {
VNodeToIdMap.set(node, id);
IdToVNodeMap.set(id, node);
}
- }, null, treeRoot, null);
+ });
return result;
}
diff --git a/libs/horizon/index.ts b/libs/horizon/index.ts
index cd194973..7004f914 100644
--- a/libs/horizon/index.ts
+++ b/libs/horizon/index.ts
@@ -13,6 +13,7 @@ import { createContext } from './src/renderer/components/context/CreateContext';
import { lazy } from './src/renderer/components/Lazy';
import { forwardRef } from './src/renderer/components/ForwardRef';
import { memo } from './src/renderer/components/Memo';
+import './src/external/devtools';
import {
useCallback,
diff --git a/libs/horizon/src/external/devtools.ts b/libs/horizon/src/external/devtools.ts
new file mode 100644
index 00000000..c9dcba9a
--- /dev/null
+++ b/libs/horizon/src/external/devtools.ts
@@ -0,0 +1,53 @@
+import { travelVNodeTree } from '../renderer/vnode/VNodeUtils';
+import { Hook, Reducer, Ref } from '../renderer/hooks/HookType';
+import { VNode } from '../renderer/vnode/VNode';
+import { launchUpdateFromVNode } from '../renderer/TreeBuilder';
+
+export const helper = {
+ travelVNodeTree: (rootVNode, fun) => {
+ travelVNodeTree(rootVNode, fun, null, rootVNode, null);
+ },
+ // 获取 hook 名,hIndex值和存储的值
+ // 目前只处理 useState和useRef
+ getHookInfo:(hook: Hook) => {
+ const { hIndex, state } = hook;
+ if ((state as Reducer).trigger) {
+ if ((state as Reducer).isUseState) {
+ return {name: 'state', hIndex, value: (state as Reducer).stateValue};
+ }
+ } else if ((state as Ref).current) {
+ return {name: 'ref', hIndex, value: (state as Ref).current};
+ }
+ return null;
+ },
+ updateProps: (vNode: VNode, props: any) =>{
+ vNode.devProps = props;
+ launchUpdateFromVNode(vNode);
+ },
+ updateState: (vNode: VNode, nextState) => {
+ const instance = vNode.realNode;
+ instance.setState(nextState);
+ },
+ updateHooks: (vNode: VNode, hIndex, nextState) => {
+ const hooks = vNode.hooks;
+ if (hooks) {
+ const editHook = hooks[hIndex];
+ const editState = editHook.state as Reducer;
+ // 暂时只支持更新 useState 的值
+ if (editState.trigger && editState.isUseState) {
+ editState.trigger(nextState);
+ }
+ } else {
+ console.error('Target vNode is not a hook vNode: ', vNode);
+ }
+ },
+};
+
+function injectUpdater() {
+ const hook = window.__HORIZON_DEV_HOOK__;
+ if (hook) {
+ hook.init(helper);
+ }
+}
+
+injectUpdater();
diff --git a/libs/horizon/src/renderer/TreeBuilder.ts b/libs/horizon/src/renderer/TreeBuilder.ts
index 394319e0..8fff0435 100644
--- a/libs/horizon/src/renderer/TreeBuilder.ts
+++ b/libs/horizon/src/renderer/TreeBuilder.ts
@@ -236,7 +236,11 @@ function buildVNodeTree(treeRoot: VNode) {
// 重置环境变量,为重新进行深度遍历做准备
resetProcessingVariables(startVNode);
-
+ // devProps 用于插件手动更新props值
+ if (startVNode.devProps !== undefined) {
+ startVNode.props = startVNode.devProps;
+ startVNode.devProps = undefined;
+ }
while (processing !== null) {
try {
while (processing !== null) {
diff --git a/libs/horizon/src/renderer/vnode/VNode.ts b/libs/horizon/src/renderer/vnode/VNode.ts
index 29fb008b..812d2b8e 100644
--- a/libs/horizon/src/renderer/vnode/VNode.ts
+++ b/libs/horizon/src/renderer/vnode/VNode.ts
@@ -70,7 +70,7 @@ export class VNode {
oldRef: RefType | ((handle: any) => void) | null = null;
oldChild: VNode | null = null;
promiseResolve: boolean; // suspense的promise是否resolve
-
+ devProps: any; // 用于dev插件临时保存更新props值
suspenseState: SuspenseState;
path = ''; // 保存从根到本节点的路径
From e343eab0f277af8a3dba7538240e589aab25fa90 Mon Sep 17 00:00:00 2001
From: * <8>
Date: Mon, 9 May 2022 16:47:25 +0800
Subject: [PATCH 19/26] Match-id-2a32f3df93794ac555089f7efbae08d7c1ed6676
---
.../src/components/ComponentInfo.tsx | 25 +++++++++++++++----
1 file changed, 20 insertions(+), 5 deletions(-)
diff --git a/libs/extension/src/components/ComponentInfo.tsx b/libs/extension/src/components/ComponentInfo.tsx
index 328642bd..b3b09d4a 100644
--- a/libs/extension/src/components/ComponentInfo.tsx
+++ b/libs/extension/src/components/ComponentInfo.tsx
@@ -70,9 +70,9 @@ function ComponentAttr({ attrsName, attrsType, attrs, id }: {
{hasChild && }
{`${item.name}`}
{' :'}
- {item.type === 'string' || item.type === 'number' ? (
- {
const nextAttrs = [...editableAttrs];
@@ -93,8 +93,23 @@ function ComponentAttr({ attrsName, attrsType, attrs, id }: {
}
}}
/>
- ) : (
- {item.value}
+ : (item.type === 'boolean'
+ ? {
+ const nextAttrs = [...editableAttrs];
+ const nextItem = {...item};
+ nextItem.value = event.target.checked;
+ nextAttrs[index] = nextItem;
+ setEditableAttrs(nextAttrs);
+ if (!isDev) {
+ const data = buildAttrModifyData(attrsType,attrs, nextItem.value,item, index, id);
+ postMessageToBackground(ModifyAttrs, data);
+ }
+ }}/>
+ : {item.value}
)}
);
From d73ab8fcb0e2f291da130c3dc7708400b1a9336b Mon Sep 17 00:00:00 2001
From: * <8>
Date: Wed, 11 May 2022 14:48:13 +0800
Subject: [PATCH 20/26] Match-id-587601ebdfc3d3ef7e317bb42380ca4a550725bd
---
libs/extension/src/devtools/mock.ts | 9 +++++----
.../src/devtools/mockPage/MockFunctionComponent.tsx | 4 +++-
libs/extension/src/devtools/mockPage/index.tsx | 6 ++++--
3 files changed, 12 insertions(+), 7 deletions(-)
diff --git a/libs/extension/src/devtools/mock.ts b/libs/extension/src/devtools/mock.ts
index 3a3eef88..1c7447dc 100644
--- a/libs/extension/src/devtools/mock.ts
+++ b/libs/extension/src/devtools/mock.ts
@@ -5,8 +5,9 @@
import { parseAttr } from '../parser/parseAttr';
import parseTreeRoot from '../parser/parseVNode';
-import { VNode } from './../../../horizon/src/renderer/vnode/VNode';
-import { FunctionComponent, ClassComponent } from './../../../horizon/src/renderer/vnode/VNodeTags';
+import { VNode } from '../../../horizon/src/renderer/vnode/VNode';
+import { FunctionComponent, ClassComponent } from '../../../horizon/src/renderer/vnode/VNodeTags';
+import { helper } from '../../../horizon/src/external/devtools';
const mockComponentNames = ['Apple', 'Pear', 'Banana', 'Orange', 'Jenny', 'Kiwi', 'Coconut'];
@@ -50,7 +51,7 @@ addOneThousandNode(tree);
/**
* 将mock数据转变为 VNode 树
- *
+ *
* @param node 树节点
* @param vNode VNode节点
*/
@@ -78,7 +79,7 @@ function getMockVNodeTree(node: IMockTree, vNode: VNode) {
const rootVNode = MockVNode(tree.tag);
getMockVNodeTree(tree, rootVNode);
-export const mockParsedVNodeData = parseTreeRoot(rootVNode);
+export const mockParsedVNodeData = parseTreeRoot(helper.travelVNodeTree, rootVNode);
const mockState = {
str: 'jenny',
diff --git a/libs/extension/src/devtools/mockPage/MockFunctionComponent.tsx b/libs/extension/src/devtools/mockPage/MockFunctionComponent.tsx
index 41437e38..9495cd60 100644
--- a/libs/extension/src/devtools/mockPage/MockFunctionComponent.tsx
+++ b/libs/extension/src/devtools/mockPage/MockFunctionComponent.tsx
@@ -17,6 +17,7 @@ function reducer(state, action) {
export default function MockFunctionComponent(props) {
const [state, dispatch] = useReducer(reducer, initialState);
const [age, setAge] = useState(0);
+ const [name, setName] = useState({test: 1});
const domRef = useRef
();
const objRef = useRef({ str: 'string' });
const context = useContext(MockContext);
@@ -26,6 +27,7 @@ export default function MockFunctionComponent(props) {
return (
age: {age}
+ name: {name.test}
count: {props.count}
@@ -33,4 +35,4 @@ export default function MockFunctionComponent(props) {
{context.ctx}
);
-}
\ No newline at end of file
+}
diff --git a/libs/extension/src/devtools/mockPage/index.tsx b/libs/extension/src/devtools/mockPage/index.tsx
index 57c96b44..f59908ff 100644
--- a/libs/extension/src/devtools/mockPage/index.tsx
+++ b/libs/extension/src/devtools/mockPage/index.tsx
@@ -1,4 +1,4 @@
-import { render } from 'horizon';
+import { render, useState } from 'horizon';
import MockClassComponent from './MockClassComponent';
import MockFunctionComponent from './MockFunctionComponent';
import { MockContext } from './MockContext';
@@ -6,11 +6,13 @@ import { MockContext } from './MockContext';
const root = document.createElement('div');
document.body.append(root);
function App() {
+ const [count, setCount] = useState(12);
return (
+
-
+
abc
From 978e25be7385ad3e1e41e4b1ec06c54ee30f4320 Mon Sep 17 00:00:00 2001
From: * <8>
Date: Wed, 11 May 2022 14:49:46 +0800
Subject: [PATCH 21/26] Match-id-0bab5f65150428dd661a5ee7c0828458fcf1e6d6
---
libs/horizon/src/external/devtools.ts | 31 +++++++++++++++++++++++++++
1 file changed, 31 insertions(+)
diff --git a/libs/horizon/src/external/devtools.ts b/libs/horizon/src/external/devtools.ts
index c9dcba9a..38167db5 100644
--- a/libs/horizon/src/external/devtools.ts
+++ b/libs/horizon/src/external/devtools.ts
@@ -2,6 +2,7 @@ import { travelVNodeTree } from '../renderer/vnode/VNodeUtils';
import { Hook, Reducer, Ref } from '../renderer/hooks/HookType';
import { VNode } from '../renderer/vnode/VNode';
import { launchUpdateFromVNode } from '../renderer/TreeBuilder';
+import { DomComponent } from '../renderer/vnode/VNodeTags';
export const helper = {
travelVNodeTree: (rootVNode, fun) => {
@@ -41,6 +42,36 @@ export const helper = {
console.error('Target vNode is not a hook vNode: ', vNode);
}
},
+ getComponentInfo: (vNode: VNode) => {
+ const { props, state, hooks } = vNode;
+ const info:any = {};
+ if (props && Object.keys(props).length !== 0) {
+ info['Props'] = props;
+ }
+ if (state && Object.keys(state).length !== 0) {
+ info['State'] = state;
+ }
+ if (hooks && hooks.length !== 0) {
+ const logHookInfo: any[] = [];
+ hooks.forEach((hook) =>{
+ const state = hook.state as Reducer;
+ if (state.trigger && state.isUseState) {
+ logHookInfo.push(state.stateValue);
+ }
+ });
+ info['Hooks'] = logHookInfo;
+ }
+ travelVNodeTree(vNode, (node: VNode) => {
+ if (node.tag === DomComponent) {
+ // 找到组件的第一个dom元素,返回它所在父节点的全部子节点
+ const dom = node.realNode;
+ info['Nodes'] = dom?.parentNode?.childNodes;
+ return true;
+ }
+ return false;
+ }, null, vNode, null);
+ return info;
+ },
};
function injectUpdater() {
From c08ff1ef54b66e5437fd37678604f34ab8e78a11 Mon Sep 17 00:00:00 2001
From: * <8>
Date: Wed, 11 May 2022 14:51:19 +0800
Subject: [PATCH 22/26] Match-id-43f11e22f0d968145c3c3c94eaa5c2be1ef41e0b
---
libs/extension/src/parser/parseAttr.ts | 22 ++++++++--------------
1 file changed, 8 insertions(+), 14 deletions(-)
diff --git a/libs/extension/src/parser/parseAttr.ts b/libs/extension/src/parser/parseAttr.ts
index 34f6d78e..0719771d 100644
--- a/libs/extension/src/parser/parseAttr.ts
+++ b/libs/extension/src/parser/parseAttr.ts
@@ -1,5 +1,5 @@
-import { Hook, Reducer, Ref } from '../../../horizon/src/renderer/hooks/HookType';
+import { Hook } from '../../../horizon/src/renderer/hooks/HookType';
import { ModifyHooks, ModifyProps, ModifyState } from '../utils/constants';
import { VNode } from '../../../horizon/src/renderer/vnode/VNode';
import { ClassComponent, FunctionComponent } from '../../../horizon/src/renderer/vnode/VNodeTags';
@@ -119,26 +119,20 @@ export function parseAttr(rootAttr: any) {
return result;
}
-export function parseHooks(hooks: Hook[]) {
+export function parseHooks(hooks: Hook[], getHookInfo) {
const result: IAttr[] = [];
const indentation = 0;
hooks.forEach(hook => {
- const { hIndex, state } = hook;
- // 不同 hook 的 state 有不同属性,根据是否存在该属性判断 hook 类型
- // 采用这种方式是因为要拿到需要的属性值,和后续触发更新,必然要感知 hook 的属性值
- // 既然已经感知了属性,就不额外添加属性进行类型判断了
- if ((state as Reducer).trigger) {
- if ((state as Reducer).isUseState) {
- parseSubAttr((state as Reducer).stateValue, indentation, 'state', result, hIndex);
- }
- } else if ((state as Ref).current) {
- parseSubAttr((state as Ref).current, indentation, 'ref', result, hIndex);
+ const hookInfo = getHookInfo(hook);
+ if (hookInfo) {
+ const {name, hIndex, value} = hookInfo;
+ parseSubAttr(value, indentation, name, result, hIndex);
}
});
return result;
}
-export function parseVNodeAttrs(vNode: VNode) {
+export function parseVNodeAttrs(vNode: VNode, getHookInfo) {
const tag = vNode.tag;
if (tag === ClassComponent) {
const { props, state } = vNode;
@@ -151,7 +145,7 @@ export function parseVNodeAttrs(vNode: VNode) {
} else if (tag === FunctionComponent) {
const { props, hooks } = vNode;
const parsedProps = parseAttr(props);
- const parsedHooks = parseHooks(hooks);
+ const parsedHooks = parseHooks(hooks, getHookInfo);
return {
parsedProps,
parsedHooks,
From e730cb1b0e66b011b0b33e37df8d79873fffdf2c Mon Sep 17 00:00:00 2001
From: * <8>
Date: Wed, 11 May 2022 14:51:35 +0800
Subject: [PATCH 23/26] Match-id-c410ccc720bdb9cc4ad9ab36fa600f63b8017a67
---
libs/extension/readme.md | 71 +++++++++++++++++++++++++++++++++++-----
1 file changed, 63 insertions(+), 8 deletions(-)
diff --git a/libs/extension/readme.md b/libs/extension/readme.md
index d725a5f8..fecc8f4f 100644
--- a/libs/extension/readme.md
+++ b/libs/extension/readme.md
@@ -1,3 +1,60 @@
+## 为什么要做 devTool 插件
+让Horizon开发者获得更好的开发体验,获取准确的组件树结构、状态信息和真实dom对应关系。
+
+## 上下文关系
+devTool功能的实现依赖浏览器 extension 开放的能力,用于绘制展示组件信息和获取真实 dom 元素。同时也需要 Horizon 提供相关接口获取组件树信息和提供调试能力。
+
+## 目标
+1. 查看组件树结构并支持过滤
+2. 查看组件与真实dom的关系
+3. 查看组件props, state, hooks 等信息
+4. 调试单个组件及其子组件
+5. 支持状态管理解决方案调试
+
+## 和 react devTool 能力对比
+||react | Horizon|
+|-|-|-|
+|查看组件树|Y |Y |
+|查看真实DOM|Y|Y|
+|查看组件信息|Y|Y|
+|调试能力|Y| Y |
+|性能调试|Y|N|
+|解析Hook名|Y|N|
+|状态管理解决方案调试|N|Y|
+
+## 架构草图
+```plantuml
+@startuml
+package "Horizon" {
+ [U I]
+ [Helper]
+}
+
+package "Script Content" {
+ [GlobalHook]
+ [MessageHandler]
+ [Parser]
+
+}
+package "Browser" {
+ [Background]
+ [Panel]
+}
+
+[GlobalHook] <-- [U I]
+[GlobalHook] --> [MessageHandler]
+[Helper] <-- [MessageHandler]
+[Helper] --> [U I]
+[MessageHandler] <--> [Background]
+[Background] <--> [Panel]
+[Parser] --> [MessageHandler]
+@enduml
+```
+
+#### 说明
+Helper: 提供接口给插件操控组件以及提供工具方法。
+Parser: 负责将组件树结构和组件信息解析成特定的数据结构,供Panel展示。
+
## 文件清单说明:
devtools_page: devtool主页面
default_popup: 拓展图标点击时弹窗页面
@@ -15,9 +72,6 @@ Optional: Feel free to dock the developer tools again if you had undocked it at
## 全局变量注入
通过content_scripts在document初始化时给页面添加script脚本,在新添加的脚本中给window注入全局变量
-## horizon页面判断
-在页面完成渲染后往全局变量中添加信息,并传递 tabId 给 background 告知这是 horizon 页面
-
## 通信方式:
```mermaid
sequenceDiagram
@@ -56,7 +110,8 @@ type passData = {
payload: {
type: string,
data: any,
- }
+ },
+ from: string,
}
```
@@ -67,14 +122,14 @@ type passData = {
- 整个页面刷新
- devTools触发组件属性更新
-## 对 hook 类型的判断和值的获取
-Horizon 是一个底层框架,在 Horizon 与插件的交互过程中,我们不希望 Horizon 额外的增加一些代码和接口给插件使用,这可能会影响到 Horizon 的性能。
-所以我们决定直接感知 hook 的属性值,通过其属性值判断 hook 类型,并直接调用 Reducer 的 trigger 函数触发更新。
+
+## 对组件的操作
+我们希望插件和Horizon能够尽量解耦,所以Horizon提供了Helper注入给插件,提供相关方法操作组件。
## 触发组件更新方式
- 类组件的state:调用实例的 setState 函数触发更新
- 类组件的props:浅复制props后更新props值并调用 forceUpdate 触发更新
-- 函数组件的props:
+- 函数组件的props:新增了devProps属性,在特定时刻重新给props赋值,触发更新
- 函数组件的state:调用 useState 函数触发更新
## VNode的清理
From f257d88e1303657c7c407324568d2505af92a74d Mon Sep 17 00:00:00 2001
From: * <8>
Date: Wed, 11 May 2022 14:51:48 +0800
Subject: [PATCH 24/26] Match-id-56752e7671c54bfcb5e26e38b12b422fa6754586
---
libs/extension/src/svgs/Copy.tsx | 8 --------
1 file changed, 8 deletions(-)
delete mode 100644 libs/extension/src/svgs/Copy.tsx
diff --git a/libs/extension/src/svgs/Copy.tsx b/libs/extension/src/svgs/Copy.tsx
deleted file mode 100644
index 483d9ece..00000000
--- a/libs/extension/src/svgs/Copy.tsx
+++ /dev/null
@@ -1,8 +0,0 @@
-
-export default function Copy() {
- return (
-
- );
-}
From 28c382aaed86c0430f1fe11b491a3eea5e6bfb26 Mon Sep 17 00:00:00 2001
From: * <8>
Date: Wed, 11 May 2022 15:01:41 +0800
Subject: [PATCH 25/26] Match-id-3623217ead4919cd5624a24a69e6e6612df52506
---
.../extension/src/components/ComponentInfo.tsx | 14 +++++++-------
.../src/components/ComponentsInfo.less | 4 +++-
libs/extension/src/injector/index.ts | 18 +++++++++++++++++-
libs/extension/src/utils/constants.ts | 5 +++++
4 files changed, 32 insertions(+), 9 deletions(-)
diff --git a/libs/extension/src/components/ComponentInfo.tsx b/libs/extension/src/components/ComponentInfo.tsx
index b3b09d4a..844da1ba 100644
--- a/libs/extension/src/components/ComponentInfo.tsx
+++ b/libs/extension/src/components/ComponentInfo.tsx
@@ -1,13 +1,12 @@
import styles from './ComponentsInfo.less';
import Eye from '../svgs/Eye';
import Debug from '../svgs/Debug';
-import Copy from '../svgs/Copy';
import Triangle from '../svgs/Triangle';
import { useState, useEffect } from 'horizon';
import { IData } from './VTree';
import { buildAttrModifyData, IAttr } from '../parser/parseAttr';
import { postMessageToBackground } from '../panelConnection';
-import { ModifyAttrs } from '../utils/constants';
+import { InspectDom, LogComponentData, ModifyAttrs } from '../utils/constants';
type IComponentInfo = {
name: string;
@@ -122,9 +121,6 @@ function ComponentAttr({ attrsName, attrsType, attrs, id }: {
{attrsName}
-
-
-
{showAttr}
@@ -141,10 +137,14 @@ export default function ComponentInfo({ name, attrs, parents, id, onClickParent
{name}
-
+ {
+ postMessageToBackground(InspectDom, id);
+ }}>
-
+ {
+ postMessageToBackground(LogComponentData, id);
+ }}>
>}
diff --git a/libs/extension/src/components/ComponentsInfo.less b/libs/extension/src/components/ComponentsInfo.less
index 853d4209..d1fd0de1 100644
--- a/libs/extension/src/components/ComponentsInfo.less
+++ b/libs/extension/src/components/ComponentsInfo.less
@@ -20,11 +20,13 @@
.eye {
flex: 0 0 1rem;
padding-right: 1rem;
+ cursor: pointer;
}
.debug {
flex: 0 0 1rem;
padding-right: 1rem;
+ cursor: pointer;
}
}
@@ -71,7 +73,7 @@
}
.attrValue {
- margin-left: 4px;
+ margin: 0 0 0 4px;
}
}
}
diff --git a/libs/extension/src/injector/index.ts b/libs/extension/src/injector/index.ts
index 09f3b6de..940f0ab9 100644
--- a/libs/extension/src/injector/index.ts
+++ b/libs/extension/src/injector/index.ts
@@ -11,6 +11,8 @@ import {
ModifyHooks,
ModifyState,
ModifyProps,
+ InspectDom,
+ LogComponentData
} from '../utils/constants';
import { VNode } from '../../../horizon/src/renderer/vnode/VNode';
import { parseVNodeAttrs } from '../parser/parseAttr';
@@ -54,7 +56,7 @@ function parseCompAttrs(id: number) {
console.error('Do not find match vNode, this is a bug, please report us');
return;
}
- const parsedAttrs = parseVNodeAttrs(vNode);
+ const parsedAttrs = parseVNodeAttrs(vNode, helper.getHookInfo);
postMessage(ComponentAttrs, parsedAttrs);
}
@@ -120,6 +122,14 @@ function modifyVNodeAttrs(data) {
}
}
+function logComponentData(id: number) {
+ const vNode = queryVNode(id);
+ if (vNode) {
+ const info = helper.getComponentInfo(vNode);
+ console.log('Component Info: ', info);
+ }
+}
+
let helper;
function init(horizonHelper) {
@@ -154,6 +164,12 @@ function injectHook() {
parseCompAttrs(data);
} else if (type === ModifyAttrs) {
modifyVNodeAttrs(data);
+ } else if (type === InspectDom) {
+ console.log(data);
+ } else if (type === LogComponentData) {
+ logComponentData(data);
+ } else {
+ console.warn('unknown command', type);
}
}
});
diff --git a/libs/extension/src/utils/constants.ts b/libs/extension/src/utils/constants.ts
index 1be5c8dc..e448bc2b 100644
--- a/libs/extension/src/utils/constants.ts
+++ b/libs/extension/src/utils/constants.ts
@@ -19,6 +19,11 @@ export const ModifyState = 'modify state';
export const ModifyHooks = 'modify hooks';
+export const InspectDom = 'inspect component dom';
+
+export const LogComponentData = 'log component data';
+
+export const CopyComponentAttr = 'copy component attr';
// 传递消息来源标志
export const DevToolPanel = 'dev tool panel';
From cc4a8d2a1d78fb42fe9fe9e187e3383055a8b1a1 Mon Sep 17 00:00:00 2001
From: * <8>
Date: Wed, 11 May 2022 15:02:07 +0800
Subject: [PATCH 26/26] Match-id-6e130b06d6dfa189100fb7eb3a42f10b22fd6913
---
libs/extension/package.json | 24 ++++++++++++------------
1 file changed, 12 insertions(+), 12 deletions(-)
diff --git a/libs/extension/package.json b/libs/extension/package.json
index ade577c7..45f5cb8a 100644
--- a/libs/extension/package.json
+++ b/libs/extension/package.json
@@ -15,21 +15,21 @@
"license": "ISC",
"devDependencies": {
"@babel/core": "7.12.3",
- "@babel/plugin-proposal-class-properties": "^7.16.7",
+ "@babel/plugin-proposal-class-properties": "7.16.7",
"@babel/preset-env": "7.12.1",
"@babel/preset-react": "7.12.1",
- "@babel/preset-typescript": "^7.16.7",
- "@types/jest": "^27.4.1",
+ "@babel/preset-typescript": "7.16.7",
+ "@types/jest": "27.4.1",
"babel-loader": "8.1.0",
- "css-loader": "^6.7.1",
- "html-webpack-plugin": "^5.5.0",
- "jest": "^27.5.1",
- "less": "^4.1.2",
- "less-loader": "^10.2.0",
- "style-loader": "^3.3.1",
- "ts-jest": "^27.1.4",
- "webpack": "^5.70.0",
- "webpack-cli": "^4.9.2",
+ "css-loader": "6.7.1",
+ "html-webpack-plugin": "5.5.0",
+ "jest": "27.5.1",
+ "less": "4.1.2",
+ "less-loader": "10.2.0",
+ "style-loader": "3.3.1",
+ "ts-jest": "27.1.4",
+ "webpack": "5.70.0",
+ "webpack-cli": "4.9.2",
"webpack-dev-server": "^4.7.4"
}
}