wip: compat integration progress

This commit is contained in:
Evan You 2021-04-20 09:25:12 -04:00
parent 7dc681c196
commit f6dee53270
10 changed files with 183 additions and 22 deletions

View File

@ -203,9 +203,10 @@ const deprecationData: Record<DeprecationTypes, DeprecationData> = {
[DeprecationTypes.INSTANCE_LISTENERS]: {
message:
`vm.$listeners has been removed. Parent v-on listeners are now ` +
`vm.$listeners has been removed. In Vue 3, parent v-on listeners are ` +
`included in vm.$attrs and it is no longer necessary to separately use ` +
`v-on="$listeners" if you are already using v-bind="$attrs".`,
`v-on="$listeners" if you are already using v-bind="$attrs". ` +
`(Note: the Vue 3 behavior only applies if this compat config is disabled)`,
link: `https://v3.vuejs.org/guide/migration/listeners-removed.html`
},

View File

@ -1,4 +1,4 @@
import { extend, NOOP } from '@vue/shared'
import { extend, NOOP, toDisplayString, toNumber } from '@vue/shared'
import { PublicPropertiesMap } from '../componentPublicInstance'
import { getCompatChildren } from './instanceChildren'
import {
@ -11,6 +11,14 @@ import { off, on, once } from './instanceEventEmitter'
import { getCompatListeners } from './instanceListeners'
import { shallowReadonly } from '@vue/reactivity'
import { legacySlotProxyHandlers } from './component'
import { compatH } from './renderFn'
import {
legacyBindObjectProps,
legacyRenderSlot,
legacyRenderStatic
} from './renderHelpers'
import { createCommentVNode, createTextVNode } from '../vnode'
import { renderList } from '../helpers/renderList'
export function installCompatInstanceProperties(map: PublicPropertiesMap) {
const set = (target: any, key: any, val: any) => {
@ -77,6 +85,19 @@ export function installCompatInstanceProperties(map: PublicPropertiesMap) {
$off: i => off.bind(null, i),
$children: getCompatChildren,
$listeners: getCompatListeners
$listeners: getCompatListeners,
// v2 render helpers
$createElement: () => compatH,
_self: i => i.proxy,
_c: () => compatH,
_n: () => toNumber,
_s: () => toDisplayString,
_l: () => renderList,
_t: i => legacyRenderSlot.bind(null, i),
_b: () => legacyBindObjectProps,
_e: () => createCommentVNode,
_v: () => createTextVNode,
_m: i => legacyRenderStatic.bind(null, i)
} as PublicPropertiesMap)
}

View File

@ -29,6 +29,8 @@ import {
} from '../vnode'
import { checkCompatEnabled, DeprecationTypes } from './compatConfig'
const v3CompiledRenderFnRE = /^(?:function \w+)?\(_ctx, _cache/
export function convertLegacyRenderFn(instance: ComponentInternalInstance) {
const Component = instance.type as ComponentOptions
const render = Component.render as InternalRenderFunction | undefined
@ -38,8 +40,7 @@ export function convertLegacyRenderFn(instance: ComponentInternalInstance) {
return
}
const string = render.toString()
if (string.startsWith('function render(_ctx') || string.startsWith('(_ctx')) {
if (v3CompiledRenderFnRE.test(render.toString())) {
// v3 pre-compiled function
render._compatChecked = true
return
@ -128,9 +129,7 @@ export function compatH(
return convertLegacySlots(createVNode(type, null, propsOrChildren))
}
} else {
if (l > 3) {
children = Array.prototype.slice.call(arguments, 2)
} else if (l === 3 && isVNode(children)) {
if (isVNode(children)) {
children = [children]
}
return convertLegacySlots(
@ -157,13 +156,20 @@ function convertLegacyProps(
} else if (key === 'on' || key === 'nativeOn') {
const listeners = legacyProps[key]
for (const event in listeners) {
const handlerKey = toHandlerKey(event)
const handlerKey = convertLegacyEventKey(event)
const existing = converted[handlerKey]
const incoming = listeners[event]
if (existing !== incoming) {
converted[handlerKey] = existing
? [].concat(existing as any, incoming as any)
: incoming
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
}
}
}
} else if (
@ -185,6 +191,20 @@ function convertLegacyProps(
return converted
}
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)
}
function convertLegacyDirectives(
vnode: VNode,
props?: LegacyVNodeProps

View File

@ -0,0 +1,94 @@
import {
camelize,
extend,
hyphenate,
isArray,
isObject,
isReservedProp,
normalizeClass
} from '@vue/shared'
import { ComponentInternalInstance } from '../component'
import { renderSlot } from '../helpers/renderSlot'
import { mergeProps, VNode } from '../vnode'
export function legacyBindObjectProps(
data: any,
_tag: string,
value: any,
_asProp: boolean,
isSync?: boolean
) {
if (value && isObject(value)) {
if (isArray(value)) {
value = toObject(value)
}
for (const key in value) {
if (isReservedProp(key)) {
data[key] = value[key]
} else if (key === 'class') {
data.class = normalizeClass([data.class, value.class])
} else if (key === 'style') {
data.style = normalizeClass([data.style, value.style])
} else {
const attrs = data.attrs || (data.attrs = {})
const camelizedKey = camelize(key)
const hyphenatedKey = hyphenate(key)
if (!(camelizedKey in attrs) && !(hyphenatedKey in attrs)) {
attrs[key] = value[key]
if (isSync) {
const on = data.on || (data.on = {})
on[`update:${key}`] = function($event: any) {
value[key] = $event
}
}
}
}
}
}
return data
}
export function legacyRenderSlot(
instance: ComponentInternalInstance,
name: string,
fallback?: VNode[],
props?: any,
bindObject?: any
) {
if (bindObject) {
props = mergeProps(props, bindObject)
}
return renderSlot(instance.slots, name, props, fallback && (() => fallback))
}
const staticCacheMap = /*#__PURE__*/ new WeakMap<
ComponentInternalInstance,
any[]
>()
export function legacyRenderStatic(
instance: ComponentInternalInstance,
index: number
) {
let cache = staticCacheMap.get(instance)
if (!cache) {
staticCacheMap.set(instance, (cache = []))
}
if (cache[index]) {
return cache[index]
}
const fn = (instance.type as any).staticRenderFns[index]
const ctx = instance.proxy
return (cache[index] = fn.call(ctx, null, ctx))
}
function toObject(arr: Array<any>): Object {
const res = {}
for (let i = 0; i < arr.length; i++) {
if (arr[i]) {
extend(res, arr[i])
}
}
return res
}

View File

@ -9,11 +9,6 @@ import {
isCompatEnabled
} from './compatConfig'
const defaultModelMapping = {
prop: 'value',
event: 'input'
}
export const compatModelEventPrefix = `onModelCompat:`
const warnedTypes = new WeakSet()
@ -40,7 +35,7 @@ export function convertLegacyVModelProps(vnode: VNode) {
warnedTypes.add(type as ComponentOptions)
}
const { prop, event } = (type as any).model || defaultModelMapping
const { prop = 'value', event = 'input' } = (type as any).model || {}
props[prop] = props.modelValue
delete props.modelValue
// important: update dynamic props

View File

@ -20,7 +20,8 @@ import {
isReservedProp,
EMPTY_ARR,
def,
extend
extend,
isOn
} from '@vue/shared'
import { warn } from './warning'
import {
@ -224,6 +225,13 @@ export function updateProps(
)
}
} else {
if (
__COMPAT__ &&
isOn(key) &&
isCompatEnabled(DeprecationTypes.INSTANCE_LISTENERS, instance)
) {
continue
}
attrs[key] = value
}
}
@ -320,6 +328,13 @@ function setFullProps(
// Any non-declared (either as a prop or an emitted event) props are put
// into a separate `attrs` object for spreading. Make sure to preserve
// original key casing
if (
__COMPAT__ &&
isOn(key) &&
isCompatEnabled(DeprecationTypes.INSTANCE_LISTENERS, instance)
) {
continue
}
attrs[key] = value
}
}

View File

@ -80,7 +80,7 @@ const normalizeObjectSlots = (rawSlots: RawSlots, slots: InternalSlots) => {
if (isFunction(value)) {
slots[key] = normalizeSlot(key, value, ctx)
} else if (value != null) {
if (__DEV__) {
if (__DEV__ && !__COMPAT__) {
warn(
`Non-function value encountered for slot "${key}". ` +
`Prefer function slots for better performance.`

View File

@ -0,0 +1,3 @@
import Vue from './index'
export default Vue
export * from '@vue/runtime-dom'

View File

@ -0,0 +1,3 @@
import Vue from './runtime'
export default Vue
export * from '@vue/runtime-dom'

View File

@ -116,7 +116,16 @@ function createConfig(format, output, plugins = []) {
// during a single build.
hasTSChecked = true
const entryFile = /runtime$/.test(format) ? `src/runtime.ts` : `src/index.ts`
let entryFile = /runtime$/.test(format) ? `src/runtime.ts` : `src/index.ts`
// the compat build needs both default AND named exports. This will cause
// Rollup to complain for non-ESM targets, so we use separate entries for
// esm vs. non-esm builds.
if (isCompatBuild && (isBrowserESMBuild || isBundlerESMBuild)) {
entryFile = /runtime$/.test(format)
? `src/esm-runtime.ts`
: `src/esm-index.ts`
}
let external = []