wip: Vue.util compat
This commit is contained in:
parent
c55f3ed0e8
commit
62bfdae043
@ -16,7 +16,7 @@ import { devtoolsInitApp, devtoolsUnmountApp } from './devtools'
|
|||||||
import { isFunction, NO, isObject } from '@vue/shared'
|
import { isFunction, NO, isObject } from '@vue/shared'
|
||||||
import { version } from '.'
|
import { version } from '.'
|
||||||
import { installCompatMount } from './compat/global'
|
import { installCompatMount } from './compat/global'
|
||||||
import { installLegacyConfigTraps } from './compat/globalConfig'
|
import { installLegacyConfigProperties } from './compat/globalConfig'
|
||||||
|
|
||||||
export interface App<HostElement = any> {
|
export interface App<HostElement = any> {
|
||||||
version: string
|
version: string
|
||||||
@ -307,7 +307,7 @@ export function createAppAPI<HostElement>(
|
|||||||
|
|
||||||
if (__COMPAT__) {
|
if (__COMPAT__) {
|
||||||
installCompatMount(app, context, render, hydrate)
|
installCompatMount(app, context, render, hydrate)
|
||||||
if (__DEV__) installLegacyConfigTraps(app.config)
|
if (__DEV__) installLegacyConfigProperties(app.config)
|
||||||
}
|
}
|
||||||
|
|
||||||
return app
|
return app
|
||||||
|
@ -18,6 +18,7 @@ export const enum DeprecationTypes {
|
|||||||
GLOBAL_SET = 'GLOBAL_SET',
|
GLOBAL_SET = 'GLOBAL_SET',
|
||||||
GLOBAL_DELETE = 'GLOBAL_DELETE',
|
GLOBAL_DELETE = 'GLOBAL_DELETE',
|
||||||
GLOBAL_OBSERVABLE = 'GLOBAL_OBSERVABLE',
|
GLOBAL_OBSERVABLE = 'GLOBAL_OBSERVABLE',
|
||||||
|
GLOBAL_UTIL = 'GLOBAL_UTIL',
|
||||||
|
|
||||||
CONFIG_SILENT = 'CONFIG_SILENT',
|
CONFIG_SILENT = 'CONFIG_SILENT',
|
||||||
CONFIG_DEVTOOLS = 'CONFIG_DEVTOOLS',
|
CONFIG_DEVTOOLS = 'CONFIG_DEVTOOLS',
|
||||||
@ -113,6 +114,12 @@ const deprecationData: Record<DeprecationTypes, DeprecationData> = {
|
|||||||
link: `https://v3.vuejs.org/api/basic-reactivity.html`
|
link: `https://v3.vuejs.org/api/basic-reactivity.html`
|
||||||
},
|
},
|
||||||
|
|
||||||
|
[DeprecationTypes.GLOBAL_UTIL]: {
|
||||||
|
message:
|
||||||
|
`Vue.util has been removed. Please refactor to avoid its usage ` +
|
||||||
|
`since it was an internal API even in Vue 2.`
|
||||||
|
},
|
||||||
|
|
||||||
[DeprecationTypes.CONFIG_SILENT]: {
|
[DeprecationTypes.CONFIG_SILENT]: {
|
||||||
message:
|
message:
|
||||||
`config.silent has been removed because it is not good practice to ` +
|
`config.silent has been removed because it is not good practice to ` +
|
||||||
|
@ -1,5 +1,19 @@
|
|||||||
import { reactive } from '@vue/reactivity'
|
import {
|
||||||
import { isFunction } from '@vue/shared'
|
isReactive,
|
||||||
|
reactive,
|
||||||
|
track,
|
||||||
|
TrackOpTypes,
|
||||||
|
trigger,
|
||||||
|
TriggerOpTypes
|
||||||
|
} from '@vue/reactivity'
|
||||||
|
import {
|
||||||
|
isFunction,
|
||||||
|
extend,
|
||||||
|
NOOP,
|
||||||
|
EMPTY_OBJ,
|
||||||
|
isArray,
|
||||||
|
isObject
|
||||||
|
} from '@vue/shared'
|
||||||
import { warn } from '../warning'
|
import { warn } from '../warning'
|
||||||
import { cloneVNode, createVNode } from '../vnode'
|
import { cloneVNode, createVNode } from '../vnode'
|
||||||
import { RootRenderFunction } from '../renderer'
|
import { RootRenderFunction } from '../renderer'
|
||||||
@ -20,7 +34,7 @@ import {
|
|||||||
isRuntimeOnly,
|
isRuntimeOnly,
|
||||||
setupComponent
|
setupComponent
|
||||||
} from '../component'
|
} from '../component'
|
||||||
import { RenderFunction } from '../componentOptions'
|
import { RenderFunction, mergeOptions } from '../componentOptions'
|
||||||
import { ComponentPublicInstance } from '../componentPublicInstance'
|
import { ComponentPublicInstance } from '../componentPublicInstance'
|
||||||
import { devtoolsInitApp } from '../devtools'
|
import { devtoolsInitApp } from '../devtools'
|
||||||
import { Directive } from '../directives'
|
import { Directive } from '../directives'
|
||||||
@ -129,17 +143,14 @@ export function createCompatVue(
|
|||||||
isCopyingConfig = false
|
isCopyingConfig = false
|
||||||
|
|
||||||
// copy prototype augmentations as config.globalProperties
|
// copy prototype augmentations as config.globalProperties
|
||||||
const isPrototypeEnabled = isCompatEnabled(
|
if (isCompatEnabled(DeprecationTypes.GLOBAL_PROTOTYPE, null)) {
|
||||||
DeprecationTypes.GLOBAL_PROTOTYPE,
|
app.config.globalProperties = Ctor.prototype
|
||||||
null
|
}
|
||||||
)
|
|
||||||
let hasPrototypeAugmentations = false
|
let hasPrototypeAugmentations = false
|
||||||
for (const key in Ctor.prototype) {
|
for (const key in Ctor.prototype) {
|
||||||
if (key !== 'constructor') {
|
if (key !== 'constructor') {
|
||||||
hasPrototypeAugmentations = true
|
hasPrototypeAugmentations = true
|
||||||
}
|
break
|
||||||
if (isPrototypeEnabled) {
|
|
||||||
app.config.globalProperties[key] = Ctor.prototype[key]
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (__DEV__ && hasPrototypeAugmentations) {
|
if (__DEV__ && hasPrototypeAugmentations) {
|
||||||
@ -228,6 +239,21 @@ export function createCompatVue(
|
|||||||
// TODO compiler warning for filters (maybe behavior compat?)
|
// TODO compiler warning for filters (maybe behavior compat?)
|
||||||
}) as any
|
}) as any
|
||||||
|
|
||||||
|
// internal utils - these are technically internal but some plugins use it.
|
||||||
|
const util = {
|
||||||
|
warn: __DEV__ ? warn : NOOP,
|
||||||
|
extend,
|
||||||
|
mergeOptions: (parent: any, child: any, vm?: ComponentPublicInstance) =>
|
||||||
|
mergeOptions(parent, child, vm && vm.$),
|
||||||
|
defineReactive
|
||||||
|
}
|
||||||
|
Object.defineProperty(Vue, 'util', {
|
||||||
|
get() {
|
||||||
|
assertCompatEnabled(DeprecationTypes.GLOBAL_UTIL, null)
|
||||||
|
return util
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
Vue.configureCompat = configureCompat
|
Vue.configureCompat = configureCompat
|
||||||
|
|
||||||
return Vue
|
return Vue
|
||||||
@ -358,3 +384,67 @@ export function installCompatMount(
|
|||||||
return instance.proxy!
|
return instance.proxy!
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const methodsToPatch = [
|
||||||
|
'push',
|
||||||
|
'pop',
|
||||||
|
'shift',
|
||||||
|
'unshift',
|
||||||
|
'splice',
|
||||||
|
'sort',
|
||||||
|
'reverse'
|
||||||
|
]
|
||||||
|
|
||||||
|
const patched = new WeakSet<object>()
|
||||||
|
|
||||||
|
function defineReactive(obj: any, key: string, val: any) {
|
||||||
|
// it's possible for the orignial object to be mutated after being defined
|
||||||
|
// and expecting reactivity... we are covering it here because this seems to
|
||||||
|
// be a bit more common.
|
||||||
|
if (isObject(val) && !isReactive(val) && !patched.has(val)) {
|
||||||
|
const reactiveVal = reactive(val)
|
||||||
|
if (isArray(val)) {
|
||||||
|
methodsToPatch.forEach(m => {
|
||||||
|
// @ts-ignore
|
||||||
|
val[m] = (...args: any[]) => {
|
||||||
|
// @ts-ignore
|
||||||
|
Array.prototype[m].call(reactiveVal, ...args)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
Object.keys(val).forEach(key => {
|
||||||
|
defineReactiveSimple(val, key, val[key])
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const i = obj.$
|
||||||
|
if (i && obj === i.proxy) {
|
||||||
|
// Vue instance, add it to data
|
||||||
|
if (i.data === EMPTY_OBJ) {
|
||||||
|
i.data = reactive({})
|
||||||
|
}
|
||||||
|
i.data[key] = val
|
||||||
|
i.accessCache = Object.create(null)
|
||||||
|
} else if (isReactive(obj)) {
|
||||||
|
obj[key] = val
|
||||||
|
} else {
|
||||||
|
defineReactiveSimple(obj, key, val)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function defineReactiveSimple(obj: any, key: string, val: any) {
|
||||||
|
val = isObject(val) ? reactive(val) : val
|
||||||
|
Object.defineProperty(obj, key, {
|
||||||
|
enumerable: true,
|
||||||
|
configurable: true,
|
||||||
|
get() {
|
||||||
|
track(obj, TrackOpTypes.GET, key)
|
||||||
|
return val
|
||||||
|
},
|
||||||
|
set(newVal) {
|
||||||
|
val = isObject(newVal) ? reactive(newVal) : newVal
|
||||||
|
trigger(obj, TriggerOpTypes.SET, key, newVal)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
import { isArray, isString } from '@vue/shared'
|
import { extend, isArray, isString } from '@vue/shared'
|
||||||
import { AppConfig } from '../apiCreateApp'
|
import { AppConfig } from '../apiCreateApp'
|
||||||
import { isRuntimeOnly } from '../component'
|
import { isRuntimeOnly } from '../component'
|
||||||
import { isCompatEnabled } from './compatConfig'
|
import { isCompatEnabled } from './compatConfig'
|
||||||
|
import { deepMergeData } from './data'
|
||||||
import { DeprecationTypes, warnDeprecation } from './deprecations'
|
import { DeprecationTypes, warnDeprecation } from './deprecations'
|
||||||
import { isCopyingConfig } from './global'
|
import { isCopyingConfig } from './global'
|
||||||
|
|
||||||
@ -34,7 +35,7 @@ export type LegacyConfig = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// dev only
|
// dev only
|
||||||
export function installLegacyConfigTraps(config: AppConfig) {
|
export function installLegacyConfigProperties(config: AppConfig) {
|
||||||
const legacyConfigOptions: Record<string, DeprecationTypes> = {
|
const legacyConfigOptions: Record<string, DeprecationTypes> = {
|
||||||
silent: DeprecationTypes.CONFIG_SILENT,
|
silent: DeprecationTypes.CONFIG_SILENT,
|
||||||
devtools: DeprecationTypes.CONFIG_DEVTOOLS,
|
devtools: DeprecationTypes.CONFIG_DEVTOOLS,
|
||||||
@ -72,4 +73,44 @@ export function installLegacyConfigTraps(config: AppConfig) {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// Internal merge strats which are no longer needed in v3, but we need to
|
||||||
|
// expose them because some v2 plugins will reuse these internal strats to
|
||||||
|
// merge their custom options.
|
||||||
|
const strats = config.optionMergeStrategies as any
|
||||||
|
strats.data = deepMergeData
|
||||||
|
// lifecycle hooks
|
||||||
|
strats.beforeCreate = mergeHook
|
||||||
|
strats.created = mergeHook
|
||||||
|
strats.beforeMount = mergeHook
|
||||||
|
strats.mounted = mergeHook
|
||||||
|
strats.beforeUpdate = mergeHook
|
||||||
|
strats.updated = mergeHook
|
||||||
|
strats.beforeDestroy = mergeHook
|
||||||
|
strats.destroyed = mergeHook
|
||||||
|
strats.activated = mergeHook
|
||||||
|
strats.deactivated = mergeHook
|
||||||
|
strats.errorCaptured = mergeHook
|
||||||
|
strats.serverPrefetch = mergeHook
|
||||||
|
// assets
|
||||||
|
strats.components = mergeObjectOptions
|
||||||
|
strats.directives = mergeObjectOptions
|
||||||
|
strats.filters = mergeObjectOptions
|
||||||
|
// objects
|
||||||
|
strats.props = mergeObjectOptions
|
||||||
|
strats.methods = mergeObjectOptions
|
||||||
|
strats.inject = mergeObjectOptions
|
||||||
|
strats.computed = mergeObjectOptions
|
||||||
|
// watch has special merge behavior in v2, but isn't actually needed in v3.
|
||||||
|
// since we are only exposing these for compat and nobody should be relying
|
||||||
|
// on the watch-specific behavior, just expose the object merge strat.
|
||||||
|
strats.watch = mergeObjectOptions
|
||||||
|
}
|
||||||
|
|
||||||
|
function mergeHook(to: Function[] | undefined, from: Function | Function[]) {
|
||||||
|
return Array.from(new Set([...(to || []), from]))
|
||||||
|
}
|
||||||
|
|
||||||
|
function mergeObjectOptions(to: Object | undefined, from: Object | undefined) {
|
||||||
|
return to ? extend(extend(Object.create(null), to), from) : from
|
||||||
}
|
}
|
||||||
|
@ -991,8 +991,12 @@ export function resolveMergedOptions(
|
|||||||
return (raw.__merged = options)
|
return (raw.__merged = options)
|
||||||
}
|
}
|
||||||
|
|
||||||
function mergeOptions(to: any, from: any, instance: ComponentInternalInstance) {
|
export function mergeOptions(
|
||||||
const strats = instance.appContext.config.optionMergeStrategies
|
to: any,
|
||||||
|
from: any,
|
||||||
|
instance?: ComponentInternalInstance
|
||||||
|
) {
|
||||||
|
const strats = instance && instance.appContext.config.optionMergeStrategies
|
||||||
const { mixins, extends: extendsOptions } = from
|
const { mixins, extends: extendsOptions } = from
|
||||||
|
|
||||||
extendsOptions && mergeOptions(to, extendsOptions, instance)
|
extendsOptions && mergeOptions(to, extendsOptions, instance)
|
||||||
@ -1000,8 +1004,8 @@ function mergeOptions(to: any, from: any, instance: ComponentInternalInstance) {
|
|||||||
mixins.forEach((m: ComponentOptionsMixin) => mergeOptions(to, m, instance))
|
mixins.forEach((m: ComponentOptionsMixin) => mergeOptions(to, m, instance))
|
||||||
|
|
||||||
for (const key in from) {
|
for (const key in from) {
|
||||||
if (strats && hasOwn(strats, key)) {
|
if (strats && hasOwn(to, key) && hasOwn(strats, key)) {
|
||||||
to[key] = strats[key](to[key], from[key], instance.proxy, key)
|
to[key] = strats[key](to[key], from[key], instance && instance.proxy, key)
|
||||||
} else {
|
} else {
|
||||||
to[key] = from[key]
|
to[key] = from[key]
|
||||||
}
|
}
|
||||||
|
@ -497,20 +497,6 @@ export function createRenderContext(instance: ComponentInternalInstance) {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
// expose global properties
|
|
||||||
const { globalProperties } = instance.appContext.config
|
|
||||||
Object.keys(globalProperties).forEach(key => {
|
|
||||||
Object.defineProperty(target, key, {
|
|
||||||
configurable: true,
|
|
||||||
enumerable: false,
|
|
||||||
get: () => {
|
|
||||||
const val = globalProperties[key]
|
|
||||||
return __COMPAT__ && isFunction(val) ? val.bind(instance.proxy) : val
|
|
||||||
},
|
|
||||||
set: NOOP
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
return target as ComponentRenderContext
|
return target as ComponentRenderContext
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user