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.
This commit is contained in:
Evan You 2020-10-06 15:31:29 -04:00
parent cf2c9f6faa
commit a66e53a24f
4 changed files with 34 additions and 29 deletions

View File

@ -74,7 +74,16 @@ export interface AppContext {
components: Record<string, Component> components: Record<string, Component>
directives: Record<string, Directive> directives: Record<string, Directive>
provides: Record<string | symbol, any> provides: Record<string | symbol, any>
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 type PluginInstallFunction = (app: App, ...options: any[]) => any
@ -169,6 +178,11 @@ export function createAppAPI<HostElement>(
if (__FEATURE_OPTIONS_API__) { if (__FEATURE_OPTIONS_API__) {
if (!context.mixins.includes(mixin)) { if (!context.mixins.includes(mixin)) {
context.mixins.push(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__) { } else if (__DEV__) {
warn( warn(
'Mixin has already been applied to target app' + 'Mixin has already been applied to target app' +

View File

@ -79,11 +79,11 @@ export interface ComponentInternalOptions {
/** /**
* @internal * @internal
*/ */
__props?: Record<number, NormalizedPropsOptions> __props?: NormalizedPropsOptions
/** /**
* @internal * @internal
*/ */
__emits?: Record<number, ObjectEmitsOptions | null> __emits?: ObjectEmitsOptions | null
/** /**
* @internal * @internal
*/ */

View File

@ -109,11 +109,8 @@ export function normalizeEmitsOptions(
appContext: AppContext, appContext: AppContext,
asMixin = false asMixin = false
): ObjectEmitsOptions | null { ): ObjectEmitsOptions | null {
const appId = appContext.app ? appContext.app._uid : -1 if (!appContext.deopt && comp.__emits !== undefined) {
const cache = comp.__emits || (comp.__emits = {}) return comp.__emits
const cached = cache[appId]
if (cached !== undefined) {
return cached
} }
const raw = comp.emits const raw = comp.emits
@ -138,7 +135,7 @@ export function normalizeEmitsOptions(
} }
if (!raw && !hasExtends) { if (!raw && !hasExtends) {
return (cache[appId] = null) return (comp.__emits = null)
} }
if (isArray(raw)) { if (isArray(raw)) {
@ -146,7 +143,7 @@ export function normalizeEmitsOptions(
} else { } else {
extend(normalized, raw) extend(normalized, raw)
} }
return (cache[appId] = normalized) return (comp.__emits = normalized)
} }
// Check if an incoming prop key is a declared emit event listener. // Check if an incoming prop key is a declared emit event listener.

View File

@ -328,11 +328,8 @@ export function normalizePropsOptions(
appContext: AppContext, appContext: AppContext,
asMixin = false asMixin = false
): NormalizedPropsOptions { ): NormalizedPropsOptions {
const appId = appContext.app ? appContext.app._uid : -1 if (!appContext.deopt && comp.__props) {
const cache = comp.__props || (comp.__props = {}) return comp.__props
const cached = cache[appId]
if (cached) {
return cached
} }
const raw = comp.props const raw = comp.props
@ -360,7 +357,7 @@ export function normalizePropsOptions(
} }
if (!raw && !hasExtends) { if (!raw && !hasExtends) {
return (cache[appId] = EMPTY_ARR) return (comp.__props = EMPTY_ARR)
} }
if (isArray(raw)) { 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 // 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 * dev only
*/ */