NEJ Build太慢怎麼辦?試試MOOC NEJ吧,只需兩步,提升70%構建效能!

有道技術團隊發表於2021-11-04

NEJ Build太慢怎麼辦?試試MOOC NEJ吧,只需兩步,提升70%構建效能!

由於歷史包袱,中國大學MOOC(簡稱中M)的主站工程生產構建時間大約在21分鐘,構建採用NEJ Build,由於NEJ當前已無人維護且在部門內應用較多,因此中M通過fork原工程在保留NEJ原有功能的前提下,將NEJ的核心打包流程進行改造升級,並提出一套通用解決方案即MOOC NEJ。MOOC NEJ已於今年年初上線驗證,構建時長縮短至6分鐘,提升70%構建效能,經過8個月的線上穩定執行,暫未發現引遷移引起的問題。

一、歷史背景

中國大學MOOC(以下簡稱中M)和許多網易前端er一樣在過去幾年搭建工程使用的框架為RegularJS與NEJ,同時在打包上採用NEJ的工具集toolkit2即NEJ Build(https://github.com/genify/toolkit2

在專案的開始總是愉快的,那時候開發維護NEJ的人還在(甚至還可以提一些feature),業務專案的人也還在,程式碼量總是清清爽爽,構建的時光總是“咻”一下就過去了。
在這裡插入圖片描述
然而!時間過去了三五年,當我接手到中M主站這個專案的時候,它的構建時間已經達到了將近21分鐘
在這裡插入圖片描述
平時等就等唄,最多在後端大佬部署的時候提前構建好等著,要命的是上完線已經10點了,這時候測試大佬來報:後端沒問題了,前端這裡有個問題修一下。

然後,大概就是修復5分鐘,打包驗證半小時。。。。。同時保佑不要再出(deng)一(ban)個(ge)問(xiao)題(shi)。

在這裡插入圖片描述

二、究竟為啥這麼慢?

速度慢的原因有兩方面:

1. 中M工程架構:
由於歷史原因,中M的主站將web和mobile端以及一併放在front-main專案下,同時,載入了兩個看不出來有什麼區別的超大lib,導致需要打包的檔案非常多,東西多了自然就蚌埠住了。
在這裡插入圖片描述
2. NEJ合併策略
toolkit有一個合併策略的引數來決定一個檔案引用計數超過多少次將合併入core.js,當這個引數設定的越大,core.js的size將越小,構建時間就越長。我們之前為了快速提高開發效率,在測試環境將該引數調至6,打包時間約11分鐘,而預發和線上該引數在13,打包時間約21分鐘。(畢竟我們不能因為想要打包快,就粗暴的使生產環境的資源變大?)

三、NEJ的合併策略為何影響打包速度【核心原理】

由於NEJ已早早無人維護,但萬幸它是開源的,我們們雖然找不到作者,但是可以通過對toolkit2的原始碼閱讀來找到答案,我大致梳理了一下NEJ打包的流程。
流程圖

  1. toolkit2全過程使用同步打包,每一步的處理結果都通過one by one的接力形式來傳遞。
  2. toolkit2打包的本質是通過對合並策略(就是二.2提到的引數)等引數將每個檔案的程式碼轉成抽象語法樹(AST),再對AST通過UgilifyJS進行壓縮混淆。

看起來這個流程沒什麼問題,因為AST和UgilifyJS在現在也都是很主流的操作(如webpack也是使用UgilifyJS),對前端來講是再熟悉不過了。

那麼為什麼會導致打包慢呢?直到我看到了AST的生成過程:

  1. 先將所有檔案通過concat生成一個大AST(原始碼的lib/adapter/script.js的_mergeCodeAndToAST
    在這裡插入圖片描述
  2. 對且僅對這一個AST進行Uglify壓縮混淆(原始碼的lib/adapter/script.js的parse)在這裡插入圖片描述

結合中M主站的歷史架構,當我列印出這一個AST時,
單主站web端的Core.js就大概由400個小AST形成。。。。
然後它就開始壓縮。。。。
然後它就卡住了。。。。住了。。。。了。。。。在這裡插入圖片描述

四、解決方案考量

針對當前我們對自身工程和NEJ的瞭解,我們大概有以下幾個思路去對打包時間進行優化:

【針對中M主站工程】

  1. 老生常談的拆分工程&重構遷移:拆分需要對業務足夠了解,才能對其做領域拆分。重構代價太大。同時功能迴歸點太多。目前一部分功能由於迭代需要已用React+Webpack做了替換。
  2. webpack替換nej:需要相容regular和nej模組,且對老專案內部也要替換webpack所需外掛,成本較大。

【針對NEJ工具集】

  1. Uglify2.0升級3.0:3.0有一個快速打包模式,但經過測試,3.0有部分不向下相容的api,nej有用到。(此處有踩坑經驗)
  2. 多程式打包:不改造打包產物,只變更打包流程,成本可控,上線後風險較小,如遇到緊急問題可快速回滾至nej build,風險可控

最終,我們選擇了給NEJ架上webpack同款多程式功能,打包需要什麼,我們就造什麼

五、實現架構

我們並不想多造一個輪子,來做一些顛覆性的改變,使效能得到提升的同時,寫的人也難受(考慮太多寫一些冗餘程式碼),使用的人也難受(遷移成本高)。
因此我們選擇將toolkit2 fork下來,將其所有的api保留、功能保留,不影響任何老功能的使用姿勢,只修改關鍵路徑,縮小改造和遷移成本。

最後生成的庫是:@edu/toolkit3
http://npm.hz.netease.com/package/@edu/toolkit3

我們的大致思路是這樣:
在這裡插入圖片描述

  1. 不對ast做concat操作,每個檔案單獨uglify
  2. 參考uglify-webpack-plugin外掛的多程式思路,將每個檔案作為task,併發打包
    (我又去看了一下uglify-webpack-plugin的原始碼,做了一張圖,畢竟知己知彼才能順利改造。)
    在這裡插入圖片描述
  3. 將同步流程改成非同步,完成所有task後回撥結果,呼叫後續操作

六、程式碼實現

fork目錄後,新增一個cluster資料夾用於存放多程式流程。

在這裡插入圖片描述

  1. minifiy.js:執行ugilify壓縮操作,生成壓縮混淆後的ast和string程式碼;
  2. TaskRunner.js:利用worker-farm來進行任務的分發、執行、計數、回撥等;
  3. worker.js: worker-farm必須要新建一個workerfile才能使用,這個workerfile用於承接;

再配合修改原檔案lib/adapter/script.js

  1. _mergeCodeAndToAST:取消concat以及this.ast的生成;
  2. parse:增加callback引數,根據filename、file、minify引數建立task,run each task,並執行callback;

配合修改原檔案lib/deploy.js

  1. _afterResPrepared:將最後的embed(將變數嵌入css、js、html)操作改成非同步,作為callback傳入,等待完成所有的壓縮後,執行最後輸出前的embed操作。
    (詳細程式碼可以前往倉庫檢視)

七、使用方式&遷移(安利)

說了這麼久,終於要開始遷移,還記得標題嗎,

只需兩步!無憂遷移!

不影響任何老功能的使用!

第一步:安裝mooc-nej(目前穩定版本在0.3.0,0.4.0尚屬beta版本,正在內測中)

npm install @edu/toolkit3 -g

第二步:使用它,甚至不用修改命令,只需將nej build替換為mooc-nej即可

 // 所有的nej build改成mooc-nej build即可
 mooc-nej build deploy/mobile/release.js 
 
 // 或者構建機安裝直接引用build檔案
<property name="mooc-nej-build.js" value="/home/appops/study/install/node_modules/@edu/toolkit3/bin/build.js"/>

<exec dir="." executable="${mooc-nej-build.js}"failonerror="true">
<arg line ="${ob_baseline.dir}/deploy/web/release.js"/>
</exec>

backup:如果你發現有問題,還可以快速回滾到nej build。

八、生產驗證

在這裡插入圖片描述
中M在年初就已經使用上了mooc-nej,目前穩定執行半年有餘,暫未發現任何由遷移引起的bug請大家放心食用

九、總結

大概從開始改造到結束改造用了去年12月的日常空餘時間,其間的心路歷程大致是:

?(打包好久555)

?(什麼東西卡這麼久)

?(哇發現了原因)

?(改造,衝!)

??????...(改完了掛了...又改完了又掛了...*n)

?(換個思路)

​?(忐忑驗證)

?(成功上線)

但是回頭看,這些踩坑都是值得的,我們終於不用再漫長的等待構建了!

-END-

相關文章