preset: 'ts-jest',
globals: {
__DEV__: true,
- __COMPAT__: false,
- __JSDOM__: true
+ __JSDOM__: true,
+ __FEATURE_OPTIONS__: true,
+ __FEATURE_PRODUCTION_TIP__: false
},
coverageDirectory: 'coverage',
coverageReporters: ['html', 'lcov', 'text'],
readonly effect: ReactiveEffect
}
-export interface ComputedOptions<T> {
+export interface ComputedOptions<T = any> {
get: () => T
set: (v: T) => void
}
function injectHook(
type: LifecycleHooks,
hook: Function,
- target: ComponentInstance | null = currentInstance
+ target: ComponentInstance | null
) {
if (target) {
;(target[type] || (target[type] = [])).push((...args: any[]) => {
})
} else if (__DEV__) {
const apiName = `on${capitalize(
- ErrorTypeStrings[name].replace(/ hook$/, '')
+ ErrorTypeStrings[type].replace(/ hook$/, '')
)}`
warn(
`${apiName} is called when there is no active component instance to be ` +
--- /dev/null
+import {
+ ComponentInstance,
+ Data,
+ ComponentOptions,
+ ComponentRenderProxy
+} from './component'
+import {
+ isFunction,
+ extend,
+ isString,
+ isObject,
+ isArray,
+ EMPTY_OBJ
+} from '@vue/shared'
+import { computed, ComputedOptions } from './apiReactivity'
+import { watch } from './apiWatch'
+import { provide, inject } from './apiInject'
+import {
+ onBeforeMount,
+ onMounted,
+ onBeforeUpdate,
+ onUpdated,
+ onErrorCaptured,
+ onRenderTracked,
+ onBeforeUnmount,
+ onUnmounted
+} from './apiLifecycle'
+import { DebuggerEvent } from '@vue/reactivity'
+
+type LegacyComponent =
+ | ComponentOptions
+ | {
+ new (): ComponentRenderProxy
+ options: ComponentOptions
+ }
+
+// TODO type inference for these options
+export interface LegacyOptions {
+ el?: any
+
+ // state
+ data?: Data | (() => Data)
+ computed?: Record<string, (() => any) | ComputedOptions>
+ methods?: Record<string, Function>
+ // TODO watch array
+ watch?: Record<
+ string,
+ | string
+ | Function
+ | { handler: Function; deep?: boolean; immediate: boolean }
+ >
+ provide?: Data | (() => Data)
+ inject?:
+ | string[]
+ | Record<
+ string | symbol,
+ string | symbol | { from: string | symbol; default: any }
+ >
+
+ // composition
+ mixins?: LegacyComponent[]
+ extends?: LegacyComponent
+
+ // lifecycle
+ beforeCreate?(): void
+ created?(): void
+ beforeMount?(): void
+ mounted?(): void
+ beforeUpdate?(): void
+ updated?(): void
+ activated?(): void
+ decativated?(): void
+ beforeDestroy?(): void
+ destroyed?(): void
+ renderTracked?(e: DebuggerEvent): void
+ renderTriggered?(e: DebuggerEvent): void
+ errorCaptured?(): boolean
+}
+
+export function processOptions(instance: ComponentInstance) {
+ const data =
+ instance.data === EMPTY_OBJ ? (instance.data = {}) : instance.data
+ const ctx = instance.renderProxy as any
+ const {
+ data: dataOptions,
+ computed: computedOptions,
+ methods,
+ watch: watchOptions,
+ provide: provideOptions,
+ inject: injectOptions,
+ // beforeCreate is handled separately
+ created,
+ beforeMount,
+ mounted,
+ beforeUpdate,
+ updated,
+ // TODO activated
+ // TODO decativated
+ beforeDestroy,
+ destroyed,
+ renderTracked,
+ renderTriggered,
+ errorCaptured
+ } = instance.type as ComponentOptions
+
+ 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]
+ const getter = () => ctx[key]
+ if (isString(raw)) {
+ const handler = data[key]
+ if (isFunction(handler)) {
+ watch(getter, handler.bind(ctx))
+ } else if (__DEV__) {
+ // TODO warn invalid watch handler path
+ }
+ } else if (isFunction(raw)) {
+ watch(getter, raw.bind(ctx))
+ } else if (isObject(raw)) {
+ watch(getter, raw.handler.bind(ctx), {
+ deep: !!raw.deep,
+ lazy: !raw.immediate
+ })
+ } else if (__DEV__) {
+ // TODO warn invalid watch options
+ }
+ }
+ }
+
+ if (provideOptions) {
+ const provides = isFunction(provideOptions)
+ ? provideOptions.call(ctx)
+ : provideOptions
+ for (const key in provides) {
+ provide(key, provides[key])
+ }
+ }
+
+ if (injectOptions) {
+ if (isArray(injectOptions)) {
+ for (let i = 0; i < injectOptions.length; i++) {
+ const key = injectOptions[i]
+ data[key] = inject(key)
+ }
+ } else {
+ for (const key in injectOptions) {
+ const opt = injectOptions[key]
+ if (isObject(opt)) {
+ data[key] = inject(opt.from, opt.default)
+ } else {
+ data[key] = inject(opt)
+ }
+ }
+ }
+ }
+
+ if (created) {
+ created.call(ctx)
+ }
+ if (beforeMount) {
+ onBeforeMount(beforeMount.bind(ctx))
+ }
+ if (mounted) {
+ onMounted(mounted.bind(ctx))
+ }
+ if (beforeUpdate) {
+ onBeforeUpdate(beforeUpdate.bind(ctx))
+ }
+ if (updated) {
+ onUpdated(updated.bind(ctx))
+ }
+ if (errorCaptured) {
+ onErrorCaptured(errorCaptured.bind(ctx))
+ }
+ if (renderTracked) {
+ onRenderTracked(renderTracked.bind(ctx))
+ }
+ if (renderTriggered) {
+ onRenderTracked(renderTriggered.bind(ctx))
+ }
+ if (beforeDestroy) {
+ onBeforeUnmount(beforeDestroy.bind(ctx))
+ }
+ if (destroyed) {
+ onUnmounted(destroyed.bind(ctx))
+ }
+}
OperationTypes,
Ref,
ComputedRef,
- UnwrapRef
+ UnwrapRef,
+ ComputedOptions
} from '@vue/reactivity'
import {
import { VNode, normalizeVNode, VNodeChild, createVNode, Empty } from './vnode'
import { ReactiveEffect, UnwrapRef, reactive, readonly } from '@vue/reactivity'
-import { EMPTY_OBJ, isFunction, capitalize, NOOP, isArray } from '@vue/shared'
+import {
+ EMPTY_OBJ,
+ isFunction,
+ capitalize,
+ NOOP,
+ isArray,
+ isObject
+} from '@vue/shared'
import { RenderProxyHandlers } from './componentProxy'
import { ComponentPropsOptions, ExtractPropTypes } from './componentProps'
import { Slots } from './componentSlots'
} from './errorHandling'
import { AppContext, createAppContext, resolveAsset } from './apiApp'
import { Directive } from './directives'
+import { processOptions, LegacyOptions } from './apiOptions'
export type Data = { [key: string]: unknown }
this: ComponentRenderProxy<Props, Bindings>
) => VNodeChild
-interface ComponentOptionsBase<Props, RawBindings> {
+interface ComponentOptionsBase<Props, RawBindings> extends LegacyOptions {
setup?: (
props: Props,
ctx: SetupContext
) => RawBindings | (() => VNodeChild) | void
+ name?: string
+ template?: string
render?: RenderFunction<Props, RawBindings>
components?: Record<string, Component>
directives?: Record<string, Directive>
- // TODO full 2.x options compat
}
interface ComponentOptionsWithoutProps<Props = {}, RawBindings = {}>
}
export function setupStatefulComponent(instance: ComponentInstance) {
+ currentInstance = instance
const Component = instance.type as ComponentOptions
// 1. create render proxy
instance.renderProxy = new Proxy(instance, RenderProxyHandlers) as any
if (setup) {
const setupContext = (instance.setupContext =
setup.length > 1 ? createSetupContext(instance) : null)
-
- currentInstance = instance
const setupResult = callWithErrorHandling(
setup,
instance,
ErrorTypes.SETUP_FUNCTION,
[propsProxy, setupContext]
)
- currentInstance = null
if (isFunction(setupResult)) {
// setup returned an inline render function
}
// setup returned bindings.
// assuming a render function compiled from template is present.
- instance.data = reactive(setupResult || {})
+ if (isObject(setupResult)) {
+ instance.data = setupResult
+ } else if (__DEV__ && setupResult !== undefined) {
+ warn(
+ `setup() should return an object. Received: ${
+ setupResult === null ? 'null' : typeof setupResult
+ }`
+ )
+ }
instance.render = (Component.render || NOOP) as RenderFunction
}
} else {
if (__DEV__ && !Component.render) {
- // TODO warn missing render fn
+ warn(
+ `Component is missing render function. Either provide a template or ` +
+ `return a render function from setup().`
+ )
}
instance.render = Component.render as RenderFunction
}
+ // support for 2.x options
+ if (__FEATURE_OPTIONS__) {
+ processOptions(instance)
+ }
+ instance.data = reactive(instance.data === EMPTY_OBJ ? {} : instance.data)
+ currentInstance = null
}
// used to identify a setup context proxy