From 772917e9e5a642d378e53a9ed52a583c0f96a530 Mon Sep 17 00:00:00 2001
From: * <8>
Date: Sun, 24 Apr 2022 16:51:00 +0800
Subject: [PATCH 01/34] 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/34] 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/34] 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/34] 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/34] 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/34] 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/34] 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/34] 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/34] 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/34] 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/34] 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/34] 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/34] 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/34] 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/34] 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/34] 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/34] 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/34] 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/34] 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/34] 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/34] 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/34] 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/34] 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/34] 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/34] 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/34] 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"
}
}
From 0ae3910553f68ffc9024f43937d7d51eca4c205a Mon Sep 17 00:00:00 2001
From: * <8>
Date: Wed, 11 May 2022 15:54:47 +0800
Subject: [PATCH 27/34] Match-id-6cde1d60850e4a64e0d4c16b2dad41f9f29fc8a5
---
libs/extension/package.json | 2 +-
libs/extension/src/panel/panel.html | 9 ++++-----
libs/extension/webpack.config.js | 16 ++++++++++++++++
libs/extension/webpack.dev.js | 2 +-
4 files changed, 22 insertions(+), 7 deletions(-)
diff --git a/libs/extension/package.json b/libs/extension/package.json
index 45f5cb8a..6562c624 100644
--- a/libs/extension/package.json
+++ b/libs/extension/package.json
@@ -7,7 +7,7 @@
"build": "webpack --config ./webpack.config.js",
"watch": "webpack --config ./webpack.config.js --watch",
"build-dev": "webpack --config ./webpack.dev.js",
- "start": "webpack serve --config ./webpack.dev.js ",
+ "start": "npm run build && webpack serve --config ./webpack.dev.js ",
"test": "jest"
},
"keywords": [],
diff --git a/libs/extension/src/panel/panel.html b/libs/extension/src/panel/panel.html
index ae944454..705a2536 100644
--- a/libs/extension/src/panel/panel.html
+++ b/libs/extension/src/panel/panel.html
@@ -1,10 +1,8 @@
-
+
- Horizon
-
+
-
-
+
+
diff --git a/libs/extension/webpack.config.js b/libs/extension/webpack.config.js
index 25dab94b..86450201 100644
--- a/libs/extension/webpack.config.js
+++ b/libs/extension/webpack.config.js
@@ -1,5 +1,21 @@
const path = require('path');
const webpack = require('webpack');
+const fs = require('fs');
+
+function handleBuildDir() {
+ const staticDir = path.join(__dirname, 'build');
+ console.log('staticDir: ', staticDir);
+ const isBuildExist = fs.existsSync(staticDir);
+ console.log('isBuildExist: ', isBuildExist);
+ if (!isBuildExist) {
+ fs.mkdirSync(staticDir);
+ }
+ fs.copyFileSync(path.join(__dirname, 'src', 'panel', 'panel.html'),path.join(staticDir, 'panel.html'));
+ fs.copyFileSync(path.join(__dirname, 'src', 'main', 'main.html'),path.join(staticDir, 'main.html'));
+ fs.copyFileSync(path.join(__dirname, 'src', 'manifest.json'),path.join(staticDir, 'manifest.json'));
+}
+handleBuildDir();
+
const config = {
entry: {
diff --git a/libs/extension/webpack.dev.js b/libs/extension/webpack.dev.js
index b3332077..fc29852f 100644
--- a/libs/extension/webpack.dev.js
+++ b/libs/extension/webpack.dev.js
@@ -47,7 +47,7 @@ module.exports = {
},
devServer: {
static: {
- directory: path.join(__dirname, 'dist'),
+ directory: path.join(__dirname, 'build'),
},
open: 'panel.html',
port: 9000,
From 5fa9f49496e2f18f450ad887bcace2a7eca33c9b Mon Sep 17 00:00:00 2001
From: * <8>
Date: Thu, 12 May 2022 22:12:27 +0800
Subject: [PATCH 28/34] Match-id-0aa7af9b42fd69fca90b5f8d9b55d3a19e3234fe
---
libs/extension/src/background/index.ts | 1 +
libs/extension/src/injector/index.ts | 2 ++
libs/horizon/src/external/devtools.ts | 2 +-
libs/horizon/src/renderer/TreeBuilder.ts | 6 ++++++
4 files changed, 10 insertions(+), 1 deletion(-)
diff --git a/libs/extension/src/background/index.ts b/libs/extension/src/background/index.ts
index 6a8c4591..01e455f8 100644
--- a/libs/extension/src/background/index.ts
+++ b/libs/extension/src/background/index.ts
@@ -51,6 +51,7 @@ chrome.runtime.onMessage.addListener(function (message, sender, sendResponse) {
changeSource(message, DevToolBackground);
connections[tabId].postMessage(message);
} else {
+ // TODO: 如果查询失败,发送 chrome message,请求 panel 主动建立连接
console.log('Tab not found in connection list.');
}
} else {
diff --git a/libs/extension/src/injector/index.ts b/libs/extension/src/injector/index.ts
index 940f0ab9..73692427 100644
--- a/libs/extension/src/injector/index.ts
+++ b/libs/extension/src/injector/index.ts
@@ -134,6 +134,7 @@ let helper;
function init(horizonHelper) {
helper = horizonHelper;
+ window.__HORIZON_DEV_HOOK__.isInit = true;
}
function injectHook() {
@@ -144,6 +145,7 @@ function injectHook() {
enumerable: false,
value: {
init,
+ isInit: false,
addIfNotInclude,
send,
deleteVNode,
diff --git a/libs/horizon/src/external/devtools.ts b/libs/horizon/src/external/devtools.ts
index 38167db5..10e70e80 100644
--- a/libs/horizon/src/external/devtools.ts
+++ b/libs/horizon/src/external/devtools.ts
@@ -74,7 +74,7 @@ export const helper = {
},
};
-function injectUpdater() {
+export function injectUpdater() {
const hook = window.__HORIZON_DEV_HOOK__;
if (hook) {
hook.init(helper);
diff --git a/libs/horizon/src/renderer/TreeBuilder.ts b/libs/horizon/src/renderer/TreeBuilder.ts
index 8fff0435..923b4275 100644
--- a/libs/horizon/src/renderer/TreeBuilder.ts
+++ b/libs/horizon/src/renderer/TreeBuilder.ts
@@ -36,6 +36,7 @@ import {
updateShouldUpdateOfTree
} from './vnode/VNodeShouldUpdate';
import { getPathArr } from './utils/vNodePath';
+import { injectUpdater } from '../external/devtools';
// 不可恢复错误
let unrecoverableErrorDuringBuild: any = null;
@@ -281,6 +282,11 @@ function renderFromRoot(treeRoot) {
if (window.__HORIZON_DEV_HOOK__) {
const hook = window.__HORIZON_DEV_HOOK__;
+ // injector.js 可能在 Horizon 代码之后加载,此时无 __HORIZON_DEV_HOOK__ 全局变量
+ // Horizon 代码初次加载时不会初始化 helper
+ if (!hook.isInit) {
+ injectUpdater();
+ }
hook.addIfNotInclude(treeRoot);
hook.send(treeRoot);
}
From c3fac65f7bbcc36f1ecd6e2c188ff36a1b41ff2a Mon Sep 17 00:00:00 2001
From: * <8>
Date: Fri, 13 May 2022 15:56:46 +0800
Subject: [PATCH 29/34] Match-id-679b9040186b30d3993cd98c0212d00848908140
---
libs/horizon/src/event/ListenerGetter.ts | 37 +-
package.json | 1 +
.../__tests__/EventTest/MouseEvent.test.js | 70 +-
scripts/gen3rdLib.js | 4 +-
scripts/horizon3rdTemplate.ejs | 17568 ++++++++++++++++
5 files changed, 17630 insertions(+), 50 deletions(-)
create mode 100644 scripts/horizon3rdTemplate.ejs
diff --git a/libs/horizon/src/event/ListenerGetter.ts b/libs/horizon/src/event/ListenerGetter.ts
index 53e14ee9..d6860f35 100644
--- a/libs/horizon/src/event/ListenerGetter.ts
+++ b/libs/horizon/src/event/ListenerGetter.ts
@@ -1,14 +1,32 @@
-import {VNode} from '../renderer/Types';
-import {DomComponent} from '../renderer/vnode/VNodeTags';
-import {EVENT_TYPE_ALL, EVENT_TYPE_CAPTURE, EVENT_TYPE_BUBBLE} from './const';
-import {AnyNativeEvent, ListenerUnitList} from './Types';
+import { VNode } from '../renderer/Types';
+import { DomComponent } from '../renderer/vnode/VNodeTags';
+import { EVENT_TYPE_ALL, EVENT_TYPE_CAPTURE, EVENT_TYPE_BUBBLE } from './const';
+import { AnyNativeEvent, ListenerUnitList } from './Types';
+
+// 从vnode属性中获取事件listener
+function getListenerFromVNode(vNode: VNode, eventName: string): Function | null {
+ const props = vNode.props;
+ const mouseEvents = ['onClick', 'onDoubleClick', 'onMouseDown', 'onMouseMove', 'onMouseUp', 'onMouseEnter'];
+ const formElements = ['button', 'input', 'select', 'textarea'];
+
+ // 是否应该阻止禁用的表单元素触发鼠标事件
+ const shouldPreventMouseEvent =
+ mouseEvents.includes(eventName) && props.disabled && formElements.includes(vNode.type);
+
+ const listener = props[eventName];
+ if (shouldPreventMouseEvent) {
+ return null;
+ } else {
+ return listener;
+ }
+}
// 获取监听事件
export function getListenersFromTree(
targetVNode: VNode | null,
horizonEvtName: string | null,
nativeEvent: AnyNativeEvent,
- eventType: string,
+ eventType: string
): ListenerUnitList {
if (!horizonEvtName) {
return [];
@@ -20,11 +38,11 @@ export function getListenersFromTree(
// 从目标节点到根节点遍历获取listener
while (vNode !== null) {
- const {realNode, tag} = vNode;
+ const { realNode, tag } = vNode;
if (tag === DomComponent && realNode !== null) {
if (eventType === EVENT_TYPE_ALL || eventType === EVENT_TYPE_CAPTURE) {
const captureName = horizonEvtName + EVENT_TYPE_CAPTURE;
- const captureListener = vNode.props[captureName];
+ const captureListener = getListenerFromVNode(vNode, captureName);
if (captureListener) {
listeners.unshift({
vNode,
@@ -36,7 +54,7 @@ export function getListenersFromTree(
}
if (eventType === EVENT_TYPE_ALL || eventType === EVENT_TYPE_BUBBLE) {
- const bubbleListener = vNode.props[horizonEvtName];
+ const bubbleListener = getListenerFromVNode(vNode, horizonEvtName);
if (bubbleListener) {
listeners.push({
vNode,
@@ -52,6 +70,3 @@ export function getListenersFromTree(
return listeners;
}
-
-
-
diff --git a/package.json b/package.json
index 5ef9d520..fa79a858 100644
--- a/package.json
+++ b/package.json
@@ -8,6 +8,7 @@
"build": " webpack --config ./scripts/webpack/webpack.config.js",
"build-3rdLib": "node ./scripts/gen3rdLib.js",
"build-3rdLib-dev": "npm run build & node ./scripts/gen3rdLib.js --dev",
+ "build-horizon3rdLib-dev": "npm run build & node ./scripts/gen3rdLib.js --dev --type horizon",
"debug-test": "yarn test --debug",
"test": "jest --config=jest.config.js",
"watch-test": "yarn test --watch --dev"
diff --git a/scripts/__tests__/EventTest/MouseEvent.test.js b/scripts/__tests__/EventTest/MouseEvent.test.js
index a0972a05..030e25de 100644
--- a/scripts/__tests__/EventTest/MouseEvent.test.js
+++ b/scripts/__tests__/EventTest/MouseEvent.test.js
@@ -2,8 +2,8 @@ import * as Horizon from '@cloudsop/horizon/index.ts';
import { getLogUtils } from '../jest/testUtils';
describe('MouseEvent Test', () => {
- const LogUtils =getLogUtils();
-
+ const LogUtils = getLogUtils();
+
describe('onClick Test', () => {
it('绑定this', () => {
class App extends Horizon.Component {
@@ -11,7 +11,7 @@ describe('MouseEvent Test', () => {
super(props);
this.state = {
num: this.props.num,
- price: this.props.price
+ price: this.props.price,
};
}
@@ -19,21 +19,24 @@ describe('MouseEvent Test', () => {
this.setState({ num: this.state.num + 1 });
}
- setPrice = (e) => {
+ setPrice = e => {
this.setState({ num: this.state.price + 1 });
- }
+ };
render() {
return (
<>
{this.state.num}
{this.state.price}
-
-
+
+
>
);
}
}
+
Horizon.render(, container);
expect(container.querySelector('p').innerHTML).toBe('0');
expect(container.querySelector('#p').innerHTML).toBe('100');
@@ -55,6 +58,20 @@ describe('MouseEvent Test', () => {
}
expect(handleClick).toHaveBeenCalledTimes(6);
});
+
+ it('disable不触发click', () => {
+ const handleClick = jest.fn();
+ const spanRef = Horizon.createRef();
+ Horizon.render(
+ ,
+ container
+ );
+ spanRef.current.click();
+
+ expect(handleClick).toHaveBeenCalledTimes(0);
+ });
});
const test = (name, config) => {
@@ -62,27 +79,21 @@ describe('MouseEvent Test', () => {
let event = new MouseEvent(name, {
relatedTarget: null,
bubbles: true,
- screenX: 1
+ screenX: 1,
});
node.dispatchEvent(event);
- expect(LogUtils.getAndClear()).toEqual([
- `${name} capture`,
- `${name} bubble`
- ]);
+ expect(LogUtils.getAndClear()).toEqual([`${name} capture`, `${name} bubble`]);
event = new MouseEvent(name, {
relatedTarget: null,
bubbles: true,
- screenX: 2
+ screenX: 2,
});
node.dispatchEvent(event);
// 再次触发新事件
- expect(LogUtils.getAndClear()).toEqual([
- `${name} capture`,
- `${name} bubble`
- ]);
+ expect(LogUtils.getAndClear()).toEqual([`${name} capture`, `${name} bubble`]);
};
describe('合成鼠标事件', () => {
@@ -93,10 +104,7 @@ describe('MouseEvent Test', () => {
const onMouseMoveCapture = () => {
LogUtils.log('mousemove capture');
};
- test('mousemove', );
+ test('mousemove', );
});
it('onMouseDown', () => {
@@ -106,10 +114,7 @@ describe('MouseEvent Test', () => {
const onMousedownCapture = () => {
LogUtils.log('mousedown capture');
};
- test('mousedown', );
+ test('mousedown', );
});
it('onMouseUp', () => {
@@ -119,10 +124,7 @@ describe('MouseEvent Test', () => {
const onMouseUpCapture = () => {
LogUtils.log('mouseup capture');
};
- test('mouseup', );
+ test('mouseup', );
});
it('onMouseOut', () => {
@@ -132,10 +134,7 @@ describe('MouseEvent Test', () => {
const onMouseOutCapture = () => {
LogUtils.log('mouseout capture');
};
- test('mouseout', );
+ test('mouseout', );
});
it('onMouseOver', () => {
@@ -145,10 +144,7 @@ describe('MouseEvent Test', () => {
const onMouseOverCapture = () => {
LogUtils.log('mouseover capture');
};
- test('mouseover', );
+ test('mouseover', );
});
});
});
diff --git a/scripts/gen3rdLib.js b/scripts/gen3rdLib.js
index 7e33dfec..3b446cd8 100644
--- a/scripts/gen3rdLib.js
+++ b/scripts/gen3rdLib.js
@@ -9,7 +9,7 @@ const argv = require('minimist')(process.argv.slice(2));
const libPathPrefix = '../build';
const suffix = argv.dev ? 'development.js' : 'production.js';
-
+const template = argv.type === 'horizon' ? 'horizon3rdTemplate.ejs' : 'template.ejs';
const readLib = (lib) => {
const libName = lib.split('.')[0];
const libPath = path.resolve(__dirname, `${libPathPrefix}/${libName}/umd/${lib}`);
@@ -20,7 +20,7 @@ const readLib = (lib) => {
}
};
-ejs.renderFile(path.resolve(__dirname, './template.ejs'), {
+ejs.renderFile(path.resolve(__dirname, `./${template}`), {
Horizon: readLib(`horizon.${suffix}`),
}, null, function(err, result) {
const common3rdLibPath = path.resolve(__dirname, `${libPathPrefix}/horizonCommon3rdlib.min.js`)
diff --git a/scripts/horizon3rdTemplate.ejs b/scripts/horizon3rdTemplate.ejs
new file mode 100644
index 00000000..889a6283
--- /dev/null
+++ b/scripts/horizon3rdTemplate.ejs
@@ -0,0 +1,17568 @@
+!function(t, r) {
+"object" == typeof exports && "object" == typeof module ? module.exports = r() : "function" == typeof define && define.amd ? define([], r) : "object" == typeof exports ? exports.ie = r() : t.ie = r()
+}(window, (function() {
+return function(t) {
+var r = {};
+function e(n) {
+if (r[n])
+return r[n].exports;
+var o = r[n] = {
+i: n,
+l: !1,
+exports: {}
+};
+return t[n].call(o.exports, o, o.exports, e),
+o.l = !0,
+o.exports
+}
+return e.m = t,
+e.c = r,
+e.d = function(t, r, n) {
+e.o(t, r) || Object.defineProperty(t, r, {
+enumerable: !0,
+get: n
+})
+}
+,
+e.r = function(t) {
+"undefined" != typeof Symbol && Symbol.toStringTag && Object.defineProperty(t, Symbol.toStringTag, {
+value: "Module"
+}),
+Object.defineProperty(t, "__esModule", {
+value: !0
+})
+}
+,
+e.t = function(t, r) {
+if (1 & r && (t = e(t)),
+8 & r)
+return t;
+if (4 & r && "object" == typeof t && t && t.__esModule)
+return t;
+var n = Object.create(null);
+if (e.r(n),
+Object.defineProperty(n, "default", {
+enumerable: !0,
+value: t
+}),
+2 & r && "string" != typeof t)
+for (var o in t)
+e.d(n, o, function(r) {
+return t[r]
+}
+.bind(null, o));
+return n
+}
+,
+e.n = function(t) {
+var r = t && t.__esModule ? function() {
+return t.default
+}
+: function() {
+return t
+}
+;
+return e.d(r, "a", r),
+r
+}
+,
+e.o = function(t, r) {
+return Object.prototype.hasOwnProperty.call(t, r)
+}
+,
+e.p = "",
+e(e.s = 192)
+}([function(t, r, e) {
+var n = e(1)
+, o = e(23).f
+, i = e(25)
+, a = e(17)
+, u = e(114)
+, c = e(90)
+, s = e(73);
+t.exports = function(t, r) {
+var e, f, l, h, p, v = t.target, g = t.global, d = t.stat;
+if (e = g ? n : d ? n[v] || u(v, {}) : (n[v] || {}).prototype)
+for (f in r) {
+if (h = r[f],
+l = t.noTargetGet ? (p = o(e, f)) && p.value : e[f],
+!s(g ? f : v + (d ? "." : "#") + f, t.forced) && void 0 !== l) {
+if (typeof h == typeof l)
+continue;
+c(h, l)
+}
+(t.sham || l && l.sham) && i(h, "sham", !0),
+a(e, f, h, t)
+}
+}
+}
+, function(t, r, e) {
+(function(r) {
+var e = function(t) {
+return t && t.Math == Math && t
+};
+t.exports = e("object" == typeof globalThis && globalThis) || e("object" == typeof window && window) || e("object" == typeof self && self) || e("object" == typeof r && r) || function() {
+return this
+}() || Function("return this")()
+}
+).call(this, e(195))
+}
+, function(t, r) {
+t.exports = function(t) {
+try {
+return !!t()
+} catch (t) {
+return !0
+}
+}
+}
+, function(t, r, e) {
+var n = e(68)
+, o = Function.prototype
+, i = o.bind
+, a = o.call
+, u = n && i.bind(a, a);
+t.exports = n ? function(t) {
+return t && u(t)
+}
+: function(t) {
+return t && function() {
+return a.apply(t, arguments)
+}
+}
+}
+, function(t, r, e) {
+var n = e(1)
+, o = e(6)
+, i = n.String
+, a = n.TypeError;
+t.exports = function(t) {
+if (o(t))
+return t;
+throw a(i(t) + " is not an object")
+}
+}
+, function(t, r, e) {
+var n = e(2);
+t.exports = !n((function() {
+return 7 != Object.defineProperty({}, 1, {
+get: function() {
+return 7
+}
+})[1]
+}
+))
+}
+, function(t, r, e) {
+var n = e(9);
+t.exports = function(t) {
+return "object" == typeof t ? null !== t : n(t)
+}
+}
+, function(t, r, e) {
+var n = e(1)
+, o = e(86)
+, i = e(12)
+, a = e(60)
+, u = e(112)
+, c = e(142)
+, s = o("wks")
+, f = n.Symbol
+, l = f && f.for
+, h = c ? f : f && f.withoutSetter || a;
+t.exports = function(t) {
+if (!i(s, t) || !u && "string" != typeof s[t]) {
+var r = "Symbol." + t;
+u && i(f, t) ? s[t] = f[t] : s[t] = c && l ? l(r) : h(r)
+}
+return s[t]
+}
+}
+, function(t, r, e) {
+var n = e(1)
+, o = e(52)
+, i = n.String;
+t.exports = function(t) {
+if ("Symbol" === o(t))
+throw TypeError("Cannot convert a Symbol value to a string");
+return i(t)
+}
+}
+, function(t, r) {
+t.exports = function(t) {
+return "function" == typeof t
+}
+}
+, function(t, r, e) {
+"use strict";
+var n, o, i, a = e(128), u = e(5), c = e(1), s = e(9), f = e(6), l = e(12), h = e(52), p = e(70), v = e(25), g = e(17), d = e(13).f, y = e(29), m = e(37), b = e(39), x = e(7), w = e(60), E = c.Int8Array, S = E && E.prototype, A = c.Uint8ClampedArray, O = A && A.prototype, R = E && m(E), T = S && m(S), I = Object.prototype, M = c.TypeError, j = x("toStringTag"), P = w("TYPED_ARRAY_TAG"), k = w("TYPED_ARRAY_CONSTRUCTOR"), _ = a && !!b && "Opera" !== h(c.opera), L = !1, N = {
+Int8Array: 1,
+Uint8Array: 1,
+Uint8ClampedArray: 1,
+Int16Array: 2,
+Uint16Array: 2,
+Int32Array: 4,
+Uint32Array: 4,
+Float32Array: 4,
+Float64Array: 8
+}, D = {
+BigInt64Array: 8,
+BigUint64Array: 8
+}, U = function(t) {
+if (!f(t))
+return !1;
+var r = h(t);
+return l(N, r) || l(D, r)
+};
+for (n in N)
+(i = (o = c[n]) && o.prototype) ? v(i, k, o) : _ = !1;
+for (n in D)
+(i = (o = c[n]) && o.prototype) && v(i, k, o);
+if ((!_ || !s(R) || R === Function.prototype) && (R = function() {
+throw M("Incorrect invocation")
+}
+,
+_))
+for (n in N)
+c[n] && b(c[n], R);
+if ((!_ || !T || T === I) && (T = R.prototype,
+_))
+for (n in N)
+c[n] && b(c[n].prototype, T);
+if (_ && m(O) !== T && b(O, T),
+u && !l(T, j))
+for (n in L = !0,
+d(T, j, {
+get: function() {
+return f(this) ? this[P] : void 0
+}
+}),
+N)
+c[n] && v(c[n], P, n);
+t.exports = {
+NATIVE_ARRAY_BUFFER_VIEWS: _,
+TYPED_ARRAY_CONSTRUCTOR: k,
+TYPED_ARRAY_TAG: L && P,
+aTypedArray: function(t) {
+if (U(t))
+return t;
+throw M("Target is not a typed array")
+},
+aTypedArrayConstructor: function(t) {
+if (s(t) && (!b || y(R, t)))
+return t;
+throw M(p(t) + " is not a typed array constructor")
+},
+exportTypedArrayMethod: function(t, r, e, n) {
+if (u) {
+if (e)
+for (var o in N) {
+var i = c[o];
+if (i && l(i.prototype, t))
+try {
+delete i.prototype[t]
+} catch (e) {
+try {
+i.prototype[t] = r
+} catch (t) {}
+}
+}
+T[t] && !e || g(T, t, e ? r : _ && S[t] || r, n)
+}
+},
+exportTypedArrayStaticMethod: function(t, r, e) {
+var n, o;
+if (u) {
+if (b) {
+if (e)
+for (n in N)
+if ((o = c[n]) && l(o, t))
+try {
+delete o[t]
+} catch (t) {}
+if (R[t] && !e)
+return;
+try {
+return g(R, t, e ? r : _ && R[t] || r)
+} catch (t) {}
+}
+for (n in N)
+!(o = c[n]) || o[t] && !e || g(o, t, r)
+}
+},
+isView: function(t) {
+if (!f(t))
+return !1;
+var r = h(t);
+return "DataView" === r || l(N, r) || l(D, r)
+},
+isTypedArray: U,
+TypedArray: R,
+TypedArrayPrototype: T
+}
+}
+, function(t, r, e) {
+var n = e(68)
+, o = Function.prototype.call;
+t.exports = n ? o.bind(o) : function() {
+return o.apply(o, arguments)
+}
+}
+, function(t, r, e) {
+var n = e(3)
+, o = e(14)
+, i = n({}.hasOwnProperty);
+t.exports = Object.hasOwn || function(t, r) {
+return i(o(t), r)
+}
+}
+, function(t, r, e) {
+var n = e(1)
+, o = e(5)
+, i = e(144)
+, a = e(145)
+, u = e(4)
+, c = e(49)
+, s = n.TypeError
+, f = Object.defineProperty
+, l = Object.getOwnPropertyDescriptor;
+r.f = o ? a ? function(t, r, e) {
+if (u(t),
+r = c(r),
+u(e),
+"function" == typeof t && "prototype" === r && "value"in e && "writable"in e && !e.writable) {
+var n = l(t, r);
+n && n.writable && (t[r] = e.value,
+e = {
+configurable: "configurable"in e ? e.configurable : n.configurable,
+enumerable: "enumerable"in e ? e.enumerable : n.enumerable,
+writable: !1
+})
+}
+return f(t, r, e)
+}
+: f : function(t, r, e) {
+if (u(t),
+r = c(r),
+u(e),
+i)
+try {
+return f(t, r, e)
+} catch (t) {}
+if ("get"in e || "set"in e)
+throw s("Accessors not supported");
+return "value"in e && (t[r] = e.value),
+t
+}
+}
+, function(t, r, e) {
+var n = e(1)
+, o = e(18)
+, i = n.Object;
+t.exports = function(t) {
+return i(o(t))
+}
+}
+, function(t, r, e) {
+var n = e(30);
+t.exports = function(t) {
+return n(t.length)
+}
+}
+, function(t, r, e) {
+var n = e(1)
+, o = e(9)
+, i = function(t) {
+return o(t) ? t : void 0
+};
+t.exports = function(t, r) {
+return arguments.length < 2 ? i(n[t]) : n[t] && n[t][r]
+}
+}
+, function(t, r, e) {
+var n = e(1)
+, o = e(9)
+, i = e(12)
+, a = e(25)
+, u = e(114)
+, c = e(88)
+, s = e(19)
+, f = e(61).CONFIGURABLE
+, l = s.get
+, h = s.enforce
+, p = String(String).split("String");
+(t.exports = function(t, r, e, c) {
+var s, l = !!c && !!c.unsafe, v = !!c && !!c.enumerable, g = !!c && !!c.noTargetGet, d = c && void 0 !== c.name ? c.name : r;
+o(e) && ("Symbol(" === String(d).slice(0, 7) && (d = "[" + String(d).replace(/^Symbol\(([^)]*)\)/, "$1") + "]"),
+(!i(e, "name") || f && e.name !== d) && a(e, "name", d),
+(s = h(e)).source || (s.source = p.join("string" == typeof d ? d : ""))),
+t !== n ? (l ? !g && t[r] && (v = !0) : delete t[r],
+v ? t[r] = e : a(t, r, e)) : v ? t[r] = e : u(r, e)
+}
+)(Function.prototype, "toString", (function() {
+return o(this) && l(this).source || c(this)
+}
+))
+}
+, function(t, r, e) {
+var n = e(1).TypeError;
+t.exports = function(t) {
+if (null == t)
+throw n("Can't call method on " + t);
+return t
+}
+}
+, function(t, r, e) {
+var n, o, i, a = e(146), u = e(1), c = e(3), s = e(6), f = e(25), l = e(12), h = e(113), p = e(89), v = e(71), g = u.TypeError, d = u.WeakMap;
+if (a || h.state) {
+var y = h.state || (h.state = new d)
+, m = c(y.get)
+, b = c(y.has)
+, x = c(y.set);
+n = function(t, r) {
+if (b(y, t))
+throw new g("Object already initialized");
+return r.facade = t,
+x(y, t, r),
+r
+}
+,
+o = function(t) {
+return m(y, t) || {}
+}
+,
+i = function(t) {
+return b(y, t)
+}
+} else {
+var w = p("state");
+v[w] = !0,
+n = function(t, r) {
+if (l(t, w))
+throw new g("Object already initialized");
+return r.facade = t,
+f(t, w, r),
+r
+}
+,
+o = function(t) {
+return l(t, w) ? t[w] : {}
+}
+,
+i = function(t) {
+return l(t, w)
+}
+}
+t.exports = {
+set: n,
+get: o,
+has: i,
+enforce: function(t) {
+return i(t) ? o(t) : n(t, {})
+},
+getterFor: function(t) {
+return function(r) {
+var e;
+if (!s(r) || (e = o(r)).type !== t)
+throw g("Incompatible receiver, " + t + " required");
+return e
+}
+}
+}
+}
+, function(t, r) {
+var e = Math.ceil
+, n = Math.floor;
+t.exports = function(t) {
+var r = +t;
+return r != r || 0 === r ? 0 : (r > 0 ? n : e)(r)
+}
+}
+, function(t, r) {
+t.exports = !1
+}
+, function(t, r, e) {
+var n = e(38)
+, o = e(3)
+, i = e(69)
+, a = e(14)
+, u = e(15)
+, c = e(77)
+, s = o([].push)
+, f = function(t) {
+var r = 1 == t
+, e = 2 == t
+, o = 3 == t
+, f = 4 == t
+, l = 6 == t
+, h = 7 == t
+, p = 5 == t || l;
+return function(v, g, d, y) {
+for (var m, b, x = a(v), w = i(x), E = n(g, d), S = u(w), A = 0, O = y || c, R = r ? O(v, S) : e || h ? O(v, 0) : void 0; S > A; A++)
+if ((p || A in w) && (b = E(m = w[A], A, x),
+t))
+if (r)
+R[A] = b;
+else if (b)
+switch (t) {
+case 3:
+return !0;
+case 5:
+return m;
+case 6:
+return A;
+case 2:
+s(R, m)
+}
+else
+switch (t) {
+case 4:
+return !1;
+case 7:
+s(R, m)
+}
+return l ? -1 : o || f ? f : R
+}
+};
+t.exports = {
+forEach: f(0),
+map: f(1),
+filter: f(2),
+some: f(3),
+every: f(4),
+find: f(5),
+findIndex: f(6),
+filterReject: f(7)
+}
+}
+, function(t, r, e) {
+var n = e(5)
+, o = e(11)
+, i = e(85)
+, a = e(35)
+, u = e(26)
+, c = e(49)
+, s = e(12)
+, f = e(144)
+, l = Object.getOwnPropertyDescriptor;
+r.f = n ? l : function(t, r) {
+if (t = u(t),
+r = c(r),
+f)
+try {
+return l(t, r)
+} catch (t) {}
+if (s(t, r))
+return a(!o(i.f, t, r), t[r])
+}
+}
+, function(t, r, e) {
+var n = e(1)
+, o = e(9)
+, i = e(70)
+, a = n.TypeError;
+t.exports = function(t) {
+if (o(t))
+return t;
+throw a(i(t) + " is not a function")
+}
+}
+, function(t, r, e) {
+var n = e(5)
+, o = e(13)
+, i = e(35);
+t.exports = n ? function(t, r, e) {
+return o.f(t, r, i(1, e))
+}
+: function(t, r, e) {
+return t[r] = e,
+t
+}
+}
+, function(t, r, e) {
+var n = e(69)
+, o = e(18);
+t.exports = function(t) {
+return n(o(t))
+}
+}
+, function(t, r, e) {
+var n = e(150)
+, o = e(12)
+, i = e(149)
+, a = e(13).f;
+t.exports = function(t) {
+var r = n.Symbol || (n.Symbol = {});
+o(r, t) || a(r, t, {
+value: i.f(t)
+})
+}
+}
+, function(t, r, e) {
+var n = e(3)
+, o = n({}.toString)
+, i = n("".slice);
+t.exports = function(t) {
+return i(o(t), 8, -1)
+}
+}
+, function(t, r, e) {
+var n = e(3);
+t.exports = n({}.isPrototypeOf)
+}
+, function(t, r, e) {
+var n = e(20)
+, o = Math.min;
+t.exports = function(t) {
+return t > 0 ? o(n(t), 9007199254740991) : 0
+}
+}
+, function(t, r, e) {
+var n = e(68)
+, o = Function.prototype
+, i = o.apply
+, a = o.call;
+t.exports = "object" == typeof Reflect && Reflect.apply || (n ? a.bind(i) : function() {
+return a.apply(i, arguments)
+}
+)
+}
+, function(t, r, e) {
+var n, o = e(4), i = e(74), a = e(116), u = e(71), c = e(148), s = e(87), f = e(89), l = f("IE_PROTO"), h = function() {}, p = function(t) {
+return "