]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
fix(compiler-core): handle template ref bound via v-bind object on v-for (#10706)
authorVadim Kruglov <49036220+quiteeasy@users.noreply.github.com>
Mon, 22 Apr 2024 12:46:11 +0000 (19:46 +0700)
committerGitHub <noreply@github.com>
Mon, 22 Apr 2024 12:46:11 +0000 (20:46 +0800)
close #10696

packages/compiler-core/__tests__/transforms/__snapshots__/transformElement.spec.ts.snap [new file with mode: 0644]
packages/compiler-core/__tests__/transforms/transformElement.spec.ts
packages/compiler-core/__tests__/transforms/vFor.spec.ts
packages/compiler-core/src/transforms/transformElement.ts

diff --git a/packages/compiler-core/__tests__/transforms/__snapshots__/transformElement.spec.ts.snap b/packages/compiler-core/__tests__/transforms/__snapshots__/transformElement.spec.ts.snap
new file mode 100644 (file)
index 0000000..3da778e
--- /dev/null
@@ -0,0 +1,228 @@
+// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
+
+exports[`compiler: v-for > codegen > basic v-for 1`] = `
+"const _Vue = Vue
+
+return function render(_ctx, _cache) {
+  with (_ctx) {
+    const { renderList: _renderList, Fragment: _Fragment, openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vue
+
+    return (_openBlock(true), _createElementBlock(_Fragment, null, _renderList(items, (item) => {
+      return (_openBlock(), _createElementBlock("span"))
+    }), 256 /* UNKEYED_FRAGMENT */))
+  }
+}"
+`;
+
+exports[`compiler: v-for > codegen > keyed template v-for 1`] = `
+"const _Vue = Vue
+
+return function render(_ctx, _cache) {
+  with (_ctx) {
+    const { renderList: _renderList, Fragment: _Fragment, openBlock: _openBlock, createElementBlock: _createElementBlock, createElementVNode: _createElementVNode } = _Vue
+
+    return (_openBlock(true), _createElementBlock(_Fragment, null, _renderList(items, (item) => {
+      return (_openBlock(), _createElementBlock(_Fragment, { key: item }, [
+        "hello",
+        _createElementVNode("span")
+      ], 64 /* STABLE_FRAGMENT */))
+    }), 128 /* KEYED_FRAGMENT */))
+  }
+}"
+`;
+
+exports[`compiler: v-for > codegen > keyed v-for 1`] = `
+"const _Vue = Vue
+
+return function render(_ctx, _cache) {
+  with (_ctx) {
+    const { renderList: _renderList, Fragment: _Fragment, openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vue
+
+    return (_openBlock(true), _createElementBlock(_Fragment, null, _renderList(items, (item) => {
+      return (_openBlock(), _createElementBlock("span", { key: item }))
+    }), 128 /* KEYED_FRAGMENT */))
+  }
+}"
+`;
+
+exports[`compiler: v-for > codegen > skipped key 1`] = `
+"const _Vue = Vue
+
+return function render(_ctx, _cache) {
+  with (_ctx) {
+    const { renderList: _renderList, Fragment: _Fragment, openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vue
+
+    return (_openBlock(true), _createElementBlock(_Fragment, null, _renderList(items, (item, __, index) => {
+      return (_openBlock(), _createElementBlock("span"))
+    }), 256 /* UNKEYED_FRAGMENT */))
+  }
+}"
+`;
+
+exports[`compiler: v-for > codegen > skipped value & key 1`] = `
+"const _Vue = Vue
+
+return function render(_ctx, _cache) {
+  with (_ctx) {
+    const { renderList: _renderList, Fragment: _Fragment, openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vue
+
+    return (_openBlock(true), _createElementBlock(_Fragment, null, _renderList(items, (_, __, index) => {
+      return (_openBlock(), _createElementBlock("span"))
+    }), 256 /* UNKEYED_FRAGMENT */))
+  }
+}"
+`;
+
+exports[`compiler: v-for > codegen > skipped value 1`] = `
+"const _Vue = Vue
+
+return function render(_ctx, _cache) {
+  with (_ctx) {
+    const { renderList: _renderList, Fragment: _Fragment, openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vue
+
+    return (_openBlock(true), _createElementBlock(_Fragment, null, _renderList(items, (_, key, index) => {
+      return (_openBlock(), _createElementBlock("span"))
+    }), 256 /* UNKEYED_FRAGMENT */))
+  }
+}"
+`;
+
+exports[`compiler: v-for > codegen > template v-for 1`] = `
+"const _Vue = Vue
+
+return function render(_ctx, _cache) {
+  with (_ctx) {
+    const { renderList: _renderList, Fragment: _Fragment, openBlock: _openBlock, createElementBlock: _createElementBlock, createElementVNode: _createElementVNode } = _Vue
+
+    return (_openBlock(true), _createElementBlock(_Fragment, null, _renderList(items, (item) => {
+      return (_openBlock(), _createElementBlock(_Fragment, null, [
+        "hello",
+        _createElementVNode("span")
+      ], 64 /* STABLE_FRAGMENT */))
+    }), 256 /* UNKEYED_FRAGMENT */))
+  }
+}"
+`;
+
+exports[`compiler: v-for > codegen > template v-for key injection with single child 1`] = `
+"const _Vue = Vue
+
+return function render(_ctx, _cache) {
+  with (_ctx) {
+    const { renderList: _renderList, Fragment: _Fragment, openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vue
+
+    return (_openBlock(true), _createElementBlock(_Fragment, null, _renderList(items, (item) => {
+      return (_openBlock(), _createElementBlock("span", {
+        key: item.id,
+        id: item.id
+      }, null, 8 /* PROPS */, ["id"]))
+    }), 128 /* KEYED_FRAGMENT */))
+  }
+}"
+`;
+
+exports[`compiler: v-for > codegen > template v-for w/ <slot/> 1`] = `
+"const _Vue = Vue
+
+return function render(_ctx, _cache) {
+  with (_ctx) {
+    const { renderList: _renderList, Fragment: _Fragment, openBlock: _openBlock, createElementBlock: _createElementBlock, renderSlot: _renderSlot } = _Vue
+
+    return (_openBlock(true), _createElementBlock(_Fragment, null, _renderList(items, (item) => {
+      return _renderSlot($slots, "default")
+    }), 256 /* UNKEYED_FRAGMENT */))
+  }
+}"
+`;
+
+exports[`compiler: v-for > codegen > v-for on <slot/> 1`] = `
+"const _Vue = Vue
+
+return function render(_ctx, _cache) {
+  with (_ctx) {
+    const { renderList: _renderList, Fragment: _Fragment, openBlock: _openBlock, createElementBlock: _createElementBlock, renderSlot: _renderSlot } = _Vue
+
+    return (_openBlock(true), _createElementBlock(_Fragment, null, _renderList(items, (item) => {
+      return _renderSlot($slots, "default")
+    }), 256 /* UNKEYED_FRAGMENT */))
+  }
+}"
+`;
+
+exports[`compiler: v-for > codegen > v-for on element with custom directive 1`] = `
+"const _Vue = Vue
+
+return function render(_ctx, _cache) {
+  with (_ctx) {
+    const { renderList: _renderList, Fragment: _Fragment, openBlock: _openBlock, createElementBlock: _createElementBlock, resolveDirective: _resolveDirective, withDirectives: _withDirectives } = _Vue
+
+    const _directive_foo = _resolveDirective("foo")
+
+    return (_openBlock(true), _createElementBlock(_Fragment, null, _renderList(list, (i) => {
+      return _withDirectives((_openBlock(), _createElementBlock("div", null, null, 512 /* NEED_PATCH */)), [
+        [_directive_foo]
+      ])
+    }), 256 /* UNKEYED_FRAGMENT */))
+  }
+}"
+`;
+
+exports[`compiler: v-for > codegen > v-for with constant expression 1`] = `
+"const _Vue = Vue
+
+return function render(_ctx, _cache) {
+  with (_ctx) {
+    const { renderList: _renderList, Fragment: _Fragment, openBlock: _openBlock, createElementBlock: _createElementBlock, toDisplayString: _toDisplayString, createElementVNode: _createElementVNode } = _Vue
+
+    return (_openBlock(), _createElementBlock(_Fragment, null, _renderList(10, (item) => {
+      return _createElementVNode("p", null, _toDisplayString(item), 1 /* TEXT */)
+    }), 64 /* STABLE_FRAGMENT */))
+  }
+}"
+`;
+
+exports[`compiler: v-for > codegen > v-if + v-for 1`] = `
+"const _Vue = Vue
+
+return function render(_ctx, _cache) {
+  with (_ctx) {
+    const { renderList: _renderList, Fragment: _Fragment, openBlock: _openBlock, createElementBlock: _createElementBlock, createCommentVNode: _createCommentVNode } = _Vue
+
+    return ok
+      ? (_openBlock(true), _createElementBlock(_Fragment, { key: 0 }, _renderList(list, (i) => {
+          return (_openBlock(), _createElementBlock("div"))
+        }), 256 /* UNKEYED_FRAGMENT */))
+      : _createCommentVNode("v-if", true)
+  }
+}"
+`;
+
+exports[`compiler: v-for > codegen > v-if + v-for on <template> 1`] = `
+"const _Vue = Vue
+
+return function render(_ctx, _cache) {
+  with (_ctx) {
+    const { renderList: _renderList, Fragment: _Fragment, openBlock: _openBlock, createElementBlock: _createElementBlock, createCommentVNode: _createCommentVNode } = _Vue
+
+    return ok
+      ? (_openBlock(true), _createElementBlock(_Fragment, { key: 0 }, _renderList(list, (i) => {
+          return (_openBlock(), _createElementBlock(_Fragment, null, [], 64 /* STABLE_FRAGMENT */))
+        }), 256 /* UNKEYED_FRAGMENT */))
+      : _createCommentVNode("v-if", true)
+  }
+}"
+`;
+
+exports[`compiler: v-for > codegen > value + key + index 1`] = `
+"const _Vue = Vue
+
+return function render(_ctx, _cache) {
+  with (_ctx) {
+    const { renderList: _renderList, Fragment: _Fragment, openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vue
+
+    return (_openBlock(true), _createElementBlock(_Fragment, null, _renderList(items, (item, key, index) => {
+      return (_openBlock(), _createElementBlock("span"))
+    }), 256 /* UNKEYED_FRAGMENT */))
+  }
+}"
+`;
index c30840a21a648b7b4e4ff774834d6fc186fae892..6c2dab9625fe27d9c62caef30efcf5af318cd986 100644 (file)
@@ -39,6 +39,7 @@ import { transformBind } from '../../src/transforms/vBind'
 import { PatchFlags } from '@vue/shared'
 import { createObjectMatcher, genFlagText } from '../testUtils'
 import { transformText } from '../../src/transforms/transformText'
+import { parseWithForTransform } from './vFor.spec'
 
 function parseWithElementTransform(
   template: string,
@@ -1338,4 +1339,42 @@ describe('compiler: element transform', () => {
       isBlock: false,
     })
   })
