跳到主要內容
黯羽輕揚每天積累一點點

Taro

免費2018-12-08#Solution#Taro教程#Taro入门#Taro原理#Taro源码分析#Taro与mpvue

一種多端代碼轉換方案,支持微信小程序、Web、ReactNative、百度小程序、支付寶小程序、頭條小程序、快應用等等

一、目標定位

一套遵循 React 語法規範的多端統一開發框架

一種多端代碼轉換方案,這裡的「端」是指微信小程序、Web、ReactNative、百度小程序、支付寶小程序、頭條小程序、快應用等等

具體地,把一份類 React 源碼,通過「編譯」轉換成兼容目標端的形式,即:

             轉換
nerv 業務代碼 ------> xx 小程序業務代碼 +
                    Web 業務代碼 +
                    ReactNative 業務代碼

目的是降低開發成本,提高效率:

讓原本只能運行在一端的項目獲得多端運行的能力,降低開發者的重構成本。

二、思路探索

初衷

用 React 寫微信小程序。

微信小程序原生方式開發起來 太費勁,遂想用 React 開發微信小程序

延伸

在 React 業務代碼轉微信小程序代碼這個最初的需求實現之後,發現依靠同樣的轉換思路可以適配多端,即從 1 對 1 延伸到 1 對 n

P.S. 其中 Nerv 是一種類 React 框架,API 與 React 類似

P.S. Taro 組件庫之所以以微信小程序為標準,也是初衷使然(都做完了不能浪費啊)

思路

想要一份代碼通吃 n 端,無非 2 種思路:

  • 直接從 1 端向 n - 1 端轉換

  • 加一層抽象,從這層抽象轉換到 n

以 Bash 與 Batch(Windows 批處理腳本)為例,如果只寫一份腳本,想既能在*nix 跑,又能在 Windows 跑,第一種思路只需要實現 1 個東西(從 bashn - 1 端轉換):

function bash2batch(bash) {
  // ...
  return equivalentBatch;
}

或者(從 batchn - 1 端轉換):

function batch2bash(batch) {
  // ...
  return equivalentBash;
}

如果能實現 AtoB,一份 A 就可以適配 AB 了,但*「硬」轉通常比較困難*,因此在 Bash 與 Batch 的場景,誕生了第二種思路的實現:

Batsh: A language that compiles to Bash and Windows Batch.

也就是加一層抽象 C,再分別實現 CtoACtoB,從 Batsh 這層抽象轉換到 n 端:

// 1. 定義抽象層 Batsh
const batsh = 'Neither bash nor batch';
// 2. 實現抽象層向 2 端轉換
function batsh2batch(batsh) {
  // ...
  return equivalentBatch;
}
function batsh2bash(batsh) {
  // ...
  return equivalentBash;
}

類似地,Taro 也採用了第二種思路,這層抽象就是 Taro 業務代碼:

P.S. Taro 業務代碼即圖中的 Nerv 代碼,叫 Taro 代碼更準確一些,因為增加了一些 Taro 特有的 API 支持(如 Taro.getEnv()),是 Nerv 的超集

三、核心實現

以微信小程序為例,它由 4 部分組成:

  • 配置(JSON)

  • 模板(WXML)

  • 樣式(WXSS)

  • 邏輯(JS)

配置與樣式沒什麼好說的,難點在於模板的轉換和邏輯的轉換

P.S. ReactNative 樣式轉換另說,也是一個難題,因為 RN 在選擇器、屬性名/值及默認值,甚至 CSS 特性支持程度都存在較大差異

編譯轉換

要把一份代碼 A 轉換成另一份代碼 B,需要做 3 件事情:

  1. 解析代碼 A 生成抽象描述(AST)

  2. 根據一些映射規則操作 AST,生成新的 AST

  3. 根據新的 AST 生成代碼 B

taro-compile

P.S. 關於編譯轉換的更多信息,請查看 再看編譯原理 與 [Babel 快速指南](/articles/babel 快速指南/)

模板的轉換

把 JSX 語法轉換成可以在小程序運行的字符串模板。

輸入 JSX:

