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), []))])) } } } }