+
+  test('ref_for marker on static ref', () => {
+    const { node } = parseWithForTransform(`<div v-for="i in l" ref="x"/>`)
+    expect((node.children[0] as any).codegenNode.props).toMatchObject(
+      createObjectMatcher({
+        ref_for: `[true]`,
+        ref: 'x',
+      }),
+    )
+  })
+
+  test('ref_for marker on dynamic ref', () => {
+    const { node } = parseWithForTransform(`<div v-for="i in l" :ref="x"/>`)
+    expect((node.children[0] as any).codegenNode.props).toMatchObject(
+      createObjectMatcher({
+        ref_for: `[true]`,
+        ref: '[x]',
+      }),
+    )
+  })
+
+  test('ref_for marker on v-bind', () => {
+    const { node } = parseWithForTransform(`<div v-for="i in l" v-bind="x" />`)
+    expect((node.children[0] as any).codegenNode.props).toMatchObject({
+      type: NodeTypes.JS_CALL_EXPRESSION,
+      callee: MERGE_PROPS,
+      arguments: [
+        createObjectMatcher({
+          ref_for: `[true]`,
+        }),
+        {
+          type: NodeTypes.SIMPLE_EXPRESSION,
+          content: 'x',
+          isStatic: false,
+        },
+      ],
+    })
+  })
 })
