├── test ├── source │ ├── miniprogram_npm │ ├── node_modules.md │ ├── detail.js │ ├── .joyinfo │ ├── project.config.json │ ├── index.wxs │ ├── .joyrc.js │ ├── index.wxss │ ├── index.json │ ├── app.json │ ├── index.wxml │ └── index.js ├── result │ ├── result-wx2wx │ │ ├── .gitu │ │ ├── .ideaaa │ │ ├── node_modules.md │ │ ├── detail.js │ │ ├── .joyinfo │ │ ├── project.config.json │ │ ├── index.wxs │ │ ├── .joyrc.js │ │ ├── index.wxss │ │ ├── index.json │ │ ├── app.json │ │ ├── index.wxml │ │ └── index.js │ └── result-wx2bd │ │ ├── node_modules.md │ │ ├── detail.js │ │ ├── project.config.json │ │ ├── index.sjs │ │ ├── project.swan.json │ │ ├── index.css │ │ ├── index.json │ │ ├── app.json │ │ ├── index.swan │ │ └── index.js ├── entry.test.js └── rules-test │ ├── wx2bd.test.js │ └── wx2wx.test.js ├── rules ├── base │ ├── README.md │ ├── base-env-plugins │ │ └── MemberExpression │ │ │ └── transformEnv.js │ ├── VariableDeclaration │ │ └── properties.js │ └── MemberExpression │ │ └── transformAPI.js ├── wx2qq │ ├── css.js │ ├── js.js │ ├── json.js │ ├── index.js │ ├── component.js │ ├── view.js │ └── api │ │ ├── plugins │ │ └── member │ │ │ └── transformAPI.js │ │ └── index.js ├── wx2bd │ ├── js.js │ ├── api │ │ ├── plugins │ │ │ ├── CallExpression │ │ │ │ ├── request.js │ │ │ │ ├── require.js │ │ │ │ ├── drawImage.js │ │ │ │ ├── page.js │ │ │ │ └── component.js │ │ │ ├── MemberExpression │ │ │ │ ├── dataset.js │ │ │ │ └── transition.js │ │ │ ├── ObjectProperty │ │ │ │ ├── exportWX.js │ │ │ │ └── componentRelations.js │ │ │ ├── ImportDeclaration │ │ │ │ └── relativePath.js │ │ │ ├── VariableDeclaration │ │ │ │ └── variablesHandle.js │ │ │ ├── objectMethod │ │ │ │ ├── onReady.js │ │ │ │ └── getuserinfo.js │ │ │ └── ExpressionStatement │ │ │ │ └── onLoad.js │ │ └── index.js │ ├── json.js │ ├── css.js │ ├── index.js │ ├── component.js │ └── view.js ├── bd2wx │ ├── js.js │ ├── component.js │ ├── index.js │ ├── json.js │ ├── view.js │ ├── css.js │ └── api │ │ ├── plugins │ │ └── member │ │ │ └── transformAPI.js │ │ └── index.js └── wx2wx │ ├── api │ └── index.js │ └── index.js ├── .editorconfig ├── .babelrc ├── jest.config.js ├── src ├── config │ └── constant.js ├── processor │ ├── createTransformInfo.js │ ├── transformCss.js │ ├── bootstrap.js │ ├── transformAPI.js │ ├── transformView.js │ ├── transformJS.js │ └── transformConfig.js ├── util │ ├── view │ │ ├── parse.js │ │ ├── env-view.js │ │ ├── stringify.js │ │ └── wxml-to-swan.js │ ├── progress.js │ └── index.js ├── innerTool │ └── rewriteRule.js ├── log │ ├── store.js │ └── log.js └── index.js ├── scripts ├── build.sh └── zip.sh ├── LICENSE ├── .gitignore ├── package.json ├── bin └── wx2 ├── README.md └── .eslintrc.js /test/source/miniprogram_npm: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/result/result-wx2wx/.gitu: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/result/result-wx2wx/.ideaaa: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/source/node_modules.md: -------------------------------------------------------------------------------- 1 | 撒大声地 -------------------------------------------------------------------------------- /test/result/result-wx2bd/node_modules.md: -------------------------------------------------------------------------------- 1 | 撒大声地 -------------------------------------------------------------------------------- /test/result/result-wx2wx/node_modules.md: -------------------------------------------------------------------------------- 1 | 撒大声地 -------------------------------------------------------------------------------- /rules/base/README.md: -------------------------------------------------------------------------------- 1 | ## 转换基础插件 2 | 3 | > 放置`wx2swan`、`wx2qq`、 `swan2wx` 等通用的转换插件 -------------------------------------------------------------------------------- /test/source/detail.js: -------------------------------------------------------------------------------- 1 | swan.showToast({ 2 | title: 'hello wx2bd' 3 | }); 4 | const name = 'detail'; 5 | -------------------------------------------------------------------------------- /test/result/result-wx2bd/detail.js: -------------------------------------------------------------------------------- 1 | swan.showToast({ 2 | title: 'hello wx2bd' 3 | }); 4 | const name = 'detail'; 5 | -------------------------------------------------------------------------------- /test/result/result-wx2wx/detail.js: -------------------------------------------------------------------------------- 1 | swan.showToast({ 2 | title: 'hello wx2bd' 3 | }); 4 | const name = 'detail'; 5 | -------------------------------------------------------------------------------- /rules/wx2qq/css.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file 3 | * Created by wangpanfe on 2019/11/25. 4 | */ 5 | 'use strict'; 6 | 7 | module.exports = {}; 8 | -------------------------------------------------------------------------------- /test/source/.joyinfo: -------------------------------------------------------------------------------- 1 | { 2 | "toolName": "joy-cli", 3 | "toolCliVersion": "1.0.0-alpha.1", 4 | "createTime": 2019-12-16 14:26:26 5 | } -------------------------------------------------------------------------------- /test/result/result-wx2wx/.joyinfo: -------------------------------------------------------------------------------- 1 | { 2 | "toolName": "joy-cli", 3 | "toolCliVersion": "1.0.0-alpha.1", 4 | "createTime": 2019-12-16 14:26:26 5 | } -------------------------------------------------------------------------------- /rules/wx2bd/js.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file 3 | * Created by wangpanfe on 2019/11/25. 4 | */ 5 | 'use strict'; 6 | 7 | module.exports = { 8 | export: 'module.exports =' 9 | }; 10 | -------------------------------------------------------------------------------- /rules/wx2qq/js.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file 3 | * Created by wangpanfe on 2019/11/25. 4 | */ 5 | 'use strict'; 6 | 7 | module.exports = { 8 | export: 'module.exports =' 9 | }; 10 | -------------------------------------------------------------------------------- /rules/wx2qq/json.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file 3 | * Created by wangpanfe on 2019/11/25. 4 | */ 5 | 'use strict'; 6 | 7 | module.exports = { 8 | 'app.json': ['prefetches'] 9 | }; 10 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | indent_style = space 6 | indent_size = 4 7 | end_of_line = lf 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | -------------------------------------------------------------------------------- /test/source/project.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "setting": {}, 3 | "appid": "wx27319776041f9e9c", 4 | "projectname": "wx", 5 | "simulatorType": "wechat", 6 | "simulatorPluginLibVersion": {}, 7 | "condition": {} 8 | } -------------------------------------------------------------------------------- /rules/bd2wx/js.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file 3 | * Created by wangpanfe on 2019/11/25. 4 | */ 5 | 'use strict'; 6 | 7 | module.exports = { 8 | AOP: ['App', 'Page'], 9 | export: 'module.exports =' 10 | }; 11 | -------------------------------------------------------------------------------- /test/source/index.wxs: -------------------------------------------------------------------------------- 1 | // /pages/comm.wxs 2 | 3 | var foo = "'hello world' from comm.wxs"; 4 | var bar = function(d) { 5 | return d; 6 | } 7 | module.exports = { 8 | foo: foo, 9 | bar: bar 10 | }; 11 | -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | ["@babel/env"] 4 | ], 5 | "plugins": [ 6 | "@babel/plugin-transform-runtime", 7 | "@babel/plugin-proposal-function-bind" 8 | ] 9 | } 10 | -------------------------------------------------------------------------------- /rules/wx2bd/api/plugins/CallExpression/request.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @des 转换转换request的方法 3 | * 4 | * @param {Object} node traverse节点 5 | */ 6 | 7 | module.exports = function ({path, context, file}) { 8 | 9 | }; 10 | -------------------------------------------------------------------------------- /rules/wx2wx/api/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file wx2wx/api/index 3 | * @description wx 转换 wx 的 API 规则 4 | */ 5 | 6 | module.exports = { 7 | currentPrefix: 'wx', 8 | targetPrefix: 'wx', 9 | API: {} 10 | }; 11 | -------------------------------------------------------------------------------- /test/result/result-wx2bd/project.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "setting": {}, 3 | "appid": "wx27319776041f9e9c", 4 | "projectname": "wx", 5 | "simulatorType": "wechat", 6 | "simulatorPluginLibVersion": {}, 7 | "condition": {} 8 | } -------------------------------------------------------------------------------- /test/result/result-wx2wx/project.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "setting": {}, 3 | "appid": "wx27319776041f9e9c", 4 | "projectname": "wx", 5 | "simulatorType": "wechat", 6 | "simulatorPluginLibVersion": {}, 7 | "condition": {} 8 | } -------------------------------------------------------------------------------- /test/result/result-wx2bd/index.sjs: -------------------------------------------------------------------------------- 1 | // /pages/comm.wxs 2 | var foo = "'hello world' from comm.wxs"; 3 | 4 | var bar = function (d) { 5 | return d; 6 | }; 7 | 8 | module.exports = { 9 | foo: foo, 10 | bar: bar 11 | }; -------------------------------------------------------------------------------- /test/result/result-wx2wx/index.wxs: -------------------------------------------------------------------------------- 1 | // /pages/comm.wxs 2 | 3 | var foo = "'hello world' from comm.wxs"; 4 | var bar = function(d) { 5 | return d; 6 | } 7 | module.exports = { 8 | foo: foo, 9 | bar: bar 10 | }; 11 | -------------------------------------------------------------------------------- /test/result/result-wx2bd/project.swan.json: -------------------------------------------------------------------------------- 1 | { 2 | "setting": {}, 3 | "appid": "wx27319776041f9e9c", 4 | "projectname": "wx", 5 | "simulatorType": "wechat", 6 | "simulatorPluginLibVersion": {}, 7 | "condition": {} 8 | } -------------------------------------------------------------------------------- /test/source/.joyrc.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author xujie07@baidu.com 3 | * @date 2019/12/9 4 | */ 5 | 'use strict'; 6 | 7 | module.exports = { 8 | project_type: 'u-design', 9 | getuserinfo:'getUserInfoHandle' //微信open-type的回调函数,默认推荐为getuserinfo,可配置其他值 10 | //。。。其他自定义转换规则 11 | }; 12 | 13 | -------------------------------------------------------------------------------- /test/result/result-wx2wx/.joyrc.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author xujie07@baidu.com 3 | * @date 2019/12/9 4 | */ 5 | 'use strict'; 6 | 7 | module.exports = { 8 | project_type: 'u-design', 9 | getuserinfo:'getUserInfoHandle' //微信open-type的回调函数,默认推荐为getuserinfo,可配置其他值 10 | //。。。其他自定义转换规则 11 | }; 12 | 13 | -------------------------------------------------------------------------------- /rules/wx2bd/api/plugins/MemberExpression/dataset.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file 针对 dataset 转换,处理大小写敏感 3 | */ 4 | 5 | const {get, set, has} = require('lodash'); 6 | 7 | module.exports = function ({path, context, file}) { 8 | if (get(path, 'node.property.type') === 'Identifier' && /dataset/i.test(get(path, 'node.property.name'))) { 9 | set(path, 'node.property.name', 'dataset'); 10 | } 11 | }; 12 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | testEnvironment: 'node', 3 | verbose: true, 4 | collectCoverage: true, 5 | collectCoverageFrom: [ 6 | './src/processor/*.js', 7 | './rules/wx2bd/api/*', 8 | './rules/wx2bd/*.js', 9 | './rules/wx2wx/*.js', 10 | '!**/node_modules/**', 11 | '!**/vendor/**', './src/config/*' 12 | ], 13 | coverageDirectory: 'coverage' 14 | }; 15 | -------------------------------------------------------------------------------- /rules/wx2bd/api/plugins/MemberExpression/transition.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file 针对 transition 转换 3 | */ 4 | 5 | const {get, set, has} = require('lodash'); 6 | 7 | module.exports = function ({path, context, file}) { 8 | if (get(path, 'node.property.type') === 'Identifier' && get(path, 'node.property.name') === 'transition') { 9 | // TODO 特殊情况兼容性 10 | path.replaceWithSourceString('this.data.animation'); 11 | } 12 | }; 13 | -------------------------------------------------------------------------------- /test/source/index.wxss: -------------------------------------------------------------------------------- 1 | @import "../new/radioItemLine/post-item-template.wxss"; 2 | .backgroundImage1{ 3 | background-image: url(//img0.sc115.com/uploads1/sc/jpgs/1510/apic15069_sc115.com.jpg); 4 | } 5 | .backgroundImage2{ 6 | background-image: url('//img0.sc115.com/uploads1/sc/jpgs/1510/apic15069_sc115.com.jpg'); 7 | } 8 | .backgroundImage3{ 9 | background-image: url("//img0.sc115.com/uploads1/sc/jpgs/1510/apic15069_sc115.com.jpg"); 10 | } 11 | -------------------------------------------------------------------------------- /rules/wx2wx/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file 3 | * Created by wangpanfe on 2019/11/25. 4 | */ 5 | 'use strict'; 6 | const api = require('./api'); 7 | 8 | module.exports = { 9 | suffixMapping: { 10 | view: 'wxml', 11 | css: 'wxss', 12 | script: 'wxs', 13 | npm: 'miniprogram_npm' 14 | }, 15 | api, 16 | view: {}, 17 | css: {}, 18 | js: {}, 19 | json: {}, 20 | component: {}, 21 | appType: 'wx' 22 | }; 23 | -------------------------------------------------------------------------------- /test/result/result-wx2wx/index.wxss: -------------------------------------------------------------------------------- 1 | @import "../new/radioItemLine/post-item-template.wxss"; 2 | .backgroundImage1{ 3 | background-image: url(//img0.sc115.com/uploads1/sc/jpgs/1510/apic15069_sc115.com.jpg); 4 | } 5 | .backgroundImage2{ 6 | background-image: url('//img0.sc115.com/uploads1/sc/jpgs/1510/apic15069_sc115.com.jpg'); 7 | } 8 | .backgroundImage3{ 9 | background-image: url("//img0.sc115.com/uploads1/sc/jpgs/1510/apic15069_sc115.com.jpg"); 10 | } 11 | -------------------------------------------------------------------------------- /rules/wx2bd/json.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Descripttion: 3 | * @Author: wangpanfe 4 | * @Date: 2020-03-17 17:01:44 5 | * @LastEditTime: 2020-03-18 23:45:59 6 | */ 7 | /** 8 | * @file 9 | * Created by wangpanfe on 2019/11/25. 10 | */ 11 | 'use strict'; 12 | const components = require('./component'); 13 | 14 | module.exports = { 15 | 'app.json': ['prefetches'], 16 | components, 17 | 'project.json': { 18 | 'miniprogramRoot': 'smartProgramRoot' 19 | } 20 | }; 21 | -------------------------------------------------------------------------------- /test/result/result-wx2bd/index.css: -------------------------------------------------------------------------------- 1 | @import "../new/radioItemLine/post-item-template.css"; 2 | .backgroundImage1{ 3 | background-image: url(https://img0.sc115.com/uploads1/sc/jpgs/1510/apic15069_sc115.com.jpg); 4 | } 5 | .backgroundImage2{ 6 | background-image: url('https://img0.sc115.com/uploads1/sc/jpgs/1510/apic15069_sc115.com.jpg'); 7 | } 8 | .backgroundImage3{ 9 | background-image: url("https://img0.sc115.com/uploads1/sc/jpgs/1510/apic15069_sc115.com.jpg"); 10 | } 11 | -------------------------------------------------------------------------------- /rules/wx2bd/api/plugins/ObjectProperty/exportWX.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Descripttion: 替换export中点wx关键词,处理api批量挂载的情形 3 | * @Author: wangpanfe 4 | */ 5 | const {has, get, set} = require('lodash'); 6 | module.exports = function ({path, context, file}) { 7 | 8 | if (has(path, 'node.value') 9 | && get(path, 'node.value.type') === 'Identifier' 10 | && get(path, 'node.value.name') === 'wx' 11 | && context.type === context.constant.WECHAT_TO_SWAN) { 12 | set(path, 'node.value.name', 'swan'); 13 | } 14 | }; 15 | -------------------------------------------------------------------------------- /rules/bd2wx/component.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file 自定义组件配置 3 | * @description 自定义组件不支持的方法 4 | */ 5 | module.exports = { 6 | // Component构造器中不支持的属性 7 | Component: { 8 | }, 9 | // 自定义组件中this上不支持的属性和方法 10 | this: { 11 | 12 | }, 13 | // 设置内置behaviors映射关系 14 | behaviors: { 15 | 'swan://form-field': { 16 | mapping: 'wx://form-field' 17 | }, 18 | 'swan://component-export': { 19 | mapping: 'wx://component-export' 20 | } 21 | }, 22 | json: { 23 | 24 | } 25 | }; 26 | -------------------------------------------------------------------------------- /rules/wx2bd/css.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file 3 | * Created by wangpanfe on 2019/11/25. 4 | */ 5 | 'use strict'; 6 | const postcss = require('postcss'); 7 | 8 | const relativePathsPlugin = postcss.plugin('postcss-match-relative-paths', ({context, file}) => { 9 | return (root, result) => { 10 | root.walkAtRules('import', decl => { 11 | decl.params = decl.params.replace(/\.(wxss|css)/ig, '.' + context.rules.suffixMapping.css); 12 | }); 13 | }; 14 | }); 15 | 16 | module.exports = { 17 | relativePathsPlugin 18 | }; 19 | -------------------------------------------------------------------------------- /rules/wx2bd/api/plugins/ImportDeclaration/relativePath.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file 针对 相对路径进行转换 3 | */ 4 | 5 | const {get, set, has} = require('lodash'); 6 | 7 | module.exports = function ({path, context, file}) { 8 | // 相对路径 9 | const Reg = /^(\/|[a-z]+)[^:]/; 10 | const importPath = get(path, 'node.source.value'); 11 | if (!importPath) { 12 | return; 13 | } 14 | 15 | if (Reg.test(importPath)) { 16 | const filePath = `./${importPath}`.replace('//', '/'); 17 | set(path, 'node.source.value', filePath); 18 | } 19 | }; 20 | -------------------------------------------------------------------------------- /src/config/constant.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file 3 | * Created by wangpanfe on 2019/11/21. 4 | */ 5 | 'use strict'; 6 | 7 | exports.WECHAT_TO_SWAN = 'wx2bd'; 8 | exports.WECHAT_TO_QQ = 'wx2qq'; 9 | exports.SWAN_TO_WECHAT = 'bd2wx'; 10 | exports.WECHAT_TO_WECHAT = 'wx2wx'; 11 | 12 | exports.SKIP_BECAUSE_BROKEN = 'SKIP_BECAUSE_BROKEN'; 13 | exports.TRANSFORM_ERROR = 'TRANSFORM_ERROR'; 14 | exports.TRANSFORM_WAIT = 'TRANSFORM_WAIT'; 15 | exports.TRANSFORMING = 'TRANSFORMING'; 16 | exports.TRANSFORM_DONE = 'TRANSFORM_DONE'; 17 | exports.SWAN_ID_FOR_SYSTEM = 'swanIdForSystem'; // 解决组件依赖关系的系统添加属性 18 | 19 | -------------------------------------------------------------------------------- /rules/bd2wx/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file 3 | * Created by wangpanfe on 2019/11/25. 4 | */ 5 | 'use strict'; 6 | const api = require('./api'); 7 | const view = require('./view'); 8 | const css = require('./css'); 9 | const js = require('./js'); 10 | const json = require('./json'); 11 | const component = require('./component'); 12 | 13 | module.exports = { 14 | suffixMapping: { 15 | view: 'wxml', 16 | css: 'wxss', 17 | script: 'wxs', 18 | npm: 'miniprogram_npm' 19 | }, 20 | api, 21 | view, 22 | css, 23 | js, 24 | json, 25 | component 26 | }; 27 | -------------------------------------------------------------------------------- /rules/wx2bd/api/plugins/CallExpression/require.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file 3 | * @des 进行require的项目路径的转换 4 | */ 5 | 6 | const {set} = require('lodash'); 7 | 8 | module.exports = function ({path, context, file}) { 9 | const {callee, arguments: requireArguments} = path.node; 10 | if (callee.name !== 'require') { 11 | return; 12 | } 13 | // TODO: 有可能是绝对 14 | const Reg = /^(\/|[a-z]+)[^:]/; 15 | if (Reg.test(requireArguments[0].value)) { 16 | const filePath = `./${requireArguments[0].value}`.replace('//', '/'); 17 | set(path, 'node.arguments[0].value', filePath); 18 | } 19 | }; 20 | -------------------------------------------------------------------------------- /test/result/result-wx2wx/index.json: -------------------------------------------------------------------------------- 1 | { 2 | "usingComponents": { 3 | "List": "/node_modules//node_modules/@baidu/tab/index", 4 | "helloWorld": "/node_modules//node_modules//path/to/HelloWorld", 5 | "tableHead": "/node_modules//node_modules//path/to/table_head", 6 | "normal": "/node_modules//node_modules//path/to/normal", 7 | "with1": "/node_modules//node_modules//path/to/with1" 8 | }, 9 | "componentGenerics": { 10 | "selectable": { 11 | "default": "path/to/default/component" 12 | } 13 | }, 14 | "navigationBarBackgroundColor": "#000000" 15 | } -------------------------------------------------------------------------------- /test/result/result-wx2bd/index.json: -------------------------------------------------------------------------------- 1 | { 2 | "usingComponents": { 3 | "list": "/node_modules//node_modules//node_modules/@baidu/tab/index", 4 | "hello-world": "/node_modules//node_modules//path/to/HelloWorld", 5 | "table-head": "/node_modules//node_modules//path/to/table_head", 6 | "normal": "/node_modules//node_modules//path/to/normal", 7 | "with1": "/node_modules//node_modules//path/to/with1" 8 | }, 9 | "componentGenerics": { 10 | "selectable": { 11 | "default": "path/to/default/component" 12 | } 13 | }, 14 | "navigationBarBackgroundColor": "#ffffff" 15 | } -------------------------------------------------------------------------------- /rules/wx2qq/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file 3 | * Created by wangpanfe on 2019/11/25. 4 | */ 5 | 'use strict'; 6 | const api = require('./api'); 7 | const view = require('./view'); 8 | const css = require('./css'); 9 | const js = require('./js'); 10 | const json = require('./json'); 11 | const component = require('./component'); 12 | const fs = require('fs-extra'); 13 | const path = require('path'); 14 | 15 | module.exports = { 16 | suffixMapping: { 17 | view: 'qml', 18 | css: 'qss', 19 | npm: 'node_modules' 20 | }, 21 | api, 22 | view, 23 | css, 24 | js, 25 | json, 26 | component 27 | }; 28 | -------------------------------------------------------------------------------- /src/processor/createTransformInfo.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file 3 | * Created by wangpanfe on 2019/11/21. 4 | */ 5 | 'use strict'; 6 | const fs = require('fs-extra'); 7 | const path = require('path'); 8 | const {version, name} = require('../../package.json'); 9 | 10 | /** 11 | * 生成转换工具信息文件 12 | * 13 | * @param {Object} context 上下文信息 14 | */ 15 | 16 | module.exports = async function (context) { 17 | const filePath = path.join(context.dist, '.wx2info'); 18 | const con = `{ 19 | "toolName": "${name}", 20 | "toolCliVersion": "${version}", 21 | "createTime": ${new Date().toLocaleString()} 22 | }`; 23 | return fs.writeFile(filePath, con); 24 | }; 25 | -------------------------------------------------------------------------------- /rules/wx2bd/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file 3 | * Created by wangpanfe on 2019/11/25. 4 | */ 5 | 'use strict'; 6 | const api = require('./api'); 7 | const view = require('./view'); 8 | const css = require('./css'); 9 | const js = require('./js'); 10 | const json = require('./json'); 11 | const component = require('./component'); 12 | const fs = require('fs-extra'); 13 | const path = require('path'); 14 | 15 | module.exports = { 16 | suffixMapping: { 17 | view: 'swan', 18 | css: 'css', 19 | script: 'sjs', 20 | npm: 'node_modules' 21 | }, 22 | api, 23 | view, 24 | css, 25 | js, 26 | json, 27 | component, 28 | appType: 'swan' 29 | }; 30 | -------------------------------------------------------------------------------- /test/source/index.json: -------------------------------------------------------------------------------- 1 | { 2 | "usingComponents": { 3 | "List": "/node_modules//node_modules/@baidu/tab/index", 4 | "helloWorld": "/node_modules//node_modules//path/to/HelloWorld", 5 | "tableHead": "/node_modules//node_modules//path/to/table_head", 6 | "normal": "/node_modules//node_modules//path/to/normal", 7 | "with1": "/node_modules//node_modules//path/to/with1" 8 | }, 9 | "componentGenerics": { 10 | "selectable": { 11 | "default": "path/to/default/component" 12 | } 13 | }, 14 | "_wxEnv": { 15 | "navigationBarBackgroundColor": "#000000" 16 | }, 17 | "_swanEnv": { 18 | "navigationBarBackgroundColor": "#ffffff" 19 | } 20 | } -------------------------------------------------------------------------------- /rules/wx2bd/api/plugins/VariableDeclaration/variablesHandle.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file 针对 var xx = wx全局替换 3 | */ 4 | 5 | const {has, get, set} = require('lodash'); 6 | 7 | module.exports = function ({path, context, file}) { 8 | 9 | let origin = get(context, 'rules.api.currentPrefix'); 10 | let target = get(context, 'rules.api.targetPrefix'); 11 | if (path.node && get(path, 'node.type') === 'VariableDeclaration' && has(path, 'node.declarations')) { 12 | let declarations = get(path, 'node.declarations'); 13 | declarations.map(item => { 14 | if (item && item.init && get(item, 'init.name') === origin) { 15 | set(item, 'init.name', target); 16 | } 17 | }); 18 | } 19 | 20 | }; -------------------------------------------------------------------------------- /rules/wx2bd/api/plugins/CallExpression/drawImage.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file 针对 drawImage 参数位置转换 3 | */ 4 | const {has, get, set} = require('lodash'); 5 | module.exports = function ({path, context, file}) { 6 | if (!has(path, 'node.callee.property')) { 7 | return; 8 | } 9 | if (get(path, 'node.callee.name') === 'drawImage') { 10 | const args = get(path, 'node.arguments'); 11 | if (get(path, 'node.arguments.length') === 9) { 12 | const first = args.slice(0, 1); 13 | const argsPrev = args.splice(0, 5).slice(1); // 前五个参数 14 | // args为后四个参数 15 | const newArr = first.concat(args, argsPrev); 16 | set(path, 'node.arguments', newArr); 17 | } 18 | } 19 | }; 20 | -------------------------------------------------------------------------------- /scripts/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | export PATH=/home/work/node/bin/:$PATH 6 | export CC=/opt/compiler/gcc-4.8.2/bin/gcc 7 | export CXX=/opt/compiler/gcc-4.8.2/bin/g++ 8 | 9 | # npm install --registry=http://registry.npm.baidu-int.com --production 10 | 11 | # tplFile="node_modules/source-map-resolve/source-map-resolve.js" 12 | # # 删除.template结尾的文件。防止和orcp的模板功能冲突导致部署失败。 13 | # if [[ -f $tplFile && -f "$tplFile.template" ]]; then 14 | # cat $tplFile > "$tplFile.template" 15 | # else 16 | # echo "tplFile => [$tplFile] has not exists!" 17 | # fi 18 | 19 | TAR="joy-cli.tar.gz" 20 | 21 | if [[ ! -d "./output" ]]; then 22 | mkdir output 23 | else 24 | rm -rf output 25 | fi 26 | 27 | #将output目录进行打包 28 | tar zcf $TAR ./* 29 | 30 | mv $TAR ./output 31 | 32 | echo "build end" -------------------------------------------------------------------------------- /rules/wx2bd/api/plugins/objectMethod/onReady.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file 处理 onReady 的逻辑 3 | * Created by wangpanfe on 2019/11/21. 4 | */ 5 | const types = require('@babel/types'); 6 | const {get} = require('lodash'); 7 | 8 | module.exports = function ({path, context, file}) { 9 | const selectComponentNode = get(context, 'data.selectComponentNode') || {}; 10 | if (get(path, 'node.key.type') === 'Identifier' && get(path, 'node.key.name') === 'onReady') { 11 | if (!selectComponentNode[file] || selectComponentNode[file].length === 0) { 12 | return; 13 | } 14 | 15 | selectComponentNode[file].reverse().forEach(selectedNode => { 16 | path.get('body').unshiftContainer('body', types.expressionStatement(get(selectedNode, 'node.expression'))); 17 | }); 18 | } 19 | }; 20 | -------------------------------------------------------------------------------- /src/util/view/parse.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 视图内容解析为ast 3 | * 4 | * @file 视图内容解析为ast 5 | */ 6 | 7 | 'use strict'; 8 | 9 | const {isArray} = require('lodash'); 10 | const FAKE_ROOT = Symbol.for('fake-root'); 11 | const utils = require('..'); 12 | 13 | module.exports = function parse(options) { 14 | options = options || { 15 | xmlMode: false, 16 | lowerCaseAttributeNames: false, 17 | recognizeSelfClosing: true, 18 | lowerCaseTags: false 19 | }; 20 | this.Parser = parser; 21 | 22 | function parser(doc) { 23 | const {htmlParser, handler} = utils.getHtmlParser(options); 24 | htmlParser.end(doc); 25 | return { 26 | type: 'tag', 27 | name: FAKE_ROOT, 28 | attribs: {}, 29 | children: isArray(handler.dom) ? handler.dom : [handler.dom] 30 | }; 31 | } 32 | }; 33 | -------------------------------------------------------------------------------- /rules/wx2bd/component.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file 自定义组件配置 3 | * @description 自定义组件不支持的方法 4 | */ 5 | module.exports = { 6 | // Component构造器中不支持的属性 7 | Component: { 8 | moved: null, 9 | // relations: null, 10 | observers: null 11 | }, 12 | // 自定义组件中this上不支持的属性和方法 13 | this: { 14 | getRelationNodes: null, 15 | getTabBar: null, 16 | getPageId: null, 17 | selectComponent: { 18 | // 方法不允许被调用的作用域 19 | notAllowParents: ['onLaunch', 'onShow', 'onLoad'] 20 | }, 21 | selectAllComponents: { 22 | // 方法不允许被调用的作用域 23 | notAllowParents: ['onLaunch', 'onShow', 'onLoad'] 24 | } 25 | }, 26 | // 设置内置behaviors映射关系 27 | behaviors: { 28 | 'wx://form-field': { 29 | mapping: 'swan://form-field' 30 | }, 31 | 'wx://component-export': { 32 | mapping: 'swan://component-export' 33 | } 34 | } 35 | }; 36 | -------------------------------------------------------------------------------- /rules/wx2qq/component.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file 自定义组件配置 3 | * @description 自定义组件不支持的方法 4 | */ 5 | module.exports = { 6 | // Component构造器中不支持的属性 7 | Component: { 8 | moved: null, 9 | // relations: null, 10 | observers: null 11 | }, 12 | // 自定义组件中this上不支持的属性和方法 13 | this: { 14 | getRelationNodes: null, 15 | getTabBar: null, 16 | getPageId: null, 17 | selectComponent: { 18 | // 方法不允许被调用的作用域 19 | notAllowParents: ['onLaunch', 'onShow', 'onLoad'] 20 | }, 21 | selectAllComponents: { 22 | // 方法不允许被调用的作用域 23 | notAllowParents: ['onLaunch', 'onShow', 'onLoad'] 24 | } 25 | }, 26 | // 设置内置behaviors映射关系 27 | behaviors: { 28 | 'wx://form-field': { 29 | mapping: 'swan://form-field' 30 | }, 31 | 'wx://component-export': { 32 | mapping: 'swan://component-export' 33 | } 34 | } 35 | }; 36 | -------------------------------------------------------------------------------- /rules/bd2wx/json.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Descripttion: 3 | * @Author: wangpanfe 4 | * @Date: 2020-03-17 17:01:44 5 | * @LastEditTime: 2020-03-18 22:23:25 6 | */ 7 | /** 8 | * @file 9 | * Created by wangpanfe on 2019/11/25. 10 | */ 11 | 'use strict'; 12 | 13 | module.exports = { 14 | 'app.json': [ 15 | 'debug', 16 | 'functionalPages', 17 | 'workers', 18 | 'plugins', 19 | 'resizable', 20 | 'navigateToMiniProgramAppIdList', 21 | 'usingComponents', 22 | 'permission', 23 | 'sitemapLocation', 24 | 'style', 25 | 'useExtendedLib', 26 | 'window.backgroundColorTop', 27 | 'window.backgroundColorBottom', 28 | 'window.pageOrientation', 29 | 'tabBar.position', 30 | 'tabBar.custom', 31 | 'requiredBackgroundModes.location' 32 | ], 33 | 'project.json': [ 34 | { 35 | 'miniProgramRoot': 'smartProgramRoot' 36 | } 37 | ] 38 | }; 39 | -------------------------------------------------------------------------------- /src/innerTool/rewriteRule.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file 自定义规则转换 3 | */ 4 | 5 | const {resolve} = require('path'); 6 | const {isDirectory, getPlugins} = require('../../src/util'); 7 | 8 | exports.getSelfRules = function(context) { 9 | rulePath = context.selfRules; 10 | let ruleConfig = { 11 | plugin: {}, 12 | baseRules: {} 13 | }; 14 | // 自定义path,无则return 15 | if (!rulePath) { 16 | return; 17 | } 18 | // 解析文件夹路径,无则return 19 | const path = resolve(rulePath); 20 | try { 21 | if (!isDirectory(path)) { 22 | return; 23 | } 24 | // 获取自定义文件夹下的wx2.json文件 25 | let wx2JSON = require(`${path}/wx2.json`); 26 | // 合并自定义规则 27 | wx2JSON && (ruleConfig.baseRules = wx2JSON); 28 | // 合并自定义插件 29 | let plugins = getPlugins(path, context.type); 30 | plugins && (ruleConfig.plugin = plugins); 31 | } catch(e) { 32 | throw new Error('自定义规则不符合规范'); 33 | } 34 | return ruleConfig; 35 | } -------------------------------------------------------------------------------- /rules/base/base-env-plugins/MemberExpression/transformEnv.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file 3 | * @des process.env.APP_TYPE 转换 4 | */ 5 | 6 | const generate = require('@babel/generator').default; 7 | const types = require('@babel/types'); 8 | const {get} = require('lodash'); 9 | 10 | /** 11 | * 根据 appType 转换配置进行处理,将 process.env.APP_TYPE 替换为对应的 APP_TYPE 12 | * 13 | * @param {Object} context 当前上下文 14 | * @param {Object} path traverse路径 15 | * @param {string} file 要转换函数所在的源文件路径 16 | */ 17 | module.exports = function ({path, context, file}) { 18 | const appType = get(context, 'rules.appType'); 19 | if (appType && generate(path.node).code === 'process.env.APP_TYPE') { 20 | /** 21 | * 这里改变了节点类型,将 MemberExpression 改为了 StringLiteral 22 | * 问题:如果其之后的插件中也遍历了 MemberExpression,该节点依旧会被拦截,若之前的插件修改了节点类型,导致运行报错 23 | * 解决方式:将该插件放置到最后运行 24 | * 参考本插件 与 wx2bd/api/plugins/MemberExpression 25 | * TODO:确认下是否有其他方式 可以避免该问题 26 | */ 27 | path.replaceWith(types.valueToNode(appType)); 28 | } 29 | }; 30 | 31 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Baidu EFE 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /rules/wx2bd/view.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file 3 | * Created by wangpanfe on 2019/11/25. 4 | */ 5 | 'use strict'; 6 | 7 | module.exports = { 8 | boolAttr: [ 9 | 's-if', 10 | 's-elif' 11 | ], 12 | 13 | selfClosingTag2EndTag: ['image'], 14 | brackets: [ 15 | 's-if', 16 | 's-elif', 17 | 's-for' 18 | ], 19 | src: { 20 | regExp: /\.wxml$/i, 21 | replace: '.swan' 22 | }, 23 | data: { // 把data数据添加{} 24 | regExp: /^{(.*)}$/, 25 | replace: '{{$1}}' 26 | }, 27 | script: { 28 | oldTagName: 'wxs', 29 | newTagName: 'import-sjs', 30 | oldFileJsSuffix: 'wxs', 31 | newFileJsSuffix: 'sjs', 32 | wxExport: 'module.exports', 33 | swanExport: 'export default', 34 | regExp: /module.exports[\s]*=/ 35 | }, 36 | bindData: { 37 | 'scroll-view': ['scroll-top', 'scroll-left', 'scroll-into-view'], 38 | 'input': ['value'], 39 | 'textarea': ['value'], 40 | 'movable-view': ['x', 'y'], 41 | 'slider': ['value'] 42 | }, 43 | bindDataBracket: { 44 | regExp: /^{{(.*)}}$/, 45 | replace: '{=$1=}' 46 | } 47 | }; 48 | -------------------------------------------------------------------------------- /rules/wx2qq/view.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file 3 | * Created by wangpanfe on 2019/11/25. 4 | */ 5 | 'use strict'; 6 | 7 | module.exports = { 8 | boolAttr: [ 9 | 's-if', 10 | 's-elif' 11 | ], 12 | 13 | selfClosingTag2EndTag: ['image'], 14 | brackets: [ 15 | 's-if', 16 | 's-elif', 17 | 's-for' 18 | ], 19 | src: { 20 | regExp: /\.wxml$/i, 21 | replace: '.swan' 22 | }, 23 | data: { // 把data数据添加{} 24 | regExp: /^{{(.*)}}$/, 25 | replace: '{{$1}}' 26 | }, 27 | script: { 28 | oldTagName: 'wxs', 29 | newTagName: 'import-sjs', 30 | oldFileJsSuffix: 'wxs', 31 | newFileJsSuffix: 'sjs', 32 | wxExport: 'module.exports', 33 | swanExport: 'export default', 34 | regExp: /module.exports[\s]*=/ 35 | }, 36 | bindData: { 37 | 'scroll-view': ['scroll-top', 'scroll-left', 'scroll-into-view'], 38 | 'input': ['value'], 39 | 'textarea': ['value'], 40 | 'movable-view': ['x', 'y'], 41 | 'slider': ['value'] 42 | }, 43 | bindDataBracket: { 44 | regExp: /^{{(.*)}}$/, 45 | replace: '{=$1=}' 46 | } 47 | }; 48 | -------------------------------------------------------------------------------- /scripts/zip.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # zip打包入口脚本 3 | 4 | # export NODE_ENV=production 5 | # export PATH=$NODEJS_BIN_LATEST:$PATH 6 | 7 | echo "node $(node -v)" 8 | echo echo "npm v$(npm -v)" 9 | 10 | echo "Install npm dependencies" 11 | 12 | # 安装json解析 13 | npm install -g json --registry=http://registry.npm.baidu-int.com 14 | 15 | version=`cat ../package.json | json version` 16 | echo 'version: '$version 17 | version_code=`cat ../pkginfo.json | json version_code` 18 | version_code=$((version_code+1)) 19 | echo 'version_code: '$version_code 20 | 21 | # 修改pkginfo中version_name 22 | json -I -f pkginfo.json -e 'this.version_name="'${version}'"' 23 | 24 | # 修改pkginfo中version_code 25 | json -I -f pkginfo.json -e 'this.version_code="'${version_code}'"' 26 | 27 | npm install --registry=http://registry.npm.baidu-int.com 28 | 29 | #生成dist目录 30 | npm run build 31 | 32 | # 移除node_modules 33 | rm -r ../node_modules 34 | 35 | # 安装dependencies依赖 36 | npm install --production --registry=http://registry.npm.baidu-int.com 37 | 38 | 压缩所有文件 39 | cd ../dist 40 | cp -r ../../node_modules ../ 41 | zip -r ../../swan-wx2swan.zip ../* 42 | 43 | cd - 44 | 45 | npm install --registry=http://registry.npm.baidu-int.com 46 | 47 | echo "Done" 48 | -------------------------------------------------------------------------------- /rules/wx2bd/api/plugins/CallExpression/page.js: -------------------------------------------------------------------------------- 1 | /*eslint-disable*/ 2 | /** 3 | * @des Page中增加onReady方法 4 | * 5 | * @param {Object} node traverse节点 6 | */ 7 | const types = require('@babel/types'); 8 | const {get} = require('lodash'); 9 | 10 | module.exports = function ({path, context, file}) { 11 | if (get(path, 'node.callee.name') !== 'Page') { 12 | return; 13 | } 14 | 15 | path.traverse({ 16 | ObjectExpression(path) { 17 | if (!(get(path, 'parentPath.node.type') === 'CallExpression' && get(path, 'parentPath.node.callee.name') === 'Page')) { 18 | return; 19 | } 20 | 21 | const hasOnReady = get(path, 'node.properties').find(e => { 22 | if (e.key && e.key.name === 'onReady') { 23 | return true; 24 | } 25 | }); 26 | 27 | if (hasOnReady) { 28 | return; 29 | } 30 | 31 | get(path, 'node.properties').splice(1, 0, 32 | types.ObjectMethod('method', types.identifier('onReady'), [], types.BlockStatement([], [])) 33 | ); 34 | path.replaceWith(types.objectExpression(get(path, 'node.properties'))); 35 | } 36 | }); 37 | }; 38 | -------------------------------------------------------------------------------- /rules/bd2wx/view.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file 3 | * Created by wangpanfe on 2019/11/25. 4 | */ 5 | 'use strict'; 6 | 7 | module.exports = { 8 | boolAttr: [ 9 | 's-if', 10 | 's-elif' 11 | ], 12 | 13 | selfClosingTag2EndTag: ['image'], 14 | brackets: [ 15 | 's-if', 16 | 's-elif', 17 | 's-for' 18 | ], 19 | // 映射引入文件路径后缀 xx.wx2bd->xx.wxml 20 | src: { 21 | regExp: /\.swan$/i, 22 | replace: '.wxml' 23 | }, 24 | data: { // 把data数据添加{} 25 | regExp: /^{(.*)}$/, 26 | replace: '$1' 27 | }, 28 | // 映射filter,sjs,wxs语法 29 | script: { 30 | oldTagName: 'import-sjs', 31 | newTagName: 'wxs', 32 | oldFileJsSuffix: 'sjs', 33 | newFileJsSuffix: 'wxs', 34 | oldExport: 'export default', 35 | newExport: 'module.exports = ', 36 | regExp: /export default[\s]*/ 37 | }, 38 | bindData: { 39 | 'scroll-view': ['scroll-top', 'scroll-left', 'scroll-into-view'], 40 | 'input': ['value'], 41 | 'textarea': ['value'], 42 | 'movable-view': ['x', 'y'], 43 | 'slider': ['value'] 44 | }, 45 | bindDataBracket: { 46 | regExp: /^{=(.*)=}$/, 47 | replace: '{{$1}}' 48 | } 49 | }; 50 | -------------------------------------------------------------------------------- /rules/bd2wx/css.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file 3 | * Created by wangpanfe on 2019/11/25. 4 | */ 5 | 'use strict'; 6 | 7 | /** 8 | * @file 9 | * Created by wangpanfe on 2019/11/25. 10 | */ 11 | 'use strict'; 12 | const postcss = require('postcss'); 13 | 14 | const relativePathsPlugin = postcss.plugin('postcss-match-relative-paths', ({context, file}) => { 15 | return (root, result) => { 16 | root.walkAtRules('import', decl => { 17 | decl.params = decl.params.replace(/\.(wxss|css)/ig, '.' + context.rules.suffixMapping.css); 18 | }); 19 | // 只有微信不支持相对目录引入,所以加上这个判断 20 | root.walkDecls(/^background(-image)?$/, decl => { 21 | const {prop, value, source: {end}} = decl; 22 | const hasUrl = /^url/.test(value); 23 | if (!hasUrl) { 24 | return; 25 | } 26 | const filePath = (/\burl\s*?\((['"]?)([^)]+)\1\)/.exec(value) || [])[2]; 27 | if (!filePath) { 28 | return; 29 | } 30 | if (!/^(https?|data):/.test(filePath)) { 31 | // eslint-disable-next-line 32 | context.log.error('存在引用相对路径的图片:\n', '文件为:' + file + ' 的第 ' + end.line + ' 行\n', '属性为:' + prop + ': ' + value); 33 | } 34 | }); 35 | }; 36 | }); 37 | 38 | module.exports = { 39 | relativePathsPlugin 40 | }; 41 | 42 | -------------------------------------------------------------------------------- /src/log/store.js: -------------------------------------------------------------------------------- 1 | class Store { 2 | constructor() { 3 | this.usingComponentsMap = {}; 4 | this.renamedComponents = {}; 5 | this.needSwanIdComponents = []; 6 | this.relationComponentsParent = {}; // {a:['com1','com1],b:[]} 7 | this.relationComponentsChild = []; // {a:['com1','com1],b:[]} 8 | } 9 | 10 | 11 | dispatch({action, payload = {}}) { 12 | let key = ''; 13 | switch (action) { 14 | case 'usingComponentsMap': 15 | this.usingComponentsMap = payload; 16 | break; 17 | case 'renamedComponents': 18 | this.renamedComponents = payload; 19 | break; 20 | case 'needSwanIdComponents': 21 | this.needSwanIdComponents.push(payload); 22 | break; 23 | case 'relationComponentsParent': 24 | key = Object.keys(payload)[0]; 25 | this.relationComponentsParent[key] = payload[key]; 26 | break; 27 | case 'relationComponentsChild': 28 | key = Object.keys(payload)[0]; 29 | this.relationComponentsChild[key] = payload[key]; 30 | break; 31 | default: 32 | throw new Error('action未定义,行为禁止'); 33 | } 34 | } 35 | } 36 | 37 | module.exports = new Store(); 38 | -------------------------------------------------------------------------------- /src/util/view/env-view.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file env-view.js 3 | * @description 转换不同平台的view代码 4 | */ 5 | const {isArray} = require('lodash'); 6 | 7 | module.exports = function (context = {}) { 8 | const envTagReg = /^[wx|swan]+-env/; 9 | 10 | return function transformEnvView(tree, file) { 11 | if (isArray(tree)) { 12 | return tree.map(node => transformEnvView(node, file)); 13 | } 14 | if (tree.type === 'tag') { 15 | const {name, children} = tree; 16 | // 不同平台编译不同代码,在所有转化之后 17 | if (envTagReg.test(name.toString())) { 18 | tree = tranformEnv(tree, file, context); 19 | } 20 | tree.children = children.map(node => transformEnvView(node, file)); 21 | } 22 | return tree; 23 | }; 24 | }; 25 | 26 | /** 27 | * 转换 APP_TYPE-env标签 28 | * 29 | * @param {Object} node 节点对象 30 | * @param {VFile} file 虚拟文件 31 | * @param {Object} context 上下文 32 | * @return {Object} 转换后的节点对象 33 | */ 34 | 35 | function tranformEnv(node, file, context) { 36 | const {rules: viewRules, log} = context; 37 | const appType = viewRules.appType; 38 | 39 | log.warning({ 40 | type: '二次迭代模板被转换', 41 | file: file, 42 | name: node.name 43 | }); 44 | 45 | if (node.name === `${appType}-env`) { 46 | return node.children; 47 | } 48 | return {data: '', type: 'text'}; 49 | } 50 | -------------------------------------------------------------------------------- /test/entry.test.js: -------------------------------------------------------------------------------- 1 | const View = require('../src/processor/transformView'); 2 | const Css = require('../src/processor/transformCss'); 3 | const Js = require('../src/processor/transformJs'); 4 | const Bootstrap = require('../src/processor/bootstrap'); 5 | const Api = require('../src/processor/transformAPI'); 6 | const Log = require('../src/log/log'); 7 | const logInstance = new Log('../src/log'); 8 | 9 | const errorMsg = { 10 | log: logInstance 11 | }; 12 | 13 | describe('error', () => { 14 | test('test transfrom wxss error: ', async () => { 15 | await Css.transformCss(errorMsg).catch(e => { 16 | expect.assertions(0); 17 | }); 18 | }); 19 | 20 | test('test transfrom bootstrap error:', async () => { 21 | await Bootstrap.transformBootstrap(errorMsg).catch(e => { 22 | expect.assertions(0); 23 | }); 24 | }); 25 | 26 | test('test transfrom view error: ', async () => { 27 | await Api.transformApi(errorMsg).catch(e => { 28 | expect.assertions(0); 29 | }); 30 | 31 | }); 32 | 33 | test('test transfrom view error: ', async () => { 34 | await View.transformView(errorMsg).catch(e => { 35 | expect.assertions(0); 36 | }); 37 | }); 38 | 39 | test('test transfrom js error: ', async () => { 40 | await Js.transformJS(errorMsg).catch(e => { 41 | expect.assertions(0); 42 | }); 43 | }); 44 | }); 45 | -------------------------------------------------------------------------------- /test/result/result-wx2bd/app.json: -------------------------------------------------------------------------------- 1 | { 2 | "pages": [ 3 | "pages/index/index", 4 | "pages/extend/extend", 5 | "pages/drawer/drawer", 6 | "pages/my/my", 7 | "pages/maps/maps" 8 | ], 9 | "subPackages": [ 10 | { 11 | "root": "pages/basic-view/", 12 | "pages": [ 13 | "color/color", 14 | "button/button" 15 | ] 16 | }, 17 | { 18 | "root": "pages/extend-view/", 19 | "pages": [ 20 | "msgtips/msgtips", 21 | "sticky/sticky", 22 | "keyboard/keyboard" 23 | ] 24 | } 25 | ], 26 | "window": { 27 | "backgroundTextStyle": "dark", 28 | "navigationBarBackgroundColor": "#5677FC", 29 | "navigationBarTitleText": "Thor UI", 30 | "navigationBarTextStyle": "white", 31 | "backgroundColorTop": "#fafafa", 32 | "backgroundColorBottom": "#fafafa" 33 | }, 34 | "tabBar": { 35 | "color": "#aaaaaa", 36 | "list": [ 37 | { 38 | "pagePath": "pages/index1/index1", 39 | "text": "code2" 40 | } 41 | ] 42 | }, 43 | "_testEnv": { 44 | "tabBar": { 45 | "color": "#aaaaaa", 46 | "list": [ 47 | { 48 | "pagePath": "pages/index1/index1", 49 | "text": "code2" 50 | } 51 | ] 52 | } 53 | } 54 | } -------------------------------------------------------------------------------- /rules/wx2bd/api/plugins/ExpressionStatement/onLoad.js: -------------------------------------------------------------------------------- 1 | const {get, set, has} = require('lodash'); 2 | 3 | module.exports = function ({path, context, file}) { 4 | const expression = get(path, 'node.expression'); 5 | const selectComponentNode = get(context, 'data.selectComponentNode') || {}; 6 | if (expression.type !== 'AssignmentExpression') { 7 | return; 8 | } 9 | 10 | // TODO 变量申明的场景待处理 11 | path.traverse({ 12 | AssignmentExpression(assignPath) { 13 | const rightNode = get(assignPath, 'node.right'); 14 | if (rightNode.type === 'CallExpression' 15 | && has(rightNode, 'callee.property') 16 | && get(rightNode, 'callee.property.name') === 'selectComponent') { 17 | const parent = assignPath.findParent(assignPath => { 18 | return assignPath.isObjectMethod() && get(assignPath, 'node.key.name') === 'onLoad'; 19 | }); 20 | if (!parent) { 21 | return; 22 | } 23 | // 记录该节点,替换到onReady中 24 | if (!selectComponentNode[file]) { 25 | selectComponentNode[file] = []; 26 | } 27 | selectComponentNode[file].push(path); 28 | 29 | if (!has(context, 'data.selectComponentNode')) { 30 | return; 31 | } 32 | set(context, 'data.selectComponentNode', selectComponentNode); 33 | } 34 | } 35 | }); 36 | }; 37 | 38 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Referenced from https://github.com/github/gitignore/blob/master/Node.gitignore 2 | 3 | # Logs 4 | logs 5 | *.log 6 | npm-debug.log* 7 | yarn-debug.log* 8 | yarn-error.log* 9 | 10 | # Runtime data 11 | pids 12 | *.pid 13 | *.seed 14 | *.pid.lock 15 | 16 | # Directory for instrumented libs generated by jscoverage/JSCover 17 | lib-cov 18 | 19 | # Coverage directory used by tools like istanbul 20 | coverage 21 | 22 | # nyc test coverage 23 | .nyc_output 24 | 25 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 26 | .grunt 27 | 28 | # Bower dependency directory (https://bower.io/) 29 | bower_components 30 | 31 | # node-waf configuration 32 | .lock-wscript 33 | 34 | # Compiled binary addons (https://nodejs.org/api/addons.html) 35 | build/Release 36 | 37 | # Dependency directories 38 | node_modules 39 | jspm_packages/ 40 | 41 | # Typescript v1 declaration files 42 | typings/ 43 | 44 | # Optional npm cache directory 45 | .npm 46 | 47 | # Optional eslint cache 48 | .eslintcache 49 | 50 | # Optional REPL history 51 | .node_repl_history 52 | 53 | # Output of 'npm pack' 54 | *.tgz 55 | 56 | # Yarn Integrity file 57 | .yarn-integrity 58 | 59 | # dotenv environment variables file 60 | .env 61 | 62 | # next.js build output 63 | .next 64 | 65 | # other stuff 66 | .DS_Store 67 | Thumbs.db 68 | 69 | # IDE configurations 70 | .idea 71 | .vscode 72 | 73 | # build assets 74 | /output 75 | /dist 76 | /dll 77 | .idea/ 78 | test/source-swan.joy.output 79 | test/source.joy.output 80 | package-lock.json 81 | /test/output -------------------------------------------------------------------------------- /src/processor/transformCss.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file 3 | * Created by wangpanfe on 2019/11/21. 4 | */ 5 | 'use strict'; 6 | 7 | const {transformCssStaticUrl, getContent, saveFile, getFiles} = require('../util/index'); 8 | const postcss = require('postcss'); 9 | const {forEach} = require('lodash'); 10 | 11 | /** 12 | * 对文件中的css进行转换 13 | * 14 | * @param {Object} context 函数上下文 15 | */ 16 | exports.transformCss = async function transformCss(context) { 17 | const suffix = context.rules.suffixMapping.css; 18 | const files = await getFiles(context.dist, suffix); 19 | 20 | 21 | await Promise.all(files.map(async file => { 22 | let content = await getContent(file); 23 | content = await exports.transformCssContent(file, content, context); 24 | await saveFile(file, content); 25 | // 更新进度 26 | global.emitter.emit('event', false); 27 | })); 28 | }; 29 | 30 | exports.transformCssContent = function transformCssContent(file, content, context) { 31 | // 无请求头的css静态资源url添加https请求头 32 | content = transformCssStaticUrl(content); 33 | const cssRules = context.rules.css; 34 | const cssPlugins = []; 35 | forEach(cssRules, (process, pluginName) => { 36 | cssPlugins.push(process({context, file})); 37 | 38 | }); 39 | return postcss(cssPlugins).process(content, {from: ''}).catch(error => { 40 | context.log.error({ 41 | progress: '[FAILURE] transforming css failure', 42 | path: file, 43 | error: error.toString() 44 | }); 45 | }); 46 | }; 47 | -------------------------------------------------------------------------------- /rules/base/VariableDeclaration/properties.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file 3 | * @author wangpanfe 4 | * @des api 处理组件中properties属性 5 | */ 6 | const types = require('@babel/types'); 7 | const {get} = require('lodash'); 8 | 9 | /** 10 | * 转换组件之间上下文关系 11 | * 12 | * @param {Object} context 当前上下文 13 | * @param {Object} path traverse路径 14 | * @param {string} file 要转换函数所在的源文件路径 15 | */ 16 | module.exports = function ({path, context, file}) { 17 | 18 | path.traverse({ 19 | VariableDeclarator(varPath) { 20 | const name = get(varPath, 'node.id.name'); 21 | const init = get(varPath, 'node.init'); 22 | 23 | if (name !== 'properties' || init.type !== 'ObjectExpression') { 24 | return; 25 | } 26 | const hasFound = init.properties.find(prop => { 27 | return get(prop, 'key.name') === get(context, 'constant.SWAN_ID_FOR_SYSTEM'); 28 | }); 29 | if (hasFound) { 30 | return; 31 | } 32 | init.properties.push(types.objectProperty(types.identifier(get(context, 'constant.SWAN_ID_FOR_SYSTEM')), 33 | types.objectExpression([ 34 | types.objectProperty(types.identifier('type'), 35 | types.identifier('String')), 36 | types.objectProperty(types.identifier('value'), 37 | types.stringLiteral('')) 38 | ]), false, false, null)); 39 | types.objectExpression(init.properties); 40 | } 41 | }); 42 | }; 43 | -------------------------------------------------------------------------------- /rules/wx2qq/api/plugins/member/transformAPI.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file 3 | * @des api 映射及 API转换 4 | */ 5 | const generate = require('@babel/generator').default; 6 | const logInstance = require('../../../../../src/log/log'); 7 | const {getNodeMethodName} = require('../../../../../src/util'); 8 | const {has, get, set} = require('lodash'); 9 | 10 | /** 11 | * 根据Api转换配置进行处理 12 | * 13 | * @param {string} ctx 当前函数命名空间 14 | * @param {Object} path traverse路径 15 | * @param {string} file 要转换函数所在的源文件路径 16 | */ 17 | module.exports = function ({path, context, file}) { 18 | const name = get(path, 'node.object.name'); 19 | const api = get(context, 'rules.api'); 20 | const {ctx, prefix} = api; 21 | let logInfo = { 22 | message: '替换前缀名称', 23 | logLevel: 'warning', 24 | file, 25 | beforeCode: generate(path.node).code 26 | }; 27 | if (!has(path, 'node.property.value')) { 28 | return; 29 | } 30 | if (name !== prefix) { 31 | return; 32 | } 33 | // 替换名称 34 | set(path, 'node.object.name', ctx[name]); 35 | const method = getNodeMethodName(path.node); 36 | 37 | // 获取操作动作 38 | if (!(method && api[prefix] && api[prefix][method])) { 39 | const conf = api[prefix][method]; 40 | logInfo = Object.assign(logInfo, conf); 41 | } 42 | 43 | if (logInfo.action === 'mapping') { 44 | api[prefix][method].mapping 45 | ? set(path, 'node.property.value', api[prefix][method].mapping) 46 | : set(path, 'node.property.value', method); 47 | } 48 | 49 | logInfo.afterCode = generate(path.node).code; 50 | logInstance[logInfo.logLevel](logInfo); 51 | }; 52 | 53 | -------------------------------------------------------------------------------- /rules/wx2bd/api/plugins/objectMethod/getuserinfo.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Descripttion: 获取用户信息 3 | * @Author: wangpanfe 4 | * @Date: 2020-02-26 21:52:30 5 | * @LastEditTime: 2020-03-16 23:53:30 6 | */ 7 | const generate = require('@babel/generator').default; 8 | const {relative, dirname} = require('path'); 9 | const types = require('@babel/types'); 10 | const propertiesString = 'xXksjUhmbvhaks'; // 临时字面量 11 | const constant = require('../../../../../src/config/constant'); 12 | const {get, set, has} = require('lodash'); 13 | 14 | module.exports = function ({path, context, file}) { 15 | const {getuserinfo, projectType} = get(context, 'rules.api'); 16 | if (projectType !== 'internal' && context.type !== constant.WECHAT_TO_SWAN) { 17 | return; 18 | } 19 | 20 | if (get(path, 'node.key.type') === 'Identifier' && get(path, 'node.key.name') === getuserinfo) { 21 | let flag = false; 22 | path.traverse({ 23 | Identifier(identifierPath) { 24 | if (get(identifierPath, 'node.name') === 'getCookieForSystem') { 25 | flag = true; 26 | } 27 | } 28 | }); 29 | if (flag) { 30 | return; 31 | } 32 | 33 | const MemberExpression = types.memberExpression(types.Identifier('swan'), types.Identifier(propertiesString)); 34 | const CallExpression = types.callExpression(MemberExpression, []); 35 | const blockBody = [types.expressionStatement(CallExpression)]; 36 | path.replaceWith(types.objectMethod('method', types.Identifier(get(path, 'node.key.name')), get(path, 'node.params'), types.blockStatement(blockBody))); 37 | 38 | /* eslint-enable */ 39 | } 40 | }; 41 | -------------------------------------------------------------------------------- /rules/bd2wx/api/plugins/member/transformAPI.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file 3 | * @des api 映射及 API转换 4 | */ 5 | const generate = require('@babel/generator').default; 6 | const logInstance = require('../../../../../src/log/log'); 7 | const {getNodeMethodName} = require('../../../../../src/util'); 8 | const {has, get, set} = require('lodash'); 9 | 10 | /** 11 | * 根据Api转换配置进行处理 12 | * 13 | * @param {string} ctx 当前函数命名空间 14 | * @param {Object} path traverse路径 15 | * @param {string} file 要转换函数所在的源文件路径 16 | */ 17 | module.exports = function ({path, context, file}) { 18 | const name = get(path, 'node.object.name'); 19 | const api = get(context, 'rules.api'); 20 | const {ctx, prefix} = api; 21 | let logInfo = { 22 | message: '替换前缀名称', 23 | logLevel: 'warning', 24 | file, 25 | beforeCode: generate(path.node).code 26 | }; 27 | if (name !== prefix) { 28 | return; 29 | } 30 | // 替换名称 31 | if (!has(path, 'node.property.name')) { 32 | return; 33 | } 34 | set(path, 'node.object.name', ctx[name]); 35 | const method = getNodeMethodName(path.node); 36 | 37 | // 获取操作动作 38 | if (!(method && api[prefix] && api[prefix][method])) { 39 | const conf = api[prefix][method]; 40 | logInfo = Object.assign(logInfo, conf); 41 | } 42 | if (!has(path, 'node.property.value')) { 43 | return; 44 | } 45 | if (logInfo.action === 'mapping') { 46 | api[prefix][method].mapping 47 | ? set(path, 'node.property.value', api[prefix][method].mapping) 48 | : set(path, 'node.property.value', method); 49 | } 50 | 51 | logInfo.afterCode = generate(path.node).code; 52 | logInstance[logInfo.logLevel](logInfo); 53 | }; 54 | 55 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "wx2", 3 | "version": "1.0.20", 4 | "author": "wangpanfe", 5 | "bin": { 6 | "wx2": "bin/wx2" 7 | }, 8 | "scripts": { 9 | "zip": "sh ./scripts/zip.sh", 10 | "test": "jest", 11 | "coverage": "jest --coverage", 12 | "eslint": "eslint . --fix " 13 | }, 14 | "main": "src/index.js", 15 | "dependencies": { 16 | "@babel/generator": "^7.8.7", 17 | "@babel/parser": "^7.5.5", 18 | "@babel/traverse": "^7.5.5", 19 | "@babel/types": "^7.7.4", 20 | "chalk": "^2.4.2", 21 | "commander": "^4.1.1", 22 | "cosmiconfig": "^6.0.0", 23 | "dependency-graph": "^0.9.0", 24 | "fast-glob": "^3.2.2", 25 | "fs-extra": "^8.1.0", 26 | "glob": "^7.1.6", 27 | "lodash": "^4.17.10", 28 | "mkdirp": "^0.5.1", 29 | "p-reduce": "^2.1.0", 30 | "postcss": "^7.0.27", 31 | "recursive-copy": "^2.0.10", 32 | "regexgen": "^1.3.0", 33 | "single-line-log": "^1.1.2", 34 | "stricter-htmlparser2": "^3.9.6", 35 | "superagent": "^5.2.2", 36 | "unified": "^8.4.2", 37 | "vfile": "^4.0.2" 38 | }, 39 | "deprecated": false, 40 | "description": "微信小程序 转换 百度小程序", 41 | "devDependencies": { 42 | "@babel/core": "^7.7.4", 43 | "@babel/plugin-proposal-function-bind": "^7.10.1", 44 | "@babel/plugin-transform-runtime": "^7.10.1", 45 | "@babel/preset-env": "^7.10.2", 46 | "@babel/preset-stage-0": "^7.8.3", 47 | "babel-eslint": "^10.1.0", 48 | "babel-preset-env": "^1.7.0", 49 | "eslint": "^6.8.0", 50 | "eslint-plugin-babel": "^5.3.0", 51 | "gulp": "^4.0.2", 52 | "gulp-babel": "^7.0.1", 53 | "jest": "^24.8.0" 54 | }, 55 | "engines": { 56 | "node": ">=8.5.0" 57 | }, 58 | "keywords": [ 59 | "小程序", 60 | "微信小程序", 61 | "百度小程序", 62 | "转换", 63 | "swan", 64 | "wx" 65 | ], 66 | "license": "MIT" 67 | } 68 | -------------------------------------------------------------------------------- /src/processor/bootstrap.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file 3 | * Created by wangpanfe on 2019/11/21. 4 | */ 5 | 'use strict'; 6 | 7 | const recursiveCopy = require('recursive-copy'); 8 | const {extname} = require('path'); 9 | const {getFileType} = require('../util/index'); 10 | 11 | /** 12 | * 拷贝文件,及其更换后缀名称 13 | * 14 | * @param {Object} context 上下文信息 15 | */ 16 | exports.transformBootstrap = async function (context) { 17 | const {entry: fromPath, dist: toPath, log, rules} = context; 18 | 19 | await recursiveCopy(fromPath, toPath, { 20 | overwrite: true, 21 | expand: true, 22 | dot: true, 23 | filter(filePath) { 24 | return !/(\.idea|\.git|DS_store)/.test(filePath); 25 | }, 26 | rename(filePath) { 27 | if (/node_modules/.test(filePath)) { 28 | return filePath; 29 | } 30 | if (/\bminiprogram_npm\b/.test(filePath)) { 31 | const npmFileName = rules.suffixMapping.npm || 'node_modules'; 32 | filePath = filePath.replace(/\bminiprogram_npm\b/, npmFileName); 33 | log.warning({ 34 | time: '[START] bootstrapping - find 『miniprogram_npm』deps', 35 | filePath 36 | }); 37 | } 38 | const ext = extname(filePath); 39 | 40 | if (!ext) { 41 | return filePath; 42 | } 43 | 44 | const fileType = getFileType(filePath); 45 | 46 | if (!fileType) { 47 | // 更新进度 48 | global.emitter.emit('event', false); 49 | return filePath; 50 | } 51 | 52 | const targetExtname = rules.suffixMapping[fileType]; 53 | 54 | if (!targetExtname) { 55 | return filePath; 56 | } 57 | return filePath.replace(ext, '.' + targetExtname); 58 | } 59 | }); 60 | }; 61 | -------------------------------------------------------------------------------- /rules/base/MemberExpression/transformAPI.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file 3 | * @des api 映射及 API转换 4 | */ 5 | const generate = require('@babel/generator').default; 6 | const {getNodeMethodName} = require('../../../src/util'); 7 | const {get, set} = require('lodash'); 8 | 9 | /** 10 | * 根据Api转换配置进行处理 11 | * 12 | * @param {Object} context 当前上下文 13 | * @param {Object} path traverse路径 14 | * @param {string} file 要转换函数所在的源文件路径 15 | */ 16 | module.exports = function ({path, context, file}) { 17 | 18 | const logInstance = context.log; 19 | const prefix = get(path, 'node.object.name'); 20 | const {currentPrefix, targetPrefix, API} = get(context, 'rules.api'); 21 | 22 | const conf = { 23 | message: '替换前缀名称', 24 | logLevel: 'warning', 25 | file, 26 | beforeCode: generate(path.node).code 27 | }; 28 | 29 | // 避免多余转换 30 | if (prefix !== currentPrefix || prefix === targetPrefix) { 31 | return; 32 | } 33 | // 替换名称 34 | // TODO: 如果这是用于babel plugin的话,直接操作path里的node,在多个babel plugin一起操作同一个node的时候是会冲突的,建议用replace之类的函数去处理 35 | set(path, 'node.object.name', targetPrefix); 36 | const method = getNodeMethodName(path.node); 37 | 38 | // 获取操作动作 39 | Object.assign(conf, API[method]); 40 | 41 | 42 | if (conf.action === 'map') { 43 | path.get('property').replaceWithSourceString(conf.mapping); 44 | } 45 | 46 | conf.afterCode = generate(path.node).code; 47 | 48 | if (conf.action === 'delete') { 49 | const defaultCode = `${targetPrefix}.showToast({ 50 | title: '${method}暂时不支持,请酌情处理', 51 | icon: 'none', 52 | })`; 53 | 54 | const code = conf.customFunction ? conf.customFunction : defaultCode; 55 | path.parentPath.replaceWithSourceString(code); 56 | 57 | conf.afterCode = code; 58 | } 59 | 60 | logInstance[conf.logLevel](conf); 61 | }; 62 | 63 | 64 | -------------------------------------------------------------------------------- /test/result/result-wx2wx/app.json: -------------------------------------------------------------------------------- 1 | { 2 | "pages": [ 3 | "pages/index/index", 4 | "pages/extend/extend", 5 | "pages/drawer/drawer", 6 | "pages/my/my", 7 | "pages/maps/maps" 8 | ], 9 | "subpackages": [ 10 | { 11 | "root": "pages/basic-view/", 12 | "pages": [ 13 | "color/color", 14 | "button/button" 15 | ] 16 | }, 17 | { 18 | "root": "pages/extend-view/", 19 | "pages": [ 20 | "msgtips/msgtips", 21 | "sticky/sticky", 22 | "keyboard/keyboard" 23 | ] 24 | } 25 | ], 26 | "window": { 27 | "backgroundTextStyle": "dark", 28 | "navigationBarBackgroundColor": "#5677FC", 29 | "navigationBarTitleText": "Thor UI", 30 | "navigationBarTextStyle": "white", 31 | "backgroundColorTop": "#fafafa", 32 | "backgroundColorBottom": "#fafafa" 33 | }, 34 | "tabBar": { 35 | "color": "#666666", 36 | "list": [ 37 | { 38 | "pagePath": "pages/index/index", 39 | "text": "code", 40 | "iconPath": "static/images/tabbar/code_gray.png", 41 | "selectedIconPath": "static/images/tabbar/code_active.png" 42 | }, 43 | { 44 | "pagePath": "pages/extend/extend", 45 | "text": "extend", 46 | "iconPath": "static/images/tabbar/extend_gray.png", 47 | "selectedIconPath": "static/images/tabbar/extend_active.png" 48 | } 49 | ] 50 | }, 51 | "_testEnv": { 52 | "tabBar": { 53 | "color": "#aaaaaa", 54 | "list": [ 55 | { 56 | "pagePath": "pages/index1/index1", 57 | "text": "code2" 58 | } 59 | ] 60 | } 61 | } 62 | } -------------------------------------------------------------------------------- /bin/wx2: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | const chalk = require('chalk'); 4 | const program = require('commander'); 5 | const {resolve, join, dirname, basename} = require('path'); 6 | const {version} = require(resolve(__dirname, '../package.json')); 7 | const transform = require('../src'); 8 | const {isDirectory} = require('../src/util'); 9 | 10 | function printHelp() { 11 | console.log(''); 12 | console.log(' Examples:'); 13 | console.log(''); 14 | console.log(' 交互式创建项目:'); 15 | console.log(' wx2 '); 16 | console.log(''); 17 | } 18 | 19 | 20 | function cleanCache() { 21 | const homedir = require('os').homedir(); 22 | require('child_process').execSync(`rm -rf ${homedir}/.wx2`); 23 | process.exit(1); 24 | } 25 | 26 | 27 | program 28 | .version(version) 29 | .usage('[options]') 30 | .option('-c, --clean', '清除缓存') 31 | .option('-t, --target', '确定转换小程序类型') 32 | .option('-v, --version', version) 33 | .on('--help', printHelp) 34 | .parse(process.argv); 35 | 36 | if (program.clean) { 37 | cleanCache(); 38 | process.exit(); 39 | } 40 | 41 | if (!program.args.length) { 42 | printHelp(); 43 | return; 44 | } 45 | 46 | 47 | function getDefaultDist(entry, target) { 48 | const dir = dirname(entry); 49 | const base = basename(entry); 50 | return join(dir, base + '.wx2.' + target + '.output'); 51 | } 52 | 53 | let target = 'swan'; 54 | for (let i = 0; i < process.argv.length; i++) { 55 | if (process.argv[i].indexOf('--target') > -1 || process.argv[i].indexOf('-t') > -1) { 56 | target = process.argv[i].split('=')[1]; 57 | } 58 | } 59 | 60 | let [entry, dist, logFor, selfRules] = program.args; 61 | 62 | entry = resolve(entry); 63 | 64 | if (!isDirectory(entry)) { 65 | throw new Error('当前不支持转换单个文件,仅支持转换项目'); 66 | } 67 | 68 | dist = dist ? resolve(dist) : getDefaultDist(entry, target); 69 | logFor = logFor ? resolve(logFor) : dist; 70 | 71 | transform({entry, dist, logFor, target, selfRules}).catch(e => { 72 | console.log(chalk.red('🚀 run error: ', e.message + '\n' + e.stack)); 73 | }); -------------------------------------------------------------------------------- /test/source/app.json: -------------------------------------------------------------------------------- 1 | { 2 | "pages": [ 3 | "pages/index/index", 4 | "pages/extend/extend", 5 | "pages/drawer/drawer", 6 | "pages/my/my", 7 | "pages/maps/maps" 8 | ], 9 | "subpackages": [ 10 | { 11 | "root": "pages/basic-view/", 12 | "pages": [ 13 | "color/color", 14 | "button/button" 15 | ] 16 | }, 17 | { 18 | "root": "pages/extend-view/", 19 | "pages": [ 20 | "msgtips/msgtips", 21 | "sticky/sticky", 22 | "keyboard/keyboard" 23 | ] 24 | } 25 | ], 26 | "window": { 27 | "backgroundTextStyle": "dark", 28 | "navigationBarBackgroundColor": "#5677FC", 29 | "navigationBarTitleText": "Thor UI", 30 | "navigationBarTextStyle": "white", 31 | "backgroundColorTop": "#fafafa", 32 | "backgroundColorBottom": "#fafafa" 33 | }, 34 | "tabBar": { 35 | "color": "#666666", 36 | "list": [ 37 | { 38 | "pagePath": "pages/index/index", 39 | "text": "code", 40 | "iconPath": "static/images/tabbar/code_gray.png", 41 | "selectedIconPath": "static/images/tabbar/code_active.png" 42 | }, 43 | { 44 | "pagePath": "pages/extend/extend", 45 | "text": "extend", 46 | "iconPath": "static/images/tabbar/extend_gray.png", 47 | "selectedIconPath": "static/images/tabbar/extend_active.png" 48 | } 49 | ] 50 | }, 51 | "_swanEnv": { 52 | "tabBar": { 53 | "color": "#aaaaaa", 54 | "list": [ 55 | { 56 | "pagePath": "pages/index1/index1", 57 | "text": "code2" 58 | } 59 | ] 60 | } 61 | }, 62 | "_testEnv": { 63 | "tabBar": { 64 | "color": "#aaaaaa", 65 | "list": [ 66 | { 67 | "pagePath": "pages/index1/index1", 68 | "text": "code2" 69 | } 70 | ] 71 | } 72 | } 73 | } -------------------------------------------------------------------------------- /src/util/progress.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const fs = require('fs'); 3 | const EventEmitter = require('events'); 4 | const emitter = new EventEmitter.EventEmitter(); 5 | const slog = require('single-line-log').stdout; 6 | 7 | global.emitter = emitter; 8 | 9 | exports.getAllFile = dir => { 10 | let transformFilesLength = 0; 11 | let transFileEnd = 0; 12 | const eachFile = path.resolve(__dirname, dir); 13 | const getFile = childDir => { 14 | fs.readdir(childDir, (err, files) => { 15 | files.forEach(item => { 16 | const filePath = path.resolve(childDir, item); 17 | // 判断是否是文件夹,如果是,递归调用 18 | if (fs.statSync(filePath).isDirectory()) { 19 | getFile(filePath); 20 | } 21 | // 判断是否是文件,如果是,总文件数加一 22 | if (fs.statSync(filePath).isFile()) { 23 | transformFilesLength += 1; 24 | } 25 | }); 26 | }); 27 | }; 28 | getFile(eachFile); 29 | 30 | // 监听打印进度 31 | emitter.on('event', args => { 32 | if (args) { 33 | transFileEnd = transformFilesLength; 34 | emitter.off('event', () => {}); 35 | } else { 36 | transFileEnd += 1; 37 | } 38 | global.transformFilesLength = transformFilesLength; 39 | new ProgressBar().render({completed: transFileEnd, total: transformFilesLength}); 40 | }); 41 | }; 42 | 43 | function ProgressBar(description, length) { 44 | this.description = description || '文件转换进度'; 45 | this.length = length || 30; 46 | 47 | this.render = function (opts) { 48 | const percent = (opts.completed / opts.total).toFixed(4); 49 | const cellNum = Math.floor(percent * this.length); 50 | // 拼接黑色条 51 | let cell = ''; 52 | for (let i = 0; i < cellNum; i++) { 53 | cell += '█'; 54 | } 55 | // 拼接灰色条 56 | let empty = ''; 57 | for (let i = 0; i < this.length - cellNum; i++) { 58 | empty += '░'; 59 | } 60 | 61 | // 进度条 & 比例 62 | const shadow = `${cell}${empty} ${opts.completed}/${opts.total}`; 63 | // 拼接最终文本 64 | const cmdText = `🎉 ${this.description} : ${(100 * percent).toFixed(2)}% ${shadow}\n`; 65 | // 在单行输出文本 66 | slog(cmdText); 67 | }; 68 | } 69 | 70 | 71 | -------------------------------------------------------------------------------- /src/log/log.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file 转换日志 3 | * @description 记录转换日志相关方法 4 | */ 5 | const utils = require('../util/index'); 6 | let instance = null; 7 | 8 | class Log { 9 | constructor(path) { 10 | if (instance) { 11 | return instance; 12 | } 13 | instance = this; 14 | this.path = path; 15 | this.logMsg = { 16 | info: '', 17 | warning: '', 18 | error: '' 19 | }; 20 | this.lineBreak = utils.isWin() ? '\r\n' : '\r'; 21 | } 22 | 23 | log(action, payload = {}) { 24 | switch (action) { 25 | case 'info': 26 | this.logMsg.info += JSON.stringify(payload) + this.lineBreak; 27 | break; 28 | case 'warning': 29 | this.logMsg.warning += JSON.stringify(payload) + this.lineBreak; 30 | break; 31 | case 'error': 32 | this.logMsg.error += JSON.stringify(payload) + this.lineBreak; 33 | break; 34 | default: 35 | throw new Error('action未定义,行为禁止'); 36 | } 37 | } 38 | 39 | info(payload) { 40 | this.log('info', payload); 41 | } 42 | 43 | warning(payload) { 44 | this.log('warning', payload); 45 | } 46 | 47 | error(payload) { 48 | this.log('error', payload); 49 | } 50 | 51 | static info(payload) { 52 | instance.log('info', payload); 53 | } 54 | 55 | static warning(payload) { 56 | instance.log('warning', payload); 57 | } 58 | 59 | static error(payload) { 60 | instance.log('error', payload); 61 | } 62 | 63 | static log(action, payload) { 64 | instance.log(action, payload); 65 | } 66 | 67 | async dump() { 68 | const path = this.path; 69 | ['info', 'warning', 'error'].forEach(level => { 70 | const logs = this.logMsg[level]; 71 | // main.create(logs, path); 72 | if (logs !== undefined) { 73 | utils.saveLog(`${path}/wx2_log/${level}.txt`, logs); 74 | } 75 | }); 76 | 77 | // 上报互转情况 78 | let result = { 79 | state: 'success', 80 | fileNumber: global.transformFilesLength 81 | }; 82 | utils.saveLog(`${path}/wx2_log/result.json`, JSON.stringify(result)); 83 | } 84 | } 85 | 86 | module.exports = Log; 87 | -------------------------------------------------------------------------------- /test/result/result-wx2wx/index.wxml: -------------------------------------------------------------------------------- 1 | 2 | test 3 | test 4 | test 5 |