背景: 隨著開(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ì)的工程化解決方案如下:
- Github主頁(yè):https://github.com/cpselvis/feflow-cli
- 碼云主頁(yè):https://gitee.com/cpselvis/feflow-cli
附件:本次分享PPT