Grunt Boilerplate
本文由大漠根据Mark McDonnell的《Grunt Boilerplate》所译,整个译文带有我们自己的理解与思想,如果译得不好或不对之处还请同行朋友指点。如需转载此译文,需注明英文出处:http://www.integralist.co.uk/posts/grunt-boilerplate/,以及作者相关信息
——作者:Mark McDonnell
——译者:大漠
Grunt是什么?
Grunt是JavaScript创建的任务管理器。这意味着它将帮助你自动化运行任务,比手工运行任务节约更多时间。
Grunt可以预先构建很多繁重的任务,能自动为大多数开发人员提供典型的工作流程。如果你有具体的要求,但并不没有现在提供,你可以编写自己自定义的任务。
大约有700多种使用Grunt创建的任务(截至2013年5月),并且这个任务数还在不断增加。一些很受欢迎的任务,我将在这篇文章中重复介绍。
- Sass(CSS预处理器)
- RequireJS(AMD/脚本加载程序)
- JSHint(JavaScript代码质量)
- Jasmine BDD(单元测试框架)
- ImageMin(压缩图像)
- HTMLMin(压缩HTML)
但还有更多这样的任务:CoffeeScript JavaScript编译,连接文件,连接到一个Web服务器,复制文件和文件夹,handlebar预编译模板,Web浏览器中重新加载,编译代码文档——这里仅列出了其中的一些。
到Grunt插件页面,可以看到更多的任务例表(也可以从GitHub中下载)。
安装
Grunt使用Node.js和Node的包管理器系统(NPM)来安装和执行Grunt任务。
原则上安装和运行Grunt(Grunt 0.4)需要有三个项目支持:
- Node.js
- NPM
- Grunt CLI(命令行接口)
在OS X系统中最简单的方法使用Homebrew程序来安装他们(如果你使用的是Windows系统,你需要自己寻找相关安装方法)。
扩展阅读
- Setting up Node.js and npm on Mac OSX
- Installing Node, NPM and Express on OSX from Scratch
- Installing and running node.js on Mac OS X
- Install Node.js
- How to Install Node.js
- Installing node.js, npm, and redis on Mac OS X
- Installing Node and npm
- Install Node.js and NPM on Windows
- Install NPM Packages Automatically for Node.js on Windows Azure Web Site
细节可能不同,但是如果你安装了Homebrew,你可以在你的命令终端执行下面的命令安装Node.js和NPM:
brew install node
下一步是安装Grunt CLI,你可以使用:npm install -g grunt-cli
(-g
的意思在你的操作系统全局安装Grunt,你可以在你的系统任何地方使用grunt
命令)。
现在还想运行:npm install -g grunt-init
和有效的安装Grunt的基本要求。
Package.json
在你可以开始使用Grunt之前,你将需要一个package.json
文件,用来存储所有的基本配置设置。
你可以执行npm init
命令自动生成该文件。
以下是我的Grunt Boilerplate项目中的package.json
文件样版:
{
"name": "Grunt Boilerplate",
"version": "0.1.0",
"description": "This is a project set-up using Grunt to take case of some standard tasks such as: compiling AMD based modules using RequireJS, watching/compiling Sass into CSS, watching/linting JS code and some other things such as running unit tests",
"main": "Gruntfile.js",
"dependencies": {},
"devDependencies": {
"grunt": "~0.4.1",
"grunt-contrib-watch": "~0.3.1",
"grunt-contrib-jshint": "~0.4.3",
"grunt-contrib-uglify": "~0.2.0",
"grunt-contrib-requirejs": "~0.4.0",
"grunt-contrib-sass": "~0.3.0",
"grunt-contrib-imagemin": "~0.1.4",
"grunt-contrib-htmlmin": "~0.1.3",
"grunt-contrib-jasmine": "~0.4.2",
"grunt-template-jasmine-istanbul": "~0.2.1",
"grunt-template-jasmine-requirejs": "~0.1.1",
"grunt-contrib-connect": "~0.3.0"
},
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"repository": {
"type": "git",
"url": "git@github.com:Integralist/Grunt-Boilerplate.git"
},
"keywords": [
"Grunt",
"JavaScript"
],
"author": "Mark McDonnell",
"license": "MIT"
}
大多数可以运行npm init
填允提供的内容和重置(如devDependencies
)自动生成的需要安装的Grunt任务(请看下一节)。
依赖性
通常我们不希望依赖我们安装全局的Grunt(记得我们使用-g
标记来安装全局的Grunt)。
理由是,依赖你安装项目X可能不同于你的下一个项目。
例如,您可以安装的Grun使用的是1.0版本,在下一个项目中可能已经更新到2.0版本,在他们的API中部分主要功能已更新。所以如果你安装了全局的更新任务,你上次项目不会因为使用旧的API而打破。
但是你仍然想要使用最新和最强的版本。所以你要安装全局的Grunt替代本地的(例如,你目前正在运行的项目安装到特定的项目),这意味项目之间没有相冲突的机会。
下面是我的Grunt Boilerplate项目中安装的Grunt任务:
-
npm install grunt --save-dev
-
npm install grunt-contrib-watch --save-dev
-
npm install grunt-contrib-jshint --save-dev
-
npm install grunt-contrib-uglify --save-dev
-
npm install grunt-contrib-requirejs --save-dev
-
npm install grunt-contrib-sass --save-dev
-
npm install grunt-contrib-imagemin --save-dev
-
npm install grunt-contrib-htmlmin --save-dev
-
npm install grunt-contrib-connect --save-dev
-
npm install grunt-contrib-jasmine --save-dev
-
npm install grunt-template-jasmine-requirejs --save-dev
这里有几件事情要注意:第一你要先安装Grunt(这是Grunt CLI的一部分)。我们已经讨论过了,这意味着我们可以安装我们需要的不同的Grunt版本(根据项目需求)。
另一件事情是使用--save-dev
标记,这意味着我们的package.json
文件会自动更新,包括依赖我们刚刚安装的。
因为这些依赖荐是安装在本地,你会发现一个新的node_module
文件夹出现在您的项目中。这个目录包含上述所有依赖项目/我们刚刚安装的任务。
我建议您创建一个.gitignore
文件(您正在使用Git版本控制系统,对吗?),忽略这个文件夹(你不想最终将这些模块提交到你的版本库让其他用户下载,最好让他们按上面的指示安装自己需要的模块)。
如吧,既然我们佛教徒吧安装了依赖关系/任务,让我们看看剩下的一部分,Gruntfile.js
。
Gruntfile.js
Gruntfile.js
主要设置文件和包含为我们已经安装的每个任务的设置。
因为Grunt是使用Node.js运行,你会注意到该文件的内容是包裹在一个闭包中和分配给一个模块的exports
属性中。
module.exports = function (grunt) {
grunt.initConfig({
// our Grunt task settings
});
};
函数内我们给Grunt对象起了一个initConfig
,我们通过一个对象,在里面设置我们的任务。
第一属性集是我们设置pkg:grunt.file.readJSON('package.json')
,这意味着我们项目中可配置文件都在package.json
文件中指定。
例如,如果我们想要访问我们包的名称(如果你记得设置是"name":"Grunt Boilerplate"
),那么我们可以使用<%= pkg.name %>
从内部访问我们的对象。
从这开始,我们开始探索我们之前安装的不同的任务(每个任务的详细设置可以到想的就网站/github仓库中查阅)。
Sass
Sass的任务让我们把Sass文件编译成CSS。
这是一个示例:
sass:{
dist:{
options:{
style:'compressed',
require:['./assets/styles/sass/helpers/url64.rb']
},
expand:true,
cwd:'./app/styles/sass/',
src:['*.scss'],
dest:'./app/styles/',
ext:'.css'
},
dev:{
options:{
style:'expanded',
debugInfo:true,
lineNumbers:true,
require:['./app/styles/sass/helpers/url64.rb']
},
expand:true,
cwd:'./app/styles/sass',
src:['*.scss'],
dest:'./app/styles/',
ext:'.css'
}
}
你在上面的例子中可以看到,我们设置了两个子任务dist
和dev
。
设置两个子任务是我想当我在开发我的应用时Sass文件可以编译到CSS(包括debug信息),但当我完成我的项目时,我想将我的Sass文件编译出的CSS文件能够压缩直接引入项目中。
你会看到每个子任我指定了一个options
对象,告诉编译器如何编译Sass以及在哪里可以找到Sass的帮助脚本。
你会发现在我的Grunt Boilerplate(上面使用的示例中)包含了Sass的帮助脚本require:['./assets/styles/sass/helpers/url64.rb']
,它允许您使用一个Sass的特殊函数,将一个背景图片转换成Base64编码的这符串,这样可以更好的减少一个对图像的HTTP请求,提高性能。它也适用于IE8的Base64编码字符串,但有一个问题存在,转换出来的数据不能大于32kb。
最后,我们使用一个Grunt特定的模式,允许我们更好的得到目标和输出多个文件:expand:true
。有效的设置允许其他属性遵循它被激活(如果我们没有设置expand:true
,属性不会正常工作)。
让我们来看看其他有点接近的属性,你会看到其他任务也会使用类似的方式,所以重要的是要了解它们是如何工作的。
cwd: './app/styles/sass/'
:在这里我设置了当前工作目录(这是我想要Grunt找到我的Sass文件)。
src: ['*.scss']
:这里告诉Grunt,我想在我当前工作目录中寻找任何.scss
文件。
dest: './app/styles/'
:这里告诉Grunt,我我答应你它导出的文件放到这个目录。
ext: '.css'
:我希望每个导出的文件具有一个.css
的扩展名。
要运行这个特定的任务,我们可以打开我们的终端,执行:grunt sass
(这样将执行两个子任务),或者我们可以执行指定的子任务,像这样:grunt sass:dev
。
在这里我不会解释运行特定任务,因为所有任务执行都是相同的。
扩展阅读
- Nodejs+Grunt配置SASS项目自动编译
- Getting started with Grunt and Sass
- Lightning fast Sass compiling with libsass, Node-sass and Grunt-sass
- grunt-contrib-sass
- Compile SASS files into one CSS file | Grunt JS Tutorial
- Compiling SASS and Compass with Grunt
- Import a Whole Directory with Sass (Using Grunt)
RequireJS
这个任务是来自于RequireJS的一个有效的r.js
构建脚本,因此我不想详细介绍如何构建这个脚本,相反我会告诉你r.js文档,但要知道在使用r.js
之前,你要完成像下面的设置。
requirejs:{
compile:{
options:{
baseUrl:'./app',
mainConfigFile:'./app/main.js',
dir:'./app/release',
fileExclusionRegExp:/^\.|node_modules|Gruntfile|\.md|package.json/,
modules:[
{
name:'main'
}
]
}
}
}
扩展阅读
- grunt-contrib-requirejs
- Backbone, RequireJS, Jasmine, PhantomJS, and Grunt
- Grunt + RequireJS with multi-page website
- 巧用 RequireJS Optimizer 给传统的前端项目打包
- Optimize (Concatenate and Minify) RequireJS Projects
- Grunt – a JavaScript build tool
JSHint
JSHint是Grunt的一个任务,你可以点击这里,你可以链接到指定文件的头部。(他是用来确保你的代码根据一组你希望的规则编写代码,让代码语法更有效)。
jshint:{
files:['Gruntfile.js','app/**/*.js','!app/release/**','modules/**/*.js','specs/**/*Spec.js'],
options:{
curly:true,//如果为真,JSHint会要求你在使用if和while等结构语句时加上{}来明确代码块。
eqeqeq:true,//如果为真,JSHint会看你在代码中是否都用了===或者是!==,而不是使用==和!。
immed:true,////如果为真,JSHint要求匿名函数的调用如下:(function(){//}());而不是(function(){}(//bla bla));
latedef:true,
newcap:true,//JSHint会要求每一个构造函数名都要大写字母开头。
noarg:true,//如果为真,JSHint会禁止arguments.caller和arguments.callee的使用
sub:true,//如果为真,JSHint会允许各种形式的下标来访问对象。
undef:true,//如果为真,JSHint会要求所有的非全局变量,在使用前都被声明。
boss:true,//如果为真,那么JSHint会允许在if,for,while里面编写赋值语句。
eqnull:true,//JSHint会允许使用"== null"作比较
browser:true,
globals:{
//AMD
module: true,
require:true,
requirejs: true,
define: true,
//Environments
console: true,
//General Purpose Libraries
$: true,
jQuery:true,
//Testing
sinon: true,
describe:true,
it: true,
expect:true,
beforeEach: true,
afterEach: true
}
}
}
有些事情需要注意:我们使用了重点操作符!
,它是用来告诉Grunt忽略特定的目录。因此你可以看到我们的设置!app/release/
,这意味着忽略指定的目录,因为它可能会根据JSHint的规则集导致错误(因为我们的目录是放置通过RequireJS编译出来的简化版本的JavaScript代码)。
同时你将会看到我们已经告诉JSHint根据我们预想的东西设置了某些变量(例如全局可用变量)。我们需要这样做,因为JSHint是单独的检查我们的JavaScript文件,当我们的应用程序加载所有脚本在一起时,JSHint不会自动识别这些不同的全局变量,所以,如果我们没有告诉它,将会出错。
最后,有关于JSHint规则的完整列表,可以点击JSHint官网查阅。
扩展阅读
Jasmine BDD
Jasmine是一个单元测试框架和声明库。
这可能是一个最复杂的任务,因为它需要讨论一些其他的任务。
首要的任务它依赖于connect
任务,而这个任务启动一个使用PhantomJS的Web服务器。但是这个不要求你的脚本是否有DOM(有可能是你不做Web开发),但我们做了DOM操作以及我们需要一个DOM交互测试做对比,因此需要一个connect
连接服务器的任务。
connect:{
test:{
port:8000
}
}
接着,我们来设置实现jasmine
的任务。
jasmine:{
src:['app/**/*.js','!app/release/**'],
options:{
host:'http://127.0.0.1:8000/',
specs:'specs/**/*Spec.js',
helpers:['specs/helpers/*Helper.js','specs/helpers/sinon.js'],
template:require('grunt-template-jasmine-requirejs'),
templateOptions: {
requireConfig:{
baseUrl:'./app/',
mainConfigFile:'./app/main.js'
}
}
}
}
你会看到我们指定了一个与connect
Web服务器任务有关的host
参数。
我们设置了specs
参数,告诉Grunt在哪找到BDD规范和测试文件。
我们设置了一个helpers
参数,用来加载做单元测试所需要的额外脚本。(这里额外加载了一个叫Sinon.js的脚本,这个脚本帮助我们在单元测试中做Handles Spies,Mocks和Stubs测试)。
接下来是复杂任务中的其他一部分:我们需要一个template
的子任务,这个子任务是专门为Jasmine创建的:grunt-template-jasmine-requirejs
。我们需要这些额外的任务,因为我们使用的是AMD让我们的JavaScript代码模块化,但AMD介绍中谈到了基于动态运行中的异步加载有一个问题,如Grunt jasmin任务。
你会看到,我们只需要告诉grunt-template-jasmine-requirejs
,基于JS的目录是依靠AMD的主要文件来引导我们的应用程序。在后台会生成一个_SpecRunner.html
文件,基本上需要的每个单的AMD模块都在里面,只要开始测试就会回调所有的模块。这并不是最好的解决方案,但它是可以正常工作的。
如果测试通过了就会删除_SpecRunner.html
文件,所以你永远不会注意到它,但如果有任何测试失败,测试文件会保存在你的根目录中,这样你就可以检查文件和通过一个真正的Web浏览器手动运行这个文件,尝试和调试任何失败的测试。
扩展阅读
- grunt-jasmine-runner
- Testing your JavaScript with Jasmine and Grunt
- Run all your JavaScript Jasmine tests on every commit
- Testing javascript projects with grunt, jasmine, and jshint
- 关于前端开发谈谈单元测试
- Karma和Jasmine自动化单元测试
- 使用jasmine测试requirejs
图片压缩
ImageMin任务正像您所想象的,它会搜索出任何图像,并找出(png或jpg格式)和压缩他们成较小的文件大小。
正如你从下面的例子中所看到的,我们使用了expand:true
设置,他告诉Grunt从哪找到我们的图片和将他们导出到哪里。
imagemin: {
png: {
options: {
optimizationLevel:7
},
files:[
{
expand:true,
cwd:'./app/images',
src:['**/*.png'],
dest:'./app/images/compressed',
ext:'.png'
}
]
},
jpg: {
options:{
progressive: true
},
files: [
{
expand: true,
cwd:'./app/images',
src:['**/*.jpg'],
dest:'./app/images/compressed',
ext:'.jpg'
}
]
}
}
扩展阅读
- grunt-contrib-imagemin
- OPTIMIZING IMAGES WITH GRUNT
- Optimizing Images with Grunt
- 玩转Grunt(一): Minification
HTML压缩
HTMLMin任务是搜索出任何HTML文件,找到并压缩他们,经过压缩后他们是较小的文件。
正如下从下面的例子中看到的一样,我们没有设置expand:true
,而这一任务提供了一个稍为不同的API,告诉Grunt在哪里找到我们的HTML文件,并导出压缩版本(但实际上,如果我们需要,可以设置expand:true
)。
htmlmin: {
dist:{
options: {
removeComments: true,
collapseWhitespace: true,
removeEmptyAttributes: true,
removeCommentsFromCDATA:true,
removeRedundantAttributes: true,
collapseBooleanAttributes:true
},
files: {
//Destination: Source
'./index-min.html':'./index.html'
}
}
}
注册任务
到目前为止,我们已经看到了可以像grunt sass:dev
和grunt jshint
运行指定的Grunt任务,但你也可以设置一个自定义的任务,他不做任何事情,但运行其他任务。
例如,你可以创建一个任务,将执行一个特定的任务集。
grunt.registerTask('release', ['jshint', 'jasmine', 'requirejs', 'sass:dist', 'imagemin', 'htmlmin']);
在上面的示例中,我们已经创建了一个release
任务,运行时将把我们准备好的文件提交到我们的生产环境。我通常运行这个任务当作我完成了我的应用创建。
正如你所看到的,我的JavaScript文件不零乱,然后确保我的JavaScript通过测试。接下来是构建RequireJS脚本,然后是我的dist
中的Sass任务(一个专门为我们生产服务器,所以它压缩所有编译的CSS文件),最后是压缩好的图片和HTML文件。
当你在命令行中执行grunt
命令本身时,Grunt就会寻找一个已注册的任务,叫作default
。
grunt.registerTask('default', ['jshint', 'connect', 'jasmine', 'sass:dev']);
在这种情况下,我们要告诉它要执行JavaScript的jshint
任务,然后检查我们的单元测试和最后生成调试版本的CSS。
扩展阅读
- grunt.task
- How to set up tasks in Grunt JS
- Automating Complex Workflows with Grunt Custom Tasks
- Automating tasks with Grunt.js
监测文件
每次手动的代替运行Grunt命令,我们需要做出改变(例如,想象一下改变你的Sass文件,然后在命令终端运行Grunt命令sass:dev
获得Sass编译出来的CSS,你能在浏览器中看到其中的变化。),我们可以使用Grunt为我们努力工作和自动运行一个任务(或多个任务),指定的文件都已改变/更新。
watch: {
files: ['<%= jshint.files %>', '<%= jasmine.options.specs %>', '<%= sass.dev.src %>'],
tasks: 'default'
}
在这里你可以看到我们使用watch
单独任务,以及具体的指定语法让它知道要监控文件。
语法是:<%= jshint.files %>
,在这种情况下它的意思是“查看jshint
属性和将返回值设置其子属性files
”。
你从上面的例子中你可以看到,如果运行了default
注册任务,告诉watch
任务找到和修改JavaScript和Sass文件。
整个Grunt文件
你可以在我的Grunt Boilerplate项目中找到以下文件:
module.exports = function (grunt) {
/*
Grunt installation:
-------------------
npm install -g grunt-cli
npm install -g grunt-init
npm init (creates a `package.json` file)
Project Dependencies:
---------------------
npm install grunt --save-dev
npm install grunt-contrib-watch --save-dev
npm install grunt-contrib-jshint --save-dev
npm install grunt-contrib-uglify --save-dev
npm install grunt-contrib-requirejs --save-dev
npm install grunt-contrib-sass --save-dev
npm install grunt-contrib-imagemin --save-dev
npm install grunt-contrib-htmlmin --save-dev
npm install grunt-contrib-connect --save-dev
npm install grunt-contrib-jasmine --save-dev
npm install grunt-template-jasmine-requirejs --save-dev
*/
// Project configuration.
grunt.initConfig({
// Store your Package file so you can reference its specific data whenever necessary
pkg: grunt.file.readJSON('package.json'),
// Used to connect to a locally running web server (so Jasmine can test against a DOM)
connect: {
test: {
port: 8000
}
},
jasmine: {
/*
Note:
In case there is a /release/ directory found, we don't want to run tests on that
so we use the ! (bang) operator to ignore the specified directory
*/
src: ['app/**/*.js', '!app/release/**'],
options: {
host: 'http://127.0.0.1:8000/',
specs: 'specs/**/*Spec.js',
helpers: ['specs/helpers/*Helper.js', 'specs/helpers/sinon.js'],
template: require('grunt-template-jasmine-requirejs'),
templateOptions: {
requireConfig: {
baseUrl: './app/',
mainConfigFile: './app/main.js'
}
}
}
},
jshint: {
/*
Note:
In case there is a /release/ directory found, we don't want to lint that
so we use the ! (bang) operator to ignore the specified directory
*/
files: ['Gruntfile.js', 'app/**/*.js', '!app/release/**', 'modules/**/*.js', 'specs/**/*Spec.js'],
options: {
curly: true,
eqeqeq: true,
immed: true,
latedef: true,
newcap: true,
noarg: true,
sub: true,
undef: true,
boss: true,
eqnull: true,
browser: true,
globals: {
// AMD
module: true,
require: true,
requirejs: true,
define: true,
// Environments
console: true,
// General Purpose Libraries
$: true,
jQuery: true,
// Testing
sinon: true,
describe: true,
it: true,
expect: true,
beforeEach: true,
afterEach: true
}
}
},
requirejs: {
compile: {
options: {
baseUrl: './app',
mainConfigFile: './app/main.js',
dir: './app/release/',
fileExclusionRegExp: /^\.|node_modules|Gruntfile|\.md|package.json/,
// optimize: 'none',
modules: [
{
name: 'main'
// include: ['module'],
// exclude: ['module']
}
]
}
}
},
sass: {
dist: {
options: {
style: 'compressed',
require: ['./assets/styles/sass/helpers/url64.rb']
},
expand: true,
cwd: './app/styles/sass/',
src: ['*.scss'],
dest: './app/styles/',
ext: '.css'
},
dev: {
options: {
style: 'expanded',
debugInfo: true,
lineNumbers: true,
require: ['./app/styles/sass/helpers/url64.rb']
},
expand: true,
cwd: './app/styles/sass/',
src: ['*.scss'],
dest: './app/styles/',
ext: '.css'
}
},
// `optimizationLevel` is only applied to PNG files (not JPG)
imagemin: {
png: {
options: {
optimizationLevel: 7
},
files: [
{
expand: true,
cwd: './app/images/',
src: ['**/*.png'],
dest: './app/images/compressed/',
ext: '.png'
}
]
},
jpg: {
options: {
progressive: true
},
files: [
{
expand: true,
cwd: './app/images/',
src: ['**/*.jpg'],
dest: './app/images/compressed/',
ext: '.jpg'
}
]
}
},
htmlmin: {
dist: {
options: {
removeComments: true,
collapseWhitespace: true,
removeEmptyAttributes: true,
removeCommentsFromCDATA: true,
removeRedundantAttributes: true,
collapseBooleanAttributes: true
},
files: {
// Destination : Source
'./index-min.html': './index.html'
}
}
},
// Run: `grunt watch` from command line for this section to take effect
watch: {
files: ['<%= jshint.files %>', '<%= jasmine.options.specs %>', '<%= sass.dev.src %>'],
tasks: 'default'
}
});
// Load NPM Tasks
grunt.loadNpmTasks('grunt-contrib-watch');
grunt.loadNpmTasks('grunt-contrib-jshint');
grunt.loadNpmTasks('grunt-contrib-uglify');
grunt.loadNpmTasks('grunt-contrib-requirejs');
grunt.loadNpmTasks('grunt-contrib-sass');
grunt.loadNpmTasks('grunt-contrib-imagemin');
grunt.loadNpmTasks('grunt-contrib-htmlmin');
grunt.loadNpmTasks('grunt-contrib-connect');
grunt.loadNpmTasks('grunt-contrib-jasmine');
// Default Task
grunt.registerTask('default', ['jshint', 'connect', 'jasmine', 'sass:dev']);
// Unit Testing Task
grunt.registerTask('test', ['connect', 'jasmine']);
// Release Task
grunt.registerTask('release', ['jshint', 'jasmine', 'requirejs', 'sass:dist', 'imagemin', 'htmlmin']);
};
结论
希望本指南能给你一个开始使用Grunt自动化工作流程的很好起点。这是一个非常强大的工具,甚至开始没有任何内置的自定义任务,你可以写自己的任务与文件系统,你可以做任何你想要做的事情。
注意:我的Grunt Boilerplate项目是不断更新的,可以时刻观注我们的版本库。
译者手语:整个翻译依照原文线路进行,并在翻译过程略加了个人对技术的理解。如果翻译有不对之处,还烦请同行朋友指点。谢谢!
如需转载,烦请注明出处: