resolve-scoped-slots.js 3.3 KB
Newer Older
1 2 3
const t = require('@babel/types')

const {
4
  METHOD_BUILT_IN,
5 6
  METHOD_CREATE_EMPTY_VNODE,
  METHOD_CREATE_ELEMENT
7 8
} = require('../../constants')

9 10 11 12
function needSlotMode (path, ids) {
  let need
  path.traverse({
    noScope: false,
13 14 15 16 17 18 19 20 21
    Property (path) {
      // 跳过事件
      if (path.node.key.name === 'on') {
        const parentPath = path.parentPath.parentPath
        if (t.isCallExpression(parentPath) && parentPath.node.callee.name === METHOD_CREATE_ELEMENT) {
          path.skip()
        }
      }
    },
22 23 24
    Identifier (path) {
      const name = path.node.name
      if (path.key !== 'key' && (path.key !== 'property' || path.parent.computed)) {
25
        // 使用作用域内方法或作用域外数据
26 27 28 29 30 31 32 33 34 35 36
        if (name in ids) {
          need = path.key === 'callee' ? true : need
        } else if (!path.scope.hasBinding(name) && !METHOD_BUILT_IN.includes(name)) {
          need = true
        }
      }
    }
  })
  return need
}

37 38
function replaceId (path, ids) {
  let replaced
39
  const fnPath = path.parentPath
40
  path.traverse({
41
    noScope: false,
42 43
    Identifier (path) {
      const name = path.node.name
44
      if (name in ids && path.key !== 'key' && (path.key !== 'property' || path.parent.computed) && path.scope.path === fnPath) {
45 46 47 48 49 50 51 52 53
        path.replaceWith(ids[name])
        replaced = true
      }
    }
  })
  return replaced
}

module.exports = function getResolveScopedSlots (parent, state) {
54 55 56 57 58 59
  let objectPath = parent.get('arguments.0.elements.0')
  // TODO v-else
  if (objectPath.isConditionalExpression()) {
    objectPath = objectPath.get('consequent')
  }
  const properties = objectPath.get('properties')
60 61 62 63 64
  const fn = properties.find(path => path.get('key').isIdentifier({ name: 'fn' }))
  const params = fn.get('value.params.0')
  if (!params) {
    return
  }
65
  const vueId = parent.parentPath.parentPath.get('properties').find(path => path.get('key').isIdentifier({ name: 'attrs' })).get('value').get('properties').find(path => path.get('key').isStringLiteral({ value: 'vue-id' })).get('value').node
66 67 68
  const slot = properties.find(path => path.get('key').isIdentifier({ name: 'key' })).get('value').node.value
  const ids = {}
  function updateIds (vueId, slot, value, key) {
69
    const array = [vueId, t.stringLiteral(slot)]
70 71 72 73 74 75 76 77 78 79 80 81 82
    if (key) {
      array.push(t.stringLiteral(key))
    }
    ids[value] = t.callExpression(t.identifier('$getScopedSlotsParams'), array)
  }
  if (params.isObjectPattern()) {
    params.get('properties').forEach(prop => {
      updateIds(vueId, slot, prop.get('value').node.name, prop.get('key').node.name)
    })
  } else if (params.isIdentifier()) {
    updateIds(vueId, slot, params.node.name)
  }
  const fnBody = fn.get('value.body')
83
  if (state.options.scopedSlotsCompiler === 'augmented' || needSlotMode(fnBody, ids)) {
84 85 86 87 88 89 90
    if (replaceId(fnBody, ids)) {
      const orgin = fnBody.get('body.0.argument')
      const elements = orgin.get('elements')
      const node = (elements.length === 1 ? elements[0] : orgin).node
      const test = t.callExpression(t.identifier('$hasScopedSlotsParams'), [vueId])
      orgin.replaceWith(t.arrayExpression([t.conditionalExpression(test, node, t.callExpression(t.identifier(METHOD_CREATE_EMPTY_VNODE), []))]))
      // scopedSlotsCompiler auto
91
      objectPath.node.scopedSlotsCompiler = 'augmented'
92
    }
93 94
  }
}