diff --git a/packages/runtime-core/src/compat/customDirective.ts b/packages/runtime-core/src/compat/customDirective.ts new file mode 100644 index 00000000..f7ae36ec --- /dev/null +++ b/packages/runtime-core/src/compat/customDirective.ts @@ -0,0 +1,49 @@ +import { isArray } from '@vue/shared/src' +import { ObjectDirective, DirectiveHook } from '../directives' +import { DeprecationTypes, warnDeprecation } from './deprecations' + +export interface LegacyDirective { + bind?: DirectiveHook + inserted?: DirectiveHook + update?: DirectiveHook + componentUpdated?: DirectiveHook + unbind?: DirectiveHook +} + +const legacyDirectiveHookMap: Partial< + Record< + keyof ObjectDirective, + keyof LegacyDirective | (keyof LegacyDirective)[] + > +> = { + beforeMount: 'bind', + mounted: 'inserted', + updated: ['update', 'componentUpdated'], + unmounted: 'unbind' +} + +export function mapCompatDirectiveHook( + name: keyof ObjectDirective, + dir: ObjectDirective & LegacyDirective +): DirectiveHook | DirectiveHook[] | undefined { + const mappedName = legacyDirectiveHookMap[name] + if (mappedName) { + if (isArray(mappedName)) { + const hook: DirectiveHook[] = [] + mappedName.forEach(name => { + const mappedHook = dir[name] + if (mappedHook) { + __DEV__ && + warnDeprecation(DeprecationTypes.CUSTOM_DIR, mappedName, name) + hook.push(mappedHook) + } + }) + return hook.length ? hook : undefined + } else { + if (__DEV__ && dir[mappedName]) { + warnDeprecation(DeprecationTypes.CUSTOM_DIR, mappedName, name) + } + return dir[mappedName] + } + } +} diff --git a/packages/runtime-core/src/compat/deprecations.ts b/packages/runtime-core/src/compat/deprecations.ts index 44fd4438..4dda842d 100644 --- a/packages/runtime-core/src/compat/deprecations.ts +++ b/packages/runtime-core/src/compat/deprecations.ts @@ -24,7 +24,9 @@ export const enum DeprecationTypes { OPTIONS_BEFORE_DESTROY, OPTIONS_DESTROYED, - PROPS_DEFAULT_THIS + PROPS_DEFAULT_THIS, + + CUSTOM_DIR } type DeprecationData = { @@ -157,6 +159,13 @@ const deprecations: Record = { `props default value function no longer has access to "this". ` + `(found in prop "${key}")`, link: `https://v3.vuejs.org/guide/migration/props-default-this.html` + }, + + [DeprecationTypes.CUSTOM_DIR]: { + message: (legacyHook: string, newHook: string) => + `Custom directive hook "${legacyHook}" has been removed. ` + + `Use "${newHook}" instead.`, + link: `https://v3.vuejs.org/guide/migration/custom-directives.html` } } diff --git a/packages/runtime-core/src/compat/global.ts b/packages/runtime-core/src/compat/global.ts index 5f09239c..68b1bb53 100644 --- a/packages/runtime-core/src/compat/global.ts +++ b/packages/runtime-core/src/compat/global.ts @@ -28,6 +28,7 @@ import { nextTick } from '../scheduler' import { warnDeprecation, DeprecationTypes } from './deprecations' import { version } from '..' import { LegacyConfig } from './globalConfig' +import { LegacyDirective } from './customDirective' /** * @deprecated the default `Vue` export has been removed in Vue 3. The type for @@ -94,6 +95,12 @@ export function createCompatVue( function createCompatApp(options: ComponentOptions = {}, Ctor: any) { const app = createApp(options) + // copy over asset registries and deopt flag + ;['mixins', 'components', 'directives', 'deopt'].forEach(key => { + // @ts-ignore + app._context[key] = singletonApp._context[key] + }) + // copy over global config mutations isCopyingConfig = true for (const key in singletonApp.config) { @@ -184,7 +191,7 @@ export function createCompatVue( return Vue } - Vue.component = ((name: string, comp: any) => { + Vue.component = ((name: string, comp: Component) => { if (comp) { singletonApp.component(name, comp) return Vue @@ -193,9 +200,9 @@ export function createCompatVue( } }) as any - Vue.directive = ((name: string, dir: any) => { + Vue.directive = ((name: string, dir: Directive | LegacyDirective) => { if (dir) { - singletonApp.directive(name, dir) + singletonApp.directive(name, dir as Directive) return Vue } else { return singletonApp.directive(name) diff --git a/packages/runtime-core/src/directives.ts b/packages/runtime-core/src/directives.ts index 20f25d03..69b3d3ad 100644 --- a/packages/runtime-core/src/directives.ts +++ b/packages/runtime-core/src/directives.ts @@ -18,6 +18,7 @@ import { ComponentInternalInstance, Data } from './component' import { currentRenderingInstance } from './componentRenderContext' import { callWithAsyncErrorHandling, ErrorCodes } from './errorHandling' import { ComponentPublicInstance } from './componentPublicInstance' +import { mapCompatDirectiveHook } from './compat/customDirective' export interface DirectiveBinding { instance: ComponentPublicInstance | null @@ -124,7 +125,10 @@ export function invokeDirectiveHook( if (oldBindings) { binding.oldValue = oldBindings[i].value } - const hook = binding.dir[name] as DirectiveHook | undefined + let hook = binding.dir[name] as DirectiveHook | DirectiveHook[] | undefined + if (__COMPAT__ && !hook) { + hook = mapCompatDirectiveHook(name, binding.dir) + } if (hook) { callWithAsyncErrorHandling(hook, instance, ErrorCodes.DIRECTIVE_HOOK, [ vnode.el,