]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
fix(compiler): avoid hoisting components and directive calls
authorEvan You <yyx990803@gmail.com>
Fri, 4 Oct 2019 18:34:26 +0000 (14:34 -0400)
committerEvan You <yyx990803@gmail.com>
Fri, 4 Oct 2019 18:34:26 +0000 (14:34 -0400)
packages/compiler-core/__tests__/__snapshots__/codegen.spec.ts.snap
packages/compiler-core/__tests__/__snapshots__/compile.spec.ts.snap
packages/compiler-core/__tests__/transforms/__snapshots__/vSlot.spec.ts.snap
packages/compiler-core/src/codegen.ts
packages/compiler-core/src/transforms/hoistStatic.ts

index fa71bf319b3aa98bdbb625bee530f524368b2675..ec0e3e8b586029b90fe0f0d3f9022ed74d7ee2c4 100644 (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() {
index d9ade929bac76119fd196c988bde9d352cbb3b00..1aafaf9ea3ce48ef8d807b2957a3ca0aeb3b539e 100644 (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) => {
index 8bbb14f28eb7a5719bfb17c8e347394ce6a8576c..2532d053fdc6bbd7fc498ab8b58bb3fc9570967d 100644 (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)]
index dfde1305e898f70f12727db8882927b1536bbb8a..9e4e866262e8fc57c40e5bb02fa7091ae682cc10 100644 (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++
index b783f9456352637b22af1d6f21a824f86d3534f1..a4371fdb9d6599fc7de37172b3773e8c9a3a041f 100644 (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