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:
parent
cf2c9f6faa
commit
a66e53a24f
@ -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' +
|
||||||
|
@ -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
|
||||||
*/
|
*/
|
||||||
|
@ -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.
|
||||||
|
@ -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
|
||||||
*/
|
*/
|
||||||
|
Loading…
x
Reference in New Issue
Block a user