《Node.js在CLI下的工程化體系實踐》成都OSC源創會分享總結

cpselvis發表於2019-02-26

背景: 隨著開發團隊規模不斷髮展壯大,在人員增加的同時也帶來了協作成本的增加,業務專案越來越多,型別也各不相同。常見的型別有元件類、活動類、基於React+redux的業務專案、RN專案、Node.js專案等等。如果想要對每個專案進行一些規範的約束比如Git提交規範、Javascript規範簡直難於登天。所有的這些,只因為缺少一個好用的工程化工具。從專案建立、開發、構建、程式碼規範檢查到最終專案上線,通過CLI可以提升效率,同時保障開發規範的實施。

Node.js實現CLI的基本原理

關鍵點在於package.json裡面的bin欄位。模組全域性安裝,對於類unix系統,在/usr/local/bin目錄建立軟連結;對於windows系統,在C:\Users\username\AppData\Roaming\npm目錄建立軟連結。
模組區域性安裝,會在專案內的./node_modules/.bin目錄建立軟連結。

現代化web工程的生命週期

隨著前端工程的不斷演進,一方面工程變得日趨複雜,同時對規範和質量的訴求在不斷增加。現代化web工程應該包含以下幾個階段:初始化、開發、構建、檢查、釋出。如下圖所示:

痛點1:專案拷貝

專案拷貝存在的問題顯而易見,大致有以下三個方面:

  • 容易出錯;一旦某個關鍵檔案拷貝丟失或者錯誤,很可能需要耗費半天到一天的時間排查環境問題。
  • 不同場景下對目錄結構要求不同;平時開發過程中,工程通常會分為運營活動、Hybrid業務、入口級別的專案(對效能和體驗有極致和苛刻的要求)。需要基於RN或者Node.js的首屏直出,還有常用的業務元件等的開發。
  • 新的Feature和BugFix難以同步;某個同學開發過程中增加的新方法或者解決的bug很難傳遞給其它同學並且沉澱成經驗積累下來。

社群裡面提供了完美的Yeoman解決方案,它是為了自動化專案的建立而生。Yeoman建立專案包括以下幾個階段:

  • initializing: 初始化一些狀態之類的,通常是和使用者輸入的 options 或者 arguments 打交道
  • prompting: 和使用者互動的時候(命令列問答之類的)呼叫
  • configuring: 儲存配置檔案(如 .babelrc 等)
  • writing: 生成模板檔案
  • install: 安裝依賴
  • end: 結束部分,初始程式碼自動提交

我們只需要繼承Yeoman的Generator類做模板定製化,基於Yeoman的腳手架設計思路應該如下圖所示:

首先,開發者會和CLI進行互動,開發者會告訴CLI需要建立哪一種型別的專案,CLI收到命令後。從本地已經安裝的Yeoman腳手架裡面選擇某種型別的模板。然後,CLI會呼叫Gitlab API在遠端建立倉庫並且授予開發者master許可權。接下來,會根據實際業務場景需要,自動化申請一些打點資訊,常見的如離線包id,監控告警id等等。之後,在本地目錄生成程式碼並且安裝專案依賴的npm包,最後將本次初始化生成的所有程式碼自動提交到遠端Git倉庫。

痛點2:運營配置頻繁修改

基於React+redux元件化開發方式中,一個頁面或者webapp是由多個容器元件拼裝後渲染而成。

某個元件通常是由:模板、cgi資料和事件組成。理想情況下,開發和產品和平共處,你可以把一個元件寫成下面這個樣子,比如規則元件:

render() {
    return (
        <div className="lottery-rule">
            <div className="section">
                <h3>活動時間:</h3>
                <p>9月14日~9月30日</p>
            </div>
            <div className="section">
                <h3>活動規則:</h3>
                <p>1、活動期間,在NOW app上錄製小視訊,上傳成功後即可參賽。</p>
                <p>2、根據參賽小視訊獲得的點贊數進行排行。</p>
                <p>3、按照城市評選,分別評選“明日之子”(僅限男性參加)和”閃亮女神“僅限女性參加。</p>
            </div>
        </div>
    );
}複製程式碼

