現(xiàn)在公認(rèn)的JavaScript典型項(xiàng)目需要運(yùn)行單元測(cè)試,合并壓縮。有些還會(huì)使用代碼生成器,代碼樣式檢查或其他構(gòu)建工具。
Grunt.js是一個(gè)開源工具,可以幫助你完成上面的所有步驟。它非常容易擴(kuò)展,并使用JavaScript書寫,所以任何為JavaScript庫或項(xiàng)目工作的人都可以按自己的需要擴(kuò)展它。
本文解釋如何使用Grunt.js構(gòu)建JavaScript庫。Grunt.js依賴Node.js和npm,所以第一節(jié)解釋其是什么,如何安裝和使用。如果你對(duì)npm有了解,那你可以跳過這一節(jié)。第四和第五節(jié)講述如何配置Grunt和一系列典型Grunt任務(wù)。
本文討論的代碼例子可以在GitHub上訪問。
開始之前,我們需要三個(gè)工具:
Node.js是一個(gè)流行的服務(wù)器端JavaScript環(huán)境。它被用來編寫和運(yùn)行JavaScript服務(wù)和JavaScript命令行工具。如果你想進(jìn)一步鏈接Node.js,你可以查看Stack Overflow上相關(guān)的資料。
Npm是Node.js的包管理工具。它能從中心倉庫下載依賴,解決大部分依賴沖突問題。Npm倉庫僅僅存儲(chǔ)Node.js服務(wù)器段和命令行項(xiàng)目。它不包含用于web和移動(dòng)app相關(guān)的庫。我們用它來下載Grunt.js。
Grunt.js是一個(gè)任務(wù)運(yùn)行工具,我們用起構(gòu)建我們的項(xiàng)目。它在Node.js之上運(yùn)行并且通過Npm安裝。
你可以直接從下載頁面或用其它包管理工具安裝node.js。安裝成功后在命令行輸入 node -v Node.js將輸出它的版本號(hào)。
大部分安裝器和包管理工具將同時(shí)安裝Npm。在命令行輸入 npm -v 測(cè)試是否安裝成功。如果成功將輸出它的版本號(hào)。不同的系統(tǒng)可能安裝方式略有不同。
下載和使用安裝腳本。
windows安裝器包含npm并會(huì)添加path變量。僅在你下載Node.exe或從源代碼編譯Node是才需要獨(dú)立安裝Npm。從這里下載Npm的最新版zip壓縮包。解壓后賦值到Node.exe的安裝目錄。如果你愿意,你也可以放到任何位置,將其加入path變量即可。
安裝包中內(nèi)建Npm。
了解Npm基礎(chǔ)操作對(duì)于使用和安裝Grunt.js都有幫助。這節(jié)僅包含接觸知識(shí)。更多細(xì)節(jié)可以查看npm文檔。
本節(jié)將解釋下面的東西:
Npm是一個(gè)包管理工具,可以從中心倉庫下載和安裝JavaScript依賴。安裝包能被用在Node.js項(xiàng)目或命令行工具。
項(xiàng)目通常在package.json文件內(nèi)部列出其依賴和安裝插件。此外npm庫也可以從命令行安裝。
每個(gè)包可以安裝在全局或本地環(huán)境。實(shí)際的區(qū)別是存儲(chǔ)位置和訪問方式。
全局安裝包被直接存儲(chǔ)在Node.js安裝路徑。他們之所以被稱為全局,是因?yàn)樗麄兛梢栽谌魏蔚胤街苯釉L問。
本地安裝將下載包安裝在當(dāng)前工作路徑。本地安裝包只能從其所在目錄訪問。
本地安裝包被存儲(chǔ)進(jìn)node_mudules子目錄。無論你使用什么版本控制系統(tǒng),你可以將其添加僅.ignorefile 文件。
package.json文件包含npm項(xiàng)目描述。它總是位于項(xiàng)目的根目錄,并且包含項(xiàng)目名稱,版本,協(xié)議和其他類似元數(shù)據(jù)。最重要的是,它包含兩個(gè)項(xiàng)目依賴列表。
第一個(gè)列表包含運(yùn)行所需要的依賴。任何希望使用此項(xiàng)目的人必須安裝它們。第二個(gè)列表包含開發(fā)時(shí)需要依賴項(xiàng)。包括測(cè)試工具,構(gòu)建工具和代碼樣式檢測(cè)工具。
創(chuàng)建package.json最簡(jiǎn)單的方法是通過 npm install 命令。這條命令會(huì)以交互式提問一系列問題,并根據(jù)回答在當(dāng)前工作目錄生成基本的package.json文件。只有名字(name)和版本(version)屬性是必須的。如果你不打算將你的庫發(fā)布到Npm,你能忽略其余的部分。
下面的鏈接包含對(duì)package.json的詳細(xì)描述:
Npm寶可以通過npm install 命令安裝。默認(rèn)安裝到本地。全局安裝需要指定 -g開關(guān)。
不帶參數(shù)的 npm install 將在當(dāng)前目錄或上層目錄查找 package.json 文件。如果發(fā)現(xiàn),將會(huì)在當(dāng)前目錄安裝所有列出的依賴項(xiàng)。
可以通過 npm install <pkg_name@version> 命令安裝具體的npm包。這條命令將從中心倉庫找到指定版本的包,并將其安裝到當(dāng)前目錄。
版本號(hào)是可選的。如果省略將下載最新穩(wěn)定版。
最后,通過 –sace-dev開關(guān)不僅可以安裝包,還會(huì)將其添加到 package.json 的開發(fā)依賴中。
我們將首先將Grunt.js添加進(jìn)我們的JavaScript項(xiàng)目。為此我們需要安裝兩個(gè)Grunt.js模塊:
提醒:最新的Grunt.js(4.0)不再兼容以前的版本。一些老的教程和文檔不再適合新版Grunt.js了。
所有實(shí)際的工作是由任務(wù)運(yùn)行器來做。命令行接口僅解析參數(shù)和將其傳遞個(gè)任務(wù)運(yùn)行器。如果任務(wù)運(yùn)行器沒有安裝將不會(huì)做任何事情。
命令行接口應(yīng)該被安裝在全局環(huán)境,然而任務(wù)運(yùn)行器在本地環(huán)境。全局命令行接口保證Grunt命令可以在所有路徑訪問。任務(wù)運(yùn)行器必須是本地的,因?yàn)椴煌捻?xiàng)目可能需要不同的Grunt版本。
安裝全局Grunt命令行接口:
npm install -g grunt-cli
切換到項(xiàng)目根目錄,通過npm init讓Npm幫你生成package.json文件。它會(huì)問你些問題然后根據(jù)你的回答生成合法的package.json文件。只有名字和版本是必須的;你可以忽略其他選項(xiàng)。
將Grunt.js最新版添加到本地環(huán)境,同時(shí)添加到package.json文件的開發(fā)依賴?yán)铩?/p>
npm install grunt --save-dev
通過前面的命令創(chuàng)建的package.json文件應(yīng)該類似相面這樣:
{
"name": "gruntdemo",
"version": "0.0.0",
"description": "Demo project using grunt.js.",
"main": "src/gruntdemo.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"repository": "",
"author": "Meri",
"license": "BSD",
"devDependencies": {
"grunt": "~0.4.1"
}
}
Grunt.js運(yùn)行任務(wù)并且通過任務(wù)完成工作。然而,Grunt.js安裝成功后還沒有任務(wù)可以使用。任務(wù)必須從插件載入,而插件通常需要Npm安裝。
我們使用五個(gè)插件:
本節(jié)將解釋如何配置這些插件。我們從最簡(jiǎn)單的配置開始,然后一步步解釋如何配置任務(wù)。余下的子章節(jié)將詳細(xì)講解如何配置每個(gè)插件。
Grunt將配置信息寫到Gruntfile.js或Gruntfile.coffee文件里。由于我們創(chuàng)建的JavaScript項(xiàng)目,我們將使用JavaScript版本。最簡(jiǎn)單的Gruntfile.js看起來像下面這樣:
//包裝函數(shù) 有一個(gè)參數(shù)
module.exports = function(grunt) {
// 默認(rèn)任務(wù)。在本例子中沒有任何操作。
grunt.registerTask('default', []);
};
配置信息被保存在module.exports函數(shù)內(nèi)部。它包含grunt對(duì)象作為其參數(shù),并且通過調(diào)用該函數(shù)完成配置。
配置函數(shù)必須創(chuàng)建至少一個(gè)任務(wù)別名,并且配置他們。例如,上面的代碼片段創(chuàng)建了一個(gè)“default”任務(wù)別名并且指定為空的任務(wù)列表。換句話說,默認(rèn)的任務(wù)別名可以工作,但不會(huì)做任何事情。
用 grunt <taskAlias> 命令運(yùn)行指定的 taskAlias任務(wù)。taskAlias的參數(shù)是可選的,如果省略,Grunt將使用“default”任務(wù)。
保存Gruntfile.js文件,在命令行運(yùn)行Grunt:
grunt
你應(yīng)該看到如下輸出:
Done, without errors.
如果配置任務(wù)返回錯(cuò)誤或警告Grunt將發(fā)出蜂鳴聲(在命令行下輸入錯(cuò)誤的聲音,譯者未發(fā)現(xiàn)這點(diǎn))。如果你不想聽到蜂鳴聲,你可以使用 -no-color 參數(shù):
grunt -no-color
從插件添加任務(wù)是通過用步驟,對(duì)所有插件都是相同的。本節(jié)將概要講述需要的過程,實(shí)際的例子將在下面的章節(jié)講解。
首先,我們需要將插件添加進(jìn)package.json文件的開發(fā)依賴?yán)锩?,并且使用Npm進(jìn)行安裝:
npm install <plugin name> --save-dev
任務(wù)配置必須被存儲(chǔ)在一個(gè)對(duì)象內(nèi)部,有各自的任務(wù)名,并且被傳遞給 grunt.initConfig方法:
module.exports = function(grunt) {
grunt.initConfig({
firstTask : { /* ... 配置第一個(gè)任務(wù) ... */ },
secondTask : { /* ... 配置第二個(gè)任務(wù) ... */ },
// ... 其他任務(wù) ...
lastTask : { /* ... 最后一個(gè)任務(wù) ... */ }
});
// ... the rest ...
};
全面的任務(wù)配置信息解釋看這里Grunt.js文檔。本節(jié)僅描述最通用,簡(jiǎn)單的例子。假設(shè)任務(wù)接受一個(gè)文件列表,并處理他們,然后生出輸出文件。
一個(gè)簡(jiǎn)單的任務(wù)配置例子:
firstTask: {
options: {
someOption: value //取決于插件
},
target: {
src: ['src/file1.js', 'src/file2.js'], //輸入文件
dest: 'dist/output.js' // 輸出文件
}
}
例子中的任務(wù)配置有兩個(gè)屬性。一個(gè)是任務(wù)選項(xiàng),名稱必須是”options“。Grunt.js不會(huì)對(duì)options的屬性執(zhí)行任何操作,其行為有插件決定。
其他項(xiàng)可以有任何名字,并且要包含任務(wù)目標(biāo)。最常見的任務(wù)是操作和生成文件,所以他們的target有兩個(gè)屬性,”src“ 和 ”dest“。src包含輸入的文件列表,dest包含輸出的文件名字。
如果你配置多個(gè)任務(wù),Grunt將依次執(zhí)行。下面的任務(wù)將運(yùn)行兩次,一次操作src及其子目錄的所有js文件,另一次操作test及其子目錄下的所有js文件:
multipleTargetsTask: {
target1: { src: ['src/**/*.js'] },
target2: { src: ['test/**/*.js']] }
}
最后,將插件載入必須使用 grunt.loadNpmTasks 函數(shù),并且注冊(cè)任務(wù)別名。
上面介紹的結(jié)構(gòu)合起來如下:
module.exports = function(grunt) {
grunt.initConfig({ /* ... tasks configuration ... */ });
grunt.loadNpmTasks('grunt-plugin-name');
grunt.registerTask('default', ['firstTask', 'secondTask', ...]);
};
JSHint檢查JavaScript代碼中潛在的問題和錯(cuò)誤。他被設(shè)計(jì)成可配置的,并且有合理的默認(rèn)值。
我們將使用 grunt-contrib-jshint 插件,grunt-contrib開頭的插件都是有Grunt官方維護(hù)的,如果你創(chuàng)建自己的插件千萬不要以次開頭。
打開命令行,在項(xiàng)目根目錄運(yùn)行 npm install grunt-contrib-jshint –save-dev。將會(huì)添加插件到package.json文件的開發(fā)依賴,并且安裝到本地Npm倉庫。
grunt-contrib-jshint插件的參數(shù)和JSHint一樣。完整的參數(shù)列表可以訪問JSHint的文檔頁面。
JSHint 的參數(shù) “eqeqeq” 會(huì)將 == 和 != 操作符報(bào)告為警告。默認(rèn)是關(guān)閉的,因?yàn)檫@些操作符是合法的。但我建議你開啟它,因?yàn)閲?yán)格相等比非嚴(yán)格相等更安全。
同時(shí)我建議你開啟trailing選項(xiàng),將會(huì)對(duì)代碼中結(jié)尾部的空白元素生成警告。結(jié)尾的空白在多行字符串中會(huì)引起奇怪的問題。
每一個(gè)可選項(xiàng)都是布爾值,設(shè)置為true將會(huì)開啟相應(yīng)檢測(cè)。下面的例子開啟了eqeqeq和trailing選項(xiàng):
options: {
eqeqeq: true,
trailing: true
}
grunt-contrib-jshint插件的任務(wù)名字是“jshint”。我們將使用上一節(jié)中的配置選項(xiàng),使它檢測(cè)位于src和test目錄下的全部JavaScript文件。
JSHint的配置信息必須寫在名為“jshint”的屬性內(nèi)部??梢杂袃蓚€(gè)屬性,一個(gè)數(shù)參數(shù)(options)另一個(gè)是目標(biāo)(target)。
目標(biāo)可以在任何屬性內(nèi)部,在這里我們僅使用“target”。其必須包含待驗(yàn)證的JavaScript文件列表。文件列表可以放在目標(biāo)的src屬性中,可以使用*和通配符。
有兩個(gè)自定義選項(xiàng),將會(huì)驗(yàn)證位于src和test目錄及其子目錄下的所有js文件。
grunt.initConfig({
jshint: {
options: {
eqeqeq: true,
trailing: true
},
target: {
src : ['src/**/*.js', 'test/**/*.js']
}
}
});
最后需要載入和注冊(cè) grunt-contrib-jshint 任務(wù):
grunt.loadNpmTasks('grunt-contrib-jshint');
grunt.registerTask('default', ['jshint']);
目前為止完整的 Gruntfile.js 文件如下:
module.exports = function(grunt) {
grunt.initConfig({
jshint: {
options: {
trailing: true,
eqeqeq: true
},
target: {
src : ['src/**/*.js', 'test/**/*.js']
}
}
});
grunt.loadNpmTasks('grunt-contrib-jshint');
grunt.registerTask('default', ['jshint']);
};
js文件一般是分散的(如果你的項(xiàng)目不是幾十行js的話),上線前我們必須將其打包進(jìn)一個(gè)文件,并添加版本號(hào)和項(xiàng)目名字。在文件的開始位置應(yīng)該包含庫名稱,版本,協(xié)議,構(gòu)建時(shí)間和其他一些信息。
例如,像下面這樣:
/*! gruntdemo v1.0.2 - 2013-06-04
* License: BSD */
var gruntdemo = function() {
...
我們將使用 grunt-contrib-concat 插件完成拼接任務(wù),并生成正確的注釋信息。
和前面一樣,將插件寫進(jìn)package.json文件的開發(fā)依賴?yán)?,然后從Npm倉庫安裝到本地。
打開命令行,運(yùn)行如下命令:
npm install grunt-contrib-concat --save-dev
首先從package.json文件加載配置信息,并存儲(chǔ)在pkg屬性。需要使用 grunt.file.readJSON 函數(shù):
pkg: grunt.file.readJSON('package.json'),
現(xiàn)在pkg的值是一個(gè)對(duì)象,包含全部package.json的信息。項(xiàng)目名被存儲(chǔ)在pkg.name屬性,版本被存儲(chǔ)在pkg.version。版權(quán)被存儲(chǔ)在pkg.license屬性等等。
Grunt提供一套模版系統(tǒng),我們可以使用它構(gòu)建頁頭和文件名稱。模版可以在字符串中嵌入JavaScript表達(dá)式,通過<%= expression %> 語法。Grunt計(jì)算表達(dá)式的值并替換模版中的表達(dá)式。
例如,模版中的 <%= pkg.name %> 將被替換為 pkg.name 的屬性值。如果屬性值是字符串,模版的行為類似字符串拼接 …’ + pkg.name + ‘…
模版中可以引用Grunt中的全部屬性。系統(tǒng)提供了一個(gè)非常有幫助的日期格式化函數(shù)。我們將使用 grunt.template.today(format) 函數(shù)生成當(dāng)前的時(shí)間戳。
讓我們生成一個(gè)簡(jiǎn)單的頁頭,包含項(xiàng)目名稱,版本號(hào),版權(quán)和當(dāng)前的日期。由于我們需要在 Uglify 任務(wù)中使用banner,所以我們將其存儲(chǔ)在變量中:
var bannerContent = '/*! <%= pkg.name %> v<%= pkg.version %> - ' +
'<%= grunt.template.today("yyyy-mm-dd") %> \n' +
' * License: <%= pkg.license %> */\n';
上面的模版生成如下的頁頭:
/*! gruntdemo v0.0.1 - 2013-06-04
* License: BSD */
項(xiàng)目的名稱和版本部分也需要在多處使用。將項(xiàng)目名和版本號(hào)拼在一起,存儲(chǔ)在一個(gè)變量中:
var name = '<%= pkg.name %>-v<%= pkg.version%>';
生成的名字如下:
gruntdemo-v0.0.1
target必須包含需要被拼接的文件列表,和合并完成后輸出文件的名字。target支持通配符和模版,所以我們使用前一節(jié)生成的模版:
target : {
// 拼接src目錄下的所有文件
src : ['src/**/*.js'],
// place the result into the dist directory,
// name variable contains template prepared in
// previous section
dest : 'distrib/' + name + '.js'
}
concat插件也可以通過banner屬性添加banner。由于上面我們已經(jīng)將banner內(nèi)容賦給bannerContent變量,所以我們僅需引入即可:
options: {
banner: bannerContent
}
最后不要忘記從Npm加載 grunt-contrib-concat ,并且將其注冊(cè)到默認(rèn)工作流:
grunt.loadNpmTasks('grunt-contrib-concat');
grunt.registerTask('default', ['jshint', 'concat']);
這一節(jié)出示包含完整contat配置的Gruntfile.js文件。
注意pkg屬性在傳遞給initConfig方法的參數(shù)中定義。我們不能把他放在其他地方,因?yàn)樗x取模版信息,并且僅在initConfig方法的參數(shù)和grunt對(duì)象中有訪問模版的權(quán)限。
module.exports = function(grunt) {
var bannerContent = '... banner template ...';
var name = '<%= pkg.name %>-v<%= pkg.version%>';
grunt.initConfig({
// pkg is used from templates and therefore
// MUST be defined inside initConfig object
pkg : grunt.file.readJSON('package.json'),
// concat configuration
concat: {
options: {
banner: bannerContent
},
target : {
src : ['src/**/*.js'],
dest : 'distrib/' + name + '.js'
}
},
jshint: { /* ... jshint configuration ... */ }
});
grunt.loadNpmTasks('grunt-contrib-jshint');
grunt.loadNpmTasks('grunt-contrib-concat');
grunt.registerTask('default', ['jshint', 'concat']);
};
如果瀏覽器載入和解析大文件,會(huì)使頁面載入變得緩慢。可能不是所有項(xiàng)目都會(huì)遇到這個(gè)問題,但對(duì)于移動(dòng)端的app和使用非常多大型庫的大型web應(yīng)用程序而言,是必須要考慮的。
因此,我們也將我們庫進(jìn)行壓縮。壓縮會(huì)將輸入的文件變小,通過去除空白元素,注釋,替換變量名稱等,但不會(huì)改變代碼邏輯。
壓縮功能使用 grunt-contrib-uglify 插件,其通過Grunt集成 UglifyJs。它通過uglify任務(wù)拼接和壓縮一組文件。
壓縮會(huì)使生成的文件難于閱讀和調(diào)試,所以我們通過生成源程序映射來簡(jiǎn)化問題。
源程序映射是壓縮文件和源文件之間的紐帶。如果瀏覽器支持,瀏覽器調(diào)試工具會(huì)顯示對(duì)人友好的源文件,而不是壓縮文件。僅有chrome和nightly版本的 firefox支持源代碼映射。你可以在HTML5 rocks 和 Tutsplus 上獲取更多信息,我建議你看看阮一峰老師的這篇文章:http://www.ruanyifeng.com/blog/2013/01/javascript_source_map.html
將插件添加到package.json的開發(fā)依賴?yán)?,并且安裝到本地Npm倉庫。
使用如下命令:
npm install grunt-contrib-uglify --save-dev
配置uglify任務(wù)目標(biāo)的方式和concat任務(wù)類似。必須要包含待壓縮的javascript文件列表和輸出文件的名字。
支持通配符和模版,所以我們可以使用前面章節(jié)中的用到的模版:
target : {
// use all files in src directory
src : ['src/**/*.js'],
// place the result into the dist directory,
// name variable contains template prepared in
// previous sub-chapter
dest : 'distrib/' + name + '.min.js'
}
配置banner的方式和concat一樣——通過設(shè)置“banner”屬性并且支持模版。因此,我們可以重復(fù)使用前面章節(jié)中準(zhǔn)備好的 bannerContent 變量。
通過“sourceMap”屬性生成源文件映射。包含生成文件的名字。此外,必須設(shè)置“sourceMapUrl”和“sourceMapRoot”屬性。前一個(gè)包含相對(duì)于uglified文件到源文件映射文件的路徑,后一個(gè)包含是源文件映射到源文件的相對(duì)路徑。
通過bannerContent變量生成頁眉,通過name變量生成源文件映射文件的名字:
options: {
banner: bannerContent,
sourceMapRoot: '../',
sourceMap: 'distrib/'+name+'.min.js.map',
sourceMapUrl: name+'.min.js.map'
}
最后一步是從Npm加載 grunt-contrib-uglify,并且添加到默認(rèn)任務(wù)列表:
grunt.loadNpmTasks('grunt-contrib-uglify');
grunt.registerTask('default', ['jshint', 'concat', 'uglify']);
下面是包含完整uglify配置信息的Gruntfile.js文件:
module.exports = function(grunt) {
var bannerContent = '... banner template ...';
var name = '<%= pkg.name %>-v<%= pkg.version%>';
grunt.initConfig({
// pkg must be defined inside initConfig object
pkg : grunt.file.readJSON('package.json'),
// uglify configuration
uglify: {
options: {
banner: bannerContent,
sourceMapRoot: '../',
sourceMap: 'distrib/'+name+'.min.js.map',
sourceMapUrl: name+'.min.js.map'
},
target : {
src : ['src/**/*.js'],
dest : 'distrib/' + name + '.min.js'
}
},
concat: { /* ... concat configuration ... */ },
jshint: { /* ... jshint configuration ... */ }
});
grunt.loadNpmTasks('grunt-contrib-jshint');
grunt.loadNpmTasks('grunt-contrib-concat');
grunt.loadNpmTasks('grunt-contrib-uglify');
grunt.registerTask('default', ['jshint', 'concat', 'uglify']);
};
最后要發(fā)布的庫包括兩個(gè)文件,并且都在名字中含有版本號(hào)。這會(huì)給想要自動(dòng)下載每個(gè)新版本的人造成不必要的困難。
如果想要查看是否發(fā)布了新版本和新版本的名字,必須每次都要獲取和解析一個(gè)json文件。如果每次都更改名稱,則必須更新下載腳本。
因此我們將使用 grunt-contrib-copy 插件創(chuàng)建無版本號(hào)的文件。
將插件添加進(jìn)package.json的開發(fā)依賴,并且從Npm倉庫安裝到本地。
使用如下命令:
npm install grunt-contrib-copy --save-dev
copy配置信息包括三個(gè)目標(biāo),分別對(duì)應(yīng)三個(gè)發(fā)布文件。沒有配置選項(xiàng),基本和前一個(gè)插件配置過程一樣。
僅有一點(diǎn)不一樣,就是多任務(wù)。每個(gè)任務(wù)包含一對(duì) src/dest,待拷貝的名字和待創(chuàng)建的名字。
對(duì)前面的任務(wù)配置選項(xiàng)稍加修改。將所有文件名字放到變量中,以便可以重復(fù)使用:
module.exports = function(grunt) {
/* define filenames */
latest = '<%= pkg.name %>';
name = '<%= pkg.name %>-v<%= pkg.version%>';
devRelease = 'distrib/'+name+'.js';
minRelease = 'distrib/'+name+'.min.js';
sourceMapMin = 'distrib/source-map-'+name+'.min.js';
lDevRelease = 'distrib/'+latest+'.js';
lMinRelease = 'distrib/'+latest+'.min.js';
lSourceMapMin = 'distrib/source-map-'+latest+'.min.js';
grunt.initConfig({
copy: {
development: { // copy non-minified release file
src: devRelease,
dest: lDevRelease
},
minified: { // copy minified release file
src: minRelease,
dest: lMinRelease
},
smMinified: { // source map of minified release file
src: sourceMapMin,
dest: lSourceMapMin
}
},
uglify: { /* ... uglify configuration ... */ },
concat: { /* ... concat configuration ... */ },
jshint: { /* ... jshint configuration ... */ }
});
grunt.loadNpmTasks('grunt-contrib-jshint');
grunt.loadNpmTasks('grunt-contrib-concat');
grunt.loadNpmTasks('grunt-contrib-uglify');
grunt.loadNpmTasks('grunt-contrib-copy');
grunt.registerTask('default', ['jshint', 'concat', 'uglify', 'copy']);
最后,配置 grunt.js 運(yùn)行單元測(cè)試,測(cè)試最新發(fā)布的文件。我們將使用 grunt-contrib-qunit 插件實(shí)現(xiàn)目標(biāo)。這個(gè)插件將在無頭的 PhantomJS 實(shí)例中運(yùn)行 QUnit 單元測(cè)試。
這個(gè)解決方案不能模擬不同瀏覽器和查找全部 bug,但對(duì)于我們來說已經(jīng)足夠了。如果想得到更好的配置,可以使用 js-test-driver 或 其他類似工具,然而,關(guān)于 js-test-dirver 的配置超出了本文的范圍。
Qunit 單元測(cè)試經(jīng)常要運(yùn)行 src 目錄里的 JavaScript 文件,由于測(cè)試是開發(fā)的一部分。如果你想測(cè)試剛剛發(fā)布的拼接壓縮后的版本工作狀況,需要?jiǎng)?chuàng)建一個(gè)新的 QUnit HTML 文件,并加載最后發(fā)布的文件。
下面是一個(gè)例子是 Qunit 的入口文件:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>QUnit Example</title>
<link rel="stylesheet" href="../libs/qunit/qunit.css">
</head>
<body>
<div id="qunit"></div>
<div id="qunit-fixture"></div>
<script src="../libs/qunit/qunit.js"></script>
<!-- Use latest versionless copy of current release -->
<script src="../distrib/gruntdemo.min.js"></script>
<script src="tests.js"></script>
</body>
</html>
將插件添加進(jìn)package.json 的開發(fā)者依賴中,并且將其安裝到本地 Npm 倉庫。
使用如下命令:
npm install grunt-contrib-qunit --save-dev
配置 grunt-contrib-qunit 插件和配置前面的任務(wù)如出一轍。由于我們使用默認(rèn)的 Qunit 配置,所以可以省略選項(xiàng)屬性。不能忽略的是必須配置 target,指定全部的 Qunit HTML 文件。
接下來指定位于測(cè)試目錄下的全部 HTML 文件,及其子目錄應(yīng)該運(yùn)行 Qunit 測(cè)試:
grunt.initConfig({
qunit:{
target: {
src: ['test/**/*.html']
}
},
// ... all previous tasks ...
});
下面是完整的 Gruntfile.js 配置信息:
module.exports = function(grunt) {
var name, latest, bannerContent, devRelease, minRelease,
sourceMap, sourceMapUrl, lDevRelease, lMinRelease,
lSourceMapMin;
latest = '<%= pkg.name %>';
name = '<%= pkg.name %>-v<%= pkg.version%>';
bannerContent = '/*! <%= pkg.name %> v<%= pkg.version %> - ' +
'<%= grunt.template.today("yyyy-mm-dd") %> \n' +
' * License: <%= pkg.license %> */\n';
devRelease = 'distrib/'+name+'.js';
minRelease = 'distrib/'+name+'.min.js';
sourceMapMin = 'distrib/'+name+'.min.js.map';
sourceMapUrl = name+'.min.js.map';
lDevRelease = 'distrib/'+latest+'.js';
lMinRelease = 'distrib/'+latest+'.min.js';
lSourceMapMin = 'distrib/'+latest+'.min.js.map';
grunt.initConfig({
pkg: grunt.file.readJSON('package.json'),
qunit:{
target: {
src: ['test/**/*.html']
}
},
// configure copy task
copy: {
development: {
src: devRelease,
dest: lDevRelease
},
minified: {
src: minRelease,
dest: lMinRelease
},
smMinified: {
src: sourceMapMin,
dest: lSourceMapMin
}
},
// configure uglify task
uglify:{
options: {
banner: bannerContent,
sourceMapRoot: '../',
sourceMap: sourceMapMin,
sourceMappingURL: sourceMapUrl
},
target: {
src: ['src/**/*.js'],
dest: minRelease
}
},
// configure concat task
concat: {
options: {
banner: bannerContent
},
target: {
src: ['src/**/*.js'],
dest: devRelease
}
},
// configure jshint task
jshint: {
options: {
trailing: true,
eqeqeq: true
},
target: {
src: ['src/**/*.js', 'test/**/*.js']
}
}
});
grunt.loadNpmTasks('grunt-contrib-jshint');
grunt.loadNpmTasks('grunt-contrib-concat');
grunt.loadNpmTasks('grunt-contrib-uglify');
grunt.loadNpmTasks('grunt-contrib-copy');
grunt.loadNpmTasks('grunt-contrib-qunit');
grunt.registerTask('default', ['jshint', 'concat', 'uglify', 'copy', 'qunit']);
};
現(xiàn)在 Grunt.js 配置好了,并且可以使用了。我們的目標(biāo)是使配置盡可能簡(jiǎn)單,使用成對(duì)的 src/dest,通配符和模版。當(dāng)然,Grunt.js 也提供其他更高級(jí)的選項(xiàng)。
如果能夠自動(dòng)下載和管理項(xiàng)目依賴的庫,會(huì)變得更美好。我發(fā)現(xiàn)兩個(gè)可行的解決方案,Bower 和 Ender。我沒有試過他們,但都可以管理前端JavaScript包和其依賴。
文章有些長(zhǎng),拖了很久的文章終于翻譯完成了,我最近打算寫一本關(guān)于 Grunt 指南的書籍,會(huì)詳細(xì)講解如何構(gòu)建一套前端自動(dòng)化工具,如果你支持我的工作,那就給我捐助吧。
原文:http://flippinawesome.org/2013/07/01/building-a-javascript-library-with-grunt-js/
更多建議: