上一篇文章介紹了作為前端建構的包管理工具的 Bower,并在文末提出了一個有待解決的問題,這篇則從這個問題展開,介紹一個前端自動化工具 Grunt。
建構任務
我們為什麼需要前端的建構任務?
首先,同後端開發一樣,前端開發實際也需要一些建構任務:LESS/SASS、CoffeeScript/TypeScript 的編譯、上線前對 CSS/JavaScript 進行連接配接或壓縮、執行 JavaScript 子產品的單元測試等等。在以往,我們為這些任務寫不同的批處理或者 Shell 腳本,然後調用它們來執行建構動作。但撰寫腳本這件事本身就浪費了寶貴的開發時間,它是一種純粹的技術活兒,對業務毫無幫助。是以,一些情況下,在按照子產品進行開發的團隊裡,那些前後端包辦的開發者幹脆不關注前端的這些建構:不使用 LESS/SASS或CoffeeScript/TypeScript(即使它們在一些方面比原生的 CSS 和 JavaScript 更優秀),加載未經壓縮的 CSS/JavaScript 檔案,跳過前端子產品的單元測試等。這種對前端的忽視,實際上為測試、使用者體驗、軟體性能甚至是系統安全都帶來不少的風險。
另一方面,體系化的建構是持續內建/部署/傳遞的重要一環。持續內建要求的速度太快,依靠人力很難做到一天進行數個甚至數十個版本的疊代還不保證出錯。這迫使我們需要将那些重複的建構任務交給機器完成,騰出時間應對業務需求、及早地發現并調整問題。雖然我們平時可能已經或多或少用一些自動化做這些瑣屑的事情:用一段伺服器上運作的腳本進行建構,但這些腳本往往需要借助于各種工具才能正常運作,例如,假設你的持續內建伺服器上缺失了某些元件,通過這些腳本進行的建構就無法成功,而你可能會在部署的包中帶上這些工具,使得包變得臃腫不堪。那麼我們是否能引入一些更加統一的方式管理建構任務呢?後端技術棧在這方面已經十分成熟,而這個系列要讨論的,是前端世界的建構。
Grunt
從上面可以看出,運作建構任務要克服兩個問題。
- 建構任務功能要足夠強大和靈活,但寫的腳本要足夠簡單。
- 建構任務對外部的依賴是可以進行管理的。
接下來将會介紹一個名為 Grunt 的工具,這個工具提供了一些激動人心的功能,作為前端的建構任務運作工具簡直再好不過了。
Grunt 是什麼
Grunt 是一個在 Node.js 上運作的 JavaScript 建構工具。它滿足了建構任務對工具的兩個要求:
- Grunt 提供了豐富的任務插件和簡單的 API,隻要寫一段 JavaScript 腳本,任務就能運作起來。
- Grunt 通過 npm 管理插件,所有依賴盡在掌握。
怎麼使用 Grunt
安裝 Grunt
安裝 grunt-cli
grunt-cli
前面說過,Grunt 依賴 Node.js,是以確定你已經安裝了 Node.js。然後,我們先通過 npm 全局安裝
grunt-cli
。
grunt-cli
是個 Grunt 指令行工具(注意,
grunt-cli
并不是 Grunt),它的作用是讓你可以在任何目錄使用 Grunt,并確定不同版本的 Grunt 隔離不互相影響。
安裝 Grunt
Grunt
安裝了
grunt-cli
後,在我們的項目主目錄下安裝 Grunt:
--save-dev
向 npm 表明 Grunt 将為開發時的依賴,而非運作時的依賴。
配置 Grunt
Grunt
在項目根目錄下建立一個名為
Gruntfile.js
的檔案,這個檔案管理項目中的全部 Grunt 任務。
将下面的代碼加到
Gruntfile.js
中:
module.exports = function(grunt) {
grunt.initConfig({
pkg: grunt.file.readJSON('package.json')
})
grunt.registerTask('default', [])
}
你可以将這段代碼作為腳手架,放到每一個項目的初始化腳本中去。
測試 Grunt
在項目根目錄下運作
grunt
或
grunt default
。
> grunt
Done.
輸出“Done.”表明 Grunt 配置正确。
内建 Grunt 任務
Grunt 生态良好,有龐大的插件庫,這些插件可以在 npm 上搜尋并安裝。這裡列出了一些可用的 Grunt 插件清單。下面,作為案例,我将用 Grunt 處理上一篇文章末尾提出的問題:隻提取
bower_components
目錄中實際有用的檔案到另一個存放庫檔案的目錄
lib
下。
我在 npm 上找到了一個名為
grunt-bower-task
的插件,這個插件做的正是我想要的:将
bower_components
裡有用的檔案複制出來。
先安裝這個插件:
修改
Gruntfile.js
,将
grunt.registerTask('default', [])
這一行删除,然後為替換成下面這句:
grunt.loadNpmTasks('grunt-bower-task')
儲存後運作
grunt --help
,你會在 Grunt 輸出的一大串幫助中看到一個名為
bower
的可用任務(Available task),表明
bower
任務被成功注冊到 Grunt 中:
...
Options marked with * have methods exposed via the grunt API and should instead
be specified inside the Gruntfile wherever possible.
Available tasks
bower Install Bower packages. *
...
接下來我們按照這個插件的文檔,對
bower
任務進行配置,達到我們一開始期望的效果。
編輯
Gruntfile.js
,在提供給
grunt.initConfig
函數的對象上加入一個
bower
屬性:
grunt.initConfig({
pkg: grunt.file.readJSON('package.json'),
// bower-task
bower: {
install: {
options: {
}
}
}
})
儲存
Gruntfile.js
,運作
grunt bower
。
Running "bower:install" (bower) task
>> Installed bower packages
>> Copied packages to *****\lib
Done.
運作成功。檢視你的項目主目錄,會發現多出一個
lib
目錄,而裡面正好是我們所需要的内容。
如果認真閱讀文檔,會發現
grunt-bower-task
其實是先完成了
bower install
的工作,再執行複制的(從原理上說,它複制的是
bower.json
裡
main
配置指定的檔案),插件的文檔中還給出了很多可選的配置項,隻用一些代碼,能滿足大部分 Bower 安裝的需求。是以,可以用這個插件将 Bower 和 Grunt 內建。
自定義 Grunt 任務
如果插件不能滿足你的需求,别擔心,隻要會寫 JavaScript,稍微查下 Node.js 和 Grunt 的文檔,你就能在
Gruntfile.js
中輕松地定義。下面的代碼在
grunt.initConfig
初始化 Grunt 配置後,注冊一個名為
foo
的自定義的任務:
grunt.task.registerTask('foo', 'A sample task that logs stuff.', function(arg1, arg2) {
grunt.log.writeln(this.name + ", " + arg1 + " " + arg2)
})
要運作該任務,執行
grunt foo[:arg1[:arg2]]
(
arg1
和
arg2
分别表示傳給
foo
任務的第一個和第二個參數)
> grunt foo:a:c
Running "foo:a:c" (foo) task
foo, a c
Done.
結論
本文引入了 Grunt 工具作為前端建構任務的基礎設施,并通過一個案例簡單地介紹如何使用 Grunt 。我沒有具體地說明 Grunt 的指令應當如何使用,這些有不少教程都已經很細緻地介紹了。通過 Grunt 的統一風格管理自動化的前端任務,軟體開發人員在更為靈活和更為規範的 持續化 道路上又前進了一步。