feat(compiler): optimize text by merging adjacent nodes
This commit is contained in:
@@ -64,6 +64,7 @@ export type ExpressionNode = SimpleExpressionNode | CompoundExpressionNode
|
||||
export type ChildNode =
|
||||
| ElementNode
|
||||
| InterpolationNode
|
||||
| CompoundExpressionNode
|
||||
| TextNode
|
||||
| CommentNode
|
||||
| IfNode
|
||||
@@ -130,7 +131,7 @@ export interface InterpolationNode extends Node {
|
||||
// always dynamic
|
||||
export interface CompoundExpressionNode extends Node {
|
||||
type: NodeTypes.COMPOUND_EXPRESSION
|
||||
children: (SimpleExpressionNode | string)[]
|
||||
children: (SimpleExpressionNode | InterpolationNode | TextNode | string)[]
|
||||
// an expression parsed as the params of a function will track
|
||||
// the identifiers declared inside the function body.
|
||||
identifiers?: string[]
|
||||
|
||||
@@ -283,6 +283,7 @@ function genChildren(
|
||||
(allowSingle ||
|
||||
type === NodeTypes.TEXT ||
|
||||
type === NodeTypes.INTERPOLATION ||
|
||||
type === NodeTypes.COMPOUND_EXPRESSION ||
|
||||
(type === NodeTypes.ELEMENT &&
|
||||
(child as ElementNode).tagType === ElementTypes.SLOT))
|
||||
) {
|
||||
@@ -423,7 +424,7 @@ function genCompoundExpression(
|
||||
if (isString(child)) {
|
||||
context.push(child)
|
||||
} else {
|
||||
genExpression(child, context)
|
||||
genNode(child, context)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@ import { transformOn } from './transforms/vOn'
|
||||
import { transformBind } from './transforms/vBind'
|
||||
import { defaultOnError, createCompilerError, ErrorCodes } from './errors'
|
||||
import { trackSlotScopes } from './transforms/vSlot'
|
||||
import { optimizeText } from './transforms/optimizeText'
|
||||
|
||||
export type CompilerOptions = ParserOptions & TransformOptions & CodegenOptions
|
||||
|
||||
@@ -45,6 +46,7 @@ export function baseCompile(
|
||||
transformIf,
|
||||
transformFor,
|
||||
...(prefixIdentifiers ? [transformExpression, trackSlotScopes] : []),
|
||||
optimizeText,
|
||||
transformStyle,
|
||||
transformSlotOutlet,
|
||||
transformElement,
|
||||
|
||||
@@ -20,7 +20,7 @@ import { TO_STRING, COMMENT, CREATE_VNODE } from './runtimeConstants'
|
||||
// Transforms that operate directly on a ChildNode. NodeTransforms may mutate,
|
||||
// replace or remove the node being processed.
|
||||
export type NodeTransform = (
|
||||
node: ChildNode,
|
||||
node: RootNode | ChildNode,
|
||||
context: TransformContext
|
||||
) => void | (() => void) | (() => void)[]
|
||||
|
||||
@@ -56,9 +56,9 @@ export interface TransformContext extends Required<TransformOptions> {
|
||||
statements: Set<string>
|
||||
hoists: JSChildNode[]
|
||||
identifiers: { [name: string]: number | undefined }
|
||||
parent: ParentNode
|
||||
parent: ParentNode | null
|
||||
childIndex: number
|
||||
currentNode: ChildNode | null
|
||||
currentNode: RootNode | ChildNode | null
|
||||
helper(name: string): string
|
||||
replaceNode(node: ChildNode): void
|
||||
removeNode(node?: ChildNode): void
|
||||
@@ -87,22 +87,30 @@ function createTransformContext(
|
||||
nodeTransforms,
|
||||
directiveTransforms,
|
||||
onError,
|
||||
parent: root,
|
||||
parent: null,
|
||||
currentNode: root,
|
||||
childIndex: 0,
|
||||
currentNode: null,
|
||||
helper(name) {
|
||||
context.imports.add(name)
|
||||
return prefixIdentifiers ? name : `_${name}`
|
||||
},
|
||||
replaceNode(node) {
|
||||
/* istanbul ignore if */
|
||||
if (__DEV__ && !context.currentNode) {
|
||||
throw new Error(`node being replaced is already removed.`)
|
||||
if (__DEV__) {
|
||||
if (!context.currentNode) {
|
||||
throw new Error(`Node being replaced is already removed.`)
|
||||
}
|
||||
if (!context.parent) {
|
||||
throw new Error(`Cannot replace root node.`)
|
||||
}
|
||||
}
|
||||
context.parent.children[context.childIndex] = context.currentNode = node
|
||||
context.parent!.children[context.childIndex] = context.currentNode = node
|
||||
},
|
||||
removeNode(node) {
|
||||
const list = context.parent.children
|
||||
if (__DEV__ && !context.parent) {
|
||||
throw new Error(`Cannot remove root node.`)
|
||||
}
|
||||
const list = context.parent!.children
|
||||
const removalIndex = node
|
||||
? list.indexOf(node as any)
|
||||
: context.currentNode
|
||||
@@ -123,7 +131,7 @@ function createTransformContext(
|
||||
context.onNodeRemoved()
|
||||
}
|
||||
}
|
||||
context.parent.children.splice(removalIndex, 1)
|
||||
context.parent!.children.splice(removalIndex, 1)
|
||||
},
|
||||
onNodeRemoved: () => {},
|
||||
addIdentifiers(exp) {
|
||||
@@ -172,7 +180,7 @@ function createTransformContext(
|
||||
|
||||
export function transform(root: RootNode, options: TransformOptions) {
|
||||
const context = createTransformContext(root, options)
|
||||
traverseChildren(root, context)
|
||||
traverseNode(root, context)
|
||||
root.imports = [...context.imports]
|
||||
root.statements = [...context.statements]
|
||||
root.hoists = context.hoists
|
||||
@@ -197,7 +205,10 @@ export function traverseChildren(
|
||||
}
|
||||
}
|
||||
|
||||
export function traverseNode(node: ChildNode, context: TransformContext) {
|
||||
export function traverseNode(
|
||||
node: RootNode | ChildNode,
|
||||
context: TransformContext
|
||||
) {
|
||||
// apply transform plugins
|
||||
const { nodeTransforms } = context
|
||||
const exitFns = []
|
||||
@@ -240,6 +251,7 @@ export function traverseNode(node: ChildNode, context: TransformContext) {
|
||||
break
|
||||
case NodeTypes.FOR:
|
||||
case NodeTypes.ELEMENT:
|
||||
case NodeTypes.ROOT:
|
||||
traverseChildren(node, context)
|
||||
break
|
||||
}
|
||||
|
||||
@@ -1,2 +0,0 @@
|
||||
// TODO 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.
|
||||
48
packages/compiler-core/src/transforms/optimizeText.ts
Normal file
48
packages/compiler-core/src/transforms/optimizeText.ts
Normal file
@@ -0,0 +1,48 @@
|
||||
import { NodeTransform } from '../transform'
|
||||
import {
|
||||
NodeTypes,
|
||||
ChildNode,
|
||||
TextNode,
|
||||
InterpolationNode,
|
||||
CompoundExpressionNode
|
||||
} from '../ast'
|
||||
|
||||
const isText = (node: ChildNode): node is TextNode | InterpolationNode =>
|
||||
node.type === NodeTypes.INTERPOLATION || node.type === NodeTypes.TEXT
|
||||
|
||||
// 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 => {
|
||||
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
|
||||
for (let i = 0; i < children.length; i++) {
|
||||
const child = children[i]
|
||||
if (isText(child)) {
|
||||
for (let j = i + 1; j < children.length; j++) {
|
||||
const next = children[j]
|
||||
if (isText(next)) {
|
||||
if (!currentContainer) {
|
||||
currentContainer = children[i] = {
|
||||
type: NodeTypes.COMPOUND_EXPRESSION,
|
||||
loc: child.loc,
|
||||
children: [child]
|
||||
}
|
||||
}
|
||||
// merge adjacent text node into current
|
||||
currentContainer.children.push(` + `, next)
|
||||
children.splice(j, 1)
|
||||
j--
|
||||
} else {
|
||||
currentContainer = undefined
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -29,7 +29,7 @@ export const transformIf = createStructuralDirectiveTransform(
|
||||
})
|
||||
} else {
|
||||
// locate the adjacent v-if
|
||||
const siblings = context.parent.children
|
||||
const siblings = context.parent!.children
|
||||
const comments = []
|
||||
let i = siblings.indexOf(node as any)
|
||||
while (i-- >= -1) {
|
||||
|
||||
Reference in New Issue
Block a user