《Node.js在CLI下的工程化體系實(shí)踐》成都OSC源創(chuàng)會(huì)分享總結(jié)

背景: 隨著開(kāi)發(fā)團(tuán)隊(duì)規(guī)模不斷發(fā)展壯大咬清,在人員增加的同時(shí)也帶來(lái)了協(xié)作成本的增加捐凭,業(yè)務(wù)項(xiàng)目越來(lái)越多记舆,類型也各不相同鸽捻。常見(jiàn)的類型有組件類、活動(dòng)類泽腮、基于React+redux的業(yè)務(wù)項(xiàng)目御蒲、RN項(xiàng)目、Node.js項(xiàng)目等等诊赊。如果想要對(duì)每個(gè)項(xiàng)目進(jìn)行一些規(guī)范的約束比如Git提交規(guī)范厚满、Javascript規(guī)范簡(jiǎn)直難于登天。所有的這些碧磅,只因?yàn)槿鄙僖粋€(gè)好用的工程化工具碘箍。從項(xiàng)目創(chuàng)建、開(kāi)發(fā)鲸郊、構(gòu)建丰榴、代碼規(guī)范檢查到最終項(xiàng)目上線,通過(guò)CLI可以提升效率秆撮,同時(shí)保障開(kāi)發(fā)規(guī)范的實(shí)施四濒。

Node.js實(shí)現(xiàn)CLI的基本原理

關(guān)鍵點(diǎn)在于package.json里面的bin字段。模塊全局安裝,對(duì)于類unix系統(tǒng)峻黍,在/usr/local/bin目錄創(chuàng)建軟鏈接复隆;對(duì)于windows系統(tǒng),在C:\Users\username\AppData\Roaming\npm目錄創(chuàng)建軟鏈接姆涩。
模塊局部安裝挽拂,會(huì)在項(xiàng)目?jī)?nèi)的./node_modules/.bin目錄創(chuàng)建軟鏈接。

現(xiàn)代化web工程的生命周期

隨著前端工程的不斷演進(jìn)骨饿,一方面工程變得日趨復(fù)雜亏栈,同時(shí)對(duì)規(guī)范和質(zhì)量的訴求在不斷增加。現(xiàn)代化web工程應(yīng)該包含以下幾個(gè)階段:初始化宏赘、開(kāi)發(fā)绒北、構(gòu)建、檢查察署、發(fā)布闷游。如下圖所示:

痛點(diǎn)1:項(xiàng)目拷貝

項(xiàng)目拷貝存在的問(wèn)題顯而易見(jiàn),大致有以下三個(gè)方面:

  • 容易出錯(cuò)贴汪;一旦某個(gè)關(guān)鍵文件拷貝丟失或者錯(cuò)誤脐往,很可能需要耗費(fèi)半天到一天的時(shí)間排查環(huán)境問(wèn)題。
  • 不同場(chǎng)景下對(duì)目錄結(jié)構(gòu)要求不同扳埂;平時(shí)開(kāi)發(fā)過(guò)程中业簿,工程通常會(huì)分為運(yùn)營(yíng)活動(dòng)、Hybrid業(yè)務(wù)阳懂、入口級(jí)別的項(xiàng)目(對(duì)性能和體驗(yàn)有極致和苛刻的要求)梅尤。需要基于RN或者Node.js的首屏直出,還有常用的業(yè)務(wù)組件等的開(kāi)發(fā)岩调。
  • 新的Feature和BugFix難以同步巷燥;某個(gè)同學(xué)開(kāi)發(fā)過(guò)程中增加的新方法或者解決的bug很難傳遞給其它同學(xué)并且沉淀成經(jīng)驗(yàn)積累下來(lái)。

社區(qū)里面提供了完美的Yeoman解決方案号枕,它是為了自動(dòng)化項(xiàng)目的創(chuàng)建而生矾湃。Yeoman創(chuàng)建項(xiàng)目包括以下幾個(gè)階段:

  • initializing: 初始化一些狀態(tài)之類的,通常是和用戶輸入的 options 或者 arguments 打交道
  • prompting: 和用戶交互的時(shí)候(命令行問(wèn)答之類的)調(diào)用
  • configuring: 保存配置文件(如 .babelrc 等)
  • writing: 生成模板文件
  • install: 安裝依賴
  • end: 結(jié)束部分堕澄,初始代碼自動(dòng)提交

