大前端項目代碼重用,也許lerna是最好的選擇

我前段時間參與了一個react爲主的大前端項目,覆蓋Web、Android、Ios三個平臺。因爲整個業務邏輯側重在手機端,且Web端也是到了項目中期纔開始啓動,我分別以react-nativereact分開建了兩個項目。javascript

但是,後端微服務集羣是同一個,兩個項目調用的API大多同樣,致使兩個項目中不可避免的出現了一些重複邏輯。好比說寫在redux中的業務邏輯代碼,寫在http攔截器中的請求處理代碼等等,這些重複的部分從目錄名和文件名便可肉眼看出。前端

我嘗試了將重複部分封裝成獨立的npm包供兩個項目引用,但這個作法僅適用於變化頻率較小的工具類代碼,一旦封裝了變化頻繁的業務邏輯代碼,用起來也麻煩不斷。首先,抽出到項目以外但不易開發和調試,其次,在項目開發過程當中業務代碼更新太頻繁,經常要不斷升級版本。權衡利弊,最終得不償失。java

那麼,有沒有辦法更好的重用這部分代碼邏輯呢?有沒有辦法把這兩個項目的通用代碼抽取成獨立項目,可是又能避開封裝成獨立npm包的弊端呢?react

我最近嘗試引入lerna框架,把這個大前端項目架構做爲參照物來改造,驚喜的發現它不但能解決當時項目的痛點,還能額外帶來一些多項目管理相關的好處。git

事實上,開源社區早已有不少項目使用了這種多項目合而爲一的方案,且採用了lerna框架的代碼庫也大多耳熟能詳,好比國外的有babelcreate-react-appreact-routerjest、以及國內跨端小程序框架Tarogithub

最後通過改造,兩個項目合併成一個,重複的代碼邏輯也被抽取成另外一個獨立項目,整個項目結構變成了下面圖示這樣。web

引入lerna

lerna的名字來源於希臘神話中的九頭蛇海德拉(Lernaean Hydra),拿它形容多項目工程是再貼切不過了。npm

lerna的引入比想象中簡單,其實,與其說引入lerna,倒不如說是導入到lerna更合適,由於具體的作法是經過命令行建立了一個新的lerna項目,而後把全部項目導入進去。並且在導入的同時,每一個項目的git提交記錄也都合併在了一塊兒。json

lerna init
lerna import 你本地的項目路徑
複製代碼

每一個被導入的項目都會被存放在根路徑的packages目錄下,下面是我demo項目的截圖,一共引入了三個子項目:rntest, web-app, shared。分別表明mobile,web和可重用的邏輯代碼。redux

使用lerna來管理項目依賴

引入lerna後,第一件事就是要處理安裝依賴的問題,咱們須要用lerna add 命令來代替咱們習慣的npmyarn,好比說給rntest項目安裝lodash,就要執行下面的命令。

lerna add lodash --scope=rntest
複製代碼

不過,執行後你會發現其餘項目中package-lock.json都發生了變化,讓人很是困惑,這背後的緣由是跟添加依賴後自動執行的安裝命令lerna bootstrap有關。

lerna的依賴提高

lerna能夠經過lerna bootstrap一行命令安裝全部子項目的依賴包,並且在安裝依賴時還有依賴提高功能,所謂「依賴提高」,就是把全部項目npm依賴文件都提高到根目錄下,這樣能避免相同依賴包在不一樣項目安裝屢次。好比多個項目都用了redux,經過依賴提高,多個項目一共只須要下載一次便可。不過,須要額外的參數--hoist讓依賴提高生效。

lerna bootstrap --hoist
複製代碼

可是自動執行lerna bootstrap命令是不帶依賴提高參數的,這就致使上面每一個項目的lock文件都會被修改的緣由。

固然,要解決這個問題也容易,能夠經過lerna的配置來避免npm對lock文件的修改便可,寫法以下:

yarn是lerna的最佳搭檔

lerna默認使用npm做爲安裝依賴包工具,但也能夠選擇其餘工具。yarn在1.0版本以後提供了workspaces的功能,該功能從更底層的地方提供了依賴提高,作的事情跟lerna一模一樣。把它跟lerna放在一塊兒看,簡直就像是爲lerna量身定作同樣。所以,推薦在lerna中搭配yarn一塊兒使用。

