const t = require('@babel/types') const uniI18n = require('@dcloudio/uni-cli-i18n') const { METHOD_CREATE_ELEMENT, METHOD_TO_STRING, METHOD_RENDER_LIST, METHOD_BUILT_IN, METHOD_RESOLVE_FILTER, METHOD_RENDER_SLOT, METHOD_RESOLVE_SCOPED_SLOTS, IDENTIFIER_FILTER, IDENTIFIER_METHOD, IDENTIFIER_GLOBAL, IDENTIFIER_TEXT } = require('../../constants') const { getTagName } = require('../../h5') const { hasOwn, hyphenate, traverseFilter, getComponentName, hasEscapeQuote, hasLengthProperty, isRootElement } = require('../../util') const traverseData = require('./data') const traverseRenderList = require('./render-list') const getMemberExpr = require('./member-expr') const getRenderSlot = require('./render-slot') const getResolveScopedSlots = require('./resolve-scoped-slots') function addStaticClass (path, staticClass) { const dataPath = path.get('arguments.1') if (dataPath && dataPath.isObjectExpression()) { const staticClassProperty = dataPath.node.properties.find(property => property.key.name === 'staticClass') if (staticClassProperty) { // update staticClassProperty.value.value = staticClassProperty.value.value + ' ' + staticClass } else { // add dataPath.node.properties.push( t.objectProperty(t.identifier('staticClass'), t.stringLiteral(staticClass)) ) } } else { // {staticClass:'data-v-aaa'} const args = path.node.arguments args.splice(1, 0, t.objectExpression( [ t.objectProperty(t.identifier('staticClass'), t.stringLiteral(staticClass)) ] )) } } function addVueId (path, state) { // const platformName = state.options.platform.name // if ( // 暂不对 mp-weixin,app-plus 增加 vueId // platformName === 'mp-weixin' || // platformName === 'app-plus' // ) { // return // } if (!hasOwn(state.options, '$vueId')) { state.options.$vueId = 1 } const hashId = state.options.hashId const vueId = (hashId ? (hashId + '-') : '') + (state.options.$vueId++) let value if (state.scoped.length) { const scopeds = state.scoped const len = scopeds.length if (len > 1) { // v-for 嵌套,forIndex 不允许重复 const forIndexSet = new Set() for (let i = 0; i < len; i++) { const scoped = scopeds[i] forIndexSet.add(scoped.forIndex) if (forIndexSet.size !== i + 1) { state.errors.add(uniI18n.__('templateCompiler.forNestedIndexNameNoArrowRepeat', { 0: 'v-for', 1: scoped.forIndex })) break } } } for (let i = len - 1; i >= 0; i--) { const scoped = scopeds[i] if (!value) { value = t.binaryExpression('+', t.stringLiteral(vueId + '-'), t.identifier(scoped.forIndex)) } else { value = t.binaryExpression('+', t.binaryExpression('+', value, t.stringLiteral('-')), t.identifier(scoped.forIndex) ) } } } else { value = t.stringLiteral(vueId) } const objectProperty = t.objectProperty( t.stringLiteral('vue-id'), value ) const dataPath = path.get('arguments.1') if (dataPath && dataPath.isObjectExpression()) { const attrsProperty = dataPath.node.properties.find(property => property.key.name === 'attrs') if (attrsProperty) { attrsProperty.value.properties.unshift(objectProperty) } else { dataPath.node.properties.push( t.objectProperty(t.identifier('attrs'), t.objectExpression([ objectProperty ])) ) } } else { // {attrs:{'vue-id':'2'}} const args = path.node.arguments args.splice(1, 0, t.objectExpression( [ t.objectProperty(t.identifier('attrs'), t.objectExpression([ objectProperty ])) ] )) } } function checkUsingGlobalComponents (name, globalUsingComponents, state) { if (globalUsingComponents && globalUsingComponents[name]) { if (!state.options.usingGlobalComponents) { state.options.usingGlobalComponents = Object.create(null) } state.options.usingGlobalComponents[name] = globalUsingComponents[name] } } module.exports = { noScope: false, MemberExpression (path) { if ( // t.m(123) t.isIdentifier(path.node.object) && this.options.filterModules.includes(path.node.object.name) ) { path.skip() } // 微信小程序平台无法观测 Array length 访问:https://developers.weixin.qq.com/community/develop/doc/000c8ee47d87a0d5b6685a8cb57000 if (this.options.platform.name === 'mp-weixin' && hasLengthProperty(path)) { let newPath = path while (newPath) { path = newPath newPath = path.findParent((path) => path.isLogicalExpression()) } path.skip() if (path.findParent((path) => path.shouldSkip || (this.options.scopedSlotsCompiler === 'legacy' && path.isCallExpression() && path.node.callee.name === METHOD_RESOLVE_SCOPED_SLOTS))) { return } path.replaceWith(getMemberExpr(path, IDENTIFIER_GLOBAL, path.node, this)) } }, CallExpression (path) { const callee = path.node.callee if (traverseFilter(callee, this)) { return path.skip() } else if (t.isIdentifier(callee)) { const methodName = callee.name switch (methodName) { case METHOD_CREATE_ELEMENT: { const tagNode = path.node.arguments[0] if (t.isStringLiteral(tagNode)) { // 需要把标签增加到 class 样式中 const tagName = getTagName(tagNode.value, this.options.platform.name) if (tagName !== tagNode.value) { addStaticClass(path, '_' + tagNode.value) } tagNode.value = getComponentName(hyphenate(tagName)) // 组件增加 vueId // 跳过支付宝插件组件 if (this.options.platform.isComponent(tagNode.value) && !tagNode.$mpPlugin) { addVueId(path, this) } // 查找全局组件 checkUsingGlobalComponents( tagNode.value, this.options.globalUsingComponents, this ) } if (this.options.scopeId) { addStaticClass(path, this.options.scopeId) } // 根节点无 attrs 时添加空对象,方便后续合并外层 attrs if (this.options.mergeVirtualHostAttributes && !t.isObjectExpression(path.node.arguments[1]) && isRootElement(path)) { path.node.arguments.splice(1, 0, t.objectExpression([])) } const dataPath = path.get('arguments.1') dataPath && dataPath.isObjectExpression() && traverseData(dataPath, this, tagNode.value) } break case METHOD_TO_STRING: { const stringPath = path.get('arguments.0') if (hasEscapeQuote(stringPath)) { // 属性中包含转义引号时部分小程序平台报错或显示异常 // TODO 简单情况翻转外层引号 stringPath.replaceWith(getMemberExpr(path, IDENTIFIER_TEXT, stringPath.node, this)) } const stringNodes = stringPath.node stringNodes.$toString = true path.replaceWith(stringNodes) } break case METHOD_RENDER_LIST: traverseRenderList(path, this) path.skip() break default: // TODO 检测是否是 filterModules if (!METHOD_BUILT_IN.includes(methodName)) { if ( path.findParent( path => path.isObjectProperty() && ['on', 'nativeOn'].includes(path.node.key.name) ) // path is model.callback // || path.findParent(path => path.isObjectProperty() && path.node.key.name === 'callback' && t.isFunctionExpression(path.node.value) && t.isObjectProperty(path.parentPath.parentPath) && path.parentPath.parentPath.node.key.name === 'model') ) { // event return path.skip() } let newPath = path while (newPath) { path = newPath newPath = path.findParent((path) => path.isLogicalExpression()) } path.skip() if (path.findParent((path) => path.shouldSkip)) { return } path.replaceWith( getMemberExpr( path, methodName === METHOD_RESOLVE_FILTER ? IDENTIFIER_FILTER : IDENTIFIER_METHOD, path.node, this ) ) } else if (this.options.scopedSlotsCompiler === 'auto' || this.options.scopedSlotsCompiler === 'augmented') { if (methodName === METHOD_RESOLVE_SCOPED_SLOTS) { getResolveScopedSlots(path, this) } else if (methodName === METHOD_RENDER_SLOT) { getRenderSlot(path, this) } } break } } else if ( t.isCallExpression(callee) && t.isIdentifier(callee.callee) && callee.callee.name === METHOD_RESOLVE_FILTER ) { // multi filter path.replaceWith(getMemberExpr(path, IDENTIFIER_FILTER, path.node, this)) } else if ( t.isMemberExpression(callee) // message.split('').reverse().join('') ) { // Object.assign... let newPath = path while (newPath) { path = newPath newPath = path.findParent((path) => path.isLogicalExpression()) } path.skip() if (path.findParent((path) => path.shouldSkip)) { return } path.replaceWith(getMemberExpr(path, IDENTIFIER_GLOBAL, path.node, this)) } }, TemplateLiteral (path) { const nodes = [] const expressions = path.get('expressions') let index = 0 for (const elem of path.node.quasis) { if (elem.value.cooked) { nodes.push(t.stringLiteral(elem.value.cooked)) } if (index < expressions.length) { const expr = expressions[index++] const node = expr.node if (!t.isStringLiteral(node, { value: '' })) { nodes.push(node) } } } // since `+` is left-to-right associative // ensure the first node is a string if first/second isn't const considerSecondNode = !t.isStringLiteral(nodes[1]) if (!t.isStringLiteral(nodes[0]) && considerSecondNode) { nodes.unshift(t.stringLiteral('')) } let root = nodes[0] for (let i = 1; i < nodes.length; i++) { root = t.binaryExpression('+', root, nodes[i]) } path.replaceWith(root) } }