咋一看,上面的寫法沒什麼問題。實際確很可能是7、8次的文案修改,甚至對外入口開放後仍然要修改文案或者圖片等靜態資料。然後,你需要走程式碼釋出流程。

更好的解決思路是:在開發某個業務元件之前,結合以往的經驗,分析哪些靜態資料很可能是需要高頻次的修改。將這些高頻次修改的靜態資料抽離出來,對於萬年不變的資料則沒有必要抽出來。那麼,如何將靜態資料動態化呢?

答案是: Schema First , 開發元件之前先設計Schema,通過schema生成一個form表單,達到靜態資料和模板分離。如果使用React開發,可以基於react-jsonschema-form定製。靜態資料和模板分離之後應該如下圖:

痛點3:缺少協作規範

此處以Git commit規範為例子進行相關改進介紹。

良好的Git commit規範有以下優勢:

  • 加快Review的流程
  • 根據Commit後設資料生成Changelog
  • 後續維護者可以知道feature被新增的原因

此處採用Google angular專案的提交作為參考,整理出Git commit的解決方案:

具體的提交格式要求如下:

<type>(<scope>): <subject>
<BLANK LINE>
<body>
<BLANK LINE>
<footer>複製程式碼

對格式的說明如下:

  • type代表某次提交的型別,比如是修復一個bug還是增加一個新的feature。所有的type型別如下:
  • feat: 新增feature
  • fix: 修復bug
  • docs: 僅僅修改了文件,比如README, CHANGELOG, CONTRIBUTE等等
  • style: 僅僅修改了空格、格式縮排、都好等等,不改變程式碼邏輯
  • refactor: 程式碼重構,沒有加新功能或者修復bug
  • perf: 優化相關,比如提升效能、體驗
  • test: 測試用例,包括單元測試、整合測試等
  • chore: 改變構建流程、或者增加依賴庫、工具等
  • revert: 回滾到上一個版本

一鍵生成Changelog版本日誌:

痛點4: 缺少程式碼規範

一次血淋淋的生產環境事故:2017年4月13日,騰訊高階工程師小聖在做充值業務時,修改了蘋果iap支付配置,將JSON配置增加了重複的key。程式碼釋出後,有小部分使用了vivo手機的使用者反饋充值頁面白屏,無法在Now app內進行充值。最後問題定位是:vivo手機使用了系統自帶的webview而沒有使用X5核心,解析JSON時遇到重複key報錯,導致頁面白屏。

分析:現代化的瀏覽器對於JSON裡面的重複key會做相容處理,但是某些老舊的瀏覽器核心並不會,比如此處的vivo手機,導致程式碼直接出錯。那麼,如何避免類似問題再次出現呢?

此處不得不提及ESLint,ESLint於2013年6月推出最新版本v4.6.0,是一款適用於Javascript和JSX的程式碼規範檢查工具,相比JSLint和JSHint而言,它更加靈活,支援自定義配置、外掛擴充套件和配置錯誤級別。雖然接入ESLint會給團隊的同學增加不少程式碼修改的成本,但是從長遠來看,收益肯定是大於付出的。

Javascript規範制定的原則:

  • 不重複造輪子,基於eslint:recommend 配置並改進
  • 能夠幫助發現程式碼錯誤的規則,全部開啟
  • 配置不應該依賴於某個具體專案,而應儘可能的合理
  • 幫助保持團隊的程式碼風格統一,而不是限制開發體驗
  • 有對應的解釋文件

為了更好的定製和維護Javascript規範,我們建立了eslint的shareable config。一方面,我們覺得eslint:recommend 裡面的部分配置定義的錯誤級別過於嚴格,比如程式碼裡面出現了console會導致校驗錯誤,另一方面,它沒有包含ESLint的最佳實踐和其它規則。我們定義的部分規則解釋如下:

