feat(compiler-dom/runtime-dom): stringify eligible static trees

This commit is contained in:
Evan You
2020-02-12 11:56:42 -05:00
parent e861c6da90
commit 27913e661a
13 changed files with 304 additions and 87 deletions

View File

@@ -18,6 +18,7 @@ import { transformModel } from './transforms/vModel'
import { transformOn } from './transforms/vOn'
import { transformShow } from './transforms/vShow'
import { warnTransitionChildren } from './transforms/warnTransitionChildren'
import { stringifyStatic } from './stringifyStatic'
export const parserOptions = __BROWSER__
? parserOptionsMinimal
@@ -41,17 +42,16 @@ export function compile(
template: string,
options: CompilerOptions = {}
): CodegenResult {
const result = baseCompile(template, {
return baseCompile(template, {
...parserOptions,
...options,
nodeTransforms: [...DOMNodeTransforms, ...(options.nodeTransforms || [])],
directiveTransforms: {
...DOMDirectiveTransforms,
...(options.directiveTransforms || {})
}
},
transformHoist: __BROWSER__ ? null : stringifyStatic
})
// debugger
return result
}
export function parse(template: string, options: ParserOptions = {}): RootNode {

View File

@@ -0,0 +1,116 @@
import {
NodeTypes,
ElementNode,
TransformContext,
TemplateChildNode,
SimpleExpressionNode,
createCallExpression,
HoistTransform,
CREATE_STATIC
} from '@vue/compiler-core'
import { isVoidTag, isString, isSymbol, escapeHtml } from '@vue/shared'
// Turn eligible hoisted static trees into stringied static nodes, e.g.
// const _hoisted_1 = createStaticVNode(`<div class="foo">bar</div>`)
export const stringifyStatic: HoistTransform = (node, context) => {
if (shouldOptimize(node)) {
return createCallExpression(context.helper(CREATE_STATIC), [
JSON.stringify(stringifyElement(node, context))
])
} else {
return node.codegenNode!
}
}
// Opt-in heuristics based on:
// 1. number of elements with attributes > 5.
// 2. OR: number of total nodes > 20
// For some simple trees, the performance can actually be worse.
// it is only worth it when the tree is complex enough
// (e.g. big piece of static content)
function shouldOptimize(node: ElementNode): boolean {
let bindingThreshold = 5
let nodeThreshold = 20
function walk(node: ElementNode) {
for (let i = 0; i < node.children.length; i++) {
if (--nodeThreshold === 0) {
return true
}
const child = node.children[i]
if (child.type === NodeTypes.ELEMENT) {
if (child.props.length > 0 && --bindingThreshold === 0) {
return true
}
if (walk(child)) {
return true
}
}
}
return false
}
return walk(node)
}
function stringifyElement(
node: ElementNode,
context: TransformContext
): string {
let res = `<${node.tag}`
for (let i = 0; i < node.props.length; i++) {
const p = node.props[i]
if (p.type === NodeTypes.ATTRIBUTE) {
res += ` ${p.name}`
if (p.value) {
res += `="${p.value.content}"`
}
} else if (p.type === NodeTypes.DIRECTIVE && p.name === 'bind') {
// constant v-bind, e.g. :foo="1"
// TODO
}
}
if (context.scopeId) {
res += ` ${context.scopeId}`
}
res += `>`
for (let i = 0; i < node.children.length; i++) {
res += stringifyNode(node.children[i], context)
}
if (!isVoidTag(node.tag)) {
res += `</${node.tag}>`
}
return res
}
function stringifyNode(
node: string | TemplateChildNode,
context: TransformContext
): string {
if (isString(node)) {
return node
}
if (isSymbol(node)) {
return ``
}
switch (node.type) {
case NodeTypes.ELEMENT:
return stringifyElement(node, context)
case NodeTypes.TEXT:
return escapeHtml(node.content)
case NodeTypes.COMMENT:
return `<!--${escapeHtml(node.content)}-->`
case NodeTypes.INTERPOLATION:
// constants
// TODO check eval
return (node.content as SimpleExpressionNode).content
case NodeTypes.COMPOUND_EXPRESSION:
// TODO proper handling
return node.children.map((c: any) => stringifyNode(c, context)).join('')
case NodeTypes.TEXT_CALL:
return stringifyNode(node.content, context)
default:
// static trees will not contain if/for nodes
return ''
}
}