美文网首页
taro-build 之 RN 转换

taro-build 之 RN 转换

作者: 微微笑的蜗牛 | 来源:发表于2020-04-25 18:45 被阅读0次

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 进行处理,处理流程如下:

  1. 如果为样式文件,不做处理。
  2. 如果为 js 文件,则会进行 ast的处理和依赖样式的转换。
  3. 其余类型文件,比如 html,则直接 copyrn_temp 文件夹。
  4. 如果是 watch 模式,则会在本地启动一个 rn 打包服务器。
  5. 打包为 bundle,调用 react-nativecli.js 实现。

下面着重讲第二步,分为「代码转换」和「样式转换」。

代码转换

其主要调用 transformJSCode,返回生成的代码和引用的样式文件路径集合,处理如下:

  1. 调用 wxTransformer 转换成 ast,其会将 ts 代码编译成 js 代码后,在 babel 中设置一系列的插件返回ast

  2. 遍历 ast,进行如下处理:

    • 处理 ClassDeclaration/ClassExpression

      即处理类的声明,获取当前类名。分为以下两种情况:

      1. 类继承自 Taro.xx,即 Taro 的预置组件。注意这里 Taro 的值是从下面的 ImportDeclaration 中获得的。即 import Taro from '@tarojs/taro
      2. 类继承自 Component/PureComponent

      如果当前类无类名,即为 export default class extends xxx 的情况,则默认加上类名 _TaroComponentClass,并修改 ast。最终记录类名。

    • 处理 ExpressionStatement

      预处理 require 样式文件,将其改成 import 的方式。

    • 处理 ImportDeclaration

      1. 处理 import 的样式文件,保存其路径

      2. 处理 js/ts 文件,重新设置其相对路径

      3. 如果从 @tarojs/taro 中导入,改成 @tarojs/taro-rn

        import Taro, { getEnv, Component } from '@tarojs/taro
        

        注意这里有两种类型,可以结合 ast 自行查看。

        • ImportDefaultSpecifier:默认导出类型,非 {} 包裹,这里为 Taro
        • ImportSpecifier{} 包裹,这里为 ComponentgetEnv

        这里会记录下 ImportDefaultSpecifierTaro 的值,在后面处理中会用到,因为需要 import

        同时会检查导入的变量是否属于 taroApis 。上述例子中,getEnv 就是 taroApi,则需要导入。

        变量需要被使用才会被 import,转换后如下:

        import { Component } from "@tarojs/taro-rn";
        import { getEnv } from "@tarojs/taro-rn";
        

        可能有人会疑惑为啥 import Taro from '@tarojs/taro-rn'; 没有生成。其实这句的导入是在后面过程才处理。

      4. 如果从 @tarojs/redux 中导入,改成 react-redux-rn

      5. 如果从 @tarojs/mobx 中导入,改成 @tarojs/mobx-rn

      6. 如果从 @tarojs/components 中导入,改成 @tarojs/components-rn

    • 处理 ClassProperty

      主要处理入口文件的 config 属性:

      1. 处理 pages,记录路径,然后移除节点
      2. 处理 tabBar,记录 icon 路径,然后移除节点
      3. config 设置为 static,由于再次生成 ast 时使用了 babel-plugin-transform-class-properties 插件,所以最终表现为:
        App.config = { window: { xx: 'yy'}
        

      最终只剩下 windows 相关的属性。

    • 处理 ClassMethod

      注意这里仅处理入口文件 app.js

      1. 标记是否有constructor/componentDidMount/componentDidShow/componentDidHide/componentWillUnmount

      2. 遍历 render方法的语法树,生成 JSXElement 的代码。

        比如 App.jsrender 方法如下:

        render () {
            return (
              <Index />
            )
        }
        

        那么代码为 <Index />

    • 处理 ExportDefaultDeclaration

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

    • 处理 JSXElement

      标记有 JSX

    • 处理 JSXOpeningElement

      处理 <Provider> 相关,获取 store 名称。

    • 处理 Programexit

      1. ClassMethod

        主要处理入口文件。

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

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

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

        • render 中插入,根据是否有 pagesprovider 等生成最终的结构。

          比如有 pages 生成如下:

           render() {
               return <TCRNProvider>
                       <RootStack />
                    </TCRNProvider>;
          }
          
      2. ClassBody

        处理一些异常情况。

        • 若有componentDidShow,但没有 componentDidMount 方法,则插入 componentDidMount 方法,并添加语句 this.componentDidShow && this.componentDidShow()

        • 若有componentDidHide,但没有 componentWillUnmount 方法,则插入 componentWillUnmount 方法,并添加语句 this.componentDidHide && this.componentDidHide()

        • 若没有constructor 方法,则添加如下代码

          constructor() {
            super(...arguments);
            Taro._$app = this;
          }
          
      3. 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;
        
注意点
  1. 上述操作对 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
    
  2. 转换完成后,会发现如下代码:

    let App = class App extends Component {}
    

    声明的 class 被赋值给了变量 App。这其实是通过步骤 1 中的插件 babel-plugin-transform-decorators-legacy 完成的。

转换样式

调用 compileDepStyles,主要处理逻辑在 StyleProcess 中。

  1. 读取样式文件,进行 scss/sass/less 的预处理后,返回 css string

  2. 使用 postcss 插件 pxtransform 进行单位转换,具体计算操作在 createPxReplace

  3. 调用 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"
      }
    })
    
  4. 校验生成的 rn 样式属性的正确性。

  5. 生成 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"
    }
    

相关文章

  • taro-build 之 RN 转换

    taro-build taro-build:命令行工具,将 react 代码转换成 weapp/swan/alip...

  • react-native 2D 转换

    2D 转换 在RN中的写法, RN项目中需要使用这个2D 转换 , 但是不知道RN中是怎么写的, 所以查了查,...

  • RN拆分包

    RN拆分包的本质是什么?RN项目开发完成后,通过RN打包命令可以将JSX的语法通过Node 的babel 模块转换...

  • reactnativ和flutter一句话说明区别

    reactnative是利用javascript编程,rn会将javascript api转换成原生api,开发者...

  • rn优势转

    、用户体验佳 RN通过JavascriptCore解析Javascript模块,转换成原生native组件渲染,相...

  • RN Geolocation 定位有偏差处理

    最近项目用到RN定位,把定位用在百度地图上,发现有400米左右的偏差 原因是RN定位的坐标是GPRS 坐标,要转换...

  • css3(5)

    2D转换之移动 2D转换之旋转 2D转换之缩放 2D转换之斜切

  • codepush3之Android原生引用集成

    codepush热更新codepush2之配置自己的服务器其实原生集成rn在集成codepush与新建rn项目集成...

  • 中文http url ,ios无法识别

    //通过 UTF-8编码转换下,就能识别了。encodeURI是RN自带的方法,直接用就可以了。 cons...

  • reactnative 使用阿里iconfont

    用纯JS把iconfont.cn的图标转换成RN组件,不依赖字体,支持多色彩,支持热更新

网友评论

      本文标题:taro-build 之 RN 转换

      本文链接:https://www.haomeiwen.com/subject/yygkwhtx.html