diff --git a/packages/compiler-core/__tests__/__snapshots__/compile.spec.ts.snap b/packages/compiler-core/__tests__/__snapshots__/compile.spec.ts.snap
index efbeb1be1..4748ed19d 100644
--- a/packages/compiler-core/__tests__/__snapshots__/compile.spec.ts.snap
+++ b/packages/compiler-core/__tests__/__snapshots__/compile.spec.ts.snap
@@ -15,11 +15,11 @@ return function render() {
(_openBlock(), ok
? _createBlock(\\"div\\", { key: 0 }, \\"yes\\")
: _createBlock(_Fragment, { key: 1 }, \\"no\\")),
- (_openBlock(), _createBlock(_Fragment, null, _renderList(list, (value, index) => {
- return _createVNode(\\"div\\", null, [
+ _createVNode(_Fragment, null, _renderList(list, (value, index) => {
+ return (_openBlock(), _createBlock(\\"div\\", null, [
_createVNode(\\"span\\", null, _toString(value + index))
- ])
- })))
+ ]))
+ }), 128 /* UNKEYED_FRAGMENT */)
], 2 /* CLASS */)
}
}"
@@ -38,11 +38,11 @@ return function render() {
(openBlock(), (_ctx.ok)
? createBlock(\\"div\\", { key: 0 }, \\"yes\\")
: createBlock(Fragment, { key: 1 }, \\"no\\")),
- (openBlock(), createBlock(Fragment, null, renderList(_ctx.list, (value, index) => {
- return createVNode(\\"div\\", null, [
+ createVNode(Fragment, null, renderList(_ctx.list, (value, index) => {
+ return (openBlock(), createBlock(\\"div\\", null, [
createVNode(\\"span\\", null, toString(value + index))
- ])
- })))
+ ]))
+ }), 128 /* UNKEYED_FRAGMENT */)
], 2 /* CLASS */)
}"
`;
@@ -60,11 +60,11 @@ export default function render() {
(openBlock(), (_ctx.ok)
? createBlock(\\"div\\", { key: 0 }, \\"yes\\")
: createBlock(Fragment, { key: 1 }, \\"no\\")),
- (openBlock(), createBlock(Fragment, null, renderList(_ctx.list, (value, index) => {
- return createVNode(\\"div\\", null, [
+ createVNode(Fragment, null, renderList(_ctx.list, (value, index) => {
+ return (openBlock(), createBlock(\\"div\\", null, [
createVNode(\\"span\\", null, _toString(value + index))
- ])
- })))
+ ]))
+ }), 128 /* UNKEYED_FRAGMENT */)
], 2 /* CLASS */)
}"
`;
diff --git a/packages/compiler-core/__tests__/transforms/__snapshots__/vFor.spec.ts.snap b/packages/compiler-core/__tests__/transforms/__snapshots__/vFor.spec.ts.snap
index d9ce98eb3..352f0f62b 100644
--- a/packages/compiler-core/__tests__/transforms/__snapshots__/vFor.spec.ts.snap
+++ b/packages/compiler-core/__tests__/transforms/__snapshots__/vFor.spec.ts.snap
@@ -5,11 +5,42 @@ exports[`compiler: v-for codegen basic v-for 1`] = `
return function render() {
with (this) {
- const { openBlock: _openBlock, createVNode: _createVNode, createBlock: _createBlock, Fragment: _Fragment, renderList: _renderList } = _Vue
+ const { renderList: _renderList, createVNode: _createVNode, Fragment: _Fragment, createBlock: _createBlock, openBlock: _openBlock } = _Vue
- return (_openBlock(), _createBlock(_Fragment, null, _renderList(items, (item) => {
- return _createVNode(\\"span\\")
- })))
+ return _createVNode(_Fragment, null, _renderList(items, (item) => {
+ return (_openBlock(), _createBlock(\\"span\\"))
+ }), 128 /* UNKEYED_FRAGMENT */)
+ }
+}"
+`;
+
+exports[`compiler: v-for codegen keyed template v-for 1`] = `
+"const _Vue = Vue
+
+return function render() {
+ with (this) {
+ const { renderList: _renderList, createVNode: _createVNode, Fragment: _Fragment, openBlock: _openBlock, createBlock: _createBlock } = _Vue
+
+ return _createVNode(_Fragment, null, _renderList(items, (item) => {
+ return (_openBlock(), _createBlock(_Fragment, { key: item }, [
+ \\"hello\\",
+ _createVNode(\\"span\\")
+ ]))
+ }), 64 /* KEYED_FRAGMENT */)
+ }
+}"
+`;
+
+exports[`compiler: v-for codegen keyed v-for 1`] = `
+"const _Vue = Vue
+
+return function render() {
+ with (this) {
+ const { renderList: _renderList, createVNode: _createVNode, Fragment: _Fragment, createBlock: _createBlock, openBlock: _openBlock } = _Vue
+
+ return _createVNode(_Fragment, null, _renderList(items, (item) => {
+ return (_openBlock(), _createBlock(\\"span\\", { key: item }))
+ }), 64 /* KEYED_FRAGMENT */)
}
}"
`;
@@ -19,11 +50,11 @@ exports[`compiler: v-for codegen skipped key 1`] = `
return function render() {
with (this) {
- const { openBlock: _openBlock, createVNode: _createVNode, createBlock: _createBlock, Fragment: _Fragment, renderList: _renderList } = _Vue
+ const { renderList: _renderList, createVNode: _createVNode, Fragment: _Fragment, createBlock: _createBlock, openBlock: _openBlock } = _Vue
- return (_openBlock(), _createBlock(_Fragment, null, _renderList(items, (value, __, index) => {
- return _createVNode(\\"span\\")
- })))
+ return _createVNode(_Fragment, null, _renderList(items, (item, __, index) => {
+ return (_openBlock(), _createBlock(\\"span\\"))
+ }), 128 /* UNKEYED_FRAGMENT */)
}
}"
`;
@@ -33,11 +64,11 @@ exports[`compiler: v-for codegen skipped value & key 1`] = `
return function render() {
with (this) {
- const { openBlock: _openBlock, createVNode: _createVNode, createBlock: _createBlock, Fragment: _Fragment, renderList: _renderList } = _Vue
+ const { renderList: _renderList, createVNode: _createVNode, Fragment: _Fragment, createBlock: _createBlock, openBlock: _openBlock } = _Vue
- return (_openBlock(), _createBlock(_Fragment, null, _renderList(items, (_, __, index) => {
- return _createVNode(\\"span\\")
- })))
+ return _createVNode(_Fragment, null, _renderList(items, (_, __, index) => {
+ return (_openBlock(), _createBlock(\\"span\\"))
+ }), 128 /* UNKEYED_FRAGMENT */)
}
}"
`;
@@ -47,11 +78,11 @@ exports[`compiler: v-for codegen skipped value 1`] = `
return function render() {
with (this) {
- const { openBlock: _openBlock, createVNode: _createVNode, createBlock: _createBlock, Fragment: _Fragment, renderList: _renderList } = _Vue
+ const { renderList: _renderList, createVNode: _createVNode, Fragment: _Fragment, createBlock: _createBlock, openBlock: _openBlock } = _Vue
- return (_openBlock(), _createBlock(_Fragment, null, _renderList(items, (_, key, index) => {
- return _createVNode(\\"span\\")
- })))
+ return _createVNode(_Fragment, null, _renderList(items, (_, key, index) => {
+ return (_openBlock(), _createBlock(\\"span\\"))
+ }), 128 /* UNKEYED_FRAGMENT */)
}
}"
`;
@@ -61,14 +92,14 @@ exports[`compiler: v-for codegen template v-for 1`] = `
return function render() {
with (this) {
- const { openBlock: _openBlock, createVNode: _createVNode, createBlock: _createBlock, Fragment: _Fragment, renderList: _renderList } = _Vue
+ const { renderList: _renderList, createVNode: _createVNode, Fragment: _Fragment, openBlock: _openBlock, createBlock: _createBlock } = _Vue
- return (_openBlock(), _createBlock(_Fragment, null, _renderList(items, (item) => {
- return [
+ return _createVNode(_Fragment, null, _renderList(items, (item) => {
+ return (_openBlock(), _createBlock(_Fragment, null, [
\\"hello\\",
_createVNode(\\"span\\")
- ]
- })))
+ ]))
+ }), 128 /* UNKEYED_FRAGMENT */)
}
}"
`;
@@ -78,12 +109,12 @@ exports[`compiler: v-for codegen v-if + v-for 1`] = `
return function render() {
with (this) {
- const { openBlock: _openBlock, createVNode: _createVNode, createBlock: _createBlock, Fragment: _Fragment, renderList: _renderList, Empty: _Empty } = _Vue
+ const { openBlock: _openBlock, renderList: _renderList, createVNode: _createVNode, Fragment: _Fragment, createBlock: _createBlock, Empty: _Empty } = _Vue
return (_openBlock(), ok
? _createBlock(_Fragment, { key: 0 }, _renderList(list, (i) => {
- return _createVNode(\\"div\\")
- }))
+ return (_openBlock(), _createBlock(\\"div\\"))
+ }), 128 /* UNKEYED_FRAGMENT */)
: _createBlock(_Empty))
}
}"
@@ -94,11 +125,11 @@ exports[`compiler: v-for codegen value + key + index 1`] = `
return function render() {
with (this) {
- const { openBlock: _openBlock, createVNode: _createVNode, createBlock: _createBlock, Fragment: _Fragment, renderList: _renderList } = _Vue
+ const { renderList: _renderList, createVNode: _createVNode, Fragment: _Fragment, createBlock: _createBlock, openBlock: _openBlock } = _Vue
- return (_openBlock(), _createBlock(_Fragment, null, _renderList(items, (item, key, index) => {
- return _createVNode(\\"span\\")
- })))
+ return _createVNode(_Fragment, null, _renderList(items, (item, key, index) => {
+ return (_openBlock(), _createBlock(\\"span\\"))
+ }), 128 /* UNKEYED_FRAGMENT */)
}
}"
`;
diff --git a/packages/compiler-core/__tests__/transforms/__snapshots__/vIf.spec.ts.snap b/packages/compiler-core/__tests__/transforms/__snapshots__/vIf.spec.ts.snap
index 6509aa12c..bbd8a8d30 100644
--- a/packages/compiler-core/__tests__/transforms/__snapshots__/vIf.spec.ts.snap
+++ b/packages/compiler-core/__tests__/transforms/__snapshots__/vIf.spec.ts.snap
@@ -19,7 +19,7 @@ exports[`compiler: v-if codegen template v-if 1`] = `
return function render() {
with (this) {
- const { openBlock: _openBlock, createVNode: _createVNode, createBlock: _createBlock, Fragment: _Fragment, Empty: _Empty } = _Vue
+ const { openBlock: _openBlock, createVNode: _createVNode, Fragment: _Fragment, createBlock: _createBlock, Empty: _Empty } = _Vue
return (_openBlock(), ok
? _createBlock(_Fragment, { key: 0 }, [
diff --git a/packages/compiler-core/__tests__/transforms/vFor.spec.ts b/packages/compiler-core/__tests__/transforms/vFor.spec.ts
index 0fa36d5bc..389560959 100644
--- a/packages/compiler-core/__tests__/transforms/vFor.spec.ts
+++ b/packages/compiler-core/__tests__/transforms/vFor.spec.ts
@@ -2,25 +2,29 @@ import { parse } from '../../src/parse'
import { transform } from '../../src/transform'
import { transformIf } from '../../src/transforms/vIf'
import { transformFor } from '../../src/transforms/vFor'
+import { transformBind } from '../../src/transforms/vBind'
import { transformElement } from '../../src/transforms/transformElement'
+import { transformExpression } from '../../src/transforms/transformExpression'
import {
ForNode,
NodeTypes,
SimpleExpressionNode,
ElementNode,
InterpolationNode,
- SequenceExpression,
CallExpression
} from '../../src/ast'
import { ErrorCodes } from '../../src/errors'
import { CompilerOptions, generate } from '../../src'
-import { transformExpression } from '../../src/transforms/transformExpression'
import {
OPEN_BLOCK,
CREATE_BLOCK,
FRAGMENT,
- RENDER_LIST
+ RENDER_LIST,
+ CREATE_VNODE
} from '../../src/runtimeConstants'
+import { PatchFlags } from '@vue/runtime-dom'
+import { PatchFlagNames } from '@vue/shared'
+import { createObjectMatcher } from '../testUtils'
function parseWithForTransform(
template: string,
@@ -34,6 +38,9 @@ function parseWithForTransform(
...(options.prefixIdentifiers ? [transformExpression] : []),
transformElement
],
+ directiveTransforms: {
+ bind: transformBind
+ },
...options
})
return {
@@ -555,31 +562,51 @@ describe('compiler: v-for', () => {
})
describe('codegen', () => {
- function assertSharedCodegen(node: SequenceExpression) {
+ function assertSharedCodegen(node: CallExpression, keyed: boolean = false) {
expect(node).toMatchObject({
- type: NodeTypes.JS_SEQUENCE_EXPRESSION,
- expressions: [
+ type: NodeTypes.JS_CALL_EXPRESSION,
+ callee: `_${CREATE_VNODE}`,
+ arguments: [
+ `_${FRAGMENT}`,
+ `null`,
{
type: NodeTypes.JS_CALL_EXPRESSION,
- callee: `_${OPEN_BLOCK}`,
- arguments: []
- },
- {
- type: NodeTypes.JS_CALL_EXPRESSION,
- callee: `_${CREATE_BLOCK}`,
+ callee: `_${RENDER_LIST}`,
arguments: [
- `_${FRAGMENT}`,
- `null`,
+ {}, // to be asserted by each test
{
- type: NodeTypes.JS_CALL_EXPRESSION,
- callee: `_${RENDER_LIST}`
+ type: NodeTypes.JS_FUNCTION_EXPRESSION,
+ returns: {
+ type: NodeTypes.JS_SEQUENCE_EXPRESSION,
+ expressions: [
+ {
+ type: NodeTypes.JS_CALL_EXPRESSION,
+ callee: `_${OPEN_BLOCK}`
+ },
+ {
+ type: NodeTypes.JS_CALL_EXPRESSION,
+ callee: `_${CREATE_BLOCK}`
+ }
+ ]
+ }
}
]
- }
+ },
+ keyed
+ ? `${PatchFlags.KEYED_FRAGMENT} /* ${
+ PatchFlagNames[PatchFlags.KEYED_FRAGMENT]
+ } */`
+ : `${PatchFlags.UNKEYED_FRAGMENT} /* ${
+ PatchFlagNames[PatchFlags.UNKEYED_FRAGMENT]
+ } */`
]
})
- return (node.expressions[1] as CallExpression)
- .arguments[2] as CallExpression
+ const renderListArgs = (node.arguments[2] as CallExpression).arguments
+ return {
+ source: renderListArgs[0] as SimpleExpressionNode,
+ params: (renderListArgs[1] as any).params,
+ blockArgs: (renderListArgs[1] as any).returns.expressions[1].arguments
+ }
}
test('basic v-for', () => {
@@ -587,17 +614,11 @@ describe('compiler: v-for', () => {
root,
node: { codegenNode }
} = parseWithForTransform('')
- expect(assertSharedCodegen(codegenNode).arguments).toMatchObject([
- { content: `items` },
- {
- type: NodeTypes.JS_FUNCTION_EXPRESSION,
- params: [{ content: `item` }],
- returns: {
- type: NodeTypes.ELEMENT,
- tag: `span`
- }
- }
- ])
+ expect(assertSharedCodegen(codegenNode)).toMatchObject({
+ source: { content: `items` },
+ params: [{ content: `item` }],
+ blockArgs: [`"span"`]
+ })
expect(generate(root).code).toMatchSnapshot()
})
@@ -606,17 +627,10 @@ describe('compiler: v-for', () => {
root,
node: { codegenNode }
} = parseWithForTransform('')
- expect(assertSharedCodegen(codegenNode).arguments).toMatchObject([
- { content: `items` },
- {
- type: NodeTypes.JS_FUNCTION_EXPRESSION,
- params: [
- { content: `item` },
- { content: `key` },
- { content: `index` }
- ]
- }
- ])
+ expect(assertSharedCodegen(codegenNode)).toMatchObject({
+ source: { content: `items` },
+ params: [{ content: `item` }, { content: `key` }, { content: `index` }]
+ })
expect(generate(root).code).toMatchSnapshot()
})
@@ -624,14 +638,11 @@ describe('compiler: v-for', () => {
const {
root,
node: { codegenNode }
- } = parseWithForTransform('')
- expect(assertSharedCodegen(codegenNode).arguments).toMatchObject([
- { content: `items` },
- {
- type: NodeTypes.JS_FUNCTION_EXPRESSION,
- params: [{ content: `_` }, { content: `key` }, { content: `index` }]
- }
- ])
+ } = parseWithForTransform('')
+ expect(assertSharedCodegen(codegenNode)).toMatchObject({
+ source: { content: `items` },
+ params: [{ content: `_` }, { content: `key` }, { content: `index` }]
+ })
expect(generate(root).code).toMatchSnapshot()
})
@@ -639,18 +650,11 @@ describe('compiler: v-for', () => {
const {
root,
node: { codegenNode }
- } = parseWithForTransform('')
- expect(assertSharedCodegen(codegenNode).arguments).toMatchObject([
- { content: `items` },
- {
- type: NodeTypes.JS_FUNCTION_EXPRESSION,
- params: [
- { content: `value` },
- { content: `__` },
- { content: `index` }
- ]
- }
- ])
+ } = parseWithForTransform('')
+ expect(assertSharedCodegen(codegenNode)).toMatchObject({
+ source: { content: `items` },
+ params: [{ content: `item` }, { content: `__` }, { content: `index` }]
+ })
expect(generate(root).code).toMatchSnapshot()
})
@@ -659,13 +663,10 @@ describe('compiler: v-for', () => {
root,
node: { codegenNode }
} = parseWithForTransform('')
- expect(assertSharedCodegen(codegenNode).arguments).toMatchObject([
- { content: `items` },
- {
- type: NodeTypes.JS_FUNCTION_EXPRESSION,
- params: [{ content: `_` }, { content: `__` }, { content: `index` }]
- }
- ])
+ expect(assertSharedCodegen(codegenNode)).toMatchObject({
+ source: { content: `items` },
+ params: [{ content: `_` }, { content: `__` }, { content: `index` }]
+ })
expect(generate(root).code).toMatchSnapshot()
})
@@ -676,17 +677,60 @@ describe('compiler: v-for', () => {
} = parseWithForTransform(
'hello'
)
- expect(assertSharedCodegen(codegenNode).arguments).toMatchObject([
- { content: `items` },
- {
- type: NodeTypes.JS_FUNCTION_EXPRESSION,
- params: [{ content: `item` }],
- returns: [
+ expect(assertSharedCodegen(codegenNode)).toMatchObject({
+ source: { content: `items` },
+ params: [{ content: `item` }],
+ blockArgs: [
+ `_${FRAGMENT}`,
+ `null`,
+ [
{ type: NodeTypes.TEXT, content: `hello` },
{ type: NodeTypes.ELEMENT, tag: `span` }
]
- }
- ])
+ ]
+ })
+ expect(generate(root).code).toMatchSnapshot()
+ })
+
+ test('keyed v-for', () => {
+ const {
+ root,
+ node: { codegenNode }
+ } = parseWithForTransform('')
+ expect(assertSharedCodegen(codegenNode, true)).toMatchObject({
+ source: { content: `items` },
+ params: [{ content: `item` }],
+ blockArgs: [
+ `"span"`,
+ createObjectMatcher({
+ key: `[item]`
+ })
+ ]
+ })
+ expect(generate(root).code).toMatchSnapshot()
+ })
+
+ test('keyed template v-for', () => {
+ const {
+ root,
+ node: { codegenNode }
+ } = parseWithForTransform(
+ 'hello'
+ )
+ expect(assertSharedCodegen(codegenNode, true)).toMatchObject({
+ source: { content: `items` },
+ params: [{ content: `item` }],
+ blockArgs: [
+ `_${FRAGMENT}`,
+ createObjectMatcher({
+ key: `[item]`
+ }),
+ [
+ { type: NodeTypes.TEXT, content: `hello` },
+ { type: NodeTypes.ELEMENT, tag: `span` }
+ ]
+ ]
+ })
expect(generate(root).code).toMatchSnapshot()
})
@@ -722,12 +766,25 @@ describe('compiler: v-for', () => {
type: NodeTypes.JS_FUNCTION_EXPRESSION,
params: [{ content: `i` }],
returns: {
- type: NodeTypes.ELEMENT,
- tag: `div`
+ type: NodeTypes.JS_SEQUENCE_EXPRESSION,
+ expressions: [
+ {
+ type: NodeTypes.JS_CALL_EXPRESSION,
+ callee: `_${OPEN_BLOCK}`
+ },
+ {
+ type: NodeTypes.JS_CALL_EXPRESSION,
+ callee: `_${CREATE_BLOCK}`,
+ arguments: [`"div"`]
+ }
+ ]
}
}
]
- }
+ },
+ `${PatchFlags.UNKEYED_FRAGMENT} /* ${
+ PatchFlagNames[PatchFlags.UNKEYED_FRAGMENT]
+ } */`
]
}
}
diff --git a/packages/compiler-core/src/ast.ts b/packages/compiler-core/src/ast.ts
index cc8f4296a..999c875a7 100644
--- a/packages/compiler-core/src/ast.ts
+++ b/packages/compiler-core/src/ast.ts
@@ -158,7 +158,7 @@ export interface ForNode extends Node {
keyAlias: ExpressionNode | undefined
objectIndexAlias: ExpressionNode | undefined
children: TemplateChildNode[]
- codegenNode: SequenceExpression
+ codegenNode: CallExpression
}
// We also include a number of JavaScript AST nodes for code generation.
@@ -198,7 +198,7 @@ export interface ArrayExpression extends Node {
export interface FunctionExpression extends Node {
type: NodeTypes.JS_FUNCTION_EXPRESSION
params: ExpressionNode | ExpressionNode[] | undefined
- returns: TemplateChildNode | TemplateChildNode[]
+ returns: TemplateChildNode | TemplateChildNode[] | JSChildNode
newline: boolean
}
diff --git a/packages/compiler-core/src/codegen.ts b/packages/compiler-core/src/codegen.ts
index c25518c14..81b7cb3f9 100644
--- a/packages/compiler-core/src/codegen.ts
+++ b/packages/compiler-core/src/codegen.ts
@@ -261,7 +261,6 @@ function genHoists(hoists: JSChildNode[], context: CodegenContext) {
// - The target position explicitly allows a single node (root, if, for)
// - The list has length === 1, AND The only child is a:
// - text
-// - expression
// - outlet, which always produces an array
function genChildren(
children: TemplateChildNode[],
diff --git a/packages/compiler-core/src/transforms/transformElement.ts b/packages/compiler-core/src/transforms/transformElement.ts
index ae9155a24..de6d1ffbe 100644
--- a/packages/compiler-core/src/transforms/transformElement.ts
+++ b/packages/compiler-core/src/transforms/transformElement.ts
@@ -163,7 +163,7 @@ export function buildProps(
// patchFlag analysis
let patchFlag = 0
const dynamicPropNames: string[] = []
- let hasDynammicKeys = false
+ let hasDynamicKeys = false
let hasClassBinding = false
let hasStyleBinding = false
let hasRef = false
@@ -207,7 +207,7 @@ export function buildProps(
// special case for v-bind and v-on with no argument
const isBind = name === 'bind'
if (!arg && (isBind || name === 'on')) {
- hasDynammicKeys = true
+ hasDynamicKeys = true
if (exp) {
if (properties.length) {
mergeArgs.push(
@@ -249,11 +249,11 @@ export function buildProps(
hasClassBinding = true
} else if (name === 'style') {
hasStyleBinding = true
- } else {
+ } else if (name !== 'key') {
dynamicPropNames.push(name)
}
} else {
- hasDynammicKeys = true
+ hasDynamicKeys = true
}
}
@@ -303,7 +303,7 @@ export function buildProps(
}
// determine the flags to add
- if (hasDynammicKeys) {
+ if (hasDynamicKeys) {
patchFlag |= PatchFlags.FULL_PROPS
} else {
if (hasClassBinding) {
diff --git a/packages/compiler-core/src/transforms/vFor.ts b/packages/compiler-core/src/transforms/vFor.ts
index aefd6f087..3af7f8619 100644
--- a/packages/compiler-core/src/transforms/vFor.ts
+++ b/packages/compiler-core/src/transforms/vFor.ts
@@ -11,17 +11,22 @@ import {
createSequenceExpression,
createCallExpression,
createFunctionExpression,
- ElementTypes
+ ElementTypes,
+ ObjectExpression,
+ createObjectExpression,
+ createObjectProperty
} from '../ast'
import { createCompilerError, ErrorCodes } from '../errors'
-import { getInnerRange } from '../utils'
+import { getInnerRange, findProp } from '../utils'
import {
RENDER_LIST,
OPEN_BLOCK,
CREATE_BLOCK,
- FRAGMENT
+ FRAGMENT,
+ CREATE_VNODE
} from '../runtimeConstants'
import { processExpression } from './transformExpression'
+import { PatchFlags, PatchFlagNames } from '@vue/shared'
export const transformFor = createStructuralDirectiveTransform(
'for',
@@ -38,9 +43,19 @@ export const transformFor = createStructuralDirectiveTransform(
const { helper, addIdentifiers, removeIdentifiers } = context
const { source, value, key, index } = parseResult
- const codegenNode = createSequenceExpression([
- createCallExpression(helper(OPEN_BLOCK))
- // to be filled in on exit after children traverse
+ // create the loop render function expression now, and add the
+ // iterator on exit after all children have been traversed
+ const renderExp = createCallExpression(helper(RENDER_LIST), [source])
+ const keyProp = findProp(node.props, `key`)
+ const fragmentFlag = keyProp
+ ? PatchFlags.KEYED_FRAGMENT
+ : PatchFlags.UNKEYED_FRAGMENT
+ const codegenNode = createCallExpression(helper(CREATE_VNODE), [
+ helper(FRAGMENT),
+ `null`,
+ renderExp,
+ fragmentFlag +
+ (__DEV__ ? ` /* ${PatchFlagNames[fragmentFlag]} */` : ``)
])
context.replaceNode({
@@ -63,6 +78,13 @@ export const transformFor = createStructuralDirectiveTransform(
}
return () => {
+ if (!__BROWSER__ && context.prefixIdentifiers) {
+ value && removeIdentifiers(value)
+ key && removeIdentifiers(key)
+ index && removeIdentifiers(index)
+ }
+
+ // finish the codegen now that all children have been traversed
const params: ExpressionNode[] = []
if (value) {
params.push(value)
@@ -83,26 +105,46 @@ export const transformFor = createStructuralDirectiveTransform(
params.push(index)
}
- codegenNode.expressions.push(
- createCallExpression(helper(CREATE_BLOCK), [
- helper(FRAGMENT),
- `null`,
- createCallExpression(helper(RENDER_LIST), [
- source,
- createFunctionExpression(
- params,
- node.tagType === ElementTypes.TEMPLATE ? node.children : node,
- true /* force newline to make it more readable */
+ let childBlock
+ if (node.tagType === ElementTypes.TEMPLATE) {
+ //
+ // should genereate a fragment block for each loop
+ let childBlockProps: string | ObjectExpression = `null`
+ if (keyProp) {
+ childBlockProps = createObjectExpression([
+ createObjectProperty(
+ createSimpleExpression(`key`, true),
+ keyProp.type === NodeTypes.ATTRIBUTE
+ ? createSimpleExpression(keyProp.value!.content, true)
+ : keyProp.exp!
)
])
+ }
+ childBlock = createSequenceExpression([
+ createCallExpression(helper(OPEN_BLOCK)),
+ createCallExpression(helper(CREATE_BLOCK), [
+ helper(FRAGMENT),
+ childBlockProps,
+ node.children
+ ])
+ ])
+ } else {
+ // Normal element v-for. Directly use the child's codegenNode,
+ // but replace createVNode() with createBlock()
+ node.codegenNode!.callee = helper(CREATE_BLOCK)
+ childBlock = createSequenceExpression([
+ createCallExpression(helper(OPEN_BLOCK)),
+ node.codegenNode!
])
- )
-
- if (!__BROWSER__ && context.prefixIdentifiers) {
- value && removeIdentifiers(value)
- key && removeIdentifiers(key)
- index && removeIdentifiers(index)
}
+
+ renderExp.arguments.push(
+ createFunctionExpression(
+ params,
+ childBlock,
+ true /* force newline */
+ )
+ )
}
} else {
context.onError(
diff --git a/packages/compiler-core/src/transforms/vIf.ts b/packages/compiler-core/src/transforms/vIf.ts
index 43aac0bce..b3c6371b5 100644
--- a/packages/compiler-core/src/transforms/vIf.ts
+++ b/packages/compiler-core/src/transforms/vIf.ts
@@ -20,9 +20,7 @@ import {
ObjectExpression,
createObjectProperty,
Property,
- ExpressionNode,
- TemplateChildNode,
- FunctionExpression
+ ExpressionNode
} from '../ast'
import { createCompilerError, ErrorCodes } from '../errors'
import { processExpression } from './transformExpression'
@@ -168,17 +166,19 @@ function createChildrenCodegenNode(
const needFragmentWrapper =
children.length > 1 || child.type !== NodeTypes.ELEMENT
if (needFragmentWrapper) {
- let fragmentChildren: TemplateChildNode[] | FunctionExpression = children
- // optimize away nested fragments when child is a ForNode
- if (children.length === 1 && child.type === NodeTypes.FOR) {
- fragmentChildren = (child.codegenNode.expressions[1] as CallExpression)
- .arguments[2] as FunctionExpression
- }
- return createCallExpression(helper(CREATE_BLOCK), [
+ const blockArgs: CallExpression['arguments'] = [
helper(FRAGMENT),
keyExp,
- fragmentChildren
- ])
+ children
+ ]
+ // optimize away nested fragments when child is a ForNode
+ if (children.length === 1 && child.type === NodeTypes.FOR) {
+ const forBlockExp = child.codegenNode
+ // directly use the for block's children and patchFlag
+ blockArgs[2] = forBlockExp.arguments[2]
+ blockArgs[3] = forBlockExp.arguments[3]
+ }
+ return createCallExpression(helper(CREATE_BLOCK), blockArgs)
} else {
const childCodegen = (child as ElementNode).codegenNode!
let vnodeCall = childCodegen
diff --git a/packages/compiler-core/src/utils.ts b/packages/compiler-core/src/utils.ts
index f0c95c73d..afd086f0a 100644
--- a/packages/compiler-core/src/utils.ts
+++ b/packages/compiler-core/src/utils.ts
@@ -1,4 +1,4 @@
-import { SourceLocation, Position } from './ast'
+import { SourceLocation, Position, ElementNode, NodeTypes } from './ast'
import { parseScript } from 'meriyah'
import { walk } from 'estree-walker'
@@ -94,3 +94,25 @@ export function assert(condition: boolean, msg?: string) {
throw new Error(msg || `unexpected compiler condition`)
}
}
+
+export function findProp(
+ props: ElementNode['props'],
+ name: string
+): ElementNode['props'][0] | undefined {
+ for (let i = 0; i < props.length; i++) {
+ const p = props[i]
+ if (p.type === NodeTypes.ATTRIBUTE) {
+ if (p.name === name && p.value && !p.value.isEmpty) {
+ return p
+ }
+ } else if (
+ p.arg &&
+ p.arg.type === NodeTypes.SIMPLE_EXPRESSION &&
+ p.arg.isStatic &&
+ p.arg.content === name &&
+ p.exp
+ ) {
+ return p
+ }
+ }
+}
diff --git a/packages/runtime-core/src/createRenderer.ts b/packages/runtime-core/src/createRenderer.ts
index b2b6be6d7..945a8b8e0 100644
--- a/packages/runtime-core/src/createRenderer.ts
+++ b/packages/runtime-core/src/createRenderer.ts
@@ -1230,7 +1230,7 @@ export function createRenderer<
// fast path
const { patchFlag, shapeFlag } = n2
if (patchFlag) {
- if (patchFlag & PatchFlags.KEYED_V_FOR) {
+ if (patchFlag & PatchFlags.KEYED_FRAGMENT) {
// this could be either fully-keyed or mixed (some keyed some not)
// presence of patchFlag means children are guaranteed to be arrays
patchKeyedChildren(
@@ -1244,7 +1244,7 @@ export function createRenderer<
optimized
)
return
- } else if (patchFlag & PatchFlags.UNKEYED_V_FOR) {
+ } else if (patchFlag & PatchFlags.UNKEYED_FRAGMENT) {
// unkeyed
patchUnkeyedChildren(
c1 as HostVNode[],
diff --git a/packages/shared/src/patchFlags.ts b/packages/shared/src/patchFlags.ts
index 8c98cf2c9..74c8bf864 100644
--- a/packages/shared/src/patchFlags.ts
+++ b/packages/shared/src/patchFlags.ts
@@ -47,11 +47,11 @@ export const enum PatchFlags {
// value.
NEED_PATCH = 1 << 5,
- // Indicates a v-for fragment with keyed or partially keyed children
- KEYED_V_FOR = 1 << 6,
+ // Indicates a fragment with keyed or partially keyed children
+ KEYED_FRAGMENT = 1 << 6,
- // Indicates a v-for fragment with unkeyed children.
- UNKEYED_V_FOR = 1 << 7,
+ // Indicates a fragment with unkeyed children.
+ UNKEYED_FRAGMENT = 1 << 7,
// Indicates a component with dynamic slots (e.g. slot that references a v-for
// iterated value, or dynamic slot names).
@@ -67,8 +67,8 @@ export const PublicPatchFlags = {
PROPS: PatchFlags.PROPS,
NEED_PATCH: PatchFlags.NEED_PATCH,
FULL_PROPS: PatchFlags.FULL_PROPS,
- KEYED_V_FOR: PatchFlags.KEYED_V_FOR,
- UNKEYED_V_FOR: PatchFlags.UNKEYED_V_FOR,
+ KEYED_FRAGMENT: PatchFlags.KEYED_FRAGMENT,
+ UNKEYED_FRAGMENT: PatchFlags.UNKEYED_FRAGMENT,
DYNAMIC_SLOTS: PatchFlags.DYNAMIC_SLOTS
}
@@ -80,7 +80,7 @@ export const PatchFlagNames = {
[PatchFlags.PROPS]: `PROPS`,
[PatchFlags.NEED_PATCH]: `NEED_PATCH`,
[PatchFlags.FULL_PROPS]: `FULL_PROPS`,
- [PatchFlags.KEYED_V_FOR]: `KEYED_V_FOR`,
- [PatchFlags.UNKEYED_V_FOR]: `UNKEYED_V_FOR`,
+ [PatchFlags.KEYED_FRAGMENT]: `KEYED_FRAGMENT`,
+ [PatchFlags.UNKEYED_FRAGMENT]: `UNKEYED_FRAGMENT`,
[PatchFlags.DYNAMIC_SLOTS]: `DYNAMIC_SLOTS`
}