})
test.todo('mixin')
-
- test.todo('config.optionsMergeStrategies')
})
--- /dev/null
+describe('api: options', () => {
+ test('data', () => {})
+
+ test('computed', () => {})
+
+ test('methods', () => {})
+
+ test('watch', () => {})
+
+ test('provide/inject', () => {})
+
+ test('mixins', () => {})
+
+ test('extends', () => {})
+
+ test('lifecycle', () => {})
+})
Component,
ComponentRenderProxy,
Data,
- ComponentInstance,
- currentRenderingInstance,
- currentInstance
+ ComponentInstance
} from './component'
import { Directive } from './directives'
import { HostNode, RootRenderFunction } from './createRenderer'
import { InjectionKey } from './apiInject'
-import { isFunction, camelize, capitalize } from '@vue/shared'
+import { isFunction } from '@vue/shared'
import { warn } from './warning'
import { createVNode } from './vnode'
return app
}
}
-
-export function resolveAsset(type: 'components' | 'directives', name: string) {
- const instance = currentRenderingInstance || currentInstance
- if (instance) {
- let camelized
- let capitalized
- let res
- const local = (instance.type as any)[type]
- if (local) {
- res =
- local[name] ||
- local[(camelized = camelize(name))] ||
- local[(capitalized = capitalize(camelized))]
- }
- if (!res) {
- const global = instance.appContext[type]
- res =
- global[name] ||
- global[camelized || (camelized = camelize(name))] ||
- global[capitalized || capitalize(camelized)]
- }
- if (__DEV__ && !res) {
- warn(`Failed to resolve ${type.slice(0, -1)}: ${name}`)
- }
- return res
- } else if (__DEV__) {
- warn(
- `resolve${capitalize(type.slice(0, -1))} ` +
- `can only be used in render() or setup().`
- )
- }
-}
ComponentInstance,
Data,
ComponentOptions,
- ComponentRenderProxy
+ currentRenderingInstance,
+ currentInstance
} from './component'
import {
isFunction,
isString,
isObject,
isArray,
- EMPTY_OBJ
+ EMPTY_OBJ,
+ capitalize,
+ camelize
} from '@vue/shared'
import { computed, ComputedOptions } from './apiReactivity'
-import { watch, WatchOptions } from './apiWatch'
+import { watch } from './apiWatch'
import { provide, inject } from './apiInject'
import {
onBeforeMount,
onUnmounted
} from './apiLifecycle'
import { DebuggerEvent } from '@vue/reactivity'
+import { warn } from './warning'
-type LegacyComponent =
- | ComponentOptions
- | {
- new (): ComponentRenderProxy
- options: ComponentOptions
- }
+// TODO legacy component definition also supports constructors with .options
+type LegacyComponent = ComponentOptions
// TODO type inference for these options
export interface LegacyOptions {
errorCaptured?(): boolean
}
-export function processOptions(instance: ComponentInstance) {
+export function applyOptions(
+ instance: ComponentInstance,
+ options: ComponentOptions,
+ asMixin: boolean = false
+) {
const data =
instance.data === EMPTY_OBJ ? (instance.data = {}) : instance.data
const ctx = instance.renderProxy as any
const {
+ // composition
+ mixins,
+ extends: extendsOptions,
+ // state
data: dataOptions,
computed: computedOptions,
methods,
watch: watchOptions,
provide: provideOptions,
inject: injectOptions,
+ // assets
+ components,
+ directives,
+ // lifecycle
// beforeCreate is handled separately
created,
beforeMount,
renderTracked,
renderTriggered,
errorCaptured
- } = instance.type as ComponentOptions
+ } = options
+
+ // global mixins are applied first, and only if this is a non-mixin call
+ // so that they are applied once per instance.
+ if (!asMixin) {
+ applyMixins(instance, instance.appContext.mixins)
+ }
+ // extending a base component...
+ if (extendsOptions) {
+ applyOptions(instance, extendsOptions, true)
+ }
+ // local mixins
+ if (mixins) {
+ applyMixins(instance, mixins)
+ }
+ // state options
if (dataOptions) {
extend(data, isFunction(dataOptions) ? dataOptions.call(ctx) : dataOptions)
}
-
if (computedOptions) {
for (const key in computedOptions) {
data[key] = computed(computedOptions[key] as any)
}
}
-
if (methods) {
for (const key in methods) {
data[key] = methods[key].bind(ctx)
}
}
-
if (watchOptions) {
for (const key in watchOptions) {
const raw = watchOptions[key]
}
}
}
-
if (provideOptions) {
const provides = isFunction(provideOptions)
? provideOptions.call(ctx)
provide(key, provides[key])
}
}
-
if (injectOptions) {
if (isArray(injectOptions)) {
for (let i = 0; i < injectOptions.length; i++) {
}
}
+ // asset options
+ if (components) {
+ extend(instance.components, components)
+ }
+ if (directives) {
+ extend(instance.directives, directives)
+ }
+
+ // lifecycle options
if (created) {
created.call(ctx)
}
}
}
-export function legacyWatch(
- this: ComponentInstance,
- source: string | Function,
- cb: Function,
- options?: WatchOptions
-): () => void {
- const ctx = this.renderProxy as any
- const getter = isString(source) ? () => ctx[source] : source.bind(ctx)
- const stop = watch(getter, cb.bind(ctx), options)
- onBeforeMount(stop, this)
- return stop
+function applyMixins(instance: ComponentInstance, mixins: ComponentOptions[]) {
+ for (let i = 0; i < mixins.length; i++) {
+ applyOptions(instance, mixins[i], true)
+ }
+}
+
+export function resolveAsset(type: 'components' | 'directives', name: string) {
+ const instance = currentRenderingInstance || currentInstance
+ if (instance) {
+ let camelized
+ const registry = instance[type]
+ const res =
+ registry[name] ||
+ registry[(camelized = camelize(name))] ||
+ registry[capitalize(camelized)]
+ if (__DEV__ && !res) {
+ warn(`Failed to resolve ${type.slice(0, -1)}: ${name}`)
+ }
+ return res
+ } else if (__DEV__) {
+ warn(
+ `resolve${capitalize(type.slice(0, -1))} ` +
+ `can only be used in render() or setup().`
+ )
+ }
}
ReactiveEffectOptions
} from '@vue/reactivity'
import { queueJob, queuePostFlushCb } from './scheduler'
-import { EMPTY_OBJ, isObject, isArray, isFunction } from '@vue/shared'
+import { EMPTY_OBJ, isObject, isArray, isFunction, isString } from '@vue/shared'
import { recordEffect } from './apiReactivity'
-import { currentInstance } from './component'
+import { currentInstance, ComponentInstance } from './component'
import {
ErrorTypes,
callWithErrorHandling,
callWithAsyncErrorHandling
} from './errorHandling'
+import { onBeforeMount } from './apiLifecycle'
export interface WatchOptions {
lazy?: boolean
}
}
+// this.$watch
+export function instanceWatch(
+ this: ComponentInstance,
+ source: string | Function,
+ cb: Function,
+ options?: WatchOptions
+): () => void {
+ const ctx = this.renderProxy as any
+ const getter = isString(source) ? () => ctx[source] : source.bind(ctx)
+ const stop = watch(getter, cb.bind(ctx), options)
+ onBeforeMount(stop, this)
+ return stop
+}
+
function traverse(value: any, seen: Set<any> = new Set()) {
if (!isObject(value) || seen.has(value)) {
return
callWithErrorHandling,
callWithAsyncErrorHandling
} from './errorHandling'
-import { AppContext, createAppContext, resolveAsset } from './apiApp'
+import { AppContext, createAppContext } from './apiApp'
import { Directive } from './directives'
-import { processOptions, LegacyOptions } from './apiOptions'
+import { applyOptions, LegacyOptions, resolveAsset } from './apiOptions'
export type Data = { [key: string]: unknown }
effects: ReactiveEffect[] | null
provides: Data
+ components: Record<string, Component>
+ directives: Record<string, Directive>
+
// the rest are only for stateful components
data: S
props: P
vnode,
parent,
appContext,
- type: vnode.type as any,
+ type: vnode.type as Component,
root: null as any, // set later so it can point to itself
next: null,
subTree: null as any,
slots: EMPTY_OBJ,
refs: EMPTY_OBJ,
+ // per-instance asset storage (mutable during options resolution)
+ components: Object.create(appContext.components),
+ directives: Object.create(appContext.directives),
+
// user namespace for storing whatever the user assigns to `this`
user: {},
}
// support for 2.x options
if (__FEATURE_OPTIONS__) {
- processOptions(instance)
+ applyOptions(instance, Component)
}
instance.data = reactive(instance.data === EMPTY_OBJ ? {} : instance.data)
currentInstance = null
}
export function resolveComponent(name: string): Component | undefined {
- return resolveAsset('components', name)
+ return resolveAsset('components', name) as any
}
import { ComponentInstance } from './component'
import { nextTick } from './scheduler'
-import { legacyWatch } from './apiOptions'
+import { instanceWatch } from './apiWatch'
export const RenderProxyHandlers = {
get(target: ComponentInstance, key: string) {
case '$nextTick':
return nextTick
case '$watch':
- return legacyWatch.bind(target)
+ return instanceWatch.bind(target)
}
}
return target.user[key]
} from './component'
import { callWithAsyncErrorHandling, ErrorTypes } from './errorHandling'
import { HostNode } from './createRenderer'
-import { resolveAsset } from './apiApp'
+import { resolveAsset } from './apiOptions'
export interface DirectiveBinding {
instance: ComponentRenderProxy | null
}
export function resolveDirective(name: string): Directive | undefined {
- return resolveAsset('directives', name)
+ return resolveAsset('directives', name) as any
}