規則名稱 錯誤級別 說明
for-direction error for 迴圈的方向要求必須正確
getter-return error getter必須有返回值,並且禁止返回值為undefined, 比如 return;
no-await-in-loop off 允許在迴圈裡面使用await
no-console off 允許在程式碼裡面使用console
no-prototype-builtins warn 直接呼叫物件原型鏈上的方法
valid-jsdoc off 函式註釋一定要遵守jsdoc規則
no-template-curly-in-string warn 在字串裡面出現{和}進行警告
accessor-pairs warn getter和setter沒有成對出現時給出警告
array-callback-return error 對於資料相關操作函式比如reduce, map, filter等,callback必須有return
block-scoped-var error 把var關鍵字看成塊級作用域,防止變數提升導致的bug
class-methods-use-this error 要求在Class裡面合理使用this,如果某個方法沒有使用this,則應該申明為靜態方法
complexity off 關閉程式碼複雜度限制
default-case error switch case語句裡面一定需要default分支

ESLint的執行可以接入到PUSH hook裡面,步驟如下:

#1, 安裝husky
$ npm install husky --save-dev

#2, 整合進npm script
{
  "scripts": {
    "precommit": "validate-commit-msg",
    "prepush": "eslint src ./.eslintrc.js --ext '.js,.jsx'"
  }
}複製程式碼

CLI設計

CLI的作用是將工程開發過程中遇到的一系列痛點問題連線起來,提升開發效率,同時保障規範的實施。

外掛設計

外掛實現原理

這裡有一個非常巧妙的設計,通過使用node提供的module和vm模組,可以通注入feflow全域性變數來訪問到cli的例項。從而能夠訪問cli上的各種屬性,比如config, log和一些helper等。

 loadPlugin(path, callback) {
    const self = this;

    return fs.readFile(path).then((script) => {

      const module = new Module(path);
      module.filename = path;
      module.paths = Module._nodeModulePaths(path);

      function require(path) {
          return module.require(path);
      }

      require.resolve = function(request) {
          return Module._resolveFilename(request, module);
      };

      require.main = process.mainModule;
      require.extensions = Module._extensions;
      require.cache = Module._cache;

      // Inject feflow variable
      script = '(function(exports, require, module, __filename, __dirname, feflow){' +
          script + '});';

      const fn = vm.runInThisContext(script, path);

      return fn(module.exports, require, module, path, pathFn.dirname(path), self);
      }).asCallback(callback);
  }複製程式碼

命令註冊:

命令需要以feflow.cmd.register進行註冊,比如:

feflow.cmd.register('deps', 'Config ivweb dependencies', function(args) {
    console.log(args); 
    // Plugin logic here.
});複製程式碼

說明:

  • register有3個引數,第一個是子命令名稱,第二個是命令描述說明資訊,第三個是對應的子命令執行邏輯函式。
  • feflow會將命令列引數args解析成Object物件,傳遞給外掛處理函式

配置

可以通過feflow.version獲取當前feflow的版本,feflow.baseDir 獲取feflow跟目錄(在使用者目錄下的.feflow),通過feflow.pluginDir 獲取外掛目錄

日誌

通過feflow.log來進行相關命令列日誌輸出

const log = feflow.log;
log.info()    // 提示日誌,控制檯中顯示綠色
log.debug()   // 除錯日誌,  命令列增加--debug可以開啟,控制檯中顯示灰色
log.warn()    // 警告日誌,控制檯中顯示黃色背景
log.error()   // 錯誤日誌,控制檯中顯示紅色
log.fatal()   // 致命錯誤日誌,,控制檯中顯示紅色複製程式碼

最後

感謝OSC源創會提供的交流機會,能和廣大開發者分享和交流學習。NOW直播IVWEB團隊的工程化解決方案如下:

附件:本次分享PPT

相關文章