UNPKG

@antmove/wx-alipay

Version:

transform wechat miniprogram to alipay miniprogram tool.

395 lines (348 loc) 11.3 kB
const path = require('path') const fs = require('fs-extra') const { externalForWxFn } = require('@antmove/utils') const _componentMap = require('../config/componentsInfo/index').descObject const Config = require('../config') const eventsMap = require('./eventsMap') const generic = require('./generic') const preProcessCustomComponent = require('./customComponent') const processButton = require('./processButton.js') const processDataSet = require('./processDataSet') // 上报缺少的组件和属性 _componentMap // 组件/属性 上报白名单 const whitePropsList = ['style', 'class', 'ref', 'scope', 'onGetAuthorize', 'wx:if', 'wx:for', 'wx:key', 'wx:id', 'wx:else', 'wx:elif', 'id', 'slot'] const whiteCompsList = ['textContent', 'slot', 'i', 'span', 'div'] global.shortCompsInfo = {} module.exports = function(ast, fileInfo, renderAxml) { let isComponentTag = false processButton(ast, fileInfo) let { type } = ast const { props } = ast const originType = type const tagInfo = _componentMap[type] const shortProps = [] processExternalClasses(ast, fileInfo) isComponentTag = processCustomComponent(ast, fileInfo) if (props) { Object.keys(props).forEach((key) => { if (key && !props[key].value[0]) { props[key] = { type: 'double', value: [' '] } } key = processDataSet(key, props[key], props) /* 自定义组件image相对路径 支付宝相对组件所在页面查找,微信相对组件路径查找: 相对路径转绝对路径 */ if (key === 'src' && type === 'image' && fileInfo.isComponent) { const rule = props[key].value[0] if (rule[0] !== '{' && !path.isAbsolute(rule[0])) { let tempPath = path.join(fileInfo.dirname, rule) if (fs.pathExistsSync(tempPath)) { tempPath = tempPath.split(fileInfo.entry)[1] if (tempPath[0] !== '/') { tempPath = `/${tempPath}` } props[key].value[0] = tempPath } } } if (key === 'src' && ['include', 'import', 'wxs'].includes(type)) { let rule = props[key].value[0] if ((rule[0] !== '/' && rule[0] !== '.' && rule[0] !== '{')) { const tempPath = path.join(fileInfo.dirname, rule.replace(/\.axml'*/g, '.wxml')) if (fs.pathExistsSync(tempPath)) { rule = `./${rule}` } else { rule = `/${rule}` } } props[key].value[0] = rule } if (key === 'wx:for') { // relation ref collect if (!isNaN(Number(props[key].value[0]))) { props[key].value[0] = `{{[${props[key].value[0].split('')}]}}` } props['ref-numbers'] = props[key] } // 数字文本兼容 const val = props[key].value[0].trim() const matched = key.match(/^data-(.+)/) if (val && !isNaN(Number(val)) && !matched) { props[key].value[0] = `{{${val}}}` } // 配置中缺失组件 || 配置中的组件缺失属性 if (((!tagInfo && !isComponentTag && !whiteCompsList.includes(type)) || (tagInfo && tagInfo.props && !tagInfo.props[key])) && !shortProps.includes(key) && !whitePropsList.includes(key) && !key.match(/data-/)) { shortProps.push(key) } }) } if ((tagInfo && shortProps.length) || (!tagInfo && !isComponentTag && !whiteCompsList.includes(type))) { shortCompsInfo[type] = shortProps } /** * 自定义组件预处理 - 事件 */ if (tagInfo && tagInfo.type === 0) { console.log(`支付宝暂不支持${type}组件`) } /** * 检测是否已存在同名的组件 */ if ( tagInfo && tagInfo.type === 5 && !checkoutCustomComponent(fileInfo, originType) ) { processComponentMethodProp(ast.props, tagInfo.props) ast.type = tagInfo.tagName || ast.type type = ast.type /** * support mutipule custom tags */ fileInfo.tagsInfo = fileInfo.tagsInfo || [] fileInfo.tagsInfo.push(tagInfo) if (tagInfo.hasChildren) { if (ast.children) { let _axml = renderAxml(ast.children[0], {}) _axml = _axml.trim() if (_axml[_axml.length] === '\n') { _axml = _axml.substring(0, _axml.length - 1) } ast.props = ast.props || {} ast.props.textContent = { value: [_axml], type: 'unknown', } ast.children = [] } else { ast.props = ast.props || {} ast.props.textContent = { value: [''], type: 'single', } ast.children = [] } } if (!tagInfo.props) { return false } } else if (tagInfo && tagInfo.type === 6) { ast.type = tagInfo.tagName || ast.type type = ast.type } if (tagInfo) { if (tagInfo.type !== undefined) { if (tagInfo.type === 1) { ast.type = tagInfo.tagName || ast.type type = ast.type } } if (tagInfo.props) { for (const prop in tagInfo.props) { if (tagInfo.props.hasOwnProperty(prop)) { const propInfo = tagInfo.props[prop] if (!props[prop]) { continue } // missing if (propInfo.type === 0) { delete props[prop] } else if (propInfo.type === 1) { const _value = props[prop] delete props[prop] props[propInfo.key] = _value } } } } } processEvents(props) return isComponentTag } function processEvents(obj = {}) { const bindReg = /^(bind|catch):?/ for (const key in obj) { if (eventsMap[key]) { obj[eventsMap[key]] = { value: ['antmoveAction'], } obj[`data-antmove-${key.replace(bindReg, '')}`] = obj[key] delete obj[key] } else if (/^bind:(.+)/.test(key) || /^bind(.+)/.test(key)) { const newEvent = RegExp.$1 const uper = newEvent[0].toUpperCase() const eventKey = `on${uper}${newEvent.substring(1)}` obj[eventKey] = obj[key] delete obj[key] } else if (generic[key]) { obj[generic[key]] = obj[key] delete obj[key] } } return obj } function processComponentMethodProp(astProps = {}, propsInfo = {}) { Object.keys(astProps).forEach((prop) => { if (propsInfo[prop] && propsInfo[prop].type === 1) { astProps[propsInfo[prop].key] = astProps[prop] delete astProps[prop] } }) return astProps } function checkoutCustomComponent(fileInfo, tagName) { let bool = false let json let appJson if (fileInfo.extname === '.wxml') { json = fileInfo.path.replace('.wxml', '.json') if (!fs.pathExistsSync(json)) { return false } if (!fileInfo.jsonUsingComponents) { json = JSON.parse(fs.readFileSync(json, 'utf8')) || {} appJson = JSON.parse( fs.readFileSync( path.join(fileInfo.entry, 'app.json'), 'utf8', ), ) || {} } else { json = fileInfo.jsonUsingComponents appJson = fileInfo.appUsingComponents } if (json.usingComponents && json.usingComponents[tagName]) { bool = true } else if ( appJson.usingComponents && appJson.usingComponents[tagName] ) { bool = true } if (!tagName) { fileInfo.jsonUsingComponents = fileInfo.jsonUsingComponents || json.usingComponents fileInfo.appUsingComponents = fileInfo.appUsingComponents || appJson.usingComponents return { component: json.usingComponents, app: appJson.usingComponents, } } } return bool } function processCustomComponent(ast, fileInfo) { let isComponentTag = false /** * 自定义组件事件处理 */ if (!fileInfo.jsonUsingComponents) { const customComponents = checkoutCustomComponent(fileInfo) || {} fileInfo.jsonUsingComponents = customComponents.component || {} } fileInfo.appUsingComponents = fileInfo.appUsingComponents || {} if ( fileInfo.jsonUsingComponents[ast.type] || fileInfo.appUsingComponents[ast.type] ) { isComponentTag = true const _type = ast.type preProcessCustomComponent(ast) componentInTemplate(ast) if (fileInfo.appUsingComponents[ast.type]) { fileInfo.customAppUsingComponents = fileInfo.customAppUsingComponents || {} fileInfo.customAppUsingComponents[ast.type] = fileInfo.customAppUsingComponents[ast.type] || fileInfo.appUsingComponents[_type] } if (ast.props) { Object.keys(ast.props).forEach((prop) => { const value = ast.props[prop].value[0] if (prop.match(/^(bind:*)(\w+)/) && !value.match(/\{/)) { const newProp = prop.replace(/^(bind:*)\w+/, ( $, $1, ) => { const _prop = $.substring($1.length) return `on${_prop[0].toUpperCase()}${_prop.substring(1)}` }) ast.props[newProp] = ast.props[prop] delete ast.props[prop] } // a:key if (prop.match(/:key$/)) { ast.props[prop].value[0] = '*this' } if (!Config.component2) { ast.props._parent_ref = { type: 'double', value: ['{{isMounted}}'] } } }) } } return isComponentTag } function componentInTemplate(ast) { function deep(node) { if (node.parent) { if (node.parent.type === 'template') { console.warn('template模版中尽量不要插入自定义组件,会有渲染异常的风险') } else { deep(node.parent) } } } deep(ast) } function processExternalClasses(ast, fileInfo) { /** * external class 只支持字符常量,不支持表达式 */ if (!fileInfo.isComponent) { return false } const opts = { externalClasses: [], } fileInfo.jsFileCode = fileInfo.jsFileCode || '' if (!fileInfo.jsFileCode) { const jsFile = fileInfo.path.replace('.wxml', '.js') const code = fs.readFileSync(jsFile, 'utf8') fileInfo.jsFileCode = code } externalForWxFn(fileInfo.jsFileCode, opts) fileInfo.externalClasses = opts if (ast.props) { if (ast.props.class) { const classInfo = ast.props.class _externalClass(classInfo) /** * 提取扩展类 -class 结尾 或者带 -class- 的命名 * */ } Object.keys(ast.props) .forEach((propName) => { if (propName.match(/-class$/) || propName.match(/-class-/g)) { ast.props[propName] = _externalClass(ast.props[propName]) } }) } function _externalClass(classInfo = {}) { let _classes = classInfo.value[0].split(/\s+/) _classes = _classes.filter((className) => { return className.match(/-class$/) || className.match(/-class-/g) }) const temp = classInfo.value[0].split(/\s+/) const newClass = [] temp.forEach((str) => { if (opts.externalClasses.includes(str) || _classes.includes(str)) { newClass.push(`{{${_transform(str)}}}`) } else { newClass.push(str) } }) classInfo.value[0] = newClass.join(' ') return classInfo } function _transform(str = '') { str = str.replace(/-(\w)/g, (...$) => { return $[1].toUpperCase() }) return str } }