wip(compiler): generate blocks for v-if

This commit is contained in:
Evan You
2019-10-01 12:25:13 -04:00
parent e31fb3c172
commit 5de744d4e1
21 changed files with 359 additions and 354 deletions

View File

@@ -142,7 +142,7 @@ export interface CompoundExpressionNode extends Node {
export interface IfNode extends Node {
type: NodeTypes.IF
branches: IfBranchNode[]
codegenNode: JSChildNode | undefined
codegenNode: SequenceExpression
}
export interface IfBranchNode extends Node {
@@ -212,9 +212,20 @@ export interface ConditionalExpression extends Node {
alternate: JSChildNode
}
// AST Utilities ---------------------------------------------------------------
// Some expressions, e.g. sequence and conditional expressions, are never
// associated with template nodes, so their source locations are just a stub.
// Container types like CompoundExpression also don't need a real location.
const locStub: SourceLocation = {
source: '',
start: { line: 1, column: 1, offset: 0 },
end: { line: 1, column: 1, offset: 0 }
}
export function createArrayExpression(
elements: ArrayExpression['elements'],
loc: SourceLocation
loc: SourceLocation = locStub
): ArrayExpression {
return {
type: NodeTypes.JS_ARRAY_EXPRESSION,
@@ -225,7 +236,7 @@ export function createArrayExpression(
export function createObjectExpression(
properties: Property[],
loc: SourceLocation
loc: SourceLocation = locStub
): ObjectExpression {
return {
type: NodeTypes.JS_OBJECT_EXPRESSION,
@@ -236,12 +247,11 @@ export function createObjectExpression(
export function createObjectProperty(
key: ExpressionNode,
value: JSChildNode,
loc: SourceLocation
value: JSChildNode
): Property {
return {
type: NodeTypes.JS_PROPERTY,
loc,
loc: locStub,
key,
value
}
@@ -250,7 +260,7 @@ export function createObjectProperty(
export function createSimpleExpression(
content: string,
isStatic: boolean,
loc: SourceLocation
loc: SourceLocation = locStub
): SimpleExpressionNode {
return {
type: NodeTypes.SIMPLE_EXPRESSION,
@@ -274,20 +284,19 @@ export function createInterpolation(
}
export function createCompoundExpression(
children: CompoundExpressionNode['children'],
loc: SourceLocation
children: CompoundExpressionNode['children']
): CompoundExpressionNode {
return {
type: NodeTypes.COMPOUND_EXPRESSION,
loc,
loc: locStub,
children
}
}
export function createCallExpression(
callee: string,
args: CallExpression['arguments'],
loc: SourceLocation
args: CallExpression['arguments'] = [],
loc: SourceLocation = locStub
): CallExpression {
return {
type: NodeTypes.JS_CALL_EXPRESSION,
@@ -300,7 +309,7 @@ export function createCallExpression(
export function createFunctionExpression(
params: ExpressionNode | undefined,
returns: TemplateChildNode[],
loc: SourceLocation
loc: SourceLocation = locStub
): SlotFunctionExpression {
return {
type: NodeTypes.JS_SLOT_FUNCTION,
@@ -310,15 +319,9 @@ export function createFunctionExpression(
}
}
// sequence and conditional expressions are never associated with template nodes,
// so their source locations are just a stub.
const locStub: SourceLocation = {
source: '',
start: { line: 1, column: 1, offset: 0 },
end: { line: 1, column: 1, offset: 0 }
}
export function createSequenceExpression(expressions: JSChildNode[]) {
export function createSequenceExpression(
expressions: JSChildNode[]
): SequenceExpression {
return {
type: NodeTypes.JS_SEQUENCE_EXPRESSION,
expressions,
@@ -330,7 +333,7 @@ export function createConditionalExpression(
test: ExpressionNode,
consequent: JSChildNode,
alternate: JSChildNode
) {
): ConditionalExpression {
return {
type: NodeTypes.JS_CONDITIONAL_EXPRESSION,
test,

View File

@@ -12,7 +12,6 @@ import {
CallExpression,
ArrayExpression,
ObjectExpression,
IfBranchNode,
SourceLocation,
Position,
InterpolationNode,
@@ -196,7 +195,7 @@ export function generate(
push(`const { ${ast.imports.join(', ')} } = Vue\n`)
} else {
// save Vue in a separate variable to avoid collision
push(`const _Vue = Vue`)
push(`const _Vue = Vue\n`)
}
}
genHoists(ast.hoists, context)
@@ -222,6 +221,7 @@ export function generate(
if (hasImports) {
push(`const { ${ast.imports.map(n => `${n}: _${n}`).join(', ')} } = _Vue`)
newline()
newline()
}
} else {
push(`const _ctx = this`)
@@ -471,44 +471,7 @@ function genComment(node: CommentNode, context: CodegenContext) {
// control flow
function genIf(node: IfNode, context: CodegenContext) {
genIfBranch(node.branches[0], node.branches, 1, context)
}
function genIfBranch(
{ condition, children }: IfBranchNode,
branches: IfBranchNode[],
nextIndex: number,
context: CodegenContext
) {
if (condition) {
// v-if or v-else-if
const { push, indent, deindent, newline } = context
if (condition.type === NodeTypes.SIMPLE_EXPRESSION) {
const needsQuote = !isSimpleIdentifier(condition.content)
needsQuote && push(`(`)
genExpression(condition, context)
needsQuote && push(`)`)
} else {
genCompoundExpression(condition, context)
}
indent()
context.indentLevel++
push(`? `)
genChildren(children, context, true)
context.indentLevel--
newline()
push(`: `)
if (nextIndex < branches.length) {
genIfBranch(branches[nextIndex], branches, nextIndex + 1, context)
} else {
context.push(`null`)
}
deindent(true /* without newline */)
} else {
// v-else
__DEV__ && assert(nextIndex === branches.length)
genChildren(children, context, true)
}
genNode(node.codegenNode, context)
}
function genFor(node: ForNode, context: CodegenContext) {
@@ -623,7 +586,14 @@ function genConditionalExpression(
context.indentLevel--
newline()
push(`: `)
const isNested = alternate.type === NodeTypes.JS_CONDITIONAL_EXPRESSION
if (!isNested) {
context.indentLevel++
}
genNode(alternate, context)
if (!isNested) {
context.indentLevel--
}
deindent(true /* without newline */)
}

View File

@@ -62,6 +62,7 @@ export const enum ErrorCodes {
X_MISSING_DYNAMIC_DIRECTIVE_ARGUMENT_END,
// transform errors
X_IF_NO_EXPRESSION,
X_ELSE_IF_NO_ADJACENT_IF,
X_ELSE_NO_ADJACENT_IF,
X_FOR_NO_EXPRESSION,
@@ -138,9 +139,10 @@ export const errorMessages: { [code: number]: string } = {
'Note that dynamic directive argument connot contain spaces.',
// transform errors
[ErrorCodes.X_IF_NO_EXPRESSION]: `v-if/v-else-if is missing expression.`,
[ErrorCodes.X_ELSE_IF_NO_ADJACENT_IF]: `v-else-if has no adjacent v-if.`,
[ErrorCodes.X_ELSE_NO_ADJACENT_IF]: `v-else has no adjacent v-if.`,
[ErrorCodes.X_FOR_NO_EXPRESSION]: `v-for has no expression.`,
[ErrorCodes.X_FOR_NO_EXPRESSION]: `v-for is missing expression.`,
[ErrorCodes.X_FOR_MALFORMED_EXPRESSION]: `v-for has invalid expression.`,
[ErrorCodes.X_V_BIND_NO_EXPRESSION]: `v-bind is missing expression.`,
[ErrorCodes.X_V_ON_NO_EXPRESSION]: `v-on is missing expression.`,

View File

@@ -5,6 +5,9 @@ export const PORTAL = `Portal`
export const COMMENT = `Comment`
export const TEXT = `Text`
export const SUSPENSE = `Suspense`
export const EMPTY = `Empty`
export const OPEN_BLOCK = `openBlock`
export const CREATE_BLOCK = `createBlock`
export const CREATE_VNODE = `createVNode`
export const RESOLVE_COMPONENT = `resolveComponent`
export const RESOLVE_DIRECTIVE = `resolveDirective`

View File

@@ -148,7 +148,6 @@ export function buildProps(
patchFlag: number
dynamicPropNames: string[]
} {
let isStatic = true
let properties: ObjectExpression['properties'] = []
const mergeArgs: PropsExpression[] = []
const runtimeDirectives: DirectiveNode[] = []
@@ -180,13 +179,11 @@ export function buildProps(
value ? value.content : '',
true,
value ? value.loc : loc
),
loc
)
)
)
} else {
// directives
isStatic = false
const { name, arg, exp, loc } = prop
// skip v-slot - it is handled by its dedicated transform.
@@ -297,11 +294,6 @@ export function buildProps(
)
}
// hoist the object if it's fully static
if (isStatic && propsExpression) {
propsExpression = context.hoist(propsExpression)
}
// determine the flags to add
if (hasDynammicKeys) {
patchFlag |= PatchFlags.FULL_PROPS
@@ -391,8 +383,7 @@ function createDirectiveArgs(
dir.modifiers.map(modifier =>
createObjectProperty(
createSimpleExpression(modifier, true, loc),
createSimpleExpression(`true`, false, loc),
loc
createSimpleExpression(`true`, false, loc)
)
),
loc

View File

@@ -192,7 +192,7 @@ export function processExpression(
let ret
if (children.length) {
ret = createCompoundExpression(children, node.loc)
ret = createCompoundExpression(children)
} else {
ret = node
}

View File

@@ -43,16 +43,13 @@ export const transformSlotOutlet: NodeTransform = (node, context) => {
arg.content === `name`
) {
// dynamic :name="xxx"
slot = createCompoundExpression(
[
$slots + `[`,
...(exp.type === NodeTypes.SIMPLE_EXPRESSION
? [exp]
: exp.children),
`]`
],
loc
)
slot = createCompoundExpression([
$slots + `[`,
...(exp.type === NodeTypes.SIMPLE_EXPRESSION
? [exp]
: exp.children),
`]`
])
nameIndex = i
break
}

View File

@@ -30,8 +30,7 @@ export const transformBind: DirectiveTransform = (dir, context) => {
return {
props: createObjectProperty(
arg!,
exp || createSimpleExpression('', true, loc),
loc
exp || createSimpleExpression('', true, loc)
),
needRuntime: false
}

View File

@@ -1,6 +1,7 @@
import {
createStructuralDirectiveTransform,
traverseChildren
traverseChildren,
TransformContext
} from '../transform'
import {
NodeTypes,
@@ -8,25 +9,66 @@ import {
ElementNode,
DirectiveNode,
IfBranchNode,
SimpleExpressionNode
SimpleExpressionNode,
createSequenceExpression,
createCallExpression,
createConditionalExpression,
ConditionalExpression,
CallExpression,
createSimpleExpression,
JSChildNode,
ObjectExpression,
createObjectProperty,
Property
} from '../ast'
import { createCompilerError, ErrorCodes } from '../errors'
import { processExpression } from './transformExpression'
import {
OPEN_BLOCK,
CREATE_BLOCK,
EMPTY,
FRAGMENT,
APPLY_DIRECTIVES
} from '../runtimeConstants'
import { isString } from '@vue/shared'
export const transformIf = createStructuralDirectiveTransform(
/^(if|else|else-if)$/,
(node, dir, context) => {
if (
dir.name !== 'else' &&
(!dir.exp || !(dir.exp as SimpleExpressionNode).content.trim())
) {
const loc = dir.exp ? dir.exp.loc : node.loc
context.onError(createCompilerError(ErrorCodes.X_IF_NO_EXPRESSION, loc))
dir.exp = createSimpleExpression(`true`, false, loc)
}
if (!__BROWSER__ && context.prefixIdentifiers && dir.exp) {
// dir.exp can only be simple expression because vIf transform is applied
// before expression transform.
processExpression(dir.exp as SimpleExpressionNode, context)
dir.exp = processExpression(dir.exp as SimpleExpressionNode, context)
}
if (dir.name === 'if') {
const codegenNode = createSequenceExpression([
createCallExpression(context.helper(OPEN_BLOCK))
])
context.replaceNode({
type: NodeTypes.IF,
loc: node.loc,
branches: [createIfBranch(node, dir)]
branches: [createIfBranch(node, dir)],
codegenNode
})
// Exit callback. Complete the codegenNode when all children have been
// transformed.
return () => {
codegenNode.expressions.push(
createCodegenNodeForBranch(node, dir, 0, context)
)
}
} else {
// locate the adjacent v-if
const siblings = context.parent!.children
@@ -50,6 +92,25 @@ export const transformIf = createStructuralDirectiveTransform(
// since the branch was removed, it will not be traversed.
// make sure to traverse here.
traverseChildren(branch, context)
// attach this branch's codegen node to the v-if root.
let parentCondition = sibling.codegenNode
.expressions[1] as ConditionalExpression
while (true) {
if (
parentCondition.alternate.type ===
NodeTypes.JS_CONDITIONAL_EXPRESSION
) {
parentCondition = parentCondition.alternate
} else {
parentCondition.alternate = createCodegenNodeForBranch(
node,
dir,
sibling.branches.length - 1,
context
)
break
}
}
} else {
context.onError(
createCompilerError(
@@ -74,3 +135,74 @@ function createIfBranch(node: ElementNode, dir: DirectiveNode): IfBranchNode {
children: node.tagType === ElementTypes.TEMPLATE ? node.children : [node]
}
}
function createCodegenNodeForBranch(
node: ElementNode,
dir: DirectiveNode,
index: number,
context: TransformContext
): ConditionalExpression | CallExpression {
if (dir.exp) {
return createConditionalExpression(
dir.exp,
createChildrenCodegenNode(node, index, context),
createCallExpression(context.helper(CREATE_BLOCK), [
context.helper(EMPTY)
])
)
} else {
return createChildrenCodegenNode(node, index, context)
}
}
function createChildrenCodegenNode(
node: ElementNode,
index: number,
{ helper }: TransformContext
): CallExpression {
const isTemplate = node.tagType === ElementTypes.TEMPLATE
const keyExp = `{ key: ${index} }`
if (isTemplate) {
return createCallExpression(helper(CREATE_BLOCK), [
helper(FRAGMENT),
keyExp,
node.children
])
} else {
let childCodegen = node.codegenNode!
if (childCodegen.callee === helper(APPLY_DIRECTIVES)) {
childCodegen = childCodegen.arguments[0] as CallExpression
}
// change child to a block
childCodegen.callee = helper(CREATE_BLOCK)
// branch key
const existingProps = childCodegen.arguments[1]
if (!existingProps || existingProps === `null`) {
childCodegen.arguments[1] = keyExp
} else {
// inject branch key if not already have a key
const props = existingProps as CallExpression | ObjectExpression
if (props.type === NodeTypes.JS_CALL_EXPRESSION) {
// merged props... add ours
// only inject key to object literal if it's the first argument so that
// if doesn't override user provided keys
const first = props.arguments[0] as string | JSChildNode
if (!isString(first) && first.type === NodeTypes.JS_OBJECT_EXPRESSION) {
first.properties.unshift(createKeyProperty(index))
} else {
props.arguments.unshift(keyExp)
}
} else {
props.properties.unshift(createKeyProperty(index))
}
}
return childCodegen
}
}
function createKeyProperty(index: number): Property {
return createObjectProperty(
createSimpleExpression(`key`, true),
createSimpleExpression(index + '', false)
)
}

View File

@@ -27,7 +27,7 @@ export const transformOn: DirectiveTransform = (dir, context) => {
arg.loc
)
} else {
eventName = createCompoundExpression([`"on" + (`, arg, `)`], arg.loc)
eventName = createCompoundExpression([`"on" + (`, arg, `)`])
}
} else {
// already a compound epxression.
@@ -40,8 +40,7 @@ export const transformOn: DirectiveTransform = (dir, context) => {
return {
props: createObjectProperty(
eventName,
exp || createSimpleExpression(`() => {}`, false, loc),
loc
exp || createSimpleExpression(`() => {}`, false, loc)
),
needRuntime: false
}

View File

@@ -144,7 +144,6 @@ function buildSlot(
slotProps,
children,
children.length ? children[0].loc : loc
),
loc
)
)
}