diff --git a/packages/runtime-core/src/apiWatch.ts b/packages/runtime-core/src/apiWatch.ts index 6d092b2a..9ff82eaf 100644 --- a/packages/runtime-core/src/apiWatch.ts +++ b/packages/runtime-core/src/apiWatch.ts @@ -8,7 +8,7 @@ import { import { queueJob, queuePostFlushCb } from './scheduler' import { EMPTY_OBJ, isObject, isArray, isFunction } from '@vue/shared' import { recordEffect } from './apiReactivity' -import { getCurrentInstance } from './component' +import { currentInstance } from './component' import { ErrorTypes, callWithErrorHandling, @@ -83,7 +83,7 @@ function doWatch( | null, { lazy, deep, flush, onTrack, onTrigger }: WatchOptions = EMPTY_OBJ ): StopHandle { - const instance = getCurrentInstance() + const instance = currentInstance let getter: Function if (isArray(source)) { diff --git a/packages/runtime-core/src/component.ts b/packages/runtime-core/src/component.ts index 39631349..a76095aa 100644 --- a/packages/runtime-core/src/component.ts +++ b/packages/runtime-core/src/component.ts @@ -355,6 +355,10 @@ function createSetupContext(instance: ComponentInstance): SetupContext { return __DEV__ ? Object.freeze(context) : context } +// mark the current rendering instance for asset resolution (e.g. +// resolveComponent, resolveDirective) during render +export let currentRenderingInstance: ComponentInstance | null = null + export function renderComponentRoot(instance: ComponentInstance): VNode { const { type: Component, @@ -367,9 +371,12 @@ export function renderComponentRoot(instance: ComponentInstance): VNode { refs, emit } = instance + + let result + currentRenderingInstance = instance try { if (vnode.shapeFlag & ShapeFlags.STATEFUL_COMPONENT) { - return normalizeVNode( + result = normalizeVNode( (instance.render as RenderFunction).call( renderProxy, props, @@ -379,7 +386,7 @@ export function renderComponentRoot(instance: ComponentInstance): VNode { } else { // functional const render = Component as FunctionalComponent - return normalizeVNode( + result = normalizeVNode( render.length > 1 ? render(props, { attrs, @@ -392,8 +399,10 @@ export function renderComponentRoot(instance: ComponentInstance): VNode { } } catch (err) { handleError(err, instance, ErrorTypes.RENDER_FUNCTION) - return createVNode(Empty) + result = createVNode(Empty) } + currentRenderingInstance = null + return result } export function shouldUpdateComponent( diff --git a/packages/runtime-core/src/directives.ts b/packages/runtime-core/src/directives.ts new file mode 100644 index 00000000..46a697e6 --- /dev/null +++ b/packages/runtime-core/src/directives.ts @@ -0,0 +1,122 @@ +/** +Runtime helper for applying directives to a vnode. Example usage: + +const comp = resolveComponent('comp') +const foo = resolveDirective('foo') +const bar = resolveDirective('bar') + +return applyDirectives( + h(comp), + [foo, this.x], + [bar, this.y] +) +*/ + +import { VNode, cloneVNode } from './vnode' +import { extend } from '@vue/shared' +import { warn } from './warning' +import { + ComponentInstance, + currentRenderingInstance, + ComponentRenderProxy +} from './component' + +interface DirectiveBinding { + instance: ComponentRenderProxy | null + value?: any + oldValue?: any + arg?: string + modifiers?: DirectiveModifiers +} + +type DirectiveHook = ( + el: any, + binding: DirectiveBinding, + vnode: VNode, + prevVNode: VNode | void +) => void + +interface Directive { + beforeMount: DirectiveHook + mounted: DirectiveHook + beforeUpdate: DirectiveHook + updated: DirectiveHook + beforeUnmount: DirectiveHook + unmounted: DirectiveHook +} + +type DirectiveModifiers = Record + +const valueCache = new WeakMap>() + +function applyDirective( + props: Record, + instance: ComponentInstance, + directive: Directive, + value?: any, + arg?: string, + modifiers?: DirectiveModifiers +) { + let valueCacheForDir = valueCache.get(directive) as WeakMap + if (!valueCacheForDir) { + valueCacheForDir = new WeakMap() + valueCache.set(directive, valueCacheForDir) + } + for (const key in directive) { + const hook = directive[key as keyof Directive] + const hookKey = `vnode` + key[0].toUpperCase() + key.slice(1) + const vnodeHook = (vnode: VNode, prevVNode?: VNode) => { + let oldValue + if (prevVNode !== void 0) { + oldValue = valueCacheForDir.get(prevVNode) + valueCacheForDir.delete(prevVNode) + } + valueCacheForDir.set(vnode, value) + hook( + vnode.el, + { + instance: instance.renderProxy, + value, + oldValue, + arg, + modifiers + }, + vnode, + prevVNode + ) + } + const existing = props[hookKey] + props[hookKey] = existing + ? [].concat(existing as any, vnodeHook as any) + : vnodeHook + } +} + +type DirectiveArguments = [ + Directive, + any, + string | undefined, + DirectiveModifiers | undefined +][] + +export function applyDirectives( + vnode: VNode, + ...directives: DirectiveArguments +) { + const instance = currentRenderingInstance + if (instance !== null) { + vnode = cloneVNode(vnode) + vnode.props = vnode.props != null ? extend({}, vnode.props) : {} + for (let i = 0; i < directives.length; i++) { + applyDirective(vnode.props, instance, ...directives[i]) + } + } else if (__DEV__) { + warn(`applyDirectives can only be used inside render functions.`) + } + return vnode +} + +export function resolveDirective(name: string): Directive { + // TODO + return {} as any +} diff --git a/packages/runtime-core/src/index.ts b/packages/runtime-core/src/index.ts index 795fab7c..f555bb98 100644 --- a/packages/runtime-core/src/index.ts +++ b/packages/runtime-core/src/index.ts @@ -35,6 +35,9 @@ export { callWithAsyncErrorHandling } from './errorHandling' +// For the compiler +export { applyDirectives, resolveDirective } from './directives' + // Types ----------------------------------------------------------------------- export { VNode } from './vnode' diff --git a/packages/runtime-core/src/vnode.ts b/packages/runtime-core/src/vnode.ts index d2f77f66..3a79c195 100644 --- a/packages/runtime-core/src/vnode.ts +++ b/packages/runtime-core/src/vnode.ts @@ -34,7 +34,7 @@ export type NormalizedChildren = string | VNodeChildren | RawSlots | null export interface VNode { type: VNodeTypes - props: { [key: string]: any } | null + props: Record | null key: string | number | null ref: string | Function | null children: NormalizedChildren