停不下來的前端,自動化流程
流程
關于流程,是從項目啟動到發布的過程。在前端通常我們都做些什么?
- 切圖,即從設計稿中獲取需要的素材,并不是所有前端開發都被要求切圖,也不是所有前端開發都會切圖,但請享受學習新知識的過程吧。
- 創建模版(html、jade、haml)、腳本(javascript、coffeescript)、樣式(css、less、sass、stylus)文件,搭建基礎的項目骨架。
- 文件(jade、coffeescript、less、sass…)編譯
- 執行測試用例
- 代碼檢測
- 移除調試代碼
- 靜態資源合并與優化
- 靜態資源通過hash計算指紋化
- 部署測試環境
- 灰度發布現網
工具化
每個流程中的過程單元,我們抽象為一個Task,即任務。把可重復規則的過程進行工具化,如把JavaScript代碼壓縮過程工具化,而UglifyJS是具體執行任務的工具,CSS代碼壓縮器CleanCSS是具體執行任務的工具。
工具文化幾乎是大平臺互聯網公司共有的特質,我們無法確定是工具文化驅動了Google、Facebook這類互聯網公司的快速發展,還是快速發展的需要使其在內推廣工具文化,但可以明確的是工具文化必不可少。在Facebook第二位中國籍工程師王淮的書中也提到提到:
當時招聘他進Facebook的總監黃易山,是對內部工具的最有力倡導者:
他極度建議,公司要把最好的人才放到工具開發那一塊,因為工具做好了,可以達到事半功倍的效果,所有人的效率都可以得到提高,而不僅僅是工程師。
在騰訊,工具文化雖沒有被明確指出,但大平臺公司對工具化的堅持是一致的:凡是被不斷重復的過程,將其工具化,綁定到自動化流程之中。技術產品也需要Don’t make me think
的方式來推廣最佳實踐。總而言之:依靠工具,而不是經驗。
自動化流程
任務工具化是自動化流程的基礎,我想你已經聽說過任務運行器Grunt。Grunt幫助開發者把任務單元建立連接,如代碼編譯Task執行完后執行檢測Task,檢測Task執行完后執行壓縮Task。雖然Grunt是基于Node.js平臺,但其定位是個通用任務管理器,通用往往意味著更高的學習與實施成本。專注于Web開發領域騰訊有Mod.js來實施前端自動化,通過Mod.js有效的簡化Web開發自動化流程實施成本。
實施Mod.js
Mod.js并不是簡單的任務運行器,其內置集成了Web前端開發常用的工具集,覆蓋了80%的前端使用場景,而另外的20%則可通過Mod.js的插件機制來擴展。
相遇
Mod.js:https://github.com/modjs/mod 可通過NPM來安裝最新的版本, 在你來到Node.js的編程世界時已同時附帶了NPM,當前Mod.js最新版本0. 4.x
要求Node.js要求>= 0.8.0
:
$ npm install modjs -g
-g
參數表示把Mod.js安裝到全局,如此mod
命令將會在system path
內,方便在任何一個目錄啟動Mod.js任務。
相識
Mod.js通過Modfile.js文件驅動任務執行,可以手動創建一個Modfile.js文件,也可以通過模版初始化一個Modfile.js文件:
$ mod init modfile
Modfile.js是一個Plain Node Module, 通過 Runner
對象來描述任務的具體執行過程:
// 暴露Runner對象module.exports = {}
如是異步配置,則可通過回調模式傳遞Runner對象:
module.exports = function(options, done){ setTimeout( function(){ // 回調Runner對象 var runner = {}; done(runner); }, 1000) }
借此一瞥通常Runner
對象的全貌:
module.exports = { version: ">=0.4.3", plugins: { pngcompressor : "mod-png-compressor", compress : "grunt-contrib-compress" }, tasks: { asset: "asset", online: "online_dist", offline: "offline_dist", offlinePackage: "{{offline}}/package.zip", rm: { online: { dest: "{{online}}" }, offline: { dest: "{{offline}}" } }, replace: { src: './js/**/*.js', search: "@VERSION", replace: require('./package.json').version }, build: { options: { src: ["*.html"] }, online: { dest: "{{online}}", rev: true }, offline: { dest: "{{offline}}", rev: false } }, cp: { options: { src: ["./img/**"] }, online: { dest: "{{online}}/img/", rev: true }, offline: { dest: "{{offline}}/img/", rev: false } }, pngcompressor: { src: "./img/**/*.png" }, compress: { dist: { options: { archive: '{{offlinePackage}}' }, // includes files in path files: [ { expand: true, cwd: '{{online}}/', src: ['*.html'], dest: 'qq.com/web' }, { expand: true, cwd: '{{online}}/img', src: ['**'], dest: 'cdn.qq.com/img' } ] } } }, targets: { default: ["rm", "pngcompressor", "replace", "build", "cp"], offline: ["default", "compress:dist"] } }
version
描述依賴的Mod.js版本plugins
描述依賴的插件,支持Mod.js插件與Grunt插件tasks
描述不同類別任務的執行targets
描述不同組合的目標,目標是需執行任務的集合
Mod.js的配置項追究極簡易懂,即使不懂JavaScript語法也能看懂配置與修改配置。
相知
在執行mod命令時,Mod.js會在當前目錄下查找是否存在Modfile.js文件。當找到Modfile.js文件時,Mod.js將讀取Modfile.js里的配置信息,如識別到有配置Mod.js插件,會自動安裝沒有安裝過的插件,插件不僅可以是發布到NPM的包,也可以是存在本地的自定義任務。
Mod.js加載插件的方式是通過Node的require機制,然后執行暴露的exports.run,這與Mod.js內置任務的完全一樣的機制。
在命令行下,通常執行mod時是需指定Modfile.js中某一特定目標,但當存在命名為default的目標或配置中只有一個獨立目標時,此時目標的指定是可選的,Mod.js會自動識別唯一的存在或default的目標:
targets: { dist: ["rm", "cp"] }
# 等價于 mod dist$ mod
配置有default目標的場景:
targets: { default: ["rm", "cp"], other: ["compress"] }
# 等價于 mod default$ mod
深入任務
任務是具體執行的類別,從配置示例開始闡述:
tasks: { min: { src: "./js/*.js" } }
以上配置了一個文件壓縮的min
類別任務,src
描述需要壓縮的文件:js
目錄的所有js文件。src
支持unix glob
語法來描述輸入文件集,其匹配規則如下:
匹配符:
- “*” 匹配0個或多個字符
- “?” 匹配單個字符
- “!” 匹配除此之外的字符
- “[]” 匹配指定范圍內的字符,如:[0-9]匹配數字0-9 [a-z]配置字母a-z
- “{x,y}” 匹配指定組中某項,如 a{d,c,b}e 匹配 ade ace abe
示例:
c/ab.min.js => c/ab.min.js *.js => a.js b.js c.js c/a*.js => c/a.js c/ab.js c/ab.min.js c/[a-z].js => c/a.js c/b.js c/c.js c/[!abe].js => c/c.js c/d.js c/a?.js => c/ab.js c/ac.js c/ab???.js => c/abdef.js c/abccc.js c/[bdz].js => c/b.js c/d.js c/z.js {a,b,c}.js => a.js b.js c.js a{b,c{d,e}}x{y,z}.js => abxy.js abxz.js acdxy.js acdxz.js acexy.js acexz.js
更多任務配置規則深入:https://github.com/modjs/mod/blob/master/doc/tutorial/configuring-tasks.md
如任務沒有配置dest
,默認在輸入文件同級目錄下輸出.min
后綴的文件:
uglifyjs Minifying ./js/unminify.js -> js/unminify.min.js
uglifyjs Original size: 1,393. Minified size: 449. Savings: 944 (210.24%)
內置的min
任務支持三種文件類別的壓縮,JavaScript、CSS與HTML,是對uglifyjs
、cleancss
與htmlminfier
任務的代理。min
通過識別文件后綴進行具體任務的分發。所以min
任務的src
選項需指定具體的后綴。通常每個不同類別的任務都支持src
與dest
,且Mod.js會結合實際項目中常見的場景,dest
往往都是可選的,如上min
任務默認的dest
是在當前目錄下輸出待.min
后綴的文件,同時后綴名是支持通常suffix
選項配置的。
每個內置任務支持的所有參數選項可通過Mod.js
的在線文檔查看:https://github.com/modjs/mod/tree/master/doc
同時有豐富的演示項目來輔助不同任務的配置:
- 合并JS文件
- 合并CSS文件,自動合并import文件
- AMD模塊文件編譯
- CMD模塊文件編譯
- 多頁面項目中AMD模塊編譯
- JS文件條件編譯
- CSS文件條件編譯
- HTML文件條件編譯
- JS文件壓縮
- CSS文件壓縮
- HTML文件壓縮
- 代碼移除,如alert、console
- 文件EOL移除
- 文件Tab移除
- 圖片DataURI
- 創建目錄
- 復制文件或目錄
- 規則替換,如版本號累加
不可或缺的插件機制
Mod.js支持2種生態的插件:Mod.js 與 Grunt。插件的配置同樣是在Runner對象下:
plugins: { // Mod.js NPM 插件 sprite: "mod-stylus", // Mod.js 本地插件 mytask: "./tasks/mytask" // Grunt NPM 插件 compress: "grunt-contrib-compress" }
同樣附上演示項目來輔助不同插件的配置:
如插件未安裝在項目目錄下或與Mod.js同級的全局目錄下,Mod.js會自動通過NPM安裝配置的插件。什么情況需要手動把插件安裝在全局下?在實際項目開發中我們往往會對同一項目拉不同的分支進行開發,他們依賴的插件版本是相同的,此時如果在不同分支都安裝一個冗余的插件版本項目是多余的,所以當你確定這是個插件是共享的,可以手動通過npm install -g mod-stylus
來安裝到全局。同時項目目錄中插件版本權重永遠是高于全局的,如需避免加載全局的版本,只需手動在項目安裝即可。
限于篇幅,更多插件相關說明可訪問以下主題頁面:
零配置快速項目構建
雖說是零配置構建項目,不如稱之為基于DOM的項目構建,這個主題的內容與我之前在Qing項目中討論的主題的一致的,在此只附上示例:
另外免配置文件對Sea.js 2.1+項目的支持正在開發中,會下Mod.js的下一迭代中支持。
服務化
了解完如何實施Mod.js進行自動化時,僅是停留在工具的層面,如何將其進一步的提升?了解一個事實,服務優于工具。如何將其封裝成服務,用戶無需安裝Mod.js,無需執行命令,只需做一次事情:提交代碼,中間的過程無需關注,最終把持續構建的結果反饋給用戶。這是下一步需要去完善的,建立接入機制,讓工具以服務的形式完全融入流程中。