Skip to content

Commit f6dee53

Browse files
committed
wip: compat integration progress
1 parent 7dc681c commit f6dee53

File tree

10 files changed

+183
-22
lines changed

10 files changed

+183
-22
lines changed

packages/runtime-core/src/compat/compatConfig.ts

+3-2
Original file line numberDiff line numberDiff line change
@@ -203,9 +203,10 @@ const deprecationData: Record<DeprecationTypes, DeprecationData> = {
203203

204204
[DeprecationTypes.INSTANCE_LISTENERS]: {
205205
message:
206-
`vm.$listeners has been removed. Parent v-on listeners are now ` +
206+
`vm.$listeners has been removed. In Vue 3, parent v-on listeners are ` +
207207
`included in vm.$attrs and it is no longer necessary to separately use ` +
208-
`v-on="$listeners" if you are already using v-bind="$attrs".`,
208+
`v-on="$listeners" if you are already using v-bind="$attrs". ` +
209+
`(Note: the Vue 3 behavior only applies if this compat config is disabled)`,
209210
link: `https://v3.vuejs.org/guide/migration/listeners-removed.html`
210211
},
211212

packages/runtime-core/src/compat/instance.ts

+23-2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { extend, NOOP } from '@vue/shared'
1+
import { extend, NOOP, toDisplayString, toNumber } from '@vue/shared'
22
import { PublicPropertiesMap } from '../componentPublicInstance'
33
import { getCompatChildren } from './instanceChildren'
44
import {
@@ -11,6 +11,14 @@ import { off, on, once } from './instanceEventEmitter'
1111
import { getCompatListeners } from './instanceListeners'
1212
import { shallowReadonly } from '@vue/reactivity'
1313
import { legacySlotProxyHandlers } from './component'
14+
import { compatH } from './renderFn'
15+
import {
16+
legacyBindObjectProps,
17+
legacyRenderSlot,
18+
legacyRenderStatic
19+
} from './renderHelpers'
20+
import { createCommentVNode, createTextVNode } from '../vnode'
21+
import { renderList } from '../helpers/renderList'
1422

1523
export function installCompatInstanceProperties(map: PublicPropertiesMap) {
1624
const set = (target: any, key: any, val: any) => {
@@ -77,6 +85,19 @@ export function installCompatInstanceProperties(map: PublicPropertiesMap) {
7785
$off: i => off.bind(null, i),
7886

7987
$children: getCompatChildren,
80-
$listeners: getCompatListeners
88+
$listeners: getCompatListeners,
89+
90+
// v2 render helpers
91+
$createElement: () => compatH,
92+
_self: i => i.proxy,
93+
_c: () => compatH,
94+
_n: () => toNumber,
95+
_s: () => toDisplayString,
96+
_l: () => renderList,
97+
_t: i => legacyRenderSlot.bind(null, i),
98+
_b: () => legacyBindObjectProps,
99+
_e: () => createCommentVNode,
100+
_v: () => createTextVNode,
101+
_m: i => legacyRenderStatic.bind(null, i)
81102
} as PublicPropertiesMap)
82103
}

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

+29-9
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@ import {
2929
} from '../vnode'
3030
import { checkCompatEnabled, DeprecationTypes } from './compatConfig'
3131

32+
const v3CompiledRenderFnRE = /^(?:function \w+)?\(_ctx, _cache/
33+
3234
export function convertLegacyRenderFn(instance: ComponentInternalInstance) {
3335
const Component = instance.type as ComponentOptions
3436
const render = Component.render as InternalRenderFunction | undefined
@@ -38,8 +40,7 @@ export function convertLegacyRenderFn(instance: ComponentInternalInstance) {
3840
return
3941
}
4042

41-
const string = render.toString()
42-
if (string.startsWith('function render(_ctx') || string.startsWith('(_ctx')) {
43+
if (v3CompiledRenderFnRE.test(render.toString())) {
4344
// v3 pre-compiled function
4445
render._compatChecked = true
4546
return
@@ -128,9 +129,7 @@ export function compatH(
128129
return convertLegacySlots(createVNode(type, null, propsOrChildren))
129130
}
130131
} else {
131-
if (l > 3) {
132-
children = Array.prototype.slice.call(arguments, 2)
133-
} else if (l === 3 && isVNode(children)) {
132+
if (isVNode(children)) {
134133
children = [children]
135134
}
136135
return convertLegacySlots(
@@ -157,13 +156,20 @@ function convertLegacyProps(
157156
} else if (key === 'on' || key === 'nativeOn') {
158157
const listeners = legacyProps[key]
159158
for (const event in listeners) {
160-
const handlerKey = toHandlerKey(event)
159+
const handlerKey = convertLegacyEventKey(event)
161160
const existing = converted[handlerKey]
162161
const incoming = listeners[event]
163162
if (existing !== incoming) {
164-
converted[handlerKey] = existing
165-
? [].concat(existing as any, incoming as any)
166-
: incoming
163+
if (existing) {
164+
// for the rare case where the same handler is attached
165+
// twice with/without .native modifier...
166+
if (key === 'nativeOn' && String(existing) === String(incoming)) {
167+
continue
168+
}
169+
converted[handlerKey] = [].concat(existing as any, incoming as any)
170+
} else {
171+
converted[handlerKey] = incoming
172+
}
167173
}
168174
}
169175
} else if (
@@ -185,6 +191,20 @@ function convertLegacyProps(
185191
return converted
186192
}
187193

194+
function convertLegacyEventKey(event: string): string {
195+
// normalize v2 event prefixes
196+
if (event[0] === '&') {
197+
event = event.slice(1) + 'Passive'
198+
}
199+
if (event[0] === '~') {
200+
event = event.slice(1) + 'Once'
201+
}
202+
if (event[0] === '!') {
203+
event = event.slice(1) + 'Capture'
204+
}
205+
return toHandlerKey(event)
206+
}
207+
188208
function convertLegacyDirectives(
189209
vnode: VNode,
190210
props?: LegacyVNodeProps
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
import {
2+
camelize,
3+
extend,
4+
hyphenate,
5+
isArray,
6+
isObject,
7+
isReservedProp,
8+
normalizeClass
9+
} from '@vue/shared'
10+
import { ComponentInternalInstance } from '../component'
11+
import { renderSlot } from '../helpers/renderSlot'
12+
import { mergeProps, VNode } from '../vnode'
13+
14+
export function legacyBindObjectProps(
15+
data: any,
16+
_tag: string,
17+
value: any,
18+
_asProp: boolean,
19+
isSync?: boolean
20+
) {
21+
if (value && isObject(value)) {
22+
if (isArray(value)) {
23+
value = toObject(value)
24+
}
25+
for (const key in value) {
26+
if (isReservedProp(key)) {
27+
data[key] = value[key]
28+
} else if (key === 'class') {
29+
data.class = normalizeClass([data.class, value.class])
30+
} else if (key === 'style') {
31+
data.style = normalizeClass([data.style, value.style])
32+
} else {
33+
const attrs = data.attrs || (data.attrs = {})
34+
const camelizedKey = camelize(key)
35+
const hyphenatedKey = hyphenate(key)
36+
if (!(camelizedKey in attrs) && !(hyphenatedKey in attrs)) {
37+
attrs[key] = value[key]
38+
39+
if (isSync) {
40+
const on = data.on || (data.on = {})
41+
on[`update:${key}`] = function($event: any) {
42+
value[key] = $event
43+
}
44+
}
45+
}
46+
}
47+
}
48+
}
49+
return data
50+
}
51+
52+
export function legacyRenderSlot(
53+
instance: ComponentInternalInstance,
54+
name: string,
55+
fallback?: VNode[],
56+
props?: any,
57+
bindObject?: any
58+
) {
59+
if (bindObject) {
60+
props = mergeProps(props, bindObject)
61+
}
62+
return renderSlot(instance.slots, name, props, fallback && (() => fallback))
63+
}
64+
65+
const staticCacheMap = /*#__PURE__*/ new WeakMap<
66+
ComponentInternalInstance,
67+
any[]
68+
>()
69+
70+
export function legacyRenderStatic(
71+
instance: ComponentInternalInstance,
72+
index: number
73+
) {
74+
let cache = staticCacheMap.get(instance)
75+
if (!cache) {
76+
staticCacheMap.set(instance, (cache = []))
77+
}
78+
if (cache[index]) {
79+
return cache[index]
80+
}
81+
const fn = (instance.type as any).staticRenderFns[index]
82+
const ctx = instance.proxy
83+
return (cache[index] = fn.call(ctx, null, ctx))
84+
}
85+
86+
function toObject(arr: Array<any>): Object {
87+
const res = {}
88+
for (let i = 0; i < arr.length; i++) {
89+
if (arr[i]) {
90+
extend(res, arr[i])
91+
}
92+
}
93+
return res
94+
}

packages/runtime-core/src/compat/vModel.ts

+1-6
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,6 @@ import {
99
isCompatEnabled
1010
} from './compatConfig'
1111

12-
const defaultModelMapping = {
13-
prop: 'value',
14-
event: 'input'
15-
}
16-
1712
export const compatModelEventPrefix = `onModelCompat:`
1813

1914
const warnedTypes = new WeakSet()
@@ -40,7 +35,7 @@ export function convertLegacyVModelProps(vnode: VNode) {
4035
warnedTypes.add(type as ComponentOptions)
4136
}
4237

43-
const { prop, event } = (type as any).model || defaultModelMapping
38+
const { prop = 'value', event = 'input' } = (type as any).model || {}
4439
props[prop] = props.modelValue
4540
delete props.modelValue
4641
// important: update dynamic props

packages/runtime-core/src/componentProps.ts

+16-1
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,8 @@ import {
2020
isReservedProp,
2121
EMPTY_ARR,
2222
def,
23-
extend
23+
extend,
24+
isOn
2425
} from '@vue/shared'
2526
import { warn } from './warning'
2627
import {
@@ -224,6 +225,13 @@ export function updateProps(
224225
)
225226
}
226227
} else {
228+
if (
229+
__COMPAT__ &&
230+
isOn(key) &&
231+
isCompatEnabled(DeprecationTypes.INSTANCE_LISTENERS, instance)
232+
) {
233+
continue
234+
}
227235
attrs[key] = value
228236
}
229237
}
@@ -320,6 +328,13 @@ function setFullProps(
320328
// Any non-declared (either as a prop or an emitted event) props are put
321329
// into a separate `attrs` object for spreading. Make sure to preserve
322330
// original key casing
331+
if (
332+
__COMPAT__ &&
333+
isOn(key) &&
334+
isCompatEnabled(DeprecationTypes.INSTANCE_LISTENERS, instance)
335+
) {
336+
continue
337+
}
323338
attrs[key] = value
324339
}
325340
}

packages/runtime-core/src/componentSlots.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ const normalizeObjectSlots = (rawSlots: RawSlots, slots: InternalSlots) => {
8080
if (isFunction(value)) {
8181
slots[key] = normalizeSlot(key, value, ctx)
8282
} else if (value != null) {
83-
if (__DEV__) {
83+
if (__DEV__ && !__COMPAT__) {
8484
warn(
8585
`Non-function value encountered for slot "${key}". ` +
8686
`Prefer function slots for better performance.`

packages/vue-compat/src/esm-index.ts

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
import Vue from './index'
2+
export default Vue
3+
export * from '@vue/runtime-dom'
+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
import Vue from './runtime'
2+
export default Vue
3+
export * from '@vue/runtime-dom'

rollup.config.js

+10-1
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,16 @@ function createConfig(format, output, plugins = []) {
116116
// during a single build.
117117
hasTSChecked = true
118118

119-
const entryFile = /runtime$/.test(format) ? `src/runtime.ts` : `src/index.ts`
119+
let entryFile = /runtime$/.test(format) ? `src/runtime.ts` : `src/index.ts`
120+
121+
// the compat build needs both default AND named exports. This will cause
122+
// Rollup to complain for non-ESM targets, so we use separate entries for
123+
// esm vs. non-esm builds.
124+
if (isCompatBuild && (isBrowserESMBuild || isBundlerESMBuild)) {
125+
entryFile = /runtime$/.test(format)
126+
? `src/esm-runtime.ts`
127+
: `src/esm-index.ts`
128+
}
120129

121130
let external = []
122131

0 commit comments

Comments
 (0)