導(dǎo)讀
58同城前端通道與京東凹凸實(shí)驗(yàn)室達(dá)成 Taro 項(xiàng)目開源合作,針對(duì) Taro 3 React Native 支持方案,做了較大程度的重構(gòu)。
經(jīng)過較長(zhǎng)時(shí)間的方案討論,現(xiàn)已正式對(duì)外公布 RFC 提案,并征集社區(qū)意見。開源社區(qū)參與者可通過 https://github.com/NervJS/taro-rfcs/pull/8 或點(diǎn)擊閱讀原文參與提案討論,期待您的參與。
提案詳情
概述
在 Taro 中實(shí)現(xiàn)使用?React?開發(fā)?React Native。
動(dòng)機(jī)
Taro 升級(jí)到 3 以后,React Native 無法使用,社區(qū)仍然有使用 Taro 開發(fā) React Native 的需求。
使用案例
在頁(yè)面組件和自定義組件開發(fā)中,與編寫 React 項(xiàng)目遵循一致的規(guī)范。
入口組件生命周期、頁(yè)面組件生命周期及頁(yè)面事件處理函數(shù),支持度與小程序相比會(huì)有差異,參考詳細(xì)設(shè)計(jì)運(yùn)行時(shí)部分。
API、組件支持度與小程序相比有些取舍,參考詳細(xì)設(shè)計(jì) API 及組件部分。
詳細(xì)設(shè)計(jì)
本提案以 Taro 3 定義的標(biāo)準(zhǔn)為基礎(chǔ),完成 React Native 端的升級(jí)改造,同時(shí)在編譯打包方案、API 及組件支持、React Native 項(xiàng)目接入靈活性等方面做了較大重構(gòu)。
設(shè)計(jì)總結(jié)
將編譯打包方案統(tǒng)一為 metro ,更貼合 React Native 生態(tài)體系。
相比 webpack + metro 的方式,可提升編譯速度,整體方案也更為清晰。
相比 webpack 多 entry 模式,可降低包大小。同時(shí)解決多 entry 模式存在的一些問題,如?#7512。
提供更好的 sourcemap 支持,優(yōu)化開發(fā)體驗(yàn)。
與 React Native APP 項(xiàng)目自身的 metro 配置,如分包等,可靈活合并。
運(yùn)行時(shí)模塊,按照 Taro 3 定義的標(biāo)準(zhǔn)進(jìn)行改造。
與小程序及 H5 內(nèi)部的寫法保持基本一致,通過 metro transformer (類似 taro loader) 生成入口及頁(yè)面代碼,通過 taro-runtime-rn 包提供的方法包裝入口組件及頁(yè)面組件。
提升對(duì)頁(yè)面事件處理函數(shù)的支持度。
增加對(duì) Tab Bar 相關(guān) API 的封裝。
提升 API 及組件的支持度。
按社區(qū)調(diào)研結(jié)果優(yōu)先級(jí)及難易程度進(jìn)行支持,仍然以 expo 體系為主。
API 及組件可按需集成,對(duì)于依賴原生的 API 和組件,提供完整的原生集成文檔。
視工作量情況,逐步提升支持度,同時(shí)歡迎社區(qū)貢獻(xiàn)。
提供更靈活的 React Native APP 接入方案。
不再鎖定 React Native 版本,用戶可在項(xiàng)目中自行安裝 >=0.60 版本的 React Native,對(duì)于 >=0.57 && <=0.59 版本的支持將在后續(xù)推出。
除 React Navigation 外不強(qiáng)制依賴其他 Native Modules,用戶可按需引入所需的原生依賴。相應(yīng)原生依賴未安裝時(shí),相關(guān)接口不可用,但不會(huì)阻斷程序運(yùn)行。同時(shí)用戶可根據(jù)自身應(yīng)用情況,對(duì)所需 API 接口或組件進(jìn)行替換。
不需要靈活定制的用戶,仍可以使用我們提供的包含所需原生依賴殼工程項(xiàng)目,快速開始開發(fā)。
整體設(shè)計(jì)圖示
大致流程如下:
? ? ?0. @taro/cli 中通過registerPlatform 注冊(cè) rn 平臺(tái);
yarn dev:rn 獲得編譯配置,轉(zhuǎn)為 babel.config.js 及 metro.config.js 配置;
所有 React Native 不支持的語(yǔ)法及配置,通過編譯配置支持;
通過編譯配置與?@tarojs/taro-rn-transformer 生成 React Native 的入口文件 index.ts;
入口文件引入?@tarojs/taro-runtime-rn 使用createReactNativeApp進(jìn)行包裝;
頁(yè)面文件引入?@tarojs/taro-runtime-rn 使用createPageConfig 進(jìn)行包裝;
啟動(dòng) metro bunlder;
在 React Native Shell 工程中運(yùn)行 react-native run-ios 或 react-native run-android 加載?index.bundle。
本次新增及修改的包列表
包名 (packages下文件夾名)包功能
*taro-cli1. 獲取公用編譯配置及 RN 特殊配置,啟動(dòng) rn-runner。
2. 檢查依賴包是否正常安裝:包括(react-native、component-rn、runtime-rn等)
*taro-component-rn1. 增加對(duì)虛擬列表的支持。
2. 增加對(duì)舊版本不支持的組件的支持,詳見組件改造小節(jié)。
*taro-rn1. 增加對(duì)舊版本不支持的接口的支持,詳見API改造小節(jié)。
taro-rn-runner1. 與用戶自定義的 metro 配置做合并。
2. 生成babel配置。
3. 引入style transformer、taro-rn-transformer 等包生成metro配置。
4. 啟動(dòng)bundler。
5. 支持通用編譯配置,包括 defineConstants、copy、alias、sass、env 等。
6. 支持RN特殊配置,如樣式縮放開關(guān)。
7. 引入 babel plugin
transform-jsx-to-stylesheet以支持 className 寫法。
8. 支持優(yōu)先加載.rn.文件,Android加載 .android.文件,iOS加載 .ios.文件。
9. 編譯平臺(tái)包替換及 alias 配置。
10. 打包壓縮、sourcemap 等能力支持。
taro-rn-transformer1. 攔截入口文件 index.js/index.ts,根據(jù)配置,生成入口文件代碼。
2. 根據(jù) app.config 的 pages 配置,攔截頁(yè)面文件,根據(jù)配置,生成頁(yè)面文件代碼。
3. 功能類似小程序及h5的 taro-loader。
taro-style-transformer1. 支持 sass、less、stylus、postcss文件的引入,轉(zhuǎn)化為 styleObject。
2. 支持 sass 的全局引入配置
3. 引入 postcss-pxtransform 以支持 React Native 單位轉(zhuǎn)化。
taro-asset-transformer1. 支持引入靜態(tài)資源,轉(zhuǎn)化圖片等資源為 base64。
babel-plugin-transform-jsx-to-stylesheet1. jsx 支持 className 屬性,將各種語(yǔ)法轉(zhuǎn)化為 styleObject[‘className’]等。
bable-preset-taro-rn1. 將所需 babel plugin 進(jìn)行合并,統(tǒng)一管理。
taro-runtime-rn1. 提供 app 包裝方法。
2. 提供 page 包裝方法。
3. 支持 pulldownrefresh 等頁(yè)面事件處理函數(shù)支持。
4. 支持頁(yè)面配置,title、navigatebar等。
5. 提供 Taro3 新增的全局對(duì)象 Current 等。
taro-router-rn1. 封裝所有的路由處理。
2. 支持新增的TabBar相關(guān)API。
3. 支持tabbar相關(guān)配置。
4. 支持icon配置。
注:帶*為做修改的包,不帶為本次新增或基本重構(gòu)的包。
編譯打包方案改造
對(duì)于通用配置的支持情況
配置是否支持方案
sourceRoot支持-
outputRoot支持-
designWidth支持-
defineConstants支持使用?babel-plugin-transform-inline-environment-variables?加入到運(yùn)行環(huán)境中。
alias支持使用?babel-plugin-module-resolver?支持。
env支持使用?babel-plugin-transform-inline-environment-variables加入到運(yùn)行環(huán)境中。
包含
process.env.TARO_ENV?值為?rn?。
copy不支持-
plugins不支持-
presets不支持-
terser支持-
csso支持transformer 引入?csso。
sass支持-
webpack loader 配置替代方案
通過 webpackChain 修改配置已不再支持,對(duì)樣式編譯配置的修改使用如下代替配置。
rn: {
sass: {
options: ...,// https://github.com/sass/node-sass#options
additionalData: ...,// {String|Function} 注入到所有 sass 文件中
sourceMap: boolean,// 不做支持
},
less: {},
stylus: {},
postcss: {},
}
平臺(tái)差異化文件引用支持
通過配置 babel plugin,支持編譯 React Native 平臺(tái)時(shí),優(yōu)先使用?*.rn.*,編譯 Android 平臺(tái)優(yōu)先使用?*.android.*,編譯 iOS 平臺(tái)優(yōu)先使用?*.ios.*。
參考 babel-plugin-react-native-platform-specific-extensions 實(shí)現(xiàn)。
編譯平臺(tái)包替換及 alias 配置
將?@tarojs/taro-components 替換為?@tarojs/taro-components-rn,將?@tarojs/taro 替換為?@tarojs/taro-rn。
安裝 babel-plugin-module-resolver,配置 babel.config.js。
{
plugins: [
[
'module-resolver',
{
alias: {
'@tarojs/components':'@tarojs/components-rn',
'@tarojs/taro':'@tarojs/taro-rn',
},
},
]
}
用戶 metro 配置自定義合并
taro-rn-runner 將所需的 metro 配置作為 defaultConfig,項(xiàng)目中的 metro.config.js 作為配置載入。
Metro.loadConfig({}, defaultConfig);
sourcemap 支持
基于 metro 本身的 sourcemap,可自行調(diào)整配置。
TypeScript 支持
metro-react-native-babel-transformer 使用的 metro-react-native-babel-preset 默認(rèn)支持 TypeScript。
樣式語(yǔ)法支持
統(tǒng)一使用 transformer 實(shí)現(xiàn)
className 語(yǔ)法支持
通過操作 visitor 的 JSXOpeningElement,ImportDeclaration,Program.exit,實(shí)現(xiàn) className 的語(yǔ)法轉(zhuǎn)換。
參考 babel-plugin-transform-jsx-stylesheet 包做一些改進(jìn)。
支持包括如下場(chǎng)景:
class string 與 多class string;
對(duì)象?{ active: this.props.isActive };
數(shù)組?['header1 header2', 'header3', { active: this.props.isActive }];
三元運(yùn)算this.props.visible ? 'show' : 'hide';
自定義函數(shù)?getClassName();
多個(gè)樣式文件,對(duì)象進(jìn)行合并;
層疊樣式表預(yù)編譯語(yǔ)言支持(sass less stylus postcss)
通過配置 metro 的?transformer.babelTransformerPath?以支持 css 預(yù)編譯語(yǔ)言,將編譯結(jié)果導(dǎo)出為 styleObject。
參考如下實(shí)現(xiàn):
react-native-sass-transformer
react-native-typed-postcss-transformer
react-native-less-transformer
react-native-stylus-transformer
sass.resource 支持全局
在使用 metro 的 sass transformer 時(shí),將?sass.resource?配置的全局 sass 文件注入到樣式文件頭部。
單位轉(zhuǎn)化
同 2.x 通過 postcss-pxtransform 插件,在使用 metro 的 postcss transformer 時(shí),引入該插件。該功能支持關(guān)閉。
樣式文件中跨平臺(tái)支持
如:
/*? #ifdef? %PLATFORM%? */
樣式代碼
/*? #endif? */
仍然通過?postcss-pxtransform?支持。
全局的 app.css 支持
將 app.jsx 中對(duì)樣式的引用,合并至頁(yè)面的 styleObject 中。
media-query-processor 及 viewport-units 支持
參考 react-native-dynamic-style-processor 實(shí)現(xiàn)。
Taro3標(biāo)準(zhǔn)運(yùn)行時(shí)支持
使用方法及實(shí)現(xiàn)方案,與小程序和h5保持一致。
使用 taro-rn-transformer/app.ts 生成入口文件
metro transformer 中判斷是否為 index.js,是的話,使用 taro-rn-transformer/app 傳入編譯配置,進(jìn)行轉(zhuǎn)換。
transformer 偽代碼如下:
varupstreamTransformer =require("metro-react-native-babel-transformer");
const{ transform } =require("@tarojs/taro-rn-transformer/app");
const{ pages } =require('./config');
module.exports.transform =function({ src, filename, options }){
if(filename ==='index.js') {
returnupstreamTransformer.transform(transform({ src, filename, {pages, ...options}}));
}
returnupstreamTransformer.transform({src, filename, options });
};
生成后入口文件的偽代碼如下:
importAppfrom'./src/App';
import{AppRegistry}from'react-native';
import{createReactNativeApp}from'@tarojs/taro-runtime-rn';
importconfigfrom'./src/App.config';
// 遍歷配置生成的頁(yè)面路由表
importPagesIndexIndexScreenfrom'./src/pages/index/index';
importPagesIndexAboutScreenfrom'./src/pages/index/about';
constrouters = [{
name:'PagesIndexIndex',
component: PagesIndexIndexScreen
}, {
name:'PagesIndexAboutScreen',
component: PagesIndexAboutScreen
}];
constbuildConfig = {};
AppRegistry.registerComponent('appName', () => createReactNativeApp(App, config, routers, buildConfig));
再經(jīng)過 metro-react-native-babel-transformer 進(jìn)行二次轉(zhuǎn)化。
頁(yè)面文件生成 taro-rn-transformer/page.ts
metro transformer 中判斷是否為頁(yè)面文件,是的話,使用 taro-rn-transformer/page 傳入編譯配置,進(jìn)行轉(zhuǎn)換。
生成后頁(yè)面文件的偽代碼如下:
import{createPageConfig}from'@tarojs/taro-runtime-rn';
importconfigfrom'./pagename.config';
// 原有的代碼
importComponetfrom'./pagename';
exportdefaultcreatePageConfig(Component, config);
再經(jīng)過 metro-react-native-babel-transformer 進(jìn)行二次轉(zhuǎn)化。
createReactNativeApp
暴露給 @tarojs/taro-rn-transformer/app 調(diào)用,在 React Native 應(yīng)用入口文件中調(diào)用,創(chuàng)建一個(gè) React Native App 構(gòu)造函數(shù)接受小程序應(yīng)用規(guī)范對(duì)象。
createPageConfig
暴露給 @tarojs/taro-rn-transformer/page 調(diào)用,在 React Native 應(yīng)用頁(yè)面文件中調(diào)用,創(chuàng)建一個(gè) React Native App 構(gòu)造函數(shù)接受小程序頁(yè)面規(guī)范對(duì)象。
Current
暴露給開發(fā)者的 Taro 全局變量,目前有三個(gè)屬性:
Current.app,返回當(dāng)前小程序應(yīng)用實(shí)例,非小程序端返回小程序規(guī)范應(yīng)用實(shí)例,可通過此實(shí)例調(diào)用小程序規(guī)范生命周期。
Current.page,返回當(dāng)前小程序頁(yè)面實(shí)例,非小程序端返回小程序規(guī)范頁(yè)面實(shí)例,可通過此實(shí)例調(diào)用小程序規(guī)范生命周期。
Current.router,返回當(dāng)前小程序路由信息,非小程序端返回小程序規(guī)范路由信息。
針對(duì) React Navigation 做一樣的封裝。
頁(yè)面事件處理函數(shù)支持
函數(shù)
支持方案
onPullDownRefresh基于 scrollView 的 refreshControll。
onReachBottom監(jiān)聽 onMomentumScrollEnd。
onPageScroll基于 scrollView。
onResize基于 Dimensions。
onTabItemTap基于 React Navigation tabPress 事件。
自定義 hooks 支持
useDidShow,useDidHide,usePullDownRefresh等,參考 H5 React 實(shí)現(xiàn)。
路由支持從外部直接帶參數(shù)進(jìn)入指定頁(yè)面
基于 React Navigation Deep linking 進(jìn)行實(shí)現(xiàn)。
API改造
API 改造
組件包 taro-rn fork 自 2.x 版本,增加對(duì)如下API的支持。
API
實(shí)現(xiàn)方案
tabBar相關(guān)API基于 React Navigation 進(jìn)行封裝。
VideoContext基于 expo-av 進(jìn)行封裝。
CameraContext基于 expo-camera 進(jìn)行封裝。
AudioContext基于 expo-av 進(jìn)行封裝。
* Animation基于 Animated 進(jìn)行封裝。
掃碼基于 expo-barcode-scanner 進(jìn)行封裝。
File相關(guān)API基于 expo-file-system 進(jìn)行封裝
* Canvas相關(guān)API基于 react-native-canvas 封裝。
網(wǎng)絡(luò)請(qǐng)求(downloadFile,uploadFile)基于 fetch 及 expo-file-system 進(jìn)行封裝。
Resize 及 Keyboard 事件監(jiān)聽Resize 基于 Dimensions,Keyboard基于
WebSocket基于 React Native WebSocket Support 進(jìn)行封裝。
EventChannel,Events,eventCenter基于 DeviceEventEmitter 進(jìn)行封裝。
RequestTask相關(guān)API-
注:帶*為評(píng)估優(yōu)先級(jí)較低。
組件改造
組件包 taro-component-rn fork 自 2.x 版本,增加對(duì)如下組件的支持。
組件
實(shí)現(xiàn)方案
VirtualList封裝 FlatList。
MovableView 及 MovableArea封裝 Animated 及 PanResponder。
Label封裝 View 當(dāng)點(diǎn)擊時(shí),會(huì)觸發(fā)對(duì)應(yīng)的控件更新。
PickerView 及 PickerViewColumn基于 @ant-design/react-native 封裝。
Navigator封裝對(duì)路由的操作。
Audio基于 expo-av 進(jìn)行封裝。
Video基于 expo-av 進(jìn)行封裝。
Camera基于 expo-camera 進(jìn)行封裝。
*Canvas基于 react-native-canvas 封裝。
注:帶*為評(píng)估優(yōu)先級(jí)較低。
APP接入方式改造
appName 支持配置
因不同 APP 有不一樣的 appName 配置,app.config.ts 增加配置項(xiàng) appName,該參數(shù)用于注冊(cè)入口組件。
import{ appName }from'./app.config';
AppRegistry.registerComponent(appName, () => App);
React Native 多版本支持
一期兼容 >= 0.60 版本,初始化會(huì)默認(rèn)選擇最新的穩(wěn)定版本,用戶根據(jù)需要可自行選擇可兼容的版本進(jìn)行安裝。同時(shí)殼工程或自身APP需要使用相應(yīng)的版本。
不強(qiáng)制依賴所有的 Native Modules
當(dāng) APP 中一些 Native Modules 不存在時(shí),部分 API 不可用,此時(shí)彈出警告,而不是阻塞運(yùn)行。
其他
狀態(tài)管理支持
同 Taro 3 方案,由使用方自行處理。
SubPackages 分包
暫不支持分包,分包的配置的 subPackages 會(huì)被合并進(jìn)入 pages。
殼工程
保留在原工程倉(cāng)庫(kù) taro-native-shell 中,增加新的分支 0.63.0,0.62.0,0.61.0,0.60.0,均對(duì)應(yīng) Taro 3版本。
缺陷
0. 不支持使用 vue 或者 jQuery 框架編寫 React Native。
1. 整體設(shè)計(jì)與小程序和 H5 有些差異。
2. 采用了 metro 編譯打包方案,與 webpack 的方案有較大不同,需及時(shí)同步新版本迭代的更新,以支持一些新寫法。
適配策略
0. 使用 Taro React 開發(fā)的代碼,對(duì)不支持的組件及接口做兼容,安裝有相應(yīng)依賴后,即可運(yùn)行于 React Native 環(huán)境。
1. 含有部分 React Native 的原生依賴,需要增加到各自的原生倉(cāng)庫(kù)里,或使用殼項(xiàng)目進(jìn)行。
2. React Native 實(shí)現(xiàn)的樣式是 css 的子集,無法做到完全兼容,需要由業(yè)務(wù)自行兼容,做跨端項(xiàng)目建議 React Native first。
作者簡(jiǎn)介:
陳志慶:58同城 前端架構(gòu)師,技術(shù)委員會(huì)委員。
推薦閱讀:
開源 | WPaxos:生產(chǎn)級(jí)Paxos算法實(shí)現(xiàn)解析
開源 | Umajs:輕量級(jí) Node.js Web 框架