test: tests for hoistStatic
This commit is contained in:
		
							parent
							
								
									802ecccc49
								
							
						
					
					
						commit
						57a5c61320
					
				@ -7,7 +7,7 @@ import {
 | 
			
		||||
  ElementCodegenNode
 | 
			
		||||
} from '../src'
 | 
			
		||||
import { CREATE_VNODE } from '../src/runtimeHelpers'
 | 
			
		||||
import { isString } from '@vue/shared'
 | 
			
		||||
import { isString, PatchFlags, PatchFlagNames, isArray } from '@vue/shared'
 | 
			
		||||
 | 
			
		||||
const leadingBracketRE = /^\[/
 | 
			
		||||
const bracketsRE = /^\[|\]$/g
 | 
			
		||||
@ -58,3 +58,15 @@ export function createElementWithCodegen(
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function genFlagText(flag: PatchFlags | PatchFlags[]) {
 | 
			
		||||
  if (isArray(flag)) {
 | 
			
		||||
    let f = 0
 | 
			
		||||
    flag.forEach(ff => {
 | 
			
		||||
      f |= ff
 | 
			
		||||
    })
 | 
			
		||||
    return `${f} /* ${flag.map(f => PatchFlagNames[f]).join(', ')} */`
 | 
			
		||||
  } else {
 | 
			
		||||
    return `${flag} /* ${PatchFlagNames[flag]} */`
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -0,0 +1,205 @@
 | 
			
		||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
 | 
			
		||||
 | 
			
		||||
exports[`compiler: hositStatic transform hoist nested static tree 1`] = `
 | 
			
		||||
"const _Vue = Vue
 | 
			
		||||
const _createVNode = Vue.createVNode
 | 
			
		||||
 | 
			
		||||
const _hoisted_1 = _createVNode(\\"p\\", null, [
 | 
			
		||||
  _createVNode(\\"span\\"),
 | 
			
		||||
  _createVNode(\\"span\\")
 | 
			
		||||
])
 | 
			
		||||
 | 
			
		||||
return function render() {
 | 
			
		||||
  with (this) {
 | 
			
		||||
    const { createVNode: _createVNode, openBlock: _openBlock, createBlock: _createBlock } = _Vue
 | 
			
		||||
    
 | 
			
		||||
    return (_openBlock(), _createBlock(\\"div\\", null, [
 | 
			
		||||
      _hoisted_1
 | 
			
		||||
    ]))
 | 
			
		||||
  }
 | 
			
		||||
}"
 | 
			
		||||
`;
 | 
			
		||||
 | 
			
		||||
exports[`compiler: hositStatic transform hoist siblings with common non-hoistable parent 1`] = `
 | 
			
		||||
"const _Vue = Vue
 | 
			
		||||
const _createVNode = Vue.createVNode
 | 
			
		||||
 | 
			
		||||
const _hoisted_1 = _createVNode(\\"span\\")
 | 
			
		||||
const _hoisted_2 = _createVNode(\\"div\\")
 | 
			
		||||
 | 
			
		||||
return function render() {
 | 
			
		||||
  with (this) {
 | 
			
		||||
    const { createVNode: _createVNode, openBlock: _openBlock, createBlock: _createBlock } = _Vue
 | 
			
		||||
    
 | 
			
		||||
    return (_openBlock(), _createBlock(\\"div\\", null, [
 | 
			
		||||
      _hoisted_1,
 | 
			
		||||
      _hoisted_2
 | 
			
		||||
    ]))
 | 
			
		||||
  }
 | 
			
		||||
}"
 | 
			
		||||
`;
 | 
			
		||||
 | 
			
		||||
exports[`compiler: hositStatic transform hoist simple element 1`] = `
 | 
			
		||||
"const _Vue = Vue
 | 
			
		||||
const _createVNode = Vue.createVNode
 | 
			
		||||
 | 
			
		||||
const _hoisted_1 = _createVNode(\\"span\\", { class: \\"inline\\" }, \\"hello\\")
 | 
			
		||||
 | 
			
		||||
return function render() {
 | 
			
		||||
  with (this) {
 | 
			
		||||
    const { createVNode: _createVNode, openBlock: _openBlock, createBlock: _createBlock } = _Vue
 | 
			
		||||
    
 | 
			
		||||
    return (_openBlock(), _createBlock(\\"div\\", null, [
 | 
			
		||||
      _hoisted_1
 | 
			
		||||
    ]))
 | 
			
		||||
  }
 | 
			
		||||
}"
 | 
			
		||||
`;
 | 
			
		||||
 | 
			
		||||
exports[`compiler: hositStatic transform hoist static props for elements with directives 1`] = `
 | 
			
		||||
"const _Vue = Vue
 | 
			
		||||
const _createVNode = Vue.createVNode
 | 
			
		||||
 | 
			
		||||
const _hoisted_1 = { id: \\"foo\\" }
 | 
			
		||||
 | 
			
		||||
return function render() {
 | 
			
		||||
  with (this) {
 | 
			
		||||
    const { createVNode: _createVNode, applyDirectives: _applyDirectives, resolveDirective: _resolveDirective, openBlock: _openBlock, createBlock: _createBlock } = _Vue
 | 
			
		||||
    
 | 
			
		||||
    const _directive_foo = _resolveDirective(\\"foo\\")
 | 
			
		||||
    
 | 
			
		||||
    return (_openBlock(), _createBlock(\\"div\\", null, [
 | 
			
		||||
      _applyDirectives(_createVNode(\\"div\\", _hoisted_1, null, 32 /* NEED_PATCH */), [
 | 
			
		||||
        [_directive_foo]
 | 
			
		||||
      ])
 | 
			
		||||
    ]))
 | 
			
		||||
  }
 | 
			
		||||
}"
 | 
			
		||||
`;
 | 
			
		||||
 | 
			
		||||
exports[`compiler: hositStatic transform hoist static props for elements with dynamic text children 1`] = `
 | 
			
		||||
"const _Vue = Vue
 | 
			
		||||
const _createVNode = Vue.createVNode
 | 
			
		||||
 | 
			
		||||
const _hoisted_1 = { id: \\"foo\\" }
 | 
			
		||||
 | 
			
		||||
return function render() {
 | 
			
		||||
  with (this) {
 | 
			
		||||
    const { toString: _toString, createVNode: _createVNode, openBlock: _openBlock, createBlock: _createBlock } = _Vue
 | 
			
		||||
    
 | 
			
		||||
    return (_openBlock(), _createBlock(\\"div\\", null, [
 | 
			
		||||
      _createVNode(\\"div\\", _hoisted_1, _toString(hello), 1 /* TEXT */)
 | 
			
		||||
    ]))
 | 
			
		||||
  }
 | 
			
		||||
}"
 | 
			
		||||
`;
 | 
			
		||||
 | 
			
		||||
exports[`compiler: hositStatic transform hoist static props for elements with unhoistable children 1`] = `
 | 
			
		||||
"const _Vue = Vue
 | 
			
		||||
const _createVNode = Vue.createVNode
 | 
			
		||||
 | 
			
		||||
const _hoisted_1 = { id: \\"foo\\" }
 | 
			
		||||
 | 
			
		||||
return function render() {
 | 
			
		||||
  with (this) {
 | 
			
		||||
    const { resolveComponent: _resolveComponent, createVNode: _createVNode, openBlock: _openBlock, createBlock: _createBlock } = _Vue
 | 
			
		||||
    
 | 
			
		||||
    const _component_Comp = _resolveComponent(\\"Comp\\")
 | 
			
		||||
    
 | 
			
		||||
    return (_openBlock(), _createBlock(\\"div\\", null, [
 | 
			
		||||
      _createVNode(\\"div\\", _hoisted_1, [
 | 
			
		||||
        _createVNode(_component_Comp)
 | 
			
		||||
      ])
 | 
			
		||||
    ]))
 | 
			
		||||
  }
 | 
			
		||||
}"
 | 
			
		||||
`;
 | 
			
		||||
 | 
			
		||||
exports[`compiler: hositStatic transform should NOT hoist components 1`] = `
 | 
			
		||||
"const _Vue = Vue
 | 
			
		||||
 | 
			
		||||
return function render() {
 | 
			
		||||
  with (this) {
 | 
			
		||||
    const { resolveComponent: _resolveComponent, createVNode: _createVNode, openBlock: _openBlock, createBlock: _createBlock } = _Vue
 | 
			
		||||
    
 | 
			
		||||
    const _component_Comp = _resolveComponent(\\"Comp\\")
 | 
			
		||||
    
 | 
			
		||||
    return (_openBlock(), _createBlock(\\"div\\", null, [
 | 
			
		||||
      _createVNode(_component_Comp)
 | 
			
		||||
    ]))
 | 
			
		||||
  }
 | 
			
		||||
}"
 | 
			
		||||
`;
 | 
			
		||||
 | 
			
		||||
exports[`compiler: hositStatic transform should NOT hoist element with dynamic props 1`] = `
 | 
			
		||||
"const _Vue = Vue
 | 
			
		||||
 | 
			
		||||
return function render() {
 | 
			
		||||
  with (this) {
 | 
			
		||||
    const { createVNode: _createVNode, openBlock: _openBlock, createBlock: _createBlock } = _Vue
 | 
			
		||||
    
 | 
			
		||||
    return (_openBlock(), _createBlock(\\"div\\", null, [
 | 
			
		||||
      _createVNode(\\"div\\", { id: foo }, null, 8 /* PROPS */, [\\"id\\"])
 | 
			
		||||
    ]))
 | 
			
		||||
  }
 | 
			
		||||
}"
 | 
			
		||||
`;
 | 
			
		||||
 | 
			
		||||
exports[`compiler: hositStatic transform should NOT hoist root node 1`] = `
 | 
			
		||||
"const _Vue = Vue
 | 
			
		||||
 | 
			
		||||
return function render() {
 | 
			
		||||
  with (this) {
 | 
			
		||||
    const { createVNode: _createVNode, openBlock: _openBlock, createBlock: _createBlock } = _Vue
 | 
			
		||||
    
 | 
			
		||||
    return (_openBlock(), _createBlock(\\"div\\"))
 | 
			
		||||
  }
 | 
			
		||||
}"
 | 
			
		||||
`;
 | 
			
		||||
 | 
			
		||||
exports[`compiler: hositStatic transform should hoist v-for children if static 1`] = `
 | 
			
		||||
"const _Vue = Vue
 | 
			
		||||
const _createVNode = Vue.createVNode
 | 
			
		||||
 | 
			
		||||
const _hoisted_1 = { id: \\"foo\\" }
 | 
			
		||||
const _hoisted_2 = _createVNode(\\"span\\")
 | 
			
		||||
 | 
			
		||||
return function render() {
 | 
			
		||||
  with (this) {
 | 
			
		||||
    const { renderList: _renderList, openBlock: _openBlock, createBlock: _createBlock, Fragment: _Fragment, createVNode: _createVNode } = _Vue
 | 
			
		||||
    
 | 
			
		||||
    return (_openBlock(), _createBlock(\\"div\\", null, [
 | 
			
		||||
      (_openBlock(), _createBlock(_Fragment, null, _renderList(list, (i) => {
 | 
			
		||||
        return (_openBlock(), _createBlock(\\"div\\", _hoisted_1, [
 | 
			
		||||
          _hoisted_2
 | 
			
		||||
        ]))
 | 
			
		||||
      }), 128 /* UNKEYED_FRAGMENT */))
 | 
			
		||||
    ]))
 | 
			
		||||
  }
 | 
			
		||||
}"
 | 
			
		||||
`;
 | 
			
		||||
 | 
			
		||||
exports[`compiler: hositStatic transform should hoist v-if props/children if static 1`] = `
 | 
			
		||||
"const _Vue = Vue
 | 
			
		||||
const _createVNode = Vue.createVNode
 | 
			
		||||
 | 
			
		||||
const _hoisted_1 = {
 | 
			
		||||
  key: 0,
 | 
			
		||||
  id: \\"foo\\"
 | 
			
		||||
}
 | 
			
		||||
const _hoisted_2 = _createVNode(\\"span\\")
 | 
			
		||||
 | 
			
		||||
return function render() {
 | 
			
		||||
  with (this) {
 | 
			
		||||
    const { openBlock: _openBlock, createVNode: _createVNode, createBlock: _createBlock, Empty: _Empty } = _Vue
 | 
			
		||||
    
 | 
			
		||||
    return (_openBlock(), _createBlock(\\"div\\", null, [
 | 
			
		||||
      (_openBlock(), ok
 | 
			
		||||
        ? _createBlock(\\"div\\", _hoisted_1, [
 | 
			
		||||
            _hoisted_2
 | 
			
		||||
          ])
 | 
			
		||||
        : _createBlock(_Empty))
 | 
			
		||||
    ]))
 | 
			
		||||
  }
 | 
			
		||||
}"
 | 
			
		||||
`;
 | 
			
		||||
							
								
								
									
										359
									
								
								packages/compiler-core/__tests__/transforms/hoistStatic.spec.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										359
									
								
								packages/compiler-core/__tests__/transforms/hoistStatic.spec.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,359 @@
 | 
			
		||||
import { parse, transform, NodeTypes, generate } from '../../src'
 | 
			
		||||
import {
 | 
			
		||||
  OPEN_BLOCK,
 | 
			
		||||
  CREATE_BLOCK,
 | 
			
		||||
  CREATE_VNODE,
 | 
			
		||||
  APPLY_DIRECTIVES,
 | 
			
		||||
  FRAGMENT,
 | 
			
		||||
  RENDER_LIST
 | 
			
		||||
} from '../../src/runtimeHelpers'
 | 
			
		||||
import { transformElement } from '../../src/transforms/transformElement'
 | 
			
		||||
import { transformIf } from '../../src/transforms/vIf'
 | 
			
		||||
import { transformFor } from '../../src/transforms/vFor'
 | 
			
		||||
import { transformBind } from '../../src/transforms/vBind'
 | 
			
		||||
import { createObjectMatcher, genFlagText } from '../testUtils'
 | 
			
		||||
import { PatchFlags } from '@vue/shared'
 | 
			
		||||
 | 
			
		||||
function transformWithHoist(template: string) {
 | 
			
		||||
  const ast = parse(template)
 | 
			
		||||
  transform(ast, {
 | 
			
		||||
    hoistStatic: true,
 | 
			
		||||
    nodeTransforms: [transformIf, transformFor, transformElement],
 | 
			
		||||
    directiveTransforms: {
 | 
			
		||||
      bind: transformBind
 | 
			
		||||
    }
 | 
			
		||||
  })
 | 
			
		||||
  expect(ast.codegenNode).toMatchObject({
 | 
			
		||||
    type: NodeTypes.JS_SEQUENCE_EXPRESSION,
 | 
			
		||||
    expressions: [
 | 
			
		||||
      {
 | 
			
		||||
        type: NodeTypes.JS_CALL_EXPRESSION,
 | 
			
		||||
        callee: OPEN_BLOCK
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        type: NodeTypes.JS_CALL_EXPRESSION,
 | 
			
		||||
        callee: CREATE_BLOCK
 | 
			
		||||
      }
 | 
			
		||||
    ]
 | 
			
		||||
  })
 | 
			
		||||
  return {
 | 
			
		||||
    root: ast,
 | 
			
		||||
    args: (ast.codegenNode as any).expressions[1].arguments
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
describe('compiler: hositStatic transform', () => {
 | 
			
		||||
  test('should NOT hoist root node', () => {
 | 
			
		||||
    // if the whole tree is static, the root still needs to be a block
 | 
			
		||||
    // so that it's patched in optimized mode to skip children
 | 
			
		||||
    const { root, args } = transformWithHoist(`<div/>`)
 | 
			
		||||
    expect(root.hoists.length).toBe(0)
 | 
			
		||||
    expect(args).toEqual([`"div"`])
 | 
			
		||||
    expect(generate(root).code).toMatchSnapshot()
 | 
			
		||||
  })
 | 
			
		||||
 | 
			
		||||
  test('hoist simple element', () => {
 | 
			
		||||
    const { root, args } = transformWithHoist(
 | 
			
		||||
      `<div><span class="inline">hello</span></div>`
 | 
			
		||||
    )
 | 
			
		||||
    expect(root.hoists).toMatchObject([
 | 
			
		||||
      {
 | 
			
		||||
        type: NodeTypes.JS_CALL_EXPRESSION,
 | 
			
		||||
        callee: CREATE_VNODE,
 | 
			
		||||
        arguments: [
 | 
			
		||||
          `"span"`,
 | 
			
		||||
          createObjectMatcher({ class: 'inline' }),
 | 
			
		||||
          {
 | 
			
		||||
            type: NodeTypes.TEXT,
 | 
			
		||||
            content: `hello`
 | 
			
		||||
          }
 | 
			
		||||
        ]
 | 
			
		||||
      }
 | 
			
		||||
    ])
 | 
			
		||||
    expect(args).toMatchObject([
 | 
			
		||||
      `"div"`,
 | 
			
		||||
      `null`,
 | 
			
		||||
      [
 | 
			
		||||
        {
 | 
			
		||||
          type: NodeTypes.ELEMENT,
 | 
			
		||||
          codegenNode: {
 | 
			
		||||
            type: NodeTypes.SIMPLE_EXPRESSION,
 | 
			
		||||
            content: `_hoisted_1`
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
      ]
 | 
			
		||||
    ])
 | 
			
		||||
    expect(generate(root).code).toMatchSnapshot()
 | 
			
		||||
  })
 | 
			
		||||
 | 
			
		||||
  test('hoist nested static tree', () => {
 | 
			
		||||
    const { root, args } = transformWithHoist(
 | 
			
		||||
      `<div><p><span/><span/></p></div>`
 | 
			
		||||
    )
 | 
			
		||||
    expect(root.hoists).toMatchObject([
 | 
			
		||||
      {
 | 
			
		||||
        type: NodeTypes.JS_CALL_EXPRESSION,
 | 
			
		||||
        callee: CREATE_VNODE,
 | 
			
		||||
        arguments: [
 | 
			
		||||
          `"p"`,
 | 
			
		||||
          `null`,
 | 
			
		||||
          [
 | 
			
		||||
            { type: NodeTypes.ELEMENT, tag: `span` },
 | 
			
		||||
            { type: NodeTypes.ELEMENT, tag: `span` }
 | 
			
		||||
          ]
 | 
			
		||||
        ]
 | 
			
		||||
      }
 | 
			
		||||
    ])
 | 
			
		||||
    expect(args[2]).toMatchObject([
 | 
			
		||||
      {
 | 
			
		||||
        type: NodeTypes.ELEMENT,
 | 
			
		||||
        codegenNode: {
 | 
			
		||||
          type: NodeTypes.SIMPLE_EXPRESSION,
 | 
			
		||||
          content: `_hoisted_1`
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    ])
 | 
			
		||||
    expect(generate(root).code).toMatchSnapshot()
 | 
			
		||||
  })
 | 
			
		||||
 | 
			
		||||
  test('hoist siblings with common non-hoistable parent', () => {
 | 
			
		||||
    const { root, args } = transformWithHoist(`<div><span/><div/></div>`)
 | 
			
		||||
    expect(root.hoists).toMatchObject([
 | 
			
		||||
      {
 | 
			
		||||
        type: NodeTypes.JS_CALL_EXPRESSION,
 | 
			
		||||
        callee: CREATE_VNODE,
 | 
			
		||||
        arguments: [`"span"`]
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        type: NodeTypes.JS_CALL_EXPRESSION,
 | 
			
		||||
        callee: CREATE_VNODE,
 | 
			
		||||
        arguments: [`"div"`]
 | 
			
		||||
      }
 | 
			
		||||
    ])
 | 
			
		||||
    expect(args[2]).toMatchObject([
 | 
			
		||||
      {
 | 
			
		||||
        type: NodeTypes.ELEMENT,
 | 
			
		||||
        codegenNode: {
 | 
			
		||||
          type: NodeTypes.SIMPLE_EXPRESSION,
 | 
			
		||||
          content: `_hoisted_1`
 | 
			
		||||
        }
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        type: NodeTypes.ELEMENT,
 | 
			
		||||
        codegenNode: {
 | 
			
		||||
          type: NodeTypes.SIMPLE_EXPRESSION,
 | 
			
		||||
          content: `_hoisted_2`
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    ])
 | 
			
		||||
    expect(generate(root).code).toMatchSnapshot()
 | 
			
		||||
  })
 | 
			
		||||
 | 
			
		||||
  test('should NOT hoist components', () => {
 | 
			
		||||
    const { root, args } = transformWithHoist(`<div><Comp/></div>`)
 | 
			
		||||
    expect(root.hoists.length).toBe(0)
 | 
			
		||||
    expect(args[2]).toMatchObject([
 | 
			
		||||
      {
 | 
			
		||||
        type: NodeTypes.ELEMENT,
 | 
			
		||||
        codegenNode: {
 | 
			
		||||
          callee: CREATE_VNODE,
 | 
			
		||||
          arguments: [`_component_Comp`]
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    ])
 | 
			
		||||
    expect(generate(root).code).toMatchSnapshot()
 | 
			
		||||
  })
 | 
			
		||||
 | 
			
		||||
  test('should NOT hoist element with dynamic props', () => {
 | 
			
		||||
    const { root, args } = transformWithHoist(`<div><div :id="foo"/></div>`)
 | 
			
		||||
    expect(root.hoists.length).toBe(0)
 | 
			
		||||
    expect(args[2]).toMatchObject([
 | 
			
		||||
      {
 | 
			
		||||
        type: NodeTypes.ELEMENT,
 | 
			
		||||
        codegenNode: {
 | 
			
		||||
          callee: CREATE_VNODE,
 | 
			
		||||
          arguments: [
 | 
			
		||||
            `"div"`,
 | 
			
		||||
            createObjectMatcher({
 | 
			
		||||
              id: `[foo]`
 | 
			
		||||
            }),
 | 
			
		||||
            `null`,
 | 
			
		||||
            genFlagText(PatchFlags.PROPS),
 | 
			
		||||
            `["id"]`
 | 
			
		||||
          ]
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    ])
 | 
			
		||||
    expect(generate(root).code).toMatchSnapshot()
 | 
			
		||||
  })
 | 
			
		||||
 | 
			
		||||
  test('hoist static props for elements with directives', () => {
 | 
			
		||||
    const { root, args } = transformWithHoist(
 | 
			
		||||
      `<div><div id="foo" v-foo/></div>`
 | 
			
		||||
    )
 | 
			
		||||
    expect(root.hoists).toMatchObject([createObjectMatcher({ id: 'foo' })])
 | 
			
		||||
    expect(args[2]).toMatchObject([
 | 
			
		||||
      {
 | 
			
		||||
        type: NodeTypes.ELEMENT,
 | 
			
		||||
        codegenNode: {
 | 
			
		||||
          callee: APPLY_DIRECTIVES,
 | 
			
		||||
          arguments: [
 | 
			
		||||
            {
 | 
			
		||||
              callee: CREATE_VNODE,
 | 
			
		||||
              arguments: [
 | 
			
		||||
                `"div"`,
 | 
			
		||||
                {
 | 
			
		||||
                  type: NodeTypes.SIMPLE_EXPRESSION,
 | 
			
		||||
                  content: `_hoisted_1`
 | 
			
		||||
                },
 | 
			
		||||
                `null`,
 | 
			
		||||
                genFlagText(PatchFlags.NEED_PATCH)
 | 
			
		||||
              ]
 | 
			
		||||
            },
 | 
			
		||||
            {
 | 
			
		||||
              type: NodeTypes.JS_ARRAY_EXPRESSION
 | 
			
		||||
            }
 | 
			
		||||
          ]
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    ])
 | 
			
		||||
    expect(generate(root).code).toMatchSnapshot()
 | 
			
		||||
  })
 | 
			
		||||
 | 
			
		||||
  test('hoist static props for elements with dynamic text children', () => {
 | 
			
		||||
    const { root, args } = transformWithHoist(
 | 
			
		||||
      `<div><div id="foo">{{ hello }}</div></div>`
 | 
			
		||||
    )
 | 
			
		||||
    expect(root.hoists).toMatchObject([createObjectMatcher({ id: 'foo' })])
 | 
			
		||||
    expect(args[2]).toMatchObject([
 | 
			
		||||
      {
 | 
			
		||||
        type: NodeTypes.ELEMENT,
 | 
			
		||||
        codegenNode: {
 | 
			
		||||
          callee: CREATE_VNODE,
 | 
			
		||||
          arguments: [
 | 
			
		||||
            `"div"`,
 | 
			
		||||
            { content: `_hoisted_1` },
 | 
			
		||||
            { type: NodeTypes.INTERPOLATION },
 | 
			
		||||
            genFlagText(PatchFlags.TEXT)
 | 
			
		||||
          ]
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    ])
 | 
			
		||||
    expect(generate(root).code).toMatchSnapshot()
 | 
			
		||||
  })
 | 
			
		||||
 | 
			
		||||
  test('hoist static props for elements with unhoistable children', () => {
 | 
			
		||||
    const { root, args } = transformWithHoist(
 | 
			
		||||
      `<div><div id="foo"><Comp/></div></div>`
 | 
			
		||||
    )
 | 
			
		||||
    expect(root.hoists).toMatchObject([createObjectMatcher({ id: 'foo' })])
 | 
			
		||||
    expect(args[2]).toMatchObject([
 | 
			
		||||
      {
 | 
			
		||||
        type: NodeTypes.ELEMENT,
 | 
			
		||||
        codegenNode: {
 | 
			
		||||
          callee: CREATE_VNODE,
 | 
			
		||||
          arguments: [
 | 
			
		||||
            `"div"`,
 | 
			
		||||
            { content: `_hoisted_1` },
 | 
			
		||||
            [{ type: NodeTypes.ELEMENT, tag: `Comp` }]
 | 
			
		||||
          ]
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    ])
 | 
			
		||||
    expect(generate(root).code).toMatchSnapshot()
 | 
			
		||||
  })
 | 
			
		||||
 | 
			
		||||
  test('should hoist v-if props/children if static', () => {
 | 
			
		||||
    const { root, args } = transformWithHoist(
 | 
			
		||||
      `<div><div v-if="ok" id="foo"><span/></div></div>`
 | 
			
		||||
    )
 | 
			
		||||
    expect(root.hoists).toMatchObject([
 | 
			
		||||
      createObjectMatcher({
 | 
			
		||||
        key: `[0]`, // key injected by v-if branch
 | 
			
		||||
        id: 'foo'
 | 
			
		||||
      }),
 | 
			
		||||
      {
 | 
			
		||||
        callee: CREATE_VNODE,
 | 
			
		||||
        arguments: [`"span"`]
 | 
			
		||||
      }
 | 
			
		||||
    ])
 | 
			
		||||
    expect(args[2][0].codegenNode).toMatchObject({
 | 
			
		||||
      type: NodeTypes.JS_SEQUENCE_EXPRESSION,
 | 
			
		||||
      expressions: [
 | 
			
		||||
        { callee: OPEN_BLOCK },
 | 
			
		||||
        {
 | 
			
		||||
          type: NodeTypes.JS_CONDITIONAL_EXPRESSION,
 | 
			
		||||
          consequent: {
 | 
			
		||||
            // blocks should NOT be hoisted
 | 
			
		||||
            callee: CREATE_BLOCK,
 | 
			
		||||
            arguments: [
 | 
			
		||||
              `"div"`,
 | 
			
		||||
              { content: `_hoisted_1` },
 | 
			
		||||
              [
 | 
			
		||||
                {
 | 
			
		||||
                  codegenNode: { content: `_hoisted_2` }
 | 
			
		||||
                }
 | 
			
		||||
              ]
 | 
			
		||||
            ]
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
      ]
 | 
			
		||||
    })
 | 
			
		||||
    expect(generate(root).code).toMatchSnapshot()
 | 
			
		||||
  })
 | 
			
		||||
 | 
			
		||||
  test('should hoist v-for children if static', () => {
 | 
			
		||||
    const { root, args } = transformWithHoist(
 | 
			
		||||
      `<div><div v-for="i in list" id="foo"><span/></div></div>`
 | 
			
		||||
    )
 | 
			
		||||
    expect(root.hoists).toMatchObject([
 | 
			
		||||
      createObjectMatcher({
 | 
			
		||||
        id: 'foo'
 | 
			
		||||
      }),
 | 
			
		||||
      {
 | 
			
		||||
        callee: CREATE_VNODE,
 | 
			
		||||
        arguments: [`"span"`]
 | 
			
		||||
      }
 | 
			
		||||
    ])
 | 
			
		||||
    const forBlockCodegen = args[2][0].codegenNode
 | 
			
		||||
    expect(forBlockCodegen).toMatchObject({
 | 
			
		||||
      type: NodeTypes.JS_SEQUENCE_EXPRESSION,
 | 
			
		||||
      expressions: [
 | 
			
		||||
        { callee: OPEN_BLOCK },
 | 
			
		||||
        {
 | 
			
		||||
          callee: CREATE_BLOCK,
 | 
			
		||||
          arguments: [
 | 
			
		||||
            FRAGMENT,
 | 
			
		||||
            `null`,
 | 
			
		||||
            {
 | 
			
		||||
              type: NodeTypes.JS_CALL_EXPRESSION,
 | 
			
		||||
              callee: RENDER_LIST
 | 
			
		||||
            },
 | 
			
		||||
            genFlagText(PatchFlags.UNKEYED_FRAGMENT)
 | 
			
		||||
          ]
 | 
			
		||||
        }
 | 
			
		||||
      ]
 | 
			
		||||
    })
 | 
			
		||||
    const innerBlockCodegen =
 | 
			
		||||
      forBlockCodegen.expressions[1].arguments[2].arguments[1].returns
 | 
			
		||||
    expect(innerBlockCodegen).toMatchObject({
 | 
			
		||||
      type: NodeTypes.JS_SEQUENCE_EXPRESSION,
 | 
			
		||||
      expressions: [
 | 
			
		||||
        { callee: OPEN_BLOCK },
 | 
			
		||||
        {
 | 
			
		||||
          callee: CREATE_BLOCK,
 | 
			
		||||
          arguments: [
 | 
			
		||||
            `"div"`,
 | 
			
		||||
            { content: `_hoisted_1` },
 | 
			
		||||
            [
 | 
			
		||||
              {
 | 
			
		||||
                codegenNode: { content: `_hoisted_2` }
 | 
			
		||||
              }
 | 
			
		||||
            ]
 | 
			
		||||
          ]
 | 
			
		||||
        }
 | 
			
		||||
      ]
 | 
			
		||||
    })
 | 
			
		||||
    expect(generate(root).code).toMatchSnapshot()
 | 
			
		||||
  })
 | 
			
		||||
})
 | 
			
		||||
@ -25,7 +25,7 @@ import { transformStyle } from '../../../compiler-dom/src/transforms/transformSt
 | 
			
		||||
import { transformOn } from '../../src/transforms/vOn'
 | 
			
		||||
import { transformBind } from '../../src/transforms/vBind'
 | 
			
		||||
import { PatchFlags } from '@vue/shared'
 | 
			
		||||
import { createObjectMatcher } from '../testUtils'
 | 
			
		||||
import { createObjectMatcher, genFlagText } from '../testUtils'
 | 
			
		||||
import { optimizeText } from '../../src/transforms/optimizeText'
 | 
			
		||||
 | 
			
		||||
function parseWithElementTransform(
 | 
			
		||||
@ -324,7 +324,7 @@ describe('compiler: element transform', () => {
 | 
			
		||||
          `"div"`,
 | 
			
		||||
          `null`,
 | 
			
		||||
          `null`,
 | 
			
		||||
          `${PatchFlags.NEED_PATCH} /* NEED_PATCH */` // should generate appropriate flag
 | 
			
		||||
          genFlagText(PatchFlags.NEED_PATCH) // should generate appropriate flag
 | 
			
		||||
        ]
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
@ -573,30 +573,30 @@ describe('compiler: element transform', () => {
 | 
			
		||||
 | 
			
		||||
      const { node: node2 } = parseWithBind(`<div>{{ foo }}</div>`)
 | 
			
		||||
      expect(node2.arguments.length).toBe(4)
 | 
			
		||||
      expect(node2.arguments[3]).toBe(`${PatchFlags.TEXT} /* TEXT */`)
 | 
			
		||||
      expect(node2.arguments[3]).toBe(genFlagText(PatchFlags.TEXT))
 | 
			
		||||
 | 
			
		||||
      // multiple nodes, merged with optimize text
 | 
			
		||||
      const { node: node3 } = parseWithBind(`<div>foo {{ bar }} baz</div>`)
 | 
			
		||||
      expect(node3.arguments.length).toBe(4)
 | 
			
		||||
      expect(node3.arguments[3]).toBe(`${PatchFlags.TEXT} /* TEXT */`)
 | 
			
		||||
      expect(node3.arguments[3]).toBe(genFlagText(PatchFlags.TEXT))
 | 
			
		||||
    })
 | 
			
		||||
 | 
			
		||||
    test('CLASS', () => {
 | 
			
		||||
      const { node } = parseWithBind(`<div :class="foo" />`)
 | 
			
		||||
      expect(node.arguments.length).toBe(4)
 | 
			
		||||
      expect(node.arguments[3]).toBe(`${PatchFlags.CLASS} /* CLASS */`)
 | 
			
		||||
      expect(node.arguments[3]).toBe(genFlagText(PatchFlags.CLASS))
 | 
			
		||||
    })
 | 
			
		||||
 | 
			
		||||
    test('STYLE', () => {
 | 
			
		||||
      const { node } = parseWithBind(`<div :style="foo" />`)
 | 
			
		||||
      expect(node.arguments.length).toBe(4)
 | 
			
		||||
      expect(node.arguments[3]).toBe(`${PatchFlags.STYLE} /* STYLE */`)
 | 
			
		||||
      expect(node.arguments[3]).toBe(genFlagText(PatchFlags.STYLE))
 | 
			
		||||
    })
 | 
			
		||||
 | 
			
		||||
    test('PROPS', () => {
 | 
			
		||||
      const { node } = parseWithBind(`<div id="foo" :foo="bar" :baz="qux" />`)
 | 
			
		||||
      expect(node.arguments.length).toBe(5)
 | 
			
		||||
      expect(node.arguments[3]).toBe(`${PatchFlags.PROPS} /* PROPS */`)
 | 
			
		||||
      expect(node.arguments[3]).toBe(genFlagText(PatchFlags.PROPS))
 | 
			
		||||
      expect(node.arguments[4]).toBe(`["foo", "baz"]`)
 | 
			
		||||
    })
 | 
			
		||||
 | 
			
		||||
@ -606,9 +606,7 @@ describe('compiler: element transform', () => {
 | 
			
		||||
      )
 | 
			
		||||
      expect(node.arguments.length).toBe(5)
 | 
			
		||||
      expect(node.arguments[3]).toBe(
 | 
			
		||||
        `${PatchFlags.PROPS |
 | 
			
		||||
          PatchFlags.CLASS |
 | 
			
		||||
          PatchFlags.STYLE} /* CLASS, STYLE, PROPS */`
 | 
			
		||||
        genFlagText([PatchFlags.CLASS, PatchFlags.STYLE, PatchFlags.PROPS])
 | 
			
		||||
      )
 | 
			
		||||
      expect(node.arguments[4]).toBe(`["foo", "baz"]`)
 | 
			
		||||
    })
 | 
			
		||||
@ -616,17 +614,13 @@ describe('compiler: element transform', () => {
 | 
			
		||||
    test('FULL_PROPS (v-bind)', () => {
 | 
			
		||||
      const { node } = parseWithBind(`<div v-bind="foo" />`)
 | 
			
		||||
      expect(node.arguments.length).toBe(4)
 | 
			
		||||
      expect(node.arguments[3]).toBe(
 | 
			
		||||
        `${PatchFlags.FULL_PROPS} /* FULL_PROPS */`
 | 
			
		||||
      )
 | 
			
		||||
      expect(node.arguments[3]).toBe(genFlagText(PatchFlags.FULL_PROPS))
 | 
			
		||||
    })
 | 
			
		||||
 | 
			
		||||
    test('FULL_PROPS (dynamic key)', () => {
 | 
			
		||||
      const { node } = parseWithBind(`<div :[foo]="bar" />`)
 | 
			
		||||
      expect(node.arguments.length).toBe(4)
 | 
			
		||||
      expect(node.arguments[3]).toBe(
 | 
			
		||||
        `${PatchFlags.FULL_PROPS} /* FULL_PROPS */`
 | 
			
		||||
      )
 | 
			
		||||
      expect(node.arguments[3]).toBe(genFlagText(PatchFlags.FULL_PROPS))
 | 
			
		||||
    })
 | 
			
		||||
 | 
			
		||||
    test('FULL_PROPS (w/ others)', () => {
 | 
			
		||||
@ -634,34 +628,26 @@ describe('compiler: element transform', () => {
 | 
			
		||||
        `<div id="foo" v-bind="bar" :class="cls" />`
 | 
			
		||||
      )
 | 
			
		||||
      expect(node.arguments.length).toBe(4)
 | 
			
		||||
      expect(node.arguments[3]).toBe(
 | 
			
		||||
        `${PatchFlags.FULL_PROPS} /* FULL_PROPS */`
 | 
			
		||||
      )
 | 
			
		||||
      expect(node.arguments[3]).toBe(genFlagText(PatchFlags.FULL_PROPS))
 | 
			
		||||
    })
 | 
			
		||||
 | 
			
		||||
    test('NEED_PATCH (static ref)', () => {
 | 
			
		||||
      const { node } = parseWithBind(`<div ref="foo" />`)
 | 
			
		||||
      expect(node.arguments.length).toBe(4)
 | 
			
		||||
      expect(node.arguments[3]).toBe(
 | 
			
		||||
        `${PatchFlags.NEED_PATCH} /* NEED_PATCH */`
 | 
			
		||||
      )
 | 
			
		||||
      expect(node.arguments[3]).toBe(genFlagText(PatchFlags.NEED_PATCH))
 | 
			
		||||
    })
 | 
			
		||||
 | 
			
		||||
    test('NEED_PATCH (dynamic ref)', () => {
 | 
			
		||||
      const { node } = parseWithBind(`<div :ref="foo" />`)
 | 
			
		||||
      expect(node.arguments.length).toBe(4)
 | 
			
		||||
      expect(node.arguments[3]).toBe(
 | 
			
		||||
        `${PatchFlags.NEED_PATCH} /* NEED_PATCH */`
 | 
			
		||||
      )
 | 
			
		||||
      expect(node.arguments[3]).toBe(genFlagText(PatchFlags.NEED_PATCH))
 | 
			
		||||
    })
 | 
			
		||||
 | 
			
		||||
    test('NEED_PATCH (custom directives)', () => {
 | 
			
		||||
      const { node } = parseWithBind(`<div v-foo />`)
 | 
			
		||||
      const vnodeCall = node.arguments[0] as CallExpression
 | 
			
		||||
      expect(vnodeCall.arguments.length).toBe(4)
 | 
			
		||||
      expect(vnodeCall.arguments[3]).toBe(
 | 
			
		||||
        `${PatchFlags.NEED_PATCH} /* NEED_PATCH */`
 | 
			
		||||
      )
 | 
			
		||||
      expect(vnodeCall.arguments[3]).toBe(genFlagText(PatchFlags.NEED_PATCH))
 | 
			
		||||
    })
 | 
			
		||||
  })
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
@ -25,8 +25,7 @@ import {
 | 
			
		||||
  RENDER_SLOT
 | 
			
		||||
} from '../../src/runtimeHelpers'
 | 
			
		||||
import { PatchFlags } from '@vue/runtime-dom'
 | 
			
		||||
import { PatchFlagNames } from '@vue/shared'
 | 
			
		||||
import { createObjectMatcher } from '../testUtils'
 | 
			
		||||
import { createObjectMatcher, genFlagText } from '../testUtils'
 | 
			
		||||
 | 
			
		||||
function parseWithForTransform(
 | 
			
		||||
  template: string,
 | 
			
		||||
@ -609,12 +608,8 @@ describe('compiler: v-for', () => {
 | 
			
		||||
                ]
 | 
			
		||||
              },
 | 
			
		||||
              keyed
 | 
			
		||||
                ? `${PatchFlags.KEYED_FRAGMENT} /* ${
 | 
			
		||||
                    PatchFlagNames[PatchFlags.KEYED_FRAGMENT]
 | 
			
		||||
                  } */`
 | 
			
		||||
                : `${PatchFlags.UNKEYED_FRAGMENT} /* ${
 | 
			
		||||
                    PatchFlagNames[PatchFlags.UNKEYED_FRAGMENT]
 | 
			
		||||
                  } */`
 | 
			
		||||
                ? genFlagText(PatchFlags.KEYED_FRAGMENT)
 | 
			
		||||
                : genFlagText(PatchFlags.UNKEYED_FRAGMENT)
 | 
			
		||||
            ]
 | 
			
		||||
          }
 | 
			
		||||
        ]
 | 
			
		||||
@ -842,9 +837,7 @@ describe('compiler: v-for', () => {
 | 
			
		||||
                    }
 | 
			
		||||
                  ]
 | 
			
		||||
                },
 | 
			
		||||
                `${PatchFlags.UNKEYED_FRAGMENT} /* ${
 | 
			
		||||
                  PatchFlagNames[PatchFlags.UNKEYED_FRAGMENT]
 | 
			
		||||
                } */`
 | 
			
		||||
                genFlagText(PatchFlags.UNKEYED_FRAGMENT)
 | 
			
		||||
              ]
 | 
			
		||||
            }
 | 
			
		||||
          }
 | 
			
		||||
 | 
			
		||||
@ -18,8 +18,8 @@ import {
 | 
			
		||||
  trackVForSlotScopes
 | 
			
		||||
} from '../../src/transforms/vSlot'
 | 
			
		||||
import { CREATE_SLOTS, RENDER_LIST } from '../../src/runtimeHelpers'
 | 
			
		||||
import { createObjectMatcher } from '../testUtils'
 | 
			
		||||
import { PatchFlags, PatchFlagNames } from '@vue/shared'
 | 
			
		||||
import { createObjectMatcher, genFlagText } from '../testUtils'
 | 
			
		||||
import { PatchFlags } from '@vue/shared'
 | 
			
		||||
import { transformFor } from '../../src/transforms/vFor'
 | 
			
		||||
import { transformIf } from '../../src/transforms/vIf'
 | 
			
		||||
 | 
			
		||||
@ -308,9 +308,7 @@ describe('compiler: transform component slots', () => {
 | 
			
		||||
                  }),
 | 
			
		||||
                  // nested slot should be forced dynamic, since scope variables
 | 
			
		||||
                  // are not tracked as dependencies of the slot.
 | 
			
		||||
                  `${PatchFlags.DYNAMIC_SLOTS} /* ${
 | 
			
		||||
                    PatchFlagNames[PatchFlags.DYNAMIC_SLOTS]
 | 
			
		||||
                  } */`
 | 
			
		||||
                  genFlagText(PatchFlags.DYNAMIC_SLOTS)
 | 
			
		||||
                ]
 | 
			
		||||
              }
 | 
			
		||||
            },
 | 
			
		||||
 | 
			
		||||
@ -136,6 +136,10 @@ export interface SlotOutletNode extends BaseElementNode {
 | 
			
		||||
 | 
			
		||||
export interface TemplateNode extends BaseElementNode {
 | 
			
		||||
  tagType: ElementTypes.TEMPLATE
 | 
			
		||||
  codegenNode:
 | 
			
		||||
    | ElementCodegenNode
 | 
			
		||||
    | CodegenNodeWithDirective<ElementCodegenNode>
 | 
			
		||||
    | undefined
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export interface TextNode extends Node {
 | 
			
		||||
@ -278,8 +282,7 @@ export interface ConditionalExpression extends Node {
 | 
			
		||||
// createVNode(...)
 | 
			
		||||
export interface ElementCodegenNode extends CallExpression {
 | 
			
		||||
  callee: typeof CREATE_VNODE
 | 
			
		||||
  arguments: // tag, props, children, patchFlag, dynamicProps
 | 
			
		||||
 | 
			
		||||
  arguments:  // tag, props, children, patchFlag, dynamicProps
 | 
			
		||||
    | [string | RuntimeHelper]
 | 
			
		||||
    | [string | RuntimeHelper, PropsExpression]
 | 
			
		||||
    | [string | RuntimeHelper, 'null' | PropsExpression, TemplateChildNode[]]
 | 
			
		||||
@ -305,8 +308,7 @@ export type ElementCodegenNodeWithDirective = CodegenNodeWithDirective<
 | 
			
		||||
// createVNode(...)
 | 
			
		||||
export interface ComponentCodegenNode extends CallExpression {
 | 
			
		||||
  callee: typeof CREATE_VNODE
 | 
			
		||||
  arguments: // Comp, props, slots, patchFlag, dynamicProps
 | 
			
		||||
 | 
			
		||||
  arguments:  // Comp, props, slots, patchFlag, dynamicProps
 | 
			
		||||
    | [string | RuntimeHelper]
 | 
			
		||||
    | [string | RuntimeHelper, PropsExpression]
 | 
			
		||||
    | [string | RuntimeHelper, 'null' | PropsExpression, SlotsExpression]
 | 
			
		||||
@ -394,8 +396,7 @@ export interface DirectiveArguments extends ArrayExpression {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export interface DirectiveArgumentNode extends ArrayExpression {
 | 
			
		||||
  elements: // dir, exp, arg, modifiers
 | 
			
		||||
 | 
			
		||||
  elements:  // dir, exp, arg, modifiers
 | 
			
		||||
    | [string]
 | 
			
		||||
    | [string, ExpressionNode]
 | 
			
		||||
    | [string, ExpressionNode, ExpressionNode]
 | 
			
		||||
@ -405,8 +406,7 @@ export interface DirectiveArgumentNode extends ArrayExpression {
 | 
			
		||||
// renderSlot(...)
 | 
			
		||||
export interface SlotOutletCodegenNode extends CallExpression {
 | 
			
		||||
  callee: typeof RENDER_SLOT
 | 
			
		||||
  arguments: // $slots, name, props, fallback
 | 
			
		||||
 | 
			
		||||
  arguments:  // $slots, name, props, fallback
 | 
			
		||||
    | [string, string | ExpressionNode]
 | 
			
		||||
    | [string, string | ExpressionNode, PropsExpression]
 | 
			
		||||
    | [
 | 
			
		||||
@ -557,7 +557,7 @@ type InferCodegenNodeType<T> = T extends typeof CREATE_VNODE
 | 
			
		||||
  : T extends typeof CREATE_BLOCK
 | 
			
		||||
    ? BlockElementCodegenNode | BlockComponentCodegenNode
 | 
			
		||||
    : T extends typeof APPLY_DIRECTIVES
 | 
			
		||||
      ? CodegenNodeWithDirective<ElementCodegenNode | ComponentCodegenNode>
 | 
			
		||||
      ? ElementCodegenNodeWithDirective | CompoenntCodegenNodeWithDirective
 | 
			
		||||
      : T extends typeof RENDER_SLOT ? SlotOutletCodegenNode : CallExpression
 | 
			
		||||
 | 
			
		||||
export function createCallExpression<T extends CallExpression['callee']>(
 | 
			
		||||
 | 
			
		||||
@ -22,8 +22,8 @@ import {
 | 
			
		||||
  RuntimeHelper,
 | 
			
		||||
  helperNameMap
 | 
			
		||||
} from './runtimeHelpers'
 | 
			
		||||
import { isVSlot, createBlockExpression, isSlotOutlet } from './utils'
 | 
			
		||||
import { hoistStatic } from './transforms/hoistStatic'
 | 
			
		||||
import { isVSlot, createBlockExpression } from './utils'
 | 
			
		||||
import { hoistStatic, isSingleElementRoot } from './transforms/hoistStatic'
 | 
			
		||||
 | 
			
		||||
// There are two types of transforms:
 | 
			
		||||
//
 | 
			
		||||
@ -229,26 +229,19 @@ export function transform(root: RootNode, options: TransformOptions) {
 | 
			
		||||
function finalizeRoot(root: RootNode, context: TransformContext) {
 | 
			
		||||
  const { helper } = context
 | 
			
		||||
  const { children } = root
 | 
			
		||||
  if (children.length === 1) {
 | 
			
		||||
    const child = children[0]
 | 
			
		||||
    if (
 | 
			
		||||
      child.type === NodeTypes.ELEMENT &&
 | 
			
		||||
      !isSlotOutlet(child) &&
 | 
			
		||||
      child.codegenNode &&
 | 
			
		||||
      child.codegenNode.type === NodeTypes.JS_CALL_EXPRESSION
 | 
			
		||||
    ) {
 | 
			
		||||
      // turn root element into a block
 | 
			
		||||
      root.codegenNode = createBlockExpression(
 | 
			
		||||
        child.codegenNode!.arguments,
 | 
			
		||||
        context
 | 
			
		||||
      )
 | 
			
		||||
    } else {
 | 
			
		||||
      // - single <slot/>, IfNode, ForNode: already blocks.
 | 
			
		||||
      // - single text node: always patched.
 | 
			
		||||
      // - transform calls without transformElement (only during tests)
 | 
			
		||||
      // Just generate the node as-is
 | 
			
		||||
      root.codegenNode = child
 | 
			
		||||
    }
 | 
			
		||||
  const child = children[0]
 | 
			
		||||
  if (isSingleElementRoot(root, child) && child.codegenNode) {
 | 
			
		||||
    // turn root element into a block
 | 
			
		||||
    root.codegenNode = createBlockExpression(
 | 
			
		||||
      child.codegenNode.arguments,
 | 
			
		||||
      context
 | 
			
		||||
    )
 | 
			
		||||
  } else if (children.length === 1) {
 | 
			
		||||
    // - single <slot/>, IfNode, ForNode: already blocks.
 | 
			
		||||
    // - single text node: always patched.
 | 
			
		||||
    // - transform calls without transformElement (only during tests)
 | 
			
		||||
    // Just generate the node as-is
 | 
			
		||||
    root.codegenNode = child
 | 
			
		||||
  } else if (children.length > 1) {
 | 
			
		||||
    // root has multiple nodes - return a fragment block.
 | 
			
		||||
    root.codegenNode = createBlockExpression(
 | 
			
		||||
@ -256,7 +249,6 @@ function finalizeRoot(root: RootNode, context: TransformContext) {
 | 
			
		||||
      context
 | 
			
		||||
    )
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // finalize meta information
 | 
			
		||||
  root.helpers = [...context.helpers]
 | 
			
		||||
  root.components = [...context.components]
 | 
			
		||||
 | 
			
		||||
@ -5,20 +5,42 @@ import {
 | 
			
		||||
  ElementNode,
 | 
			
		||||
  ElementTypes,
 | 
			
		||||
  ElementCodegenNode,
 | 
			
		||||
  ElementCodegenNodeWithDirective
 | 
			
		||||
  ElementCodegenNodeWithDirective,
 | 
			
		||||
  PlainElementNode,
 | 
			
		||||
  ComponentNode,
 | 
			
		||||
  TemplateNode
 | 
			
		||||
} from '../ast'
 | 
			
		||||
import { TransformContext } from '../transform'
 | 
			
		||||
import { APPLY_DIRECTIVES } from '../runtimeHelpers'
 | 
			
		||||
import { PatchFlags } from '@vue/shared'
 | 
			
		||||
import { isSlotOutlet } from '../utils'
 | 
			
		||||
 | 
			
		||||
export function hoistStatic(root: RootNode, context: TransformContext) {
 | 
			
		||||
  walk(root.children, context, new Map<TemplateChildNode, boolean>())
 | 
			
		||||
  walk(
 | 
			
		||||
    root.children,
 | 
			
		||||
    context,
 | 
			
		||||
    new Map(),
 | 
			
		||||
    isSingleElementRoot(root, root.children[0])
 | 
			
		||||
  )
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function isSingleElementRoot(
 | 
			
		||||
  root: RootNode,
 | 
			
		||||
  child: TemplateChildNode
 | 
			
		||||
): child is PlainElementNode | ComponentNode | TemplateNode {
 | 
			
		||||
  const { children } = root
 | 
			
		||||
  return (
 | 
			
		||||
    children.length === 1 &&
 | 
			
		||||
    child.type === NodeTypes.ELEMENT &&
 | 
			
		||||
    !isSlotOutlet(child)
 | 
			
		||||
  )
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function walk(
 | 
			
		||||
  children: TemplateChildNode[],
 | 
			
		||||
  context: TransformContext,
 | 
			
		||||
  resultCache: Map<TemplateChildNode, boolean>
 | 
			
		||||
  resultCache: Map<TemplateChildNode, boolean>,
 | 
			
		||||
  doNotHoistNode: boolean = false
 | 
			
		||||
) {
 | 
			
		||||
  for (let i = 0; i < children.length; i++) {
 | 
			
		||||
    const child = children[i]
 | 
			
		||||
@ -27,7 +49,7 @@ function walk(
 | 
			
		||||
      child.type === NodeTypes.ELEMENT &&
 | 
			
		||||
      child.tagType === ElementTypes.ELEMENT
 | 
			
		||||
    ) {
 | 
			
		||||
      if (isStaticNode(child, resultCache)) {
 | 
			
		||||
      if (!doNotHoistNode && isStaticNode(child, resultCache)) {
 | 
			
		||||
        // whole tree is static
 | 
			
		||||
        ;(child as any).codegenNode = context.hoist(child.codegenNode!)
 | 
			
		||||
        continue
 | 
			
		||||
@ -51,11 +73,16 @@ function walk(
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    if (child.type === NodeTypes.ELEMENT || child.type === NodeTypes.FOR) {
 | 
			
		||||
    if (child.type === NodeTypes.ELEMENT) {
 | 
			
		||||
      walk(child.children, context, resultCache)
 | 
			
		||||
    } else if (child.type === NodeTypes.FOR) {
 | 
			
		||||
      // Do not hoist v-for single child because it has to be a block
 | 
			
		||||
      walk(child.children, context, resultCache, child.children.length === 1)
 | 
			
		||||
    } else if (child.type === NodeTypes.IF) {
 | 
			
		||||
      for (let i = 0; i < child.branches.length; i++) {
 | 
			
		||||
        walk(child.branches[i].children, context, resultCache)
 | 
			
		||||
        const branchChildren = child.branches[i].children
 | 
			
		||||
        // Do not hoist v-if single child because it has to be a block
 | 
			
		||||
        walk(branchChildren, context, resultCache, branchChildren.length === 1)
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user