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`] = `
|
exports[`compiler: codegen hoists 1`] = `
|
||||||
"const _hoisted_1 = hello
|
"
|
||||||
|
const _hoisted_1 = hello
|
||||||
const _hoisted_2 = { id: \\"foo\\" }
|
const _hoisted_2 = { id: \\"foo\\" }
|
||||||
|
|
||||||
return function render() {
|
return function render() {
|
||||||
|
@ -35,7 +35,7 @@ return function render() {
|
|||||||
class: _ctx.bar.baz
|
class: _ctx.bar.baz
|
||||||
}, [
|
}, [
|
||||||
toString(_ctx.world.burn()),
|
toString(_ctx.world.burn()),
|
||||||
(openBlock(), (_ctx.ok)
|
(openBlock(), _ctx.ok)
|
||||||
? createBlock(\\"div\\", { key: 0 }, \\"yes\\")
|
? createBlock(\\"div\\", { key: 0 }, \\"yes\\")
|
||||||
: createBlock(Fragment, { key: 1 }, [\\"no\\"])),
|
: createBlock(Fragment, { key: 1 }, [\\"no\\"])),
|
||||||
(openBlock(), createBlock(Fragment, null, renderList(_ctx.list, (value, index) => {
|
(openBlock(), createBlock(Fragment, null, renderList(_ctx.list, (value, index) => {
|
||||||
@ -57,7 +57,7 @@ export default function render() {
|
|||||||
class: _ctx.bar.baz
|
class: _ctx.bar.baz
|
||||||
}, [
|
}, [
|
||||||
_toString(_ctx.world.burn()),
|
_toString(_ctx.world.burn()),
|
||||||
(openBlock(), (_ctx.ok)
|
(openBlock(), _ctx.ok)
|
||||||
? createBlock(\\"div\\", { key: 0 }, \\"yes\\")
|
? createBlock(\\"div\\", { key: 0 }, \\"yes\\")
|
||||||
: createBlock(Fragment, { key: 1 }, [\\"no\\"])),
|
: createBlock(Fragment, { key: 1 }, [\\"no\\"])),
|
||||||
(openBlock(), createBlock(Fragment, null, renderList(_ctx.list, (value, index) => {
|
(openBlock(), createBlock(Fragment, null, renderList(_ctx.list, (value, index) => {
|
||||||
|
@ -71,7 +71,7 @@ return function render() {
|
|||||||
const _component_Comp = resolveComponent(\\"Comp\\")
|
const _component_Comp = resolveComponent(\\"Comp\\")
|
||||||
|
|
||||||
return (openBlock(), createBlock(_component_Comp, null, createSlots({ _compiled: true }, [
|
return (openBlock(), createBlock(_component_Comp, null, createSlots({ _compiled: true }, [
|
||||||
(_ctx.ok)
|
_ctx.ok)
|
||||||
? {
|
? {
|
||||||
name: \\"one\\",
|
name: \\"one\\",
|
||||||
fn: (props) => [toString(props)]
|
fn: (props) => [toString(props)]
|
||||||
|
@ -197,6 +197,7 @@ export function generate(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
genHoists(ast.hoists, context)
|
genHoists(ast.hoists, context)
|
||||||
|
context.newline()
|
||||||
push(`return `)
|
push(`return `)
|
||||||
} else {
|
} else {
|
||||||
// generate import statements for helpers
|
// generate import statements for helpers
|
||||||
@ -204,6 +205,7 @@ export function generate(
|
|||||||
push(`import { ${ast.imports.join(', ')} } from "vue"\n`)
|
push(`import { ${ast.imports.join(', ')} } from "vue"\n`)
|
||||||
}
|
}
|
||||||
genHoists(ast.hoists, context)
|
genHoists(ast.hoists, context)
|
||||||
|
context.newline()
|
||||||
push(`export default `)
|
push(`export default `)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -258,12 +260,15 @@ export function generate(
|
|||||||
}
|
}
|
||||||
|
|
||||||
function genHoists(hoists: JSChildNode[], context: CodegenContext) {
|
function genHoists(hoists: JSChildNode[], context: CodegenContext) {
|
||||||
|
if (!hoists.length) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
context.newline()
|
||||||
hoists.forEach((exp, i) => {
|
hoists.forEach((exp, i) => {
|
||||||
context.push(`const _hoisted_${i + 1} = `)
|
context.push(`const _hoisted_${i + 1} = `)
|
||||||
genNode(exp, context)
|
genNode(exp, context)
|
||||||
context.newline()
|
context.newline()
|
||||||
})
|
})
|
||||||
context.newline()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function isText(n: string | CodegenNode) {
|
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) {
|
switch (node.type) {
|
||||||
case NodeTypes.ELEMENT:
|
case NodeTypes.ELEMENT:
|
||||||
case NodeTypes.IF:
|
case NodeTypes.IF:
|
||||||
@ -517,12 +526,13 @@ function genConditionalExpression(
|
|||||||
const { test, consequent, alternate } = node
|
const { test, consequent, alternate } = node
|
||||||
const { push, indent, deindent, newline } = context
|
const { push, indent, deindent, newline } = context
|
||||||
if (test.type === NodeTypes.SIMPLE_EXPRESSION) {
|
if (test.type === NodeTypes.SIMPLE_EXPRESSION) {
|
||||||
const needsQuote = !isSimpleIdentifier(test.content)
|
const needsParens = !isSimpleIdentifier(test.content)
|
||||||
needsQuote && push(`(`)
|
|
||||||
genExpression(test, context)
|
genExpression(test, context)
|
||||||
needsQuote && push(`)`)
|
needsParens && push(`)`)
|
||||||
} else {
|
} else {
|
||||||
|
push(`(`)
|
||||||
genCompoundExpression(test, context)
|
genCompoundExpression(test, context)
|
||||||
|
push(`)`)
|
||||||
}
|
}
|
||||||
indent()
|
indent()
|
||||||
context.indentLevel++
|
context.indentLevel++
|
||||||
|
@ -3,79 +3,96 @@ import {
|
|||||||
NodeTypes,
|
NodeTypes,
|
||||||
TemplateChildNode,
|
TemplateChildNode,
|
||||||
CallExpression,
|
CallExpression,
|
||||||
ElementNode
|
ElementNode,
|
||||||
|
ElementTypes
|
||||||
} from '../ast'
|
} from '../ast'
|
||||||
import { TransformContext } from '../transform'
|
import { TransformContext } from '../transform'
|
||||||
import { CREATE_VNODE } from '../runtimeConstants'
|
import { APPLY_DIRECTIVES } from '../runtimeConstants'
|
||||||
import { PropsExpression } from './transformElement'
|
import { PropsExpression } from './transformElement'
|
||||||
|
import { PatchFlags } from '@vue/runtime-dom'
|
||||||
|
|
||||||
export function hoistStatic(root: RootNode, context: TransformContext) {
|
export function hoistStatic(root: RootNode, context: TransformContext) {
|
||||||
walk(root.children, context, new Set<TemplateChildNode>())
|
walk(root.children, context, new Map<TemplateChildNode, boolean>())
|
||||||
}
|
}
|
||||||
|
|
||||||
function walk(
|
function walk(
|
||||||
children: TemplateChildNode[],
|
children: TemplateChildNode[],
|
||||||
context: TransformContext,
|
context: TransformContext,
|
||||||
knownStaticNodes: Set<TemplateChildNode>
|
resultCache: Map<TemplateChildNode, boolean>
|
||||||
) {
|
) {
|
||||||
for (let i = 0; i < children.length; i++) {
|
for (let i = 0; i < children.length; i++) {
|
||||||
const child = children[i]
|
const child = children[i]
|
||||||
if (child.type === NodeTypes.ELEMENT) {
|
// only plain elements are eligible for hoisting.
|
||||||
if (isStaticNode(child, knownStaticNodes)) {
|
if (
|
||||||
|
child.type === NodeTypes.ELEMENT &&
|
||||||
|
child.tagType === ElementTypes.ELEMENT
|
||||||
|
) {
|
||||||
|
if (isStaticNode(child, resultCache)) {
|
||||||
// whole tree is static
|
// whole tree is static
|
||||||
child.codegenNode = context.hoist(child.codegenNode!)
|
child.codegenNode = context.hoist(child.codegenNode!)
|
||||||
continue
|
continue
|
||||||
} else if (!getPatchFlag(child)) {
|
} else {
|
||||||
// has dynamic children, but self props are static, hoist props instead
|
// node may contain dynamic children, but its props may be eligible for
|
||||||
const props = (child.codegenNode as CallExpression).arguments[1] as
|
// 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
|
| PropsExpression
|
||||||
| `null`
|
| `null`
|
||||||
if (props !== `null`) {
|
| undefined
|
||||||
|
if (props && props !== `null`) {
|
||||||
;(child.codegenNode as CallExpression).arguments[1] = context.hoist(
|
;(child.codegenNode as CallExpression).arguments[1] = context.hoist(
|
||||||
props
|
props
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
if (child.type === NodeTypes.ELEMENT || child.type === NodeTypes.FOR) {
|
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) {
|
} else if (child.type === NodeTypes.IF) {
|
||||||
for (let i = 0; i < child.branches.length; i++) {
|
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 {
|
function getPatchFlag(node: ElementNode): number | undefined {
|
||||||
const codegenNode = node.codegenNode as CallExpression
|
let codegenNode = node.codegenNode as CallExpression
|
||||||
if (
|
if (codegenNode.callee.includes(APPLY_DIRECTIVES)) {
|
||||||
// callee is createVNode (i.e. no runtime directives)
|
codegenNode = codegenNode.arguments[0] as CallExpression
|
||||||
codegenNode.callee.includes(CREATE_VNODE)
|
}
|
||||||
) {
|
|
||||||
const flag = codegenNode.arguments[3]
|
const flag = codegenNode.arguments[3]
|
||||||
return flag ? parseInt(flag as string, 10) : undefined
|
return flag ? parseInt(flag as string, 10) : undefined
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function isStaticNode(
|
function isStaticNode(
|
||||||
node: TemplateChildNode,
|
node: TemplateChildNode,
|
||||||
knownStaticNodes: Set<TemplateChildNode>
|
resultCache: Map<TemplateChildNode, boolean>
|
||||||
): boolean {
|
): boolean {
|
||||||
switch (node.type) {
|
switch (node.type) {
|
||||||
case NodeTypes.ELEMENT:
|
case NodeTypes.ELEMENT:
|
||||||
if (knownStaticNodes.has(node)) {
|
if (node.tagType !== ElementTypes.ELEMENT) {
|
||||||
return true
|
return false
|
||||||
|
}
|
||||||
|
if (resultCache.has(node)) {
|
||||||
|
return resultCache.get(node) as boolean
|
||||||
}
|
}
|
||||||
const flag = getPatchFlag(node)
|
const flag = getPatchFlag(node)
|
||||||
if (!flag) {
|
if (!flag) {
|
||||||
// element self is static. check its children.
|
// element self is static. check its children.
|
||||||
for (let i = 0; i < node.children.length; i++) {
|
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
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
knownStaticNodes.add(node)
|
resultCache.set(node, true)
|
||||||
return true
|
return true
|
||||||
} else {
|
} else {
|
||||||
return false
|
return false
|
||||||
|
Loading…
Reference in New Issue
Block a user