diff --git a/packages/compiler-core/__tests__/__snapshots__/codegen.spec.ts.snap b/packages/compiler-core/__tests__/__snapshots__/codegen.spec.ts.snap index fa71bf31..ec0e3e8b 100644 --- a/packages/compiler-core/__tests__/__snapshots__/codegen.spec.ts.snap +++ b/packages/compiler-core/__tests__/__snapshots__/codegen.spec.ts.snap @@ -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() { diff --git a/packages/compiler-core/__tests__/__snapshots__/compile.spec.ts.snap b/packages/compiler-core/__tests__/__snapshots__/compile.spec.ts.snap index d9ade929..1aafaf9e 100644 --- a/packages/compiler-core/__tests__/__snapshots__/compile.spec.ts.snap +++ b/packages/compiler-core/__tests__/__snapshots__/compile.spec.ts.snap @@ -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) => { diff --git a/packages/compiler-core/__tests__/transforms/__snapshots__/vSlot.spec.ts.snap b/packages/compiler-core/__tests__/transforms/__snapshots__/vSlot.spec.ts.snap index 8bbb14f2..2532d053 100644 --- a/packages/compiler-core/__tests__/transforms/__snapshots__/vSlot.spec.ts.snap +++ b/packages/compiler-core/__tests__/transforms/__snapshots__/vSlot.spec.ts.snap @@ -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)] diff --git a/packages/compiler-core/src/codegen.ts b/packages/compiler-core/src/codegen.ts index dfde1305..9e4e8662 100644 --- a/packages/compiler-core/src/codegen.ts +++ b/packages/compiler-core/src/codegen.ts @@ -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++ diff --git a/packages/compiler-core/src/transforms/hoistStatic.ts b/packages/compiler-core/src/transforms/hoistStatic.ts index b783f945..a4371fdb 100644 --- a/packages/compiler-core/src/transforms/hoistStatic.ts +++ b/packages/compiler-core/src/transforms/hoistStatic.ts @@ -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()) + walk(root.children, context, new Map()) } function walk( children: TemplateChildNode[], context: TransformContext, - knownStaticNodes: Set + resultCache: Map ) { 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 + resultCache: Map ): 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