feat(compiler): convert text mixed with elements into createVNode calls

This ensures they are tracked as dynamic children when inside blocks.
Also guaruntees compiled vnodes always have vnode children in arrays
so that they can skip normalizeVNode safely in optimized mode.
This commit is contained in:
Evan You
2019-10-21 15:52:29 -04:00
parent a0d570b16d
commit 052febc127
13 changed files with 236 additions and 112 deletions

View File

@@ -35,6 +35,7 @@ export const enum NodeTypes {
IF,
IF_BRANCH,
FOR,
TEXT_CALL,
// codegen
JS_CALL_EXPRESSION,
JS_OBJECT_EXPRESSION,
@@ -86,6 +87,7 @@ export type TemplateChildNode =
| CommentNode
| IfNode
| ForNode
| TextCallNode
export interface RootNode extends Node {
type: NodeTypes.ROOT
@@ -227,6 +229,12 @@ export interface ForNode extends Node {
codegenNode: ForCodegenNode
}
export interface TextCallNode extends Node {
type: NodeTypes.TEXT_CALL
content: TextNode | InterpolationNode | CompoundExpressionNode
codegenNode: CallExpression
}
// We also include a number of JavaScript AST nodes for code generation.
// The AST is an intentionally minimal subset just to meet the exact needs of
// Vue render function generation.

View File

@@ -400,6 +400,9 @@ function genNode(node: CodegenNode | symbol | string, context: CodegenContext) {
case NodeTypes.INTERPOLATION:
genInterpolation(node, context)
break
case NodeTypes.TEXT_CALL:
genNode(node.codegenNode, context)
break
case NodeTypes.COMPOUND_EXPRESSION:
genCompoundExpression(node, context)
break

View File

@@ -12,7 +12,7 @@ import { transformOn } from './transforms/vOn'
import { transformBind } from './transforms/vBind'
import { defaultOnError, createCompilerError, ErrorCodes } from './errors'
import { trackSlotScopes, trackVForSlotScopes } from './transforms/vSlot'
import { optimizeText } from './transforms/optimizeText'
import { transformText } from './transforms/transformText'
import { transformOnce } from './transforms/vOnce'
import { transformModel } from './transforms/vModel'
@@ -56,7 +56,7 @@ export function baseCompile(
transformSlotOutlet,
transformElement,
trackSlotScopes,
optimizeText,
transformText,
...(options.nodeTransforms || []) // user transforms
],
directiveTransforms: {

View File

@@ -122,6 +122,7 @@ export function isStaticNode(
case NodeTypes.FOR:
return false
case NodeTypes.INTERPOLATION:
case NodeTypes.TEXT_CALL:
return isStaticNode(node.content, resultCache)
case NodeTypes.SIMPLE_EXPRESSION:
return node.isConstant

View File

@@ -4,8 +4,11 @@ import {
TemplateChildNode,
TextNode,
InterpolationNode,
CompoundExpressionNode
CompoundExpressionNode,
createCallExpression
} from '../ast'
import { TEXT, CREATE_VNODE } from '../runtimeHelpers'
import { PatchFlags, PatchFlagNames } from '@vue/shared'
const isText = (
node: TemplateChildNode
@@ -14,16 +17,19 @@ const isText = (
// Merge adjacent text nodes and expressions into a single expression
// e.g. <div>abc {{ d }} {{ e }}</div> should have a single expression node as child.
export const optimizeText: NodeTransform = node => {
export const transformText: NodeTransform = (node, context) => {
if (node.type === NodeTypes.ROOT || node.type === NodeTypes.ELEMENT) {
// perform the transform on node exit so that all expressions have already
// been processed.
return () => {
const children = node.children
let currentContainer: CompoundExpressionNode | undefined = undefined
let hasText = false
for (let i = 0; i < children.length; i++) {
const child = children[i]
if (isText(child)) {
hasText = true
for (let j = i + 1; j < children.length; j++) {
const next = children[j]
if (isText(next)) {
@@ -45,6 +51,31 @@ export const optimizeText: NodeTransform = node => {
}
}
}
if (hasText && children.length > 1) {
// when an element has mixed text/element children, convert text nodes
// into createVNode(Text) calls.
for (let i = 0; i < children.length; i++) {
const child = children[i]
if (isText(child) || child.type === NodeTypes.COMPOUND_EXPRESSION) {
const callArgs = [context.helper(TEXT), `null`, child]
if (child.type !== NodeTypes.TEXT) {
callArgs.push(
`${PatchFlags.TEXT} /* ${PatchFlagNames[PatchFlags.TEXT]} */`
)
}
children[i] = {
type: NodeTypes.TEXT_CALL,
content: child,
loc: child.loc,
codegenNode: createCallExpression(
context.helper(CREATE_VNODE),
callArgs
)
}
}
}
}
}
}
}

View File

@@ -293,9 +293,16 @@ export function hasScopeRef(
case NodeTypes.COMPOUND_EXPRESSION:
return node.children.some(c => isObject(c) && hasScopeRef(c, ids))
case NodeTypes.INTERPOLATION:
case NodeTypes.TEXT_CALL:
return hasScopeRef(node.content, ids)
case NodeTypes.TEXT:
case NodeTypes.COMMENT:
return false
default:
// TextNode or CommentNode
if (__DEV__) {
const exhaustiveCheck: never = node
exhaustiveCheck
}
return false
}
}