Coding 前端重構之路
在 CodeInsight 開發告一段落之後,CTO 大人找到我說要想一個把 Coding.net 的前端拆分重構的方案,於是我從一個歡脫的開發狀態開始切換到要面對一句魔咒的考驗。
動態語言一時爽,代碼重構火葬場。
不管怎麼樣,先從梳理現狀開始。
Coding 前端使用 Angular 構建,前端工程化還是使用合併文件打包的方式,並沒有引入 CommonJS 之類的模塊化開發方式,作為一個 SPA 網站,隨著網站規模的增大,前端代碼開始越來越臃腫,開發體驗也直線下降,這是我們考慮重構的原因。
所以首先我們要想清楚重構要解決的問題
- 代碼打包拆分,避免所有功能模塊打包到單一的文件
- 引入 CommonJS 做到更清晰的模塊化
- 邊開車邊換輪子
要做到最後一點尤其困難,但這也是我們能否順利重構的關鍵,重構不是重寫,所以如何在現有代碼基礎上重構,並且還要和當前的開發進度無縫銜接起來就是我們所要面臨的一個挑戰。
好消息是我們使用了 Angular 保證了我們的代碼分 Module 有了一層封裝,不至於太過散亂。作為一個 SPA 網站,前端路由已經很好的分離出了各個功能模塊。我們用到了 Grunt,雖然有點過時,task 寫得有點複雜,但是提供了一個工程化的切入口。
經過小夥伴們幾輪討論之後,最終確定了一套比較靠譜的方案:
- 按照功能模塊重新整理/拆分代碼
- 保持作為一個 SPA 網站,按照路由懶載入功能模塊
後一點是我們這套重構思路的核心,在這之前我們有考慮把每個功能模塊拆分出獨立的單元來跑,但是為了保證「邊開車邊換輪子」,重構必須是一個快速迭代的過程,不能說等到某一個完整的功能模塊單元重構完了再去整合到現有的代碼,這種重構方式將是一個漫長耗時的過程,並且風險也很大。
保持 SPA,引入懶載入,我們可以快速將這種架構調整整合到現有代碼中去,驗證是否可行,之後的重構過程就可以細化到每一個 controller,做到「邊開車邊換輪子」。
功能模塊拆分
這一步很簡單,Coding 網站的功能模塊已經比較清晰了,比如冒泡,任務,搜索等等,我們只需要確立一套統一的目錄結構和命名空間規範來重新整理代碼,得益於 Angular 的依賴注入機制,之前的代碼邏輯完全可以保持不變,對於那些獨立的模塊,這個重構過程基本上沒有什麼引入 Regression Bugs 的風險,重構一個模塊只是修改命名空間而已。
我們約定:
- 重構的每個 controller, service 等等都有自己的命名空間(規範)
- 每個功能模塊定義自己的路由
- 每個功能模塊有一個唯一的 module 注入所有依賴
比如重構後的冒泡可能是類似這樣的結構:
tweet/n├── tweet-list.controller.jsn├── tweet-list.htmln├── tweet-topic.controller.jsn├── tweet-topic.htmln├── tweet.module.jsn└── tweet.routes.jsn
tweet.routes.js 將會指定懶載入 tweet.module.js。
Webpack 懶載入
為了做到代碼打包拆分,我們使用懶載入的方式,當導航到對應的功能模塊時才去載入相應的功能模塊代碼,引入 webpack 一併實現了對 CommonJS 的支持以及 Lazy Load。
現有的 Angular 路由已經很好的分離出了功能模塊入口,所以我們只要把這個路由文件當做一個切入點,作為 webpack 的打包入口文件,由這個入口文件引入的所有依賴就都可以使用 CommonJS 的模塊化方式了,也就是說我們所有重構的代碼自然而然就可以遷移到使用 CommonJS,在這裡 webpack 將作為一個完美的粘合劑,銜接現有的代碼和重構後的代碼,這裡通過一個簡單的路由來看一下是如何做到的。
./tweet/tweet.routes.js
$routeProvider.when(/pp/:region?, {n templateUrl: require(./tweet-list.html),n controller: TweetListController,n title: 冒泡,n resolve: {n lazyLoader: function($q, $ocLazyLoad) {n var defer = $q.defer();n require.ensure([], function() {n var module = require(./tweet.module);n $ocLazyLoad.load({ name: module.name });n defer.resolve(module);n });n return defer.promise;n }n }n });n
Angular 的路由支持非同步載入,require.ensure 是 webpack 用來非同步載入回調函數內部指定的 tweet.module.js,這個 module 注入了 tweet 這個功能模塊的所有依賴,比如TweetListController
use strict;nnvar angular = require(angular);nnmodule.exports = angularn .module(tweet, [n require(./tweet-list.controller).name,n require(./tweet-topic.controller).name,n ]);n
最後我們用到了 ocLazyLoad 來注入這個非同步載入的 module。
grunt-webpack
新引入的 webpack 可以很容易的整合到我們現有的開發流程裡面去,使用 grunt-webpack 就可以把 webpack 作為一個新的任務給 grunt 調用,所以我們可以獨立 webpack 的打包編譯流程,並且作為一個子任務插入原來的編譯流程,而不影響原來的開發/發布方式。
gruntfile.js
...nn webpack: {n dev: {n entry: {n routes: [./src/routes.js],n },n ...n },n prod: { ... }n }nngrunt.registerTask(server, [..., webpack:dev, ...]);ngrunt.registerTask(build, [..., webpack:prod, ...]);nn...n
公用模塊
對於獨立的,不被其他地方依賴的 module,重構可以很方便,但是對於公用的模塊,雖然可以重構這個模塊,但是要更改所有針對這個模塊的引用,牽涉到的代碼就有點不受控制了。
所以我們才要約定所有重構的模塊都會有自己的命名空間,對於那些公用的模塊,遷移到新的命名空間,同時會保留之前的代碼,直到我們重構其他功能模塊到某個時間點,可以確定沒有模塊依賴這些被保留的公用模塊,再去清理,這樣在前期會有一部分代碼冗餘,但是保證了我們重構的質量和進度。
至此,Coding 前端開啟了重構之路,相信 http://Coding.net 將會逐步帶來更好的體驗。
> 本文作者:CODING 工程師 劉輝
推薦閱讀:
※為什麼鎚子手機官網用angularjs開發?
※在用angularjs的時候有沒有必要用requirejs或者是browserify?
※angularJS適不適合做互聯網金融產品?