feat(compiler): optimize text by merging adjacent nodes
This commit is contained in:
parent
7ee07447c5
commit
e5e40e1e38
@ -44,7 +44,7 @@ exports[`compiler: codegen compound expression 1`] = `
|
|||||||
"
|
"
|
||||||
return function render() {
|
return function render() {
|
||||||
with (this) {
|
with (this) {
|
||||||
return _toString(_ctx.foo)
|
return _ctx.foo + _toString(bar)
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
|
@ -226,17 +226,23 @@ describe('compiler: codegen', () => {
|
|||||||
const { code } = generate(
|
const { code } = generate(
|
||||||
createRoot({
|
createRoot({
|
||||||
children: [
|
children: [
|
||||||
createInterpolation(
|
createCompoundExpression(
|
||||||
createCompoundExpression(
|
[
|
||||||
[`_ctx.`, createSimpleExpression(`foo`, false, mockLoc)],
|
`_ctx.`,
|
||||||
mockLoc
|
createSimpleExpression(`foo`, false, mockLoc),
|
||||||
),
|
` + `,
|
||||||
|
{
|
||||||
|
type: NodeTypes.INTERPOLATION,
|
||||||
|
loc: mockLoc,
|
||||||
|
content: createSimpleExpression(`bar`, false, mockLoc)
|
||||||
|
}
|
||||||
|
],
|
||||||
mockLoc
|
mockLoc
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
expect(code).toMatch(`return _${TO_STRING}(_ctx.foo)`)
|
expect(code).toMatch(`return _ctx.foo + _${TO_STRING}(bar)`)
|
||||||
expect(code).toMatchSnapshot()
|
expect(code).toMatchSnapshot()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -25,22 +25,29 @@ describe('compiler: transform', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
const div = ast.children[0] as ElementNode
|
const div = ast.children[0] as ElementNode
|
||||||
expect(calls.length).toBe(3)
|
expect(calls.length).toBe(4)
|
||||||
expect(calls[0]).toMatchObject([
|
expect(calls[0]).toMatchObject([
|
||||||
|
ast,
|
||||||
|
{
|
||||||
|
parent: null,
|
||||||
|
currentNode: ast
|
||||||
|
}
|
||||||
|
])
|
||||||
|
expect(calls[1]).toMatchObject([
|
||||||
div,
|
div,
|
||||||
{
|
{
|
||||||
parent: ast,
|
parent: ast,
|
||||||
currentNode: div
|
currentNode: div
|
||||||
}
|
}
|
||||||
])
|
])
|
||||||
expect(calls[1]).toMatchObject([
|
expect(calls[2]).toMatchObject([
|
||||||
div.children[0],
|
div.children[0],
|
||||||
{
|
{
|
||||||
parent: div,
|
parent: div,
|
||||||
currentNode: div.children[0]
|
currentNode: div.children[0]
|
||||||
}
|
}
|
||||||
])
|
])
|
||||||
expect(calls[2]).toMatchObject([
|
expect(calls[3]).toMatchObject([
|
||||||
div.children[1],
|
div.children[1],
|
||||||
{
|
{
|
||||||
parent: div,
|
parent: div,
|
||||||
@ -76,11 +83,11 @@ describe('compiler: transform', () => {
|
|||||||
expect(ast.children.length).toBe(2)
|
expect(ast.children.length).toBe(2)
|
||||||
const newElement = ast.children[0] as ElementNode
|
const newElement = ast.children[0] as ElementNode
|
||||||
expect(newElement.tag).toBe('p')
|
expect(newElement.tag).toBe('p')
|
||||||
expect(spy).toHaveBeenCalledTimes(3)
|
expect(spy).toHaveBeenCalledTimes(4)
|
||||||
// should traverse the children of replaced node
|
// should traverse the children of replaced node
|
||||||
expect(spy.mock.calls[1][0]).toBe(newElement.children[0])
|
expect(spy.mock.calls[2][0]).toBe(newElement.children[0])
|
||||||
// should traverse the node after the replaced node
|
// should traverse the node after the replaced node
|
||||||
expect(spy.mock.calls[2][0]).toBe(ast.children[1])
|
expect(spy.mock.calls[3][0]).toBe(ast.children[1])
|
||||||
})
|
})
|
||||||
|
|
||||||
test('context.removeNode', () => {
|
test('context.removeNode', () => {
|
||||||
@ -103,10 +110,10 @@ describe('compiler: transform', () => {
|
|||||||
expect(ast.children[1]).toBe(c2)
|
expect(ast.children[1]).toBe(c2)
|
||||||
|
|
||||||
// should not traverse children of remove node
|
// should not traverse children of remove node
|
||||||
expect(spy).toHaveBeenCalledTimes(3)
|
expect(spy).toHaveBeenCalledTimes(4)
|
||||||
// should traverse nodes around removed
|
// should traverse nodes around removed
|
||||||
expect(spy.mock.calls[0][0]).toBe(c1)
|
expect(spy.mock.calls[1][0]).toBe(c1)
|
||||||
expect(spy.mock.calls[2][0]).toBe(c2)
|
expect(spy.mock.calls[3][0]).toBe(c2)
|
||||||
})
|
})
|
||||||
|
|
||||||
test('context.removeNode (prev sibling)', () => {
|
test('context.removeNode (prev sibling)', () => {
|
||||||
@ -118,7 +125,7 @@ describe('compiler: transform', () => {
|
|||||||
if (node.type === NodeTypes.ELEMENT && node.tag === 'div') {
|
if (node.type === NodeTypes.ELEMENT && node.tag === 'div') {
|
||||||
context.removeNode()
|
context.removeNode()
|
||||||
// remove previous sibling
|
// remove previous sibling
|
||||||
context.removeNode(context.parent.children[0])
|
context.removeNode(context.parent!.children[0])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const spy = jest.fn(plugin)
|
const spy = jest.fn(plugin)
|
||||||
@ -129,11 +136,11 @@ describe('compiler: transform', () => {
|
|||||||
expect(ast.children.length).toBe(1)
|
expect(ast.children.length).toBe(1)
|
||||||
expect(ast.children[0]).toBe(c2)
|
expect(ast.children[0]).toBe(c2)
|
||||||
|
|
||||||
expect(spy).toHaveBeenCalledTimes(3)
|
expect(spy).toHaveBeenCalledTimes(4)
|
||||||
// should still traverse first span before removal
|
// should still traverse first span before removal
|
||||||
expect(spy.mock.calls[0][0]).toBe(c1)
|
expect(spy.mock.calls[1][0]).toBe(c1)
|
||||||
// should still traverse last span
|
// should still traverse last span
|
||||||
expect(spy.mock.calls[2][0]).toBe(c2)
|
expect(spy.mock.calls[3][0]).toBe(c2)
|
||||||
})
|
})
|
||||||
|
|
||||||
test('context.removeNode (next sibling)', () => {
|
test('context.removeNode (next sibling)', () => {
|
||||||
@ -145,7 +152,7 @@ describe('compiler: transform', () => {
|
|||||||
if (node.type === NodeTypes.ELEMENT && node.tag === 'div') {
|
if (node.type === NodeTypes.ELEMENT && node.tag === 'div') {
|
||||||
context.removeNode()
|
context.removeNode()
|
||||||
// remove next sibling
|
// remove next sibling
|
||||||
context.removeNode(context.parent.children[1])
|
context.removeNode(context.parent!.children[1])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const spy = jest.fn(plugin)
|
const spy = jest.fn(plugin)
|
||||||
@ -156,20 +163,22 @@ describe('compiler: transform', () => {
|
|||||||
expect(ast.children.length).toBe(1)
|
expect(ast.children.length).toBe(1)
|
||||||
expect(ast.children[0]).toBe(c1)
|
expect(ast.children[0]).toBe(c1)
|
||||||
|
|
||||||
expect(spy).toHaveBeenCalledTimes(2)
|
expect(spy).toHaveBeenCalledTimes(3)
|
||||||
// should still traverse first span before removal
|
// should still traverse first span before removal
|
||||||
expect(spy.mock.calls[0][0]).toBe(c1)
|
expect(spy.mock.calls[1][0]).toBe(c1)
|
||||||
// should not traverse last span
|
// should not traverse last span
|
||||||
expect(spy.mock.calls[1][0]).toBe(d1)
|
expect(spy.mock.calls[2][0]).toBe(d1)
|
||||||
})
|
})
|
||||||
|
|
||||||
test('context.hoist', () => {
|
test('context.hoist', () => {
|
||||||
const ast = parse(`<div :id="foo"/><div :id="bar"/>`)
|
const ast = parse(`<div :id="foo"/><div :id="bar"/>`)
|
||||||
const hoisted: ExpressionNode[] = []
|
const hoisted: ExpressionNode[] = []
|
||||||
const mock: NodeTransform = (node, context) => {
|
const mock: NodeTransform = (node, context) => {
|
||||||
const dir = (node as ElementNode).props[0] as DirectiveNode
|
if (node.type === NodeTypes.ELEMENT) {
|
||||||
hoisted.push(dir.exp!)
|
const dir = node.props[0] as DirectiveNode
|
||||||
dir.exp = context.hoist(dir.exp!)
|
hoisted.push(dir.exp!)
|
||||||
|
dir.exp = context.hoist(dir.exp!)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
transform(ast, {
|
transform(ast, {
|
||||||
nodeTransforms: [mock]
|
nodeTransforms: [mock]
|
||||||
|
112
packages/compiler-core/__tests__/transforms/optimizeText.spec.ts
Normal file
112
packages/compiler-core/__tests__/transforms/optimizeText.spec.ts
Normal file
@ -0,0 +1,112 @@
|
|||||||
|
import { CompilerOptions, parse, transform, NodeTypes } from '../../src'
|
||||||
|
import { optimizeText } from '../../src/transforms/optimizeText'
|
||||||
|
import { transformExpression } from '../../src/transforms/transformExpression'
|
||||||
|
|
||||||
|
function transformWithTextOpt(template: string, options: CompilerOptions = {}) {
|
||||||
|
const ast = parse(template)
|
||||||
|
transform(ast, {
|
||||||
|
nodeTransforms: [
|
||||||
|
...(options.prefixIdentifiers ? [transformExpression] : []),
|
||||||
|
optimizeText
|
||||||
|
],
|
||||||
|
...options
|
||||||
|
})
|
||||||
|
return ast
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('compiler: optimize interpolation', () => {
|
||||||
|
test('no consecutive text', () => {
|
||||||
|
const root = transformWithTextOpt(`{{ foo }}`)
|
||||||
|
expect(root.children[0]).toMatchObject({
|
||||||
|
type: NodeTypes.INTERPOLATION,
|
||||||
|
content: {
|
||||||
|
content: `foo`
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
test('consecutive text', () => {
|
||||||
|
const root = transformWithTextOpt(`{{ foo }} bar {{ baz }}`)
|
||||||
|
expect(root.children.length).toBe(1)
|
||||||
|
expect(root.children[0]).toMatchObject({
|
||||||
|
type: NodeTypes.COMPOUND_EXPRESSION,
|
||||||
|
children: [
|
||||||
|
{ type: NodeTypes.INTERPOLATION, content: { content: `foo` } },
|
||||||
|
` + `,
|
||||||
|
{ type: NodeTypes.TEXT, content: ` bar ` },
|
||||||
|
` + `,
|
||||||
|
{ type: NodeTypes.INTERPOLATION, content: { content: `baz` } }
|
||||||
|
]
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
test('consecutive text between elements', () => {
|
||||||
|
const root = transformWithTextOpt(`<div/>{{ foo }} bar {{ baz }}<div/>`)
|
||||||
|
expect(root.children.length).toBe(3)
|
||||||
|
expect(root.children[0].type).toBe(NodeTypes.ELEMENT)
|
||||||
|
expect(root.children[1]).toMatchObject({
|
||||||
|
type: NodeTypes.COMPOUND_EXPRESSION,
|
||||||
|
children: [
|
||||||
|
{ type: NodeTypes.INTERPOLATION, content: { content: `foo` } },
|
||||||
|
` + `,
|
||||||
|
{ type: NodeTypes.TEXT, content: ` bar ` },
|
||||||
|
` + `,
|
||||||
|
{ type: NodeTypes.INTERPOLATION, content: { content: `baz` } }
|
||||||
|
]
|
||||||
|
})
|
||||||
|
expect(root.children[2].type).toBe(NodeTypes.ELEMENT)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('consecutive text mixed with elements', () => {
|
||||||
|
const root = transformWithTextOpt(
|
||||||
|
`<div/>{{ foo }} bar {{ baz }}<div/>{{ foo }} bar {{ baz }}<div/>`
|
||||||
|
)
|
||||||
|
expect(root.children.length).toBe(5)
|
||||||
|
expect(root.children[0].type).toBe(NodeTypes.ELEMENT)
|
||||||
|
expect(root.children[1]).toMatchObject({
|
||||||
|
type: NodeTypes.COMPOUND_EXPRESSION,
|
||||||
|
children: [
|
||||||
|
{ type: NodeTypes.INTERPOLATION, content: { content: `foo` } },
|
||||||
|
` + `,
|
||||||
|
{ type: NodeTypes.TEXT, content: ` bar ` },
|
||||||
|
` + `,
|
||||||
|
{ type: NodeTypes.INTERPOLATION, content: { content: `baz` } }
|
||||||
|
]
|
||||||
|
})
|
||||||
|
expect(root.children[2].type).toBe(NodeTypes.ELEMENT)
|
||||||
|
expect(root.children[3]).toMatchObject({
|
||||||
|
type: NodeTypes.COMPOUND_EXPRESSION,
|
||||||
|
children: [
|
||||||
|
{ type: NodeTypes.INTERPOLATION, content: { content: `foo` } },
|
||||||
|
` + `,
|
||||||
|
{ type: NodeTypes.TEXT, content: ` bar ` },
|
||||||
|
` + `,
|
||||||
|
{ type: NodeTypes.INTERPOLATION, content: { content: `baz` } }
|
||||||
|
]
|
||||||
|
})
|
||||||
|
expect(root.children[4].type).toBe(NodeTypes.ELEMENT)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('with prefixIdentifiers: true', () => {
|
||||||
|
const root = transformWithTextOpt(`{{ foo }} bar {{ baz + qux }}`, {
|
||||||
|
prefixIdentifiers: true
|
||||||
|
})
|
||||||
|
expect(root.children.length).toBe(1)
|
||||||
|
expect(root.children[0]).toMatchObject({
|
||||||
|
type: NodeTypes.COMPOUND_EXPRESSION,
|
||||||
|
children: [
|
||||||
|
{ type: NodeTypes.INTERPOLATION, content: { content: `_ctx.foo` } },
|
||||||
|
` + `,
|
||||||
|
{ type: NodeTypes.TEXT, content: ` bar ` },
|
||||||
|
` + `,
|
||||||
|
{
|
||||||
|
type: NodeTypes.INTERPOLATION,
|
||||||
|
content: {
|
||||||
|
type: NodeTypes.COMPOUND_EXPRESSION,
|
||||||
|
children: [{ content: `_ctx.baz` }, ` + `, { content: `_ctx.qux` }]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
@ -64,6 +64,7 @@ export type ExpressionNode = SimpleExpressionNode | CompoundExpressionNode
|
|||||||
export type ChildNode =
|
export type ChildNode =
|
||||||
| ElementNode
|
| ElementNode
|
||||||
| InterpolationNode
|
| InterpolationNode
|
||||||
|
| CompoundExpressionNode
|
||||||
| TextNode
|
| TextNode
|
||||||
| CommentNode
|
| CommentNode
|
||||||
| IfNode
|
| IfNode
|
||||||
@ -130,7 +131,7 @@ export interface InterpolationNode extends Node {
|
|||||||
// always dynamic
|
// always dynamic
|
||||||
export interface CompoundExpressionNode extends Node {
|
export interface CompoundExpressionNode extends Node {
|
||||||
type: NodeTypes.COMPOUND_EXPRESSION
|
type: NodeTypes.COMPOUND_EXPRESSION
|
||||||
children: (SimpleExpressionNode | string)[]
|
children: (SimpleExpressionNode | InterpolationNode | TextNode | string)[]
|
||||||
// an expression parsed as the params of a function will track
|
// an expression parsed as the params of a function will track
|
||||||
// the identifiers declared inside the function body.
|
// the identifiers declared inside the function body.
|
||||||
identifiers?: string[]
|
identifiers?: string[]
|
||||||
|
@ -283,6 +283,7 @@ function genChildren(
|
|||||||
(allowSingle ||
|
(allowSingle ||
|
||||||
type === NodeTypes.TEXT ||
|
type === NodeTypes.TEXT ||
|
||||||
type === NodeTypes.INTERPOLATION ||
|
type === NodeTypes.INTERPOLATION ||
|
||||||
|
type === NodeTypes.COMPOUND_EXPRESSION ||
|
||||||
(type === NodeTypes.ELEMENT &&
|
(type === NodeTypes.ELEMENT &&
|
||||||
(child as ElementNode).tagType === ElementTypes.SLOT))
|
(child as ElementNode).tagType === ElementTypes.SLOT))
|
||||||
) {
|
) {
|
||||||
@ -423,7 +424,7 @@ function genCompoundExpression(
|
|||||||
if (isString(child)) {
|
if (isString(child)) {
|
||||||
context.push(child)
|
context.push(child)
|
||||||
} else {
|
} else {
|
||||||
genExpression(child, context)
|
genNode(child, context)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -13,6 +13,7 @@ import { transformOn } from './transforms/vOn'
|
|||||||
import { transformBind } from './transforms/vBind'
|
import { transformBind } from './transforms/vBind'
|
||||||
import { defaultOnError, createCompilerError, ErrorCodes } from './errors'
|
import { defaultOnError, createCompilerError, ErrorCodes } from './errors'
|
||||||
import { trackSlotScopes } from './transforms/vSlot'
|
import { trackSlotScopes } from './transforms/vSlot'
|
||||||
|
import { optimizeText } from './transforms/optimizeText'
|
||||||
|
|
||||||
export type CompilerOptions = ParserOptions & TransformOptions & CodegenOptions
|
export type CompilerOptions = ParserOptions & TransformOptions & CodegenOptions
|
||||||
|
|
||||||
@ -45,6 +46,7 @@ export function baseCompile(
|
|||||||
transformIf,
|
transformIf,
|
||||||
transformFor,
|
transformFor,
|
||||||
...(prefixIdentifiers ? [transformExpression, trackSlotScopes] : []),
|
...(prefixIdentifiers ? [transformExpression, trackSlotScopes] : []),
|
||||||
|
optimizeText,
|
||||||
transformStyle,
|
transformStyle,
|
||||||
transformSlotOutlet,
|
transformSlotOutlet,
|
||||||
transformElement,
|
transformElement,
|
||||||
|
@ -20,7 +20,7 @@ import { TO_STRING, COMMENT, CREATE_VNODE } from './runtimeConstants'
|
|||||||
// Transforms that operate directly on a ChildNode. NodeTransforms may mutate,
|
// Transforms that operate directly on a ChildNode. NodeTransforms may mutate,
|
||||||
// replace or remove the node being processed.
|
// replace or remove the node being processed.
|
||||||
export type NodeTransform = (
|
export type NodeTransform = (
|
||||||
node: ChildNode,
|
node: RootNode | ChildNode,
|
||||||
context: TransformContext
|
context: TransformContext
|
||||||
) => void | (() => void) | (() => void)[]
|
) => void | (() => void) | (() => void)[]
|
||||||
|
|
||||||
@ -56,9 +56,9 @@ export interface TransformContext extends Required<TransformOptions> {
|
|||||||
statements: Set<string>
|
statements: Set<string>
|
||||||
hoists: JSChildNode[]
|
hoists: JSChildNode[]
|
||||||
identifiers: { [name: string]: number | undefined }
|
identifiers: { [name: string]: number | undefined }
|
||||||
parent: ParentNode
|
parent: ParentNode | null
|
||||||
childIndex: number
|
childIndex: number
|
||||||
currentNode: ChildNode | null
|
currentNode: RootNode | ChildNode | null
|
||||||
helper(name: string): string
|
helper(name: string): string
|
||||||
replaceNode(node: ChildNode): void
|
replaceNode(node: ChildNode): void
|
||||||
removeNode(node?: ChildNode): void
|
removeNode(node?: ChildNode): void
|
||||||
@ -87,22 +87,30 @@ function createTransformContext(
|
|||||||
nodeTransforms,
|
nodeTransforms,
|
||||||
directiveTransforms,
|
directiveTransforms,
|
||||||
onError,
|
onError,
|
||||||
parent: root,
|
parent: null,
|
||||||
|
currentNode: root,
|
||||||
childIndex: 0,
|
childIndex: 0,
|
||||||
currentNode: null,
|
|
||||||
helper(name) {
|
helper(name) {
|
||||||
context.imports.add(name)
|
context.imports.add(name)
|
||||||
return prefixIdentifiers ? name : `_${name}`
|
return prefixIdentifiers ? name : `_${name}`
|
||||||
},
|
},
|
||||||
replaceNode(node) {
|
replaceNode(node) {
|
||||||
/* istanbul ignore if */
|
/* istanbul ignore if */
|
||||||
if (__DEV__ && !context.currentNode) {
|
if (__DEV__) {
|
||||||
throw new Error(`node being replaced is already removed.`)
|
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) {
|
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
|
const removalIndex = node
|
||||||
? list.indexOf(node as any)
|
? list.indexOf(node as any)
|
||||||
: context.currentNode
|
: context.currentNode
|
||||||
@ -123,7 +131,7 @@ function createTransformContext(
|
|||||||
context.onNodeRemoved()
|
context.onNodeRemoved()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
context.parent.children.splice(removalIndex, 1)
|
context.parent!.children.splice(removalIndex, 1)
|
||||||
},
|
},
|
||||||
onNodeRemoved: () => {},
|
onNodeRemoved: () => {},
|
||||||
addIdentifiers(exp) {
|
addIdentifiers(exp) {
|
||||||
@ -172,7 +180,7 @@ function createTransformContext(
|
|||||||
|
|
||||||
export function transform(root: RootNode, options: TransformOptions) {
|
export function transform(root: RootNode, options: TransformOptions) {
|
||||||
const context = createTransformContext(root, options)
|
const context = createTransformContext(root, options)
|
||||||
traverseChildren(root, context)
|
traverseNode(root, context)
|
||||||
root.imports = [...context.imports]
|
root.imports = [...context.imports]
|
||||||
root.statements = [...context.statements]
|
root.statements = [...context.statements]
|
||||||
root.hoists = context.hoists
|
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
|
// apply transform plugins
|
||||||
const { nodeTransforms } = context
|
const { nodeTransforms } = context
|
||||||
const exitFns = []
|
const exitFns = []
|
||||||
@ -240,6 +251,7 @@ export function traverseNode(node: ChildNode, context: TransformContext) {
|
|||||||
break
|
break
|
||||||
case NodeTypes.FOR:
|
case NodeTypes.FOR:
|
||||||
case NodeTypes.ELEMENT:
|
case NodeTypes.ELEMENT:
|
||||||
|
case NodeTypes.ROOT:
|
||||||
traverseChildren(node, context)
|
traverseChildren(node, context)
|
||||||
break
|
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 {
|
} else {
|
||||||
// locate the adjacent v-if
|
// locate the adjacent v-if
|
||||||
const siblings = context.parent.children
|
const siblings = context.parent!.children
|
||||||
const comments = []
|
const comments = []
|
||||||
let i = siblings.indexOf(node as any)
|
let i = siblings.indexOf(node as any)
|
||||||
while (i-- >= -1) {
|
while (i-- >= -1) {
|
||||||
|
Loading…
Reference in New Issue
Block a user