]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
fix(runtime-core): fix SSR memoery leak due to props normalization cache
authorEvan You <yyx990803@gmail.com>
Tue, 6 Oct 2020 19:31:29 +0000 (15:31 -0400)
committerEvan You <yyx990803@gmail.com>
Tue, 6 Oct 2020 19:31:29 +0000 (15:31 -0400)
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
packages/runtime-core/src/component.ts
packages/runtime-core/src/componentEmits.ts
packages/runtime-core/src/componentProps.ts

index 2397cdf337980f48cbd2dc723cba43e3d190ed23..bcb2fec1c32a352901372175266618b86b92edc5 100644 (file)
@@ -74,7 +74,16 @@ export interface AppContext {
   components: Record<string, Component>
   directives: Record<string, Directive>
   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
@@ -169,6 +178,11 @@ export function createAppAPI<HostElement>(
         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' +
index 78abad60a8afafef65340d02208d4a900081640c..19035961c96bb46f18e30580a226edd9ecd96c70 100644 (file)
@@ -79,11 +79,11 @@ export interface ComponentInternalOptions {
   /**
    * @internal
    */
-  __props?: Record<number, NormalizedPropsOptions>
+  __props?: NormalizedPropsOptions
   /**
    * @internal
    */
-  __emits?: Record<number, ObjectEmitsOptions | null>
+  __emits?: ObjectEmitsOptions | null
   /**
    * @internal
    */
index daf82b71e301a2702141e71cb025bbda0153f0db..ac37bc568d8aa8f147a13f5e672abb69d64396ad 100644 (file)
@@ -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.
index f977099d8063dab6d9ea2952dcc868d00a7173c1..9fc2ac78b88ade10b5603770a194468c2d61be34 100644 (file)
@@ -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
  */