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 "