| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581 |
- import {
- isArray,
- isFunction,
- isString,
- isObject,
- EMPTY_ARR,
- extend,
- normalizeClass,
- normalizeStyle,
- PatchFlags,
- ShapeFlags
- } from '@vue/shared'
- import {
- ComponentInternalInstance,
- Data,
- Component,
- ClassComponent
- } from './component'
- import { RawSlots } from './componentSlots'
- import { isProxy, Ref, toRaw } from '@vue/reactivity'
- import { AppContext } from './apiCreateApp'
- import {
- SuspenseImpl,
- isSuspense,
- SuspenseBoundary
- } from './components/Suspense'
- import { DirectiveBinding } from './directives'
- import { TransitionHooks } from './components/BaseTransition'
- import { warn } from './warning'
- import { currentScopeId } from './helpers/scopeId'
- import { TeleportImpl, isTeleport } from './components/Teleport'
- import { currentRenderingInstance } from './componentRenderUtils'
- import { RendererNode, RendererElement } from './renderer'
- import { NULL_DYNAMIC_COMPONENT } from './helpers/resolveAssets'
- export const Fragment = (Symbol(__DEV__ ? 'Fragment' : undefined) as any) as {
- __isFragment: true
- new (): {
- $props: VNodeProps
- }
- }
- export const Text = Symbol(__DEV__ ? 'Text' : undefined)
- export const Comment = Symbol(__DEV__ ? 'Comment' : undefined)
- export const Static = Symbol(__DEV__ ? 'Static' : undefined)
- export type VNodeTypes =
- | string
- | Component
- | typeof Text
- | typeof Static
- | typeof Comment
- | typeof Fragment
- | typeof TeleportImpl
- | typeof SuspenseImpl
- export type VNodeRef =
- | string
- | Ref
- | ((ref: object | null, refs: Record<string, any>) => void)
- export type VNodeNormalizedRef = [ComponentInternalInstance, VNodeRef]
- type VNodeMountHook = (vnode: VNode) => void
- type VNodeUpdateHook = (vnode: VNode, oldVNode: VNode) => void
- export type VNodeHook =
- | VNodeMountHook
- | VNodeUpdateHook
- | VNodeMountHook[]
- | VNodeUpdateHook[]
- export interface VNodeProps {
- [key: string]: any
- key?: string | number
- ref?: VNodeRef
- // vnode hooks
- onVnodeBeforeMount?: VNodeMountHook | VNodeMountHook[]
- onVnodeMounted?: VNodeMountHook | VNodeMountHook[]
- onVnodeBeforeUpdate?: VNodeUpdateHook | VNodeUpdateHook[]
- onVnodeUpdated?: VNodeUpdateHook | VNodeUpdateHook[]
- onVnodeBeforeUnmount?: VNodeMountHook | VNodeMountHook[]
- onVnodeUnmounted?: VNodeMountHook | VNodeMountHook[]
- }
- type VNodeChildAtom =
- | VNode
- | string
- | number
- | boolean
- | null
- | undefined
- | void
- export interface VNodeArrayChildren
- extends Array<VNodeArrayChildren | VNodeChildAtom> {}
- export type VNodeChild = VNodeChildAtom | VNodeArrayChildren
- export type VNodeNormalizedChildren =
- | string
- | VNodeArrayChildren
- | RawSlots
- | null
- export interface VNode<HostNode = RendererNode, HostElement = RendererElement> {
- /**
- * @internal
- */
- __v_isVNode: true
- /**
- * @internal
- */
- __v_skip: true
- type: VNodeTypes
- props: VNodeProps | null
- key: string | number | null
- ref: VNodeNormalizedRef | null
- scopeId: string | null // SFC only
- children: VNodeNormalizedChildren
- component: ComponentInternalInstance | null
- suspense: SuspenseBoundary | null
- dirs: DirectiveBinding[] | null
- transition: TransitionHooks<HostElement> | null
- // DOM
- el: HostNode | null
- anchor: HostNode | null // fragment anchor
- target: HostElement | null // teleport target
- targetAnchor: HostNode | null // teleport target anchor
- staticCount: number // number of elements contained in a static vnode
- // optimization only
- shapeFlag: number
- patchFlag: number
- dynamicProps: string[] | null
- dynamicChildren: VNode[] | null
- // application root node only
- appContext: AppContext | 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)[] = []
- let currentBlock: VNode[] | null = 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:
- *
- * ```js
- * function render() {
- * return (openBlock(),createBlock('div', null, [...]))
- * }
- * ```
- * disableTracking is true when creating a v-for fragment block, since a v-for
- * fragment always diffs its children.
- *
- * @private
- */
- export function openBlock(disableTracking = false) {
- blockStack.push((currentBlock = disableTracking ? null : []))
- }
- // Whether we should be tracking dynamic child nodes inside a block.
- // Only tracks when this value is > 0
- // We are not using a simple boolean because this value may need to be
- // incremented/decremented by nested usage of v-once (see below)
- let shouldTrack = 1
- /**
- * Block tracking sometimes needs to be disabled, for example during the
- * creation of a tree that needs to be cached by v-once. The compiler generates
- * code like this:
- *
- * ``` js
- * _cache[1] || (
- * setBlockTracking(-1),
- * _cache[1] = createVNode(...),
- * setBlockTracking(1),
- * _cache[1]
- * )
- * ```
- *
- * @private
- */
- export function setBlockTracking(value: number) {
- shouldTrack += value
- }
- /**
- * 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.
- *
- * @private
- */
- export function createBlock(
- type: VNodeTypes | ClassComponent,
- props?: { [key: string]: any } | null,
- children?: any,
- patchFlag?: number,
- dynamicProps?: string[]
- ): VNode {
- const vnode = createVNode(
- type,
- props,
- children,
- patchFlag,
- dynamicProps,
- true /* isBlock: prevent a block from tracking itself */
- )
- // save current block children on the block vnode
- vnode.dynamicChildren = currentBlock || EMPTY_ARR
- // close block
- blockStack.pop()
- currentBlock = blockStack[blockStack.length - 1] || null
- // a block is always going to be patched, so track it as a child of its
- // parent block
- if (currentBlock) {
- currentBlock.push(vnode)
- }
- return vnode
- }
- export function isVNode(value: any): value is VNode {
- return value ? value.__v_isVNode === true : false
- }
- export function isSameVNodeType(n1: VNode, n2: VNode): boolean {
- if (
- __DEV__ &&
- n2.shapeFlag & ShapeFlags.COMPONENT &&
- (n2.type as Component).__hmrUpdated
- ) {
- // HMR only: if the component has been hot-updated, force a reload.
- return false
- }
- return n1.type === n2.type && n1.key === n2.key
- }
- let vnodeArgsTransformer:
- | ((
- args: Parameters<typeof _createVNode>,
- instance: ComponentInternalInstance | null
- ) => Parameters<typeof _createVNode>)
- | undefined
- /**
- * Internal API for registering an arguments transform for createVNode
- * used for creating stubs in the test-utils
- * It is *internal* but needs to be exposed for test-utils to pick up proper
- * typings
- */
- export function transformVNodeArgs(transformer?: typeof vnodeArgsTransformer) {
- vnodeArgsTransformer = transformer
- }
- const createVNodeWithArgsTransform = (
- ...args: Parameters<typeof _createVNode>
- ): VNode => {
- return _createVNode(
- ...(vnodeArgsTransformer
- ? vnodeArgsTransformer(args, currentRenderingInstance)
- : args)
- )
- }
- export const InternalObjectKey = `__vInternal`
- const normalizeKey = ({ key }: VNodeProps): VNode['key'] =>
- key != null ? key : null
- const normalizeRef = ({ ref }: VNodeProps): VNode['ref'] =>
- (ref != null
- ? isArray(ref)
- ? ref
- : [currentRenderingInstance!, ref]
- : null) as any
- export const createVNode = (__DEV__
- ? createVNodeWithArgsTransform
- : _createVNode) as typeof _createVNode
- function _createVNode(
- type: VNodeTypes | ClassComponent | typeof NULL_DYNAMIC_COMPONENT,
- props: (Data & VNodeProps) | null = null,
- children: unknown = null,
- patchFlag: number = 0,
- dynamicProps: string[] | null = null,
- isBlockNode = false
- ): VNode {
- if (!type || type === NULL_DYNAMIC_COMPONENT) {
- if (__DEV__ && !type) {
- warn(`Invalid vnode type when creating vnode: ${type}.`)
- }
- type = Comment
- }
- // class component normalization.
- if (isFunction(type) && '__vccOpts' in type) {
- type = type.__vccOpts
- }
- // class & style normalization.
- if (props) {
- // for reactive or proxy objects, we need to clone it to enable mutation.
- if (isProxy(props) || InternalObjectKey in props) {
- props = extend({}, props)
- }
- let { class: klass, style } = props
- if (klass && !isString(klass)) {
- props.class = normalizeClass(klass)
- }
- if (isObject(style)) {
- // reactive state objects need to be cloned since they are likely to be
- // mutated
- if (isProxy(style) && !isArray(style)) {
- style = extend({}, style)
- }
- props.style = normalizeStyle(style)
- }
- }
- // encode the vnode type information into a bitmap
- const shapeFlag = isString(type)
- ? ShapeFlags.ELEMENT
- : __FEATURE_SUSPENSE__ && isSuspense(type)
- ? ShapeFlags.SUSPENSE
- : isTeleport(type)
- ? ShapeFlags.TELEPORT
- : isObject(type)
- ? ShapeFlags.STATEFUL_COMPONENT
- : isFunction(type)
- ? ShapeFlags.FUNCTIONAL_COMPONENT
- : 0
- if (__DEV__ && shapeFlag & ShapeFlags.STATEFUL_COMPONENT && isProxy(type)) {
- type = toRaw(type)
- warn(
- `Vue received a Component which was made a reactive object. This can ` +
- `lead to unnecessary performance overhead, and should be avoided by ` +
- `marking the component with \`markRaw\` or using \`shallowRef\` ` +
- `instead of \`ref\`.`,
- `\nComponent that was made reactive: `,
- type
- )
- }
- const vnode: VNode = {
- __v_isVNode: true,
- __v_skip: true,
- type,
- props,
- key: props && normalizeKey(props),
- ref: props && normalizeRef(props),
- scopeId: currentScopeId,
- children: null,
- component: null,
- suspense: null,
- dirs: null,
- transition: null,
- el: null,
- anchor: null,
- target: null,
- targetAnchor: null,
- staticCount: 0,
- shapeFlag,
- patchFlag,
- dynamicProps,
- dynamicChildren: null,
- appContext: null
- }
- normalizeChildren(vnode, children)
- // presence of a patch flag indicates this node needs patching on updates.
- // component nodes also should always be patched, 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 > 0 &&
- !isBlockNode &&
- currentBlock &&
- // the EVENTS flag is only for hydration and if it is the only flag, the
- // vnode should not be considered dynamic due to handler caching.
- patchFlag !== PatchFlags.HYDRATE_EVENTS &&
- (patchFlag > 0 ||
- shapeFlag & ShapeFlags.SUSPENSE ||
- shapeFlag & ShapeFlags.TELEPORT ||
- shapeFlag & ShapeFlags.STATEFUL_COMPONENT ||
- shapeFlag & ShapeFlags.FUNCTIONAL_COMPONENT)
- ) {
- currentBlock.push(vnode)
- }
- return vnode
- }
- export function cloneVNode<T, U>(
- vnode: VNode<T, U>,
- extraProps?: Data & VNodeProps
- ): VNode<T, U> {
- const props = (extraProps
- ? vnode.props
- ? mergeProps(vnode.props, extraProps)
- : extend({}, extraProps)
- : vnode.props) as any
- // This is intentionally NOT using spread or extend to avoid the runtime
- // key enumeration cost.
- return {
- __v_isVNode: true,
- __v_skip: true,
- type: vnode.type,
- props,
- key: props && normalizeKey(props),
- ref: props && normalizeRef(props),
- scopeId: vnode.scopeId,
- children: vnode.children,
- target: vnode.target,
- targetAnchor: vnode.targetAnchor,
- staticCount: vnode.staticCount,
- shapeFlag: vnode.shapeFlag,
- // if the vnode is cloned with extra props, we can no longer assume its
- // existing patch flag to be reliable and need to bail out of optimized mode.
- // however we don't want block nodes to de-opt their children, so if the
- // vnode is a block node, we only add the FULL_PROPS flag to it.
- patchFlag: extraProps
- ? vnode.dynamicChildren
- ? vnode.patchFlag | PatchFlags.FULL_PROPS
- : PatchFlags.BAIL
- : vnode.patchFlag,
- dynamicProps: vnode.dynamicProps,
- dynamicChildren: vnode.dynamicChildren,
- appContext: vnode.appContext,
- dirs: vnode.dirs,
- transition: vnode.transition,
- // These should technically only be non-null on mounted VNodes. However,
- // they *should* be copied for kept-alive vnodes. So we just always copy
- // them since them being non-null during a mount doesn't affect the logic as
- // they will simply be overwritten.
- component: vnode.component,
- suspense: vnode.suspense,
- el: vnode.el,
- anchor: vnode.anchor
- }
- }
- /**
- * @private
- */
- export function createTextVNode(text: string = ' ', flag: number = 0): VNode {
- return createVNode(Text, null, text, flag)
- }
- /**
- * @private
- */
- export function createStaticVNode(
- content: string,
- numberOfNodes: number
- ): VNode {
- // A static vnode can contain multiple stringified elements, and the number
- // of elements is necessary for hydration.
- const vnode = createVNode(Static, null, content)
- vnode.staticCount = numberOfNodes
- return vnode
- }
- /**
- * @private
- */
- export function createCommentVNode(
- text: string = '',
- // when used as the v-else branch, the comment node must be created as a
- // block to ensure correct updates.
- asBlock: boolean = false
- ): VNode {
- return asBlock
- ? (openBlock(), createBlock(Comment, null, text))
- : createVNode(Comment, null, text)
- }
- export function normalizeVNode(child: VNodeChild): VNode {
- if (child == null || typeof child === 'boolean') {
- // empty placeholder
- return createVNode(Comment)
- } 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.el === null ? child : cloneVNode(child)
- } else {
- // strings and numbers
- return createVNode(Text, null, String(child))
- }
- }
- // optimized normalization for template-compiled render fns
- export function cloneIfMounted(child: VNode): VNode {
- return child.el === null ? child : cloneVNode(child)
- }
- export function normalizeChildren(vnode: VNode, children: unknown) {
- let type = 0
- const { shapeFlag } = vnode
- if (children == null) {
- children = null
- } else if (isArray(children)) {
- type = ShapeFlags.ARRAY_CHILDREN
- } else if (typeof children === 'object') {
- // Normalize slot to plain children
- if (
- (shapeFlag & ShapeFlags.ELEMENT || shapeFlag & ShapeFlags.TELEPORT) &&
- (children as any).default
- ) {
- normalizeChildren(vnode, (children as any).default())
- return
- } else {
- type = ShapeFlags.SLOTS_CHILDREN
- if (!(children as RawSlots)._ && !(InternalObjectKey in children!)) {
- // if slots are not normalized, attach context instance
- // (compiled / normalized slots already have context)
- ;(children as RawSlots)._ctx = currentRenderingInstance
- }
- }
- } else if (isFunction(children)) {
- children = { default: children, _ctx: currentRenderingInstance }
- type = ShapeFlags.SLOTS_CHILDREN
- } else {
- children = String(children)
- // force teleport children to array so it can be moved around
- if (shapeFlag & ShapeFlags.TELEPORT) {
- type = ShapeFlags.ARRAY_CHILDREN
- children = [createTextVNode(children as string)]
- } else {
- type = ShapeFlags.TEXT_CHILDREN
- }
- }
- vnode.children = children as VNodeNormalizedChildren
- vnode.shapeFlag |= type
- }
- const handlersRE = /^on|^vnode/
- export function mergeProps(...args: (Data & VNodeProps)[]) {
- const ret: Data = {}
- extend(ret, args[0])
- for (let i = 1; i < args.length; i++) {
- const toMerge = args[i]
- for (const key in toMerge) {
- if (key === 'class') {
- if (ret.class !== toMerge.class) {
- ret.class = normalizeClass([ret.class, toMerge.class])
- }
- } else if (key === 'style') {
- ret.style = normalizeStyle([ret.style, toMerge.style])
- } else if (handlersRE.test(key)) {
- // on*, vnode*
- const existing = ret[key]
- const incoming = toMerge[key]
- if (existing !== incoming) {
- ret[key] = existing
- ? [].concat(existing as any, toMerge[key] as any)
- : incoming
- }
- } else {
- ret[key] = toMerge[key]
- }
- }
- }
- return ret
- }
|