Skip to content

Commit a66e53a

Browse files
committed
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.
1 parent cf2c9f6 commit a66e53a

File tree

4 files changed

+34
-29
lines changed

4 files changed

+34
-29
lines changed

packages/runtime-core/src/apiCreateApp.ts

+15-1
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,16 @@ export interface AppContext {
7474
components: Record<string, Component>
7575
directives: Record<string, Directive>
7676
provides: Record<string | symbol, any>
77-
reload?: () => void // HMR only
77+
/**
78+
* Flag for de-optimizing props normalization
79+
* @internal
80+
*/
81+
deopt?: boolean
82+
/**
83+
* HMR only
84+
* @internal
85+
*/
86+
reload?: () => void
7887
}
7988

8089
type PluginInstallFunction = (app: App, ...options: any[]) => any
@@ -169,6 +178,11 @@ export function createAppAPI<HostElement>(
169178
if (__FEATURE_OPTIONS_API__) {
170179
if (!context.mixins.includes(mixin)) {
171180
context.mixins.push(mixin)
181+
// global mixin with props/emits de-optimizes props/emits
182+
// normalization caching.
183+
if (mixin.props || mixin.emits) {
184+
context.deopt = true
185+
}
172186
} else if (__DEV__) {
173187
warn(
174188
'Mixin has already been applied to target app' +

packages/runtime-core/src/component.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -79,11 +79,11 @@ export interface ComponentInternalOptions {
7979
/**
8080
* @internal
8181
*/
82-
__props?: Record<number, NormalizedPropsOptions>
82+
__props?: NormalizedPropsOptions
8383
/**
8484
* @internal
8585
*/
86-
__emits?: Record<number, ObjectEmitsOptions | null>
86+
__emits?: ObjectEmitsOptions | null
8787
/**
8888
* @internal
8989
*/

packages/runtime-core/src/componentEmits.ts

+4-7
Original file line numberDiff line numberDiff line change
@@ -109,11 +109,8 @@ export function normalizeEmitsOptions(
109109
appContext: AppContext,
110110
asMixin = false
111111
): ObjectEmitsOptions | null {
112-
const appId = appContext.app ? appContext.app._uid : -1
113-
const cache = comp.__emits || (comp.__emits = {})
114-
const cached = cache[appId]
115-
if (cached !== undefined) {
116-
return cached
112+
if (!appContext.deopt && comp.__emits !== undefined) {
113+
return comp.__emits
117114
}
118115

119116
const raw = comp.emits
@@ -138,15 +135,15 @@ export function normalizeEmitsOptions(
138135
}
139136

140137
if (!raw && !hasExtends) {
141-
return (cache[appId] = null)
138+
return (comp.__emits = null)
142139
}
143140

144141
if (isArray(raw)) {
145142
raw.forEach(key => (normalized[key] = null))
146143
} else {
147144
extend(normalized, raw)
148145
}
149-
return (cache[appId] = normalized)
146+
return (comp.__emits = normalized)
150147
}
151148

152149
// Check if an incoming prop key is a declared emit event listener.

packages/runtime-core/src/componentProps.ts

+13-19
Original file line numberDiff line numberDiff line change
@@ -328,11 +328,8 @@ export function normalizePropsOptions(
328328
appContext: AppContext,
329329
asMixin = false
330330
): NormalizedPropsOptions {
331-
const appId = appContext.app ? appContext.app._uid : -1
332-
const cache = comp.__props || (comp.__props = {})
333-
const cached = cache[appId]
334-
if (cached) {
335-
return cached
331+
if (!appContext.deopt && comp.__props) {
332+
return comp.__props
336333
}
337334

338335
const raw = comp.props
@@ -360,7 +357,7 @@ export function normalizePropsOptions(
360357
}
361358

362359
if (!raw && !hasExtends) {
363-
return (cache[appId] = EMPTY_ARR)
360+
return (comp.__props = EMPTY_ARR)
364361
}
365362

366363
if (isArray(raw)) {
@@ -398,7 +395,16 @@ export function normalizePropsOptions(
398395
}
399396
}
400397

401-
return (cache[appId] = [normalized, needCastKeys])
398+
return (comp.__props = [normalized, needCastKeys])
399+
}
400+
401+
function validatePropName(key: string) {
402+
if (key[0] !== '$') {
403+
return true
404+
} else if (__DEV__) {
405+
warn(`Invalid prop name: "${key}" is a reserved property.`)
406+
}
407+
return false
402408
}
403409

404410
// use function string name to check type constructors
@@ -441,18 +447,6 @@ function validateProps(props: Data, instance: ComponentInternalInstance) {
441447
}
442448
}
443449

444-
/**
445-
* dev only
446-
*/
447-
function validatePropName(key: string) {
448-
if (key[0] !== '$') {
449-
return true
450-
} else if (__DEV__) {
451-
warn(`Invalid prop name: "${key}" is a reserved property.`)
452-
}
453-
return false
454-
}
455-
456450
/**
457451
* dev only
458452
*/

0 commit comments

Comments
 (0)