把npm替換成yarn只需在lerna的配置文件添加兩行代碼便可,配置完之後馬上順暢百倍。

高效的代碼重用

在我參與的這個大前端項目裏,多端之間代碼重複的部分包含redux中的業務邏輯、http請求的處理、代碼規範工具的檢查、git鉤子中的自定義腳本等等。在lerna架構下,前二者可直接抽取到一個獨立的項目,而後被其餘項目引用,好比在個人demo中,能夠像其餘依賴包同樣直接引入shared項目, lerna會自動識別並把它導向內部項目。

import shared from 'shared'
複製代碼

這跟直接封裝成npm包的一大區別就是實時更新,修改馬上可見,就像在同一個項目同樣,不影響開發和調試。

git鉤子和自定義腳本的重用

我嘗試把處理git鉤子的工具husky安裝到了根目錄,觸發的事件和自定義腳本能覆蓋到每一個項目,給這部分代碼重用帶來了極大便利。好比,很多項目會添加自定義腳原本約束git commit提交時的消息描述,在lerna架構下,只需寫一次便可。

eslint的重用

那些經常須要在根目錄添加配置文件的第三方依賴,好比eslintprettierbabel等,在lerna中沒法簡單粗暴的提高合併到一處。所以,對於eslint這種前端開發已不可或缺的工具,能夠嘗試將全部配置項抽取到獨立項目,而後安裝第三方依賴的方式引入,相似eslint-config-airbnbeslint-config-prettiereslint-config-google這樣。

不得不說,即使不用lerna框架咱們也能夠這麼作,只不過在lerna框架下修改馬上可見,方便了調試和開發。

lerna框架下的CI/CD

多項目的結構無疑給CI/CD帶來挑戰,好在主流的CI框架能完美解決這個問題。好比在gitlab上,only/changes參數徹底知足了咱們的需求,讓咱們能夠爲每個子項目設置單獨的pipeline,好比如今咱們設置一個pipeline,只當rntest項目下的文件被修改時纔會觸發:

在lerna框架下,全部項目都合在一個工程裏,但CI/CD並沒必要這樣。經過把腳本中的關鍵參數配置到CI/CD的項目內裏,共用同一份.gitlab-ci.yml文件,從而可以實現每一個子項目對應一個獨立的CI/CD項目,最終CI/CD結構以下圖:

lerna框架下的子項目權限

因爲全部的項目都歸併到了一個lerna工程下,一旦有了訪問權限意味着你能夠修改全部子項目中的代碼,在實際的開發工做中多多少少會帶來一些麻煩。好比說,開發web和開發mobile平臺的是兩個不一樣的團隊,假如我做爲web組的一員,一不當心修改了或刪除了mobile項目的文件該怎麼辦?假如不加入任何限制,這種事情早晚會發生,我想這多是lerna框架與生俱來的的痛點。

不幸的是,在lerna框架下,gitlab或github這類第三方代碼託管平臺,自己的權限管理功能沒法解決這問題。但好在有其餘工具的幫助能夠緩解這種痛,我嘗試用來約束開源貢獻者提交PR規範的工具dangerjs來完成權限分隔,利用的信息就是當前gitlab帳號的用戶名,看起來效果還不錯。

能夠看到,此工具會在合併MR時,判斷出我gitlab帳號沒有權限修改rntest子項目內的文件,從而禁止合併此MR,並將這些信息自動添加到MR的評論裏。固然,腳本判斷是本身寫的僅用做演示,邏輯比較簡陋,腳本代碼以下:

關於dangerjs的部分我會另寫一篇文章詳細介紹。

結語

大前端項目將會是前端發展的趨勢,如何更好的管理大前端項目是每一位前端開發躲不開的課題。lerna框架經過合而爲一的理念提供了一種解決方案,經過揚長避短,咱們能夠發揮出lerna的最大效用。假如你尚未用過,也許,下一個項目就能夠試試看。

相關資料

yarn的workspaces

lerna的github地址

文章中的demo

相關文章
相關標籤/搜索