fix(compiler): avoid hoisting components and directive calls
This commit is contained in:
parent
5047bc8dbe
commit
277651ce89
@ -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() {
|
||||
|
@ -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) => {
|
||||
|
@ -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)]
|
||||
|
@ -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++
|
||||
|
@ -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
|
||||
} 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`
|
||||
if (props !== `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)
|
||||
) {
|
||||
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
|
||||
|
Loading…
Reference in New Issue
Block a user