From 42e8bd11715a8c745b645dcefd59fc69aecdbffa Mon Sep 17 00:00:00 2001 From: 13659257719 <819781841@qq.com> Date: Thu, 9 Nov 2023 10:34:51 +0800 Subject: [PATCH] =?UTF-8?q?[inula-dev-tools]=20=E8=A7=A3=E6=9E=90=20?= =?UTF-8?q?VNode=20tree?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../inula-dev-tools/src/parser/parseAttr.ts | 2 - .../inula-dev-tools/src/parser/parseVNode.ts | 229 ++++++++++++++++++ 2 files changed, 229 insertions(+), 2 deletions(-) create mode 100644 packages/inula-dev-tools/src/parser/parseVNode.ts diff --git a/packages/inula-dev-tools/src/parser/parseAttr.ts b/packages/inula-dev-tools/src/parser/parseAttr.ts index 94473576..59b47073 100644 --- a/packages/inula-dev-tools/src/parser/parseAttr.ts +++ b/packages/inula-dev-tools/src/parser/parseAttr.ts @@ -28,8 +28,6 @@ import { import { helper } from '../injector'; import { JSXElement, ContextType } from '../../../inula/src/renderer/Types'; import { decycle } from 'json-decycle'; -import {arrify} from "ts-loader/dist/utils"; -import {add} from "../../../inula/src/renderer/taskExecutor/TaskQueue"; // 展示值为 string 的可编辑模型 type EditableStringType = 'string' | 'number' | 'undefined' | 'null'; diff --git a/packages/inula-dev-tools/src/parser/parseVNode.ts b/packages/inula-dev-tools/src/parser/parseVNode.ts new file mode 100644 index 00000000..0b01799f --- /dev/null +++ b/packages/inula-dev-tools/src/parser/parseVNode.ts @@ -0,0 +1,229 @@ +/* + * Copyright (c) 2023 Huawei Technologies Co.,Ltd. + * + * openInula is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * + * http://license.coscl.org.cn/MulanPSL2 + * + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, + * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, + * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ + +import { VNode } from '../../../inula/src/renderer/vnode/VNode'; +import { + ClassComponent, + ContextConsumer, + ContextProvider, + ForwardRef, + FunctionComponent, + MemoComponent, + SuspenseComponent +} from '../../../inula/src/renderer/vnode/VNodeTags'; + +export type NameObj = { + itemName: string; + badge: Array; +}; + +// 建立双向映射关系,当用户在修改属性值后,可以找到对应的 VNode +export let VNodeToIdMap: Map; +export let IdToVNodeMap: Map; + +if (!VNodeToIdMap) { + VNodeToIdMap = new Map(); +} + +if (!IdToVNodeMap) { + IdToVNodeMap = new Map(); +} + +let uid = 0; +function generateUid(vNode: VNode) { + const id = VNodeToIdMap.get(vNode); + if (id !== undefined) { + return id; + } + uid++; + return uid; +} + +const componentType = [ + ClassComponent, + FunctionComponent, + ContextProvider, + ContextConsumer, + ForwardRef, + SuspenseComponent, + MemoComponent, +]; + +const badgeNameArr: Array = [ + 'withRouter(', + 'SideEffect(', + 'Connect(', + 'injectIntl(', + 'Pure(', +]; + +export function isUserComponent(tag: string) { + return componentType.includes(tag); +} + +function getParentUserComponent(node: VNode) { + let parent = node.parent; + while (parent) { + if (isUserComponent(parent.tag)) { + break; + } + parent = parent.parent; + } + return parent; +} + +function getContextName(node: VNode, type: string) { + const contextType = type; + if (!node.type.displayName) { + if (node.type.value) { + if (typeof node.type.value === 'object') { + return `Context.${contextType}`; + } else { + return `${node.type.value}.${contextType}`; + } + } else { + if (node.type._context?.displayName) { + return `${node.type._context.displayName}.${contextType}`; + } + return `Context.${contextType}`; + } + } + return `${node.type.displayName}.${contextType}`; +} + +const getForwardRefName = (node: VNode): NameObj => { + const forwardRefName: NameObj = { + itemName: '', + badge: ['ForwardRef'], + }; + if (!node.type.render?.name) { + if (node.type.render?.name !== '') { + forwardRefName.itemName = node.type?.displayName ? node.type?.displayName : 'Anonymous'; + } else { + forwardRefName.itemName = 'Anonymous'; + } + } else { + forwardRefName.itemName = node.type.render?.name; + } + return forwardRefName; +}; + +// 用于结构组件名,例如: Pure(Memo(xxx)) => xxx 并且把 Pure Memo 加到 NameObj.badge 里 +const parseComponentName = (name: NameObj): NameObj => { + badgeNameArr.forEach(badgeName => { + if (name.itemName.startsWith(badgeName)) { + // 截断开头的高阶组件名,并把最后一个 ) 替换为 ''。例如: Pure(Memo(xxx)) => Memo(xxx)) => Memo(xxx) + name.itemName = name.itemName.substring(badgeName.length).replace(/(\))(?!.*\1)/, ''); + name.badge.push(badgeName.substring(0, badgeName.length - 1)); + } + }); + return name; +}; + +// 取字符串括号里的值 +const getValuesInParentheses = (name: string) => { + let result = name; + const regex = /\((.+?)\)/g; + const results = name.match(regex); + if (results) { + const option = results[0]; + if (option) { + result = option.substring(1, option.length - 1); + } + } + return result; +}; + +function isNullOrUndefined(prop) { + return !prop || typeof prop === 'undefined' || prop === 0; +} + +function parseTreeRoot(travelVNodeTree, treeRoot: VNode) { + const result: any[] = []; + travelVNodeTree(treeRoot, (node: VNode) => { + const tag = node.tag; + + if (isUserComponent(tag)) { + // 添加 ID + const id = generateUid(node); + result.push(id); + let nameObj: NameObj = { + itemName: '', + badge: [], + }; + // 拿到不同类型的展示名字 + if (tag === ContextProvider) { + nameObj.itemName = getContextName(node, 'Provider'); + result.push(nameObj); + } else if (tag === ContextConsumer) { + nameObj.itemName = getContextName(node, 'Consumer'); + result.push(nameObj); + } else if (tag === ForwardRef) { + const name = getForwardRefName(node); + result.push(name); + } else if (tag === SuspenseComponent) { + nameObj.itemName = 'Suspense'; + result.push(nameObj); + } else if (tag === MemoComponent) { + const name = node.type?.displayName || node.type?.name || node.type.render?.name; + nameObj.itemName = !isNullOrUndefined(name) ? name : 'Anonymous'; + nameObj.badge.push('Memo'); + nameObj = parseComponentName(nameObj); + result.push(nameObj); + } else { + const name = node.type.displayName || node.type?.name; + nameObj.itemName = !isNullOrUndefined(name) ? name : 'Anonymous'; + nameObj = parseComponentName(nameObj); + result.push(nameObj); + } + + // 添加父节点 ID + const parent = getParentUserComponent(node); + if (parent) { + const parentId = VNodeToIdMap.get(parent); + result.push(parentId); + } else { + result.push(''); + } + + // 添加节点 key 值 + const key = node.key; + if (key !== null) { + result.push(key); + } else { + result.push(''); + } + + VNodeToIdMap.set(node, id); + IdToVNodeMap.set(id, node); + } + }); + + return result; +} + +export function queryVNode(id: number): VNode | undefined { + return IdToVNodeMap.get(id); +} + +export function clearVNode(vNode: VNode) { + if (VNodeToIdMap.has(vNode)) { + const id = VNodeToIdMap.get(vNode); + VNodeToIdMap.delete(vNode); + IdToVNodeMap.delete(id); + } +} + +export default parseTreeRoot;