目錄
- 序言
- 遺留項目概述
- 條件限制下的升級原則
- 升級改造的演進方向
- 遇到的主要難點
- 小結
- 參考
1. 序言
Angular
官方網站針對 從 AngularJS 升級到 Angular 提供了比較詳細的文檔,並給出了一個 PhoneCat 升級教程 的案例演示,指導一步步如何改造。但總的來說,這個案例還是太過簡單,並不能很好地還原一個最原始的、相對復雜的、版本更低的遺留項目該如何一步步升級,以及升級過程中可能需要考慮的一些額外因素。
本篇文章會以一個相對復雜的遺留項目為原型來探討該如何一步步進行漸進式地升級改造,以及針對不同情況可以采取哪些策略,算是一篇結合了實際項目改造后的經驗之談。
2. 遺留項目概述
遺留項目按照不同業務拆分成了多個業務模塊和一個公共模塊,即有多個代碼倉庫,如下圖:
從上圖可知,遺留項目中主要使用的是 AngularJS 1.4
,只有一個模塊D
使用了高版本的 Angular
。其實如果正確的業務划分,模塊D
是屬於模塊C
的一個子模塊(或一部分)。
原本是考慮將模塊C
拆分為更多更小的模塊,再加上想嘗試用較新的 Angular
技術棧來寫新功能,在種種原因的促使下最終有了 Angular
高版本的模塊D
。但是,在后續的開發實踐中發現這樣的拆分是存在一定問題的(比如維護兩套類似邏輯的代碼、修改容易疏漏等等),不過由於已經用了高版本的 Angular
,無法再簡簡單單地合並回去。
最終,除了需要考慮如何將 AngularJS 1.4
一步步升級到高版本的Angular
,也需要考慮在升級到一定程度之后將相同業務模塊的代碼倉庫進行合並。
3. 條件限制下的升級原則
這部分主要包含實際改造中遇到的一些硬性限制以及相對應的升級改造原則。
(1)代碼量與時間上的限制
首先,作為遺留項目,各個倉庫的代碼量不一(有多有少),但總體的量是非常龐大的。因此不可能在短期內或一次性全部改造完成。對此較好的策略是從較小的倉庫開始着手,這樣既能用較小的成本來做技術預研,判斷改造方案的可行性,也能較好地控制改造后的風險。改造成功一個之后,則可以依葫蘆畫瓢慢慢鋪開,改造剩余的倉庫。
其次,現有遺留項目還在不斷地改舊增新,這也將占據大部分的編碼時間,並且還存在定期的發版上線,在做技術升級改造的同時需要優先保證正常的功能開發與發版。簡而言之,升級改造和改舊增新是並行的,升級改造需要兼顧改舊增新。
不論是從代碼量上考慮,還是從改造時間的不連續性上考慮,新舊代碼必然是長期共存的,並且為了保證正常的發版,升級改造也必須是漸進式的增量升級。
(2)公共模塊便利帶來的升級限制
所謂成也蕭何敗蕭何,在升級改造中,公共模塊的存在是最為尷尬的。正是因為其復用得多,對其改造后的影響范圍也是最大的,比如改一個公共的組件就需要檢查並修改所有引用了公共模塊的倉庫。
特別是如果要對公共模塊中使用的某個庫進行升級,那么所有引用公共模塊的模塊也都必須同時升級,並且還需要檢查 break changes
的影響,這很有可能需要較大的工作量才能完成。因此有時不能只從升級的可行性上考慮,還需要考慮升級的必要性和其后所能帶來的收益大小等等。(這個問題在 angular-ui-boostrap
的升級改造中遇到,后面詳談。)
總的來說,雖然不同的遺留項目可能面對的情況和限制或多或少會有所差異,但大體上升級改造無法做到一步到位,將會是一個長期的過程是比較常見的情況,對此所需要的則是一個漸進式、增量升級的過程與解決方案。
4. 升級改造的演進方向
這部分所談的演進方向主要是一些概要描述,不包含具體的實踐細節。
(1)代碼風格改造
遺留項目中第一優先需要改造的就是代碼風格。由於 AngularJS 1.4
版本本身的特性限制,遺留項目中存在着大量的 replace:true
、變量綁定在 $scope
上、文件目錄不清晰等與 Angular
規范不太匹配的代碼。
而要改造好代碼風格,與升級相比還是較為容易實現,只需要約定好相應的規范(可以參考 Angular
官網推薦的 風格指南),之后則是花費工作量的事情。
(2)AngularJS 1.4 升級到 1.5
從以下三方面綜合考慮,有必要將遺留項目中使用的 AngularJS 1.4
至少升級到 AngularJS 1.5
:
第一: 升級的代價相對較小。因為畢竟是小版本的升級,雖然存在一定的 breaking changes
,但根據官方提供的遷移文檔,所需更改相對較少,只需要針對性的檢查一番和做少量修改即可。詳情可見此
第二: AngularJS 1.5
新增的特性將有助於更方便地實現新功能(比如 component
、單向綁定、新的生命周期等)。
第三: AngularJS 1.5
新增了組件API,有助於改造遺留代碼的風格。不論是在代碼風格上還是在組件的生命周期上,其都比較像 Angular 中的等價物,在此基礎上將代碼升級到 Angular
時會更容易。
(3)引入 TypeScript
這里所說的引入 TypeScript
,並不會像官網案例中那樣引入了 TypeScript
后就將所有文件直接改為 .ts
文件,而是依舊采用漸進式的升級改造方式。可以通過借助 webpack
打包工具,讓項目同時支持 .js
和 .ts
兩種文件格式,有針對性的使用相關插件,最終統一生成 js
的目標文件。這樣就可以不用一次性將全部文件改為 .ts
文件,把改造的影響降到最低,只需要在后續改造中一步步將 .js
替換為 .ts
文件即可。
另外,在現有遺留項目中,針對 js
文件有用 eslint
進行代碼風格檢查與約束。現在添加了 TypeScript
,針對 ts
文件同樣也可以使用 eslint
。從 eslint 6.0
之后可以根據不同的文件后綴使用不同的規則,這樣就可以同時支持 js
和 ts
兩種文件。
(4)引入 angular-ts-decorator(可選)
在將 AngularJS
升級到 1.5+
之后,可以通過引入 angular-ts-decorator 以 Angular 2
的代碼風格對遺留代碼進一步改造或直接編寫新業務。angular-ts-decorator
的原理很簡單,其實就是借助裝飾器,將 AngularJS
模塊聲明、指令、控制器聲明全部包裝了一層,其內在實質沒有變化。
簡而言之,可以通過使用 angular-ts-decorator
將 AngularJS
的代碼風格改為如同 Angular 2
代碼風格,在享受 Angular
風格的代碼帶來的便利性的同時,也方便后續的升級改造。
到這一步,你或許會有所疑惑,因為按照官網的升級改造,似乎完全沒有必要進行這一步。在必要的代碼風格改造 +
引入 TypeScript
后,其實就可以直接進入到開啟 AngularJS + Angular
的混合模式了。然后就可以快樂地用高版本的 Angular
的寫組件,新功能完全用高版本寫,至於涉及到 AngularJS
的部分,利用組件的升/降級方案,可以在 AngularJS
和 Angular
兩邊混用組件 ,一切看起來似乎很美好,但實際情況會有這么簡單和容易嗎?
一方面, 需要考慮“改舊增新”開發新功能會占據主要時間,技術升級改造的時間相對較少且不連續。而在項目中引入 angular-ts-decorator
庫的工作量是極小的,基本上可以開箱即用,只需要寫一兩個樣例,整個團隊就可以按照新風格來寫 AngularJS
。這將直接提升團隊整體的開發體驗,同時新寫法與升級后的 Angular
組件很類似(除了 html
依舊是 AngularJS
寫法),除了方便后續的升級改造,也更易於維護。
另一方面, 升/降級組件其實都沒有想象中那么簡單。這里的不簡單主要受限於過濾器/管道、屬性指令以及第三方 UI
組件庫這三個方面(具體在后面遇到的難點中詳談)。如果能夠較好的解決這三個問題,那么升級 AngularJS
的組件為 Angular
的組件相對來說就比較容易。
也因此,雖然這一步是可選的,但結合項目的具體情況,其也可能變成是必要的。
(5)啟用 AngularJS + Angular 混合模式
開啟混合模式本身很簡單,只需要引入 Angular
相關的庫,然后在 Angular
中引導 AngularJS
模塊加載啟動即可。詳細可見
(6)逐步升級替換 AngularJS
第一: 引入 HttpClient
來處理 Http
請求,並配置好相關的 Http Intercepters
。這會與 AngularJS
中的 $resource
以及配置的 $httpProvider
相關的策略相對應。
第二: 引入 RouterModule
,使用相鄰出口配置 Angular 的路由策略,讓混合應用同時支持 AngularJS
和 Angular
的兩種路由。
第三: 如果遺留項目中用了第三方的 AngularJS
的 UI
組件庫(比如 angular-ui-bootstrap
),首先考慮是否能夠升級到對應的 Angular
的版本。如果不能或工作量實在太大,那么則需要考慮是否有可替代的 Angular
版的 UI
組件庫,當然這會使得項目中存在 AngularJS
和 Angular
兩套第三方 UI
組件庫,需要考慮的樣式和交互上的一致性。
第四: 全新的功能和頁面,可以完全采用 Angular
組件和路由來寫,而涉及到 AngularJS
的部分,如果不能一次性升級改造完,則可以采用臨時的升/降級組件和服務,來實現混用。(總體原則:優先用高版本 Angular
組件或服務實現相關業務功能)
第五: 合並相同業務模塊。因為已經開啟混合模式,配置好了 Http
請求和路由策略,所以可以考慮將高版本的 Angular
模塊合並到開啟混合模式的模塊中。
......
(7)最終目標
不論准備工作和具體的升級實施方案如何,技術升級改造的最終目標是簡單明確的——合並相同業務模塊,並將所有倉庫的代碼升級到高版本 Angular
。如下圖:
5. 遇到的主要難點
(1)路由及路由組件的升級改造
Angular
官方文檔在路由改造這一塊考慮不是很周全或參考性不強,其升級改造方式並不是漸進式的。一般來說,大的遺留項目根本無法一次性將所有的路由組件替換完。因此需要考慮 AngularJS Router
和 Angular Router
兩種路由的長期共存的可能性,並在改造中逐步用 Angular Router
去替換 AngularJS Router
。而這方面相關的解決方案,官方的升級文檔中並沒有提供,需要自己摸索或搜尋。
Tips: 如果考慮在混合應用中只用 AngularJS Router
路由,也是可行的。其中一種解決方案是將所有的 Angular
路由組件進行降級使用,或者如果 AngularJS Router
用的是 ui-router
, ui-router
官方也提供了一套對混合應用進行支持的方案 angular-hybrid 。但如果考慮到升級改造本身就是要替換掉 AngularJS Router
路由,那么首選混合路由相對較好。
(2)升級改造中的 breaking changes
對所有第三方庫的升級,即使是次版本的升級,有時也會有一些 breaking changes
(比如 Angular 1.4
到 1.5
),這是升級時所必須注意的。而對相應的 breaking changes
則必須結合實際項目作出評估,判斷出影響范圍有哪些或者是否很大。如果影響范圍很大或修改工作量太大,就需要考慮是否有升級的必要性。
另外,在升級過程中還遇到了依賴升級的情況。在將 Angular 1.4
升級到 1.5
后,在使用 1.5
新增的 component
組件特性時,發現其作為路由組件在項目中使用的 ui-router 0.4.x
中不支持這一特性,而它是從 1.0
及其以后開始支持的。這種大版本的變更必然帶來 breaking changes
,在結合了官方的 UI-Router 1.0 Migration 以及項目中使用情況,梳理出 breaking changes
帶來的影響點后,判定為影響相對較小可以接受,因此也連帶着將 ui-router
升級到了 1.0
。
(3)官方 Angular 升級方案本身的限制
無法對 AngularJS
的過濾器 filter
以及屬性指令 attribute directive
進行升級在 Angular 中使用,同時 Angular 的管道 Pipe
以及屬性指令 attribute directive
也無法降級在 AngularJS
中使用。
這個主要會帶來兩個問題:
第一: 無法復用,一定時間內可能會同時存在類似邏輯的兩份代碼。
第二: 有時要升級一個 AngularJS
組件,會發現里面大量使用了過濾器 filter
以及自定義的屬性指令 attribute directive
,升級一個組件的工作量會比預想中的大得多(不能很順滑的升級組件)。
(4)第三方 UI 組件庫的升級改造
遺留項目中主要使用的 UI
組件庫是 angular-ui-bootstrap
,一個純 AngularJS
的組件庫。
首先,由於其引入的版本比較低只有 0.14.x
,其組件指令 component directive
實現還是用了 replace: true
等這些無法進行升級的特性,所以無法直接通過升級在 Angular
中使用。
其次,在遺留項目中不僅大量使用 angular-ui-bootstrap
的組件指令 component directive
,也使用了很多它的屬性指令 attribute directive
,這也導致了就算將 angular-ui-bootstrap
本身進行升級(至少升到 2.0
,存在大量 breaking changes
),以使得組件指令 component directive
可以通過暫時升級的方式在 Angular
中使用,但屬性指令 attribute directive
無法使用的問題仍舊無法解決。
也因此,最終放棄了升級 angular-ui-bootstrap
本身,而是考慮直接用一個高版本的 Angualr
的 UI
組件庫進行替代,只要保證樣式和基本交互能夠基本一致即可。
6. 小結
綜上,主要介紹了遺留項目的基本情況、項目中的限制與應當遵循的升級改造原則、大致的升級改造方向以及遇到的主要難點。后續系列文章准備將進一步討論升級方案中一些步驟具體如何實踐以及踩過的坑。