index fb3d7dc7fb89b467abed3fa67e0eae6f938ead8b..7fabcbb579cdd231078fdf2ecc29b8d09b46d1c3 100644 (file)
@@ -21,7 +21,7 @@ import { FRAGMENT, RENDER_LIST, RENDER_SLOT } from '../../src/runtimeHelpers'
 import { PatchFlags } from '@vue/shared'
 import { createObjectMatcher, genFlagText } from '../testUtils'
 
-function parseWithForTransform(
+export function parseWithForTransform(
   template: string,
   options: CompilerOptions = {},
 ) {
index ca6e59df32bc4debcc7e28efa1785b2ca95c37b8..5c431f9d4dacb94958dd35308e92a9a2ba4cd663 100644 (file)
@@ -433,6 +433,18 @@ export function buildProps(
     if (arg) mergeArgs.push(arg)
   }
 
+  // mark template ref on v-for
+  const pushRefVForMarker = () => {
+    if (context.scopes.vFor > 0) {
+      properties.push(
+        createObjectProperty(
+          createSimpleExpression('ref_for', true),
+          createSimpleExpression('true'),
+        ),
+      )
+    }
+  }
+
   const analyzePatchFlag = ({ key, value }: Property) => {
     if (isStaticExp(key)) {
       const name = key.content
@@ -502,14 +514,7 @@ export function buildProps(
       let isStatic = true
       if (name === 'ref') {
         hasRef = true
-        if (context.scopes.vFor > 0) {
-          properties.push(
-            createObjectProperty(
-              createSimpleExpression('ref_for', true),
-              createSimpleExpression('true'),
-            ),
-          )
-        }
+        pushRefVForMarker()
         // in inline mode there is no setupState object, so we can't use string
         // keys to set the ref. Instead, we need to transform it to pass the
         // actual ref instead.
@@ -601,13 +606,8 @@ export function buildProps(
         shouldUseBlock = true
       }
 
-      if (isVBind && isStaticArgOf(arg, 'ref') && context.scopes.vFor > 0) {
-        properties.push(
-          createObjectProperty(
-            createSimpleExpression('ref_for', true),
-            createSimpleExpression('true'),
-          ),
-        )
+      if (isVBind && isStaticArgOf(arg, 'ref')) {
+        pushRefVForMarker()
       }
 
       // special case for v-bind and v-on with no argument
@@ -615,6 +615,8 @@ export function buildProps(
         hasDynamicKeys = true
         if (exp) {
           if (isVBind) {
+            // #10696 in case a v-bind object contains ref
+            pushRefVForMarker()
             // have to merge early for compat build check
             pushMergeArg()
             if (__COMPAT__) {