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