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:
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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: {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user