2019-06-01 02:14:49 +08:00
|
|
|
import { isArray, isFunction, isString, isObject, EMPTY_ARR } from '@vue/shared'
|
2019-05-28 19:36:15 +08:00
|
|
|
import { ComponentInstance } from './component'
|
2019-05-28 17:19:47 +08:00
|
|
|
import { HostNode } from './createRenderer'
|
2019-05-31 18:07:43 +08:00
|
|
|
import { RawSlots } from './componentSlots'
|
2019-06-01 17:44:06 +08:00
|
|
|
import { CLASS } from './patchFlags'
|
2019-05-28 10:28:25 +08:00
|
|
|
|
2019-05-25 23:51:20 +08:00
|
|
|
export const Fragment = Symbol('Fragment')
|
|
|
|
export const Text = Symbol('Text')
|
|
|
|
export const Empty = Symbol('Empty')
|
2019-05-28 17:19:47 +08:00
|
|
|
export const Portal = Symbol('Portal')
|
2018-09-19 23:35:38 +08:00
|
|
|
|
2019-05-25 23:51:20 +08:00
|
|
|
type VNodeTypes =
|
|
|
|
| string
|
|
|
|
| Function
|
2019-05-28 17:19:47 +08:00
|
|
|
| Object
|
2019-05-25 23:51:20 +08:00
|
|
|
| typeof Fragment
|
|
|
|
| typeof Text
|
|
|
|
| typeof Empty
|
2018-09-19 23:35:38 +08:00
|
|
|
|
2019-05-28 17:19:47 +08:00
|
|
|
type VNodeChildAtom = VNode | string | number | null | void
|
|
|
|
export interface VNodeChildren extends Array<VNodeChildren | VNodeChildAtom> {}
|
|
|
|
export type VNodeChild = VNodeChildAtom | VNodeChildren
|
2018-10-13 07:49:41 +08:00
|
|
|
|
2019-05-31 18:07:43 +08:00
|
|
|
export type NormalizedChildren = string | VNodeChildren | RawSlots | null
|
|
|
|
|
2019-05-25 23:51:20 +08:00
|
|
|
export interface VNode {
|
|
|
|
type: VNodeTypes
|
|
|
|
props: { [key: string]: any } | null
|
|
|
|
key: string | number | null
|
2019-05-31 18:07:43 +08:00
|
|
|
children: NormalizedChildren
|
2019-05-28 19:36:15 +08:00
|
|
|
component: ComponentInstance | null
|
2019-05-28 13:27:31 +08:00
|
|
|
|
|
|
|
// DOM
|
2019-05-28 17:19:47 +08:00
|
|
|
el: HostNode | null
|
|
|
|
anchor: HostNode | null // fragment anchor
|
2019-05-29 16:10:25 +08:00
|
|
|
target: HostNode | null // portal target
|
2019-05-28 13:27:31 +08:00
|
|
|
|
|
|
|
// optimization only
|
2019-06-01 17:43:41 +08:00
|
|
|
patchFlag: number
|
2019-05-25 23:51:20 +08:00
|
|
|
dynamicProps: string[] | null
|
|
|
|
dynamicChildren: VNode[] | null
|
2018-10-13 07:49:41 +08:00
|
|
|
}
|
|
|
|
|
2019-05-30 21:24:40 +08:00
|
|
|
// 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).
|
2019-05-27 13:48:40 +08:00
|
|
|
const blockStack: (VNode[] | null)[] = []
|
2018-09-19 23:35:38 +08:00
|
|
|
|
2019-05-30 21:24:40 +08:00
|
|
|
// 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, [...]))
|
|
|
|
// }
|
2019-05-27 13:48:40 +08:00
|
|
|
export function openBlock(disableTrackng?: boolean) {
|
|
|
|
blockStack.push(disableTrackng ? null : [])
|
2019-05-25 23:51:20 +08:00
|
|
|
}
|
2018-10-13 07:49:41 +08:00
|
|
|
|
2019-05-25 23:51:20 +08:00
|
|
|
let shouldTrack = true
|
2018-10-13 07:49:41 +08:00
|
|
|
|
2019-05-30 21:24:40 +08:00
|
|
|
// 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.
|
2019-05-25 23:51:20 +08:00
|
|
|
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
|
2019-05-27 13:48:40 +08:00
|
|
|
const trackedNodes = blockStack.pop()
|
|
|
|
vnode.dynamicChildren =
|
2019-05-29 16:10:25 +08:00
|
|
|
trackedNodes && trackedNodes.length ? trackedNodes : EMPTY_ARR
|
2019-05-25 23:51:20 +08:00
|
|
|
// a block is always going to be patched
|
|
|
|
trackDynamicNode(vnode)
|
|
|
|
return vnode
|
2018-10-13 01:42:19 +08:00
|
|
|
}
|
|
|
|
|
2019-05-25 23:51:20 +08:00
|
|
|
export function createVNode(
|
|
|
|
type: VNodeTypes,
|
2019-06-01 02:14:49 +08:00
|
|
|
props: { [key: string]: any } | null | 0 = null,
|
2019-05-25 23:51:20 +08:00
|
|
|
children: any = null,
|
2019-06-01 17:43:41 +08:00
|
|
|
patchFlag: number = 0,
|
2019-05-25 23:51:20 +08:00
|
|
|
dynamicProps: string[] | null = null
|
|
|
|
): VNode {
|
2019-06-01 17:43:41 +08:00
|
|
|
// Allow passing 0 for props, this can save bytes on generated code.
|
|
|
|
props = props || null
|
2019-05-25 23:51:20 +08:00
|
|
|
const vnode: VNode = {
|
|
|
|
type,
|
2019-06-01 17:43:41 +08:00
|
|
|
props,
|
2019-05-25 23:51:20 +08:00
|
|
|
key: props && props.key,
|
2019-05-31 18:07:43 +08:00
|
|
|
children: normalizeChildren(children),
|
2019-05-28 13:27:31 +08:00
|
|
|
component: null,
|
2019-05-27 13:48:40 +08:00
|
|
|
el: null,
|
|
|
|
anchor: null,
|
2019-05-29 16:10:25 +08:00
|
|
|
target: null,
|
2019-05-25 23:51:20 +08:00
|
|
|
patchFlag,
|
|
|
|
dynamicProps,
|
|
|
|
dynamicChildren: null
|
2018-09-19 23:35:38 +08:00
|
|
|
}
|
2019-06-01 17:43:41 +08:00
|
|
|
|
|
|
|
// 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)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-05-30 21:24:40 +08:00
|
|
|
// presence of a patch flag indicates this node is dynamic
|
2019-06-01 02:14:49 +08:00
|
|
|
// 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.
|
2019-06-01 17:43:41 +08:00
|
|
|
if (shouldTrack && (patchFlag || isObject(type) || isFunction(type))) {
|
2019-05-25 23:51:20 +08:00
|
|
|
trackDynamicNode(vnode)
|
2018-09-25 07:11:14 +08:00
|
|
|
}
|
2019-06-01 17:43:41 +08:00
|
|
|
|
2019-05-25 23:51:20 +08:00
|
|
|
return vnode
|
|
|
|
}
|
2018-09-19 23:35:38 +08:00
|
|
|
|
2019-05-25 23:51:20 +08:00
|
|
|
function trackDynamicNode(vnode: VNode) {
|
|
|
|
const currentBlockDynamicNodes = blockStack[blockStack.length - 1]
|
2019-05-27 13:48:40 +08:00
|
|
|
if (currentBlockDynamicNodes != null) {
|
2019-05-25 23:51:20 +08:00
|
|
|
currentBlockDynamicNodes.push(vnode)
|
2018-09-19 23:35:38 +08:00
|
|
|
}
|
2019-05-25 23:51:20 +08:00
|
|
|
}
|
2018-09-19 23:35:38 +08:00
|
|
|
|
2019-05-25 23:51:20 +08:00
|
|
|
export function cloneVNode(vnode: VNode): VNode {
|
|
|
|
// TODO
|
2019-05-26 15:19:44 +08:00
|
|
|
return vnode
|
2019-05-25 23:51:20 +08:00
|
|
|
}
|
2019-05-28 13:27:31 +08:00
|
|
|
|
2019-05-28 17:19:47 +08:00
|
|
|
export function normalizeVNode(child: VNodeChild): VNode {
|
2019-05-28 13:27:31 +08:00
|
|
|
if (child == null) {
|
|
|
|
// empty placeholder
|
|
|
|
return createVNode(Empty)
|
|
|
|
} else if (isArray(child)) {
|
|
|
|
// fragment
|
|
|
|
return createVNode(Fragment, null, child)
|
|
|
|
} else if (typeof child === 'object') {
|
2019-05-31 18:07:43 +08:00
|
|
|
// already vnode, this should be the most common since compiled templates
|
|
|
|
// always produce all-vnode children arrays
|
2019-05-28 13:27:31 +08:00
|
|
|
return child as VNode
|
|
|
|
} else {
|
|
|
|
// primitive types
|
|
|
|
return createVNode(Text, null, child + '')
|
|
|
|
}
|
|
|
|
}
|
2019-05-31 18:07:43 +08:00
|
|
|
|
|
|
|
export function normalizeChildren(children: unknown): NormalizedChildren {
|
|
|
|
if (children == null) {
|
|
|
|
return null
|
|
|
|
} else if (isArray(children)) {
|
|
|
|
return children
|
|
|
|
} else if (typeof children === 'object') {
|
|
|
|
return children as RawSlots
|
|
|
|
} else if (isFunction(children)) {
|
|
|
|
return { default: children }
|
|
|
|
} else {
|
|
|
|
return isString(children) ? children : children + ''
|
|
|
|
}
|
|
|
|
}
|
2019-06-01 17:43:41 +08:00
|
|
|
|
|
|
|
function normalizeStyle(
|
|
|
|
value: unknown
|
|
|
|
): Record<string, string | number> | void {
|
|
|
|
if (isArray(value)) {
|
|
|
|
const res: Record<string, string | number> = {}
|
|
|
|
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
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
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()
|
|
|
|
}
|