Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(compiler-core): Refs inside v-for are not applied through v-bind (fix #10696) #10706

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -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 */))
}
}"
`;
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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,
},
],
})
})
})
2 changes: 1 addition & 1 deletion packages/compiler-core/__tests__/transforms/vFor.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 = {},
) {
Expand Down
32 changes: 17 additions & 15 deletions packages/compiler-core/src/transforms/transformElement.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -601,20 +606,17 @@ 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
if (!arg && (isVBind || isVOn)) {
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__) {
Expand Down