feat(compiler): v-for codegen w/ correct blocks optimization + key flags

This commit is contained in:
Evan You 2019-10-01 23:19:48 -04:00
parent 07955e6c96
commit a477594d65
12 changed files with 324 additions and 173 deletions

View File

@ -15,11 +15,11 @@ return function render() {
(_openBlock(), ok (_openBlock(), ok
? _createBlock(\\"div\\", { key: 0 }, \\"yes\\") ? _createBlock(\\"div\\", { key: 0 }, \\"yes\\")
: _createBlock(_Fragment, { key: 1 }, \\"no\\")), : _createBlock(_Fragment, { key: 1 }, \\"no\\")),
(_openBlock(), _createBlock(_Fragment, null, _renderList(list, (value, index) => { _createVNode(_Fragment, null, _renderList(list, (value, index) => {
return _createVNode(\\"div\\", null, [ return (_openBlock(), _createBlock(\\"div\\", null, [
_createVNode(\\"span\\", null, _toString(value + index)) _createVNode(\\"span\\", null, _toString(value + index))
]) ]))
}))) }), 128 /* UNKEYED_FRAGMENT */)
], 2 /* CLASS */) ], 2 /* CLASS */)
} }
}" }"
@ -38,11 +38,11 @@ return function render() {
(openBlock(), (_ctx.ok) (openBlock(), (_ctx.ok)
? createBlock(\\"div\\", { key: 0 }, \\"yes\\") ? createBlock(\\"div\\", { key: 0 }, \\"yes\\")
: createBlock(Fragment, { key: 1 }, \\"no\\")), : createBlock(Fragment, { key: 1 }, \\"no\\")),
(openBlock(), createBlock(Fragment, null, renderList(_ctx.list, (value, index) => { createVNode(Fragment, null, renderList(_ctx.list, (value, index) => {
return createVNode(\\"div\\", null, [ return (openBlock(), createBlock(\\"div\\", null, [
createVNode(\\"span\\", null, toString(value + index)) createVNode(\\"span\\", null, toString(value + index))
]) ]))
}))) }), 128 /* UNKEYED_FRAGMENT */)
], 2 /* CLASS */) ], 2 /* CLASS */)
}" }"
`; `;
@ -60,11 +60,11 @@ export default function render() {
(openBlock(), (_ctx.ok) (openBlock(), (_ctx.ok)
? createBlock(\\"div\\", { key: 0 }, \\"yes\\") ? createBlock(\\"div\\", { key: 0 }, \\"yes\\")
: createBlock(Fragment, { key: 1 }, \\"no\\")), : createBlock(Fragment, { key: 1 }, \\"no\\")),
(openBlock(), createBlock(Fragment, null, renderList(_ctx.list, (value, index) => { createVNode(Fragment, null, renderList(_ctx.list, (value, index) => {
return createVNode(\\"div\\", null, [ return (openBlock(), createBlock(\\"div\\", null, [
createVNode(\\"span\\", null, _toString(value + index)) createVNode(\\"span\\", null, _toString(value + index))
]) ]))
}))) }), 128 /* UNKEYED_FRAGMENT */)
], 2 /* CLASS */) ], 2 /* CLASS */)
}" }"
`; `;

View File

