import { isFunction, NO, isObject } from '@vue/shared'
import { version } from '.'
import { installCompatMount } from './compat/global'
-import { installLegacyConfigTraps } from './compat/globalConfig'
+import { installLegacyConfigProperties } from './compat/globalConfig'
export interface App<HostElement = any> {
version: string
if (__COMPAT__) {
installCompatMount(app, context, render, hydrate)
- if (__DEV__) installLegacyConfigTraps(app.config)
+ if (__DEV__) installLegacyConfigProperties(app.config)
}
return app
GLOBAL_SET = 'GLOBAL_SET',
GLOBAL_DELETE = 'GLOBAL_DELETE',
GLOBAL_OBSERVABLE = 'GLOBAL_OBSERVABLE',
+ GLOBAL_UTIL = 'GLOBAL_UTIL',
CONFIG_SILENT = 'CONFIG_SILENT',
CONFIG_DEVTOOLS = 'CONFIG_DEVTOOLS',
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]: {
message:
`config.silent has been removed because it is not good practice to ` +
-import { reactive } from '@vue/reactivity'
-import { isFunction } from '@vue/shared'
+import {
+ 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 { cloneVNode, createVNode } from '../vnode'
import { RootRenderFunction } from '../renderer'
isRuntimeOnly,
setupComponent
} from '../component'
-import { RenderFunction } from '../componentOptions'
+import { RenderFunction, mergeOptions } from '../componentOptions'
import { ComponentPublicInstance } from '../componentPublicInstance'
import { devtoolsInitApp } from '../devtools'
import { Directive } from '../directives'
isCopyingConfig = false
// copy prototype augmentations as config.globalProperties
- const isPrototypeEnabled = isCompatEnabled(
- DeprecationTypes.GLOBAL_PROTOTYPE,
- null
- )
+ if (isCompatEnabled(DeprecationTypes.GLOBAL_PROTOTYPE, null)) {
+ app.config.globalProperties = Ctor.prototype
+ }
let hasPrototypeAugmentations = false
for (const key in Ctor.prototype) {
if (key !== 'constructor') {
hasPrototypeAugmentations = true
- }
- if (isPrototypeEnabled) {
- app.config.globalProperties[key] = Ctor.prototype[key]
+ break
}
}
if (__DEV__ && hasPrototypeAugmentations) {
// TODO compiler warning for filters (maybe behavior compat?)
}) 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
return Vue
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)
+ }
+ })
+}
-import { isArray, isString } from '@vue/shared'
+import { extend, isArray, isString } from '@vue/shared'
import { AppConfig } from '../apiCreateApp'
import { isRuntimeOnly } from '../component'
import { isCompatEnabled } from './compatConfig'
+import { deepMergeData } from './data'
import { DeprecationTypes, warnDeprecation } from './deprecations'
import { isCopyingConfig } from './global'
}
// dev only
-export function installLegacyConfigTraps(config: AppConfig) {
+export function installLegacyConfigProperties(config: AppConfig) {
const legacyConfigOptions: Record<string, DeprecationTypes> = {
silent: DeprecationTypes.CONFIG_SILENT,
devtools: DeprecationTypes.CONFIG_DEVTOOLS,
}
})
})
+
+ // 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
}
return (raw.__merged = options)
}
-function mergeOptions(to: any, from: any, instance: ComponentInternalInstance) {
- const strats = instance.appContext.config.optionMergeStrategies
+export function mergeOptions(
+ to: any,
+ from: any,
+ instance?: ComponentInternalInstance
+) {
+ const strats = instance && instance.appContext.config.optionMergeStrategies
const { mixins, extends: extendsOptions } = from
extendsOptions && mergeOptions(to, extendsOptions, instance)
mixins.forEach((m: ComponentOptionsMixin) => mergeOptions(to, m, instance))
for (const key in from) {
- if (strats && hasOwn(strats, key)) {
- to[key] = strats[key](to[key], from[key], instance.proxy, key)
+ if (strats && hasOwn(to, key) && hasOwn(strats, key)) {
+ to[key] = strats[key](to[key], from[key], instance && instance.proxy, key)
} else {
to[key] = from[key]
}
})
})
- // 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
}