]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
wip: Vue.util compat
authorEvan You <yyx990803@gmail.com>
Sun, 11 Apr 2021 20:53:43 +0000 (16:53 -0400)
committerEvan You <yyx990803@gmail.com>
Sun, 11 Apr 2021 20:53:43 +0000 (16:53 -0400)
packages/runtime-core/src/apiCreateApp.ts
packages/runtime-core/src/compat/deprecations.ts
packages/runtime-core/src/compat/global.ts
packages/runtime-core/src/compat/globalConfig.ts
packages/runtime-core/src/componentOptions.ts
packages/runtime-core/src/componentPublicInstance.ts

index ed1971cbb2b651157a5a595f03749c8daf29a7d5..bda1501b87ad8b33e9f820c377eaefd229781849 100644 (file)
@@ -16,7 +16,7 @@ import { devtoolsInitApp, devtoolsUnmountApp } from './devtools'
 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
@@ -307,7 +307,7 @@ export function createAppAPI<HostElement>(
 
     if (__COMPAT__) {
       installCompatMount(app, context, render, hydrate)
-      if (__DEV__) installLegacyConfigTraps(app.config)
+      if (__DEV__) installLegacyConfigProperties(app.config)
     }
 
     return app
index e91ea9204870d2ff8ba144acaf39ec5f2137b3c3..f33f360d8e47f007b9ec7a92fcd07c40b7e786ee 100644 (file)
@@ -18,6 +18,7 @@ export const enum DeprecationTypes {
   GLOBAL_SET = 'GLOBAL_SET',
   GLOBAL_DELETE = 'GLOBAL_DELETE',
   GLOBAL_OBSERVABLE = 'GLOBAL_OBSERVABLE',
+  GLOBAL_UTIL = 'GLOBAL_UTIL',
 
   CONFIG_SILENT = 'CONFIG_SILENT',
   CONFIG_DEVTOOLS = 'CONFIG_DEVTOOLS',
@@ -113,6 +114,12 @@ const deprecationData: Record<DeprecationTypes, DeprecationData> = {
     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 ` +
index 93490b2c4c20846b2b1497f42a562d28c9ca06f0..13cb05c8030c583b681c68cc1afb266a22b80f7a 100644 (file)
@@ -1,5 +1,19 @@
-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'
@@ -20,7 +34,7 @@ import {
   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'
@@ -129,17 +143,14 @@ export function createCompatVue(
     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) {
@@ -228,6 +239,21 @@ export function createCompatVue(
     // 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
@@ -358,3 +384,67 @@ export function installCompatMount(
     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)
+    }
+  })
+}
index cf081c3eeabbcef609dadd3deab6bddfae27d4cd..fab0196fa839ba0123d7292bd5a57828448992a3 100644 (file)
@@ -1,7 +1,8 @@
-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'
 
@@ -34,7 +35,7 @@ export type LegacyConfig = {
 }
 
 // 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,
@@ -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
 }
index 8b029380ffa7fe5bbca50d23438776f0bf7cafd7..3ba0b9d3978cef179cde7b4f6401c29955d28a2f 100644 (file)
@@ -991,8 +991,12 @@ export function resolveMergedOptions(
   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)
@@ -1000,8 +1004,8 @@ function mergeOptions(to: any, from: any, instance: ComponentInternalInstance) {
     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]
     }
index f9d95aacd9cdad9ab3f7bfd3cd570a5b58c22ddc..22022317d258e1c081d5b79aa47795e1afa9a593 100644 (file)
@@ -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
 }