import { isArray, isFunction, isString, isObject, EMPTY_ARR } from '@vue/shared' import { ComponentInstance } from './component' import { HostNode } from './createRenderer' import { RawSlots } from './componentSlots' import { CLASS } from './patchFlags' import { ELEMENT, FUNCTIONAL_COMPONENT, STATEFUL_COMPONENT, TEXT_CHILDREN, ARRAY_CHILDREN, SLOTS_CHILDREN } from './typeFlags' export const Fragment = Symbol('Fragment') export const Text = Symbol('Text') export const Empty = Symbol('Empty') export const Portal = Symbol('Portal') type VNodeTypes = | string | Function | Object | typeof Fragment | typeof Text | typeof Empty type VNodeChildAtom = VNode | string | number | null | void export interface VNodeChildren extends Array {} export type VNodeChild = VNodeChildAtom | VNodeChildren export type NormalizedChildren = string | VNodeChildren | RawSlots | null export interface VNode { type: VNodeTypes props: { [key: string]: any } | null key: string | number | null ref: string | Function | null children: NormalizedChildren component: ComponentInstance | null // DOM el: HostNode | null anchor: HostNode | null // fragment anchor target: HostNode | null // portal target // optimization only shapeFlag: number patchFlag: number dynamicProps: string[] | null dynamicChildren: VNode[] | null } // Since v-if and v-for are the two possible ways node structure can dynamically // change, once we consider v-if branches and each v-for fragment a block, we // can divide a template into nested blocks, and within each block the node // structure would be stable. This allows us to skip most children diffing // and only worry about the dynamic nodes (indicated by patch flags). const blockStack: (VNode[] | null)[] = [] // Open a block. // This must be called before `createBlock`. It cannot be part of `createBlock` // because the children of the block are evaluated before `createBlock` itself // is called. The generated code typically looks like this: // // function render() { // return (openBlock(),createBlock('div', null, [...])) // } // // disableTracking is true when creating a fragment block, since a fragment // always diffs its children. export function openBlock(disableTrackng?: boolean) { blockStack.push(disableTrackng ? null : []) } let shouldTrack = true // Create a block root vnode. Takes the same exact arguments as `createVNode`. // A block root keeps track of dynamic nodes within the block in the // `dynamicChildren` array. export function createBlock( type: VNodeTypes, props?: { [key: string]: any } | null, children?: any, patchFlag?: number, dynamicProps?: string[] ): VNode { // avoid a block with optFlag tracking itself shouldTrack = false const vnode = createVNode(type, props, children, patchFlag, dynamicProps) shouldTrack = true const trackedNodes = blockStack.pop() vnode.dynamicChildren = trackedNodes && trackedNodes.length ? trackedNodes : EMPTY_ARR // a block is always going to be patched trackDynamicNode(vnode) return vnode } export function createVNode( type: VNodeTypes, props: { [key: string]: any } | null | 0 = null, children: any = null, patchFlag: number = 0, dynamicProps: string[] | null = null ): VNode { // Allow passing 0 for props, this can save bytes on generated code. props = props || null // encode the vnode type information into a bitmap const typeFlag = isString(type) ? ELEMENT : isObject(type) ? STATEFUL_COMPONENT : isFunction(type) ? FUNCTIONAL_COMPONENT : 0 const vnode: VNode = { type, props, key: props && props.key, ref: props && props.ref, children: null, component: null, el: null, anchor: null, target: null, shapeFlag: typeFlag, patchFlag, dynamicProps, dynamicChildren: null } normalizeChildren(vnode, children) // class & style normalization. if (props !== null) { // class normalization only needed if the vnode isn't generated by // compiler-optimized code if (props.class != null && !(patchFlag & CLASS)) { props.class = normalizeClass(props.class) } if (props.style != null) { props.style = normalizeStyle(props.style) } } // presence of a patch flag indicates this node is dynamic // component nodes also should always be tracked, because even if the // component doesn't need to update, it needs to persist the instance on to // the next vnode so that it can be properly unmounted later. if ( shouldTrack && (patchFlag || typeFlag & STATEFUL_COMPONENT || typeFlag & FUNCTIONAL_COMPONENT) ) { trackDynamicNode(vnode) } return vnode } function trackDynamicNode(vnode: VNode) { const currentBlockDynamicNodes = blockStack[blockStack.length - 1] if (currentBlockDynamicNodes != null) { currentBlockDynamicNodes.push(vnode) } } export function cloneVNode(vnode: VNode): VNode { // TODO return vnode } export function normalizeVNode(child: VNodeChild): VNode { if (child == null) { // empty placeholder return createVNode(Empty) } else if (isArray(child)) { // fragment return createVNode(Fragment, null, child) } else if (typeof child === 'object') { // already vnode, this should be the most common since compiled templates // always produce all-vnode children arrays return child as VNode } else { // primitive types return createVNode(Text, null, child + '') } } export function normalizeChildren(vnode: VNode, children: unknown) { let type = 0 if (children == null) { children = null } else if (isArray(children)) { type = ARRAY_CHILDREN } else if (typeof children === 'object') { type = SLOTS_CHILDREN } else if (isFunction(children)) { children = { default: children } type = SLOTS_CHILDREN } else { children = isString(children) ? children : children + '' type = TEXT_CHILDREN } vnode.children = children as NormalizedChildren vnode.shapeFlag |= type } function normalizeStyle( value: unknown ): Record | void { if (isArray(value)) { const res: Record = {} for (let i = 0; i < value.length; i++) { const normalized = normalizeStyle(value[i]) if (normalized) { for (const key in normalized) { res[key] = normalized[key] } } } return res } else if (isObject(value)) { return value } } export function normalizeClass(value: unknown): string { let res = '' if (isString(value)) { res = value } else if (isArray(value)) { for (let i = 0; i < value.length; i++) { res += normalizeClass(value[i]) + ' ' } } else if (isObject(value)) { for (const name in value) { if (value[name]) { res += name + ' ' } } } return res.trim() }