乾貨 | 前端跨端業務整合的探索與實踐

{"type":"doc","content":[{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"一、背景"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Trip與Ctrip爲獨立運行的兩個站點,雖存在各自品牌化的差異,其業務功能有着極高的一致性。兩個站點相互獨立開發與維護存在着以下的問題:"}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"1.1 技術架構不統一"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Trip與Ctrip使用的開發技術棧存在較大差異。Trip訂後場景在APP端使用Native iOS、Android開發,H5\/PC端採用React技術;Ctrip訂後項目使用可在iOS及Android雙端運行的基於React Native的CRN①框架,在H5端採用CRN-WEB②進行動態打包將CRN代碼生成對應H5頁面。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"兩個站點整體技術架構上多種技術方案並行,相同的業務邏輯需要在各端分別實現,在打包發佈流程中,各端需要通過不同的方式進行相關操作(如MCD③、Ares④、PAAS⑤等)。再加上訂後場景業務維護複雜性比較高,開發週期冗長,兩個站點分別開發的效率不容樂觀。"}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"1.2 功能迭代存在冗餘"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"由於技術架構的不統一導致在業務維護上需要分別進行開發迭代,在開發效率上存在很大的冗餘,同時開發團隊需要面對多種技術棧,學習成本和開發成本都非常高。相同的業務在多團隊重複開發時也難以實現功能與進度上的對齊。"}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"1.3 發佈與監控分散"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"正由於第一點提到的,Trip與Ctrip使用了不同的技術框架分別進行開發,其打包、發佈及日常埋點監控、數據維護存在極大差別且難以整合。Trip的iOS、Android使用的是MCD③平臺使用雙頻道分別進行打包與發佈,而H5頁面需結合Ares④平臺的打包以及PAAS⑤的發佈管理。相對而言,Ctrip對於APP、H5端使用MCD打包發佈,PC端使用Ares與Captain。對於不同的開發代碼,相關的數據埋點緯度與落地方式也不同,分散的埋點數據給之後的統一分析帶來了困難。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"因此TRIP和CTRIP技術架構的統一,共同維護一套流程,那將極大解放開發資源而着重於更有意義的業務探索,也縮小了其後維護流程的成本。在改造過程中,我們將技術棧統一,將原先iOS、Android、H5替換爲CRN架構,將PC替換爲React架構,並在此基礎上建造了模塊化的基礎組件,打造前端中臺化產品。本文將針對前端中臺化改造的探索做出闡述。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https:\/\/static001.geekbang.org\/infoq\/45\/45f08ecd8dbd4cbb782fb5ab81463283.png","alt":"圖片","title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":"center","origin":null},"content":[{"type":"text","text":"整體架構圖"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"\/\/ 章節尾註"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"① CRN:Ctrip React Native,攜程對於React Native的再封裝,提供多種業務部門可以直接使用的基礎工具;"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"② CRN-Web:攜程提供的將CRN\/RN轉爲H5頁面的工具,使APP頁面能在瀏覽器上展示。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"③ MCD:攜程無線持續交付平臺。含多類型項目的集成、測試、發佈、運營等多種服務。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"④ Ares:攜程提供一套前端研發整體解決方案。從編碼、測試、編譯、發佈等多個環節整合前端資源的開發。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"⑤ PAAS:攜程研發服務平臺。一站式提供多種服務產品。"}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"二、面臨的挑戰"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"面對龐大繁複的業務邏輯、Ctrip站點與Trip站點的表現差異,中臺化開發兩邊的產品線並不是一個簡單的改造。就前端而言,將現有的國內站點代碼直接套用於國際站點是一個海量級的改造工作,但經過仔細拆分,難點分類列出並逐個擊破其實是一個可量化好控制的迭代流程。 "}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"1)組件共用;"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"2)樣式拆分;"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"3)暗黑模式的適配;"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"4)多語言的適配(i18n)"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"5)國際單位的本地化(l10n)"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"6)基於Gitlab pipeline的自動化測試"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"7)發佈與監控"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"針對以上各個方面,機票前端開發做出一些探索供大家探討。"}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"三、解決方案"}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"3.1 組件化開發"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"爲了使一套代碼能驅動仍存在差異的Ctrip與Trip流程,首先需要將公用的且因平臺存在差異的模塊或功能抽象化爲組件。機票訂後流程開發技術棧基於React Native + Redux的技術框架,控制流程邏輯的action和reducer一層可以高度重用。然而和視覺相關的View層需要做品牌化區別、不同平臺的語言需要不同的翻譯結果、響應同一操作的服務請求與底層處理邏輯也會有些許不同。由此搭建一套兼容兩端的公共組件庫是拼接一切業務的基石。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"爲了滿足各個功能的兼容性,公共庫包含了原子UI組件、業務基礎組件、基礎處理事件、公共工具等四象限緯度的組件:"}]},{"type":"image","attrs":{"src":"https:\/\/static001.geekbang.org\/infoq\/18\/18c2d09f55cb7ff26b027c2efa7ff503.png","alt":"圖片","title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":"center","origin":null},"content":[{"type":"text","text":"公共庫四象限組件"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"原子UI組件"},{"type":"text","text":":包含了頁面展示的基礎UI,包括Icon、Card、Button等。其組件與上下文無關,更多是在針對Ctrip及Trip不同平臺進行品牌化差異的樣式處理(詳見第2小節)、基礎事件的綁定和必要的曝光點擊等埋點的處理。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"基礎業務組件"},{"type":"text","text":":是針對一個原子業務塊的UI封裝,例如機票卡片、進度軸、運價明細卡片等,通常需要依賴上下文數據的傳入。一個業務組件雖然依賴的數據源往往是一致的,但其組裝起的基礎UI組件、頁面的排版格式往往存在一定的差異。封裝之後的業務組件可使業務開發無需考慮其中的展示、排版邏輯,使用統一的數據源及事件操作可達到相同效果。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"基礎處理事件"},{"type":"text","text":":包含了相同性質的業務事件處理,例如Ajax請求的發送、業務埋點的發送、多語言的轉換(詳見第4小節)、國際單位的本地化轉換(詳見第5小節)等。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"公共工具"},{"type":"text","text":":則提供與上下文無關的純函數處理工具,例如電話號碼的正則校驗、url參數的轉換等等。保證對其調用不會受方法以外的環境影響,也不會影響對外的環境。"}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"3.2 樣式拆分"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在追求流程統一化的前提下,Ctrip和Trip的品牌化視覺體驗還是有很大的差異。兩端針對字號、顏色、頭部樣式、彈窗樣式、甚至圓角都有各自的標準。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" "}]},{"type":"image","attrs":{"src":"https:\/\/static001.geekbang.org\/infoq\/cc\/ccad7262bfe9ed52e63497cf33f64bca.png","alt":"圖片","title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":"center","origin":null},"content":[{"type":"text","text":"Ctrip & Trip 字號大小映射表"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https:\/\/static001.geekbang.org\/infoq\/eb\/ebc6d4b826912efee3364ebeaf70e1de.png","alt":"圖片","title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":"center","origin":null},"content":[{"type":"text","text":"Ctrip & Trip 顏色映射表"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":"br"}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"爲了解決兩端樣式的適配,公共庫封裝了技術樣式表組件。改造初期對於整個流程針對字號和顏色進行了一次整理,將流程所使用到的字號和顏色總結到了一張基準樣式常量表,再將常量表再跟進國際站點的標準重填入對應的值,並寫入樣式表組件庫。之前寫到樣式表裏的字號和顏色全部改爲引用樣式表裏的常量,而用哪張表則取決於當前是哪個站點的APP。抽離常量的過程雖然繁瑣,換來的是兩端的代碼可以儘可能得使用一張樣式表。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"text"},"content":[{"type":"text","text":"\nconst BasicIBU = {\n \/\/ Fonts\n subHead: 20, \/\/ sub head\n headline: 18, \/\/ title\n headlineS: 16, \/\/ title\n bodyText: 15, \/\/ body\n callout: 14, \/\/ title\n textNormal: 14,\n bodyTextS: 13,\n footnote: 13, \/\/ void\n caption1: 12, \/\/caption\n \/\/ ...\n \n \/\/ Colors\n black: '#0F294D', \/\/ 基礎黑色字\n secondaryBlack: '#455873', \/\/ 基礎黑色字 二級黑\n tertiaryBlack: '#8592A6', \/\/ 基礎淺黑色字 三級黑\n titleBlack: '#042950', \/\/ 標題黑色字\n grey: '#ACB4BF', \/\/ 基礎灰色\n switchGrey: '#AAA', \/\/ 切換按鈕灰\n lightGrey: '#F5F7FA', \/\/ 基礎淺灰\n lineGrey: '#DDDDDD', \/\/ 邊框灰\n placeholderGray: '#F0F2F5', \/\/ 呼吸態灰\n \/\/ ...\n}"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":"center","origin":null},"content":[{"type":"text","text":"IBU基礎樣式常量表截取"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"text"},"content":[{"type":"text","text":"\nmodalTitle: {\n textAlign: 'center',\n fontSize: BasicStyles.headline,\n color: BasicStyles.black,\n}"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":"center","origin":null},"content":[{"type":"text","text":"一個簡單的樣式表"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":"br"}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"有了字號和顏色的基礎,可以在這基礎上開發出兩端共用的基礎UI組件與基礎業務組件。例如頁面頭部、選擇彈框、抽屜浮層等。因爲基礎組件的交互邏輯一致,不同的只是兩端(或者三端:國際站點針對IOS端和Android端有不同標準)的表現樣式,所有的公共組件都是針對邏輯寫了一份共用的JS邏輯以及針對渲染層級寫一份共用的JSX Dom表。JSX Dom表上綁定的StyleSheet則是針對性得讀取三張表(FBU、IBU IOS、IBU Android)的樣式內容。而樣式表中的字體、顏色使用基礎樣式表的封裝便可按圖索驥渲染不同的品牌樣式。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https:\/\/static001.geekbang.org\/infoq\/f8\/f81dffde27a10a86cc11993ac977548e.png","alt":"圖片","title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":"center","origin":null},"content":[{"type":"text","text":"公共組件目錄結構"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"同樣的,在業務開發過程中,非基礎組件的View層也需要區別開發。因爲業務邏輯相同,各個業務場景往往只需製作一套JSX Dom表,而對於FBU和IBU往往需要兩套樣式表。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https:\/\/static001.geekbang.org\/infoq\/24\/249ee3377996b0ce63441f5a0bd8a3f5.png","alt":"圖片","title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":"center","origin":null},"content":[{"type":"text","text":"業務組件目錄結構"}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"3.3 暗黑模式的適配"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"除了常規的兩端樣式差異以外,爲了加強品牌效應,流程還需要支持暗黑模式(DarkMode)。DarkMode在轉換時,看似只是將顏色做一個簡單的白轉黑,黑轉白映射轉換,實在底層有很多讓人頭疼的邏輯。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"首先並不是白色都轉換爲統一的白色,明亮模式下白色卡片相互疊加因爲有黑色邊框或者黑色陰影的隔離,層級區分很自然明細;然而在暗黑模式下,自然黑色的邊框和陰影並不能將黑色的卡片有效的區分開來,所以需要將所有白色做語義化區分,不同層級不同語義的白色轉換爲不同深淺的黑色。如此,一個 #FFF白色,可以根據場景語義化區分爲背景純白ThemeWhite,主要內容白色PrimaryContentWhite,次要內容白色SecondaryContentWhite等,分別對應的暗黑模式色值爲#0D131A,#252B31,#333B46。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"其次,如上面提到的陰影和邊框等擬物色,在暗黑模式下不能轉換(自然界中未有過白色的陰影吧)。需要將這些擬物色剝離出來(如陰影的ShadowBlack),在暗黑模式下不做轉換。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"最後,所有的彩色在亮度更低的暗黑模式下需要轉換爲飽和度更低的對應顏色。例如警戒紅色從#EE3B28映射爲#F37668,品牌藍色從#287DFA映射爲#7EB0FC。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https:\/\/static001.geekbang.org\/infoq\/61\/616b674b690b38d4299eec7f2e7a1b57.png","alt":"圖片","title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https:\/\/static001.geekbang.org\/infoq\/73\/73eb8e7f8b72fc3d189216df2df01229.png","alt":"圖片","title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":"center","origin":null},"content":[{"type":"text","text":"明亮模式&暗黑模式對比圖"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"顏色的映射規則弄清楚了,那怎麼把暗黑模式應用到流程的呢。這就要回到在樣式品牌化章節提到的基礎樣式表,FBU站點有一張基礎樣式表,IBU有一張基礎樣式表,只需要將原來的IBU基礎樣式表作爲明亮模式的樣式,再在此基礎上映射出一張暗黑模式表。在APP啓動階段動態得判斷當前所在的模式,並加載對應的樣式表。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https:\/\/static001.geekbang.org\/infoq\/91\/91246fb76a4d81f7828e5a150b2fb68a.png","alt":"圖片","title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":"center","origin":null},"content":[{"type":"text","text":"加載基礎樣式表流程"}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"3.4 多語言的適配(i18n)"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"國際化的改造自然離不開多語言的適配(i18n即internationalization的簡寫,由首尾的i、n及中間的18個字母組成)。此次機票訂後流程的多語言翻譯及加載機制依託於攜程的Shark多語言整體解決方案⑥。Shark翻譯平臺以及框架提供的i18n組件已經是相當成熟的一套系統,這裏不再贅述。這次改造的難點還是在如何在已有的流程中摳出需要翻譯的文本,以及管理各頁面翻譯文本的加載。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在流程改造初期,一個繁重但必不可少的工作就是在全流程代碼摳出需要翻譯的展示詞條。爲了方便管理以及優化資源分配,整個業務層將詞條分頁整理爲多個數組:其中全流程都使用的基礎詞條(如“確定”、“取消”等)單獨列爲一個數組;而頁面獨有的詞條根據頁面緯度分別建組。數組裏的每個詞條實體包含一個鍵值對,鍵爲提供給Shark平臺翻譯唯一標記的key,值爲其key對應的默認簡體中文文案。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"text"},"content":[{"type":"text","text":"\n\/\/ 通用KEY\nexport const sharkCommon = {\n goToDetailPage: {\n id: 'key.flight.postservice.common.godetail',\n defaultMessage: '去訂單詳情',\n },\n calendarTitle: {\n id: 'key.flight.postservice.calendar.title',\n defaultMessage: '選擇日期',\n },\n \/\/ ...\n}\n\/\/ 退票選程頁用的KEY\nexport const refundSelectTrip = {\n changeTagTxt: {\n id: 'key.flight.postservice.refund.select.change.tag.txt',\n defaultMessage: '航班調整',\n },\n refundBtnTxt: {\n id: 'key.flight.postservice.refund.select.refund.btn.txt',\n defaultMessage: '申請退票',\n },\n \/\/ ...\n}"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":"center","origin":null},"content":[{"type":"text","text":"shark keys"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":"br"}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"頁面加載時,會根據各個頁面使用情況動態加載去加載翻譯文本。每個頁面繼承了一個基礎的頁面組件(CommonBasePage),組件加載後(於RN的生命週期componentDidMount)首先需要鎖住頁面的渲染展示加載態,這時兩條業務線的加載邏輯略有不同。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"針對Trip站點,加載態時需要拿着當前頁面渲染使用的翻譯詞條給到Shark SDK申請翻譯結果,翻譯詞條一般包含之前劃分好的公用詞條組和當前頁面特有詞條組,Shark SDK會拿詞條的key與當前手機配置的區域語言匹配到翻譯後文案並返回給業務端,當翻譯返回後放開頁面的加載態繼續進行頁面後續的渲染工作。而針對Ctrip站點,不需要向shark平臺請求翻譯結果,所有內容都已包含翻譯鍵值對的默認翻譯中,則直接跳過獲取翻譯這一步,並取消加載態進入後續的頁面渲染。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Shark平臺其實也提供了簡體中文的翻譯,那麼爲什麼不將Ctrip業務線的展示詞條也託管到shark平臺統一管理呢?"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"這裏有幾方面的考慮:首先shark平臺的簡體中文需要開發和產品自行翻譯且維護,和維護鍵值對的默認中文並無區別;其次,通過Shark SDK加載翻譯需要額外的外部依賴調用,且在目前流程是阻塞式的,頁面穩定性及頁面加載效率來說不如本地讀取鍵值對的方案;最後,Ctrip團隊針對業務線已寫有大量的UI自動化測試及單元測試且已接入CI\/CD持續化構建平臺,如若每次測試都需要額外調用Shark SDK,穩定性及自動化構建的效率也會受到挑戰(關於自動化測試相關解決方案之後章節有更詳細討論)。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https:\/\/static001.geekbang.org\/infoq\/05\/0578b07668fec161596622c4ef4ab13d.png","alt":"圖片","title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":"center","origin":null},"content":[{"type":"text","text":" 頁面獲取翻譯流程"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在流程上線之後,仍需要對翻譯結果查漏補缺,監控可能出現的因漏提翻譯或系統錯誤導致的展示中文的情況。好在前端的所有文字展示都使用Text基礎拓展組件,組件在觸發渲染時對子元素所包含的字符串做一次正則檢測。在Trip環境中若正則檢測到中文,則發送一次警告。開發可以方便得通過警告信息關聯的訂單號或流水號定位到系統展示中文是因爲該字段是漏提交了翻譯還是系統錯誤造成的。(除去中文檢測,此正則式還可以很方便得檢測出null、NaN、undefined等頁面的錯誤展示。)"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"text"},"content":[{"type":"text","text":"\nstatic checkChildren(children?: ReactNode) {\n const chineseReg = \/[\\u4e00-\\u9fa5]\/;\n if (StringChecker.needCheck && children && typeof children === 'string') {\n if (chineseReg.test(children)) {\n if (!StringChecker.data[children]) {\n StringChecker.data[children] = DataStatus.DEFAULT;\n Expose.sendError('Error:find chinese', { str: children }, true);\n }\n }\n }\n}"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":"center","origin":null},"content":[{"type":"text","text":"查找中文並報警"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":"br"}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"另外,針對一些特殊場景如人名等,可以配置相應的可忽略檢查的語言以及業務模塊。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"\/\/ 章節尾註"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"⑥ Shark:攜程提供的多語言站點UI文案管理與翻譯的一整套解決方案。實現提供原文後交於統一交於翻譯團隊,並通過其提供的SDK工具於業務代碼中抓取下發對應翻譯後的多語言結果。"}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"3.5 國際單位的本地化(l10n)"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"和國際化(i18n)相對的,Trip站點也許考慮各計量單位的本地化(l10n即localization的簡寫,由首尾的l、n及中間的10個字母組成)。每個國家和地區對於貨幣、時間、重量、距離等的展示標準各有差異,因此需要根據APP所設置的地區與語言,動態得去轉換所展示的計量數據格式。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"例如時間的展示,不同的區域會展示如“01\/01\/2020 Monday”、“2020\/01\/01 月曜日”等格式。決定時間以何種格式展示,方法類似於上一章節的多語言翻譯。基礎頁面組件(CommonBasePage)加載翻譯語言詞條時,也會拿手機當前語言及地區向Shark SDK請求對應的基礎計量單位展示格式制式包,其中包含了諸如日期、重量、數字等計量單位展示時所使用的標準格式,之後的業務代碼再將具體需要轉換的數字向對應的格式進行轉換。這樣就使服務下發或計算出來的唯一格式的時間根據不同的APP設置轉換爲不同的格式。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"貨幣,重量、距離、數字的千分位展示及小數默認位數等的個數都需要根據不同的地域語言做區分。轉換使用方式類似,這裏不再贅述。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https:\/\/static001.geekbang.org\/infoq\/6b\/6be3bd6fc2d30bb25f8419c4c1a4cc54.png","alt":"圖片","title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":"center","origin":null},"content":[{"type":"text","text":" Currency轉換"}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"3.6 基於Gitlab Pipeline的自動化測試流程"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在質量這一塊,除了常規的UT之外,機票前端團隊做了大量的自動化測試,這些自動化的流程適配於中臺化開發的流程中,保證了Ctrip和Trip代碼的質量。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"我們基於Gitlab,在其Pipeline中加入相應的檢測機制,把Soanr、UT、UI自動化等流程融合到流程中,確保每一次代碼簽入和MR都執行成功。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" "}]},{"type":"image","attrs":{"src":"https:\/\/static001.geekbang.org\/infoq\/9d\/9dbd33a849d0900472a3e0b5202db391.png","alt":"圖片","title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在UI自動化測試實現過程中,內核採用的是Cucumber⑦和Puppeteer⑧運行業務代碼的H5版本來實現測試。我們將CTRIP和Trip的測試步驟拆分(增加isIBU字段作爲標識),在Cucumber底層會根據此標識區分測試Trip站點與Ctrip站點對應的頁面。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"針對於多語言環境,我們執行在Trip站點用例時會分情況進行判斷。如若此用例更多偏向測試業務邏輯而不太在意翻譯的內容與質量,運行過程將屏蔽獲取翻譯的流程,直接使用默認的簡體中文內容,這樣我們的測試用例斷言內容全部都不需要修改,並且屏蔽了因翻譯服務不穩定、翻譯內容時常變更帶來的比確定性。而若此用例需要考慮進測試多語言翻譯結果的正確性,則可以給予標識打開翻譯流程,實時獲取翻譯內容進行校驗。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https:\/\/static001.geekbang.org\/infoq\/ac\/ac08e6364317d6f056bfee4d900c3f15.png","alt":"圖片","title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"\/\/ 章節尾註"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"⑦ Cucumber:一個基於行爲驅動(BDD: Behavier Driven Development)的開發測試工具(https:\/\/cucumber.io\/);"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"⑧ Puppeteer: 一個通過提供高階API用於控制Chrome或Chromium的Node工具集(https:\/\/pptr.dev\/)。"}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"3.7 發佈與監控"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"相較於多端分開開發,中臺化開發還帶來一個好處是發佈更加便捷、易於管理、更新更加及時。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"原先發布就一個需求而言,全平臺上線需要先後於MCD平臺分別發佈IOS、Android版本,於Ares打包發佈H5的靜態資源,於PAAS平臺將H5打包結果發佈生產站點。特別的,IOS、Android需要跟隨APP主程序包的更新進行發佈,這樣便限制了APP端業務的發佈節奏。也就是說進行單頻道的熱更新修復或者緊急需求上線比較困難,並且上線之後未更新的客戶端仍無法使用最新的業務邏輯。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"進行中臺化開發後的訂後產品,使用相同的技術棧,在APP端採用CRN框架開發,在IOS、Android、H5統一使用MCD發佈系統進行打包發佈,避免了多平臺發佈的差異性。並且使用中臺化開發後,所有站點需求會在統一的發佈時間節點進行統一發布,意味着Trip站點和Ctrip站點的需求可以統一管理髮布上線。使用CRN還可以很方便得在APP內進行熱更新,和APP版本發佈相解耦,實現了需求的隨發佈隨使用,解決了緊急修復難於上線的困難。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https:\/\/static001.geekbang.org\/infoq\/7e\/7e22296c3a93ade4e39dd8988e607b87.png","alt":"圖片","title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":"center","origin":null},"content":[{"type":"text","text":" 統一前後的發佈邏輯"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在監控上,Ctrip和Trip站點接入統一的監控平臺,數據採集統一彙總到hickwall中,製作相應的監控面板和告警規則,針對客戶端Error統一採集並分發到Bigeyes管理平臺,同時針對上面提到的異常空字符串也會做到及時監控、及時告警。"}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"四、小結"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"以上幾點便是機票前端後服團隊針對Ctrip和Trip實現中臺化開發的一些探索,雖然磕磕碰碰繞過彎路,現在仍有一些待優化的點。但陣痛過後的收穫是滿滿的:現在Ctrip和Trip站點在APP、H5、Online三線業務邏輯統一,相同功能迭代的開發用時減少,多個站點只需要維護一套代碼,開發成本得到很大的降低,開發效率也有很大的提高。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"作者簡介:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Jeff,攜程前端開發經理,對前端自動化技術感興趣,推動了團隊使用cucumber進行UI自動化測試。Harry,攜程前端開發工程師,秉持“Don’t make me think“的理念向用戶交付頁面、向同事協作工程。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"爲應對攜程國際化的需求,機票前端團隊開始業務統一化的步伐,Ctrip和Trip的業務整合和代碼複用成爲面臨的困難和挑戰。在實踐過程中,團隊積累了大量的經驗,下文是機票實現業務統一化、技術中臺化、迭代敏捷化的思路和方法。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"本文轉載自:攜程技術中心(ID:ctriptech)"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"原文鏈接:"},{"type":"link","attrs":{"href":"https:\/\/mp.weixin.qq.com\/s\/pHLZyMr5YJ5NyILZgzY3XA","title":"xxx","type":null},"content":[{"type":"text","text":"乾貨 | 前端跨端業務整合的探索與實踐"}]}]}]}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章