wip: compat integration progress
This commit is contained in:
parent
7dc681c196
commit
f6dee53270
@ -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`
|
||||
},
|
||||
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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
|
||||
|
94
packages/runtime-core/src/compat/renderHelpers.ts
Normal file
94
packages/runtime-core/src/compat/renderHelpers.ts
Normal 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
|
||||
}
|
@ -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
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
@ -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.`
|
||||
|
3
packages/vue-compat/src/esm-index.ts
Normal file
3
packages/vue-compat/src/esm-index.ts
Normal file
@ -0,0 +1,3 @@
|
||||
import Vue from './index'
|
||||
export default Vue
|
||||
export * from '@vue/runtime-dom'
|
3
packages/vue-compat/src/esm-runtime.ts
Normal file
3
packages/vue-compat/src/esm-runtime.ts
Normal file
@ -0,0 +1,3 @@
|
||||
import Vue from './runtime'
|
||||
export default Vue
|
||||
export * from '@vue/runtime-dom'
|
@ -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 = []
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user