Skip to content

Commit

Permalink
perf: 系统性地合并 html += <Literal> 语句
Browse files Browse the repository at this point in the history
  • Loading branch information
harttle committed Dec 15, 2020
1 parent 1c20c7e commit 4f145b3
Show file tree
Hide file tree
Showing 4 changed files with 163 additions and 1 deletion.
96 changes: 96 additions & 0 deletions src/ast/syntax-tree-walker.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
import { Statement, SyntaxKind, Expression } from '../ast/syntax-node'
import { assertNever } from '../utils/lang'

export function * walk (node: Expression | Statement): Iterable<Expression | Statement> {
yield node
switch (node.kind) {
case SyntaxKind.Literal:
case SyntaxKind.JSONStringify:
case SyntaxKind.UnaryExpression:
yield * walk(node.value)
break
case SyntaxKind.Identifier:
case SyntaxKind.CreateComponentInstance:
case SyntaxKind.Null:
case SyntaxKind.ImportHelper:
break
case SyntaxKind.ArrayIncludes:
yield * walk(node.arr)
yield * walk(node.item)
break
case SyntaxKind.MapAssign:
yield * walk(node.dest)
for (const src of node.srcs) yield * walk(src)
break
case SyntaxKind.RegexpReplace:
yield * walk(node.original)
yield * walk(node.replacement)
break
case SyntaxKind.ConditionalExpression:
yield * walk(node.cond)
yield * walk(node.falseValue)
yield * walk(node.trueValue)
break
case SyntaxKind.EncodeURIComponent:
yield * walk(node.str)
break
case SyntaxKind.ComputedCall:
// TODO static computed call
yield * walk(node.name)
break
case SyntaxKind.FilterCall:
for (const arg of node.args) yield * walk(arg)
break
case SyntaxKind.FunctionDefinition:
for (const arg of node.args) yield * walk(arg)
for (const stmt of node.body) yield * walk(stmt)
break
case SyntaxKind.FunctionCall:
for (const arg of node.args) yield * walk(arg)
yield * walk(node.fn)
break
case SyntaxKind.NewExpression:
for (const arg of node.args) yield * walk(arg)
yield * walk(node.name)
break
case SyntaxKind.ArrayLiteral:
for (const [expr] of node.items) yield * walk(expr)
break
case SyntaxKind.MapLiteral:
for (const [key, val] of node.items) {
yield * walk(key)
yield * walk(val)
}
break
case SyntaxKind.ComponentRendererReference:
yield * walk(node.ref)
break
case SyntaxKind.ReturnStatement:
case SyntaxKind.ExpressionStatement:
yield * walk(node.expression)
break
case SyntaxKind.AssignmentStatement:
case SyntaxKind.BinaryExpression:
yield * walk(node.lhs)
yield * walk(node.rhs)
break
case SyntaxKind.VariableDefinition:
if (node.initial) yield * walk(node.initial)
break
case SyntaxKind.If:
case SyntaxKind.ElseIf:
yield * walk(node.cond)
for (const stmt of node.body) yield * walk(stmt)
break
case SyntaxKind.Else:
for (const stmt of node.body) yield * walk(stmt)
break
case SyntaxKind.Foreach:
yield * walk(node.key)
yield * walk(node.value)
yield * walk(node.iterable)
for (const stmt of node.body) yield * walk(stmt)
break
default: assertNever(node)
}
}
5 changes: 4 additions & 1 deletion src/compilers/renderer-compiler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { RenderOptions } from './renderer-options'
import { FunctionDefinition, ComputedCall, Foreach, FunctionCall, MapLiteral, If, CreateComponentInstance, ImportHelper } from '../ast/syntax-node'
import { STATMENT, NEW, BINARY, ASSIGN, DEF, RETURN, createDefaultValue, L, I } from '../ast/syntax-util'
import { IDGenerator } from '../utils/id-generator'
import { mergeLiteralAdd } from '../optimizers/merge-literal-add'

/**
* 每个 ComponentClass 对应一个 Render 函数,由 RendererCompiler 生成。
Expand All @@ -20,9 +21,11 @@ export class RendererCompiler {
*/
public compileToRenderer (componentInfo: ComponentInfo) {
const args = [DEF('data'), DEF('noDataOutput'), DEF('parentCtx'), DEF('tagName', L('div')), DEF('slots')]
return new FunctionDefinition(this.options.functionName || '', args,
const fn = new FunctionDefinition(this.options.functionName || '', args,
this.compileComponentRendererBody(componentInfo)
)
mergeLiteralAdd(fn)
return fn
}

private compileComponentRendererBody (info: ComponentInfo) {
Expand Down
39 changes: 39 additions & 0 deletions src/optimizers/merge-literal-add.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { Expression, Literal, Statement, Identifier, Block, SyntaxNode } from '../ast/syntax-node'
import { isLiteral, isIdentifier, isBlock, isBinaryExpression, isExpressionStatement } from '../ast/syntax-util'
import { walk } from '../ast/syntax-tree-walker'

type HTMLAddEqualLiteral = Statement & { expression: { lhs: Identifier, op: '+=', rhs: Literal } }

export function mergeLiteralAdd (node: Expression | Statement): void {
for (const descendant of walk(node)) {
if (isBlock(descendant)) doMergeLiteralAdd(descendant)
}
}

function doMergeLiteralAdd (node: Block) {
let prevHTMLAddEqualLiteral: HTMLAddEqualLiteral | null = null
const filteredBody = []
for (const child of node.body) {
if (isHTMLAddEqualLiteral(child)) {
if (prevHTMLAddEqualLiteral !== null) {
prevHTMLAddEqualLiteral.expression.rhs.value += child.expression.rhs.value
continue
}
prevHTMLAddEqualLiteral = child
} else {
prevHTMLAddEqualLiteral = null
}
filteredBody.push(child)
}
node.body = filteredBody
}

function isHTMLAddEqualLiteral (statement: SyntaxNode): statement is HTMLAddEqualLiteral {
if (!isExpressionStatement(statement)) return false

const expr = statement.expression
return isBinaryExpression(expr) &&
isIdentifier(expr.lhs) && expr.lhs.name === 'html' &&
expr.op === '+=' &&
isLiteral(expr.rhs)
}
24 changes: 24 additions & 0 deletions test/unit/optimizers/merge-literal-add.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { RETURN, BINARY, STATMENT, L, I } from '../../../src/ast/syntax-util'
import { mergeLiteralAdd } from '../../../src/optimizers/merge-literal-add'
import { FunctionDefinition } from '../../../src/ast/syntax-node'

describe('optimizers/merge-literal-add', () => {
it('should merge to successive html+=', () => {
const fn = new FunctionDefinition('', [], [
STATMENT(BINARY(I('html'), '+=', L('foo'))),
STATMENT(BINARY(I('html'), '+=', L('bar')))
])
mergeLiteralAdd(fn)
expect(fn.body).toHaveLength(1)
expect(fn.body[0]).toHaveProperty('expression.rhs.value', 'foobar')
})
it('should not merge if not successive', () => {
const fn = new FunctionDefinition('', [], [
STATMENT(BINARY(I('html'), '+=', L('foo'))),
RETURN(I('html')),
STATMENT(BINARY(I('html'), '+=', L('bar')))
])
mergeLiteralAdd(fn)
expect(fn.body).toHaveLength(3)
})
})

0 comments on commit 4f145b3

Please sign in to comment.