From baa6973b139f6aa57a62e3204eea9870f2a19765 Mon Sep 17 00:00:00 2001 From: Evan You Date: Fri, 15 May 2020 15:50:42 -0400 Subject: [PATCH] wip(compiler): tests for new stringification --- packages/compiler-core/src/ast.ts | 2 +- packages/compiler-core/src/codegen.ts | 10 ++- packages/compiler-core/src/transform.ts | 2 +- .../transforms/stringifyStatic.spec.ts | 42 +++++++-- .../src/transforms/stringifyStatic.ts | 87 +++++++++---------- 5 files changed, 88 insertions(+), 55 deletions(-) diff --git a/packages/compiler-core/src/ast.ts b/packages/compiler-core/src/ast.ts index 3e843b8a2..d4c11c07b 100644 --- a/packages/compiler-core/src/ast.ts +++ b/packages/compiler-core/src/ast.ts @@ -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 diff --git a/packages/compiler-core/src/codegen.ts b/packages/compiler-core/src/codegen.ts index 7982eedaf..d7d8b8bfa 100644 --- a/packages/compiler-core/src/codegen.ts +++ b/packages/compiler-core/src/codegen.ts @@ -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) { diff --git a/packages/compiler-core/src/transform.ts b/packages/compiler-core/src/transform.ts index fc03af4c2..92961ef1a 100644 --- a/packages/compiler-core/src/transform.ts +++ b/packages/compiler-core/src/transform.ts @@ -82,7 +82,7 @@ export interface TransformContext extends Required { helpers: Set components: Set directives: Set - hoists: JSChildNode[] + hoists: (JSChildNode | null)[] imports: Set temps: number cached: number diff --git a/packages/compiler-dom/__tests__/transforms/stringifyStatic.spec.ts b/packages/compiler-dom/__tests__/transforms/stringifyStatic.spec.ts index cf1c7e0a6..7c7f985df 100644 --- a/packages/compiler-dom/__tests__/transforms/stringifyStatic.spec.ts +++ b/packages/compiler-dom/__tests__/transforms/stringifyStatic.spec.ts @@ -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', () => { ``, StringifyThresholds.ELEMENT_WITH_BINDING_COUNT )}` - ) + ), + '1' ] }) }) @@ -75,7 +76,36 @@ describe('stringify static html', () => { ``, StringifyThresholds.NODE_COUNT )}` - ) + ), + '1' + ] + }) + }) + + test('should work for multiple adjacent nodes', () => { + const { ast } = compileWithStringify( + `
${repeat( + ``, + StringifyThresholds.ELEMENT_WITH_BINDING_COUNT + )}
` + ) + // 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( + ``, + StringifyThresholds.ELEMENT_WITH_BINDING_COUNT + ) + ), + '5' ] }) }) @@ -98,7 +128,8 @@ describe('stringify static html', () => { `1 + false`, StringifyThresholds.ELEMENT_WITH_BINDING_COUNT )}` - ) + ), + '1' ] }) }) @@ -122,7 +153,8 @@ describe('stringify static html', () => { `1 + <` + `&`, StringifyThresholds.ELEMENT_WITH_BINDING_COUNT )}` - ) + ), + '1' ] }) }) diff --git a/packages/compiler-dom/src/transforms/stringifyStatic.ts b/packages/compiler-dom/src/transforms/stringifyStatic.ts index 01cf7da2d..ad5ad616e 100644 --- a/packages/compiler-dom/src/transforms/stringifyStatic.ts +++ b/packages/compiler-dom/src/transforms/stringifyStatic.ts @@ -13,8 +13,7 @@ import { ExpressionNode, ElementTypes, PlainElementNode, - JSChildNode, - createSimpleExpression + JSChildNode } from '@vue/compiler-core' import { isVoidTag, @@ -39,9 +38,42 @@ 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 stringifyCurrentChunk = (currentIndex: number): number => { + if ( + nc >= StringifyThresholds.NODE_COUNT || + ec >= StringifyThresholds.ELEMENT_WITH_BINDING_COUNT + ) { + // combine all currently eligible nodes into a single static vnode call + const staticCall = createCallExpression(context.helper(CREATE_STATIC), [ + JSON.stringify( + currentChunk.map(node => stringifyElement(node, context)).join('') + ), + // the 2nd argument indicates the number of DOM nodes this static vnode + // will insert / hydrate + String(currentChunk.length) + ]) + // replace the first node's hoisted expression with the static vnode call + replaceHoist(currentChunk[0], staticCall, context) + + if (currentChunk.length > 1) { + for (let i = 1; i < currentChunk.length; i++) { + // for the merged nodes, set their hoisted expression to null + replaceHoist(currentChunk[i], null, context) + } + + // also remove merged nodes from children + 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) { @@ -52,54 +84,21 @@ export const stringifyStatic: HoistTransform = (children, context) => { // node is stringifiable, record state nc += result[0] ec += result[1] - currentEligibleNodes.push(node) + 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. - if ( - nc >= StringifyThresholds.NODE_COUNT || - ec >= StringifyThresholds.ELEMENT_WITH_BINDING_COUNT - ) { - // 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('') - ), - // the 2nd argument indicates the number of DOM nodes this static vnode - // will insert / hydrate - String(currentEligibleNodes.length) - ]) - // replace the first node's hoisted expression with the static vnode call - replaceHoist(currentEligibleNodes[0], staticCall, context) - - const n = currentEligibleNodes.length - if (n > 1) { - for (let j = 1; j < n; j++) { - // for the merged nodes, set their hoisted expression to null - replaceHoist( - currentEligibleNodes[j], - createSimpleExpression(`null`, false), - context - ) - } - // also remove merged nodes from children - const deleteCount = n - 1 - children.splice(i - n + 1, deleteCount) - // adjust iteration index - i -= deleteCount - } - } - + // 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!