2019-08-23 10:07:51 +08:00
|
|
|
import {
|
|
|
|
isArray,
|
|
|
|
isFunction,
|
|
|
|
isString,
|
|
|
|
isObject,
|
|
|
|
EMPTY_ARR,
|
2019-10-12 03:09:37 +08:00
|
|
|
extend
|
2019-08-23 10:07:51 +08:00
|
|
|
} from '@vue/shared'
|
2019-10-08 21:26:09 +08:00
|
|
|
import {
|
|
|
|
ComponentInternalInstance,
|
|
|
|
Data,
|
|
|
|
SetupProxySymbol,
|
|
|
|
Component
|
|
|
|
} from './component'
|
2019-05-31 18:07:43 +08:00
|
|
|
import { RawSlots } from './componentSlots'
|
2019-08-22 23:12:37 +08:00
|
|
|
import { ShapeFlags } from './shapeFlags'
|
2019-08-23 10:07:51 +08:00
|
|
|
import { isReactive } from '@vue/reactivity'
|
2019-09-04 06:11:04 +08:00
|
|
|
import { AppContext } from './apiApp'
|
2019-10-30 00:30:09 +08:00
|
|
|
import { SuspenseBoundary, isSuspenseType } from './suspense'
|
2019-10-27 04:00:07 +08:00
|
|
|
import { DirectiveBinding } from './directives'
|
2019-10-30 00:30:09 +08:00
|
|
|
import { SuspenseImpl } from './suspense'
|
2019-05-28 10:28:25 +08:00
|
|
|
|
2019-10-26 22:51:55 +08:00
|
|
|
export const Fragment = Symbol(__DEV__ ? 'Fragment' : undefined)
|
|
|
|
export const Portal = Symbol(__DEV__ ? 'Portal' : undefined)
|
|
|
|
export const Text = Symbol(__DEV__ ? 'Text' : undefined)
|
|
|
|
export const Comment = Symbol(__DEV__ ? 'Comment' : undefined)
|
2018-09-19 23:35:38 +08:00
|
|
|
|
2019-10-30 02:04:44 +08:00
|
|
|
const Suspense = (__FEATURE_SUSPENSE__
|
|
|
|
? SuspenseImpl
|
|
|
|
: null) as typeof SuspenseImpl
|
2019-10-30 00:30:09 +08:00
|
|
|
export { Suspense }
|
|
|
|
|
2019-08-24 03:27:17 +08:00
|
|
|
export type VNodeTypes =
|
2019-05-25 23:51:20 +08:00
|
|
|
| string
|
2019-10-08 21:26:09 +08:00
|
|
|
| Component
|
2019-05-25 23:51:20 +08:00
|
|
|
| typeof Fragment
|
2019-09-05 23:11:33 +08:00
|
|
|
| typeof Portal
|
2019-05-25 23:51:20 +08:00
|
|
|
| typeof Text
|
2019-09-25 02:37:14 +08:00
|
|
|
| typeof Comment
|
2019-10-30 00:30:09 +08:00
|
|
|
| typeof SuspenseImpl
|
2018-09-19 23:35:38 +08:00
|
|
|
|
2019-09-07 04:58:32 +08:00
|
|
|
type VNodeChildAtom<HostNode, HostElement> =
|
|
|
|
| VNode<HostNode, HostElement>
|
|
|
|
| string
|
|
|
|
| number
|
|
|
|
| boolean
|
|
|
|
| null
|
|
|
|
| void
|
|
|
|
|
|
|
|
export interface VNodeChildren<HostNode = any, HostElement = any>
|
|
|
|
extends Array<
|
|
|
|
| VNodeChildren<HostNode, HostElement>
|
|
|
|
| VNodeChildAtom<HostNode, HostElement>
|
|
|
|
> {}
|
2018-10-13 07:49:41 +08:00
|
|
|
|
2019-09-07 04:58:32 +08:00
|
|
|
export type VNodeChild<HostNode = any, HostElement = any> =
|
|
|
|
| VNodeChildAtom<HostNode, HostElement>
|
|
|
|
| VNodeChildren<HostNode, HostElement>
|
|
|
|
|
2019-09-07 23:45:32 +08:00
|
|
|
export type NormalizedChildren<HostNode = any, HostElement = any> =
|
2019-09-07 04:58:32 +08:00
|
|
|
| string
|
|
|
|
| VNodeChildren<HostNode, HostElement>
|
|
|
|
| RawSlots
|
|
|
|
| null
|
2019-05-31 18:07:43 +08:00
|
|
|
|
2019-09-07 04:58:32 +08:00
|
|
|
export interface VNode<HostNode = any, HostElement = any> {
|
2019-09-30 22:45:50 +08:00
|
|
|
_isVNode: true
|
2019-05-25 23:51:20 +08:00
|
|
|
type: VNodeTypes
|
2019-09-01 04:36:36 +08:00
|
|
|
props: Record<any, any> | null
|
2019-05-25 23:51:20 +08:00
|
|
|
key: string | number | null
|
2019-06-03 13:44:45 +08:00
|
|
|
ref: string | Function | null
|
2019-09-07 04:58:32 +08:00
|
|
|
children: NormalizedChildren<HostNode, HostElement>
|
2019-09-07 00:58:31 +08:00
|
|
|
component: ComponentInternalInstance | null
|
2019-09-10 01:59:53 +08:00
|
|
|
suspense: SuspenseBoundary<HostNode, HostElement> | null
|
2019-10-27 04:00:07 +08:00
|
|
|
dirs: DirectiveBinding[] | 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-09-07 04:58:32 +08:00
|
|
|
target: HostElement | null // portal target
|
2019-05-28 13:27:31 +08:00
|
|
|
|
|
|
|
// optimization only
|
2019-06-02 16:35:19 +08:00
|
|
|
shapeFlag: number
|
2019-06-01 17:43:41 +08:00
|
|
|
patchFlag: number
|
2019-05-25 23:51:20 +08:00
|
|
|
dynamicProps: string[] | null
|
|
|
|
dynamicChildren: VNode[] | null
|
2019-09-03 04:09:34 +08:00
|
|
|
|
|
|
|
// application root node only
|
|
|
|
appContext: AppContext | 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)[] = []
|
2019-10-21 23:30:45 +08:00
|
|
|
let currentBlock: VNode[] | null = 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-06-03 13:44:45 +08:00
|
|
|
//
|
|
|
|
// disableTracking is true when creating a fragment block, since a fragment
|
|
|
|
// always diffs its children.
|
2019-10-05 22:38:02 +08:00
|
|
|
export function openBlock(disableTracking?: boolean) {
|
2019-10-21 23:30:45 +08:00
|
|
|
blockStack.push((currentBlock = disableTracking ? null : []))
|
2019-05-25 23:51:20 +08:00
|
|
|
}
|
2018-10-13 07:49:41 +08:00
|
|
|
|
2019-10-24 05:57:40 +08:00
|
|
|
// 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:
|
|
|
|
// _cache[1] || (
|
|
|
|
// setBlockTracking(-1),
|
|
|
|
// _cache[1] = createVNode(...),
|
|
|
|
// setBlockTracking(1),
|
|
|
|
// _cache[1]
|
|
|
|
// )
|
|
|
|
export function setBlockTracking(value: number) {
|
|
|
|
shouldTrack += value
|
|
|
|
}
|
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 {
|
2019-10-21 23:30:45 +08:00
|
|
|
// avoid a block with patchFlag tracking itself
|
2019-10-24 05:57:40 +08:00
|
|
|
shouldTrack--
|
2019-05-25 23:51:20 +08:00
|
|
|
const vnode = createVNode(type, props, children, patchFlag, dynamicProps)
|
2019-10-24 05:57:40 +08:00
|
|
|
shouldTrack++
|
2019-10-21 23:30:45 +08:00
|
|
|
// 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 !== null) {
|
|
|
|
currentBlock.push(vnode)
|
|
|
|
}
|
2019-05-25 23:51:20 +08:00
|
|
|
return vnode
|
2018-10-13 01:42:19 +08:00
|
|
|
}
|
|
|
|
|
2019-10-10 22:17:16 +08:00
|
|
|
export function isVNode(value: any): value is VNode {
|
2019-09-30 22:52:50 +08:00
|
|
|
return value ? value._isVNode === true : false
|
2019-09-12 11:44:37 +08:00
|
|
|
}
|
|
|
|
|
2019-05-25 23:51:20 +08:00
|
|
|
export function createVNode(
|
|
|
|
type: VNodeTypes,
|
2019-10-01 09:17:12 +08:00
|
|
|
props: { [key: string]: any } | null = null,
|
|
|
|
children: unknown = 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-08-23 10:07:51 +08:00
|
|
|
// class & style normalization.
|
|
|
|
if (props !== null) {
|
|
|
|
// for reactive or proxy objects, we need to clone it to enable mutation.
|
|
|
|
if (isReactive(props) || SetupProxySymbol in props) {
|
|
|
|
props = extend({}, props)
|
|
|
|
}
|
2019-10-21 23:30:45 +08:00
|
|
|
let { class: klass, style } = props
|
|
|
|
if (klass != null && !isString(klass)) {
|
|
|
|
props.class = normalizeClass(klass)
|
2019-08-23 10:07:51 +08:00
|
|
|
}
|
|
|
|
if (style != null) {
|
|
|
|
// reactive state objects need to be cloned since they are likely to be
|
|
|
|
// mutated
|
|
|
|
if (isReactive(style) && !isArray(style)) {
|
|
|
|
style = extend({}, style)
|
|
|
|
}
|
|
|
|
props.style = normalizeStyle(style)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-06-02 22:22:44 +08:00
|
|
|
// encode the vnode type information into a bitmap
|
2019-08-22 23:12:37 +08:00
|
|
|
const shapeFlag = isString(type)
|
|
|
|
? ShapeFlags.ELEMENT
|
2019-10-30 00:30:09 +08:00
|
|
|
: __FEATURE_SUSPENSE__ && isSuspenseType(type)
|
|
|
|
? ShapeFlags.SUSPENSE
|
|
|
|
: isObject(type)
|
|
|
|
? ShapeFlags.STATEFUL_COMPONENT
|
|
|
|
: isFunction(type)
|
|
|
|
? ShapeFlags.FUNCTIONAL_COMPONENT
|
|
|
|
: 0
|
2019-06-02 19:40:50 +08:00
|
|
|
|
2019-05-25 23:51:20 +08:00
|
|
|
const vnode: VNode = {
|
2019-09-30 22:45:50 +08:00
|
|
|
_isVNode: true,
|
2019-05-25 23:51:20 +08:00
|
|
|
type,
|
2019-06-01 17:43:41 +08:00
|
|
|
props,
|
2019-10-21 23:30:45 +08:00
|
|
|
key: (props !== null && props.key) || null,
|
|
|
|
ref: (props !== null && props.ref) || null,
|
2019-06-02 22:22:44 +08:00
|
|
|
children: null,
|
2019-05-28 13:27:31 +08:00
|
|
|
component: null,
|
2019-09-07 23:28:40 +08:00
|
|
|
suspense: null,
|
2019-10-27 04:00:07 +08:00
|
|
|
dirs: null,
|
2019-05-27 13:48:40 +08:00
|
|
|
el: null,
|
|
|
|
anchor: null,
|
2019-05-29 16:10:25 +08:00
|
|
|
target: null,
|
2019-08-22 23:12:37 +08:00
|
|
|
shapeFlag,
|
2019-05-25 23:51:20 +08:00
|
|
|
patchFlag,
|
|
|
|
dynamicProps,
|
2019-09-03 04:09:34 +08:00
|
|
|
dynamicChildren: null,
|
|
|
|
appContext: null
|
2018-09-19 23:35:38 +08:00
|
|
|
}
|
2019-06-01 17:43:41 +08:00
|
|
|
|
2019-06-02 22:22:44 +08:00
|
|
|
normalizeChildren(vnode, children)
|
|
|
|
|
2019-09-01 10:17:46 +08:00
|
|
|
// presence of a patch flag indicates this node needs patching on updates.
|
|
|
|
// component nodes also should always be patched, because even if the
|
2019-06-01 02:14:49 +08:00
|
|
|
// 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-02 16:35:19 +08:00
|
|
|
if (
|
2019-10-24 05:57:40 +08:00
|
|
|
shouldTrack > 0 &&
|
2019-10-21 23:30:45 +08:00
|
|
|
currentBlock !== null &&
|
|
|
|
(patchFlag > 0 ||
|
2019-08-22 23:12:37 +08:00
|
|
|
shapeFlag & ShapeFlags.STATEFUL_COMPONENT ||
|
|
|
|
shapeFlag & ShapeFlags.FUNCTIONAL_COMPONENT)
|
2019-06-02 16:35:19 +08:00
|
|
|
) {
|
2019-10-21 23:30:45 +08:00
|
|
|
currentBlock.push(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-10-28 08:54:54 +08:00
|
|
|
export function cloneVNode<T, U>(
|
|
|
|
vnode: VNode<T, U>,
|
|
|
|
extraProps?: Data
|
|
|
|
): VNode<T, U> {
|
2019-10-28 08:45:54 +08:00
|
|
|
// This is intentionally NOT using spread or extend to avoid the runtime
|
|
|
|
// key enumeration cost.
|
2019-08-23 05:12:39 +08:00
|
|
|
return {
|
2019-09-30 22:45:50 +08:00
|
|
|
_isVNode: true,
|
2019-08-23 05:12:39 +08:00
|
|
|
type: vnode.type,
|
2019-10-26 00:12:17 +08:00
|
|
|
props: extraProps
|
|
|
|
? vnode.props
|
|
|
|
? mergeProps(vnode.props, extraProps)
|
|
|
|
: extraProps
|
|
|
|
: vnode.props,
|
2019-08-23 05:12:39 +08:00
|
|
|
key: vnode.key,
|
|
|
|
ref: vnode.ref,
|
2019-09-03 00:09:29 +08:00
|
|
|
children: vnode.children,
|
|
|
|
target: vnode.target,
|
2019-08-23 05:12:39 +08:00
|
|
|
shapeFlag: vnode.shapeFlag,
|
|
|
|
patchFlag: vnode.patchFlag,
|
|
|
|
dynamicProps: vnode.dynamicProps,
|
2019-09-03 00:09:29 +08:00
|
|
|
dynamicChildren: vnode.dynamicChildren,
|
2019-09-03 04:09:34 +08:00
|
|
|
appContext: vnode.appContext,
|
2019-10-27 04:00:07 +08:00
|
|
|
dirs: vnode.dirs,
|
2019-09-03 00:09:29 +08:00
|
|
|
|
|
|
|
// these should be set to null since they should only be present on
|
|
|
|
// mounted VNodes. If they are somehow not null, this means we have
|
|
|
|
// encountered an already-mounted vnode being used again.
|
|
|
|
component: null,
|
2019-09-07 23:28:40 +08:00
|
|
|
suspense: null,
|
2019-09-03 00:09:29 +08:00
|
|
|
el: null,
|
|
|
|
anchor: null
|
2019-08-23 05:12:39 +08:00
|
|
|
}
|
2019-05-25 23:51:20 +08:00
|
|
|
}
|
2019-05-28 13:27:31 +08:00
|
|
|
|
2019-10-25 05:55:00 +08:00
|
|
|
export function createTextVNode(text: string = ' ', flag: number = 0): VNode {
|
|
|
|
return createVNode(Text, null, text, flag)
|
|
|
|
}
|
|
|
|
|
2019-10-25 09:19:02 +08:00
|
|
|
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
|
|
|
|
? createBlock(Comment, null, text)
|
|
|
|
: createVNode(Comment, null, text)
|
2019-10-25 05:55:00 +08:00
|
|
|
}
|
|
|
|
|
2019-10-28 08:54:54 +08:00
|
|
|
export function normalizeVNode<T, U>(child: VNodeChild<T, U>): VNode<T, U> {
|
2019-05-28 13:27:31 +08:00
|
|
|
if (child == null) {
|
|
|
|
// empty placeholder
|
2019-09-25 02:37:14 +08:00
|
|
|
return createVNode(Comment)
|
2019-05-28 13:27:31 +08:00
|
|
|
} 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-08-23 05:12:39 +08:00
|
|
|
return child.el === null ? child : cloneVNode(child)
|
2019-05-28 13:27:31 +08:00
|
|
|
} else {
|
|
|
|
// primitive types
|
|
|
|
return createVNode(Text, null, child + '')
|
|
|
|
}
|
|
|
|
}
|
2019-05-31 18:07:43 +08:00
|
|
|
|
2019-06-02 22:22:44 +08:00
|
|
|
export function normalizeChildren(vnode: VNode, children: unknown) {
|
|
|
|
let type = 0
|
2019-05-31 18:07:43 +08:00
|
|
|
if (children == null) {
|
2019-06-02 22:22:44 +08:00
|
|
|
children = null
|
2019-05-31 18:07:43 +08:00
|
|
|
} else if (isArray(children)) {
|
2019-08-22 23:12:37 +08:00
|
|
|
type = ShapeFlags.ARRAY_CHILDREN
|
2019-05-31 18:07:43 +08:00
|
|
|
} else if (typeof children === 'object') {
|
2019-08-22 23:12:37 +08:00
|
|
|
type = ShapeFlags.SLOTS_CHILDREN
|
2019-05-31 18:07:43 +08:00
|
|
|
} else if (isFunction(children)) {
|
2019-06-02 22:22:44 +08:00
|
|
|
children = { default: children }
|
2019-08-22 23:12:37 +08:00
|
|
|
type = ShapeFlags.SLOTS_CHILDREN
|
2019-05-31 18:07:43 +08:00
|
|
|
} else {
|
2019-06-02 22:22:44 +08:00
|
|
|
children = isString(children) ? children : children + ''
|
2019-08-22 23:12:37 +08:00
|
|
|
type = ShapeFlags.TEXT_CHILDREN
|
2019-05-31 18:07:43 +08:00
|
|
|
}
|
2019-09-07 23:45:32 +08:00
|
|
|
vnode.children = children as NormalizedChildren
|
2019-06-02 22:22:44 +08:00
|
|
|
vnode.shapeFlag |= type
|
2019-05-31 18:07:43 +08:00
|
|
|
}
|
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
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-06-01 17:47:19 +08:00
|
|
|
export function normalizeClass(value: unknown): string {
|
2019-06-01 17:43:41 +08:00
|
|
|
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()
|
|
|
|
}
|
2019-08-23 05:12:39 +08:00
|
|
|
|
|
|
|
const handlersRE = /^on|^vnode/
|
|
|
|
|
|
|
|
export function mergeProps(...args: Data[]) {
|
|
|
|
const ret: Data = {}
|
2019-08-23 10:07:51 +08:00
|
|
|
extend(ret, args[0])
|
2019-08-23 05:12:39 +08:00
|
|
|
for (let i = 1; i < args.length; i++) {
|
|
|
|
const toMerge = args[i]
|
|
|
|
for (const key in toMerge) {
|
|
|
|
if (key === '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]
|
|
|
|
ret[key] = existing
|
|
|
|
? [].concat(existing as any, toMerge[key] as any)
|
|
|
|
: toMerge[key]
|
|
|
|
} else {
|
|
|
|
ret[key] = toMerge[key]
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return ret
|
|
|
|
}
|