Shopee在React?Native?架構(gòu)方面的探索及發(fā)展歷程
1. 背景
React Native(下文簡稱 RN)是混合應(yīng)用領(lǐng)域流行的跨端開發(fā)框架。RN 非常適合靈活多變的電商領(lǐng)域業(yè)務(wù),由于 RN 是基于客戶端渲染的技術(shù),所以相較于 H5 頁面,它在用戶體驗(yàn)方面有一定優(yōu)勢(shì)。
伴隨著 Shopee 業(yè)務(wù)的飛速發(fā)展,我們 App 中的 RN 代碼量增長得非常快,出現(xiàn)了構(gòu)建產(chǎn)物體積過大、部署時(shí)間太長、不同團(tuán)隊(duì)依賴沖突等問題。為了應(yīng)對(duì)這些痛點(diǎn),我們探索了去中心化的 RN 架構(gòu),并結(jié)合該模型自研了系統(tǒng)(Code Push Platform,簡稱 CPP)和客戶端 SDK,覆蓋了多團(tuán)隊(duì)的開發(fā)、構(gòu)建、發(fā)布、運(yùn)行等一系列 RN 研發(fā)周期。經(jīng)過近三年的迭代,現(xiàn)已接入多款公司級(jí)核心 App。
Shopee 商家服務(wù)前端團(tuán)隊(duì)打造了多款商家端應(yīng)用,大部分用戶是商家服務(wù)人員,他們對(duì)業(yè)務(wù)系統(tǒng)高可用和問題及時(shí)反饋有著很高的要求,從而也推動(dòng)我們對(duì) React Native 的架構(gòu)有了更高的要求。
本文會(huì)從發(fā)展歷史、架構(gòu)模型、系統(tǒng)設(shè)計(jì)、遷移方案四個(gè)方向逐一介紹我們?nèi)绾我徊讲降貪M足多團(tuán)隊(duì)在復(fù)雜業(yè)務(wù)中的開發(fā)需求。
2. 發(fā)展歷程
隨著業(yè)務(wù)高速發(fā)展,我們的 RN bundle 個(gè)數(shù)飛速增加,App 個(gè)數(shù)也達(dá)到近十個(gè)。整個(gè) RN 項(xiàng)目在開發(fā)模型、部署模型和架構(gòu)模型三個(gè)維度都發(fā)生了變化,從單團(tuán)隊(duì)發(fā)展成多團(tuán)隊(duì),從一個(gè) bundle 發(fā)展成多個(gè) bundle,從中心化架構(gòu)發(fā)展成為去中心化,最終發(fā)展成為每個(gè)團(tuán)隊(duì)的業(yè)務(wù)代碼可以獨(dú)立地開發(fā)、部署、運(yùn)行。
整個(gè)發(fā)展歷史分為 4 個(gè)階段,分別是單 bundle 集中開發(fā)模式、單 bundle 多業(yè)務(wù)組開發(fā)模式、多 bundle 中心化發(fā)布模式、多 bundle 去中心化發(fā)布模式。
2.1 第一階段:單 bundle 集中開發(fā)模式
最初的 RN 整體技術(shù)架構(gòu)相對(duì)簡單。由于當(dāng)時(shí)業(yè)務(wù)形態(tài)不算復(fù)雜,為了滿足獨(dú)立團(tuán)隊(duì)在同一個(gè)代碼倉庫當(dāng)中的開發(fā)流程,整個(gè)發(fā)布流程是基于 CDN 的更新發(fā)布,并且使用配置文件記錄 RN bundle 文件的版本以及下載地址,以此進(jìn)行資源管理。整個(gè)發(fā)布的產(chǎn)物有兩個(gè),一個(gè)是 RN 資源包,一個(gè)是用于資源版本管理的 JSON 配置文件。
每次 RN 資源在完成構(gòu)建后,這兩種構(gòu)建產(chǎn)物會(huì)被放置在靜態(tài)資源目錄下。App 在特定的時(shí)間節(jié)點(diǎn)(例如 App 重啟等)會(huì)自動(dòng)拉取配置文件檢查資源更新狀態(tài),然后再從 CDN 拉取 RN 靜態(tài)資源。在下一次打開頁面的時(shí)候,App 會(huì)加載最新的頁面內(nèi)容。
隨著業(yè)務(wù)發(fā)展,越來越多業(yè)務(wù)團(tuán)隊(duì)期望使用 RN 技術(shù)棧開發(fā)業(yè)務(wù),這種情況讓已有架構(gòu)發(fā)生改變,我們自然地產(chǎn)生了“多個(gè)業(yè)務(wù)組多個(gè)代碼倉庫”的想法。
2.2 第二階段:單 bundle 多業(yè)務(wù)組開發(fā)模式
針對(duì)上述問題,多業(yè)務(wù)組的研發(fā)解決方案是 host-plugin 這種模式。
host 用于管理公共依賴和通用邏輯,它將 React、React Native、Shopee RN SDK 等通過一個(gè)獨(dú)立的倉庫管理起來,保證了特殊 RN 依賴的“singleton”(單例模式)條件,避免了部分客戶端組件的重疊依賴,而這種重疊依賴是 RN 官方不允許的。
一個(gè) host 對(duì)應(yīng)著多個(gè) plugin 倉庫,業(yè)務(wù)代碼倉庫則是被看作為一個(gè)插件(plugin),以插件的形式接入主應(yīng)用當(dāng)中。業(yè)務(wù)團(tuán)隊(duì)可以按自己的編碼規(guī)范來管理這個(gè)倉庫。每個(gè)插件倉庫會(huì)被視為 host 項(xiàng)目的 npm 依賴,它的構(gòu)建是一個(gè)集中發(fā)布的流程。所有代碼都會(huì)集成在 host 項(xiàng)目當(dāng)中執(zhí)行構(gòu)建腳本。這種模式滿足超級(jí) App 的要求。
與此同時(shí),host-plugin 的模式也帶來了一個(gè)“難題”,業(yè)務(wù)發(fā)展使得 RN 產(chǎn)物體積逐漸變大,過大的產(chǎn)物會(huì)影響客戶端的解壓效率和 RN 容器加載 JS 時(shí)長。
2.3 第三階段:多 bundle 中心化架構(gòu)模式
針對(duì) RN 產(chǎn)物體積過大的問題,我們利用構(gòu)建工具將打包產(chǎn)物細(xì)分成多個(gè) bundle,這一優(yōu)化是非常有必要的,我們稱它為“分包”。host 項(xiàng)目對(duì)應(yīng)的是公共包,plugin 項(xiàng)目對(duì)應(yīng)的是業(yè)務(wù)包。
整個(gè)構(gòu)建發(fā)生在 host 項(xiàng)目,項(xiàng)目的模式還是“集中構(gòu)建”和“集中發(fā)布”。多 bundle 產(chǎn)物將會(huì)發(fā)布到系統(tǒng)當(dāng)中,客戶端將拉取熱更新的內(nèi)容??蛻舳藭?huì)按需加載對(duì)應(yīng)的 bundle,RN 容器單次加載消耗的資源大大減少,解決了效率問題。
但是它的缺點(diǎn)也很明顯。隨著業(yè)務(wù)團(tuán)隊(duì)的變大和業(yè)務(wù)內(nèi)容的擴(kuò)張,多 bundle 中心化發(fā)布模式同樣也具備四個(gè)弊端:
針對(duì) RN 的運(yùn)行時(shí),即使分包技術(shù)使得產(chǎn)物分離,但是它們還是運(yùn)行在同一個(gè) JSContext 當(dāng)中,這種情況可能會(huì)導(dǎo)致依賴沖突和環(huán)境變量污染;在開發(fā)調(diào)試的過程中,項(xiàng)目重依賴于 host 項(xiàng)目,每次存在著代碼變更,需要重新加載很多內(nèi)容,讓開發(fā)調(diào)試不太友好;在項(xiàng)目構(gòu)建的過程中,打包速度受到 plugin 個(gè)數(shù)的影響,特大型應(yīng)用甚至需要 50 分鐘執(zhí)行一次構(gòu)建,過長的構(gòu)建耗時(shí)嚴(yán)重影響了發(fā)布效率;在部署發(fā)布的過程中,host 項(xiàng)目維護(hù)者負(fù)責(zé)整個(gè) App,每個(gè)業(yè)務(wù)組不能獨(dú)立發(fā)布,發(fā)布時(shí)間會(huì)綁定在一起。當(dāng)出現(xiàn) live issue 的情況,開發(fā)者需要花費(fèi)大量的溝通成本,且只能整體回滾。 2.4 第四階段:多 bundle 去中心化架構(gòu)模式
去中心化 React Native 架構(gòu)模式與網(wǎng)頁的“微前端”或者客戶端的“微應(yīng)用”的概念類似,滿足了多業(yè)務(wù)團(tuán)隊(duì)獨(dú)立開發(fā)部署,能夠在同一個(gè) App 各模塊獨(dú)立運(yùn)行。它涵蓋了開發(fā)、構(gòu)建、發(fā)布、運(yùn)行等多個(gè)方面。該模型解決了上面所說的四個(gè)弊端,并針對(duì)整個(gè)研發(fā)體系有了全面的升級(jí),優(yōu)點(diǎn)有:RN 運(yùn)行時(shí)的互不干擾,開發(fā)調(diào)試的高效,構(gòu)建發(fā)布的獨(dú)立性。
下文會(huì)重點(diǎn)介紹項(xiàng)目的去中心化 RN 架構(gòu)和系統(tǒng)設(shè)計(jì),以及我們是怎樣做到靈活性和穩(wěn)定性的平衡的。
3. 去中心的 RN 架構(gòu)模型
簡單來說,去中心化的 RN 發(fā)布模型涉及到四個(gè)部分:獨(dú)立的 JS 運(yùn)行時(shí);獨(dú)立的開發(fā)流程;獨(dú)立的構(gòu)建流程;獨(dú)立的發(fā)布流程。在這四個(gè)關(guān)鍵環(huán)節(jié)的幫助下,每個(gè)團(tuán)隊(duì)按自己的節(jié)奏掌控 RN 的研發(fā)流程。
3.1 獨(dú)立 JS 運(yùn)行時(shí)
獨(dú)立運(yùn)行時(shí)(多 JSContext,執(zhí)行上下文環(huán)境)的出現(xiàn)是去中心化架構(gòu)的最大特色。獨(dú)立運(yùn)行時(shí)是對(duì)獨(dú)立發(fā)布的完美保證,將 RN 運(yùn)行代碼按照 plugin 維度進(jìn)行隔離,它可以有效避免不同業(yè)務(wù)之間的變量沖突以及依賴沖突問題,即“plugin A”的發(fā)布絕對(duì)不會(huì)影響到“plugin B”。
它的設(shè)計(jì)主要包含以下三點(diǎn):
提前創(chuàng)建 JSContext 且預(yù)加載公共包;進(jìn)入 plugin 的頁面,SDK 會(huì)查看對(duì)應(yīng)的 JSContext 是否已被實(shí)例化。如果已經(jīng)被實(shí)例化,就直接使用,否則從 JSContext Pool 選取一個(gè)獨(dú)立的上下文,加載執(zhí)行業(yè)務(wù)包,各個(gè) plugin 之間運(yùn)行時(shí)是隔離的;退出業(yè)務(wù)頁面時(shí),該 JSContext 不會(huì)立即銷毀,而是放入一個(gè)緩存池,使得重復(fù)進(jìn)入該業(yè)務(wù)可以獲得極致體驗(yàn)。
裝置 JSContext 的容器可以是線程或者進(jìn)程。為了避免它頻繁創(chuàng)建和回收,我們要維護(hù)緩存池且盡可能地復(fù)用現(xiàn)有的 JSContext。
這里我們采用 Least Frequently Recently Used(簡稱 LFRU)的策略。當(dāng)剛退出的應(yīng)用被重新打開,該 JSContext 會(huì)被重新啟用。這樣,我們能夠節(jié)省 85% 的首屏渲染時(shí)長。緩存?zhèn)€數(shù)管理是可配置的,業(yè)務(wù)方可以根據(jù)應(yīng)用的規(guī)模作為合理的預(yù)估。當(dāng)該 RN 頁面還在使用中,即使超出預(yù)估數(shù),該上下文也不會(huì)立即被回收,該設(shè)計(jì)有效地保證頁面的可用性。
3.2 開發(fā)流程
上文提及 RN 項(xiàng)目的調(diào)試效率問題,它會(huì)隨著業(yè)務(wù)代碼的體量增多,代碼調(diào)試效能也會(huì)隨之下降。每個(gè)開發(fā)者的效率問題直接影響到大家的“幸福感”。相比之下,RN 去中心化發(fā)布則是針對(duì)開發(fā)流程做了特定的優(yōu)化。
隨著獨(dú)立運(yùn)行時(shí)環(huán)境的出現(xiàn),RN 進(jìn)入調(diào)試的時(shí)候,客戶端可以做到只加載一個(gè) plugin 到對(duì)應(yīng)的 JSContext 中,其他 plugin 則采用內(nèi)置 cache。
這樣做有兩個(gè)好處:一是保證了服務(wù)啟動(dòng)范圍的最小化,保證了代碼熱加載的效率;二是確保開發(fā)和構(gòu)建兩種流程的一致性,這樣會(huì)讓一些問題在開發(fā)階段提前暴露出來,比如 babel 插件缺失導(dǎo)致的編譯問題。這樣的“去中心化”的開發(fā)流程提高了 RN 調(diào)試效率。
3.3 構(gòu)建流程
隨著業(yè)務(wù)發(fā)展,某 App 的 RN plugin 數(shù)有 4 個(gè),舊構(gòu)建流程受到 plugin 個(gè)數(shù)的影響,集中構(gòu)建時(shí)長超過 20 分鐘。而采用去中心化 RN 架構(gòu),構(gòu)建時(shí)長不再隨 plugin 個(gè)數(shù)增長,只和該 plugin 代碼量有關(guān),穩(wěn)定在 5 分鐘左右。
新架構(gòu)也是同樣基于 host-plugin 模型,獨(dú)立倉庫的隔離讓每個(gè)團(tuán)隊(duì)有自由的發(fā)展空間。考慮到在應(yīng)用內(nèi)的基礎(chǔ) Native 依賴是統(tǒng)一的,host 項(xiàng)目僅用來管理統(tǒng)一的公共依賴。項(xiàng)目需要優(yōu)先將 common bundle 構(gòu)建完成,系統(tǒng)會(huì)記錄公共包中的依賴信息。當(dāng)每個(gè) plugin 項(xiàng)目進(jìn)行構(gòu)建的時(shí)候,構(gòu)建工具會(huì)剔除掉公共包依賴信息,并完成業(yè)務(wù)包的構(gòu)建。每個(gè)業(yè)務(wù)包的構(gòu)建產(chǎn)物都是獨(dú)立地存放于系統(tǒng)當(dāng)中。系統(tǒng)具備獨(dú)立回滾、獨(dú)立發(fā)布、獨(dú)立灰度的能力。
這樣的好處在于構(gòu)建任務(wù)的最小粒度化,每個(gè) plugin 的構(gòu)建不會(huì)引起整個(gè)項(xiàng)目的重新構(gòu)建,做到真正意義的“按需打包”。
3.4 發(fā)布流程
RN 的構(gòu)建和發(fā)布是兩個(gè)獨(dú)立的流程。這也意味著 bundle 的構(gòu)建環(huán)節(jié)和發(fā)布環(huán)節(jié)完全解耦,發(fā)布時(shí)間點(diǎn)也可以由每個(gè)業(yè)務(wù)團(tuán)隊(duì)發(fā)布負(fù)責(zé)人靈活安排。每個(gè)業(yè)務(wù)組對(duì)自己的代碼質(zhì)量負(fù)責(zé),靈活地把控自己的發(fā)版本節(jié)奏,不會(huì)影響其他團(tuán)隊(duì)線上業(yè)務(wù)。發(fā)布流程里面包含了全量發(fā)布、聯(lián)合發(fā)布、灰度發(fā)布、回滾等操作,后續(xù)章節(jié)會(huì)詳細(xì)介紹如何保證發(fā)布的穩(wěn)定性。
4. 系統(tǒng)設(shè)計(jì)
對(duì)于復(fù)雜的大型項(xiàng)目來說,簡單的熱更新流程已無法滿足多業(yè)務(wù)組協(xié)同合作,我們需要一個(gè)功能完善、性能優(yōu)越、操作友好的熱更新系統(tǒng)來滿足復(fù)雜業(yè)務(wù)的發(fā)展。Code Push Platform 由 Node.js 編寫,搭配系統(tǒng)附屬的命令行工具和客戶端 SDK。
為了滿足該系統(tǒng)在多業(yè)務(wù)團(tuán)隊(duì)的運(yùn)作,整個(gè)系統(tǒng)從功能角度可以劃分為三大部分,分別是:
多團(tuán)隊(duì)權(quán)限管控;bundle 生命周期管理;系統(tǒng)效能提升。
其中,系統(tǒng)效能提升功能又細(xì)分為:
增量差分;多場(chǎng)景入口體積優(yōu)化;一站式多環(huán)境整合。 4.1 多團(tuán)隊(duì)權(quán)限管控
系統(tǒng)除了記錄每次構(gòu)建操作,更重要的是工作流程的去中心化,每個(gè) plugin 的權(quán)限是隔離的。每個(gè)負(fù)責(zé)人只能在系統(tǒng)內(nèi)部操作,plugin 1 的負(fù)責(zé)人只能觸發(fā)相關(guān)的構(gòu)建和發(fā)布,沒法看到 plugin 2 的操作情況。系統(tǒng)通過嚴(yán)格的權(quán)限管控來規(guī)范所有發(fā)布流程,保證了項(xiàng)目的可控性。
React Native 去中心化發(fā)布的設(shè)計(jì)目標(biāo)是節(jié)省不同團(tuán)隊(duì)之間的溝通成本。系統(tǒng)會(huì)限制他們的構(gòu)建和發(fā)布的動(dòng)作,各自的發(fā)布不會(huì)互相干擾。
權(quán)限的管理呈樹狀結(jié)構(gòu),一個(gè) App 對(duì)應(yīng)著一個(gè)項(xiàng)目,項(xiàng)目負(fù)責(zé)人默認(rèn)是 App 團(tuán)隊(duì)的項(xiàng)目負(fù)責(zé)人。創(chuàng)建一個(gè)全新的插件等系統(tǒng)操作需要項(xiàng)目負(fù)責(zé)人審批。一個(gè) App 包含有多個(gè) plugin,每個(gè) plugin 負(fù)責(zé)人默認(rèn)是相應(yīng)的業(yè)務(wù)團(tuán)隊(duì)負(fù)責(zé)人,他有權(quán)限分配發(fā)布和構(gòu)建的權(quán)限。
4.2 bundle 生命周期管理
4.2.1 客戶端版本控制
RN 有別于網(wǎng)頁應(yīng)用,它對(duì)客戶端有著緊密的依賴關(guān)系。在客戶端底層依賴沒有變化的情況下,一般情況下開發(fā)者可以通過熱更新進(jìn)行 RN 代碼的更新。但是遇到重大的更新,例如 React Native 的版本從 59 升級(jí)到 63,不僅僅需要 JavaScript 側(cè)改動(dòng),客戶端也要升級(jí)版本且沒法繼續(xù)向下兼容。從技術(shù)層面看,它是難以避免的。這種客戶端無法向下兼容的情況,被稱為“斷層”。
系統(tǒng)會(huì)提供客戶端版本控制的能力。當(dāng)重大變更出現(xiàn)時(shí),App 負(fù)責(zé)人應(yīng)該在系統(tǒng)上新建一個(gè)“斷層信息”,版本號(hào)的范圍是從最低 App 兼容版本到最高 App 版本。在這個(gè)區(qū)間客戶端才能拉取到該斷層的最新 RN 資源。
如下表所示,大于等于 2.5.0 版本的 App 拉取的是 105 版本 RN 包;在 2.0.0 至 2.5.0 版本拉取到 103 版本 RN 包;在 1.0.0 至 2.0.0 版本拉取到 100 版本 RN 包。
這種措施能夠有效避免潛在風(fēng)險(xiǎn)。而最新的需求只會(huì)在最新斷層上線,舊的斷層只做線上問題修復(fù)。畢竟是兩套代碼,代碼的維護(hù)有成本,隨著用戶更新至最新版本,應(yīng)當(dāng)逐漸淘汰掉舊斷層。
4.2.2 灰度和回滾
發(fā)布流程里面包含了全量發(fā)布、灰度發(fā)布、回滾等操作。對(duì)于大型需求,全量上線會(huì)帶來潛在風(fēng)險(xiǎn)。一般來說,優(yōu)先針對(duì)部分用戶投放新版本,發(fā)布負(fù)責(zé)人可以根據(jù)指定用戶和特定范圍進(jìn)行灰度發(fā)布,逐步擴(kuò)大灰度發(fā)布范圍,直至轉(zhuǎn)到全量。當(dāng)發(fā)現(xiàn)重大 bug 的時(shí)候,發(fā)布者可以采用“零構(gòu)建”的方式進(jìn)行“秒級(jí)”回滾。
去中心化 RN 架構(gòu)支持每個(gè) plugin 獨(dú)立發(fā)布、獨(dú)立灰度、獨(dú)立回滾,以最小顆粒度的操作來保證質(zhì)量規(guī)避風(fēng)險(xiǎn)。plugin 維度級(jí)別的灰度和回滾能夠?yàn)椴煌臉I(yè)務(wù)團(tuán)隊(duì)帶了靈活性,每個(gè)業(yè)務(wù)團(tuán)隊(duì)可以自行發(fā)布版本,控制灰度節(jié)奏,處理線上問題。
4.3 系統(tǒng)效能提升
4.3.1 差分增量
App 頻繁更新 RN 資源包會(huì)造成對(duì)用戶流量的消耗,最有效的方式是利用增量更新來節(jié)省流量。RN 資源包涵蓋了編譯后的 JavaScript 產(chǎn)物、圖片、翻譯文件等靜態(tài)資源。它們的前后版本差異即是該版本變更的代碼或者其他資源文件。為了讓差分粒度深入到資源包內(nèi)部,系統(tǒng)專門提供獨(dú)立的“差分服務(wù)”,采用二進(jìn)制差分的方式對(duì)構(gòu)建產(chǎn)物進(jìn)行差分。
RN 資源包的 diff(差分)操作在服務(wù)端完成 ,patch(整合)操作在 App 端完成。在去中心化 RN 架構(gòu)中,每個(gè) plugin 的差分都是獨(dú)立的。plugin 的發(fā)布會(huì)自動(dòng)觸發(fā)差分的執(zhí)行,系統(tǒng)會(huì)以 plugin 為維度拉取最近五個(gè)版本,Diff Server 則會(huì)依次將它們和當(dāng)前版本進(jìn)行差分計(jì)算。如果計(jì)算成功,會(huì)將差分結(jié)果上傳到 CDN 并反饋給系統(tǒng),否則繼續(xù)重試。整個(gè)差分操作是一個(gè)異步的過程,即使出現(xiàn)“差分服務(wù)”下線等極端情況,系統(tǒng)會(huì)自動(dòng)降級(jí)為全量包,保證系統(tǒng)的可用性。
4.3.2 多場(chǎng)景入口體積優(yōu)化
由于 React Native 的構(gòu)建官方依賴于 metro.js,而它并沒有具備無用代碼剔除(tree-shaking)的能力。隨著業(yè)務(wù)代碼的膨脹,包體積的優(yōu)化是一個(gè)很重要的問題。
例如,ShopeePay 為公司多款核心 App 提供支付業(yè)務(wù)。ShopeePay plugin 在不同地區(qū)、不同 App 之間存在一些頁面級(jí)別差異。同一個(gè)倉庫含了所有代碼和資源,但是構(gòu)建腳本會(huì)將它們都會(huì)打包成為一個(gè)產(chǎn)物。很明顯,這導(dǎo)致 ShopeePay 的發(fā)布產(chǎn)物包含大量冗余資源,并非最優(yōu),浪費(fèi)下載流量,同時(shí)也影響代碼的執(zhí)行效率。
我們采用自研的多場(chǎng)景插件(babel-plugin-scene),該插件通過注入的環(huán)境變量設(shè)置一個(gè)場(chǎng)景值,babel 可以根據(jù)場(chǎng)景值的差異化加載不同的文件,并且以默認(rèn)文件作為降級(jí)兜底。不同場(chǎng)景對(duì)應(yīng)不同的入口文件,利用這種形式可以有效控制包體積。
4.3.3 一站式多環(huán)境融合
一個(gè)正常的研發(fā)流程是從 test 環(huán)境,到 uat 環(huán)境,再到 live環(huán)境。Code Push Platform 對(duì)接了 App 的 test/uat/live環(huán)境,所以 RN 開發(fā)者只需要在該系統(tǒng)就可以進(jìn)行“一站式”的操作,方可滿足一個(gè)需求的整個(gè)研發(fā)周期。
不同環(huán)境的包資源流轉(zhuǎn),是多環(huán)境融合的一大亮點(diǎn)。如果某 RN bundle 在 uat 環(huán)境構(gòu)建,它也不需要重新構(gòu)建,將 bundle 無縫轉(zhuǎn)換到 線上 環(huán)境進(jìn)行發(fā)布。它帶來的優(yōu)勢(shì)在于“零構(gòu)建時(shí)長”以及資源包的穩(wěn)定性,因?yàn)?bundle 沒有重新進(jìn)行構(gòu)建,所以它的內(nèi)容已經(jīng)在 uat 得到了充分的驗(yàn)證,發(fā)布風(fēng)險(xiǎn)更小。
5. 舊業(yè)務(wù)的遷移方案
如何遷移現(xiàn)有業(yè)務(wù)的 App 是一個(gè)非常嚴(yán)肅的問題,特別是歷史背景較重的業(yè)務(wù),它們可能存在“邏輯耦合”或者“組件耦合”的場(chǎng)景。與此同時(shí),很多相關(guān)業(yè)務(wù)都在需求迭代當(dāng)中,系統(tǒng)的遷移是不能阻礙需求迭代,所以舊業(yè)務(wù)“漸進(jìn)式遷移”方案是非常必要的。
5.1 邏輯耦合
如果兩個(gè)以上 plugin 存在邏輯依賴關(guān)系,用戶必須同時(shí)加載到最新的 plugin??紤]到熱更新失敗的可能性,邏輯耦合就是多個(gè) plugin 隱藏著一種約束關(guān)系。例如,訂單業(yè)務(wù)和購買業(yè)務(wù)存在一定的邏輯耦合關(guān)系,發(fā)布負(fù)責(zé)人針對(duì)流量極大的超級(jí) App,不可能逐個(gè)發(fā)布 plugin。在極端的狀態(tài)下,用戶可能會(huì)先加載到 plugin A,新版本的 plugin A 和舊版本的 plugin B 是不兼容的,這樣會(huì)帶來嚴(yán)重后果。遇到這種情況,有兩種解決方案:
方案一:plugin 間邏輯解耦,保證每個(gè) plugin 的獨(dú)立性。
方案二:系統(tǒng)提供了聯(lián)合發(fā)布,在 Native 側(cè)保證多個(gè) plugin 能夠同時(shí)加載到最新。
方案一是最理想化的狀態(tài),但是在業(yè)務(wù)場(chǎng)景細(xì)分的情況下,項(xiàng)目結(jié)構(gòu)很難做到絕對(duì)獨(dú)立。
針對(duì)老業(yè)務(wù)可以考慮方案二,系統(tǒng)提供了 module 的概念,一個(gè) module 對(duì)應(yīng)著兩個(gè)以上的 plugin。它們存在著一個(gè)綁定的關(guān)系。在同一個(gè)下載任務(wù)里面,客戶端 SDK 以“事務(wù)”形式,保證多個(gè) plugin 能夠同時(shí)下載完成并投入使用。聯(lián)合發(fā)布這個(gè)能力在系統(tǒng)層面,有效規(guī)避這種錯(cuò)誤的可能性。
5.2 組件耦合
如果說聯(lián)合發(fā)布是針對(duì)在 plugin 維度的“邏輯耦合”兼容方案,“組件耦合”則是更細(xì)粒度的組件級(jí)別的耦合關(guān)系。也就是說,一個(gè)頁面中存在多個(gè)組件來自不同的團(tuán)隊(duì),例如商品詳情頁等頁面有評(píng)價(jià)功能組件。這種“一個(gè)頁面存在著 JSContext 相互嵌套”的情景存在于電商業(yè)務(wù)當(dāng)中。
針對(duì)這種“組件耦合”情況,有兩種解決方案:
方案一:嵌套組件抽離成為一個(gè)獨(dú)立倉庫,供第三方 plugin 使用。
方案二:使用“同屏渲染”的能力實(shí)現(xiàn)“多 Context 嵌套”。
方案一是最理想的解決方案。但是考慮到遷移成本,我們也提供了方案二(一種“同屏渲染”嵌套組件)來支持這種場(chǎng)景,它類似一種 Native 組件。在多個(gè) JSContext 的情況下,通過 plugin 名和頁面名將所需要的內(nèi)容嵌套到另一個(gè)頁面當(dāng)中。
如下圖所示,plugin A 會(huì)嵌套 plugin B 的內(nèi)容,A 和 B 也可以實(shí)現(xiàn)在同一個(gè)屏幕進(jìn)行渲染。從 Web 的方向理解,這種情況有點(diǎn)像 “iframe” 的場(chǎng)景,支持多個(gè)頁面的嵌套。它非常易于 RN 開發(fā)者的理解,客戶端 SDK 能夠動(dòng)態(tài)加載目標(biāo) bundle 并將它渲染在合適的位置。
5.3 漸進(jìn)式遷移
對(duì)于現(xiàn)有的 App,因?yàn)闃I(yè)務(wù)沒法暫停迭代,我們難以一次性完成整體遷移。因此,我們提供了“漸進(jìn)式遷移”方案??紤]到歷史背景,該方案不會(huì)一次性把所有 plugin 都遷移,而是逐步拆分,再遷移接入到新發(fā)布系統(tǒng)。
遷移的步驟如下圖所示:
優(yōu)先將獨(dú)立的業(yè)務(wù)遷移到 Code Push Platform,它們享用一個(gè)獨(dú)立的 JSContext;所有“待拆分代碼”共用一個(gè)獨(dú)立的JSContext;將“待拆分代碼”繼續(xù)拆分成幾個(gè)獨(dú)立 plugin,獨(dú)立使用 JSContext,其他內(nèi)容則保持步驟二的狀態(tài)。
隨著版本迭代,重復(fù)第二和第三步驟,直至歷史業(yè)務(wù)全部拆分完畢。這樣我們可以達(dá)到一個(gè)最優(yōu)的目標(biāo),即是真正意義的“獨(dú)立構(gòu)建”和“獨(dú)立發(fā)布”。
6. 總結(jié)
該系統(tǒng)的目標(biāo)在于滿足所有 App 的多團(tuán)隊(duì)研發(fā)協(xié)作效率問題,去中心化 RN 發(fā)布模型考慮到“獨(dú)立運(yùn)行時(shí)”、“獨(dú)立開發(fā)”、“獨(dú)立構(gòu)建”、“獨(dú)立發(fā)布”四大方面,保障了每個(gè) plugin 運(yùn)行的獨(dú)立性。最終目標(biāo)在于支撐 Shopee 的多個(gè) RN 團(tuán)隊(duì)在不同 App 平臺(tái)根據(jù)自己節(jié)奏自由發(fā)布且高效運(yùn)作。
系統(tǒng)設(shè)計(jì)涉及到“多團(tuán)隊(duì)權(quán)限管控”、“客戶端版本控制”、“灰度和回滾”、“增量差分”、“多入口包體積優(yōu)化”、 “一站式多環(huán)境融合”,加速了整個(gè)研發(fā)流程,真正做到了“靈活性”和“穩(wěn)定性”的兼得。
到此這篇關(guān)于Shopee在React Native 架構(gòu)方面的探索的文章就介紹到這了,更多相關(guān)React Native 架構(gòu)內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
詳細(xì)談?wù)凴eact中setState是一個(gè)宏任務(wù)還是微任務(wù)
學(xué)過react的人都知道,setState在react里是一個(gè)很重要的方法,使用它可以更新我們數(shù)據(jù)的狀態(tài),下面這篇文章主要給大家介紹了關(guān)于React中setState是一個(gè)宏任務(wù)還是微任務(wù)的相關(guān)資料,需要的朋友可以參考下2021-09-09Can't?perform?a?React?state?update?on?an?unmoun
這篇文章主要為大家介紹了Can't?perform?a?React?state?update?on?an?unmounted?component報(bào)錯(cuò)解決方法詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-12-12React如何使用sortablejs實(shí)現(xiàn)拖拽排序
這篇文章主要介紹了React如何使用sortablejs實(shí)現(xiàn)拖拽排序問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-01-01React?Hooks?實(shí)現(xiàn)的中文輸入組件
這篇文章主要為大家介紹了React?Hooks實(shí)現(xiàn)的中文輸入組件示例,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-05-05Redux DevTools不能顯示數(shù)據(jù)問題
這篇文章主要介紹了Redux DevTools不能顯示數(shù)據(jù)問題及解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-01-01JavaScript的React框架中的JSX語法學(xué)習(xí)入門教程
這篇文章主要介紹了JavaScript的React框架中的JSX語法學(xué)習(xí)入門教程,React是由Facebook開發(fā)并開源的高人氣js框架,需要的朋友可以參考下2016-03-03基于React.js實(shí)現(xiàn)原生js拖拽效果引發(fā)的思考
這篇文章主要為大家詳細(xì)介紹了基于React.js實(shí)現(xiàn)原生js拖拽效果,繼而引發(fā)的一系列思考,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2016-03-03