diff --git a/packages/runtime-core/src/compat/compatConfig.ts b/packages/runtime-core/src/compat/compatConfig.ts index cc79a5393..ee2bfcacb 100644 --- a/packages/runtime-core/src/compat/compatConfig.ts +++ b/packages/runtime-core/src/compat/compatConfig.ts @@ -30,6 +30,11 @@ export function isCompatEnabled( key: DeprecationTypes, instance: ComponentInternalInstance | null ): boolean { + // skip compat for built-in components + if (instance && instance.type.__isBuiltIn) { + return false + } + const mode = getCompatConfigForKey('MODE', instance) || 2 const val = getCompatConfigForKey(key, instance) if (mode === 2) { diff --git a/packages/runtime-core/src/compat/component.ts b/packages/runtime-core/src/compat/component.ts index e02dfcd89..d77477891 100644 --- a/packages/runtime-core/src/compat/component.ts +++ b/packages/runtime-core/src/compat/component.ts @@ -19,6 +19,10 @@ export function convertLegacyComponent( comp: any, instance: ComponentInternalInstance | null ): Component { + if (comp.__isBuiltIn) { + return comp + } + // 2.x async component // since after disabling this, plain functions are still valid usage, do not // use softAssert here. diff --git a/packages/runtime-core/src/compat/instance.ts b/packages/runtime-core/src/compat/instance.ts index 1a25801ca..2f9c254df 100644 --- a/packages/runtime-core/src/compat/instance.ts +++ b/packages/runtime-core/src/compat/instance.ts @@ -49,7 +49,11 @@ export function installCompatInstanceProperties(map: PublicPropertiesMap) { // overrides existing accessor $slots: i => { - if (isCompatEnabled(DeprecationTypes.RENDER_FUNCTION, i)) { + if ( + isCompatEnabled(DeprecationTypes.RENDER_FUNCTION, i) && + i.render && + i.render._compatWrapped + ) { return new Proxy(i.slots, legacySlotProxyHandlers) } return __DEV__ ? shallowReadonly(i.slots) : i.slots diff --git a/packages/runtime-core/src/compat/renderFn.ts b/packages/runtime-core/src/compat/renderFn.ts index 5bc720cf7..69e773f3c 100644 --- a/packages/runtime-core/src/compat/renderFn.ts +++ b/packages/runtime-core/src/compat/renderFn.ts @@ -5,7 +5,13 @@ import { ShapeFlags, toHandlerKey } from '@vue/shared' -import { Component, Data } from '../component' +import { + Component, + ComponentInternalInstance, + ComponentOptions, + Data, + InternalRenderFunction +} from '../component' import { DirectiveArguments, withDirectives } from '../directives' import { resolveDirective, @@ -19,6 +25,35 @@ import { VNodeArrayChildren, VNodeProps } from '../vnode' +import { checkCompatEnabled } from './compatConfig' +import { DeprecationTypes } from './deprecations' + +export function convertLegacyRenderFn(instance: ComponentInternalInstance) { + const Component = instance.type as ComponentOptions + const render = Component.render as InternalRenderFunction | undefined + + // v3 runtime compiled, or already checked / wrapped + if (!render || render._rc || render._compatChecked || render._compatWrapped) { + return + } + + const string = render.toString() + if (string.startsWith('function render(_ctx') || string.startsWith('(_ctx')) { + // v3 pre-compiled function + render._compatChecked = true + return + } + + // v2 render function, try to provide compat + if (checkCompatEnabled(DeprecationTypes.RENDER_FUNCTION, instance)) { + const wrapped = (Component.render = function compatRender() { + // @ts-ignore + return render.call(this, compatH) + }) + // @ts-ignore + wrapped._compatWrapped = true + } +} interface LegacyVNodeProps { key?: string | number diff --git a/packages/runtime-core/src/component.ts b/packages/runtime-core/src/component.ts index 2791fa952..d0e729457 100644 --- a/packages/runtime-core/src/component.ts +++ b/packages/runtime-core/src/component.ts @@ -54,9 +54,7 @@ import { CompilerOptions } from '@vue/compiler-core' import { markAttrsAccessed } from './componentRenderUtils' import { currentRenderingInstance } from './componentRenderContext' import { startMeasure, endMeasure } from './profiling' -import { checkCompatEnabled } from './compat/compatConfig' -import { DeprecationTypes } from './compat/deprecations' -import { compatH } from './compat/renderFn' +import { convertLegacyRenderFn } from './compat/renderFn' export type Data = Record @@ -96,6 +94,10 @@ export interface ComponentInternalOptions { * @internal */ __hmrId?: string + /** + * Compat build only, for bailing out of certain compatibility behavior + */ + __isBuiltIn?: boolean /** * This one should be exposed so that devtools can make use of it */ @@ -188,6 +190,10 @@ export type InternalRenderFunction = { $options: ComponentInternalInstance['ctx'] ): VNodeChild _rc?: boolean // isRuntimeCompiled + + // __COMPAT__ only + _compatChecked?: boolean // v3 and already checked for v2 compat + _compatWrapped?: boolean // is wrapped for v2 compat } /** @@ -684,15 +690,8 @@ export function finishComponentSetup( ) { const Component = instance.type as ComponentOptions - if ( - __COMPAT__ && - Component.render && - checkCompatEnabled(DeprecationTypes.RENDER_FUNCTION, instance) - ) { - const originalRender = Component.render - Component.render = function compatRender() { - return originalRender.call(this, compatH) - } + if (__COMPAT__) { + convertLegacyRenderFn(instance) } // template / render function normalization diff --git a/packages/runtime-core/src/components/BaseTransition.ts b/packages/runtime-core/src/components/BaseTransition.ts index a6f2fb27f..1cb47c819 100644 --- a/packages/runtime-core/src/components/BaseTransition.ts +++ b/packages/runtime-core/src/components/BaseTransition.ts @@ -1,7 +1,8 @@ import { getCurrentInstance, SetupContext, - ComponentInternalInstance + ComponentInternalInstance, + ComponentOptions } from '../component' import { cloneVNode, @@ -110,7 +111,7 @@ export function useTransitionState(): TransitionState { const TransitionHookValidator = [Function, Array] -const BaseTransitionImpl = { +const BaseTransitionImpl: ComponentOptions = { name: `BaseTransition`, props: { @@ -250,6 +251,10 @@ const BaseTransitionImpl = { } } +if (__COMPAT__) { + BaseTransitionImpl.__isBuiltIn = true +} + // export the public type for h/tsx inference // also to avoid inline import() in generated d.ts files export const BaseTransition = (BaseTransitionImpl as any) as { diff --git a/packages/runtime-core/src/components/KeepAlive.ts b/packages/runtime-core/src/components/KeepAlive.ts index cbba10fd7..b99355252 100644 --- a/packages/runtime-core/src/components/KeepAlive.ts +++ b/packages/runtime-core/src/components/KeepAlive.ts @@ -5,7 +5,8 @@ import { ComponentInternalInstance, LifecycleHooks, currentInstance, - getComponentName + getComponentName, + ComponentOptions } from '../component' import { VNode, cloneVNode, isVNode, VNodeProps } from '../vnode' import { warn } from '../warning' @@ -62,7 +63,7 @@ export interface KeepAliveContext extends ComponentRenderContext { export const isKeepAlive = (vnode: VNode): boolean => (vnode.type as any).__isKeepAlive -const KeepAliveImpl = { +const KeepAliveImpl: ComponentOptions = { name: `KeepAlive`, // Marker for special handling inside the renderer. We are not using a === @@ -298,6 +299,10 @@ const KeepAliveImpl = { } } +if (__COMPAT__) { + KeepAliveImpl.__isBuildIn = true +} + // export the public type for h/tsx inference // also to avoid inline import() in generated d.ts files export const KeepAlive = (KeepAliveImpl as any) as { diff --git a/packages/runtime-dom/src/components/Transition.ts b/packages/runtime-dom/src/components/Transition.ts index 1121af1eb..a253f51b4 100644 --- a/packages/runtime-dom/src/components/Transition.ts +++ b/packages/runtime-dom/src/components/Transition.ts @@ -46,6 +46,10 @@ export const Transition: FunctionalComponent = ( Transition.displayName = 'Transition' +if (__COMPAT__) { + Transition.__isBuiltIn = true +} + const DOMTransitionPropsValidators = { name: String, type: String, diff --git a/packages/runtime-dom/src/components/TransitionGroup.ts b/packages/runtime-dom/src/components/TransitionGroup.ts index f5d443e22..99f5fa987 100644 --- a/packages/runtime-dom/src/components/TransitionGroup.ts +++ b/packages/runtime-dom/src/components/TransitionGroup.ts @@ -22,7 +22,8 @@ import { SetupContext, toRaw, compatUtils, - DeprecationTypes + DeprecationTypes, + ComponentOptions } from '@vue/runtime-core' import { extend } from '@vue/shared' @@ -39,7 +40,7 @@ export type TransitionGroupProps = Omit & { moveClass?: string } -const TransitionGroupImpl = { +const TransitionGroupImpl: ComponentOptions = { name: 'TransitionGroup', props: /*#__PURE__*/ extend({}, TransitionPropsValidators, { @@ -145,6 +146,10 @@ const TransitionGroupImpl = { } } +if (__COMPAT__) { + TransitionGroupImpl.__isBuiltIn = true +} + /** * TransitionGroup does not support "mode" so we need to remove it from the * props declarations, but direct delete operation is considered a side effect