wx2swan
Version:
微信小程序 转换 百度小程序
561 lines (515 loc) • 21 kB
JavaScript
'use strict';
var _keys = require('babel-runtime/core-js/object/keys');
var _keys2 = _interopRequireDefault(_keys);
var _regenerator = require('babel-runtime/regenerator');
var _regenerator2 = _interopRequireDefault(_regenerator);
var _promise = require('babel-runtime/core-js/promise');
var _promise2 = _interopRequireDefault(_promise);
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
/**
* @file wxml convert swan
* @author yican
*/
var glob = require('glob');
var babylon = require('babylon');
var traverse = require('babel-traverse').default;
var generate = require('babel-generator').default;
var utils = require('./util/index');
var chalk = require('chalk');
var t = require('babel-types');
var log = require('./util/log');
var componentConf = require('../config/wxmp2swan/component');
var path = require('path');
exports.transformApiContent = function transformViewContent(content, api, prefix, transformedCtx, file) {
var result = babylon.parse(content, {
sourceType: 'module',
plugins: '*'
});
// 处理自定义组件log
traverse(result, {
ObjectProperty: function ObjectProperty(path) {
componentLog(path, file, log);
},
ObjectMethod: function ObjectMethod(path) {
componentLog(path, file, log);
},
MemberExpression: function MemberExpression(path) {
componentLog(path, file, log);
},
StringLiteral: function StringLiteral(path) {
componentLog(path, file, log);
}
});
// 转换api接口
traverse(result, {
MemberExpression: function MemberExpression(path) {
var ctx = path.node.object.name;
handleApiConfigTransform({ ctx: ctx, path: path, api: api, prefix: prefix, transformedCtx: transformedCtx, file: file, log: log });
}
});
// 转换剩余的是标识符的wx字段
traverse(result, {
enter: function enter(path) {
transformWx(path, file, log, prefix, transformedCtx);
transformRoute(path, file, log);
},
Identifier: function Identifier(path) {
transformGetExtConfigSync(path, file, log);
}
});
var generateResult = generate(result, {});
return generateResult.code;
};
exports.transformApi = /*#__PURE__*/_regenerator2.default.mark(function transformApi(context) {
var files, api, prefix, transformedCtx, content, i, code;
return _regenerator2.default.wrap(function transformApi$(_context) {
while (1) {
switch (_context.prev = _context.next) {
case 0:
_context.next = 2;
return new _promise2.default(function (resolve) {
var filePath = context.dist;
// 添加支持单一文件入口逻辑
if (utils.isDirectory(filePath)) {
filePath = filePath + '/**/*.js';
}
var extname = path.extname(filePath);
if (extname === '.js') {
glob(filePath, { ignore: '**/node_modules/**/*.js' }, function (err, res) {
resolve(err ? [] : res);
});
} else {
resolve([]);
}
});
case 2:
files = _context.sent;
api = require('../config/' + context.type + '/api');
prefix = context.type === 'wxmp2swan' ? 'wx' : '';
// 用于转换context
transformedCtx = api.ctx;
content = void 0;
// 遍历文件进行转换
i = 0;
case 8:
if (!(i < files.length)) {
_context.next = 18;
break;
}
_context.next = 11;
return utils.getContent(files[i]);
case 11:
content = _context.sent;
code = exports.transformApiContent(content, api, prefix, transformedCtx, files[i]);
_context.next = 15;
return utils.saveFile(files[i], code);
case 15:
i++;
_context.next = 8;
break;
case 18:
console.log(chalk.cyan('👉 Successfully transform js file'));
case 19:
case 'end':
return _context.stop();
}
}
}, transformApi, this);
});
/**
* 转换wx.__route__成员表达式调用为wx.route
*
* @param {Object} path traverse路径
* @param {string} file 文件路径
* @param {Object} log 日志工具
*/
function transformRoute(path, file, log) {
var beforeAttr = '__route__';
var afterAttr = 'route';
var node = path.node;
if (t.isIdentifier(path.node, { name: beforeAttr })) {
path.replaceWithSourceString(afterAttr);
handleLog();
} else if (t.isStringLiteral(path.node, { value: beforeAttr })) {
path.replaceWithSourceString('\'' + afterAttr + '\'');
handleLog();
}
function handleLog() {
log.logger({
type: 'transform attr __route__',
file: file,
row: node.loc.start.line,
column: node.loc.start.column,
before: beforeAttr,
after: afterAttr,
message: '转换了属性, __route__ ==> route'
}, 'info');
}
}
/**
* 转换swan.getExtConfigSync()函数调用为swan.getExtConfigSync().extConfig
*
* @param {Object} path traverse路径
* @param {string} file 文件路径
* @param {Object} log 日志工具
*/
function transformGetExtConfigSync(path, file, log) {
var node = path.node;
if (!t.isIdentifier(node, { name: 'getExtConfigSync' })) {
return;
}
var parentCallExpression = path.parentPath.parentPath;
var sourceCode = generate(parentCallExpression.node).code;
var sourceNode = parentCallExpression.node;
var parent = parentCallExpression.parentPath;
var parentProperty = parent.node.property;
if (!(parentProperty && parentProperty.name === 'extConfig')) {
var resExpression = t.memberExpression(parentCallExpression.node, t.identifier('extConfig'));
parentCallExpression.replaceWith(resExpression);
var afterCode = generate(resExpression).code;
handleLog(sourceCode, afterCode, sourceNode, file);
}
function handleLog(sourceCode, afterCode, node, file) {
log.logger({
type: 'transform function getExtConfigSync',
file: file,
row: node.loc.start.line,
column: node.loc.start.column,
before: sourceCode,
after: afterCode,
message: '\u8F6C\u6362\u4E86\u51FD\u6570\u8C03\u7528, ' + sourceCode + ' ==> ' + afterCode
}, 'info');
}
}
/**
* 转换剩余的是标识符的wx字段
* 如:for in, while中的wx
*
* @param {Object} path traverse路径
* @param {string} file 文件路径
* @param {Object} log 日志工具
* @param {string} prefix 要转换的字段名称
* @param {Object} transformedCtx 转换后的字段集合
*/
function transformWx(path, file, log, prefix, transformedCtx) {
if (t.isIdentifier(path.node, { name: prefix })) {
var node = path.node;
path.replaceWithSourceString(transformedCtx[prefix]);
log.logger({
type: 'transform function call arg name',
file: file,
row: node.loc.start.line,
column: node.loc.start.column,
before: prefix,
after: transformedCtx[prefix],
message: '只转换了上下文, wx ==> swan'
}, 'info');
}
}
/**
* 获取对象方法调用成员表达式中的方法名称
*
* @param {Object} node traverse节点
*/
function getNodeMethodName(node) {
var stringLiteralMethod = node.property.value;
var identifierMethod = node.property.name;
var methodName = node.property.type === 'StringLiteral' ? stringLiteralMethod : identifierMethod;
return methodName;
}
/**
* 获取转换后的对象方法调用成员表达式字符串
*
* @param {Object} node traverse节点
* @param {string} ObjectName 要转换的对象名称
* @param {string} methodName 要转换的方法名称
* @return {string} 转换后的方法调用字符串
*/
function getMemberExpressionReplaceCode(node, ObjectName, methodName) {
var afterCode = '';
var isStringLiteral = node.property.type === 'StringLiteral';
/**
* 对象取值使用[]时computed为true
* 对象取值使用.时computed为false
*/
var nodeComputed = node.computed;
// xxx['yyy'] -> ObjectName['methodName']
if (isStringLiteral && nodeComputed) {
afterCode = ObjectName + '[\'' + methodName + '\']';
// xxx[yyy] -> ObjectName[methodName]
} else if (nodeComputed) {
afterCode = ObjectName + '[' + methodName + ']';
// xxx.yyy -> ObjectName.methodName
} else {
afterCode = ObjectName + '.' + methodName;
}
return afterCode;
}
/**
* 自定义组件中不支持的属性打印error日志
*
* @param {Object} path traverse路径
* @param {string} file 文件路径
* @param {Object} log 日志工具
*/
function componentLog(path, file, log) {
var node = path.node;
var sourceCode = generate(path.node).code;
(0, _keys2.default)(componentConf).forEach(function (key) {
switch (key) {
case 'behaviors':
handleBehaviors();
break;
case 'this':
handleThis();
break;
case 'Component':
handleComponent();
break;
case 'Behavior':
handleBehavior();
}
});
// 处理自定义组件behaviors中的属性
function handleBehaviors() {
(0, _keys2.default)(componentConf.behaviors).forEach(function (attr) {
var confValue = componentConf.behaviors[attr] || {};
var mappingValue = confValue.mapping;
if (mappingValue && t.isStringLiteral(path.node) && node.value === attr) {
// const parent = path.parentPath.parentPath;
var behaviorsParent = path.findParent(function (path) {
return path.isObjectProperty() && path.node.key.name === 'behaviors';
});
if (behaviorsParent) {
node.value = mappingValue;
var afterCode = generate(path.node).code;
log.logger({
type: 'Compsonent behaviors',
file: file,
row: node.loc.start.line,
column: node.loc.start.column,
before: sourceCode,
after: afterCode,
message: '\u81EA\u5B9A\u4E49\u7EC4\u4EF6---behaviors[' + attr + ']: \u88AB\u66FF\u6362\u4E3Abehaviors[' + mappingValue + ']'
}, 'info');
}
}
});
}
// 处理自定义组件this上挂载的不支持方法
function handleThis() {
if (t.isThisExpression(path.node.object)) {
(0, _keys2.default)(componentConf.this).forEach(function (method) {
var confValue = componentConf.this[method];
var componentParent = path.findParent(function (path) {
return path.isCallExpression() && path.node.callee.name === 'Component';
});
if (componentParent && confValue === null && t.isIdentifier(path.node.property, { name: method })) {
log.logger({
type: 'Compsonent this api',
file: file,
row: node.loc.start.line,
column: node.loc.start.column,
before: sourceCode,
after: sourceCode,
message: '\u81EA\u5B9A\u4E49\u7EC4\u4EF6---this.' + method + ': ' + '没有相对应的方法'
}, 'error');
}
if (t.isIdentifier(path.node.property, { name: method }) && utils.isObject(confValue) && confValue.notAllowParents) {
var notAllowParents = confValue.notAllowParents;
notAllowParents.forEach(function (notAllowParent) {
var parent = path.findParent(function (path) {
return (path.isObjectProperty() || path.isObjectMethod()) && path.node.key.name === notAllowParent;
});
if (!parent) {
return;
}
log.logger({
type: 'Compsonent this api',
file: file,
row: node.loc.start.line,
column: node.loc.start.column,
before: sourceCode,
after: sourceCode,
message: '\u81EA\u5B9A\u4E49\u7EC4\u4EF6---this.' + method + ': \u4E0D\u652F\u6301\u5728' + notAllowParent + '\u4E2D\u8C03\u7528'
}, 'error');
});
}
});
}
}
// 处理不支持的自定义组件方法
function handleComponent() {
(0, _keys2.default)(componentConf.Component).forEach(function (method) {
var confValue = componentConf.Component[method];
if (confValue === null && t.isIdentifier(path.node.key, { name: method })) {
var parent = path.parentPath.parentPath.node;
if (parent.type === 'CallExpression' && parent.callee.name === 'Component' || parent.type === 'ObjectProperty' && parent.key.name === 'lifetimes') {
log.logger({
type: 'Component api',
file: file,
row: node.loc.start.line,
column: node.loc.start.column,
before: sourceCode,
after: sourceCode,
message: '\u81EA\u5B9A\u4E49\u7EC4\u4EF6---' + method + ': ' + '没有相对应的方法'
}, 'error');
}
}
});
}
// 处理不支持的Behavior方法
function handleBehavior() {
(0, _keys2.default)(componentConf.Behavior).forEach(function (method) {
var confValue = componentConf.Behavior[method];
if (confValue === null && t.isIdentifier(path.node.key, { name: method })) {
var parent = path.parentPath.parentPath.node;
if (parent.type === 'CallExpression' && parent.callee.name === 'Behavior') {
log.logger({
type: 'Behavior api',
file: file,
row: node.loc.start.line,
column: node.loc.start.column,
before: sourceCode,
after: sourceCode,
message: '\u81EA\u5B9A\u4E49\u7EC4\u4EF6---Behavior[' + method + ']: ' + '没有相对应的方法'
}, 'error');
}
}
});
}
}
/**
* 根据Api转换配置进行处理
*
* @param {string} ctx 当前函数命名空间
* @param {Object} path traverse路径
* @param {Object} api Api转换配置
* @param {string} prefix 要转换的函数命名空间
* @param {string} transformedCtx Api转换配置中指定的转换后的函数命名空间
* @param {string} file 要转换函数所在的源文件路径
* @param {Object} log 日志工具
*/
function handleApiConfigTransform(_ref) {
var ctx = _ref.ctx,
path = _ref.path,
api = _ref.api,
prefix = _ref.prefix,
transformedCtx = _ref.transformedCtx,
file = _ref.file,
log = _ref.log;
var method = getNodeMethodName(path.node);
if (!(ctx === prefix && method && api[ctx] && api[ctx][method])) {
return;
}
var action = api[ctx][method].action;
switch (action) {
case 'tip':
handleTip({ ctx: ctx, path: path, api: api, transformedCtx: transformedCtx, method: method, file: file, log: log });
break;
case 'mapping':
handleMapping({ ctx: ctx, path: path, api: api, transformedCtx: transformedCtx, method: method, file: file, log: log });
break;
case 'delete':
handleDelete({ ctx: ctx, path: path, api: api, transformedCtx: transformedCtx, method: method, file: file, log: log });
break;
}
function handleTip(_ref2) {
var ctx = _ref2.ctx,
path = _ref2.path,
api = _ref2.api,
transformedCtx = _ref2.transformedCtx,
method = _ref2.method,
file = _ref2.file,
log = _ref2.log;
var node = path.node;
var sourceCode = generate(path.node).code;
var afterCode = transformedCtx[ctx] + '.' + method;
// 二级api,只处理context,给tips提示,让开发者去手动兼容
path.replaceWithSourceString(afterCode);
// 增加transform logs
log.logger({
type: 'show tips',
file: file,
row: node.loc.start.line,
column: node.loc.start.column,
before: sourceCode,
after: afterCode,
message: ctx + '.' + method + ' --- ' + api[ctx][method].message
}, api[ctx][method].logLevel);
}
function handleMapping(_ref3) {
var ctx = _ref3.ctx,
path = _ref3.path,
api = _ref3.api,
transformedCtx = _ref3.transformedCtx,
method = _ref3.method,
file = _ref3.file,
log = _ref3.log;
var node = path.node;
var sourceCode = generate(path.node).code;
// 需要转换
var mappingName = api[ctx][method].mapping ? api[ctx][method].mapping : method;
// 只要替换ctx和函数名即可
var afterCode = transformedCtx[ctx] + '.' + mappingName;
path.replaceWithSourceString(afterCode);
// 增加transform logs
log.logger({
type: 'transform context && method',
file: file,
row: node.loc.start.line,
column: node.loc.start.column,
before: sourceCode,
after: afterCode,
message: ctx + '.' + method + ' --- ' + api[ctx][method].message
}, api[ctx][method].logLevel);
}
function handleDelete(_ref4) {
var ctx = _ref4.ctx,
path = _ref4.path,
api = _ref4.api,
transformedCtx = _ref4.transformedCtx,
method = _ref4.method,
file = _ref4.file,
log = _ref4.log;
var node = path.node;
var sourceCode = generate(path.node).code;
var afterCode = '';
var logType = 'delete api';
// 处理逻辑父节点是逻辑运算时的场景
// wx.xxxx && true
if (t.isLogicalExpression(path.parent)) {
logType = 'replace with binary expression';
path.node.object.name = transformedCtx[ctx];
path.replaceWith(t.binaryExpression('!=', path.node, t.nullLiteral()));
afterCode = generate(path.node).code;
}
// 处理 !wx.unSupported API
else if (t.isUnaryExpression(path.parent)) {
logType = 'transform context';
path.node.object.name = transformedCtx[ctx];
afterCode = generate(path.node).code;
} else if (path.parentPath) {
sourceCode = generate(path.parentPath.node).code;
// 避免移除父节点导致转码异常退出问题。
try {
path.parentPath.remove();
} catch (err) {
// @TODO: 优化日志
logType = 'transform failed';
afterCode = sourceCode;
}
}
var methodConfig = api[ctx][method] || {};
// 增加transform logs
log.logger({
type: logType,
file: file,
row: node.loc.start.line,
column: node.loc.start.column,
before: sourceCode,
after: afterCode,
message: ctx + '.' + method + (':' + methodConfig.message)
}, api[ctx][method].logLevel);
}
}