?
This document uses PHP Chinese website manual Release
安裝
命令腳本文件Gruntfile.js
Gruntfile.js實(shí)例:grunt-contrib-cssmin模塊
常用模塊設(shè)置
grunt-contrib-jshint
grunt-contrib-concat
grunt-contrib-uglify
grunt-contrib-copy
grunt-contrib-watch
其他模塊
參考鏈接
在Javascript的開發(fā)過程中,經(jīng)常會遇到一些重復(fù)性的任務(wù),比如合并文件、壓縮代碼、檢查語法錯誤、將Sass代碼轉(zhuǎn)成CSS代碼等等。通常,我們需要使用不同的工具,來完成不同的任務(wù),既重復(fù)勞動又非常耗時。Grunt就是為了解決這個問題而發(fā)明的工具,可以幫助我們自動管理和運(yùn)行各種任務(wù)。
簡單說,Grunt是一個自動任務(wù)運(yùn)行器,會按照預(yù)先設(shè)定的順序自動運(yùn)行一系列的任務(wù)。這可以簡化工作流程,減輕重復(fù)性工作帶來的負(fù)擔(dān)。
Grunt基于Node.js,安裝之前要先安裝Node.js,然后運(yùn)行下面的命令。
sudo npm install grunt-cli -g
grunt-cli表示安裝的是grunt的命令行界面,參數(shù)g表示全局安裝。
Grunt使用模塊結(jié)構(gòu),除了安裝命令行界面以外,還要根據(jù)需要安裝相應(yīng)的模塊。這些模塊應(yīng)該采用局部安裝,因?yàn)椴煌?xiàng)目可能需要同一個模塊的不同版本。
首先,在項(xiàng)目的根目錄下,創(chuàng)建一個文本文件package.json,指定當(dāng)前項(xiàng)目所需的模塊。下面就是一個例子。
{ "name": "my-project-name", "version": "0.1.0", "author": "Your Name", "devDependencies": { "grunt": "0.x.x", "grunt-contrib-jshint": "*", "grunt-contrib-concat": "~0.1.1", "grunt-contrib-uglify": "~0.1.0", "grunt-contrib-watch": "~0.1.4" } }
上面這個package.json文件中,除了注明項(xiàng)目的名稱和版本以外,還在devDependencies屬性中指定了項(xiàng)目依賴的grunt模塊和版本:grunt核心模塊為最新的0.x.x版,jshint插件為最新版本,concat插件不低于0.1.1版,uglify插件不低于0.1.0版,watch插件不低于0.1.4版。
然后,在項(xiàng)目的根目錄下運(yùn)行下面的命令,這些插件就會被自動安裝在node_modules子目錄。
npm install
上面這種方法是針對已有package.json的情況。如果想要自動生成package.json文件,可以使用npm init命令,按照屏幕提示回答所需模塊的名稱和版本即可。
npm init
如果已有的package.json文件不包括Grunt模塊,可以在直接安裝Grunt模塊的時候,加上--save-dev參數(shù),該模塊就會自動被加入package.json文件。
npm install <module> --save-dev
比如,對應(yīng)上面package.json文件指定的模塊,需要運(yùn)行以下npm命令。
npm install grunt --save-dev npm install grunt-contrib-jshint --save-dev npm install grunt-contrib-concat --save-dev npm install grunt-contrib-uglify --save-dev npm install grunt-contrib-watch --save-dev
模塊安裝完以后,下一步在項(xiàng)目的根目錄下,新建腳本文件Gruntfile.js。它是grunt的配置文件,就好像package.json是npm的配置文件一樣。Gruntfile.js就是一般的Node.js模塊的寫法。
module.exports = function(grunt) { // 配置Grunt各種模塊的參數(shù) grunt.initConfig({ jshint: { /* jshint的參數(shù) */ }, concat: { /* concat的參數(shù) */ }, uglify: { /* uglify的參數(shù) */ }, watch: { /* watch的參數(shù) */ } }); // 從node_modules目錄加載模塊文件 grunt.loadNpmTasks('grunt-contrib-jshint'); grunt.loadNpmTasks('grunt-contrib-concat'); grunt.loadNpmTasks('grunt-contrib-uglify'); grunt.loadNpmTasks('grunt-contrib-watch'); // 每行registerTask定義一個任務(wù) grunt.registerTask('default', ['jshint', 'concat', 'uglify']); grunt.registerTask('check', ['jshint']); };
上面的代碼用到了grunt代碼的三個方法:
grunt.initConfig:定義各種模塊的參數(shù),每一個成員項(xiàng)對應(yīng)一個同名模塊。
grunt.loadNpmTasks:加載完成任務(wù)所需的模塊。
grunt.registerTask:定義具體的任務(wù)。第一個參數(shù)為任務(wù)名,第二個參數(shù)是一個數(shù)組,表示該任務(wù)需要依次使用的模塊。default任務(wù)名表示,如果直接輸入grunt命令,后面不跟任何參數(shù),這時所調(diào)用的模塊(該例為jshint,concat和uglify);該例的check任務(wù)則表示使用jshint插件對代碼進(jìn)行語法檢查。
上面的代碼一共加載了四個模塊:jshint(檢查語法錯誤)、concat(合并文件)、uglify(壓縮代碼)和watch(自動執(zhí)行)。接下來,有兩種使用方法。
(1)命令行執(zhí)行某個模塊,比如
grunt jshint
上面代碼表示運(yùn)行jshint模塊。
(2)命令行執(zhí)行某個任務(wù)。比如
grunt check
上面代碼表示運(yùn)行check任務(wù)。如果運(yùn)行成功,就會顯示“Done, without errors.”。
如果沒有給出任務(wù)名,只鍵入grunt,就表示執(zhí)行默認(rèn)的default任務(wù)。
下面通過cssmin模塊,演示如何編寫Gruntfile.js文件。cssmin模塊的作用是最小化CSS文件。
首先,在項(xiàng)目的根目錄下安裝該模塊。
npm install grunt-contrib-cssmin --save-dev
然后,新建文件Gruntfile.js。
module.exports = function(grunt) { grunt.initConfig({ cssmin: { minify: { expand: true, cwd: 'css/', src: ['*.css', '!*.min.css'], dest: 'css/', ext: '.min.css' }, combine: { files: { 'css/out.min.css': ['css/part1.min.css', 'css/part2.min.css'] } } } }); grunt.loadNpmTasks('grunt-contrib-cssmin'); grunt.registerTask('default', ['cssmin:minify','cssmin:combine']); };
下面詳細(xì)解釋上面代碼中的三個方法,下面一個個來看。
(1)grunt.loadNpmTasks
grunt.loadNpmTasks方法載入模塊文件。
grunt.loadNpmTasks('grunt-contrib-cssmin');
你需要使用幾個模塊,這里就要寫幾條grunt.loadNpmTasks語句,將各個模塊一一加載。
如果加載模塊很多,這部分會非常冗長。而且,還存在一個問題,就是凡是在這里加載的模塊,也同時出現(xiàn)在package.json文件中。如果使用npm命令卸載模塊以后,模塊會自動從package.json文件中消失,但是必須手動從Gruntfile.js文件中清除,這樣很不方便,一旦忘記,還會出現(xiàn)運(yùn)行錯誤。這里有一個解決辦法,就是安裝load-grunt-tasks模塊,然后在Gruntfile.js文件中,用下面的語句替代所有的grunt.loadNpmTasks語句。
require('load-grunt-tasks')(grunt);
這條語句的作用是自動分析package.json文件,自動加載所找到的grunt模塊。
(2)grunt.initConfig
grunt.initConfig方法用于模塊配置,它接受一個對象作為參數(shù)。該對象的成員與使用的同名模塊一一對應(yīng)。由于我們要配置的是cssmin模塊,所以里面有一個cssmin成員(屬性)。
cssmin(屬性)指向一個對象,該對象又包含多個成員。除了一些系統(tǒng)設(shè)定的成員(比如options),其他自定義的成員稱為目標(biāo)(target)。一個模塊可以有多個目標(biāo)(target),上面代碼里面,cssmin模塊共有兩個目標(biāo),一個是“minify”,用于壓縮css文件;另一個是“combine”,用于將多個css文件合并一個文件。
每個目標(biāo)的具體設(shè)置,需要參考該模板的文檔。就cssmin來講,minify目標(biāo)的參數(shù)具體含義如下:
expand:如果設(shè)為true,就表示下面文件名的占位符(即*號)都要擴(kuò)展成具體的文件名。
cwd:需要處理的文件(input)所在的目錄。
src:表示需要處理的文件。如果采用數(shù)組形式,數(shù)組的每一項(xiàng)就是一個文件名,可以使用通配符。
dest:表示處理后的文件名或所在目錄。
ext:表示處理后的文件后綴名。
除了上面這些參數(shù),還有一些參數(shù)也是grunt所有模塊通用的。
filter:一個返回布爾值的函數(shù),用于過濾文件名。只有返回值為true的文件,才會被grunt處理。
dot:是否匹配以點(diǎn)號(.)開頭的系統(tǒng)文件。
makeBase:如果設(shè)置為true,就只匹配文件路徑的最后一部分。比如,a?b可以匹配/xyz/123/acb,而不匹配/xyz/acb/123。
關(guān)于通配符,含義如下:
*:匹配任意數(shù)量的字符,不包括/。
?:匹配單個字符,不包括/。
**:匹配任意數(shù)量的字符,包括/。
{}:允許使用逗號分隔的列表,表示“or”(或)關(guān)系。
!:用于模式的開頭,表示只返回不匹配的情況。
比如,foo/.js匹配foo目錄下面的文件名以.js結(jié)尾的文件,foo/**/.js匹配foo目錄和它的所有子目錄下面的文件名以.js結(jié)尾的文件,!*.css表示匹配所有后綴名不為“.css”的文件。
使用通配符設(shè)置src屬性的更多例子:
{src: 'foo/th*.js'}grunt-contrib-uglify {src: 'foo/{a,b}*.js'} {src: ['foo/a*.js', 'foo/b*.js']}
至于combine目標(biāo),就只有一個files參數(shù),表示輸出文件是css子目錄下的out.min.css,輸入文件則是css子目錄下的part1.min.css和part2.min.css。
files參數(shù)的格式可以是一個對象,也可以是一個數(shù)組。
files: { 'dest/b.js': ['src/bb.js', 'src/bbb.js'], 'dest/b1.js': ['src/bb1.js', 'src/bbb1.js'], }, // or files: [ {src: ['src/aa.js', 'src/aaa.js'], dest: 'dest/a.js'}, {src: ['src/aa1.js', 'src/aaa1.js'], dest: 'dest/a1.js'}, ],
如果minify目標(biāo)和combine目標(biāo)的屬性設(shè)置有重合的部分,可以另行定義一個與minify和combine平行的options屬性。
grunt.initConfig({ cssmin: { options: { /* ... */ }, minify: { /* ... */ }, combine: { /* ... */ } } });
(3)grunt.registerTask
grunt.registerTask方法定義如何調(diào)用具體的任務(wù)。“default”任務(wù)表示如果不提供參數(shù),直接輸入grunt命令,則先運(yùn)行“cssmin:minify”,后運(yùn)行“cssmin:combine”,即先壓縮再合并。如果只執(zhí)行壓縮,或者只執(zhí)行合并,則需要在grunt命令后面指明“模塊名:目標(biāo)名”。
grunt # 默認(rèn)情況下,先壓縮后合并 grunt cssmin:minify # 只壓縮不合并 grunt css:combine # 只合并不壓縮
如果不指明目標(biāo),只是指明模塊,就表示將所有目標(biāo)依次運(yùn)行一遍。
grunt cssmin
grunt的模塊已經(jīng)超過了2000個,且還在快速增加。下面是一些常用的模塊(按字母排序)。
grunt-contrib-clean:刪除文件。
grunt-contrib-compass:使用compass編譯sass文件。
grunt-contrib-concat:合并文件。
grunt-contrib-copy:復(fù)制文件。
grunt-contrib-cssmin:壓縮以及合并CSS文件。
grunt-contrib-imagemin:圖像壓縮模塊。
grunt-contrib-jshint:檢查JavaScript語法。
grunt-contrib-uglify:壓縮以及合并JavaScript文件。
grunt-contrib-watch:監(jiān)視文件變動,做出相應(yīng)動作。
模塊的前綴如果是grunt-contrib,就表示該模塊由grunt開發(fā)團(tuán)隊維護(hù);如果前綴是grunt(比如grunt-pakmanager),就表示由第三方開發(fā)者維護(hù)。
以下選幾個模塊,看看它們配置參數(shù)的寫法,也就是說如何在grunt.initConfig方法中配置各個模塊。
jshint用來檢查語法錯誤,比如分號的使用是否正確、有沒有忘記寫括號等等。它在grunt.initConfig方法里面的配置代碼如下。
jshint: { options: { eqeqeq: true, trailing: true }, files: ['Gruntfile.js', 'lib/**/*.js'] },
上面代碼先指定jshint的檢查項(xiàng)目,eqeqeq表示要用嚴(yán)格相等運(yùn)算符取代相等運(yùn)算符,trailing表示行尾不得有多余的空格。然后,指定files屬性,表示檢查目標(biāo)是Gruntfile.js文件,以及l(fā)ib目錄的所有子目錄下面的JavaScript文件。
concat用來合并同類文件,它不僅可以合并JavaScript文件,還可以合并CSS文件。
concat: { js: { src: ['lib/module1.js', 'lib/module2.js', 'lib/plugin.js'], dest: 'dist/script.js' } css: { src: ['style/normalize.css', 'style/base.css', 'style/theme.css'], dest: 'dist/screen.css' } },
js目標(biāo)用于合并JavaScript文件,css目標(biāo)用語合并CSS文件。兩者的src屬性指定需要合并的文件(input),dest屬性指定輸出的目標(biāo)文件(output)。
uglify模塊用來壓縮代碼,減小文件體積。
uglify: { options: { banner: bannerContent, sourceMapRoot: '../', sourceMap: 'distrib/'+name+'.min.js.map', sourceMapUrl: name+'.min.js.map' }, target : { expand: true, cwd: 'js/origin', src : '*.js', dest : 'js/' } },
上面代碼中的options屬性指定壓縮后文件的文件頭,以及sourceMap設(shè)置;target目標(biāo)指定輸入和輸出文件。
copy模塊用于復(fù)制文件與目錄。
copy: { main: { src: 'src/*', dest: 'dest/', }, },
上面代碼將src子目錄(只包含它下面的第一層文件和子目錄),拷貝到dest子目錄下面(即dest/src目錄)。如果要更準(zhǔn)確控制拷貝行為,比如只拷貝文件、不拷貝目錄、不保持目錄結(jié)構(gòu),可以寫成下面這樣:
copy: { main: { expand: true, cwd: 'src/', src: '**', dest: 'dest/', flatten: true, filter: 'isFile', }, },
watch模塊用來在后臺運(yùn)行,監(jiān)聽指定事件,然后自動運(yùn)行指定的任務(wù)。
watch: { scripts: { files: '**/*.js', tasks: 'jshint', options: { livereload: true, }, }, css: { files: '**/*.sass', tasks: ['sass'], options: { livereload: true, }, }, },
設(shè)置好上面的代碼,打開另一個進(jìn)程,運(yùn)行g(shù)runt watch。此后,任何的js代碼變動,文件保存后就會自動運(yùn)行jshint任務(wù);任何sass文件變動,文件保存后就會自動運(yùn)行sass任務(wù)。
需要注意的是,這兩個任務(wù)的options參數(shù)之中,都設(shè)置了livereload,表示任務(wù)運(yùn)行結(jié)束后,自動在瀏覽器中重載(reload)。這需要在瀏覽器中安裝livereload插件。安裝后,livereload的默認(rèn)端口為localhost:35729,但是也可以用livereload: 1337的形式重設(shè)端口(localhost:1337)。
下面是另外一些有用的模塊。
(1)grunt-contrib-clean
該模塊用于刪除文件或目錄。
clean: { build: { src: ["path/to/dir/one", "path/to/dir/two"] } }
(2)grunt-autoprefixer
該模塊用于為CSS語句加上瀏覽器前綴。
autoprefixer: { build: { expand: true, cwd: 'build', src: [ '**/*.css' ], dest: 'build' } },
(3)grunt-contrib-connect
該模塊用于在本機(jī)運(yùn)行一個Web Server。
connect: { server: { options: { port: 4000, base: 'build', hostname: '*' } } }
connect模塊會隨著grunt運(yùn)行結(jié)束而結(jié)束,為了使它一直處于運(yùn)行狀態(tài),可以把它放在watch模塊之前運(yùn)行。因?yàn)閣atch模塊需要手動中止,所以connect模塊也就會一直運(yùn)行。
(4)grunt-htmlhint
該模塊用于檢查HTML語法。
htmlhint: { build: { options: { 'tag-pair': true, 'tagname-lowercase': true, 'attr-lowercase': true, 'attr-value-double-quotes': true, 'spec-char-escape': true, 'id-unique': true, 'head-script-disabled': true, }, src: ['index.html'] } }
上面代碼用于檢查index.html文件:HTML標(biāo)記是否配對、標(biāo)記名和屬性名是否小寫、屬性值是否包括在雙引號之中、特殊字符是否轉(zhuǎn)義、HTML元素的id屬性是否為唯一值、head部分是否沒有script標(biāo)記。
(5)grunt-contrib-sass模塊
該模塊用于將SASS文件轉(zhuǎn)為CSS文件。
sass: { build: { options: { style: 'compressed' }, files: { 'build/css/master.css': 'assets/sass/master.scss' } } }
上面代碼指定輸出文件為build/css/master.css,輸入文件為assets/sass/master.scss。
(6)grunt-markdown
該模塊用于將markdown文檔轉(zhuǎn)為HTML文檔。
markdown: { all: { files: [ { expand: true, src: '*.md', dest: 'docs/html/', ext: '.html' } ], options: { template: 'templates/index.html', } } },
上面代碼指定將md后綴名的文件,轉(zhuǎn)為docs/html/目錄下的html文件。template屬性指定轉(zhuǎn)換時采用的模板,模板樣式如下。
<!DOCTYPE html> <html> <head> <title>Document</title> </head> <body> <div id="main" class="container"> <%=content%> </div> </body> </html>
Frederic Hemberger, A build tool for front-end projects
Mária Jur?ovi?ová, Building a JavaScript Library with Grunt.js
Swapnil Mishra, Simplifying Chores with Grunt
AJ ONeal, Moving to GruntJS
Grunt Documentation, Configuring tasks
Landon Schropp, Writing an Awesome Build Script with Grunt
Mike Cunsolo, Get Up And Running With Grunt
Matt Bailey, A Beginner’s Guide to Using Grunt With Magento
Paul Bakaus, Supercharging your Gruntfile