我們只需要繼承Yeoman的Generator類做模板定制化邀跃,基于Yeoman的腳手架設(shè)計(jì)思路應(yīng)該如下圖所示:


首先,開(kāi)發(fā)者會(huì)和CLI進(jìn)行交互蛙紫,開(kāi)發(fā)者會(huì)告訴CLI需要?jiǎng)?chuàng)建哪一種類型的項(xiàng)目拍屑,CLI收到命令后。從本地已經(jīng)安裝的Yeoman腳手架里面選擇某種類型的模板坑傅。然后僵驰,CLI會(huì)調(diào)用Gitlab API在遠(yuǎn)程創(chuàng)建倉(cāng)庫(kù)并且授予開(kāi)發(fā)者master權(quán)限。接下來(lái),會(huì)根據(jù)實(shí)際業(yè)務(wù)場(chǎng)景需要蒜茴,自動(dòng)化申請(qǐng)一些打點(diǎn)信息星爪,常見(jiàn)的如離線包id,監(jiān)控告警id等等粉私。之后顽腾,在本地目錄生成代碼并且安裝項(xiàng)目依賴的npm包,最后將本次初始化生成的所有代碼自動(dòng)提交到遠(yuǎn)程Git倉(cāng)庫(kù)诺核。

痛點(diǎn)2:運(yùn)營(yíng)配置頻繁修改

基于React+redux組件化開(kāi)發(fā)方式中抄肖,一個(gè)頁(yè)面或者webapp是由多個(gè)容器組件拼裝后渲染而成。


某個(gè)組件通常是由:模板窖杀、cgi數(shù)據(jù)和事件組成漓摩。理想情況下,開(kāi)發(fā)和產(chǎn)品和平共處入客,你可以把一個(gè)組件寫(xiě)成下面這個(gè)樣子管毙,比如規(guī)則組件:

render() {
    return (
        <div className="lottery-rule">
            <div className="section">
                <h3>活動(dòng)時(shí)間:</h3>
                <p>9月14日~9月30日</p>
            </div>
            <div className="section">
                <h3>活動(dòng)規(guī)則:</h3>
                <p>1、活動(dòng)期間桌硫,在NOW app上錄制小視頻夭咬,上傳成功后即可參賽。</p>
                <p>2鞍泉、根據(jù)參賽小視頻獲得的點(diǎn)贊數(shù)進(jìn)行排行。</p>
                <p>3肮帐、按照城市評(píng)選咖驮,分別評(píng)選“明日之子”(僅限男性參加)和”閃亮女神“僅限女性參加。</p>
            </div>
        </div>
    );
}

咋一看训枢,上面的寫(xiě)法沒(méi)什么問(wèn)題托修。實(shí)際確很可能是7、8次的文案修改恒界,甚至對(duì)外入口開(kāi)放后仍然要修改文案或者圖片等靜態(tài)數(shù)據(jù)睦刃。然后,你需要走代碼發(fā)布流程十酣。

更好的解決思路是:在開(kāi)發(fā)某個(gè)業(yè)務(wù)組件之前涩拙,結(jié)合以往的經(jīng)驗(yàn),分析哪些靜態(tài)數(shù)據(jù)很可能是需要高頻次的修改耸采。將這些高頻次修改的靜態(tài)數(shù)據(jù)抽離出來(lái)兴泥,對(duì)于萬(wàn)年不變的數(shù)據(jù)則沒(méi)有必要抽出來(lái)。那么虾宇,如何將靜態(tài)數(shù)據(jù)動(dòng)態(tài)化呢搓彻?

答案是: ** Schema First **, 開(kāi)發(fā)組件之前先設(shè)計(jì)Schema,通過(guò)schema生成一個(gè)form表單旭贬,達(dá)到靜態(tài)數(shù)據(jù)和模板分離怔接。如果使用React開(kāi)發(fā),可以基于react-jsonschema-form定制稀轨。靜態(tài)數(shù)據(jù)和模板分離之后應(yīng)該如下圖:

痛點(diǎn)3:缺少協(xié)作規(guī)范

此處以Git commit規(guī)范為例子進(jìn)行相關(guān)改進(jìn)介紹扼脐。

良好的Git commit規(guī)范有以下優(yōu)勢(shì):

  • 加快Review的流程
  • 根據(jù)Commit元數(shù)據(jù)生成Changelog
  • 后續(xù)維護(hù)者可以知道feature被添加的原因

此處采用Google angular項(xiàng)目的提交作為參考,整理出Git commit的解決方案:


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

<type>(<scope>): <subject>
<BLANK LINE>
<body>
<BLANK LINE>
<footer>

對(duì)格式的說(shuō)明如下:

  • type代表某次提交的類型靶端,比如是修復(fù)一個(gè)bug還是增加一個(gè)新的feature谎势。所有的type類型如下:
  • feat: 新增feature
  • fix: 修復(fù)bug
  • docs: 僅僅修改了文檔,比如README, CHANGELOG, CONTRIBUTE等等
  • style: 僅僅修改了空格杨名、格式縮進(jìn)脏榆、都好等等,不改變代碼邏輯
  • refactor: 代碼重構(gòu)台谍,沒(méi)有加新功能或者修復(fù)bug
  • perf: 優(yōu)化相關(guān)须喂,比如提升性能、體驗(yàn)
  • test: 測(cè)試用例趁蕊,包括單元測(cè)試坞生、集成測(cè)試等
  • chore: 改變構(gòu)建流程、或者增加依賴庫(kù)掷伙、工具等
  • revert: 回滾到上一個(gè)版本

一鍵生成Changelog版本日志:


痛點(diǎn)4: 缺少代碼規(guī)范

一次血淋淋的生產(chǎn)環(huán)境事故:2017年4月13日是己,騰訊高級(jí)工程師小圣在做充值業(yè)務(wù)時(shí),修改了蘋(píng)果iap支付配置任柜,將JSON配置增加了重復(fù)的key卒废。代碼發(fā)布后,有小部分使用了vivo手機(jī)的用戶反饋充值頁(yè)面白屏宙地,無(wú)法在Now app內(nèi)進(jìn)行充值摔认。最后問(wèn)題定位是:vivo手機(jī)使用了系統(tǒng)自帶的webview而沒(méi)有使用X5內(nèi)核,解析JSON時(shí)遇到重復(fù)key報(bào)錯(cuò)宅粥,導(dǎo)致頁(yè)面白屏参袱。

分析:現(xiàn)代化的瀏覽器對(duì)于JSON里面的重復(fù)key會(huì)做兼容處理,但是某些老舊的瀏覽器內(nèi)核并不會(huì)秽梅,比如此處的vivo手機(jī)抹蚀,導(dǎo)致代碼直接出錯(cuò)。那么企垦,如何避免類似問(wèn)題再次出現(xiàn)呢况鸣?

此處不得不提及ESLint,ESLint于2013年6月推出最新版本v4.6.0竹观,是一款適用于Javascript和JSX的代碼規(guī)范檢查工具镐捧,相比JSLint和JSHint而言潜索,它更加靈活,支持自定義配置懂酱、插件擴(kuò)展和配置錯(cuò)誤級(jí)別竹习。雖然接入ESLint會(huì)給團(tuán)隊(duì)的同學(xué)增加不少代碼修改的成本,但是從長(zhǎng)遠(yuǎn)來(lái)看列牺,收益肯定是大于付出的整陌。

Javascript規(guī)范制定的原則:

  • 不重復(fù)造輪子,基于eslint:recommend 配置并改進(jìn)
  • 能夠幫助發(fā)現(xiàn)代碼錯(cuò)誤的規(guī)則瞎领,全部開(kāi)啟
  • 配置不應(yīng)該依賴于某個(gè)具體項(xiàng)目泌辫,而應(yīng)盡可能的合理
  • 幫助保持團(tuán)隊(duì)的代碼風(fēng)格統(tǒng)一,而不是限制開(kāi)發(fā)體驗(yàn)
  • 有對(duì)應(yīng)的解釋文檔