@ -5,11 +5,42 @@ exports[`compiler: v-for codegen basic v-for 1`] = `
return function render() { return function render() {
with (this) { 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(_Fragment, null, _renderList(items, (item) => {
return _createVNode(\\"span\\") 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() { return function render() {
with (this) { 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(_Fragment, null, _renderList(items, (item, __, index) => {
return _createVNode(\\"span\\") return (_openBlock(), _createBlock(\\"span\\"))
}))) }), 128 /* UNKEYED_FRAGMENT */)
} }
}" }"
`; `;
@ -33,11 +64,11 @@ exports[`compiler: v-for codegen skipped value & key 1`] = `
return function render() { return function render() {
with (this) { 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(_Fragment, null, _renderList(items, (_, __, index) => {
return _createVNode(\\"span\\") return (_openBlock(), _createBlock(\\"span\\"))
}))) }), 128 /* UNKEYED_FRAGMENT */)
} }
}" }"
`; `;
@ -47,11 +78,11 @@ exports[`compiler: v-for codegen skipped value 1`] = `
return function render() { return function render() {
with (this) { 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(_Fragment, null, _renderList(items, (_, key, index) => {
return _createVNode(\\"span\\") return (_openBlock(), _createBlock(\\"span\\"))
}))) }), 128 /* UNKEYED_FRAGMENT */)
} }
}" }"
`; `;
@ -61,14 +92,14 @@ exports[`compiler: v-for codegen template v-for 1`] = `
return function render() { return function render() {
with (this) { 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 _createVNode(_Fragment, null, _renderList(items, (item) => {
return [ return (_openBlock(), _createBlock(_Fragment, null, [
\\"hello\\", \\"hello\\",
_createVNode(\\"span\\") _createVNode(\\"span\\")
] ]))
}))) }), 128 /* UNKEYED_FRAGMENT */)
} }
}" }"
`; `;
@ -78,12 +109,12 @@ exports[`compiler: v-for codegen v-if + v-for 1`] = `
return function render() { return function render() {
with (this) { 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 return (_openBlock(), ok
? _createBlock(_Fragment, { key: 0 }, _renderList(list, (i) => { ? _createBlock(_Fragment, { key: 0 }, _renderList(list, (i) => {
return _createVNode(\\"div\\") return (_openBlock(), _createBlock(\\"div\\"))
})) }), 128 /* UNKEYED_FRAGMENT */)
: _createBlock(_Empty)) : _createBlock(_Empty))
} }
}" }"
@ -94,11 +125,11 @@ exports[`compiler: v-for codegen value + key + index 1`] = `
return function render() { return function render() {
with (this) { 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(_Fragment, null, _renderList(items, (item, key, index) => {
return _createVNode(\\"span\\") return (_openBlock(), _createBlock(\\"span\\"))
}))) }), 128 /* UNKEYED_FRAGMENT */)
} }
}" }"
`; `;

View File

