UNPKG

@antmove/wx-alipay

Version:

transform wechat miniprogram to alipay miniprogram tool.

547 lines (482 loc) 14.2 kB
const os = require('os') const path = require('path') const fs = require('fs-extra') const { cjsToes, processMixTemplate, isNumeric } = require('@antmove/utils') const propsHandle = require('../props/index.js') const proccessComponentProps = require('../component/props') const createComponentNode = require('../component/processRelations') const config = require('../config') const { parseString, parseFile } = require('../parse/parse') const wxsApp = require('./generateWxsDep') const indentWidthChar = ' ' /** * process wxs */ function processImportJs(code) { return cjsToes(code) } /** * @special tags */ function createElement(tagName, children = []) { return { typeof: 'wxml.element', key: null, props: {}, type: tagName, children, } } function processSpecialTags(ast = {}) { if (ast.type === 'picker' && ast.children[0] && ast.children[0].length > 1) { ast.children[0] = [createElement('view', ast.children[0])] return ast } } global.appNodesTreeStr = 'module.exports = {\n' const isParseTemplate = true // 保存模板的声明,全局可用 const includeCustomCompTpls = {} module.exports = function axmlRender(ast = [], fileInfo, appPages = []) { /** * container node render */ fileInfo.nodeId = 0 processPageTpl(fileInfo, appPages) if (fileInfo.isPage && ast.length > 1) { const _astPage = parseString( `<view class='${config.options.pageContainerClassName}'></view>`, ) _astPage[0].children = [ast] ast = _astPage } const refRender = createComponentNode(ast[0], fileInfo) processComponentIs(fileInfo) if (typeof ast === 'string') { return ast } let _code = '' let indentWidth = '' ast.forEach((tagAst) => { _code += renderFn( tagAst, fileInfo, refRender, isReportHandler(tagAst.props), ) }) generateRenderFn(fileInfo, refRender.toJsFile()) return _code function incIndent() { indentWidth += indentWidthChar } function decIndent() { indentWidth = indentWidth.slice(0, -1 * indentWidthChar.length) } function processTemplate(_ast) { const { props } = _ast const templateName = props.is.value.join('') const replacedChildren = includeCustomCompTpls[templateName] // console.log('template is', templateName) _ast.type = 'block' if (_ast.props.data) { _ast.props = { ..._ast.props, 'wx:for': { type: 'unknown', value: [processData()], }, 'wx:for-item': { type: 'unknown', value: ['amitem'], }, } // todo: 表达式没处理 _ast.children = parseString( replacedChildren .map((childAst) => { return renderFn(childAst, fileInfo, refRender) }) .join('') .replace(/{{\s*?((\w+)(\.?\w+)+?)\s*?}}/g, (match, p1, p2) => { if (isNumeric(p1) || p1 === 'item' || p2 === 'item') { return `{{${p1}}}` } return `{{amitem.${p1}}}` }), ) } else { _ast.children = replacedChildren } _ast.props._is = _ast.props.is delete _ast.props.is delete _ast.props.data function processData() { try { const value = _ast.props.data.value[0].match( /\s*\{\s*\{\s*([^}]+)\s*\}\s*\}/, )[1] // data: item 或者 data, item // 再套一层大括号 if (/,|:/.test(value)) { return `{{ [{ ${value} }] }}` } else { // data return `{{ [ ${value} ] }}` } } catch (err) { console.log('err', err) } } } // todo: 后期优化,只对用了自定义组件的页面使用 function includeCustomComp(_ast) { return true } function parseImportTemplate(_ast) { const srcVal = _ast.props.src.value[0] // console.log('Import template', srcVal) const templatePath = path.join( /^\//.test(srcVal) ? fileInfo.entry : fileInfo.dirname, srcVal, ) let templatesAst = parseFile(templatePath) templatesAst = templatesAst.filter( (__ast) => __ast.type === 'template' && __ast.props.name, ) templatesAst.forEach((templateAst) => { const name = templateAst.props.name.value[0] includeCustomCompTpls[name] = templateAst.children }) } function renderFn(_ast, _fileInfo, parentRenderNode, _isReport = false) { // 处理使用自定义模板 // 对于template的处理,必须把template的定义或者引入放在使用前 if (isParseTemplate) { // 处理模板的定义 if ( _ast.type === 'template' && _ast.props.name && includeCustomComp(_ast) ) { includeCustomCompTpls[_ast.props.name.value.join('')] = _ast.children } // 处理模板的引入 if (_ast.type === 'import' && /wxml$/.test(_ast.props.src.value[0])) { parseImportTemplate(_ast) return '' } // 处理模板的使用 if ( _ast.type === 'template' && _ast.props.is && includeCustomCompTpls[_ast.props.is.value.join('')] ) { processTemplate(_ast) } } let _parentRenderNode = parentRenderNode _ast.children = _ast.children || [] if (!config.hasWxs) { const bool = processSjs(_ast, _fileInfo) if (bool) { return '' } } if (_ast.type === 'wxs' && _ast.children.length) { try { let filename = _fileInfo.dist const sjsCode = _ast.children[0].value const moduleName = `${_ast.props.module.value[0]}.sjs` filename += moduleName fs.outputFileSync(filename, processImportJs(sjsCode)) _ast.children[0].value = '' const relativePath = filename.split(path.sep) const _relativePath = relativePath[relativePath.length - 1] _ast.props.src = { type: 'double', value: [`./${_relativePath}`] } } catch (e) { if (e) { console.error(e) } } } let { props } = _ast const isComponentRender = proccessComponentProps( _ast, _fileInfo, axmlRender, _isReport, ) if (isComponentRender) { _parentRenderNode = createComponentNode(_ast, _fileInfo) parentRenderNode.appendChild(_parentRenderNode) } processSpecialTags(_ast) if (_ast.type === 'textContent') { // todo: fix comment parse bug if (_ast.value.match(/-->/)) { return '' } return `${_ast.value}` } if (_ast.type === 'span') { _ast.type = 'text' } if (_ast.type === 'div') { _ast.type = 'view' } if (_ast.type === 'i') { _ast.type = 'icon' } let code = '' const tagName = _ast.type const children = _ast.children const isReport = _isReport ? isReportHandler(props) : _isReport appendCode(`<${tagName}`) props = props || {} let attrCode = '' Object.keys(props).forEach((prop) => { const propInfo = propsHandle(prop, props[prop], ast) // a:for process if (propInfo.key === 'wx:for-items' || propInfo.key === 'a:for-items') { propInfo.key = 'a:for' } // wx-if => a:if if (propInfo.key === 'wx-if') { propInfo.key = 'a:if' } if (propInfo.value === null) { // 无值属性 attrCode += ` ${propInfo.key}` } else { let value = propInfo.value.value[0] || '' value = value.replace(/\.wxml/g, '.axml').replace(/\.wxs/g, '.sjs') /** * support unknown type string * */ if (propInfo.value && propInfo.value.type === 'unknown') { let singleIndex = value.indexOf("'") let doubleIndex = value.indexOf('"') singleIndex = singleIndex > -1 ? singleIndex : 0 doubleIndex = doubleIndex > -1 ? doubleIndex : 0 if (singleIndex > doubleIndex) { propInfo.value.type = 'double' } else { propInfo.value.type = 'single' } } if (propInfo.value && propInfo.value.type === 'double') { if (propInfo.key === 'wx:else' || propInfo.key === 'a:else') { attrCode += ` ${propInfo.key} ` } else { attrCode += ` ${propInfo.key}="${value}"` } } else if ( (propInfo.key === 'a:key' || propInfo.key === 'wx:key') && !/{{/.test(value) ) { attrCode += ` ${propInfo.key}='{{${value}}}'` } else { attrCode += ` ${propInfo.key}='${value}'` } } }) /** * close element */ if (children.length === 0) { appendCode(`${attrCode}>`) // decIndent() } else { appendCode(`${attrCode}>`) incIndent() // children element if (Array.isArray(children)) { children.forEach((child) => { if (Array.isArray(child)) { child.forEach((subChild) => { appendCode( renderFn( subChild, _fileInfo, _parentRenderNode, isReport ? isReportHandler(subChild.props) : isReport, ), ) }) } else { appendCode( renderFn( child, _fileInfo, _parentRenderNode, isReport ? isReportHandler(child.props) : isReport, ), ) } }) } else { appendCode(children) } decIndent() } appendCode(`</${tagName}>`) return code.replace(os.EOL + os.EOL, os.EOL) function appendCode(appendChars) { const isType = processMixTemplate('alipay', _ast) if (!isType) { return } if (appendChars.trim().length === 0) { return } // if (appendChars.startsWith('<')) { // code += (appendChars.startsWith('</') ? os.EOL : '') + String(indentWidth) + appendChars; // } else if (appendChars.endsWith('>')) { // code += appendChars + os.EOL; // } else { // code += indentWidth + appendChars; // } if (appendChars.startsWith('<')) { code += (appendChars.startsWith('</') && !/<\/text>/.test(appendChars) ? os.EOL : '') + String(indentWidth) + appendChars } else if (appendChars.endsWith('>')) { if (/<\/text>/.test(appendChars)) { code += appendChars } else { code += appendChars + os.EOL } } else { code += indentWidth + appendChars } } } } function processPageTpl(fileInfo = {}, appPages = []) { let bool const jsonFile = `${fileInfo.dirname}/${fileInfo.basename}.json` if (fs.pathExistsSync(jsonFile)) { const obj = JSON.parse(fs.readFileSync(jsonFile, 'utf8')) if (obj.component === undefined) { fileInfo.isPage = true fileInfo.isComponent = false } else { fileInfo.isComponent = true } } else { /** * 页面无json文件输出 {"component":true} ,组件无json文件输出 {} */ let jsonContent = {} appPages.forEach((p) => { if ( !( path.join(fileInfo.entry, p) === `${fileInfo.dirname}/${fileInfo.basename}` ) ) { jsonContent = { component: true, } } }) fs.outputFileSync( fileInfo.dist.replace('.axml', '.json'), JSON.stringify(jsonContent), ) } return bool } /** * 组件层级关系 */ function generateRenderFn(fileInfo, renderStr = '') { let route = fileInfo.dist.replace(fileInfo.output, '') route = route.replace(/\.axml/, '') route = route.replace(/\\+/g, '/') global.appNodesTreeStr += `'${route}': ${renderStr},` } /** * sjs exports to props object */ function processSjs(_ast, _fileInfo) { let route = _fileInfo.dist.replace(_fileInfo.output, '') route = route.replace(/\.axml/, '') route = route.replace(/\\+/g, '/') let bool = false if (_ast.type === 'wxs') { if (_ast.children.length) { /** * 内联 wxs 处理 */ try { let filename = _fileInfo.dist let sjsCode = _ast.children[0].value const moduleName = _ast.props.module.value[0] filename = filename.replace('.axml', '.') let wxsPath = filename wxsPath = wxsPath.replace(_fileInfo.output, '') wxsPath = `${wxsPath + moduleName}sjs.js` if (sjsCode.match(/\s*getRegExp/g)) { const preCode = ` function getRegExp (p1, p2) { return new RegExp(p1, p2); } \n ` sjsCode = preCode + sjsCode } fs.outputFileSync(`${filename + moduleName}sjs.js`, sjsCode) _ast.children[0].value = '' wxsApp.createDep(route, wxsPath, moduleName, _fileInfo.output) // _ast.props.src = { type: 'double', value: [ './' + _relativePath ] }; bool = true } catch (e) { if (e) { console.error(e) } } } else { const filename = _fileInfo.dist const moduleName = _ast.props.module.value[0] let wxsPath = `${_ast.props.src.value[0]}.js` wxsPath = path.join(filename, '../', wxsPath) wxsPath = wxsPath.replace(_fileInfo.output, '') if (wxsPath[0] !== '/') { wxsPath = `/${wxsPath}` } wxsApp.createDep(route, wxsPath, moduleName, _fileInfo.output) bool = true } } return bool } function processComponentIs(fileInfo) { // let dist = fileInfo.dist.replace(/\.axml$/, '.is.js'); const isPath = fileInfo.dist .replace(fileInfo.output, '') .replace(/\.axml$/, '') .replace(/\\/g, '/') if (fileInfo.parent) { fileInfo.parent.is = isPath } } /** * 条件编译不上报 * */ function isReportHandler(props) { const typeArr = ['is-wx', 'is-alipay', 'is-swan', 'is-tt', 'is-quick'] let isReport = true if (props) { typeArr.forEach((type) => { if (props[type]) { isReport = false } }) } return isReport }