vue3-core/packages/runtime-core/src/compat/renderFn.ts

347 lines
9.0 KiB
TypeScript
Raw Normal View History

2021-04-10 06:52:14 +08:00
import {
extend,
2021-04-22 03:09:18 +08:00
hyphenate,
2021-04-10 06:52:14 +08:00
isArray,
isObject,
2021-04-23 02:59:54 +08:00
isString,
2021-04-22 03:09:18 +08:00
makeMap,
2021-04-12 10:21:10 +08:00
normalizeClass,
normalizeStyle,
2021-04-10 06:52:14 +08:00
ShapeFlags,
toHandlerKey
} from '@vue/shared'
2021-04-11 23:15:40 +08:00
import {
Component,
ComponentInternalInstance,
ComponentOptions,
Data,
InternalRenderFunction
} from '../component'
2021-04-22 03:09:18 +08:00
import { currentRenderingInstance } from '../componentRenderContext'
2021-04-10 06:52:14 +08:00
import { DirectiveArguments, withDirectives } from '../directives'
import {
resolveDirective,
resolveDynamicComponent
} from '../helpers/resolveAssets'
2021-04-08 22:06:12 +08:00
import {
2021-04-23 05:30:54 +08:00
Comment,
2021-04-08 22:06:12 +08:00
createVNode,
isVNode,
2021-04-10 06:52:14 +08:00
normalizeChildren,
2021-04-08 22:06:12 +08:00
VNode,
VNodeArrayChildren,
VNodeProps
} from '../vnode'
2021-04-22 03:09:18 +08:00
import {
checkCompatEnabled,
DeprecationTypes,
isCompatEnabled
} from './compatConfig'
import { compatModelEventPrefix } from './vModel'
2021-04-11 23:15:40 +08:00
2021-04-20 21:25:12 +08:00
const v3CompiledRenderFnRE = /^(?:function \w+)?\(_ctx, _cache/
2021-04-11 23:15:40 +08:00
export function convertLegacyRenderFn(instance: ComponentInternalInstance) {
const Component = instance.type as ComponentOptions
const render = Component.render as InternalRenderFunction | undefined
// v3 runtime compiled, or already checked / wrapped
if (!render || render._rc || render._compatChecked || render._compatWrapped) {
return
}
2021-04-20 21:25:12 +08:00
if (v3CompiledRenderFnRE.test(render.toString())) {
2021-04-11 23:15:40 +08:00
// v3 pre-compiled function
render._compatChecked = true
return
}
// v2 render function, try to provide compat
if (checkCompatEnabled(DeprecationTypes.RENDER_FUNCTION, instance)) {
const wrapped = (Component.render = function compatRender() {
// @ts-ignore
return render.call(this, compatH)
})
// @ts-ignore
wrapped._compatWrapped = true
}
}
2021-04-08 22:06:12 +08:00
interface LegacyVNodeProps {
key?: string | number
ref?: string
refInFor?: boolean
staticClass?: string
class?: unknown
staticStyle?: Record<string, unknown>
style?: Record<string, unknown>
attrs?: Record<string, unknown>
domProps?: Record<string, unknown>
on?: Record<string, Function | Function[]>
nativeOn?: Record<string, Function | Function[]>
directives?: LegacyVNodeDirective[]
2021-04-10 06:52:14 +08:00
// component only
props?: Record<string, unknown>
2021-04-08 22:06:12 +08:00
slot?: string
scopedSlots?: Record<string, Function>
2021-04-22 03:09:18 +08:00
model?: {
value: any
callback: (v: any) => void
expression: string
}
2021-04-08 22:06:12 +08:00
}
interface LegacyVNodeDirective {
name: string
value: unknown
arg?: string
modifiers?: Record<string, boolean>
}
type LegacyVNodeChildren =
| string
| number
| boolean
| VNode
| VNodeArrayChildren
export function compatH(
2021-04-08 22:06:12 +08:00
type: string | Component,
children?: LegacyVNodeChildren
): VNode
export function compatH(
2021-04-08 22:06:12 +08:00
type: string | Component,
props?: LegacyVNodeProps,
children?: LegacyVNodeChildren
): VNode
export function compatH(
type: any,
propsOrChildren?: any,
children?: any
): VNode {
2021-04-23 05:30:54 +08:00
if (!type) {
type = Comment
}
2021-04-22 03:09:18 +08:00
// to support v2 string component name look!up
if (typeof type === 'string') {
const t = hyphenate(type)
if (t === 'transition' || t === 'transition-group' || t === 'keep-alive') {
// since transition and transition-group are runtime-dom-specific,
// we cannot import them directly here. Instead they are registered using
// special keys in @vue/compat entry.
type = `__compat__${t}`
}
type = resolveDynamicComponent(type)
}
2021-04-10 06:52:14 +08:00
2021-04-08 22:06:12 +08:00
const l = arguments.length
2021-04-22 03:09:18 +08:00
const is2ndArgArrayChildren = isArray(propsOrChildren)
if (l === 2 || is2ndArgArrayChildren) {
if (isObject(propsOrChildren) && !is2ndArgArrayChildren) {
2021-04-08 22:06:12 +08:00
// single vnode without props
if (isVNode(propsOrChildren)) {
return convertLegacySlots(createVNode(type, null, [propsOrChildren]))
}
// props without children
2021-04-10 06:52:14 +08:00
return convertLegacySlots(
convertLegacyDirectives(
2021-04-22 03:09:18 +08:00
createVNode(type, convertLegacyProps(propsOrChildren, type)),
2021-04-10 06:52:14 +08:00
propsOrChildren
)
2021-04-08 22:06:12 +08:00
)
} else {
// omit props
return convertLegacySlots(createVNode(type, null, propsOrChildren))
}
} else {
2021-04-20 21:25:12 +08:00
if (isVNode(children)) {
2021-04-08 22:06:12 +08:00
children = [children]
}
return convertLegacySlots(
convertLegacyDirectives(
2021-04-22 03:09:18 +08:00
createVNode(type, convertLegacyProps(propsOrChildren, type), children),
2021-04-08 22:06:12 +08:00
propsOrChildren
)
)
}
}
2021-04-22 03:09:18 +08:00
const skipLegacyRootLevelProps = /*#__PURE__*/ makeMap(
'refInFor,staticStyle,staticClass,directives,model'
)
2021-04-12 10:21:10 +08:00
function convertLegacyProps(
2021-04-22 03:09:18 +08:00
legacyProps: LegacyVNodeProps | undefined,
type: any
2021-04-12 10:21:10 +08:00
): Data & VNodeProps | null {
if (!legacyProps) {
return null
}
2021-04-10 06:52:14 +08:00
const converted: Data & VNodeProps = {}
for (const key in legacyProps) {
if (key === 'attrs' || key === 'domProps' || key === 'props') {
extend(converted, legacyProps[key])
} else if (key === 'on' || key === 'nativeOn') {
const listeners = legacyProps[key]
for (const event in listeners) {
2021-04-20 21:25:12 +08:00
const handlerKey = convertLegacyEventKey(event)
2021-04-10 06:52:14 +08:00
const existing = converted[handlerKey]
const incoming = listeners[event]
if (existing !== incoming) {
2021-04-20 21:25:12 +08:00
if (existing) {
// for the rare case where the same handler is attached
// twice with/without .native modifier...
if (key === 'nativeOn' && String(existing) === String(incoming)) {
continue
}
converted[handlerKey] = [].concat(existing as any, incoming as any)
} else {
converted[handlerKey] = incoming
}
2021-04-10 06:52:14 +08:00
}
}
2021-04-23 05:30:54 +08:00
} else if (key === 'hook') {
// TODO
2021-04-22 03:09:18 +08:00
} else if (!skipLegacyRootLevelProps(key)) {
2021-04-10 06:52:14 +08:00
converted[key] = legacyProps[key as keyof LegacyVNodeProps]
}
}
2021-04-12 10:21:10 +08:00
if (legacyProps.staticClass) {
converted.class = normalizeClass([legacyProps.staticClass, converted.class])
}
if (legacyProps.staticStyle) {
converted.style = normalizeStyle([legacyProps.staticStyle, converted.style])
}
2021-04-22 03:09:18 +08:00
if (legacyProps.model && isObject(type)) {
// v2 compiled component v-model
const { prop = 'value', event = 'input' } = (type as any).model || {}
converted[prop] = legacyProps.model.value
converted[compatModelEventPrefix + event] = legacyProps.model.callback
}
2021-04-10 06:52:14 +08:00
return converted
2021-04-08 22:06:12 +08:00
}
2021-04-20 21:25:12 +08:00
function convertLegacyEventKey(event: string): string {
// normalize v2 event prefixes
if (event[0] === '&') {
event = event.slice(1) + 'Passive'
}
if (event[0] === '~') {
event = event.slice(1) + 'Once'
}
if (event[0] === '!') {
event = event.slice(1) + 'Capture'
}
return toHandlerKey(event)
}
2021-04-10 06:52:14 +08:00
function convertLegacyDirectives(
vnode: VNode,
props?: LegacyVNodeProps
): VNode {
if (props && props.directives) {
return withDirectives(
vnode,
props.directives.map(({ name, value, arg, modifiers }) => {
return [
resolveDirective(name)!,
value,
arg,
modifiers
] as DirectiveArguments[number]
})
)
}
2021-04-08 22:06:12 +08:00
return vnode
}
function convertLegacySlots(vnode: VNode): VNode {
2021-04-10 06:52:14 +08:00
const { props, children } = vnode
let slots: Record<string, any> | undefined
if (vnode.shapeFlag & ShapeFlags.COMPONENT && isArray(children)) {
slots = {}
// check "slot" property on vnodes and turn them into v3 function slots
for (let i = 0; i < children.length; i++) {
const child = children[i]
const slotName =
(isVNode(child) && child.props && child.props.slot) || 'default'
2021-04-22 03:09:18 +08:00
const slot = slots[slotName] || (slots[slotName] = [] as any[])
if (isVNode(child) && child.type === 'template') {
slot.push(child.children)
} else {
slot.push(child)
}
2021-04-10 06:52:14 +08:00
}
if (slots) {
for (const key in slots) {
const slotChildren = slots[key]
slots[key] = () => slotChildren
}
}
}
const scopedSlots = props && props.scopedSlots
if (scopedSlots) {
delete props!.scopedSlots
if (slots) {
extend(slots, scopedSlots)
} else {
slots = scopedSlots
}
}
if (slots) {
normalizeChildren(vnode, slots)
}
2021-04-08 22:06:12 +08:00
return vnode
}
2021-04-22 03:09:18 +08:00
export function defineLegacyVNodeProperties(vnode: VNode) {
if (
2021-04-23 05:50:49 +08:00
isCompatEnabled(
DeprecationTypes.RENDER_FUNCTION,
currentRenderingInstance
) &&
isCompatEnabled(DeprecationTypes.PRIVATE_APIS, currentRenderingInstance)
2021-04-22 03:09:18 +08:00
) {
2021-04-23 02:59:54 +08:00
const context = currentRenderingInstance
2021-04-22 03:09:18 +08:00
const getInstance = () => vnode.component && vnode.component.proxy
let componentOptions: any
Object.defineProperties(vnode, {
2021-04-23 02:59:54 +08:00
tag: { get: () => vnode.type },
data: { get: () => vnode.props, set: p => (vnode.props = p) },
2021-04-22 03:09:18 +08:00
elm: { get: () => vnode.el },
componentInstance: { get: getInstance },
child: { get: getInstance },
2021-04-23 02:59:54 +08:00
text: { get: () => (isString(vnode.children) ? vnode.children : null) },
context: { get: () => context && context.proxy },
2021-04-22 03:09:18 +08:00
componentOptions: {
get: () => {
if (vnode.shapeFlag & ShapeFlags.STATEFUL_COMPONENT) {
if (componentOptions) {
return componentOptions
}
return (componentOptions = {
Ctor: vnode.type,
propsData: vnode.props,
children: vnode.children
})
}
}
}
})
}
}