@ -19,7 +19,7 @@ exports[`compiler: v-if codegen template v-if 1`] = `
return function render() { return function render() {
with (this) { 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 return (_openBlock(), ok
? _createBlock(_Fragment, { key: 0 }, [ ? _createBlock(_Fragment, { key: 0 }, [

View File

@ -2,25 +2,29 @@ import { parse } from '../../src/parse'
import { transform } from '../../src/transform' import { transform } from '../../src/transform'
import { transformIf } from '../../src/transforms/vIf' import { transformIf } from '../../src/transforms/vIf'
import { transformFor } from '../../src/transforms/vFor' import { transformFor } from '../../src/transforms/vFor'
import { transformBind } from '../../src/transforms/vBind'
import { transformElement } from '../../src/transforms/transformElement' import { transformElement } from '../../src/transforms/transformElement'
import { transformExpression } from '../../src/transforms/transformExpression'
import { import {
ForNode, ForNode,
NodeTypes, NodeTypes,
SimpleExpressionNode, SimpleExpressionNode,
ElementNode, ElementNode,
InterpolationNode, InterpolationNode,
SequenceExpression,
CallExpression CallExpression
} from '../../src/ast' } from '../../src/ast'
import { ErrorCodes } from '../../src/errors' import { ErrorCodes } from '../../src/errors'
import { CompilerOptions, generate } from '../../src' import { CompilerOptions, generate } from '../../src'
import { transformExpression } from '../../src/transforms/transformExpression'
import { import {
OPEN_BLOCK, OPEN_BLOCK,
CREATE_BLOCK, CREATE_BLOCK,
FRAGMENT, FRAGMENT,
RENDER_LIST RENDER_LIST,
CREATE_VNODE
} from '../../src/runtimeConstants' } from '../../src/runtimeConstants'
import { PatchFlags } from '@vue/runtime-dom'
import { PatchFlagNames } from '@vue/shared'
import { createObjectMatcher } from '../testUtils'
function parseWithForTransform( function parseWithForTransform(
template: string, template: string,
@ -34,6 +38,9 @@ function parseWithForTransform(
...(options.prefixIdentifiers ? [transformExpression] : []), ...(options.prefixIdentifiers ? [transformExpression] : []),
transformElement transformElement
], ],
directiveTransforms: {
bind: transformBind
},
...options ...options
}) })
return { return {
@ -555,31 +562,51 @@ describe('compiler: v-for', () => {
}) })
describe('codegen', () => { describe('codegen', () => {
function assertSharedCodegen(node: SequenceExpression) { function assertSharedCodegen(node: CallExpression, keyed: boolean = false) {
expect(node).toMatchObject({ expect(node).toMatchObject({
type: NodeTypes.JS_SEQUENCE_EXPRESSION, type: NodeTypes.JS_CALL_EXPRESSION,
expressions: [ callee: `_${CREATE_VNODE}`,
arguments: [
`_${FRAGMENT}`,
`null`,
{ {
type: NodeTypes.JS_CALL_EXPRESSION, type: NodeTypes.JS_CALL_EXPRESSION,
callee: `_${OPEN_BLOCK}`, callee: `_${RENDER_LIST}`,
arguments: []
},
{
type: NodeTypes.JS_CALL_EXPRESSION,
callee: `_${CREATE_BLOCK}`,
arguments: [ arguments: [
`_${FRAGMENT}`, {}, // to be asserted by each test
`null`,
{ {
type: NodeTypes.JS_CALL_EXPRESSION, type: NodeTypes.JS_FUNCTION_EXPRESSION,
callee: `_${RENDER_LIST}` 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) const renderListArgs = (node.arguments[2] as CallExpression).arguments
.arguments[2] as CallExpression 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', () => { test('basic v-for', () => {
@ -587,17 +614,11 @@ describe('compiler: v-for', () => {
root, root,
node: { codegenNode } node: { codegenNode }
} = parseWithForTransform('<span v-for="(item) in items" />') } = parseWithForTransform('<span v-for="(item) in items" />')
expect(assertSharedCodegen(codegenNode).arguments).toMatchObject([ expect(assertSharedCodegen(codegenNode)).toMatchObject({
{ content: `items` }, source: { content: `items` },
{ params: [{ content: `item` }],
type: NodeTypes.JS_FUNCTION_EXPRESSION, blockArgs: [`"span"`]
params: [{ content: `item` }], })
returns: {
type: NodeTypes.ELEMENT,
tag: `span`
}
}
])
expect(generate(root).code).toMatchSnapshot() expect(generate(root).code).toMatchSnapshot()
}) })
@ -606,17 +627,10 @@ describe('compiler: v-for', () => {
root, root,
node: { codegenNode } node: { codegenNode }
} = parseWithForTransform('<span v-for="(item, key, index) in items" />') } = parseWithForTransform('<span v-for="(item, key, index) in items" />')
expect(assertSharedCodegen(codegenNode).arguments).toMatchObject([ expect(assertSharedCodegen(codegenNode)).toMatchObject({
{ content: `items` }, source: { content: `items` },
{ params: [{ content: `item` }, { content: `key` }, { content: `index` }]
type: NodeTypes.JS_FUNCTION_EXPRESSION, })
params: [
{ content: `item` },
{ content: `key` },
{ content: `index` }
]
}
])
expect(generate(root).code).toMatchSnapshot() expect(generate(root).code).toMatchSnapshot()
}) })
@ -624,14 +638,11 @@ describe('compiler: v-for', () => {
const { const {
root, root,
node: { codegenNode } node: { codegenNode }
} = parseWithForTransform('<span v-for="(,key,index) in items" />') } = parseWithForTransform('<span v-for="(, key, index) in items" />')
expect(assertSharedCodegen(codegenNode).arguments).toMatchObject([ expect(assertSharedCodegen(codegenNode)).toMatchObject({
{ content: `items` }, source: { content: `items` },
{ params: [{ content: `_` }, { content: `key` }, { content: `index` }]
type: NodeTypes.JS_FUNCTION_EXPRESSION, })
params: [{ content: `_` }, { content: `key` }, { content: `index` }]
}
])
expect(generate(root).code).toMatchSnapshot() expect(generate(root).code).toMatchSnapshot()
}) })
@ -639,18 +650,11 @@ describe('compiler: v-for', () => {
const { const {
root, root,
node: { codegenNode } node: { codegenNode }
} = parseWithForTransform('<span v-for="(value,,index) in items" />') } = parseWithForTransform('<span v-for="(item,,index) in items" />')
expect(assertSharedCodegen(codegenNode).arguments).toMatchObject([ expect(assertSharedCodegen(codegenNode)).toMatchObject({
{ content: `items` }, source: { content: `items` },
{ params: [{ content: `item` }, { content: `__` }, { content: `index` }]
type: NodeTypes.JS_FUNCTION_EXPRESSION, })
params: [
{ content: `value` },
{ content: `__` },
{ content: `index` }
]
}
])
expect(generate(root).code).toMatchSnapshot() expect(generate(root).code).toMatchSnapshot()
}) })
@ -659,13 +663,10 @@ describe('compiler: v-for', () => {
root, root,
node: { codegenNode } node: { codegenNode }
} = parseWithForTransform('<span v-for="(,,index) in items" />') } = parseWithForTransform('<span v-for="(,,index) in items" />')
expect(assertSharedCodegen(codegenNode).arguments).toMatchObject([ expect(assertSharedCodegen(codegenNode)).toMatchObject({
{ content: `items` }, source: { content: `items` },
{ params: [{ content: `_` }, { content: `__` }, { content: `index` }]
type: NodeTypes.JS_FUNCTION_EXPRESSION, })
params: [{ content: `_` }, { content: `__` }, { content: `index` }]
}
])
expect(generate(root).code).toMatchSnapshot() expect(generate(root).code).toMatchSnapshot()
}) })
@ -676,17 +677,60 @@ describe('compiler: v-for', () => {
} = parseWithForTransform( } = parseWithForTransform(
'<template v-for="item in items">hello<span/></template>' '<template v-for="item in items">hello<span/></template>'
) )
expect(assertSharedCodegen(codegenNode).arguments).toMatchObject([ expect(assertSharedCodegen(codegenNode)).toMatchObject({
{ content: `items` }, source: { content: `items` },
{ params: [{ content: `item` }],
type: NodeTypes.JS_FUNCTION_EXPRESSION, blockArgs: [
params: [{ content: `item` }], `_${FRAGMENT}`,
returns: [ `null`,
[
{ type: NodeTypes.TEXT, content: `hello` }, { type: NodeTypes.TEXT, content: `hello` },
{ type: NodeTypes.ELEMENT, tag: `span` } { type: NodeTypes.ELEMENT, tag: `span` }
] ]
} ]
]) })
expect(generate(root).code).toMatchSnapshot()
})
test('keyed v-for', () => {
const {
root,
node: { codegenNode }
} = parseWithForTransform('<span v-for="(item) in items" :key="item" />')
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(
'<template v-for="item in items" :key="item">hello<span/></template>'
)
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() expect(generate(root).code).toMatchSnapshot()
}) })
@ -722,12 +766,25 @@ describe('compiler: v-for', () => {
type: NodeTypes.JS_FUNCTION_EXPRESSION, type: NodeTypes.JS_FUNCTION_EXPRESSION,
params: [{ content: `i` }], params: [{ content: `i` }],
returns: { returns: {
type: NodeTypes.ELEMENT, type: NodeTypes.JS_SEQUENCE_EXPRESSION,
tag: `div` 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]
} */`
] ]
} }
} }

