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": [

"/**"

],

"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 (

)

}

那么代码为 。

处理 ExportDefaultDeclaration

如果为入口文件,标记有 export default。

处理 JSXElement

标记有 JSX。

处理 JSXOpeningElement

处理 相关,获取 store 名称。

处理 Program 的 exit。

ClassMethod

主要处理入口文件。

在 constructor 中插入语句 Taro._$app = this

在 componentDidMount 中插入语句 this.componentDidShow(),前提为 componentDidShow 存在。

在 componentWillUnmount 中插入语句 this.componentDidHide(),前提为 componentDidHide 存在。

在render 中插入,根据是否有 pages,provider 等生成最终的结构。

比如有 pages 生成如下:

render() {

return

;

}

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'

转换后:

import indexStyleSheet from "./index_styles";

var _styleSheet = indexStyleSheet;

;

生成工程文件

生成如下文件:

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"

}

Logo

开源鸿蒙跨平台开发社区汇聚开发者与厂商,共建“一次开发,多端部署”的开源生态,致力于降低跨端开发门槛,推动万物智联创新。

更多推荐