]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
wip(compiler): tests for new stringification
authorEvan You <yyx990803@gmail.com>
Fri, 15 May 2020 19:50:42 +0000 (15:50 -0400)
committerEvan You <yyx990803@gmail.com>
Fri, 15 May 2020 19:50:42 +0000 (15:50 -0400)
packages/compiler-core/src/ast.ts
packages/compiler-core/src/codegen.ts
packages/compiler-core/src/transform.ts
packages/compiler-dom/__tests__/transforms/stringifyStatic.spec.ts
packages/compiler-dom/src/transforms/stringifyStatic.ts

index 3e843b8a25d7f31259bbdd8f2a994069efc00c1e..d4c11c07b9efb1be98e79ca1bccf976655bef37a 100644 (file)
@@ -103,7 +103,7 @@ export interface RootNode extends Node {
   helpers: symbol[]
   components: string[]
   directives: string[]
-  hoists: JSChildNode[]
+  hoists: (JSChildNode | null)[]
   imports: ImportItem[]
   cached: number
   temps: number
index 7982eedafcc83a0520ef5d0161f94fc1f458cda7..d7d8b8bfa37c7d7b04ad368c720efd600439f302 100644 (file)
@@ -434,7 +434,7 @@ function genAssets(
   }
 }
 
-function genHoists(hoists: JSChildNode[], context: CodegenContext) {
+function genHoists(hoists: (JSChildNode | null)[], context: CodegenContext) {
   if (!hoists.length) {
     return
   }
@@ -451,9 +451,11 @@ function genHoists(hoists: JSChildNode[], context: CodegenContext) {
   }
 
   hoists.forEach((exp, i) => {
-    push(`const _hoisted_${i + 1} = `)
-    genNode(exp, context)
-    newline()
+    if (exp) {
+      push(`const _hoisted_${i + 1} = `)
+      genNode(exp, context)
+      newline()
+    }
   })
 
   if (genScopeId) {
index fc03af4c27c1118118009fbba042d730e3841dc2..92961ef1aeef6b9de5e6abd18f6d40f9f88f2eae 100644 (file)
@@ -82,7 +82,7 @@ export interface TransformContext extends Required<TransformOptions> {
   helpers: Set<symbol>
   components: Set<string>
   directives: Set<string>
-  hoists: JSChildNode[]
+  hoists: (JSChildNode | null)[]
   imports: Set<ImportItem>
   temps: number
   cached: number
index cf1c7e0a6d802c8e32170c0e8d90d54bbc5c464c..7c7f985dfbeccf5bbc8b12f910d786c2e84430f3 100644 (file)
@@ -31,7 +31,7 @@ describe('stringify static html', () => {
     )
     expect(ast.hoists.length).toBe(1)
     // should be a normal vnode call
-    expect(ast.hoists[0].type).toBe(NodeTypes.VNODE_CALL)
+    expect(ast.hoists[0]!.type).toBe(NodeTypes.VNODE_CALL)
   })
 
   test('should work on eligible content (elements with binding > 5)', () => {
@@ -52,7 +52,8 @@ describe('stringify static html', () => {
             `<span class="foo"></span>`,
             StringifyThresholds.ELEMENT_WITH_BINDING_COUNT
           )}</div>`
-        )
+        ),
+        '1'
       ]
     })
   })
@@ -75,7 +76,36 @@ describe('stringify static html', () => {
             `<span></span>`,
             StringifyThresholds.NODE_COUNT
           )}</div>`
-        )
+        ),
+        '1'
+      ]
+    })
+  })
+
+  test('should work for multiple adjacent nodes', () => {
+    const { ast } = compileWithStringify(
+      `<div>${repeat(
+        `<span class="foo"/>`,
+        StringifyThresholds.ELEMENT_WITH_BINDING_COUNT
+      )}</div>`
+    )
+    // should have 5 hoisted nodes, but the other 4 should be null
+    expect(ast.hoists.length).toBe(5)
+    for (let i = 1; i < 5; i++) {
+      expect(ast.hoists[i]).toBe(null)
+    }
+    // should be optimized now
+    expect(ast.hoists[0]).toMatchObject({
+      type: NodeTypes.JS_CALL_EXPRESSION,
+      callee: CREATE_STATIC,
+      arguments: [
+        JSON.stringify(
+          repeat(
+            `<span class="foo"></span>`,
+            StringifyThresholds.ELEMENT_WITH_BINDING_COUNT
+          )
+        ),
+        '5'
       ]
     })
   })
@@ -98,7 +128,8 @@ describe('stringify static html', () => {
             `<span class="foo bar">1 + false</span>`,
             StringifyThresholds.ELEMENT_WITH_BINDING_COUNT
           )}</div>`
-        )
+        ),
+        '1'
       ]
     })
   })
@@ -122,7 +153,8 @@ describe('stringify static html', () => {
             `<span class="foo&gt;ar">1 + &lt;</span>` + `<span>&amp;</span>`,
             StringifyThresholds.ELEMENT_WITH_BINDING_COUNT
           )}</div>`
