'a'
mh-two-thousand-and-two
2024-04-12 44d2c92345cd156a59fc327b3060292a282d2893
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
const t = require('@babel/types')
const template = require('@babel/template').default
 
const {
  METHOD_BUILT_IN,
  METHOD_CREATE_EMPTY_VNODE,
  METHOD_CREATE_ELEMENT,
  METHOD_RENDER_LIST
} = require('../../constants')
 
function findBinding (fnPath, idPath) {
  const name = idPath.node.name
  return fnPath.scope.bindings[name].referencePaths.find(refPath => refPath === idPath)
}
 
function needAugmentedSlotMode (path, ids, state) {
  const platformName = state.options.platform.name
  const fnPath = path.parentPath
  let need
  path.traverse({
    noScope: false,
    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()
        }
      }
    },
    Identifier (path) {
      const name = path.node.name
      if (path.key !== 'key' && (path.key !== 'property' || path.parent.computed)) {
        // 使用作用域内方法或作用域外数据
        if (name in ids) {
          if (path.key === 'callee') {
            need = true
          }
        } else if (!path.scope.hasBinding(name) && !METHOD_BUILT_IN.includes(name)) {
          // 原生支持作用域插槽的平台允许使用作用域外数据,暂时只考虑作用内的数据作为方法参数的情况
          if (['mp-baidu', 'mp-alipay'].includes(platformName)) {
            if (path.key === 'callee') {
              path.parentPath.traverse({
                noScope: false,
                Identifier (path) {
                  const name = path.node.name
                  if (name in ids && findBinding(fnPath, path)) {
                    need = true
                    path.stop()
                  }
                }
              })
            }
          } else {
            need = true
          }
        }
      } else if (platformName === 'mp-weixin' && path.key === 'property' && name === 'length') {
        // 微信小程序平台无法观测 Array length 访问:https://developers.weixin.qq.com/community/develop/doc/000c8ee47d87a0d5b6685a8cb57000
        need = true
      }
      if (need) {
        path.stop()
      }
    }
  })
  return need
}
 
function replaceId (path, ids) {
  let replaced
  const fnPath = path.parentPath
  path.traverse({
    noScope: false,
    Identifier (path) {
      const name = path.node.name
      if (name in ids && findBinding(fnPath, path)) {
        path.replaceWith(t.cloneNode(ids[name], true))
        replaced = true
      }
    }
  })
  return replaced
}
 
module.exports = function getResolveScopedSlots (parent, state) {
  const elements0 = parent.get('arguments.0.elements.0')
  let objectPath = elements0
  // TODO v-else
  if (objectPath.isConditionalExpression()) {
    objectPath = objectPath.get('consequent')
  }
  if (objectPath.isCallExpression()) {
    objectPath = objectPath.get('arguments.1.body.body.0.argument')
  }
  const properties = objectPath.get('properties')
  const fn = properties.find(path => path.get('key').isIdentifier({ name: 'fn' }))
  const params = fn.get('value.params.0')
  if (!params) {
    return
  }
  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
  // TODO 多层 v-for 嵌套时,后续处理作用域可能发生变化,需安全重命名
  const slotPath = properties.find(path => path.get('key').isIdentifier({ name: 'key' })).get('value')
  const slotNode = slotPath.node
  const slotMultipleInstance = state.options.scopedSlotsCompiler === 'augmented' && state.options.slotMultipleInstance
  const scopedSlotsParams = {
    item: elements0.scope.generateUidIdentifier('item'),
    index: elements0.scope.generateUidIdentifier('index')
  }
  const ids = {}
  function updateIds (vueId, slot, value, key) {
    let node = slotMultipleInstance ? scopedSlotsParams.item : t.callExpression(t.identifier('$getSSP'), [vueId, slot])
    if (key) {
      node = t.memberExpression(node, t.stringLiteral(key), true)
    }
    ids[value] = node
  }
  if (params.isObjectPattern()) {
    params.get('properties').forEach(prop => {
      updateIds(vueId, slotNode, prop.get('value').node.name, prop.get('key').node.name)
    })
  } else if (params.isIdentifier()) {
    updateIds(vueId, slotNode, params.node.name)
  }
  const fnBody = fn.get('value.body')
  // 非原生支持作用域插槽的平台在含有动态 slotName 的情况下,scopedSlotsCompiler 指定使用增强编译模式
  const isStaticSlotName = t.isStringLiteral(slotNode)
  if (state.options.scopedSlotsCompiler === 'augmented' || needAugmentedSlotMode(fnBody, ids, state) || (!['mp-baidu', 'mp-alipay'].includes(state.options.platform.name) && !isStaticSlotName)) {
    if (replaceId(fnBody, ids)) {
      const test = t.callExpression(t.identifier('$hasSSP'), [vueId])
      // scopedSlotsCompiler auto
      objectPath.node.scopedSlotsCompiler = 'augmented'
      if (slotMultipleInstance) {
        // elements0 节点替换增加一层循环
        let node = elements0.node
        const builder = template(`${METHOD_RENDER_LIST}($getSSP(%%vueId%%, %%slot%%, true), function (%%item%%, %%index%%) {return %%node%%})`)
        node = builder({
          vueId,
          slot: slotNode,
          node,
          item: scopedSlotsParams.item,
          index: scopedSlotsParams.index
        }).expression
        node = t.conditionalExpression(test, node, t.callExpression(t.identifier(METHOD_CREATE_EMPTY_VNODE), []))
        elements0.replaceWith(node)
        // 插槽名拼接 '.'+index
        // 百度、字节小程序不支持 v-for 嵌套 slot,且支持渲染多个实例,固定输出到第一个
        const indexNode = ['mp-baidu', 'mp-toutiao'].includes(state.options.platform.name) ? t.numericLiteral(0) : scopedSlotsParams.index
        slotPath.replaceWith(t.binaryExpression('+', slotNode, t.binaryExpression('+', t.stringLiteral('.'), indexNode)))
      } else {
        const orgin = fnBody.get('body.0.argument')
        const elements = orgin.get('elements')
        const node = (elements.length === 1 ? elements[0] : orgin).node
        orgin.replaceWith(t.arrayExpression([t.conditionalExpression(test, node, t.callExpression(t.identifier(METHOD_CREATE_EMPTY_VNODE), []))]))
      }
    }
  }
}