為了更好的定制和維護(hù)Javascript規(guī)范九默,我們創(chuàng)建了eslint的shareable config震放。一方面,我們覺(jué)得eslint:recommend 里面的部分配置定義的錯(cuò)誤級(jí)別過(guò)于嚴(yán)格驼修,比如代碼里面出現(xiàn)了console會(huì)導(dǎo)致校驗(yàn)錯(cuò)誤殿遂,另一方面,它沒(méi)有包含ESLint的最佳實(shí)踐和其它規(guī)則乙各。我們定義的部分規(guī)則解釋如下:

規(guī)則名稱 錯(cuò)誤級(jí)別 說(shuō)明
for-direction error for 循環(huán)的方向要求必須正確
getter-return error getter必須有返回值墨礁,并且禁止返回值為undefined, 比如 return;
no-await-in-loop off 允許在循環(huán)里面使用await
no-console off 允許在代碼里面使用console
no-prototype-builtins warn 直接調(diào)用對(duì)象原型鏈上的方法
valid-jsdoc off 函數(shù)注釋一定要遵守jsdoc規(guī)則
no-template-curly-in-string warn 在字符串里面出現(xiàn){和}進(jìn)行警告
accessor-pairs warn getter和setter沒(méi)有成對(duì)出現(xiàn)時(shí)給出警告
array-callback-return error 對(duì)于數(shù)據(jù)相關(guān)操作函數(shù)比如reduce, map, filter等,callback必須有return
block-scoped-var error 把var關(guān)鍵字看成塊級(jí)作用域耳峦,防止變量提升導(dǎo)致的bug
class-methods-use-this error 要求在Class里面合理使用this恩静,如果某個(gè)方法沒(méi)有使用this,則應(yīng)該申明為靜態(tài)方法
complexity off 關(guān)閉代碼復(fù)雜度限制
default-case error switch case語(yǔ)句里面一定需要default分支

ESLint的執(zhí)行可以接入到PUSH hook里面,步驟如下:

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

#2, 集成進(jìn)npm script
{
  "scripts": {
    "precommit": "validate-commit-msg",
    "prepush": "eslint src ./.eslintrc.js --ext '.js,.jsx'"
  }
}

CLI設(shè)計(jì)

CLI的作用是將工程開(kāi)發(fā)過(guò)程中遇到的一系列痛點(diǎn)問(wèn)題連接起來(lái)蹲坷,提升開(kāi)發(fā)效率驶乾,同時(shí)保障規(guī)范的實(shí)施。

插件設(shè)計(jì)

插件實(shí)現(xiàn)原理

這里有一個(gè)非常巧妙的設(shè)計(jì)冠句,通過(guò)使用node提供的module和vm模塊轻掩,可以通注入feflow全局變量來(lái)訪問(wèn)到cli的實(shí)例幸乒。從而能夠訪問(wèn)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);
  }

命令注冊(cè):

命令需要以feflow.cmd.register進(jìn)行注冊(cè)罕扎,比如:

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

說(shuō)明:

  • register有3個(gè)參數(shù)聚唐,第一個(gè)是子命令名稱,第二個(gè)是命令描述說(shuō)明信息腔召,第三個(gè)是對(duì)應(yīng)的子命令執(zhí)行邏輯函數(shù)杆查。
  • feflow會(huì)將命令行參數(shù)args解析成Object對(duì)象,傳遞給插件處理函數(shù)

配置

可以通過(guò)feflow.version獲取當(dāng)前feflow的版本臀蛛,feflow.baseDir 獲取feflow跟目錄(在用戶目錄下的.feflow)亲桦,通過(guò)feflow.pluginDir 獲取插件目錄

日志

通過(guò)feflow.log來(lái)進(jìn)行相關(guān)命令行日志輸出

const log = feflow.log;
log.info()    // 提示日志崖蜜,控制臺(tái)中顯示綠色
log.debug()   // 調(diào)試日志,  命令行增加--debug可以開(kāi)啟,控制臺(tái)中顯示灰色
log.warn()    // 警告日志客峭,控制臺(tái)中顯示黃色背景
log.error()   // 錯(cuò)誤日志豫领,控制臺(tái)中顯示紅色
log.fatal()   // 致命錯(cuò)誤日志,舔琅,控制臺(tái)中顯示紅色