-        )
+        ),
+        '1'
       ]
     })
   })
index 01cf7da2d1fb97490d15046c8b6e0d3d3fdd9038..ad5ad616e9e9109df2fb1fc33a1033c666f2f969 100644 (file)
@@ -13,8 +13,7 @@ import {
   ExpressionNode,
   ElementTypes,
   PlainElementNode,
-  JSChildNode,
-  createSimpleExpression
+  JSChildNode
 } from '@vue/compiler-core'
 import {
   isVoidTag,
@@ -39,26 +38,9 @@ export const enum StringifyThresholds {
 export const stringifyStatic: HoistTransform = (children, context) => {
   let nc = 0 // current node count
   let ec = 0 // current element with binding count
-  const currentEligibleNodes: PlainElementNode[] = []
+  const currentChunk: PlainElementNode[] = []
 
-  for (let i = 0; i < children.length; i++) {
-    const child = children[i]
-    const hoisted = getHoistedNode(child)
-    if (hoisted) {
-      // presence of hoisted means child must be a plain element Node
-      const node = child as PlainElementNode
-      const result = analyzeNode(node)
-      if (result) {
-        // node is stringifiable, record state
-        nc += result[0]
-        ec += result[1]
-        currentEligibleNodes.push(node)
-        continue
-      }
-    }
-
-    // we only reach here if we ran into a node that is not stringifiable
-    // check if currently analyzed nodes meet criteria for stringification.
+  const stringifyCurrentChunk = (currentIndex: number): number => {
     if (
       nc >= StringifyThresholds.NODE_COUNT ||
       ec >= StringifyThresholds.ELEMENT_WITH_BINDING_COUNT
@@ -66,40 +48,57 @@ export const stringifyStatic: HoistTransform = (children, context) => {
       // combine all currently eligible nodes into a single static vnode call
       const staticCall = createCallExpression(context.helper(CREATE_STATIC), [
         JSON.stringify(
-          currentEligibleNodes
-            .map(node => stringifyElement(node, context))
-            .join('')
+          currentChunk.map(node => stringifyElement(node, context)).join('')
         ),
         // the 2nd argument indicates the number of DOM nodes this static vnode
         // will insert / hydrate
-        String(currentEligibleNodes.length)
+        String(currentChunk.length)
       ])
       // replace the first node's hoisted expression with the static vnode call
-      replaceHoist(currentEligibleNodes[0], staticCall, context)
+      replaceHoist(currentChunk[0], staticCall, context)
 
-      const n = currentEligibleNodes.length
-      if (n > 1) {
-        for (let j = 1; j < n; j++) {
+      if (currentChunk.length > 1) {
+        for (let i = 1; i < currentChunk.length; i++) {
           // for the merged nodes, set their hoisted expression to null
-          replaceHoist(
-            currentEligibleNodes[j],
-            createSimpleExpression(`null`, false),
-            context
-          )
+          replaceHoist(currentChunk[i], null, context)
         }
+
         // also remove merged nodes from children
-        const deleteCount = n - 1
-        children.splice(i - n + 1, deleteCount)
-        // adjust iteration index
-        i -= deleteCount
+        const deleteCount = currentChunk.length - 1
+        children.splice(currentIndex - currentChunk.length + 1, deleteCount)
+        return deleteCount
       }
     }
+    return 0
+  }
 
+  let i = 0
+  for (; i < children.length; i++) {
+    const child = children[i]
+    const hoisted = getHoistedNode(child)
+    if (hoisted) {
+      // presence of hoisted means child must be a plain element Node
+      const node = child as PlainElementNode
+      const result = analyzeNode(node)
+      if (result) {
+        // node is stringifiable, record state
+        nc += result[0]
+        ec += result[1]
+        currentChunk.push(node)
+        continue
+      }
+    }
+    // we only reach here if we ran into a node that is not stringifiable
+    // check if currently analyzed nodes meet criteria for stringification.
+    // adjust iteration index
+    i -= stringifyCurrentChunk(i)
     // reset state
     nc = 0
     ec = 0
-    currentEligibleNodes.length = 0
+    currentChunk.length = 0
   }
+  // in case the last node was also stringifiable
+  stringifyCurrentChunk(i)
 }
 
 const getHoistedNode = (node: TemplateChildNode) =>
@@ -116,7 +115,7 @@ const isStringifiableAttr = (name: string) => {
 
 const replaceHoist = (
   node: PlainElementNode,
-  replacement: JSChildNode,
+  replacement: JSChildNode | null,
   context: TransformContext
 ) => {
   const hoistToReplace = (node.codegenNode as SimpleExpressionNode).hoisted!