本文由大漠根据Mike Cunsolo的《Get Up And Running With Grunt》所译,整个译文带有我们自己的理解与思想,如果译得不好或不对之处还请同行朋友指点。如需转载此译文,需注明英文出处:http://coding.smashingmagazine.com/2013/10/29/get-up-running-grunt/,以及作者相关信息
——作者:Mike Cunsolo
——译者:大漠
在本文中,我们将探讨如何使用Grunt在项目和你的网站更快。我们将简要的分析一下Grunt可以做什么,如何设置和使用各种插件来做举世瞩目的项目。
我们将看看如何构建一个简单的表单验证器,使用Sass预处理器,如何使用grunt-cssc
和CssMin结合压缩我们的CSS,如何使用HTMLHint确保我们的HTML书写正确,如何在fly.Lastly
构建我们的压缩资产,也会看看使用UglifyJS减小我们JavaScript的大小和确保我们的网站使用尽可能的少的带宽。
Grunt.js是一个JavaScript任务执行者,他可以帮助你执行重复性任务,比如压缩,编译,单元测试等
开始使用Grunt
大多数开发人员都一致认为,JavaScript开发的速度和节奏在过去的几年里已经相当惊人。不管是Backbone.js和Ember.js的框架还是JS Bin社区,这种语言的发展变化不仅提高我们网站的用户体验,而且我们也在开始使用他们。
当您在使用JavaScript,您可能需要定期执行多个任务。虽然这在大多项目中都存在,但切实是一个耗时的和重复的工作方式。在这样一个活跃的社区,你会假设工具都可以自动化,加快这个过程。此时就出现了Grunt。
什么是Grunt
建立在Node.js之上,Grunt是一个基于命令行的工具,用于加快工作流程,减少用于生前之前所做的准备。它可以结速工作与自动编译一同进行。基本上,你可以使用Grunt的大部分任务来帮你处理你认为乏味的,通常需要手工配置和运行的工作。
虽然早期版本附带了JSHint和Uglyify插件,但最近的版本(版本0.4)依赖于插件做一切事情。
什么样的任务?这有一个详细的列表。我只想说,Grunt可以处理大部你想处理的事情,从压缩到加载JavaScript。他也可以用于与JavaScript无关的一系列任务,比如说将Sass和LESS编译成CSS。我们甚至使用blink当构建失败时通知我们。
为什么要使用Grunt
最爽的事情之一就是Grunt让团队做到一致性。如果你的工作是协作型的,你将要知道在代码中不一致性多么令人烦恼的。Grunt工作使用团队具有统一的命令,从而确保团队中的每个人都使用相同的标准来编写代码。毕竟,因为在团队中如何编写代码这样的小问题引起项目失败更让人感到沮丧。
Grunt也有一个令人难以置信的活跃社区,开发人员会在社区中定期发布新插件。进入的门槛相对较低,因为一个广泛的工具和自动执行的任务就可以使用。
设置
使用Grunt的首要事情就是设置Node.js。(如果你对Node.js一无所知,别担心,它只是安装为Grunt能够运行)。
一量你安装好了Node.js,你可以终端命令中输入:
$ npm install -g grunt-cli
为了确保Grunt已经正确安装,你可以运行下面的命令:
$ grunt --version
接下来在你项目的根目中创建一个package.json
和一个Gruntfile.js
文件。
//测试项目(SampleGrunt)目录结构+SampleGrunt
+----Gruntfile.js
+----package.json
创建package.json文件
JSON文件使我们能跟踪和安装我们所有开发所依赖的信息。然后,对项目工作的人会拥有当前开发依赖性,最终有助于保持同步的开发环境。
在你项目根据下创建一个文件,并且包含下面的信息:
{"name":"SampleGrunt","version":"0.1.0", "author":"Brandon Random", "private":true, "devDependencies":{ "grunt":"~0.4.0" } }
一旦你这样做了,运行下面的命令:
$ npm install
这告诉npm
所需的依赖关系,然后把它们安装在node_modules
目录中。
这个时候,你的项目的根目录下会新增加一个node_modules
的目录:
+SampleGrunt
+----Gruntfile.js
+----node_modules
+----package.json
创建Gruntfile.js文件
Gruntfile.js
本质上是一个函数,而且他的参数是grunt
。
module.exports = function(grunt){grunt.initConfig({pkg:grunt.file.readJSON('package.json')});grunt.registerTask('default',[]); };
你现在可以在你的项目根根目录下运行Grunt命令。但是在这个阶段你这样做,你将会看到以下的警告信息:
$ grunt
> Task "default" not found. Use --force to continue.
得到这样的信息是因为我们除了Grunt之外没有指定任何任务和依赖。所以,让我们这样做。但首先,让我们看看如何扩展package.json
文件。
扩展package.json文件
最好是通过Node.js来处理,它可以找到安装包并将他们安装在一起,而且只基于包文件的内容。安装所有新的依赖关系,只需要在文件中添加下面的内容:
{"name":"SampleGrunt","version":"0.1.0", "author":"Brandon Random", "private":true, "devDependencies":{ "grunt":"~0.4.0", "grunt-contrib-cssmin":"*", "grunt-contrib-sass":"*", "grunt-contrib-uglify":"*", "grunt-contrib-watch":"*", "grunt-cssc":"*", "grunt-htmlhint":"*", "matchdep":"*" } }
在命令行中输入下面命令可以完成安装流程:
$ npm install
执行完上面的命令后,所有的依赖关系都将安装在node_modules
目录中:
+SampleGrunt
+----Gruntfile.js
+----node_modules
+++----css-condense
+++----grunt +++----grunt-contrib-cssmin +++----grunt-contrib-sass +++----grunt-contrib-uglify +++----grunt-contrib-watch +++----grunt-cssc +++----grunt-htmlhint +++----matchdep +----package.json
在Grunt中加载npm任务
现在任务包已安装好了,他们已经加载到Grunt中,我们现在能用他们做些什么。使用matchdep
我们可以使用一行代码加载所有的任务。这是很好的一点,因为现在依赖关系的列表都只包括了包文件。
在顶部的Gruntfile.js
的grunt.initConfig
上面粘贴下面的一段代码:
require("matchdep").filterDev("grunt-*").forEach(grunt.loadNpmTasks);
没有matchdep
,我们需要为每个依赖关系写grunt.loadNpmTasks('grunt-task-name')
,这将很快增加和安装其他插件。
因为插件加载到Grunt中,我们有可能会指定开始选项。首先是HTML文件(index.html),包含下面的内容:
<!DOCTYPE html>
<html lang="en"> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width; initial-scale=1.0; maximum-scale=1.0;"> <title>Enter your first name</title> <link rel="stylesheet" href="build/css/master.css"> </head> <body> <label for="firstname">Enter your first name</label> <input id="firstname" name="firstname" type="text"> <p id="namevalidation" class="validation"></p> <script type="text/javascript" src="build/js/base.min.js"></script> </body> </html>
使用HTMLHint验证
在grunt.initConfig
中添加这样的配置:
htmlhint: {build: {options: {'tag-pair':true,'tagname-lowercase':true,'attr-lowercase':true, 'attr-value-double-quotes':true, 'doctype-first':true, 'spec-char-escape':true, 'id-unique':true, 'head-script-disabled':true, 'style-disabled':true }, src:['index.html'] } }
一个插件的配置通常是这样的:插件的名称(不带grunt-contrib-/grunt-
前缀)然后是你选择的一个或更多的目标(可以用于创建自定义插件的文件),一个options
对象,和这个文件效果。现在你在终端运行grunt htmlhint
,它会查看源文件,并确保我们的HTML没有错误!然而,一小时手动输入这个命令数次,将让你的工作很快变得乏味。
在命令的终端执行grunt htmlhint
命令后,如果index.html
没有出错时,在终端会有信息提出:
Running "htmlhint:build" (htmlhint) task >> 0 files lint free. Running "htmlhint:src" (htmlhint) task >> 1 file lint free.
每次保存文件自动化运行任务
watch
任务可以根据被保存的文件运行一个独特的任务。在grunt.initConfig
加入下面的代码:
watch: {html: { files:['index.html'], tasks:['htmlhint'] } }
然后在终端运行grunt watch
。你会在终端看到下面的提示信息:
Running "watch" task
Waiting...
现在尝试在index.html
中添加一个注释。你会注意到,当文件被保存,验证是自动执行的!你会在终端看到对应的信息:
Running "watch" task
Waiting...OK >> File "index.html" changed. Running "htmlhint:build" (htmlhint) task >> 0 files lint free. Running "htmlhint:src" (htmlhint) task >> 1 file lint free. Done, without errors. Completed in 0.538s at Fri Nov 01 2013 22:54:03 GMT+0800 (CST) - Waiting...
这对于开发者来说是一个很好的东西,因为它意味着,你在编写代码的时候他会默默的进行验证,如果代码没有通过验证测试的时候他会失败(和它会告诉你是什么问题引起的错误)。
注意,grunt watch
将会一直运行,直到关闭终端或者直到它停止(在Mac上执行control + c
可以直接停止grunt watch
命令)。
尽可能让JavaScript文件小
让我们创建一个JavaScript文件来验证一个用户名。尽可能我可以分简单,我们将检查的只是非字母字符。我们也会利用JavaScript的strict
模式,它阻止我们品尼高有效但质量差劲的JavaScript。把下面的代码粘贴到assets/js/base.js
:
function Validator(){ "use strict"; } Validator.prototype.checkName = function(name) { "use strict"; return(/[^a-z]/i.test(name) === false); }; window.addEventListener('load',function(){ "use strict"; document.getElementById('firstname').addEventListener('blur',function(){ var _this = this; var validator = new Validator(); var validation = document.getElementById('namevalidation'); if (validator.checkName(_this.value) === true){ validation.innerHTML = 'Looks good! :)'; validation.className = 'validation yep'; _this.className = "yep"; } else { validation.innerHTML = 'Looks bad! :('; validation.className = "validation nope"; _this.className = "nope"; } }); });
我们使用UglifyJS来压缩这个文件,在grunt.initConfig
中加入下面代码:
uglify: {build: { files: { 'build/js/base.min.js' : ['assets/js/base.js'] } } }
UglifyJS压缩我们源文件中所有的变量和函数名,让我们文件尽可能少的占用空间,然后剪去空白和注释——非常适合用于生产的JavaScript。接着,我们需要为我们的Uglify的JavaScript创建一个watch
任务。将下面的代码添加到watch
配置中:
watch: {js: { files: ['assets/js/base.js'], tasks: ['uglify'] } }
从Sass源文件创建CSS
Sass用来处理CSS是令人难以置信的有用,特别是在一个团队。通常在源文件中写更少的代码,因为Sass可以使用函数和变量产生大的CSS代码块。如何使用Sass本身有点超出了本文的范围,所以,在这个阶段你还不习惯和学习一个预处理器,你可以跳过这一节。我们将介绍一个非常简单的用例,使用具有变量和混合的SCSS,这非常类似于CSS!
Grunt的Sass插件需要sass gem
。你将需要在你的系统上安装Ruby。你可以在你的终端使用下面的命令检查你的系统是否安装了Ruby:
ruby -v
如果你的系统已经安装了Ruby,在终端执行上面的命令会得到如下所示的提示信息:
ruby 1.9.3p392 (2013-02-22 revision 39386) [x86_64-darwin12.4.0]
接下来运行下面的命令安装Sass:
gem install sass
有关于Sass的更多教程,你可以点击这里查阅。——大漠
根据您的配置,你可以需要通过sudo
命令来运行这个命令——例如sudo gem install sass
——这个时候终端会要求你输入你的电脑登录密码。当Sass安装好后,创建一个新的目录叫作assets
,并在里面创建另一个叫sass
目录。在这个目录中创建一个新的文件名master.scss
,并将下面的代码粘贴进行:
@mixin prefix($property, $value, $prefixes: webkit moz ms o spec) { @each $p in $prefixes { @if $p == spec { #{$property}: $value; } @else { -#{$p}-#{$property}: $value; } } } $input_field: #999; $input_focus: #559ab9; $validation_passed: #8aba56; $validation_failed: #ba5656; $bg_colour: #f4f4f4; $box_colour: #fff; $border_style: 1px solid; $border_radius: 4px; html { background: $bg_colour; } body { width: 720px; padding: 40px; margin: 80px auto; background: $box_colour; box-shadow: 0 1px 3px rgba(0, 0, 0, .1); border-radius: $border_radius; font-family: sans-serif; } input[type="text"] { @include prefix(appearance, none, webkit moz); @include prefix(transition, border .3s ease); border-radius: $border_radius; border: $border_style $input_field; width: 220px; } input[type="text"]:focus { border-color: $input_focus; outline: 0; } label, input[type="text"], .validation { line-height: 1; font-size: 1em; padding: 10px; display: inline; margin-right: 20px; } input.yep { border-color: $validation_passed; } input.nope { border-color: $validation_failed; } p.yep { color: $validation_passed; } p.nope { color: $validation_failed; }
你将发现SCSS扩展起来比传统的Sass更像CSS。这个样式表中使用了Sass的两个特性:混合(mixins)和变量(variables)。Mixins
就像一个函数,可以将一些参数传递给它,用来构造CSS块,然后重用。
变量是特别有用的十六进制颜色,我们可以是构建一个调色板,可以在一个地方改变,非常快的调整整个方面设计。Mixin可以用于前缀等规则上,例如appearance
和transition
等属性,它可以减少写很多重复性代码。
在处理大型样式表时,当一个团队的其他成员想要更新一个样式,什么方法可以减少行数使用文件易于阅读。
除了Sass,Grunt-cssc和CSS规则结合在一起,确保生成的CSS有最和的重复代码。在中大型项目是有很多风格是重复的,这显得非常的有用。然而,输出的文件并不总是总小的。这样就出现了cssmin
任务。它不仅可以去掉代码中的空格,而且琮可以将颜色转换成最短值(因此,white
将转换成#fff
)。在Gruntfile.js
中加入下面的任务:
cssc: {build: {options: {consolidateViaDeclarations: true,consolidateViaSelectors: true,consolidateMediaQueries: true},files: {'build/css/master.css':'build/css/master.css' } } }, cssmin: { build: { src: 'build/css/master.css', dest: 'build/css/master.css' } }, sass: { build: { files: { 'build/css/master.css':'assets/sass/master.scss' } } }
现在,我们有一些在适当的地方处理样式表,这些任务也应该自动运行。build
目录是Grunt自动创建的,用来放置脚本,CSS和(如果这是一个完整的网站)压缩图像。这意味着assets
目录是用于放置开发的文件,里面文件内容有可能有大量的注释或很多文件,然而build
目录,是用来放置用于生产环境所需的文件,里面只留下了心样能优化后的文件。
我们将定义一组新的任务来处理CSS。在gruntfile.js
中添加下面的代码,下面是默认的task
:
grunt.registerTask('buildcss', ['sass', 'cssc', 'cssmin']);
现在,当运行grunt buildcss
,所有CSS任务将一个接一个执行。这远比运行grunt sass
,接着grunt cssc
,接着grunt cssmin
更快。我们可以通过配置watch
做所有更新,自动运行。
watch: {css: { files: ['assets/sass/**/*.scss'], task: ['buildcss'] } }
在运行grunt buildcss
命令后,命令终端运行有关于CSS方面的任务,命令端将显示:
Running "sass:build" (sass) taskRunning "cssc:build" (cssc) task
File "build/css/master.css" created.Running "cssmin:build" (cssmin) task
File build/css/master.css created.Done, without errors.
同时,项目根目录下将自动创建一个build
目录,此时整个项目目录结构如下:
+SampleGrunt
+----assets
+++----js
+++----sass
+----build +++----css +----Gruntfile.js +----node_modules +++----css-condense +++----grunt +++----grunt-contrib-cssmin +++----grunt-contrib-sass +++----grunt-contrib-uglify +++----grunt-contrib-watch +++----grunt-cssc +++----grunt-htmlhint +++----matchdep +----package.json
这路径你看起来有点奇怪。基本上,它会依次检查assets/sass
目录下所有目录中的.scss
文件,它充行我们创建尽可能多的Sass文件,而无需在gruntfile.js
中添加路径。添加这个之后,gruntfile.js
将会像下面这样:
module.exports = function(grunt){"use strict";require("matchdep").filterDev("grunt-*").forEach(grunt.loadNpmTasks); grunt.initConfig({ pkg: grunt.file.readJSON('package.json'), cssc: { build: { options: { consolidateViaDeclarations: true, consolidateViaSelectors: true, consolidateMediaQueries: true }, files: { 'build/css/master.css': 'build/css/master.css' } } }, cssmin: { build: { src: 'build/css/master.css', dest: 'build/css/master.css' } }, sass: { build: { files: { 'build/css/master.css': 'assets/sass/master.scss' } } }, watch: { html: { files: ['index.html'], tasks: ['htmlhint'] }, js: { files: ['assets/js/base.js'], tasks: ['uglify'] }, css: { files: ['assets/sass/**/*.scss'], tasks: ['buildcss'] } }, htmlhint: { build: { options: { 'tag-pair': true, // Force tags to have a closing pair 'tagname-lowercase': true, // Force tags to be lowercase 'attr-lowercase': true, // Force attribute names to be lowercase e.g. <div id="header"> is invalid 'attr-value-double-quotes': true, // Force attributes to have double quotes rather than single 'doctype-first': true, // Force the DOCTYPE declaration to come first in the document 'spec-char-escape': true, // Force special characters to be escaped 'id-unique': true, // Prevent using the same ID multiple times in a document 'head-script-disabled': true, // Prevent script tags being loaded in the for performance reasons 'style-disabled': true // Prevent style tags. CSS should be loaded through }, src: ['index.html'] } }, uglify: { build: { files: { 'build/js/base.min.js': ['assets/js/base.js'] } } } }); grunt.registerTask('default', []); grunt.registerTask('buildcss', ['sass', 'cssc', 'cssmin']); };
我们现在有一个静态的HTML页面,一个放了Sass和JavaScript资源的assets
目录,以及一个放了整理的CSS和JavaScript文件在build
目录中,以及一个package.json
和gruntfile.js
文件。
现在,你应该有一个很坚实的基础,可以进一步探讨Grunt。正如前面所提到的,一个令人难以置信的活跃的开发者社区在不断的提供前端插件。我的建议是,请到插件库查阅这300多个插件。
译者手语:整个翻译依照原文线路进行,并在翻译过程略加了个人对技术的理解。如果翻译有不对之处,还烦请同行朋友指点。谢谢!
如需转载,烦请注明出处:
英文原文:http://coding.smashingmagazine.com/2013/10/29/get-up-running-grunt/
中文译文:http://www.w3cplus.com/tools/get-up-running-grunt.html