taro-convert
是一个命令行工具,其作用是将原生微信小程序转换成 taro
标准代码。其主要是通过src/convertor/index.ts
中的 Convertor
类来实现。
初始化
在生成Convertor
对象时,除了一些必要的初始化工作外,比如初始化项目根路径,生成输出文件夹等等。其还会去解析微信小程序工程。主要包括如下几方面:
读取入口文件
app.js
路径。-
读取配置文件
app.json
,获取如下信息:-
pages
,获取路由信息,存放到页面路由集合中。 -
sitemapLocaiton
,若存在,则将sitemap.json
拷贝到转换输出目录。 -
subpackages/subPackages
,获取subpackages
信息,添加到路由页面集合中。
-
读取样式文件
app.wxss
。
转换过程
转换流程分为以下三个步骤:
- 入口文件的转换
- 页面文件的转换,逐个遍历页面文件,进行转换。即在
app.json
中定义的pages
。 - 根据模板生成工程配置文件
js 代码转换
主要分为入口文件App.js
和导入文件的转换。
入口文件转换
主要是在 generateEntry
方法中,其内部调用了generateScriptFiles
处理导入的文件。
主要步骤如下:
- 调用
taroize
生成ast
及wxml
中导入的模板信息。taroize
的内部处理下面会有说明。 - 调用
parseAst
遍历并修改ast
,并且统计import
的js
文件,作为结果返回。 - 调用
generateMinimalEscapeCode
,根据ast
生成代码,并写入文件到输出目录。 - 若入口有样式文件,则调用
traverseStyle
进行处理。 - 若入口文件有
import
其他js
文件,则继续调用generateScriptFiles
进行处理。内部逻辑在下面会讲到。 - 若入口配置文件中有定义
tabBar
,则调用generateTabBarIcon
进行处理。将Icon/selectedIcon
复制到输出目录。
导入文件转换
导入文件指的是通过 require/import
引入的文件。其转换逻辑主要在 generateScriptFiles
方法,入参为 js
文件路径集合。
主要步骤如下:
- 遍历每个
js
文件。 - 调用
wxTransformer
生成ast
。其内部逻辑很复杂,但是对于微信小程序来说,只是简单返回其ast
,无其他处理。 - 调用
parseAst
遍历并修改ast
,并返回import
的js
文件。其逻辑下面会有说明。 - 调用
generateMinimalEscapeCode
生成代码,写入文件。 - 因为当前处理的
js
文件可能也import
了其他js
文件,所以递归调用generateScriptFiles
进行处理。
taroize 方法
taroize
是比较重要的一个方法,会进行一系列的预处理。其参数中包含了配置信息 xx.json
,js
文件内容,wxml
模板信息。具体处理如下:
-
parseXML
如果传入了
xml
,则进行解析,包括标签的解析。 -
parseJSON
将
json
加上括号包装成表达式(json)
,调用babel
的template
方法返回对象表达式的节点信息ObjectExpression
。会用在后续的parsePage
中(下面会提到),主要用于填充新生成类中的config
的值。比如
json
为{'title': 'hello'}
,则会返回下图中红框部分。
-
parseScript
设置一系列
babel
插件参数,将代码转换为ast
。然后做如下处理:-
自定义
vistior
进行访问。-
遇到
BlockStatement
,即方法具体实现{}
, 将 作用域scope
的值从wx
修改为Taro
。也就是说如果在
xx.js
最外层定义了function
,会走到这里。如下://获取应用实例 const app = Taro.getApp() function test() { console.log('test') }
遇到
Identifier
标识符,且节点名为wx
,则替换为Taro
。-
遇到
CallExpression
函数调用,进行标识符、属性调用的替换以及类的生成。-
标识符替换
如果
Identifier
为getApp
/getCurrentPages
,替换成Taro.getApp
/Taro.getCurrentPages
调用。 -
属性调用替换
如果为
wx.xxx
的调用,则替换为Taro.xxx
。 -
Page/Component/App
方法调用,类的生成如果是
Page/Component/App
方法,则说明其为微信小程序
·。调用parsePage
返回一个ClassDecleration
结构,插入到body
中。这样生成的文件中就会包含如下信息:@withWeapp({ xx }) class _C extends Taro.Component { config = { navigationBarTitleText: '查看启动日志' } render () { } }
其中
parsePage
中包括config
,render 方法
,装饰器withWeapp
的生成。config
为配置文件json
中的信息。{ "navigationBarTitleText": "查看启动日志", "usingComponents": {} }
-
-
如果不为微信小程序,则在
js
文件末尾默认加上;Component({})
,再进行以上处理。-
导入使用的组件和依赖文件,结果如下所示:
``` import { Block, View, Button, Image, Text } from '@tarojs/components' import Taro from '@tarojs/taro' import withWeapp from '@tarojs/with-weapp' ```
其中使用的组件是通过解析
wxml
中的标签来计算,比如以下xml
代码:``` <view class="container log-list"> <block wx:for="{{logs}}" wx:for-item="log"> <text class="log-item">{{index + 1}}. {{log}}</text> </block> </view> ```
则会自动引入
Block
、View
、Text
组件,其中Block
是默认就会引入的,无论在xml
中是否使用。
-
parseAst 方法
可使用 https://astexplorer.net/ 查看语法树。
主要用来遍历 ast
,对其进行修改,并统计 import
的 js
文件信息。其中语法树的修改包括头部导入 Taro
框架,调用对象的替换为Taro
等等。
首先使用 traverse
进行 ast
遍历,分为 enter
和 exit
两个过程。
enter 处理
在 enter
时会处理如下几种类型:
-
ClassDeclaration
,类的声明。内部会继续遍历
ClassMethod
,类中定义的方法。如果遇到render
,继续遍历,如果存在JSXElment
,标记为taro
组件,并记录类名。一个
JSXElement
如下,相当于一个组件的声明。<Image src={require('../kkk')}></Image>
-
ClassExpression
,这里主要用于类被赋值给一个变量的情况,=
右边为ClassExpression
。内部处理方式同步骤1
。f = class NamedFoo { constructor() {} whoIsThere() { return NamedFoo.name; } }
-
ExportDefaultDeclaration
,处理export default
的情况。同样包括两种情况。内部处理方式同步骤1
。export default class Component {}
export default a = class Component {}
-
ImportDeclaration
,处理import
的js/ts
文件。通过调用
analyzeImportUrl
分析引入的js/ts
文件。 -
CallExpression
,处理函数调用。- 若调用了
require
,则调用analyzeImportUrl
分析出import
的js/ts
文件。 - 若调用了微信小程序的
['getApp', 'getCurrentPages', 'requirePlugin']
这几个方法,则会替换成Taro
调用,并标记需要引入Taro
框架。
- 若调用了
-
MemberExpression
,处理对象属性调用。
如果调用对象为wx
,则替换成Taro
。同时标记需要导入Taro
框架。wx.setStorageSync('logs', logs) => Taro.setStorageSync('logs', logs)
exit 处理
在 exit
中做如下处理。
-
处理
body
节点,如果需要导入@tarojs/taro
,则加入importDeclaration
节点。import Taro from '@tarojs/taro'
-
遍历
StringLiteral
,主要处理图片路径引用。计算其绝对路径, 并将图片复制到输出目录。重新计算源文件与图片的相对路径
relativePath
。如果其父节点是变量声明(值为图片路径),则使用
require(relativePath)
进行替换。-
如果其父节点是
JSX
属性,则使用jSXExpressionContainer
进行替换,即{require(relativePath)}
表达式的方式进行替换。在下例中,
src
就是JSX
属性。<Image src={require('../kkk')}></Image>
-
导入依赖文件,包括引入样式和其他依赖
导入样式:
import './app.scss'
导入依赖组件和文件:
import xx from 'xx'
其中依赖的组件是从页面配置文件的
usingComponent
中解析出来的。 -
如果判断
isApp
为true
,则会在最后插入一段代码。Taro.render(<App />, document.getElementById('app'))
但只有在解析入口文件时,
isApp
才为true
。
样式转换
通过 postcss,传入自己写的postcss-taro-unit-transform
插件来进行转换的。其内部实现很简单,主要做了两件事情:
- 把
10px
转换为20px
,即将数值 *2
。 - 把
rpx
替换为px
。
页面转换
包括js
文件转换 + 样式转换 + 依赖组件递归转换。与入口文件的转换类似,多了页面模板 wxml
+ 依赖组件的转换
。
其会从页面配置信息中拿到出依赖的组件信息。
组件转换
同页面转换。
根据模板生成配置文件
根据对应文件的模板生成相应的内容。
配置文件
-
index.js
:通用配置 -
dev.js
:开发配置,会合并index.js
-
pro.js
:发布配置,会合并index.js
.gitignore
-
.editorconfig
:编辑器相关配置 -
.eslintrc
:eslint
相关配置 -
project.config.json
:工程配置 package.json
src/index.html