render() {
  const { percent } = this.state;

  return (
    <View className='index'>
      <Button className='add_btn' onClick={this.props.add}>+</Button>
      { percent && <MyProgress percent={percent} strokeWidth={6} color='#FF4949' /> }
    </View>
  );
}

@tarojs/transformer-wx 轉換,輸出微信小程序模板:

<block>
  <view class="index">
    <button class="add_btn" bindtap="funPrivatesBrJC">+</button>
    <block wx:if="{{percent}}">
      <my-progress percent="{{percent}}" strokeWidth="{{6}}" color="#FF4949"></my-progress>
    </block>
  </view>
</block>

ViewButton 等都是Taro 內置組件

Taro 以 微信小程序組件庫 為標準,結合 jsx 語法規範,定製了一套自己的組件庫規範

相關 package 如下:

  • @tarojs/components:支持 Web 環境 Nerv 組件庫,通過編譯替換為目標平台的原生標籤/組件

  • @tarojs/taro-components-rn:支持 ReactNative 環境的 React 組件庫(之所以 ReactNative 組件庫獨立出來,可能是因為差異較大,難以通過編譯手段實現轉換)

都會被轉換成目標端的原生組件:

在小程序端,我們可以使用所有的小程序原生組件,而在其他端,我們提供了對應的組件庫實現

但自定義組件 my-progress 在微信小程序中是不存在的,所以並不能如預期地跑起來

勢必需要一種跨端組件定義,為此 Taro 提供了 2 個東西:

前者解決有沒有的問題,應對一般應用場景。後者開放一種自定義的能力,滿足需要定製的場景

邏輯的轉換

類似於組件庫需要做多端適配,各端能力差異也同樣需要適配:

組件庫以及端能力都是依靠不同的端做不同實現來抹平差異

運行時框架負責適配各端能力,以支持跑在上面的 Taro 業務代碼,主要有 3 個作用:

  • 適配組件化方案、配置選項等基礎 API

  • 適配平台能力相關的 API(如網絡請求、支付、拍照等)

  • 提供一些應用級的特性,如事件總線(Taro.EventsTaro.eventCenter)、運行環境相關的 API(Taro.getEnv()Taro.ENV_TYPE)、UI 適配方案(Taro.initPxTransform())等

實現上,@tarojs/taro 是 API 適配的統一入口,編譯時分平台替換

平台適配相關的 package 有 6 個:

P.S. 與組件庫適配方案不同的是,API 乾脆放棄編譯轉換這條路,直接整個替掉

實際上,要想只維護一份業務代碼,那麼 Taro 提供的 API 必定是n 端 API 的並集,例如:

// 各小程序都支持的 API
Taro.setStorage()
// 百度小程序專有 API
Taro.textToAudio()
// 支付寶小程序與微信小程序參數處理上存在差異的 API
Taro.getStorageSync()
// ...

這些 API 都可以直接使用,不用關心當前平台是否支持,因為運行時框架的適配工作的一部分就是抹平平台能力 API 差異,例如:

H5 端就無法調用掃碼、藍牙等端能力

採用微信小程序標準,所以這些 API 在 H5 端運行的時候將什麼也不做。

同時在業務層區分目標環境,保證這些平台相關的代碼僅在預期的目標環境下執行:

  • 編譯時:process.env.TARO_ENV

  • 運行時:Taro.getEnv()

例如:

// 分平台調用 API
if (process.env.TARO_ENV === 'swan') {
  Taro.textToAudio()
}
// 分平台使用不同組件
<View>
  {process.env.TARO_ENV === 'weapp' && <ScrollViewWeapp />}
  {process.env.TARO_ENV === 'h5' && <ScrollViewH5 />}
</View>

P.S. 編譯時靜態的環境區分足夠應對大多數場景了,運行時的環境區分僅 備不時之需

四、結構

從設計上看,Taro 方案分為 3 層:

業務層(類 React 代碼)
---------------------
轉換層(JSX 轉微信小程序)
---------------------
適配層 組件庫(適配 n 端原生組件)
      運行時框架(適配 n 端 API 能力)
---------------------

此外,還有

  • 生態:UI 庫、路由、數據流管理、CSS 預處理等

  • 構建:Web 走 Webpack,ReactNative 走 Expo 的 xdl,其餘的各自走自己的 IDE

  • Lint:對於轉換層不支持的寫法,通過靜態檢查給出一部分警告

