fix(compiler): avoid hoisting components and directive calls

This commit is contained in:
Evan You 2019-10-04 14:34:26 -04:00
parent 5047bc8dbe
commit 277651ce89
5 changed files with 66 additions and 38 deletions

View File

@ -89,7 +89,8 @@ return function render() {
`;
exports[`compiler: codegen hoists 1`] = `
"const _hoisted_1 = hello
"
const _hoisted_1 = hello
const _hoisted_2 = { id: \\"foo\\" }
return function render() {

View File

@ -35,7 +35,7 @@ return function render() {
class: _ctx.bar.baz
}, [
toString(_ctx.world.burn()),
(openBlock(), (_ctx.ok)
(openBlock(), _ctx.ok)
? createBlock(\\"div\\", { key: 0 }, \\"yes\\")
: createBlock(Fragment, { key: 1 }, [\\"no\\"])),
(openBlock(), createBlock(Fragment, null, renderList(_ctx.list, (value, index) => {
@ -57,7 +57,7 @@ export default function render() {
class: _ctx.bar.baz
}, [
_toString(_ctx.world.burn()),
(openBlock(), (_ctx.ok)
(openBlock(), _ctx.ok)
? createBlock(\\"div\\", { key: 0 }, \\"yes\\")
: createBlock(Fragment, { key: 1 }, [\\"no\\"])),
(openBlock(), createBlock(Fragment, null, renderList(_ctx.list, (value, index) => {

View File

@ -71,7 +71,7 @@ return function render() {
const _component_Comp = resolveComponent(\\"Comp\\")
return (openBlock(), createBlock(_component_Comp, null, createSlots({ _compiled: true }, [
(_ctx.ok)
_ctx.ok)
? {
name: \\"one\\",
fn: (props) => [toString(props)]

View File

@ -197,6 +197,7 @@ export function generate(
}
}
genHoists(ast.hoists, context)
context.newline()
push(`return `)
} else {
// generate import statements for helpers
@ -204,6 +205,7 @@ export function generate(
push(`import { ${ast.imports.join(', ')} } from "vue"\n`)
}
genHoists(ast.hoists, context)
context.newline()
push(`export default `)
}
@ -258,12 +260,15 @@ export function generate(
}
function genHoists(hoists: JSChildNode[], context: CodegenContext) {
if (!hoists.length) {
return
}
context.newline()
hoists.forEach((exp, i) => {
context.push(`const _hoisted_${i + 1} = `)
genNode(exp, context)
context.newline()
})
context.newline()
}
function isText(n: string | CodegenNode) {
@ -316,7 +321,11 @@ function genNodeList(
}
}
function genNode(node: CodegenNode, context: CodegenContext) {
function genNode(node: CodegenNode | string, context: CodegenContext) {
if (isString(node)) {
context.push(node)
return
}
switch (node.type) {
case NodeTypes.ELEMENT:
case NodeTypes.IF:
@ -517,12 +526,13 @@ function genConditionalExpression(
const { test, consequent, alternate } = node
const { push, indent, deindent, newline } = context
if (test.type === NodeTypes.SIMPLE_EXPRESSION) {
const needsQuote = !isSimpleIdentifier(test.content)
needsQuote && push(`(`)
const needsParens = !isSimpleIdentifier(test.content)
genExpression(test, context)
needsQuote && push(`)`)
needsParens && push(`)`)
} else {
push(`(`)
genCompoundExpression(test, context)
push(`)`)
}
indent()
context.indentLevel++

View File

@ -3,79 +3,96 @@ import {
NodeTypes,
TemplateChildNode,
CallExpression,
ElementNode
ElementNode,
ElementTypes
} from '../ast'
import { TransformContext } from '../transform'
import { CREATE_VNODE } from '../runtimeConstants'
import { APPLY_DIRECTIVES } from '../runtimeConstants'
import { PropsExpression } from './transformElement'
import { PatchFlags } from '@vue/runtime-dom'
export function hoistStatic(root: RootNode, context: TransformContext) {
walk(root.children, context, new Set<TemplateChildNode>())
walk(root.children, context, new Map<TemplateChildNode, boolean>())
}
function walk(
children: TemplateChildNode[],
context: TransformContext,
knownStaticNodes: Set<TemplateChildNode>
resultCache: Map<TemplateChildNode, boolean>
) {
for (let i = 0; i < children.length; i++) {
const child = children[i]
if (child.type === NodeTypes.ELEMENT) {
if (isStaticNode(child, knownStaticNodes)) {
// only plain elements are eligible for hoisting.
if (
child.type === NodeTypes.ELEMENT &&
child.tagType === ElementTypes.ELEMENT
) {
if (isStaticNode(child, resultCache)) {
// whole tree is static
child.codegenNode = context.hoist(child.codegenNode!)
continue
} else if (!getPatchFlag(child)) {
// has dynamic children, but self props are static, hoist props instead
const props = (child.codegenNode as CallExpression).arguments[1] as
| PropsExpression
| `null`
if (props !== `null`) {
;(child.codegenNode as CallExpression).arguments[1] = context.hoist(
props
)
} else {
// node may contain dynamic children, but its props may be eligible for
// hoisting.
const flag = getPatchFlag(child)
if (!flag || flag === PatchFlags.NEED_PATCH) {
let codegenNode = child.codegenNode as CallExpression
if (codegenNode.callee.includes(APPLY_DIRECTIVES)) {
codegenNode = codegenNode.arguments[0] as CallExpression
}
const props = codegenNode.arguments[1] as
| PropsExpression
| `null`
| undefined
if (props && props !== `null`) {
;(child.codegenNode as CallExpression).arguments[1] = context.hoist(
props
)
}
}
}
}
if (child.type === NodeTypes.ELEMENT || child.type === NodeTypes.FOR) {
walk(child.children, context, knownStaticNodes)
walk(child.children, context, resultCache)
} else if (child.type === NodeTypes.IF) {
for (let i = 0; i < child.branches.length; i++) {
walk(child.branches[i].children, context, knownStaticNodes)
walk(child.branches[i].children, context, resultCache)
}
}
}
}
function getPatchFlag(node: ElementNode): number | undefined {
const codegenNode = node.codegenNode as CallExpression
if (
// callee is createVNode (i.e. no runtime directives)
codegenNode.callee.includes(CREATE_VNODE)
) {
const flag = codegenNode.arguments[3]
return flag ? parseInt(flag as string, 10) : undefined
let codegenNode = node.codegenNode as CallExpression
if (codegenNode.callee.includes(APPLY_DIRECTIVES)) {
codegenNode = codegenNode.arguments[0] as CallExpression
}
const flag = codegenNode.arguments[3]
return flag ? parseInt(flag as string, 10) : undefined
}
function isStaticNode(
node: TemplateChildNode,
knownStaticNodes: Set<TemplateChildNode>
resultCache: Map<TemplateChildNode, boolean>
): boolean {
switch (node.type) {
case NodeTypes.ELEMENT:
if (knownStaticNodes.has(node)) {
return true
if (node.tagType !== ElementTypes.ELEMENT) {
return false
}
if (resultCache.has(node)) {
return resultCache.get(node) as boolean
}
const flag = getPatchFlag(node)
if (!flag) {
// element self is static. check its children.
for (let i = 0; i < node.children.length; i++) {
if (!isStaticNode(node.children[i], knownStaticNodes)) {
if (!isStaticNode(node.children[i], resultCache)) {
resultCache.set(node, false)
return false
}
}
knownStaticNodes.add(node)
resultCache.set(node, true)
return true
} else {
return false