taro-build
taro-build
:命令行工具,将 react
代码转换成 weapp/swan/alipay/tt/h5/quickapp/rn
几种类型的应用。
其参数包括如下:
-
type
,待转换的类型,包括weapp/swan/alipay/tt/h5/quickapp/rn
。 -
watch
,监听模式,可监测文件改动,自动做转换。 -
page
,编译页面。 -
component
,编译组件。 -
ui
,编译taro ui
库。 -
ui-index
,指定taro ui
库的索引文件路径。 -
plugin
,编译taro
插件,微信小程序所用。 -
port
,指定端口。 -
release
,发布快应用quickapp
。
在这里我们需要梳理转换为 rn
的流程,因此 type
传入 rn
即可。
launch.json
的配置如下:
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"type": "node",
"request": "launch",
"name": "Launch Program",
"skipFiles": [
"<node_internals>/**"
],
"program": "${workspaceFolder}/bin/taro-build",
"preLaunchTask": "tsc: build - tsconfig.json",
"outFiles": [
"${workspaceFolder}/dist/**/*.js"
],
"cwd": "${workspaceFolder}/myApp",
"args": [
"--type","rn"
]
}
]
}
cwd
为待转换 taro
项目路径。
转换过程
主要代码逻辑在 src/rn.ts -> build()
中,转换输出目录为 rn_tmp
。步骤如下:
检查依赖
检查 package.json
中是否有 rn
相关依赖,即检查 dependencies
中是否包含 react-native
。若没有,更新 package.json
文件添加如下依赖,并进行安装。
{
"@tarojs/components-rn": "^${version}",
"@tarojs/taro-rn": "^${version}",
"@tarojs/taro-router-rn": "^${version}",
"@tarojs/taro-redux-rn": "^${version}",
"react": "16.3.1",
"react-native": "0.55.4",
"redux": "^4.0.0",
"tslib": "^1.8.0"
}
代码与样式转换
主要工作由 Compiler.buildTemp()
来实现。
其遍历 taro
工程下的所有文件,调用 processFile
进行处理,处理流程如下:
- 如果为样式文件,不做处理。
- 如果为
js
文件,则会进行ast
的处理和依赖样式的转换。 - 其余类型文件,比如
html
,则直接copy
到rn_temp
文件夹。 - 如果是
watch
模式,则会在本地启动一个rn
打包服务器。 - 打包为
bundle
,调用react-native
的cli.js
实现。
下面着重讲第二步,分为「代码转换」和「样式转换」。
代码转换
其主要调用 transformJSCode
,返回生成的代码和引用的样式文件路径集合,处理如下:
-
调用
wxTransformer
转换成ast
,其会将ts
代码编译成js
代码后,在babel
中设置一系列的插件返回ast
。 -
遍历
ast
,进行如下处理:-
处理
ClassDeclaration/ClassExpression
即处理类的声明,获取当前类名。分为以下两种情况:
- 类继承自
Taro.xx
,即Taro
的预置组件。注意这里Taro
的值是从下面的ImportDeclaration
中获得的。即import Taro from '@tarojs/taro
。 - 类继承自
Component/PureComponent
。
如果当前类无类名,即为
export default class extends xxx
的情况,则默认加上类名_TaroComponentClass
,并修改ast
。最终记录类名。 - 类继承自
-
处理
ExpressionStatement
预处理
require
样式文件,将其改成import
的方式。 -
处理
ImportDeclaration
-
处理
import
的样式文件,保存其路径 -
处理
js/ts
文件,重新设置其相对路径 -
如果从
@tarojs/taro
中导入,改成@tarojs/taro-rn
import Taro, { getEnv, Component } from '@tarojs/taro
注意这里有两种类型,可以结合
ast
自行查看。-
ImportDefaultSpecifier
:默认导出类型,非{}
包裹,这里为Taro
。 -
ImportSpecifier
:{}
包裹,这里为Component
和getEnv
。
这里会记录下
ImportDefaultSpecifier
中Taro
的值,在后面处理中会用到,因为需要import
。同时会检查导入的变量是否属于
taroApis
。上述例子中,getEnv
就是taroApi
,则需要导入。变量需要被使用才会被
import
,转换后如下:import { Component } from "@tarojs/taro-rn"; import { getEnv } from "@tarojs/taro-rn";
可能有人会疑惑为啥
import Taro from '@tarojs/taro-rn';
没有生成。其实这句的导入是在后面过程才处理。 -
-
如果从
@tarojs/redux
中导入,改成react-redux-rn
-
如果从
@tarojs/mobx
中导入,改成@tarojs/mobx-rn
-
如果从
@tarojs/components
中导入,改成@tarojs/components-rn
-
-
处理
ClassProperty
主要处理入口文件的
config
属性:- 处理
pages
,记录路径,然后移除节点 - 处理
tabBar
,记录icon
路径,然后移除节点 - 将
config
设置为static
,由于再次生成ast
时使用了babel-plugin-transform-class-properties
插件,所以最终表现为:App.config = { window: { xx: 'yy'}
最终只剩下
windows
相关的属性。 - 处理
-
处理
ClassMethod
注意这里仅处理入口文件
app.js
。-
标记是否有
constructor/componentDidMount/componentDidShow/componentDidHide/componentWillUnmount
-
遍历
render
方法的语法树,生成JSXElement
的代码。比如
App.js
中render
方法如下:render () { return ( <Index /> ) }
那么代码为
<Index />
。
-
-
处理
ExportDefaultDeclaration
如果为入口文件,标记有
export default
。 -
处理
JSXElement
标记有
JSX
。 -
处理
JSXOpeningElement
处理
<Provider>
相关,获取store
名称。 -
处理
Program
的exit
。-
ClassMethod
主要处理入口文件。
-
在
constructor
中插入语句Taro._$app = this
-
在
componentDidMount
中插入语句this.componentDidShow()
,前提为componentDidShow
存在。 -
在
componentWillUnmount
中插入语句this.componentDidHide()
,前提为componentDidHide
存在。 -
在
render
中插入,根据是否有pages
,provider
等生成最终的结构。比如有
pages
生成如下:render() { return <TCRNProvider> <RootStack /> </TCRNProvider>; }
-
-
ClassBody
处理一些异常情况。
-
若有
componentDidShow
,但没有componentDidMount
方法,则插入componentDidMount
方法,并添加语句this.componentDidShow && this.componentDidShow()
。 -
若有
componentDidHide
,但没有componentWillUnmount
方法,则插入componentWillUnmount
方法,并添加语句this.componentDidHide && this.componentDidHide()
。 -
若没有
constructor
方法,则添加如下代码constructor() { super(...arguments); Taro._$app = this; }
-
-
CallExpression
如果有直接调用
Taro.render()
,则进行移除。
-
-
如果有
JSX
,则导入import React from 'react'
-
如果有使用
Taro
,则导入import Taro from @tarojs/taro-rn'
-
入口文件处理
-
导入依赖的页面文件路径。
-
导入
tarbBarIcon
路径,如果有设置tarBar
的话。 -
末尾插入一些额外的代码,比如页面路由/ 初始化
api
能力/px
转换等。// router 相关 const RootStack = TaroRouter.initRouter([['pages/index/index', pagesIndexIndex]], Taro, App.config); // api 能力 Taro.initNativeApi(Taro); // pxTransform Taro.initPxTransform({ "designWidth": 750, "deviceRatio": { "640": 1.17, "750": 1, "828": 0.905 } }); // 默认导出 export default App;
-
-
注意点
-
上述操作对
ast
修改完成后,会调用babel.transformFromAst
,设置一系列插件重新生成ast
,再转换为最终代码。// 将 jsx 中样式的写法转换为 stylesheet babel-plugin-transform-jsx-to-stylesheet // 使用 class property babel-plugin-transform-class-properties // 使用装饰器 babel-plugin-transform-decorators-legacy // 移除无用 import babel-plugin-danger-remove-unused-import // 定义常量信息,这里用来设置环境,process.env.TARO_ENV babel-plugin-transform-define
-
转换完成后,会发现如下代码:
let App = class App extends Component {}
声明的
class
被赋值给了变量App
。这其实是通过步骤1
中的插件babel-plugin-transform-decorators-legacy
完成的。
转换样式
调用 compileDepStyles
,主要处理逻辑在 StyleProcess
中。
-
读取样式文件,进行
scss/sass/less
的预处理后,返回css string
。 -
使用
postcss
插件pxtransform
进行单位转换,具体计算操作在createPxReplace
。 -
调用
taro-css-to-react-native
进行样式的处理。app.scss
:.node { background-color: red; }
转换之后生成
app_styles.js
:import { StyleSheet, Dimensions } from 'react-native' // 一般app 只有竖屏模式,所以可以只获取一次 width const deviceWidthDp = Dimensions.get('window').width const uiWidthPx = 375 function scalePx2dp (uiElementPx) { return uiElementPx * deviceWidthDp / uiWidthPx } export default StyleSheet.create({ "node": { "backgroundColor": "red" } })
-
校验生成的
rn
样式属性的正确性。 -
生成
rn
样式文件xx_styles.js
。
在转换后我们可以发现,原本引入的是 xx.scss
样式文件,现在变为了 import xxStyleSheet from "./xx_styles"
。那么这一步是在哪做的呢?
其实是在上一步提到的,再次重新生成 ast
的过程中,设置 babel-plugin-transform-jsx-to-stylesheet
插件起的作用。
原始:
import './index.scss'
<View className='index'>
</View>
转换后:
import indexStyleSheet from "./index_styles";
var _styleSheet = indexStyleSheet;
<View style={_styleSheet["index"]}>
</View>;
生成工程文件
生成如下文件:
-
index.js
,入口文件,添加下列代码。import {AppRegistry} from 'react-native'; import App from './app'; import {name as appName} from './app.json'; AppRegistry.registerComponent(appName, () => App);
-
app.json
,工程配置
主要是将package.json
中的name
值取出,放入到app.json
中。{ "name": "myApp" }
网友评论