目的:自动化构建项目;完成压缩,编译,单元测试,代码检查以及打包发布的任务。
生成线上调试source map,代码行统计。
常用Grunt任务:
Cache :cache-breaker、usemin + rev
CSS :grunt-recess、less、cssmin、uncss、csslint(CSS检测)
JS :uglify(压缩JS,Beautify JS,生成线上调试source map)、 jshint(JS代码检测及检测规则配置)、sloc(代码行统计)、ng-min(Angular代码压缩)、plato(代码复杂度、可维护性)
HTML :html-validation、
测试 :jasmine、qunit
基本任务 :clean、concat、copy、watch
Cache清除缓存任务
. usemin + rev
目的是清除浏览器缓存。
给静态文件重名后,再将html、css中的静态文件引用的名称替换成新文件名。
rev给静态文件重命名。usemin替换html、css中引用的静态文件名。
虽然usemin任务执行之前会自动执行 concat、uglify、cssmin任务。一般还是要自己定义任务:要先执行clean清除重命名的静态文件;concat合并文件;recess重新编译和压缩css文件;uglify重新压缩js文件。最后再执行useminPrepare,rev,usemin。
useminPrepare: { src: "<%= site.destination %>/{,*/ /* }*.html", options: { dest: "<%= site.destination %>", root: "<%= site.destination %>" } }, rev: { options: { algorithm: 'md5', length: 8 }, assets: { files: { src: [ '<%= kui_css %>', '<%= kui_js %>', '<%= kuidoc_css %>', '<%= kuidoc_js %>', './images/{,*/ /* }*.{png,jpg,jpeg,gif,webp}', ] } } }, usemin: { html: ["<%= site.destination %>/{,*/ /* }*.html" ], css: ["./css/*.css" ], options : { assetDirs: [ '<%= site.destination %>', './css' ] } },
. cache-breaker
这个任务也是清除静态文件的浏览器缓存,相对usemin简单些。但是,cache-breaker只替换head标签中的url,不替换body中的链接。一般都会有置底加载的JS文件,这个任务就不太适用。
// Turn these :
<script src="/js/dist/combined.min.js"></script>
<link href="/css/style.css"></link>
// Into these :
<script src="/js/dist/combined.min.js?rel=123456"></script>
<link href="/css/style.css?rel=123456"></link>
cachebreaker: { kuidoc_js: { asset_url : '<%= kuidoc_js %>', files: { src : './_kui/*.html' } }, kuidoc_css: { asset_url : '<%= kuidoc_css %>', files: { src : './_kui/*.html' } } },
. cacheBust
这个任务也是清除静态文件的浏览器缓存,支持的静态文件格式有:CSS, JavaScript, images 和 favicons。这个任务会忽略远程静态文件,因为一般这些远程文件是存储在CDN上的静态文件通常是很稳定的库文件,比如jQuery、Bootstrap...它们的url通常会有个版本标志,通过这个标识符来避免浏览器缓存。你的页面最好引用用这些标准的url,保证浏览器缓存的命中率。304总是比200快。也就是,类似以下的url会被忽略:
<link href="//netdna.bootstrapcdn.com/twitter-bootstrap/2.3.2/css/bootstrap-combined.min.css" rel="stylesheet">
没有采用这个任务的理由:
1、项目中css和js独立部署和引用,静态文件引入的是绝对地址,我们也希望能够清除缓存,这个任务不适用。
2、cacheBust针对html文件中的静态文件寻址是相对于Grunt.js文件的,这就要求html文件和Grunt.js在同一个文件夹中。
3、任务执行后有漏掉的css。
JS任务
. Uglify
这个任务一直在更新增强:
1、压缩我们的JavaScript文件;
2、Beautify反压缩JS文件;
3、可以用它给多文件生成source-map。
注意:Angular压缩的时候会将$scope
变成 a
,压缩前要先执行ng-min任务。
uglify: { controller: { options: { banner: '<%= banner %>', report: 'min', // for Angular keyword $scope mangle: false, // except: ['jQuery', 'Angular', '$scope'], // for source map sourceMapRoot: '<%= doc_dir %>', sourceMap: '<%= doc_dir %>/js/kui-controller.min.js.map', sourceMapUrl: '<%= doc_dir %>/js/kui-controller.min.js.map' }, src: '<%= doc_dir %>/js/kui-controller.ngmin.js', dest: '<%= doc_dir %>/js/kui-controller.min.js' } },
. ng-min
AngularJS控制器构造函数通常简写成
$var SomeCtrl = function($scope, $element) {}
$scope, $element 是参数,但换个名字代码就不能正常运行了。也就是AngularJS是通过控制器构造函数的参数名字来推断依赖服务名称。所以如果你要压缩控制器的JS代码,它所有的参数也同时会被压缩,这时候依赖注入系统就不能正确的识别出服务了。
为了克服压缩引起的问题,
第一种方法是在控制器函数里面给$inject属性赋值一个依赖服务标识符的数组。
第二种方法使用Javascript数组方式构造控制器:把要注入的服务放到一个字符串数组(代表依赖的名字)里,数组最后一个元素是控制器的方法函数,就像AMD声明一样,写成
$var SomeCtrl = ['$scope', '$element', function(s, e) {}
ng-min采用的是第二种方法,来避免压缩引起的依赖注入系统不能识别服务问题。
ngmin: { controllers: { src: ['<%= doc_dir %>/js/kui-controller.js'], dest: '<%= doc_dir %>/js/kui-controller.ngmin.js' } },
. sloc
计算代码行。sloc: { options: { reportType: 'json', reportPath: '<%= doc_dir %>/sloc-v<%= pkg.version %>.json', }, files: { './': '<%= doc_dir %>/js/kui-controller.js', './': [ 'kui.js' ] } },
CSS任务
. grunt-recess
包括less编译和css压缩。
recess: { options: { compile: true, banner: '<%= banner %>' }, kui: { src: ['./less/kui.less'], dest: './css/kui.css' }, kui_min: { options: { compress: true }, src: ['./less/kui.less'], dest: '<%= kui_css %>' } },
. Less
less编译
. cssmin
css压缩(有时压缩后浏览器无法载入)
验证和测试任务
. jshint
检查js代码错误。通过.jshintrc 来定义检测项。
jshint: { //JSHint (http://www.jshint.com/docs/options/) options: { jshintrc: 'js/.jshintrc', globals: { jquery: true } }, kui: { src: ['<%= js_dir %>/modal.confirm.js'] }, controller: { src: ['<%= doc_dir %>/js/controller/*.js'] }, kuidoc: { src: ['<%= doc_dir %>/js/<%= pkg.name %>.min.js', '<%= doc_dir %>/js/<%= pkg.name %>.js'] } },
{ /* https://gist.github.com/haschek/2595796 */ "asi" : true, // Tolerate Automatic Semicolon Insertion (no semicolons). "boss" : false, // Tolerate assignments inside if, for & while. Usually conditions & loops are for comparison, not assignments. "browser" : true, // Standard browser globals e.g. `window`, `document`. "curly" : false, // Require {} for every new block or scope. "debug" : true, // Allow debugger statements e.g. browser breakpoints. "devel" : true, // Allow development statements e.g. `console.log();`. "eqeqeq" : false, // true: Require triple equals (===) for comparison "eqnull" : true, // true: Tolerate use of `== null` "expr" : true, // Tolerate `ExpressionStatement` as Programs. "laxbreak" : true, // Tolerate unsafe line breaks e.g. `return [\n] x` without semicolons. "laxcomma" : true, // Suppress warnings about comma-first coding style. "validthis": true, // Tolerate strict violations when the code is running in strict mode and you use this in a non-constructor function. }
. html-validation
检查HTMlL。
options.relaxerror定义需要忽略的报错,可以基于正则表达式匹配。比如,AngularJS中需要忽略HTML标签中的“ng-”开头的属性: 'Attribute ng-[a-z]+ not allowed on element [a-z]+ at this point.'这个配置项非常有用。
validation: { options: { reset: true, relaxerror: [ 'Bad value X-UA-Compatible for attribute http-equiv on element meta.', 'Element link is missing one or more of the following attributes: itemprop, property, rel.', 'Attribute srcset not allowed on element img at this point.', 'Attribute ng-[a-z]+ not allowed on element [a-z]+ at this point.', 'document type does not allow element [a-z]+ here', '& did not start a character reference. *', 'Element xmp not allowed as child of element div in this context. *' ] }, files: { src: ["_kui/*.html"] } },
. uncss
检测HTML中没有使用到的css,并去除多余的css。
uncss: { dist: { files: { 'dist/css/tidy.css': ['app/index.html','app/about.html'] } } },
. jasmine
前端单元测试
基本grunt任务
. clean
清除文件,每次编译,需要清除旧文件。
. concat
合并文件。注意:如果concat后的文件不清除,会不停的在该文件内容的最前concat内容。
. copy
拷贝代码到build路径。
. watch
使用grunt watch
来运行这个任务。监视特定目录中的变化,然后把代码编译/打包。
注意:clean任务第一个执行,copy任务最后执行。
安装命令
//安装Grunt的命令行接口(CLI) npm install -g grunt-cli npm install grunt-contrib-clean --save-dev npm install grunt-contrib-copy --save-dev npm install grunt-contrib-concat --save-dev npm install grunt-contrib-uglify --save-dev npm install grunt-contrib-jshint --save-dev npm install grunt-recess --save-dev npm install grunt-html-validation --save-dev npm install grunt-contrib-watch --save-dev npm install grunt-cache-bust --save-dev npm install grunt-cache-breaker --save-dev npm install grunt-jekyll --save-dev npm install grunt-ngmin --save-dev npm install grunt-sloc --save-dev npm install grunt-contrib-less --save-dev npm install grunt-contrib-cssmin --save-dev npm install grunt-contrib-jasmine --save-dev
加载所需要的Grunt插件
grunt.loadNpmTasks('grunt-contrib-clean'); grunt.loadNpmTasks('grunt-contrib-concat'); grunt.loadNpmTasks('grunt-contrib-jshint'); grunt.loadNpmTasks('grunt-ngmin'); grunt.loadNpmTasks('grunt-contrib-uglify'); grunt.loadNpmTasks('grunt-recess'); grunt.loadNpmTasks('grunt-html-validation'); grunt.loadNpmTasks('grunt-jekyll'); grunt.loadNpmTasks('grunt-cache-breaker'); grunt.loadNpmTasks('grunt-rev'); grunt.loadNpmTasks('grunt-usemin'); grunt.loadNpmTasks('grunt-contrib-copy'); grunt.loadNpmTasks('grunt-contrib-watch'); grunt.loadNpmTasks('grunt-sloc');
http://24ways.org/2013/grunt-is-not-weird-and-hard/
http://mikemclin.net/configuring-package-json-and-gruntfile-js/
Semantic Versioning Specification http://semver.org
NPM | package.json https://npmjs.org/doc/json.html