View File

@ -158,7 +158,7 @@ export interface ForNode extends Node {
keyAlias: ExpressionNode | undefined keyAlias: ExpressionNode | undefined
objectIndexAlias: ExpressionNode | undefined objectIndexAlias: ExpressionNode | undefined
children: TemplateChildNode[] children: TemplateChildNode[]
codegenNode: SequenceExpression codegenNode: CallExpression
} }
// We also include a number of JavaScript AST nodes for code generation. // 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 { export interface FunctionExpression extends Node {
type: NodeTypes.JS_FUNCTION_EXPRESSION type: NodeTypes.JS_FUNCTION_EXPRESSION
params: ExpressionNode | ExpressionNode[] | undefined params: ExpressionNode | ExpressionNode[] | undefined
returns: TemplateChildNode | TemplateChildNode[] returns: TemplateChildNode | TemplateChildNode[] | JSChildNode
newline: boolean newline: boolean
} }

View File

@ -261,7 +261,6 @@ function genHoists(hoists: JSChildNode[], context: CodegenContext) {
// - The target position explicitly allows a single node (root, if, for) // - The target position explicitly allows a single node (root, if, for)
// - The list has length === 1, AND The only child is a: // - The list has length === 1, AND The only child is a:
// - text // - text
// - expression
// - <slot> outlet, which always produces an array // - <slot> outlet, which always produces an array
function genChildren( function genChildren(
children: TemplateChildNode[], children: TemplateChildNode[],

View File

@ -163,7 +163,7 @@ export function buildProps(
// patchFlag analysis // patchFlag analysis
let patchFlag = 0 let patchFlag = 0
const dynamicPropNames: string[] = [] const dynamicPropNames: string[] = []
let hasDynammicKeys = false let hasDynamicKeys = false
let hasClassBinding = false let hasClassBinding = false
let hasStyleBinding = false let hasStyleBinding = false
let hasRef = false let hasRef = false
@ -207,7 +207,7 @@ export function buildProps(
// special case for v-bind and v-on with no argument // special case for v-bind and v-on with no argument
const isBind = name === 'bind' const isBind = name === 'bind'
if (!arg && (isBind || name === 'on')) { if (!arg && (isBind || name === 'on')) {
hasDynammicKeys = true hasDynamicKeys = true
if (exp) { if (exp) {
if (properties.length) { if (properties.length) {
mergeArgs.push( mergeArgs.push(
@ -249,11 +249,11 @@ export function buildProps(
hasClassBinding = true hasClassBinding = true
} else if (name === 'style') { } else if (name === 'style') {
hasStyleBinding = true hasStyleBinding = true
} else { } else if (name !== 'key') {
dynamicPropNames.push(name) dynamicPropNames.push(name)
} }
} else { } else {
hasDynammicKeys = true hasDynamicKeys = true
} }
} }
@ -303,7 +303,7 @@ export function buildProps(
} }
// determine the flags to add // determine the flags to add
if (hasDynammicKeys) { if (hasDynamicKeys) {
patchFlag |= PatchFlags.FULL_PROPS patchFlag |= PatchFlags.FULL_PROPS
} else { } else {
if (hasClassBinding) { if (hasClassBinding) {

View File

@ -11,17 +11,22 @@ import {
createSequenceExpression, createSequenceExpression,
createCallExpression, createCallExpression,
createFunctionExpression, createFunctionExpression,
ElementTypes ElementTypes,
ObjectExpression,
createObjectExpression,
createObjectProperty
} from '../ast' } from '../ast'
import { createCompilerError, ErrorCodes } from '../errors' import { createCompilerError, ErrorCodes } from '../errors'
import { getInnerRange } from '../utils' import { getInnerRange, findProp } from '../utils'
import { import {
RENDER_LIST, RENDER_LIST,
OPEN_BLOCK, OPEN_BLOCK,
CREATE_BLOCK, CREATE_BLOCK,
FRAGMENT FRAGMENT,
CREATE_VNODE
} from '../runtimeConstants' } from '../runtimeConstants'
import { processExpression } from './transformExpression' import { processExpression } from './transformExpression'
import { PatchFlags, PatchFlagNames } from '@vue/shared'
export const transformFor = createStructuralDirectiveTransform( export const transformFor = createStructuralDirectiveTransform(
'for', 'for',
@ -38,9 +43,19 @@ export const transformFor = createStructuralDirectiveTransform(
const { helper, addIdentifiers, removeIdentifiers } = context const { helper, addIdentifiers, removeIdentifiers } = context
const { source, value, key, index } = parseResult const { source, value, key, index } = parseResult
const codegenNode = createSequenceExpression([ // create the loop render function expression now, and add the
createCallExpression(helper(OPEN_BLOCK)) // iterator on exit after all children have been traversed
// to be filled in on exit after children traverse 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({ context.replaceNode({
@ -63,6 +78,13 @@ export const transformFor = createStructuralDirectiveTransform(
} }
return () => { 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[] = [] const params: ExpressionNode[] = []
if (value) { if (value) {
params.push(value) params.push(value)
@ -83,26 +105,46 @@ export const transformFor = createStructuralDirectiveTransform(
params.push(index) params.push(index)
} }
codegenNode.expressions.push( let childBlock
createCallExpression(helper(CREATE_BLOCK), [ if (node.tagType === ElementTypes.TEMPLATE) {
helper(FRAGMENT), // <template v-for="...">
`null`, // should genereate a fragment block for each loop
createCallExpression(helper(RENDER_LIST), [ let childBlockProps: string | ObjectExpression = `null`
source, if (keyProp) {
createFunctionExpression( childBlockProps = createObjectExpression([
params, createObjectProperty(
node.tagType === ElementTypes.TEMPLATE ? node.children : node, createSimpleExpression(`key`, true),
true /* force newline to make it more readable */ 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 { } else {
context.onError( context.onError(

View File

@ -20,9 +20,7 @@ import {
ObjectExpression, ObjectExpression,
createObjectProperty, createObjectProperty,
Property, Property,
ExpressionNode, ExpressionNode
TemplateChildNode,
FunctionExpression
} from '../ast' } from '../ast'
import { createCompilerError, ErrorCodes } from '../errors' import { createCompilerError, ErrorCodes } from '../errors'
import { processExpression } from './transformExpression' import { processExpression } from './transformExpression'
@ -168,17 +166,19 @@ function createChildrenCodegenNode(
const needFragmentWrapper = const needFragmentWrapper =
children.length > 1 || child.type !== NodeTypes.ELEMENT children.length > 1 || child.type !== NodeTypes.ELEMENT
if (needFragmentWrapper) { if (needFragmentWrapper) {
let fragmentChildren: TemplateChildNode[] | FunctionExpression = children const blockArgs: CallExpression['arguments'] = [
// 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), [
helper(FRAGMENT), helper(FRAGMENT),
keyExp, 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 { } else {
const childCodegen = (child as ElementNode).codegenNode! const childCodegen = (child as ElementNode).codegenNode!
let vnodeCall = childCodegen let vnodeCall = childCodegen

View File

@ -1,4 +1,4 @@
import { SourceLocation, Position } from './ast' import { SourceLocation, Position, ElementNode, NodeTypes } from './ast'
import { parseScript } from 'meriyah' import { parseScript } from 'meriyah'
import { walk } from 'estree-walker' import { walk } from 'estree-walker'
@ -94,3 +94,25 @@ export function assert(condition: boolean, msg?: string) {
throw new Error(msg || `unexpected compiler condition`) 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
}
}
}

View File

@ -1230,7 +1230,7 @@ export function createRenderer<
// fast path // fast path
const { patchFlag, shapeFlag } = n2 const { patchFlag, shapeFlag } = n2
if (patchFlag) { 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) // this could be either fully-keyed or mixed (some keyed some not)
// presence of patchFlag means children are guaranteed to be arrays // presence of patchFlag means children are guaranteed to be arrays
patchKeyedChildren( patchKeyedChildren(
@ -1244,7 +1244,7 @@ export function createRenderer<
optimized optimized
) )
return return
} else if (patchFlag & PatchFlags.UNKEYED_V_FOR) { } else if (patchFlag & PatchFlags.UNKEYED_FRAGMENT) {
// unkeyed // unkeyed
patchUnkeyedChildren( patchUnkeyedChildren(
c1 as HostVNode[], c1 as HostVNode[],

View File

@ -47,11 +47,11 @@ export const enum PatchFlags {
// value. // value.
NEED_PATCH = 1 << 5, NEED_PATCH = 1 << 5,
// Indicates a v-for fragment with keyed or partially keyed children // Indicates a fragment with keyed or partially keyed children
KEYED_V_FOR = 1 << 6, KEYED_FRAGMENT = 1 << 6,
// Indicates a v-for fragment with unkeyed children. // Indicates a fragment with unkeyed children.
UNKEYED_V_FOR = 1 << 7, UNKEYED_FRAGMENT = 1 << 7,
// Indicates a component with dynamic slots (e.g. slot that references a v-for // Indicates a component with dynamic slots (e.g. slot that references a v-for
// iterated value, or dynamic slot names). // iterated value, or dynamic slot names).
@ -67,8 +67,8 @@ export const PublicPatchFlags = {
PROPS: PatchFlags.PROPS, PROPS: PatchFlags.PROPS,
NEED_PATCH: PatchFlags.NEED_PATCH, NEED_PATCH: PatchFlags.NEED_PATCH,
FULL_PROPS: PatchFlags.FULL_PROPS, FULL_PROPS: PatchFlags.FULL_PROPS,
KEYED_V_FOR: PatchFlags.KEYED_V_FOR, KEYED_FRAGMENT: PatchFlags.KEYED_FRAGMENT,
UNKEYED_V_FOR: PatchFlags.UNKEYED_V_FOR, UNKEYED_FRAGMENT: PatchFlags.UNKEYED_FRAGMENT,
DYNAMIC_SLOTS: PatchFlags.DYNAMIC_SLOTS DYNAMIC_SLOTS: PatchFlags.DYNAMIC_SLOTS
} }
@ -80,7 +80,7 @@ export const PatchFlagNames = {
[PatchFlags.PROPS]: `PROPS`, [PatchFlags.PROPS]: `PROPS`,
[PatchFlags.NEED_PATCH]: `NEED_PATCH`, [PatchFlags.NEED_PATCH]: `NEED_PATCH`,
[PatchFlags.FULL_PROPS]: `FULL_PROPS`, [PatchFlags.FULL_PROPS]: `FULL_PROPS`,
[PatchFlags.KEYED_V_FOR]: `KEYED_V_FOR`, [PatchFlags.KEYED_FRAGMENT]: `KEYED_FRAGMENT`,
[PatchFlags.UNKEYED_V_FOR]: `UNKEYED_V_FOR`, [PatchFlags.UNKEYED_FRAGMENT]: `UNKEYED_FRAGMENT`,
[PatchFlags.DYNAMIC_SLOTS]: `DYNAMIC_SLOTS` [PatchFlags.DYNAMIC_SLOTS]: `DYNAMIC_SLOTS`
} }