wip(compiler): generate blocks for v-if
This commit is contained in:
@@ -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,
|
||||
|
||||
@@ -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 */)
|
||||
}
|
||||
|
||||
|
||||
@@ -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.`,
|
||||
|
||||
@@ -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`
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -192,7 +192,7 @@ export function processExpression(
|
||||
|
||||
let ret
|
||||
if (children.length) {
|
||||
ret = createCompoundExpression(children, node.loc)
|
||||
ret = createCompoundExpression(children)
|
||||
} else {
|
||||
ret = node
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -144,7 +144,6 @@ function buildSlot(
|
||||
slotProps,
|
||||
children,
|
||||
children.length ? children[0].loc : loc
|
||||
),
|
||||
loc
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user