]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
fix(slots): differentiate dynamic/static compiled slots
authorEvan You <yyx990803@gmail.com>
Mon, 13 Jul 2020 16:36:41 +0000 (12:36 -0400)
committerEvan You <yyx990803@gmail.com>
Mon, 13 Jul 2020 16:36:41 +0000 (12:36 -0400)
fix #1557

packages/compiler-core/__tests__/__snapshots__/scopeId.spec.ts.snap
packages/compiler-core/__tests__/transforms/__snapshots__/vSlot.spec.ts.snap
packages/compiler-core/__tests__/transforms/vSlot.spec.ts
packages/compiler-core/src/transforms/vSlot.ts
packages/compiler-ssr/__tests__/ssrComponent.spec.ts
packages/runtime-core/src/componentSlots.ts
packages/runtime-core/src/helpers/renderSlot.ts

index ba004bb40641397ceb55bcf748f3fda1c3ce022a..8d7613eec58199eb74e80beb6aeafdf4483b2397 100644 (file)
@@ -41,7 +41,7 @@ const _withId = /*#__PURE__*/_withScopeId(\\"test\\")
 export const render = /*#__PURE__*/_withId(function render(_ctx, _cache) {
   const _component_Child = _resolveComponent(\\"Child\\")
 
-  return (_openBlock(), _createBlock(_component_Child, null, _createSlots({ _: 1 }, [
+  return (_openBlock(), _createBlock(_component_Child, null, _createSlots({ _: 2 }, [
     (_ctx.ok)
       ? {
           name: \\"foo\\",
index 2eb27e83ec58775be9ab6b6a6fa55257da8f3b87..d99dcdfb14248be2c9ef649c78685ebf7ddc4dc0 100644 (file)
@@ -9,7 +9,7 @@ return function render(_ctx, _cache) {
   return (_openBlock(), _createBlock(_component_Comp, null, {
     [_ctx.one]: _withCtx(({ foo }) => [_toDisplayString(foo), _toDisplayString(_ctx.bar)]),
     [_ctx.two]: _withCtx(({ bar }) => [_toDisplayString(_ctx.foo), _toDisplayString(bar)]),
-    _: 1
+    _: 2
   }, 1024 /* DYNAMIC_SLOTS */))
 }"
 `;
@@ -35,7 +35,7 @@ exports[`compiler: transform component slots named slot with v-for w/ prefixIden
 return function render(_ctx, _cache) {
   const _component_Comp = _resolveComponent(\\"Comp\\")
 
-  return (_openBlock(), _createBlock(_component_Comp, null, _createSlots({ _: 1 }, [
+  return (_openBlock(), _createBlock(_component_Comp, null, _createSlots({ _: 2 }, [
     _renderList(_ctx.list, (name) => {
       return {
         name: name,
@@ -52,7 +52,7 @@ exports[`compiler: transform component slots named slot with v-if + prefixIdenti
 return function render(_ctx, _cache) {
   const _component_Comp = _resolveComponent(\\"Comp\\")
 
-  return (_openBlock(), _createBlock(_component_Comp, null, _createSlots({ _: 1 }, [
+  return (_openBlock(), _createBlock(_component_Comp, null, _createSlots({ _: 2 }, [
     (_ctx.ok)
       ? {
           name: \\"one\\",
@@ -72,7 +72,7 @@ return function render(_ctx, _cache) {
 
     const _component_Comp = _resolveComponent(\\"Comp\\")
 
-    return (_openBlock(), _createBlock(_component_Comp, null, _createSlots({ _: 1 }, [
+    return (_openBlock(), _createBlock(_component_Comp, null, _createSlots({ _: 2 }, [
       ok
         ? {
             name: \\"one\\",
@@ -101,7 +101,7 @@ return function render(_ctx, _cache) {
 
     const _component_Comp = _resolveComponent(\\"Comp\\")
 
-    return (_openBlock(), _createBlock(_component_Comp, null, _createSlots({ _: 1 }, [
+    return (_openBlock(), _createBlock(_component_Comp, null, _createSlots({ _: 2 }, [
       ok
         ? {
             name: \\"one\\",
@@ -145,7 +145,7 @@ return function render(_ctx, _cache) {
     default: _withCtx(({ foo }) => [
       _createVNode(_component_Inner, null, {
         default: _withCtx(({ bar }) => [_toDisplayString(foo), _toDisplayString(bar), _toDisplayString(_ctx.baz)]),
-        _: 1
+        _: 2
       }, 1024 /* DYNAMIC_SLOTS */),
       \\" \\",
       _toDisplayString(foo),
@@ -165,8 +165,8 @@ return function render(_ctx, _cache) {
 
   return (_openBlock(), _createBlock(_component_Comp, null, {
     [_ctx.named]: _withCtx(({ foo }) => [_toDisplayString(foo), _toDisplayString(_ctx.bar)]),
-    _: 1
-  }))
+    _: 2
+  }, 1024 /* DYNAMIC_SLOTS */))
 }"
 `;
 
index c1cb651d9c98c4d73a9142051496af748f949475..c9a2e3e7cb98a6496505ce9d4b6d1cf4073e8441 100644 (file)
@@ -53,7 +53,7 @@ function parseWithSlots(template: string, options: CompilerOptions = {}) {
   }
 }
 
-function createSlotMatcher(obj: Record<string, any>) {
+function createSlotMatcher(obj: Record<string, any>, isDynamic = false) {
   return {
     type: NodeTypes.JS_OBJECT_EXPRESSION,
     properties: Object.keys(obj)
@@ -70,7 +70,7 @@ function createSlotMatcher(obj: Record<string, any>) {
       })
       .concat({
         key: { content: `_` },
-        value: { content: `1`, isStatic: false }
+        value: { content: isDynamic ? `2` : `1`, isStatic: false }
       })
   }
 }
@@ -230,29 +230,32 @@ describe('compiler: transform component slots', () => {
       { prefixIdentifiers: true }
     )
     expect(slots).toMatchObject(
-      createSlotMatcher({
-        '[_ctx.named]': {
-          type: NodeTypes.JS_FUNCTION_EXPRESSION,
-          params: {
-            type: NodeTypes.COMPOUND_EXPRESSION,
-            children: [`{ `, { content: `foo` }, ` }`]
-          },
-          returns: [
-            {
-              type: NodeTypes.INTERPOLATION,
-              content: {
-                content: `foo`
-              }
+      createSlotMatcher(
+        {
+          '[_ctx.named]': {
+            type: NodeTypes.JS_FUNCTION_EXPRESSION,
+            params: {
+              type: NodeTypes.COMPOUND_EXPRESSION,
+              children: [`{ `, { content: `foo` }, ` }`]
             },
-            {
-              type: NodeTypes.INTERPOLATION,
-              content: {
-                content: `_ctx.bar`
+            returns: [
+              {
+                type: NodeTypes.INTERPOLATION,
+                content: {
+                  content: `foo`
+                }
+              },
+              {
+                type: NodeTypes.INTERPOLATION,
+                content: {
+                  content: `_ctx.bar`
+                }
               }
-            }
-          ]
-        }
-      })
+            ]
+          }
+        },
+        true
+      )
     )
     expect(generate(root, { prefixIdentifiers: true }).code).toMatchSnapshot()
   })
@@ -307,50 +310,53 @@ describe('compiler: transform component slots', () => {
       { prefixIdentifiers: true }
     )
     expect(slots).toMatchObject(
-      createSlotMatcher({
-        '[_ctx.one]': {
-          type: NodeTypes.JS_FUNCTION_EXPRESSION,
-          params: {
-            type: NodeTypes.COMPOUND_EXPRESSION,
-            children: [`{ `, { content: `foo` }, ` }`]
-          },
-          returns: [
-            {
-              type: NodeTypes.INTERPOLATION,
-              content: {
-                content: `foo`
-              }
+      createSlotMatcher(
+        {
+          '[_ctx.one]': {
+            type: NodeTypes.JS_FUNCTION_EXPRESSION,
+            params: {
+              type: NodeTypes.COMPOUND_EXPRESSION,
+              children: [`{ `, { content: `foo` }, ` }`]
             },
-            {
-              type: NodeTypes.INTERPOLATION,
-              content: {
-                content: `_ctx.bar`
+            returns: [
+              {
+                type: NodeTypes.INTERPOLATION,
+                content: {
+                  content: `foo`
+                }
+              },
+              {
+                type: NodeTypes.INTERPOLATION,
+                content: {
+                  content: `_ctx.bar`
+                }
               }
-            }
-          ]
-        },
-        '[_ctx.two]': {
-          type: NodeTypes.JS_FUNCTION_EXPRESSION,
-          params: {
-            type: NodeTypes.COMPOUND_EXPRESSION,
-            children: [`{ `, { content: `bar` }, ` }`]
+            ]
           },
-          returns: [
-            {
-              type: NodeTypes.INTERPOLATION,
-              content: {
-                content: `_ctx.foo`
-              }
+          '[_ctx.two]': {
+            type: NodeTypes.JS_FUNCTION_EXPRESSION,
+            params: {
+              type: NodeTypes.COMPOUND_EXPRESSION,
+              children: [`{ `, { content: `bar` }, ` }`]
             },
-            {
-              type: NodeTypes.INTERPOLATION,
-              content: {
-                content: `bar`
+            returns: [
+              {
+                type: NodeTypes.INTERPOLATION,
+                content: {
+                  content: `_ctx.foo`
+                }
+              },
+              {
+                type: NodeTypes.INTERPOLATION,
+                content: {
+                  content: `bar`
+                }
               }
-            }
-          ]
-        }
-      })
+            ]
+          }
+        },
+        true
+      )
     )
     expect(generate(root, { prefixIdentifiers: true }).code).toMatchSnapshot()
   })
@@ -382,35 +388,38 @@ describe('compiler: transform component slots', () => {
                 type: NodeTypes.VNODE_CALL,
                 tag: `_component_Inner`,
                 props: undefined,
-                children: createSlotMatcher({
-                  default: {
-                    type: NodeTypes.JS_FUNCTION_EXPRESSION,
-                    params: {
-                      type: NodeTypes.COMPOUND_EXPRESSION,
-                      children: [`{ `, { content: `bar` }, ` }`]
-                    },
-                    returns: [
-                      {
-                        type: NodeTypes.INTERPOLATION,
-                        content: {
-                          content: `foo`
-                        }
-                      },
-                      {
-                        type: NodeTypes.INTERPOLATION,
-                        content: {
-                          content: `bar`
-                        }
+                children: createSlotMatcher(
+                  {
+                    default: {
+                      type: NodeTypes.JS_FUNCTION_EXPRESSION,
+                      params: {
+                        type: NodeTypes.COMPOUND_EXPRESSION,
+                        children: [`{ `, { content: `bar` }, ` }`]
                       },
-                      {
-                        type: NodeTypes.INTERPOLATION,
-                        content: {
-                          content: `_ctx.baz`
+                      returns: [
+                        {
+                          type: NodeTypes.INTERPOLATION,
+                          content: {
+                            content: `foo`
+                          }
+                        },
+                        {
+                          type: NodeTypes.INTERPOLATION,
+                          content: {
+                            content: `bar`
+                          }
+                        },
+                        {
+                          type: NodeTypes.INTERPOLATION,
+                          content: {
+                            content: `_ctx.baz`
+                          }
                         }
-                      }
-                    ]
-                  }
-                }),
+                      ]
+                    }
+                  },
+                  true
+                ),
                 // nested slot should be forced dynamic, since scope variables
                 // are not tracked as dependencies of the slot.
                 patchFlag: genFlagText(PatchFlags.DYNAMIC_SLOTS)
@@ -522,7 +531,7 @@ describe('compiler: transform component slots', () => {
       callee: CREATE_SLOTS,
       arguments: [
         createObjectMatcher({
-          _: `[1]`
+          _: `[2]`
         }),
         {
           type: NodeTypes.JS_ARRAY_EXPRESSION,
@@ -564,7 +573,7 @@ describe('compiler: transform component slots', () => {
       callee: CREATE_SLOTS,
       arguments: [
         createObjectMatcher({
-          _: `[1]`
+          _: `[2]`
         }),
         {
           type: NodeTypes.JS_ARRAY_EXPRESSION,
@@ -613,7 +622,7 @@ describe('compiler: transform component slots', () => {
       callee: CREATE_SLOTS,
       arguments: [
         createObjectMatcher({
-          _: `[1]`
+          _: `[2]`
         }),
         {
           type: NodeTypes.JS_ARRAY_EXPRESSION,
@@ -672,7 +681,7 @@ describe('compiler: transform component slots', () => {
       callee: CREATE_SLOTS,
       arguments: [
         createObjectMatcher({
-          _: `[1]`
+          _: `[2]`
         }),
         {
           type: NodeTypes.JS_ARRAY_EXPRESSION,
index 548e90fd521ec88138c7976bd3ac4e5493aa774f..5930bcedf4774ae857db5621433aabe4b6f63f0d 100644 (file)
@@ -135,7 +135,7 @@ export function buildSlots(
   let hasDynamicSlots = context.scopes.vSlot > 0 || context.scopes.vFor > 0
   // with `prefixIdentifiers: true`, this can be further optimized to make
   // it dynamic only when the slot actually uses the scope variables.
-  if (!__BROWSER__ && context.prefixIdentifiers) {
+  if (!__BROWSER__ && !context.ssr && context.prefixIdentifiers) {
     hasDynamicSlots = hasScopeRef(node, context.identifiers)
   }
 
@@ -144,6 +144,9 @@ export function buildSlots(
   const onComponentSlot = findDir(node, 'slot', true)
   if (onComponentSlot) {
     const { arg, exp } = onComponentSlot
+    if (arg && !isStaticExp(arg)) {
+      hasDynamicSlots = true
+    }
     slotsProperties.push(
       createObjectProperty(
         arg || createSimpleExpression('default', true),
@@ -317,7 +320,12 @@ export function buildSlots(
 
   let slots = createObjectExpression(
     slotsProperties.concat(
-      createObjectProperty(`_`, createSimpleExpression(`1`, false))
+      createObjectProperty(
+        `_`,
+        // 2 = compiled but dynamic = can skip normalization, but must run diff
+        // 1 = compiled and static = can skip normalization AND diff as optimized
+        createSimpleExpression(hasDynamicSlots ? `2` : `1`, false)
+      )
     ),
     loc
   ) as SlotsExpression
index 860462a4004f1bdb397c10e96ce9fd8d494a2df6..d175d2dfc116de0bc13d3ffd68f0e35f58d619b3 100644 (file)
@@ -140,7 +140,7 @@ describe('ssr: components', () => {
         return function ssrRender(_ctx, _push, _parent, _attrs) {
           const _component_foo = _resolveComponent(\\"foo\\")
 
-          _push(_ssrRenderComponent(_component_foo, _attrs, _createSlots({ _: 1 }, [
+          _push(_ssrRenderComponent(_component_foo, _attrs, _createSlots({ _: 2 }, [
             (_ctx.ok)
               ? {
                   name: \\"named\\",
@@ -172,7 +172,7 @@ describe('ssr: components', () => {
         return function ssrRender(_ctx, _push, _parent, _attrs) {
           const _component_foo = _resolveComponent(\\"foo\\")
 
-          _push(_ssrRenderComponent(_component_foo, _attrs, _createSlots({ _: 1 }, [
+          _push(_ssrRenderComponent(_component_foo, _attrs, _createSlots({ _: 2 }, [
             _renderList(_ctx.names, (key) => {
               return {
                 name: key,
index cdee341ba66db9fc61bcac098a0e1fee66609b78..b0a52b0f50564b8c9ff26823fc5e2ee14b89b4d4 100644 (file)
@@ -11,7 +11,6 @@ import {
   isFunction,
   EMPTY_OBJ,
   ShapeFlags,
-  PatchFlags,
   extend,
   def
 } from '@vue/shared'
@@ -28,6 +27,11 @@ export type InternalSlots = {
 
 export type Slots = Readonly<InternalSlots>
 
+export const enum CompiledSlotTypes {
+  STATIC = 1,
+  DYNAMIC = 2
+}
+
 export type RawSlots = {
   [name: string]: unknown
   // manual render fn hint to skip forced children updates
@@ -40,7 +44,7 @@ export type RawSlots = {
   // object may be directly passed down to a child component in a manual
   // render function, and the optimization hint need to be on the slot object
   // itself to be preserved.
-  _?: 1
+  _?: CompiledSlotTypes
 }
 
 const isInternalKey = (key: string) => key[0] === '_' || key === '$stable'
@@ -105,10 +109,11 @@ export const initSlots = (
   children: VNodeNormalizedChildren
 ) => {
   if (instance.vnode.shapeFlag & ShapeFlags.SLOTS_CHILDREN) {
-    if ((children as RawSlots)._ === 1) {
+    const type = (children as RawSlots)._
+    if (type) {
       instance.slots = children as InternalSlots
       // make compiler marker non-enumerable
-      def(children as InternalSlots, '_', 1)
+      def(children as InternalSlots, '_', type)
     } else {
       normalizeObjectSlots(children as RawSlots, (instance.slots = {}))
     }
@@ -129,21 +134,20 @@ export const updateSlots = (
   let needDeletionCheck = true
   let deletionComparisonTarget = EMPTY_OBJ
   if (vnode.shapeFlag & ShapeFlags.SLOTS_CHILDREN) {
-    if ((children as RawSlots)._ === 1) {
+    const type = (children as RawSlots)._
+    if (type) {
       // compiled slots.
       if (__DEV__ && isHmrUpdating) {
         // Parent was HMR updated so slot content may have changed.
         // force update slots and mark instance for hmr as well
         extend(slots, children as Slots)
-      } else if (
-        // bail on dynamic slots (v-if, v-for, reference of scope variables)
-        !(vnode.patchFlag & PatchFlags.DYNAMIC_SLOTS)
-      ) {
+      } else if (type === CompiledSlotTypes.STATIC) {
         // compiled AND static.
         // no need to update, and skip stale slots removal.
         needDeletionCheck = false
       } else {
-        // compiled but dynamic - update slots, but skip normalization.
+        // compiled but dynamic (v-if/v-for on slots) - update slots, but skip
+        // normalization.
         extend(slots, children as Slots)
       }
     } else {
index 75d8c5111952f4792d6c92d35284ced9369e0257..0cfb6ed694fbc242717dcd154a282a82d3e3eb4d 100644 (file)
@@ -1,5 +1,5 @@
 import { Data } from '../component'
-import { Slots } from '../componentSlots'
+import { Slots, RawSlots, CompiledSlotTypes } from '../componentSlots'
 import {
   VNodeArrayChildren,
   openBlock,
@@ -39,7 +39,9 @@ export function renderSlot(
       Fragment,
       { key: props.key },
       slot ? slot(props) : fallback ? fallback() : [],
-      slots._ ? PatchFlags.STABLE_FRAGMENT : PatchFlags.BAIL
+      (slots as RawSlots)._ === CompiledSlotTypes.STATIC
+        ? PatchFlags.STABLE_FRAGMENT
+        : PatchFlags.BAIL
     )
   )
 }