五、源碼簡析

對應到具體實現,各部分對應的 package 如下(taro/packages/):

// 轉換
babel-plugin-transform-jsx-to-stylesheet
taro-plugin-babel
taro-plugin-csso
taro-plugin-uglifyjs
taro-transformer-wx

// 適配 - 組件庫
taro-components-rn
taro-components

// 適配 - 運行時框架
taro-alipay
taro-h5
taro-qapp
taro-rn
taro-swan
taro-tt
taro-weapp
taro

// 生態
postcss-plugin-constparse
postcss-pxtransform
postcss-unit-transform
taro-async-await
taro-mobx-common
taro-mobx-h5
taro-mobx-prop-types
taro-mobx-rn
taro-mobx
taro-plugin-less
taro-plugin-sass
taro-plugin-stylus
taro-plugin-typescript
taro-redux-h5
taro-redux-rn
taro-redux
taro-router-rn
taro-router

// 構建
taro-cli
taro-rn-runner
taro-webpack-runner

// Lint
eslint-config-taro
eslint-plugin-taro

// 其它(公共方法)
taro-utils

另外,還有個有意思的東西

// 微信小程序轉 Taro
taroize
// taroize 之後的運行時
taro-with-weapp

反向轉換是另一扇門,就轉換而言,從 1 對 1 延伸到 1 對 n 之後,下一個階段就是 n 到 1 了,即:

// 目標端
A = weapp
B = ReactNative
C = ReactNative
// 抽象層
T = Taro

// 第一階段:1 對 1
T2A()
// 第二階段:1 對 n
T2A(), T2B(), T2C()...
// 第三階段:n 到 1
A2T(), B2T(), C2T()...

等到第三階段完成,就天下大同了(隨便拿個什麼東西都能轉換到 n 端)

P.S. 目前(2018/12/9),A2T()(小程序代碼轉 Taro)已經待發布了,具體見 版本計劃

六、限制

限制方面感受最深的應該是 JSX,畢竟 JSX 的靈活性令人髮指(動態組件、高階組件),同時微信小程序的模板語法又限制極多(即便通過 WXS 這個補丁增強了一部分能力),這就出現了一個不可調和的矛盾,因此:

JSX 的寫法極其靈活多變,我們只能通過窮舉的方式,將常用的、React 官方推薦的寫法作為轉換規則加以支持,而一些比較生僻的,或者是不那麼推薦的寫的寫法則不做支持,轉而以 eslint 插件的方式,提示用戶進行修改

具體地,JSX 限制如下:

  • 不支持 動態組件
  • 不能在包含 JSX 元素的 map 循環中使用 if 表達式
  • 不能使用 Array#map 之外的方法操作 JSX 數組
  • 不能在 JSX 參數中使用匿名函數
  • 不允許在 JSX 參數 (props) 中傳入 JSX 元素
  • 只支持 class 組件
  • 暫不支持在 render() 之外的方法定義 JSX
  • 不能在 JSX 參數中使用對象展開符
  • 不支持無狀態組件(函數式組件)
  • props.children 只能傳遞不能操作
  • ...

對於這些轉換限制,彌補性方案是 Lint 檢查報錯,並提供替代方案

除 JSX 外,還有 2 點比較大的限制:

  • CSS:受限於 ReactNative 的 CSS 支持程度(只能使用 flex 佈局)

  • 標籤:約定 不要使用 HTML 標籤(都用多端適配過的內置組件,如 ViewButton

P.S. 囿於靜態轉換自身的限制,很多轉換是沒辦法實現的

七、應用場景

當業務要求同時在不同的端都要求有所表現的時候,針對不同的端去編寫多套代碼的成本顯然非常高

也就是說,當同一業務在多端有重疊需求時,Taro 之類的多端代碼轉換方案才有意義

另一類場景是 Taro 最初想要解決的微信小程序開發體驗問題,如果用 Taro 來開發微信小程序,一不小心還能適配多端,也是個不錯的選擇

參考資料

評論

暫無評論,快來發表你的看法吧

提交評論