From a66e53a24f445b688eef6812ecb872dc53cf2702 Mon Sep 17 00:00:00 2001 From: Evan You Date: Tue, 6 Oct 2020 15:31:29 -0400 Subject: [PATCH] fix(runtime-core): fix SSR memoery leak due to props normalization cache fix #2225 The previous props/emits normlaization was caching normalized result per app instance, but during SSR there is a new app instance created for every request. The fix now de-opts props/emits normlaization caching when there are props/emits declared in global mixins - which is a very rare use case. --- packages/runtime-core/src/apiCreateApp.ts | 16 ++++++++++- packages/runtime-core/src/component.ts | 4 +-- packages/runtime-core/src/componentEmits.ts | 11 +++---- packages/runtime-core/src/componentProps.ts | 32 +++++++++------------ 4 files changed, 34 insertions(+), 29 deletions(-) diff --git a/packages/runtime-core/src/apiCreateApp.ts b/packages/runtime-core/src/apiCreateApp.ts index 2397cdf3379..bcb2fec1c32 100644 --- a/packages/runtime-core/src/apiCreateApp.ts +++ b/packages/runtime-core/src/apiCreateApp.ts @@ -74,7 +74,16 @@ export interface AppContext { components: Record directives: Record provides: Record - reload?: () => void // HMR only + /** + * Flag for de-optimizing props normalization + * @internal + */ + deopt?: boolean + /** + * HMR only + * @internal + */ + reload?: () => void } type PluginInstallFunction = (app: App, ...options: any[]) => any @@ -169,6 +178,11 @@ export function createAppAPI( if (__FEATURE_OPTIONS_API__) { if (!context.mixins.includes(mixin)) { context.mixins.push(mixin) + // global mixin with props/emits de-optimizes props/emits + // normalization caching. + if (mixin.props || mixin.emits) { + context.deopt = true + } } else if (__DEV__) { warn( 'Mixin has already been applied to target app' + diff --git a/packages/runtime-core/src/component.ts b/packages/runtime-core/src/component.ts index 78abad60a8a..19035961c96 100644 --- a/packages/runtime-core/src/component.ts +++ b/packages/runtime-core/src/component.ts @@ -79,11 +79,11 @@ export interface ComponentInternalOptions { /** * @internal */ - __props?: Record + __props?: NormalizedPropsOptions /** * @internal */ - __emits?: Record + __emits?: ObjectEmitsOptions | null /** * @internal */ diff --git a/packages/runtime-core/src/componentEmits.ts b/packages/runtime-core/src/componentEmits.ts index daf82b71e30..ac37bc568d8 100644 --- a/packages/runtime-core/src/componentEmits.ts +++ b/packages/runtime-core/src/componentEmits.ts @@ -109,11 +109,8 @@ export function normalizeEmitsOptions( appContext: AppContext, asMixin = false ): ObjectEmitsOptions | null { - const appId = appContext.app ? appContext.app._uid : -1 - const cache = comp.__emits || (comp.__emits = {}) - const cached = cache[appId] - if (cached !== undefined) { - return cached + if (!appContext.deopt && comp.__emits !== undefined) { + return comp.__emits } const raw = comp.emits @@ -138,7 +135,7 @@ export function normalizeEmitsOptions( } if (!raw && !hasExtends) { - return (cache[appId] = null) + return (comp.__emits = null) } if (isArray(raw)) { @@ -146,7 +143,7 @@ export function normalizeEmitsOptions( } else { extend(normalized, raw) } - return (cache[appId] = normalized) + return (comp.__emits = normalized) } // Check if an incoming prop key is a declared emit event listener. diff --git a/packages/runtime-core/src/componentProps.ts b/packages/runtime-core/src/componentProps.ts index f977099d806..9fc2ac78b88 100644 --- a/packages/runtime-core/src/componentProps.ts +++ b/packages/runtime-core/src/componentProps.ts @@ -328,11 +328,8 @@ export function normalizePropsOptions( appContext: AppContext, asMixin = false ): NormalizedPropsOptions { - const appId = appContext.app ? appContext.app._uid : -1 - const cache = comp.__props || (comp.__props = {}) - const cached = cache[appId] - if (cached) { - return cached + if (!appContext.deopt && comp.__props) { + return comp.__props } const raw = comp.props @@ -360,7 +357,7 @@ export function normalizePropsOptions( } if (!raw && !hasExtends) { - return (cache[appId] = EMPTY_ARR) + return (comp.__props = EMPTY_ARR) } if (isArray(raw)) { @@ -398,7 +395,16 @@ export function normalizePropsOptions( } } - return (cache[appId] = [normalized, needCastKeys]) + return (comp.__props = [normalized, needCastKeys]) +} + +function validatePropName(key: string) { + if (key[0] !== '$') { + return true + } else if (__DEV__) { + warn(`Invalid prop name: "${key}" is a reserved property.`) + } + return false } // use function string name to check type constructors @@ -441,18 +447,6 @@ function validateProps(props: Data, instance: ComponentInternalInstance) { } } -/** - * dev only - */ -function validatePropName(key: string) { - if (key[0] !== '$') { - return true - } else if (__DEV__) { - warn(`Invalid prop name: "${key}" is a reserved property.`) - } - return false -} - /** * dev only */