diff --git a/libs/horizon/src/external/ChildrenUtil.ts b/libs/horizon/src/external/ChildrenUtil.ts index d1a928fb..b034107a 100644 --- a/libs/horizon/src/external/ChildrenUtil.ts +++ b/libs/horizon/src/external/ChildrenUtil.ts @@ -1,7 +1,7 @@ -import {throwIfTrue} from '../renderer/utils/throwIfTrue'; -import {TYPE_COMMON_ELEMENT, TYPE_PORTAL} from './JSXElementType'; +import { throwIfTrue } from '../renderer/utils/throwIfTrue'; +import { TYPE_COMMON_ELEMENT, TYPE_PORTAL } from './JSXElementType'; -import {isValidElement, JSXElement} from './JSXElement'; +import { isValidElement, JSXElement } from './JSXElement'; // 生成key function getItemKey(item: any, index: number): string { @@ -12,12 +12,7 @@ function getItemKey(item: any, index: number): string { return '.' + index.toString(36); } -function mapChildrenToArray( - children: any, - arr: Array, - prefix: string, - callback?: Function, -): number | void { +function mapChildrenToArray(children: any, arr: Array, prefix: string, callback?: Function): number | void { const type = typeof children; switch (type) { // 继承原有规格,undefined和boolean类型按照null处理 @@ -36,44 +31,27 @@ function mapChildrenToArray( } const vtype = children.vtype; if (vtype === TYPE_COMMON_ELEMENT || vtype === TYPE_PORTAL) { - callMapFun(children, arr, prefix, callback) ; + callMapFun(children, arr, prefix, callback); return; } if (Array.isArray(children)) { processArrayChildren(children, arr, prefix, callback); return; } - throw new Error( - 'Object is invalid as a Horizon child. ' - ); + throw new Error('Object is invalid as a Horizon child. '); // No Default } } -function processArrayChildren( - children: any, - arr: Array, - prefix: string, - callback: Function, -) { +function processArrayChildren(children: any, arr: Array, prefix: string, callback: Function) { for (let i = 0; i < children.length; i++) { const childItem = children[i]; const nextPrefix = prefix + getItemKey(childItem, i); - mapChildrenToArray( - childItem, - arr, - nextPrefix, - callback, - ); + mapChildrenToArray(childItem, arr, nextPrefix, callback); } } -function callMapFun( - children: any, - arr: Array, - prefix: string, - callback: Function, -) { +function callMapFun(children: any, arr: Array, prefix: string, callback: Function) { let mappedChild = callback(children); if (Array.isArray(mappedChild)) { // 维持原有规格,如果callback返回结果是数组,处理函数修改为返回数组item @@ -83,9 +61,8 @@ function callMapFun( if (isValidElement(mappedChild)) { const childKey = prefix === '' ? getItemKey(children, 0) : ''; const mappedKey = getItemKey(mappedChild, 0); - const newKey = prefix + childKey + (mappedChild.key && mappedKey !== getItemKey(children, 0) - ? '.$' + mappedChild.key - : ''); + const newKey = + prefix + childKey + (mappedChild.key && mappedKey !== getItemKey(children, 0) ? '.$' + mappedChild.key : ''); // 返回一个修改key的children mappedChild = JSXElement( mappedChild.type, @@ -93,6 +70,7 @@ function callMapFun( mappedChild.ref, mappedChild.belongClassVNode, mappedChild.props, + mappedChild.src ); } arr.push(mappedChild); @@ -100,11 +78,7 @@ function callMapFun( } // 在 children 里的每个直接子节点上调用一个函数,并将 this 设置为 thisArg -function mapChildren( - children: any, - func: Function, - context?: any, -): Array { +function mapChildren(children: any, func: Function, context?: any): Array { if (children === null || children === undefined) { return children; } @@ -121,27 +95,22 @@ const Children = { }, map: mapChildren, // 并非所有元素都会计数,只计数调用callMapFun函数次数 - count: (children) => { + count: children => { let n = 0; mapChildren(children, () => { n++; }); return n; }, - only: (children) => { - throwIfTrue( - !isValidElement(children), - 'Horizon.Children.only function received invalid element.' - ); + only: children => { + throwIfTrue(!isValidElement(children), 'Horizon.Children.only function received invalid element.'); return children; }, - toArray: (children) => { + toArray: children => { const result = []; mapChildrenToArray(children, result, '', child => child); return result; }, -} - -export { - Children }; + +export { Children }; diff --git a/libs/horizon/src/external/JSXElement.ts b/libs/horizon/src/external/JSXElement.ts index d805b843..f44219ee 100644 --- a/libs/horizon/src/external/JSXElement.ts +++ b/libs/horizon/src/external/JSXElement.ts @@ -1,6 +1,6 @@ import { TYPE_COMMON_ELEMENT } from './JSXElementType'; import { getProcessingClassVNode } from '../renderer/GlobalVar'; - +import { Source } from '../renderer/Types'; /** * vtype 节点的类型,这里固定是element @@ -9,10 +9,11 @@ import { getProcessingClassVNode } from '../renderer/GlobalVar'; * ref ref属性 * props 其他常规属性 */ -export function JSXElement(type, key, ref, vNode, props) { +export function JSXElement(type, key, ref, vNode, props, source: Source | null) { return { // 元素标识符 vtype: TYPE_COMMON_ELEMENT, + src: isDev ? source : null, // 属于元素的内置属性 type: type, @@ -26,7 +27,8 @@ export function JSXElement(type, key, ref, vNode, props) { } function isValidKey(key) { - return key !== 'key' && key !== 'ref' && key !== '__source'; + const keyArray = ['key', 'ref', '__source']; + return !keyArray.includes(key); } function mergeDefault(sourceObj, defaultObj) { @@ -66,8 +68,15 @@ function buildElement(isClone, type, setting, children) { if (element && element.defaultProps) { mergeDefault(props, element.defaultProps); } + let src: Source | null = null; + if (setting?.__source) { + src = { + fileName: setting.__source.fileName, + lineNumber: setting.__source.lineNumber, + }; + } - return JSXElement(element, key, ref, vNode, props); + return JSXElement(element, key, ref, vNode, props, src); } // 创建Element结构体,供JSX编译时调用 diff --git a/libs/horizon/src/external/devtools.ts b/libs/horizon/src/external/devtools.ts index e1edf1ff..0cc4d530 100644 --- a/libs/horizon/src/external/devtools.ts +++ b/libs/horizon/src/external/devtools.ts @@ -1,12 +1,5 @@ import { travelVNodeTree } from '../renderer/vnode/VNodeUtils'; -import { - Hook, - Reducer, - Ref, - Effect, - CallBack, - Memo -} from '../renderer/hooks/HookType'; +import { Hook, Reducer, Ref, Effect, CallBack, Memo } from '../renderer/hooks/HookType'; import { VNode } from '../renderer/vnode/VNode'; import { launchUpdateFromVNode } from '../renderer/TreeBuilder'; import { DomComponent } from '../renderer/vnode/VNodeTags'; @@ -26,7 +19,7 @@ const HookName = { MemoHook: 'Memo', RefHook: 'Ref', ReducerHook: 'Reducer', - CallbackHook: 'Callback' + CallbackHook: 'Callback', }; export const helper = { @@ -45,7 +38,8 @@ export const helper = { } else if (isRefHook(state)) { return { name: HookName.RefHook, hIndex, value: (state as Ref).current }; } else if (isEffectHook(state)) { - const name = state.effectConstant == EffectConstant.LayoutEffect ? HookName.LayoutEffectHook : HookName.EffectHook; + const name = + state.effectConstant == EffectConstant.LayoutEffect ? HookName.LayoutEffectHook : HookName.EffectHook; return { name, hIndex, value: (state as Effect).effect }; } else if (isCallbackHook(state)) { return { name: HookName.CallbackHook, hIndex, value: (state as CallBack).func }; @@ -86,7 +80,7 @@ export const helper = { } if (hooks && hooks.length !== 0) { const logHookInfo: any[] = []; - hooks.forEach((hook) => { + hooks.forEach(hook => { const state = hook.state as Reducer; if (state.trigger && state.isUseState) { logHookInfo.push(state.stateValue); @@ -94,20 +88,26 @@ export const helper = { }); 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); + 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; }, getElementTag: (element: JSXElement) => { return getElementTag(element); - } + }, }; export function injectUpdater() { diff --git a/libs/horizon/src/renderer/Types.ts b/libs/horizon/src/renderer/Types.ts index 6e1572fe..45f76fa9 100644 --- a/libs/horizon/src/renderer/Types.ts +++ b/libs/horizon/src/renderer/Types.ts @@ -3,20 +3,16 @@ export { VNode } from './vnode/VNode'; type Trigger = (A) => void; export type UseStateHookType = { - useState( - initialState: (() => S) | S - ): [S, Trigger<((S) => S) | S>]; + useState(initialState: (() => S) | S): [S, Trigger<((S) => S) | S>]; }; export type UseReducerHookType = { - useReducer( - reducer: (S, A) => S, - initArg: P, init?: (P) => S, - ): [S, Trigger]; + useReducer(reducer: (S, A) => S, initArg: P, init?: (P) => S): [S, Trigger]; }; -export type UseContextHookType = { useContext(context: ContextType,): T }; +export type UseContextHookType = { useContext(context: ContextType): T }; export type JSXElement = { vtype: any; + src: any; type: any; key: any; ref: any; @@ -31,8 +27,8 @@ export type ProviderType = { export type ContextType = { vtype: number; - Consumer: ContextType; - Provider: ProviderType; + Consumer: ContextType | null; + Provider: ProviderType | null; value: T; }; @@ -50,7 +46,7 @@ export type RefType = { export interface PromiseType { then( onFulfill: (value: R) => void | PromiseType | U, - onReject: (error: any) => void | PromiseType | U, + onReject: (error: any) => void | PromiseType | U ): void | PromiseType; } @@ -61,3 +57,8 @@ export interface SuspenseState { didCapture: boolean; // suspense是否捕获了异常 promiseResolved: boolean; // suspense的promise是否resolve } + +export type Source = { + fileName: string; + lineNumber: number; +}; diff --git a/libs/horizon/src/renderer/render/MemoComponent.ts b/libs/horizon/src/renderer/render/MemoComponent.ts index 99a34364..270346e7 100644 --- a/libs/horizon/src/renderer/render/MemoComponent.ts +++ b/libs/horizon/src/renderer/render/MemoComponent.ts @@ -1,21 +1,19 @@ -import type {VNode} from '../Types'; +import type { Source, VNode } from '../Types'; -import {mergeDefaultProps} from './LazyComponent'; -import {updateVNode, onlyUpdateChildVNodes, createFragmentVNode, createUndeterminedVNode} from '../vnode/VNodeCreator'; -import {shallowCompare} from '../utils/compare'; +import { mergeDefaultProps } from './LazyComponent'; import { - TYPE_FRAGMENT, - TYPE_PROFILER, - TYPE_STRICT_MODE, -} from '../../external/JSXElementType'; + updateVNode, + onlyUpdateChildVNodes, + createFragmentVNode, + createUndeterminedVNode, +} from '../vnode/VNodeCreator'; +import { shallowCompare } from '../utils/compare'; +import { TYPE_FRAGMENT, TYPE_PROFILER, TYPE_STRICT_MODE } from '../../external/JSXElementType'; import { markVNodePath } from '../utils/vNodePath'; export function bubbleRender() {} -export function captureMemoComponent( - processing: VNode, - shouldUpdate: boolean, -): VNode | null { +export function captureMemoComponent(processing: VNode, shouldUpdate: boolean): VNode | null { const Component = processing.type; // 合并 函数组件或类组件 的defaultProps const newProps = mergeDefaultProps(Component, processing.props); @@ -26,7 +24,7 @@ export function captureMemoComponent( if (type === TYPE_STRICT_MODE || type === TYPE_FRAGMENT || type === TYPE_PROFILER) { newChild = createFragmentVNode(null, newProps.children); } else { - newChild = createUndeterminedVNode(type, null, newProps); + newChild = createUndeterminedVNode(type, null, newProps, processing.src); } newChild.parent = processing; newChild.ref = processing.ref; diff --git a/libs/horizon/src/renderer/vnode/VNode.ts b/libs/horizon/src/renderer/vnode/VNode.ts index 7c360e47..80baedc9 100644 --- a/libs/horizon/src/renderer/vnode/VNode.ts +++ b/libs/horizon/src/renderer/vnode/VNode.ts @@ -17,10 +17,10 @@ import { Profiler, MemoComponent, } from './VNodeTags'; -import type { VNodeTag } from './VNodeTags'; -import type { RefType, ContextType, SuspenseState } from '../Types'; -import type { Hook } from '../hooks/HookType'; -import { InitFlag } from './VNodeFlags'; +import type {VNodeTag} from './VNodeTags'; +import type {RefType, ContextType, SuspenseState, Source} from '../Types'; +import type {Hook} from '../hooks/HookType'; +import {InitFlag} from './VNodeFlags'; export class VNode { tag: VNodeTag; @@ -77,8 +77,8 @@ export class VNode { // 根节点数据 toUpdateNodes: Set | null; // 保存要更新的节点 - delegatedEvents: Set - delegatedNativeEvents: Set + delegatedEvents: Set; + delegatedNativeEvents: Set; belongClassVNode: VNode | null = null; // 记录JSXElement所属class vNode,处理ref的时候使用 @@ -86,6 +86,7 @@ export class VNode { isStoreChange: boolean; observers: Set | null = null; // 记录这个函数组件/类组件依赖哪些Observer classComponentWillUnmount: Function | null; // HorizonX会在classComponentWillUnmount中清除对VNode的引入用 + src: Source | null; // 节点所在代码位置 constructor(tag: VNodeTag, props: any, key: null | string, realNode) { this.tag = tag; // 对应组件的类型,比如ClassComponent等 @@ -116,6 +117,7 @@ export class VNode { this.isStoreChange = false; this.observers = null; this.classComponentWillUnmount = null; + this.src = null; break; case ClassComponent: this.realNode = null; @@ -130,15 +132,18 @@ export class VNode { this.isStoreChange = false; this.observers = null; this.classComponentWillUnmount = null; + this.src = null; break; case DomPortal: this.realNode = null; this.context = null; + this.src = null; break; case DomComponent: this.realNode = null; this.changeList = null; this.context = null; + this.src = null; break; case DomText: this.realNode = null; @@ -150,14 +155,17 @@ export class VNode { didCapture: false, promiseResolved: false, oldChildStatus: '', - childStatus: '' + childStatus: '', }; + this.src = null; break; case ContextProvider: + this.src = null; this.context = null; break; case MemoComponent: this.effectList = null; + this.src = null; break; case LazyComponent: this.realNode = null; @@ -165,6 +173,7 @@ export class VNode { this.isLazyComponent = true; this.lazyType = null; this.updates = null; + this.src = null; break; case Fragment: break; diff --git a/libs/horizon/src/renderer/vnode/VNodeCreator.ts b/libs/horizon/src/renderer/vnode/VNodeCreator.ts index c5b4c306..79496323 100644 --- a/libs/horizon/src/renderer/vnode/VNodeCreator.ts +++ b/libs/horizon/src/renderer/vnode/VNodeCreator.ts @@ -17,14 +17,17 @@ import { } from './VNodeTags'; import { TYPE_CONTEXT, - TYPE_FORWARD_REF, TYPE_FRAGMENT, + TYPE_FORWARD_REF, + TYPE_FRAGMENT, TYPE_LAZY, - TYPE_MEMO, TYPE_PROFILER, - TYPE_PROVIDER, TYPE_STRICT_MODE, + TYPE_MEMO, + TYPE_PROFILER, + TYPE_PROVIDER, + TYPE_STRICT_MODE, TYPE_SUSPENSE, } from '../../external/JSXElementType'; import { VNode } from './VNode'; -import { JSXElement } from '../Types'; +import { JSXElement, Source } from '../Types'; import { markVNodePath } from '../utils/vNodePath'; const typeLazyMap = { @@ -56,7 +59,7 @@ export function getLazyVNodeTag(lazyComp: any): string { } else if (lazyComp !== undefined && lazyComp !== null && typeLazyMap[lazyComp.vtype]) { return typeLazyMap[lazyComp.vtype]; } - throw Error('Horizon can\'t resolve the content of lazy'); + throw Error("Horizon can't resolve the content of lazy"); } // 创建processing @@ -102,11 +105,10 @@ export function createPortalVNode(portal) { return vNode; } -export function createUndeterminedVNode(type, key, props) { +export function createUndeterminedVNode(type, key, props, source: Source | null): VNode { let vNodeTag = FunctionComponent; let isLazy = false; const componentType = typeof type; - if (componentType === 'function') { if (isClassComponent(type)) { vNodeTag = ClassComponent; @@ -129,6 +131,8 @@ export function createUndeterminedVNode(type, key, props) { if (isLazy) { vNode.lazyType = type; } + + vNode.src = isDev ? source : null; return vNode; } @@ -181,14 +185,12 @@ export function createVNode(tag: VNodeTag | string, ...secondArg) { } export function createVNodeFromElement(element: JSXElement): VNode { - const type = element.type; - const key = element.key; - const props = element.props; + const { type, key, props, src } = element; if (type === TYPE_STRICT_MODE || type === TYPE_FRAGMENT || type === TYPE_PROFILER) { return createFragmentVNode(key, props.children); } else { - return createUndeterminedVNode(type, key, props); + return createUndeterminedVNode(type, key, props, src); } } @@ -241,4 +243,3 @@ export function onlyUpdateChildVNodes(processing: VNode): VNode | null { // 子树无需工作 return null; } - diff --git a/package.json b/package.json index fe76b6b7..500c07f5 100644 --- a/package.json +++ b/package.json @@ -38,6 +38,7 @@ "@babel/plugin-transform-shorthand-properties": "7.16.7", "@babel/plugin-transform-spread": "7.16.7", "@babel/plugin-transform-template-literals": "7.16.7", + "@babel/plugin-transform-react-jsx-source": "^7.16.7", "@babel/preset-env": "7.16.7", "@babel/preset-typescript": "7.16.7", "@rollup/plugin-babel": "^5.3.1",