Skip to content

Commit 62bfdae

Browse files
committed
wip: Vue.util compat
1 parent c55f3ed commit 62bfdae

File tree

6 files changed

+160
-32
lines changed

6 files changed

+160
-32
lines changed

packages/runtime-core/src/apiCreateApp.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ import { devtoolsInitApp, devtoolsUnmountApp } from './devtools'
1616
import { isFunction, NO, isObject } from '@vue/shared'
1717
import { version } from '.'
1818
import { installCompatMount } from './compat/global'
19-
import { installLegacyConfigTraps } from './compat/globalConfig'
19+
import { installLegacyConfigProperties } from './compat/globalConfig'
2020

2121
export interface App<HostElement = any> {
2222
version: string
@@ -307,7 +307,7 @@ export function createAppAPI<HostElement>(
307307

308308
if (__COMPAT__) {
309309
installCompatMount(app, context, render, hydrate)
310-
if (__DEV__) installLegacyConfigTraps(app.config)
310+
if (__DEV__) installLegacyConfigProperties(app.config)
311311
}
312312

313313
return app

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

+7
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ export const enum DeprecationTypes {
1818
GLOBAL_SET = 'GLOBAL_SET',
1919
GLOBAL_DELETE = 'GLOBAL_DELETE',
2020
GLOBAL_OBSERVABLE = 'GLOBAL_OBSERVABLE',
21+
GLOBAL_UTIL = 'GLOBAL_UTIL',
2122

2223
CONFIG_SILENT = 'CONFIG_SILENT',
2324
CONFIG_DEVTOOLS = 'CONFIG_DEVTOOLS',
@@ -113,6 +114,12 @@ const deprecationData: Record<DeprecationTypes, DeprecationData> = {
113114
link: `https://v3.vuejs.org/api/basic-reactivity.html`
114115
},
115116

117+
[DeprecationTypes.GLOBAL_UTIL]: {
118+
message:
119+
`Vue.util has been removed. Please refactor to avoid its usage ` +
120+
`since it was an internal API even in Vue 2.`
121+
},
122+
116123
[DeprecationTypes.CONFIG_SILENT]: {
117124
message:
118125
`config.silent has been removed because it is not good practice to ` +

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

+100-10
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,19 @@
1-
import { reactive } from '@vue/reactivity'
2-
import { isFunction } from '@vue/shared'
1+
import {
2+
isReactive,
3+
reactive,
4+
track,
5+
TrackOpTypes,
6+
trigger,
7+
TriggerOpTypes
8+
} from '@vue/reactivity'
9+
import {
10+
isFunction,
11+
extend,
12+
NOOP,
13+
EMPTY_OBJ,
14+
isArray,
15+
isObject
16+
} from '@vue/shared'
317
import { warn } from '../warning'
418
import { cloneVNode, createVNode } from '../vnode'
519
import { RootRenderFunction } from '../renderer'
@@ -20,7 +34,7 @@ import {
2034
isRuntimeOnly,
2135
setupComponent
2236
} from '../component'
23-
import { RenderFunction } from '../componentOptions'
37+
import { RenderFunction, mergeOptions } from '../componentOptions'
2438
import { ComponentPublicInstance } from '../componentPublicInstance'
2539
import { devtoolsInitApp } from '../devtools'
2640
import { Directive } from '../directives'
@@ -129,17 +143,14 @@ export function createCompatVue(
129143
isCopyingConfig = false
130144

131145
// copy prototype augmentations as config.globalProperties
132-
const isPrototypeEnabled = isCompatEnabled(
133-
DeprecationTypes.GLOBAL_PROTOTYPE,
134-
null
135-
)
146+
if (isCompatEnabled(DeprecationTypes.GLOBAL_PROTOTYPE, null)) {
147+
app.config.globalProperties = Ctor.prototype
148+
}
136149
let hasPrototypeAugmentations = false
137150
for (const key in Ctor.prototype) {
138151
if (key !== 'constructor') {
139152
hasPrototypeAugmentations = true
140-
}
141-
if (isPrototypeEnabled) {
142-
app.config.globalProperties[key] = Ctor.prototype[key]
153+
break
143154
}
144155
}
145156
if (__DEV__ && hasPrototypeAugmentations) {
@@ -228,6 +239,21 @@ export function createCompatVue(
228239
// TODO compiler warning for filters (maybe behavior compat?)
229240
}) as any
230241

242+
// internal utils - these are technically internal but some plugins use it.
243+
const util = {
244+
warn: __DEV__ ? warn : NOOP,
245+
extend,
246+
mergeOptions: (parent: any, child: any, vm?: ComponentPublicInstance) =>
247+
mergeOptions(parent, child, vm && vm.$),
248+
defineReactive
249+
}
250+
Object.defineProperty(Vue, 'util', {
251+
get() {
252+
assertCompatEnabled(DeprecationTypes.GLOBAL_UTIL, null)
253+
return util
254+
}
255+
})
256+
231257
Vue.configureCompat = configureCompat
232258

233259
return Vue
@@ -358,3 +384,67 @@ export function installCompatMount(
358384
return instance.proxy!
359385
}
360386
}
387+
388+
const methodsToPatch = [
389+
'push',
390+
'pop',
391+
'shift',
392+
'unshift',
393+
'splice',
394+
'sort',
395+
'reverse'
396+
]
397+
398+
const patched = new WeakSet<object>()
399+
400+
function defineReactive(obj: any, key: string, val: any) {
401+
// it's possible for the orignial object to be mutated after being defined
402+
// and expecting reactivity... we are covering it here because this seems to
403+
// be a bit more common.
404+
if (isObject(val) && !isReactive(val) && !patched.has(val)) {
405+
const reactiveVal = reactive(val)
406+
if (isArray(val)) {
407+
methodsToPatch.forEach(m => {
408+
// @ts-ignore
409+
val[m] = (...args: any[]) => {
410+
// @ts-ignore
411+
Array.prototype[m].call(reactiveVal, ...args)
412+
}
413+
})
414+
} else {
415+
Object.keys(val).forEach(key => {
416+
defineReactiveSimple(val, key, val[key])
417+
})
418+
}
419+
}
420+
421+
const i = obj.$
422+
if (i && obj === i.proxy) {
423+
// Vue instance, add it to data
424+
if (i.data === EMPTY_OBJ) {
425+
i.data = reactive({})
426+
}
427+
i.data[key] = val
428+
i.accessCache = Object.create(null)
429+
} else if (isReactive(obj)) {
430+
obj[key] = val
431+
} else {
432+
defineReactiveSimple(obj, key, val)
433+
}
434+
}
435+
436+
function defineReactiveSimple(obj: any, key: string, val: any) {
437+
val = isObject(val) ? reactive(val) : val
438+
Object.defineProperty(obj, key, {
439+
enumerable: true,
440+
configurable: true,
441+
get() {
442+
track(obj, TrackOpTypes.GET, key)
443+
return val
444+
},
445+
set(newVal) {
446+
val = isObject(newVal) ? reactive(newVal) : newVal
447+
trigger(obj, TriggerOpTypes.SET, key, newVal)
448+
}
449+
})
450+
}

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

+43-2
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
1-
import { isArray, isString } from '@vue/shared'
1+
import { extend, isArray, isString } from '@vue/shared'
22
import { AppConfig } from '../apiCreateApp'
33
import { isRuntimeOnly } from '../component'
44
import { isCompatEnabled } from './compatConfig'
5+
import { deepMergeData } from './data'
56
import { DeprecationTypes, warnDeprecation } from './deprecations'
67
import { isCopyingConfig } from './global'
78

@@ -34,7 +35,7 @@ export type LegacyConfig = {
3435
}
3536

3637
// dev only
37-
export function installLegacyConfigTraps(config: AppConfig) {
38+
export function installLegacyConfigProperties(config: AppConfig) {
3839
const legacyConfigOptions: Record<string, DeprecationTypes> = {
3940
silent: DeprecationTypes.CONFIG_SILENT,
4041
devtools: DeprecationTypes.CONFIG_DEVTOOLS,
@@ -72,4 +73,44 @@ export function installLegacyConfigTraps(config: AppConfig) {
7273
}
7374
})
7475
})
76+
77+
// Internal merge strats which are no longer needed in v3, but we need to
78+
// expose them because some v2 plugins will reuse these internal strats to
79+
// merge their custom options.
80+
const strats = config.optionMergeStrategies as any
81+
strats.data = deepMergeData
82+
// lifecycle hooks
83+
strats.beforeCreate = mergeHook
84+
strats.created = mergeHook
85+
strats.beforeMount = mergeHook
86+
strats.mounted = mergeHook
87+
strats.beforeUpdate = mergeHook
88+
strats.updated = mergeHook
89+
strats.beforeDestroy = mergeHook
90+
strats.destroyed = mergeHook
91+
strats.activated = mergeHook
92+
strats.deactivated = mergeHook
93+
strats.errorCaptured = mergeHook
94+
strats.serverPrefetch = mergeHook
95+
// assets
96+
strats.components = mergeObjectOptions
97+
strats.directives = mergeObjectOptions
98+
strats.filters = mergeObjectOptions
99+
// objects
100+
strats.props = mergeObjectOptions
101+
strats.methods = mergeObjectOptions
102+
strats.inject = mergeObjectOptions
103+
strats.computed = mergeObjectOptions
104+
// watch has special merge behavior in v2, but isn't actually needed in v3.
105+
// since we are only exposing these for compat and nobody should be relying
106+
// on the watch-specific behavior, just expose the object merge strat.
107+
strats.watch = mergeObjectOptions
108+
}
109+
110+
function mergeHook(to: Function[] | undefined, from: Function | Function[]) {
111+
return Array.from(new Set([...(to || []), from]))
112+
}
113+
114+
function mergeObjectOptions(to: Object | undefined, from: Object | undefined) {
115+
return to ? extend(extend(Object.create(null), to), from) : from
75116
}

packages/runtime-core/src/componentOptions.ts

+8-4
Original file line numberDiff line numberDiff line change
@@ -991,17 +991,21 @@ export function resolveMergedOptions(
991991
return (raw.__merged = options)
992992
}
993993

994-
function mergeOptions(to: any, from: any, instance: ComponentInternalInstance) {
995-
const strats = instance.appContext.config.optionMergeStrategies
994+
export function mergeOptions(
995+
to: any,
996+
from: any,
997+
instance?: ComponentInternalInstance
998+
) {
999+
const strats = instance && instance.appContext.config.optionMergeStrategies
9961000
const { mixins, extends: extendsOptions } = from
9971001

9981002
extendsOptions && mergeOptions(to, extendsOptions, instance)
9991003
mixins &&
10001004
mixins.forEach((m: ComponentOptionsMixin) => mergeOptions(to, m, instance))
10011005

10021006
for (const key in from) {
1003-
if (strats && hasOwn(strats, key)) {
1004-
to[key] = strats[key](to[key], from[key], instance.proxy, key)
1007+
if (strats && hasOwn(to, key) && hasOwn(strats, key)) {
1008+
to[key] = strats[key](to[key], from[key], instance && instance.proxy, key)
10051009
} else {
10061010
to[key] = from[key]
10071011
}

packages/runtime-core/src/componentPublicInstance.ts

-14
Original file line numberDiff line numberDiff line change
@@ -497,20 +497,6 @@ export function createRenderContext(instance: ComponentInternalInstance) {
497497
})
498498
})
499499

500-
// expose global properties
501-
const { globalProperties } = instance.appContext.config
502-
Object.keys(globalProperties).forEach(key => {
503-
Object.defineProperty(target, key, {
504-
configurable: true,
505-
enumerable: false,
506-
get: () => {
507-
const val = globalProperties[key]
508-
return __COMPAT__ && isFunction(val) ? val.bind(instance.proxy) : val
509-
},
510-
set: NOOP
511-
})
512-
})
513-
514500
return target as ComponentRenderContext
515501
}
516502

0 commit comments

Comments
 (0)