beeshell:開源的 React Native 元件庫
介紹
beeshell 是一個 React Native 應用的基礎元件庫,基於 0.53.3 版本,提供一整套開箱即用的高質量元件,包含 JavaScript(以下簡稱 JS)元件和複合元件(包含 Native 程式碼),涉及前端(FE)、iOS、Android 三端技術,兼顧通用性和定製化,支援自定義主題,用於開發和服務企業級移動應用。現在已經在 GitHub 上開源,地址:https://github.com/meituan/beeshell
截止目前,beeshell 中的元件已經在美團外賣移動端應用蜜蜂 App 中廣泛應用,而且已經持續了一年多時間,通過了各種業務場景、作業系統、機型的實戰考驗,具備很好的穩定性、安全性和易用性,所以我們將其開源,以期發揮出更大的應用價值。
特性
- UI 樣式的一致性和定製化。
- 通用性。主要使用 JS 來實現,保證跨平臺通用性。
- 定製化。我們在比較細的粒度上對元件進行拆分,通過繼承的方式層層依賴,功能漸進式增強,為在任意層級上的繼承擴充套件、個性化定製提供了可能。
- 原生功能支援。元件庫中的複合元件包含 Native 程式碼,支援圖片選擇、定位等原生功能。
- 功能豐富。不僅僅提供元件,還提供了基礎工具、動畫以及 UI 規範。
- 完善的文件和使用示例。
對比
在開源之前,我們對業界已經開源的元件庫進行了調研,這裡主要對比了 beeshell 與其他元件庫的優勢與劣勢,為大家選擇元件庫提供參考意見。目前,業界開源的元件庫比較多,我們在這裡僅選取 Github Star 數 5000 以上的元件庫,並從元件數量、通用性、定製化、是否包含原生功能、文件完善程度五個維度來進行對比分析
元件庫 | 元件數量 | 通用性 | 定製化 | 是否包含原生功能 | 文件完善程度 |
---|---|---|---|---|---|
react-native-elements | 16 | 強,提供一套風格一致的 UI 控制元件 | 弱,若要定製化可能需要重寫 | 否 | 高 |
NativeBase | 28 | 強,提供一套風格一致的 UI 控制元件 | 中,支援主題變數 | 是 | 高 |
ant-design-mobile | 41 | 強,提供一套風格一致的 UI 控制元件 | 中,部分可以支援定製化需求 | 是 | 低 |
beeshell | 25 | 強,提供一套風格一致的 UI 控制元件 | 強,不僅支援主題變數,還支援使用繼承的方式進行定製化擴充套件 | 是 | 高 |
通過對比可以看出,beeshell 只在元件數量上稍有劣勢,在其他方面都一致或者優於其他專案。因為 beeshell 具備了良好的系統架構,所以豐富元件數量只時間問題,而且我們團隊也已經有了詳細的規劃來完善數量上的不足。
系統設計
系統設計是將一個實際問題轉換成相應解決方案的主動過程,是解決辦法的描述。在通用的軟體工程模型中,需求分析完成後的第一步就是系統設計。一個專案最終的穩定性、易用性在很大程度上也取決於系統設計這一步。
beeshell 元件庫是為了更加快速的搭建移動端應用,為業務開發提供基礎技術支援,大幅提升開發人效。然而,面對不同的業務方、不同的功能需求、不同的 UI 規範與互動方式,如何有效的兼顧所有的需求?這對系統設計提出了更高的要求,下面以抽象層次逐層降低的方式來詳細介紹 beeshell 的系統設計。
框架設計
這些年,React Native 的出現為移動端開發提供了一種新的選擇。React Native 相比原生開發有著更高的開發效率,同時比 HTML5、Hybrid 的效能更好,所以能夠脫穎而出,這也使得越來越多的開發者開始學習和使用 React Native。
beeshell 元件庫基於 React Native,向下通過 React Native 與 iOS、Android 平臺進行系統層面的互動,向上提供開發者友好的統一介面,抹平平臺差異,為使用者開發業務功能提供服務支援。beeshell 扮演了一箇中間者的角色,從而保證了移動端應用基礎功能的穩定性、易用性。
框架設計確定了 beeshell 的系統邊界,指明瞭包含的功能與不包含的功能之間的界限。明確了系統邊界,我們才能繼續進行下面的分析、設計等工作。
設計原則
在進行元件庫的詳細設計之前,我們提出了幾個設計原則:
- JS 實現優先。使用 JS 來實現功能有幾個好處:跨平臺通用性、更高的開發效率、更低的學習和使用成本。
- 繼承與組合靈活運用。繼承和組合都是實現功能複用、程式碼複用的有效的設計技巧,都是設計模式中的基礎結構。繼承允許子類覆蓋重寫父類的實現細節,父類的實現對於子類是可見的,一般稱之為“白盒複用”,這對元件的定製化擴充套件很有效,beeshell 強大的定製化擴充套件的能力就是基於繼承實現;組合是 React 推薦的方式,React 元件具有強大的組合模型,整體類和部分類之間不會去關心各自的實現細節,它們之間的實現細節是不可見的,一般稱之為“黑盒複用”。beeshell 也廣泛使用了組合,基於通用型的元件組合出更加豐富、強大、個性化的功能,在一定程度上提高了 beeshell 的定製化的能力。
- 低耦合、高內聚。一個 beeshell 元件本質上就是一個 React 元件,React 元件之間主要通過 Props 通訊,這屬於資料耦合,相比於內容耦合、控制耦合等其他耦合方式,資料耦合是耦合程度最低的一種,受益於 React 的實現,beeshell 元件低耦合是自然而然的;而要做的高內聚,則對元件的編碼實現方式有一定的要求,我們推行內聚方式中內聚程度比較高的互動內聚和順序內聚。使用單一資料來源,使各個元素操作相同的資料結構,實現互動內聚。使用不可變資料更新的方式,上一個環節的輸出是下一個環節的輸入,像流水線一樣處理邏輯,這便是順序內聚。
方案設計
整體上使用 JS 作為統一入口,多層封裝隱藏實現細節,抹平 JS 與 Native、iOS 平臺與 Android 平臺的差異,開箱即用,降低了使用者的學習和使用成本。區域性上基於 React Native 的技術特點,分成 JS 元件部分和複合元件部分,兩部分推行“鬆耦合”的開發模式,使得 Native 部分擁有替換變更的能力,提升元件庫的靈活性。
複合元件部分可以直接暴露 JS 介面,如果有需要,也可以在 JS 元件部分進行定製化封裝。我們儘量保證 Native 部分功能的原子性、簡潔性,有任何定製化需求都使用 JS 來統一實現,遵循 JS 實現優先的設計原則,保證跨平臺通用的特性。下面分別介紹 JS 元件部分和複合元件部分的設計。
JS 元件部分設計
一個軟體的設計分為三個設計層次:體系結構、程式碼設計和可執行設計。我們使用自上而下的方法,從體系結構開始進行 JS 元件部分的設計。
軟體的體系結構的風格通常有 7 種:管道和過濾器,面向物件,隱式請求,層次化,知識庫,解釋程式和過程控制。
JS 元件部分使用了層次化的體系結構風格,整體分成三層:基礎工具、通用元件、擴充套件元件,從上到下通用性逐漸減弱、定製化逐漸增強,功能漸進式增強,通過分層設計,各層各司其職,兼顧通用性和定製化。
- 基礎工具(common):最基礎的、通用的部分,包含 JS Utils、動畫定義、UI 規範等。
- 通用元件(components):把功能相似的元件進行歸類,整理成一個個系列,每個系列內部使用繼承的方式實現,層層依賴,功能漸進式增強,該部分專注通用性,不考慮定製化需求,保證程式碼的簡潔性。同時,在比較細的粒度對元件進行拆分,提供了良好的可擴充套件性。
- 擴充套件元件(modules):是對通用元件的繼承擴充套件、組合應用,該部分專注定製化,在最大程度上滿足業務上的需求,通用性較低。
我們擴充套件元件部分會提供大量的定製化元件,如果仍然不能滿足需求,使用者就可以借鑑擴充套件元件的實現,根據自己業務需求,在某一繼承層級上繼承通用元件,自行進行定製化擴充套件,這點充分體現了 beeshell 定製化的能力。
複合元件部分設計
既然是 React Native 元件庫當然少不了 Native 部分,複合元件包含 Native 的功能。beeshell 元件庫已經完成了 Native 部分的整合方案與規範,有良好的開發與使用體驗,可以不斷的整合原生功能。
複合元件部分通過 JS 封裝介面,保證了跨平臺。Native 部分主要分成 Native Bridge 和純 Native 兩大部分,Bridge 是針對 React Native 的封裝,必須在元件庫中實現;而純 Native 部分則可以通過 Pods/Gradle 依賴三方實現,有效的吸收利用原生開發的技術積累。
元件庫實現
跨平臺通用性保障
React Native 提供了一些內建元件,我們能使用 JS 來實現功能都是基於這些內建元件,這些內建的元件一些是跨平臺通用的元件,如:View、Text、TextInput;而另一些是兩個平臺分別實現的,如 DatePickerIOS 和 DatePickerAndroid、AlertIOS 和 ToastAndroid。跨平臺元件當然沒有什麼問題,我們可以專注業務功能的開發,問題是這些非跨平臺的元件,給我們的業務功能開發帶來極大困擾,下面舉例說明。
iOS 平臺的 DatePickerIOS 元件:
Android 平臺的 DatePickerAndroid 元件:
不僅功能互動完全不同,而且類名、呼叫方式各異,這不僅滿足不了業務需求,而且也有很高的學習和使用成本。這樣類似的元件還有很多,如何抹平平臺的差異,實現跨平臺?我們提出的方案是優先使用 JS 來實現功能,這也是我們元件庫的設計原則。
針對上面的問題我們開發了基於 ScrollView 的 Datepicker 元件,統一類名與呼叫方式,保證了跨平臺通用性。
iOS 平臺的 Datepicker 元件:
Android 平臺的 Datepicker 元件:
Datepicker 是使用 JS 完全實現了一個完整功能,但是有的情況不需要實現完整的功能,我們可以通過 React Native 提供的 Platform
來進行區域性的跨平臺處理,例如 TextInput 元件。
iOS 平臺的 TextInput 元件:
Android 平臺的 TextInput 元件:
我們可以看到,在 Andriod 平臺並沒有清空圖示,為了抹平平臺的差異,提供更好的通用性,我們開發了 Input 元件,對 TextInput 進行封裝與優化,利用 Platform
定位 Android 平臺提供清空功能,
Input 元件在 Android 平臺的效果:
總之,beeshell 對跨平臺通用性做了進一步的優化,遵循 JS 實現優先的原則,配合 Platform
平臺定位 API 為元件的易用性、通用性提供了更好的保障。
定製化支援
隨著移動網際網路的快速發展,各類移動端產品湧現並且不斷髮展,這也讓軟體知識不斷被普及,業務方對產品功能的定位逐漸從廠商主導轉變為使用者主導。產品功能更加精準,個性化、細化、深化是必然趨勢,通過定製化服務來滿足產品發展的要求也應運而生。不同行業、不同型別的產品,功能、特點各不相同,用某一種既定的軟體產品來滿足不同型別的需求,其適用性可想而知。定製化有良好的技術架構和技術優勢,可定製、可擴充套件、可整合、跨平臺,在個性化需求的處理方面,有著很好的優勢,所以我們需要定製化。
綜上所述,beeshell 把定製化作為核心特性,力求滿足不同產品的定製化需求,下文將從元件的樣式定製化和功能定製化兩方面來進行闡述。
樣式定製化
beeshell 的設計規範支援一定程度的樣式定製,以滿足業務和品牌上多樣化的視覺需求,包括但不限於品牌色、圓角、邊框等的視覺定製。
在元件庫設計之初,就已經統一好了 UI 規範。我們根據 UI 規範,統一定義樣式變數並放置在基礎工具層中,即 beeshell/common/styles/varibles.js
檔案中,在 React Native 應用中,樣式變數其實就是普通的 JS 變數,可以很方便的進行復用與重寫操作。React Native 提供了 StyleSheet
通過建立一個樣式表,使用 ID 來引用樣式,減少頻繁建立新的樣式物件,在元件庫的樣式變數應用中靈活使用 StyleSheet.create
和 StyleSheet.flatten
來獲取樣式 ID 和樣式物件。
在每個組的實現中,會事先引入基礎工具層中的樣式變數,使用統一的變數物件而不是在元件中自行定義,這樣就保證了 UI 樣式的一致性。同時,beeshell 提供了重置樣式變數的 API,可以實現一鍵換膚。我們推薦 beeshell 的使用者在開發移動應用時,事先定義好樣式變數。一方面使用自己的樣式變數重置 beeshell 的樣式變數;另一方面在業務功能開發時,使用自己定義好的樣式變數,從而保證整體 UI 的一致性。
功能定製化
樣式定製化可以從巨集觀和整體的角度來實現,而功能的定製化則需要具體問題具體分析,從微觀和區域性的角度來分析和實現。下文將以 Modal 系列的實現為例,來詳細介紹功能定製化。
在移動端的彈窗互動,與 PC 端相比一般會比較簡單,我們把模態框、下拉選單、資訊提示等互動類似的元件統一歸類為 Modal 系列,使用繼承的方式實現。有人可能會問為什麼使用繼承而不用使用組合?前文已經講過,組合的主要目的是程式碼複用,而繼承的主要目的是擴充套件。考慮到彈窗互動有很多定製化的可能性,為了滿足更好的擴充套件性,我們選擇了繼承。
首先我們看下幾個元件的實現效果圖,對 Modal 系列先有一個直觀的認識。
Modal 元件:
提供了遮罩、彈出容器以及淡入淡出(Fade)動畫效果,彈出內容部分完全由使用者自定義。這個元件通用性極強,沒有任何定製化的功能。這裡需要說明下,動畫部分獨立實現,提供了 FadeAnimated 和 SlideAnimated 兩個子類,使用了策略模式與 Modal 系列整合,Modal 元件預設整合 FadeAnimated。
ConfirmModal 元件:
繼承 Modal 元件,對彈出內容做了一定程度的定製化擴充套件,支援標題、確認按鈕、取消按鈕以及自定義 body 部分的功能,通用性減弱,定製化增強。
SlideModal 元件:
繼承 Modal 元件,對動畫、彈出容器做了重寫,在初始化時例項化 SlideAnimated 型別物件,完成上拉、下拉動畫,同時支援了自定義彈出位置的功能。
PageModal 元件:
繼承 SlideModal 元件,對彈出內容做了定製化擴充套件,支援標題、確認按鈕、取消按鈕以及自定義 body 功能,通用性減弱,定製化增強。
CheckboxModal 元件:
CheckboxModal 元件由 PageModal 和 Checkbox 兩個元件使用組合的方式實現,基於通用型元件組合出了更加強大功能,遵循繼承與組合靈活運用的設計原則。
通過以上部分,我們已經對 Modal 系列已經有了直觀的認識,然後我們來看下 Modal 系列的類圖以及分層:
動畫部分在基礎工具(common)中實現;在通用元件(components)中 Modal 元件聚合 FadeAnimated 動畫,同時因為 SlideModal、ConfirmModal 比較通用,也在該部分實現;CheckboxModal 則定製化比較強,歸類到擴充套件元件(modules)中。通過這種方式的分層,三層各司其職,使得元件庫的層次結構更加清晰,不僅實現了定製化,還保證了通用部分的簡潔性和可維護性。
複雜 Case 處理
相互遞迴處理非同步渲染
React Native 應用的 JS 執行緒和 UI 執行緒是兩個執行緒,與瀏覽器中共用一個執行緒的實現不同,所以我們可以看到 React Native 提供的操作 UI 元素的 API,都是通過回撥函式的方式進行呼叫。
受益於 React,我們一般不需要直接操作 UI 元素,但是有的元件確實需要複雜的 UI 操作,例如完全由 JS 實現的 Scrollerpicker 元件:
我們需要精確的計算容器以及每一項元素的高度,才能正確得到當前選中的項在資料模型(陣列)中的索引。現在面臨的問題是:在元件渲染完成後的生命週期 componentDidMount
並不能拿到正確容器的高度為,而使用 setTimeout
也會有延遲時長設定為多少的問題。我們選擇使用遞迴來解決,一次 setTimeout
不行就執行多次。
這裡使用了互動遞迴,反覆執行,直到得到有效的元素尺寸。
UI 尺寸容錯機制
React Native 為使用者提供了 style 屬性來控制元素的樣式,我們可以手動設定相關 UI 元素的尺寸。但是,在一些 Android 機器上,我們設定的元素尺寸與 measure
方法獲取的尺寸資訊不一致,經過大量 Android 機器的實際的測試,我們得到的結論是:有零點幾畫素的誤差。
我們把通過 measure
方法得到尺寸資訊進行向上與向下取整,得到一個閾值範圍,手動設定的尺寸資訊只要在這個閾值範圍內,就認為是有效尺寸,這種容錯機制有效的相容了極端情況,提高了元件的穩定性。
精細化佈局控制
在使用 Form 元件時,最常見的需求就是校驗功能,通常元件庫的 Form 元件都會內建校驗功能。然而,因為校驗方式有同步與非同步兩種,校驗結果展示的樣式、位置五花八門,這就導致了校驗功能的複雜度變得很高。
絕對定位:
Static 定位:
自定義位置
如何有效的兼顧不同的需求?我們提出了校驗獨立實現的方式,在使用 Form 元件的父元件中,使用 CVD 來定義、配置校驗規則,校驗結果輸出到統一的資料結構(單一資料來源),基於這個資料結構,我們就能在任意時機、任意位置、使用任意樣式來展示校驗資訊。
下面我們先介紹下 CVD:
CVD 是一個針對複雜表單錄入場景的分層解決方案,輕量級、跨平臺、易擴充套件,內建在 beeshell 元件庫中,可以直接使用。
CVD 把表單某個控制元件的錄入的流程分成三層:
- Connector 聯結器,把使用者輸入的資訊轉化成所需的資料格式。
- Validator 校驗器,對格式化的資料進行校驗。
- Dependency 依賴處理器,處理當前控制元件與其他控制元件的依賴關係。
每一層都對單一資料來源 Store 進行不可變資料更新,符合互動內聚和順序內聚,內聚程度高。
每一層使用函式式組合的方式,定義 key(表單控制元件的唯一標誌)與 key 對應的回撥函式,避免了批量 if else
,可以有效降低程式的圓環複雜度。
下面以 Input 元件錄入姓名為例,來具體說明,程式碼如下:
在 onChange
中獲取使用者輸入,呼叫 cvd.flow
然後就可以通過 cvd.getStore
獲取到結果:
通過校驗功能獨立實現,把校驗資訊輸出到 Store 中,在需要的時候從 Store 中獲取校驗資訊,可以更加精細化的控制元素的樣式、位置與佈局,相容各種定製化需求。很多時候,只有我們想不到,沒有做不到。
測試
程式碼的終極目標有兩個,第一個是實現需求,第二個是提高程式碼質量和可維護性。測試是為了提高程式碼質量和可維護性,是實現程式碼的第二個目標的一種方法。
單元測試
單元測試(Unit Testing),是指對軟體中的最小可測試單元進行檢查和驗證。在結構化程式設計的時代,單元測試中單元指的就是函式。beeshell 元件庫全面使用單元測試,由元件的開發者完成。研究成果表明,無論什麼時候作出修改都需要進行完整的迴歸測試,對於提供基礎功能的元件來說更是如此,在生命週期中儘早地對軟體產品進行測試將使效率和質量都得到最好的保證。Bug 發現的越晚,修改它所需的成本就越高,單元測試是一個在早期抓住 Bug 的機會。
單元測試的優點有以下幾點:
- 是一種驗證行為。程式的每一項功能是測試來驗證正確性,為後期的增加功能、程式碼重構提供了保障。
- 是一種設計行為。單元測試使得我們從呼叫者的角度觀察、思考,迫使開發者把程式設計成易於呼叫和可測試的,在一定程度上降低耦合性。
- 是一種編寫文件的行為。是展示函式、類使用的最佳文件。
beeshell 元件庫使用 Jest 做為單元測試的工具,自帶斷言、測試覆蓋率工具,實現開箱即用。
測試用例設計
測試用例的核心是輸入資料,我們會選擇具有代表性的資料作為輸入資料,主要有三種:正常輸入,邊界輸入,非法輸入,下面以元件庫中提供的 isLeapYear
工具函式來舉例說明,程式碼如下:
Jest 使用 test
函式來描述一個測試用例,其中的 toBe
邊是一句斷言。
函式使用了外部資料,正常輸入肯定會有,這裡的 2000
和 '2000'
都是正常輸入;邊界輸入和非法輸入並不是所有的函式都有,這裡為了說明使用了有這兩種輸入的例子,邊界輸入是有效輸入的極限值,這裡 0
和 Infinity
是邊界輸入;非法輸入是正常取值範圍以外的資料, 'xx'
和 false
則是非法輸入。一般情況下,考慮以上三種輸入可以找出函式的基本功能點,單元測試與程式碼編寫是“一體兩面”的關係,編碼時對上述三種輸入都是應該考慮的,否則程式碼的健壯性就會出現問題。
上文所說的測試是針對程式的功能來設計的,就是所謂的“黑盒測試”。單元測試還需要從另一個角度來設計測試資料,即針對程式的邏輯結構來設計測試用例,就是所謂的“白盒測試”。
還是以 isLeapYear
函式來進行說明,其程式碼如下:
這裡有一個 if else
語句,如果我們只提供一個 2000
的輸入,只會測試到 if
語句,而不會測試 else
語句。雖然,在黑盒測試足夠充分的情況下,白盒測試沒有必要,可惜“足夠充分”只是一種理想狀態,難於衡量測試的完整性是黑盒測試的主要缺陷。而白盒測試恰恰具有易於衡量測試完整性的優點,兩者之間具有極好的互補性,例如:完成功能測試後統計語句覆蓋率,如果語句覆蓋未完成,很可能是未覆蓋的語句所對應的功能點未測試。
白盒測試也是比較常見的需求,Jest 內建了測試覆蓋率工具,可以直接在命令中新增 --coverage
引數便可以輸出單元測試覆蓋率的報告,結果如下:
可以看到程式碼的每一行都覆蓋到了 Coverage 為 100%,在很大程度上保證了功能的穩定性。
UI 自動化測試
想要確保元件庫的 UI 不會意外被更改,快照測試(Snapshot Testing)是非常有用的工具。一個典型的移動 App 快照測試案例過程是,先渲染 UI 元件,然後截圖,最後和獨立於測試儲存的參考影象進行比較。使用 Jest 進行在快照測試,在 beeshell 中第一次對某個元件進行測試時,會在測試目錄下建立一個 snapshots 資料夾,並將快照結果存放在該資料夾中。快照結果檔案以 <元件名>.js.snap 命名,其內容為某個狀態下的 UI 元件樹。
下面以 Button 元件快照測試為例來說明:
執行命令後得到快照結果:
靜態分析
經常與單元測試聯絡起來的開發活動還有靜態分析(Static analysis)。靜態分析就是對軟體的原始碼進行研讀,查詢錯誤或收集一些度量資料,並不需要對程式碼進行編譯和執行。
靜態分析效果較好而且快速,可以發現 30%~70% 的程式碼問題,可以在幾分鐘內檢查一遍,成本低、收益高。beeshell 使用 SonarQube 進行靜態程式碼檢查。
SonarQube 是一個開源的程式碼質量管理系統,支援 25+ 種語言,可以通過使用外掛機制與 Eclipse、VSCode 等工具整合,實現對程式碼的質量的全面自動化分析和管理。
SonarQube 通過對 Reliability(可靠性)、Security(安全性)、Maintainability(可維護性)、Coverage(測試覆蓋率)、Duplications(重複)幾個維度,對程式碼進行全方位的分析,通過設定 Quality Gates 保證程式碼質量。
beeshell 元件庫的分析結果概況如圖:
可靠性達到 A 級別,是最高等級,表示無 Bug:
安全性達到 A 級別,是最高等級,表示無漏洞:
測試覆蓋率平均達到 70% 以上
開發與使用一致性
beeshell 元件庫使用 npm 包的形式下載使用,下載成功後會放置在專案根目錄的 node_modules 目錄,然後在專案中通過引入模組的方式,引入 beeshell 的元件來使用。
那我們如何開發元件庫?如何保證元件庫的開發與使用的體驗一致性?
首先,我們需要一個 demo 專案,這個專案是 beeshell 元件庫的開發環境,是一個 React Native 應用。然後,我們把 beeshell 做為 demo 專案的依賴,在 demo 專案中下載安裝。
現在,我們的問題就變成了 node_modules 目錄中的 beeshell 如何和本地的 beeshell 原始碼進行同步。
npm link
我們知道可以使用 npm link 來開發 npm 包,原理如下:
本質是就是使用 Symbol link,但是我們建立好軟連結後,執行打包命令卻報錯了,錯誤資訊為 Expected path '/xxx/xxx/index.js' to be relative to one of project roots
我們前端開發通常會用 Webpack 做為打包工具,而 React Native 應用使用的是 Metro,我們需要分析 Metro 來定位問題。
Webpack vs Metro
經過 Metro 的原始碼分析,我們發現 Metro 的打包方案與 Webpack 有較大差異,Webpack 是根據入口檔案,即配置中的 entry 屬性,遞迴解析依賴,構建依賴關係圖而 Metro 是爬取特定路徑下的所有檔案來構建依賴關係圖。
分析發現 Metro 的特定路徑預設是執行打包命裡的路徑,以及 node_modules 下第一層目錄,這樣我們就定位到了問題的所在:
Metro 在爬取檔案的時候,通過軟連結找到了全域性的 beeshell 但是並沒有繼續判斷全域性的 beeshell 是否有軟連結,所以無法爬取 beeshell 原始碼部分。
直接使用軟連結
通過 ln -s 命令,直接建立 demo 專案 node_modules 下 beeshell 包 與 beeshell 原始碼的軟連結:
這種方式同時支援 Native 部分 iOS、Android 的原始碼開發,注意 Android 部分的需要在 setting.gradle 中呼叫 getCanonicalPath 方法獲取建立軟連結後的路徑。
通過試驗、發現問題、分析原始碼、定位問題、解決問題、方案完善這幾個步驟,完整的實現了 beeshell 元件庫的開發與使用的體驗一致性,同時提升了元件庫的開發效率。
未來展望
我們的目標是把 beeshell 建設成為一個大而全的元件庫,不僅會不斷豐富 JS 元件,而且會不斷加強複合元件去支援更多的底層功能。因為我們支援全部引入和按需引入兩種方式,使用者不需要擔心會引入過多無用元件而使得包體積過大,影響開發和使用效率。
beeshell 目前提供了 20+ 元件以及基礎工具,基於良好的架構設計、開發體驗,為我們不斷地豐富元件庫提供了良好的基礎。同時在開發 React Native 應用的幾年時間中,我們已經積累了 50+ 基礎以及業務元件,我們後續會把積累的元件進行梳理與調整,全部遷移到 beeshell 中。因為我們的元件主要來源於我們的業務需求,但是業務場景有限,可能會使得 beeshell 的發展受到限制,所以我們將其開源。希望藉助社群的力量不斷豐富元件庫的功能,盡最大努力覆蓋到移動應用方方面面的功能,歡迎大家獻計獻策,多多支援。
我們為元件庫發展規劃了三個階段:
- 第一階段,即我們現在所處的階段,開源 20+ 元件,主要提供基礎功能。
- 第二階段,對我們在開發 React Native 應用幾年時間積累的元件進行整理,開源 50+ 元件。
- 第三階段,調研移動端 App 常用的功能,分析與整理,然後在 beeshell 中實現,開源 100+ 元件。