最后

感謝OSC源創(chuàng)會(huì)提供的交流機(jī)會(huì)等恐,能和廣大開(kāi)發(fā)者分享和交流學(xué)習(xí)。NOW直播IVWEB團(tuán)隊(duì)的工程化解決方案如下:

附件:本次分享PPT

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末备蚓,一起剝皮案震驚了整個(gè)濱河市课蔬,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌郊尝,老刑警劉巖二跋,帶你破解...
    沈念sama閱讀 212,080評(píng)論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異虚循,居然都是意外死亡同欠,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,422評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門(mén)横缔,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)铺遂,“玉大人,你說(shuō)我怎么就攤上這事茎刚〗笕瘢” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 157,630評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵膛锭,是天一觀的道長(zhǎng)粮坞。 經(jīng)常有香客問(wèn)我,道長(zhǎng)初狰,這世上最難降的妖魔是什么莫杈? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 56,554評(píng)論 1 284
  • 正文 為了忘掉前任,我火速辦了婚禮奢入,結(jié)果婚禮上筝闹,老公的妹妹穿的比我還像新娘。我一直安慰自己腥光,他們只是感情好关顷,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,662評(píng)論 6 386
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著武福,像睡著了一般议双。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上捉片,一...
    開(kāi)封第一講書(shū)人閱讀 49,856評(píng)論 1 290
  • 那天平痰,我揣著相機(jī)與錄音汞舱,去河邊找鬼。 笑死宗雇,一個(gè)胖子當(dāng)著我的面吹牛兵拢,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播逾礁,決...
    沈念sama閱讀 39,014評(píng)論 3 408
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼说铃,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了嘹履?” 一聲冷哼從身側(cè)響起腻扇,我...
    開(kāi)封第一講書(shū)人閱讀 37,752評(píng)論 0 268
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎砾嫉,沒(méi)想到半個(gè)月后幼苛,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,212評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡焕刮,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,541評(píng)論 2 327
  • 正文 我和宋清朗相戀三年舶沿,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片配并。...
    茶點(diǎn)故事閱讀 38,687評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡括荡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出溉旋,到底是詐尸還是另有隱情畸冲,我是刑警寧澤,帶...
    沈念sama閱讀 34,347評(píng)論 4 331
  • 正文 年R本政府宣布观腊,位于F島的核電站邑闲,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏梧油。R本人自食惡果不足惜苫耸,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,973評(píng)論 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望儡陨。 院中可真熱鬧褪子,春花似錦、人聲如沸迄委。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,777評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)叙身。三九已至,卻和暖如春硫狞,著一層夾襖步出監(jiān)牢的瞬間信轿,已是汗流浹背晃痴。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,006評(píng)論 1 266
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留财忽,地道東北人倘核。 一個(gè)月前我還...
    沈念sama閱讀 46,406評(píng)論 2 360
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像即彪,于是被迫代替她去往敵國(guó)和親紧唱。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,576評(píng)論 2 349

推薦閱讀更多精彩內(nèi)容

  • Android 自定義View的各種姿勢(shì)1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 171,806評(píng)論 25 707
  • Spring Boot 參考指南 介紹 轉(zhuǎn)載自:https://www.gitbook.com/book/qbgb...
    毛宇鵬閱讀 46,773評(píng)論 6 342
  • 圖片發(fā)自簡(jiǎn)書(shū)App 本書(shū)作者試圖通過(guò)“剩女問(wèn)題”這個(gè)切口了解整個(gè)中國(guó)城市社會(huì)的性別和政治狀況隶校, 深入地了解“剩女時(shí)...
    何舒卉閱讀 901評(píng)論 0 3
  • 上一章:淚城誕生 (1)臨危受命 智慧老人去了何處漏益,沒(méi)有人知道,也無(wú)暇顧及深胳。 自從那日離奇事件起绰疤,整個(gè)淚城似乎都被...
    野兔丫閱讀 401評(píng)論 7 4
  • 一顆大石掉入水中 咕咚——濺起一朵浪花 一顆小石子掉入水中 嘀嗒——泛起一圈漣漪 然而 有一顆石頭落下去啦 看 看...
    賢談律事閱讀 201評(píng)論 0 0