import { compile, NodeTypes, CREATE_STATIC, createSimpleExpression, ConstantTypes } from '../../src' import { stringifyStatic, StringifyThresholds } from '../../src/transforms/stringifyStatic' describe('stringify static html', () => { function compileWithStringify(template: string) { return compile(template, { hoistStatic: true, prefixIdentifiers: true, transformHoist: stringifyStatic }) } function repeat(code: string, n: number): string { return new Array(n) .fill(0) .map(() => code) .join('') } test('should bail on non-eligible static trees', () => { const { ast } = compileWithStringify( `
hello
hello
` ) // should be a normal vnode call expect(ast.hoists[0]!.type).toBe(NodeTypes.VNODE_CALL) }) test('should work on eligible content (elements with binding > 5)', () => { const { ast } = compileWithStringify( `
${repeat( ``, StringifyThresholds.ELEMENT_WITH_BINDING_COUNT )}
` ) // should be optimized now expect(ast.hoists).toMatchObject([ { type: NodeTypes.JS_CALL_EXPRESSION, callee: CREATE_STATIC, arguments: [ JSON.stringify( `
${repeat( ``, StringifyThresholds.ELEMENT_WITH_BINDING_COUNT )}
` ), '1' ] }, // the children array is hoisted as well { type: NodeTypes.JS_ARRAY_EXPRESSION } ]) }) test('should work on eligible content (elements > 20)', () => { const { ast } = compileWithStringify( `
${repeat( ``, StringifyThresholds.NODE_COUNT )}
` ) // should be optimized now expect(ast.hoists).toMatchObject([ { type: NodeTypes.JS_CALL_EXPRESSION, callee: CREATE_STATIC, arguments: [ JSON.stringify( `
${repeat( ``, StringifyThresholds.NODE_COUNT )}
` ), '1' ] }, // the children array is hoisted as well { type: NodeTypes.JS_ARRAY_EXPRESSION } ]) }) test('should work for multiple adjacent nodes', () => { const { ast } = compileWithStringify( `
${repeat( ``, StringifyThresholds.ELEMENT_WITH_BINDING_COUNT )}
` ) // should have 6 hoisted nodes (including the entire array), // but 2~5 should be null because they are merged into 1 expect(ast.hoists).toMatchObject([ { type: NodeTypes.JS_CALL_EXPRESSION, callee: CREATE_STATIC, arguments: [ JSON.stringify( repeat( ``, StringifyThresholds.ELEMENT_WITH_BINDING_COUNT ) ), '5' ] }, null, null, null, null, { type: NodeTypes.JS_ARRAY_EXPRESSION } ]) }) test('serializing constant bindings', () => { const { ast } = compileWithStringify( `
${repeat( `{{ 1 }} + {{ false }}`, StringifyThresholds.ELEMENT_WITH_BINDING_COUNT )}
` ) // should be optimized now expect(ast.hoists).toMatchObject([ { type: NodeTypes.JS_CALL_EXPRESSION, callee: CREATE_STATIC, arguments: [ JSON.stringify( `
${repeat( `1 + false`, StringifyThresholds.ELEMENT_WITH_BINDING_COUNT )}
` ), '1' ] }, { type: NodeTypes.JS_ARRAY_EXPRESSION } ]) }) test('escape', () => { const { ast } = compileWithStringify( `
${repeat( `{{ 1 }} + {{ '<' }}` + `&`, StringifyThresholds.ELEMENT_WITH_BINDING_COUNT )}
` ) // should be optimized now expect(ast.hoists).toMatchObject([ { type: NodeTypes.JS_CALL_EXPRESSION, callee: CREATE_STATIC, arguments: [ JSON.stringify( `
${repeat( `1 + <` + `&`, StringifyThresholds.ELEMENT_WITH_BINDING_COUNT )}
` ), '1' ] }, { type: NodeTypes.JS_ARRAY_EXPRESSION } ]) }) test('should bail on bindings that are hoisted but not stringifiable', () => { const { ast, code } = compile( `
${repeat( `foo`, StringifyThresholds.ELEMENT_WITH_BINDING_COUNT )}
`, { hoistStatic: true, prefixIdentifiers: true, transformHoist: stringifyStatic, nodeTransforms: [ node => { if (node.type === NodeTypes.ELEMENT && node.tag === 'img') { const exp = createSimpleExpression( '_imports_0_', false, node.loc, ConstantTypes.CAN_HOIST ) node.props[0] = { type: NodeTypes.DIRECTIVE, name: 'bind', arg: createSimpleExpression('src', true), exp, modifiers: [], loc: node.loc } } } ] } ) expect(ast.hoists).toMatchObject([ { // the expression and the tree are still hoistable // but should stay NodeTypes.VNODE_CALL // if it's stringified it will be NodeTypes.JS_CALL_EXPRESSION type: NodeTypes.VNODE_CALL }, { type: NodeTypes.JS_ARRAY_EXPRESSION } ]) expect(code).toMatchSnapshot() }) test('should work with bindings that are non-static but stringifiable', () => { // if a binding is non-static but marked as CAN_STRINGIFY, it means it's // a known reference to a constant string. const { ast, code } = compile( `
${repeat( `foo`, StringifyThresholds.ELEMENT_WITH_BINDING_COUNT )}
`, { hoistStatic: true, prefixIdentifiers: true, transformHoist: stringifyStatic, nodeTransforms: [ node => { if (node.type === NodeTypes.ELEMENT && node.tag === 'img') { const exp = createSimpleExpression( '_imports_0_', false, node.loc, ConstantTypes.CAN_STRINGIFY ) node.props[0] = { type: NodeTypes.DIRECTIVE, name: 'bind', arg: createSimpleExpression('src', true), exp, modifiers: [], loc: node.loc } } } ] } ) expect(ast.hoists).toMatchObject([ { // the hoisted node should be NodeTypes.JS_CALL_EXPRESSION // of `createStaticVNode()` instead of dynamic NodeTypes.VNODE_CALL type: NodeTypes.JS_CALL_EXPRESSION }, { type: NodeTypes.JS_ARRAY_EXPRESSION } ]) expect(code).toMatchSnapshot() }) // #1128 test('should bail on non attribute bindings', () => { const { ast } = compileWithStringify( `
${repeat( `foo`, StringifyThresholds.ELEMENT_WITH_BINDING_COUNT )}
` ) expect(ast.hoists).toMatchObject([ { type: NodeTypes.VNODE_CALL // not CALL_EXPRESSION }, { type: NodeTypes.JS_ARRAY_EXPRESSION } ]) const { ast: ast2 } = compileWithStringify( `
${repeat( `foo`, StringifyThresholds.ELEMENT_WITH_BINDING_COUNT )}
` ) expect(ast2.hoists).toMatchObject([ { type: NodeTypes.VNODE_CALL // not CALL_EXPRESSION }, { type: NodeTypes.JS_ARRAY_EXPRESSION } ]) }) test('should bail on non attribute bindings', () => { const { ast } = compileWithStringify( `
${repeat( `foo`, StringifyThresholds.ELEMENT_WITH_BINDING_COUNT )}
` ) expect(ast.hoists).toMatchObject([ { type: NodeTypes.VNODE_CALL // not CALL_EXPRESSION }, { type: NodeTypes.JS_ARRAY_EXPRESSION } ]) const { ast: ast2 } = compileWithStringify( `
${repeat( `foo`, StringifyThresholds.ELEMENT_WITH_BINDING_COUNT )}
` ) expect(ast2.hoists).toMatchObject([ { type: NodeTypes.VNODE_CALL // not CALL_EXPRESSION }, { type: NodeTypes.JS_ARRAY_EXPRESSION } ]) }) test('should bail on tags that has placement constraints (eg.tables related tags)', () => { const { ast } = compileWithStringify( `${repeat( ``, StringifyThresholds.ELEMENT_WITH_BINDING_COUNT )}
foo
` ) expect(ast.hoists).toMatchObject([ { type: NodeTypes.VNODE_CALL // not CALL_EXPRESSION }, { type: NodeTypes.JS_ARRAY_EXPRESSION } ]) }) test('should bail inside slots', () => { const { ast } = compileWithStringify( `${repeat( `
`, StringifyThresholds.ELEMENT_WITH_BINDING_COUNT )}
` ) expect(ast.hoists.length).toBe( StringifyThresholds.ELEMENT_WITH_BINDING_COUNT ) ast.hoists.forEach(node => { expect(node).toMatchObject({ type: NodeTypes.VNODE_CALL // not CALL_EXPRESSION }) }) const { ast: ast2 } = compileWithStringify( `` ) expect(ast2.hoists.length).toBe( StringifyThresholds.ELEMENT_WITH_BINDING_COUNT ) ast2.hoists.forEach(node => { expect(node).toMatchObject({ type: NodeTypes.VNODE_CALL // not CALL_EXPRESSION }) }) }) test('should remove attribute for `null`', () => { const { ast } = compileWithStringify( `
${repeat( ``, StringifyThresholds.ELEMENT_WITH_BINDING_COUNT )}
` ) expect(ast.hoists[0]).toMatchObject({ type: NodeTypes.JS_CALL_EXPRESSION, callee: CREATE_STATIC, arguments: [ JSON.stringify( `${repeat( ``, StringifyThresholds.ELEMENT_WITH_BINDING_COUNT )}` ), '5' ] }) }) test('should stringify svg', () => { const svg = `` const repeated = `` const { ast } = compileWithStringify( `
${svg}${repeat( repeated, StringifyThresholds.ELEMENT_WITH_BINDING_COUNT )}
` ) expect(ast.hoists[0]).toMatchObject({ type: NodeTypes.JS_CALL_EXPRESSION, callee: CREATE_STATIC, arguments: [ JSON.stringify( `${svg}${repeat( repeated, StringifyThresholds.ELEMENT_WITH_BINDING_COUNT )}` ), '1' ] }) }) // #5439 test('stringify v-html', () => { const { code } = compileWithStringify(`
12
`) expect(code).toMatch(`show-it `) expect(code).toMatchSnapshot() }) test('stringify v-text', () => { const { code } = compileWithStringify(`
12
`) expect(code).toMatch(`<span>show-it </span>`) expect(code).toMatchSnapshot() }) })