首先上代码:
let babel = require('babel-core');
let t = require('@babel/types');
let jsCode =`const a = 100`
const visitor ={
VariableDeclaration(path){
const {node} = path
let barDec = t.variableDeclaration('const',[
// t.VariableDeclarator('a','100') ? 这样写肯定是错的,不要问啥,问就是不知道。那咋写呢?
// 注意观察 id 的type 是 Identifier init 的type 是 NumberLiteral ,我有一个大胆的想法
t.VariableDeclarator(
t.Identifier('a'),
t.NumericLiteral(100)
)
])
console.log(t.isVariableDeclaration(barDec))
}
}
let result= babel.transform(jsCode, {
ast:true,
plugins: [
{ visitor}
]
});
第二步:结果输出true,没毛病。
啰嗦几句:
1.npm init
2.装依赖
3. node index.js
4.告辞
好,现在来说下babel-code,两句话:1. 引入babel-code来转换代码成ast 2.目前只晓得 babel.transform 可以转成ast语法树。其他的深入研究请交给以后的自己。(主要是我也不知道)
现在接着上小程序代码:
Page({
data:{
index:0
},
onload(){},
init(){},
carry(){}
})
react代码:
// imrc
class Page extends Component{
constructor(props){
super(props)
this.state ={
index:0
}
}
componentDidMounted(){},
init=()=>{}
carry=()=>{}
}
首先来看一下page这颗树
再来看看react这颗树
所以我们要做什么呢?
1.ExpressionStatement => ClassDeclaration
2.ObjectExpression => ClassBody
3.FunctionExpression=> classmethod || classproperty
现在让我们来看看最开始的那些代码是什么意思:
let result= babel.transform(jsCode, {
ast:true,
plugins: [
{ visitor}
]
});
console.log(result.code)
console.log(result.ast)
// babel.transform 的作用就是将代码转为ast语法树
// 而plugin则是对ast语法树进行处理
// 在上一章我们了解到 const a = 100 对应的ast type 是 VariableDeclaration
// 所以我们需要 在pluigin 中通过 VariableDeclaration 去 获取const a = 100 对应的语法树
// 我们需要去对什么原有js部分进行处理,就要去 通过visitor去获取他的type
// 比如说 我们要把所有的 变量 a 改为x .
const visitor = {
Identifier(path){
const {node} = path
node.name = 'x'
}
}
将微信小程序转为react
// 根据上面的图片我们观察到微信小程序代码的最顶层是个 ExpressionStatement 类型
// 所以我们就来通过 ExpressionStatement(path) 来获取到Page({})
ExpressionStatement(path) {
const pNode = path.node
const node = pNode.expression // 通过pNode.expression 获取到 类型为 CallExpression 的结构
// 对应的代码为({data:{}....})
// 下面这个if则是判断是否是Page
if (node.callee && node.callee.name === 'Page') {
// properties 代表的是 {data:{}....} 对象内的每一项
const { properties } = node.arguments[0]
let dataObj = {}
for (var i = 0; i < properties.length; i++) {
const item = properties[i]
const { type } = item
if (type === 'ObjectProperty') {
// type === 'ObjectProperty' 代表的是 data:{xxxx}
// 我们要把data:{xxxx} 转成
/*
* constructor(props) {
super(props);
this.state = {
pageNum: 12,
pageSize: 220,
index: '',
current: ''
};
}
*
**/
dataObj = Object.assign({}, item.value)
const params = t.Identifier('props')
const kk = Object.assign({}, item.key)
kk.name = 'constructor'
// 从一个属性对象构造成一个方法所以需要重新去构造一个function body
// blockStatement 构造一个代码块 { }
// t.CallExpression(t.identifier('super'), [t.identifier('props')]) 构造 super(props)
const body = t.blockStatement([t.expressionStatement(
t.CallExpression(t.identifier('super'), [t.identifier('props')])
), t.ExpressionStatement(
// ExpressionStatement 变量声明 第一个参数 是变量名,第二个是value
t.AssignmentExpression('=', t.MemberExpression(
// t.thisExpression() 声明一个this指针 t.identifier('state') 声明一个state变量
t.thisExpression(), t.identifier('state')
), item.value)
)])
properties[i] = t.classMethod('constructor', kk, [params], body, false, null)
} else if (type === 'ObjectMethod') {
let ta = t.arrowFunctionExpression(item.params, item.body, item.async)
properties[i] = t.classProperty(item.key, ta, null, null)
}
}
let stateIdf = []
for (let i = 0; i < dataObj.properties.length; i++) {
const pItemName = dataObj.properties[i].key.name
stateIdf.push(t.objectProperty(t.identifier(pItemName), t.identifier(pItemName), false, true))
}
//新增一个render函数
const renderKey = t.identifier('render')
// const {aaa,bbb,cccc} = this.state
const renderbody = t.VariableDeclaration('const', [
t.VariableDeclarator(
t.objectPattern(
stateIdf
), t.MemberExpression(
t.thisExpression(), t.identifier('state')
)
)]
)
//构造结束
console.log(htmlCode)
// 构造return <div>{code}</div>
const openEle = t.JSXOpeningElement(t.JSXIdentifier('div'),[])
const closeEle = t.jsxClosingElement(t.JSXIdentifier('div'))
const jsxChildText = t.JSXText(htmlCode)
const returnBody = t.ReturnStatement(t.JSXElement(openEle,closeEle,[jsxChildText],true))
//
const renderBlockStatement = t.blockStatement([renderbody,returnBody])
const renderProperty = t.classMethod('method', renderKey, [], renderBlockStatement)
//构造结束
properties.push(renderProperty)
const claBody = t.classBody(properties)
const className = t.Identifier('PageClass')
const extendClassName = t.Identifier('Component')
const cld = t.exportDefaultDeclaration(t.ClassDeclaration(className, extendClassName, claBody, null))
path.replaceWith(cld)
}
说一下整体思路把,首先是基于上一篇的两个网址 ,当我们想用babel去做一些代码转换的时候,我们肯定是可以知道我们需要被转换的代码格式和被转换后的代码格式 ast语法树 通过这个网址我们可以得到两颗语法树,对比语法树差异,确定我们需要改动的地方。最后通过 path.replaceWith 替换 通过 path.remove()删除,新增之前看网上有insetBefore 、insetAfter 方法但是我不会用,我用的是蛮干,因为需要新增的地方肯定都是数组,如果不是请掰弯了以后再push。babel 没我们想的那么难,其实只是个搭积木的游戏,只不过需要用特制的积木,我们通过上面的网址,去找到对应的色块,按照树结构填充就好了。
babel 如何编译wxml成一颗ast
直接上代码
let visitor = require('./visitor')
let {parse} = require('@babel/parser')
var {default: generate} = require('@babel/generator');
var {default: traverse} = require('@babel/traverse');
module.exports = (htmlCode) =>{
let resetCode = htmlCode.replace(/<!--[\w\W\r\n]*?-->/gmi, '')
resetCode = resetCode.replace(/<wxs[\w\W\r\n]*?\/>/gmi, '')
let originAST = parse(resetCode,{plugins:['jsx']})
traverse(originAST,visitor);
var targetCode = generate(originAST); // ast 转为原始代码
console.log(targetCode.code) // 输出结果
return targetCode.code
}
// visitor.js
module.exports = {
JSXIdentifier(path){
var {node} = path;
let keys = Object.keys(htmlOptions)
if(keys.includes(node.name)){
node.name = htmlOptions[node.name]
}
}
}
// 没找到如何配置babel-transform 来支持jsx ,因为babel.transform 默认编译ast语法树的方式是babel-eslint-parser 这会导致html 代码在里面是错误的,所以必须通过 babel-parse 来转换。