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

@@ -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
}

View File

@@ -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
// - <slot> outlet, which always produces an array
function genChildren(
children: TemplateChildNode[],

View File

@@ -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) {

View File

@@ -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) {
// <template v-for="...">
// 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(

View File

@@ -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

View File

@@ -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
}
}
}