☰
Grunt 构建工具实战:以 jstutorial 仓库为例掌握 Gruntfile.js 自动化工作流
2026/10/13 12:26:00 网站建设 项目流程
  • 教程
  • 文档

【免费下载链接】jstutorial

Javascript tutorial book

项目地址:https://gitcode.com/gh_mirrors/js/jstutorial
点击查看免费下载

本指南系统讲解 Grunt——JavaScript 生态中经典的自动任务运行器:从安装grunt-cli、声明依赖,到编写Gruntfile.js的核心三方法(initConfig/loadNpmTasks/registerTask),再到 jshint、concat、uglify、watch、cssmin 等常用模块的配置写法。文中所有原理均以 jstutorial 仓库真实的 Gruntfile.js 与 package.json 为佐证,读完你将能够独立搭建一套"源码目录 → 压缩/检查/监听"的前端构建流水线。

为什么需要 Grunt:把重复性前端任务自动化

在 JavaScript 开发过程中,经常会遇到一些重复性的任务:合并文件、压缩代码、检查语法错误、将 Sass 代码转成 CSS 代码等等。通常,我们需要使用不同的工具来完成不同的任务,既重复劳动又非常耗时。Grunt 就是为了解决这个问题而发明的工具,它可以帮助我们自动管理和运行各种任务。

简单说,Grunt 是一个自动任务运行器,会按照预先设定的顺序自动运行一系列的任务,从而简化工作流程、减轻重复性工作带来的负担。

jstutorial 仓库本身就是 Grunt 的典型应用场景:站点的 JavaScript 源码放在 js/origin(未压缩的main.js、toc.js、jquery.js),压缩后的成品输出到 js;CSS 源码放在 css/origin,压缩产物输出到 css。这套"源目录 → 产物目录"的划分,正是由 Gruntfile.js 中uglify与cssmin任务的cwd/dest设计所驱动的。

安装 Grunt

Grunt 基于 Node.js,安装之前要先安装 Node.js,然后运行下面的命令安装命令行界面:

sudo npm install grunt-cli -g

其中grunt-cli表示安装的是 Grunt 的命令行界面(CLI),参数-g表示全局安装。CLI 只需全局安装一次,它的职责是在任意目录下定位并调用当前项目本地安装的grunt模块本体。

Grunt 使用模块结构,除了安装命令行界面以外,还要根据需要安装相应的模块。这些模块应该采用局部安装,因为不同项目可能需要同一个模块的不同版本。

首先,在项目的根目录下创建文本文件package.json,指定当前项目所需的模块。下面就是一个例子:

{ "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中,除了注明项目的名称和版本以外,还在devDependencies属性中指定了项目依赖的 Grunt 模块及版本约束:

  • grunt核心模块为最新的 0.x.x 版;
  • jshint插件为最新版本(*);
  • concat插件不低于 0.1.1 版(~0.1.1表示允许 0.1.x 内的补丁升级);
  • uglify插件不低于 0.1.0 版;
  • watch插件不低于 0.1.4 版。

然后,在项目的根目录下运行下面的命令,这些插件就会被自动安装在node_modules子目录:

npm install

以上是针对已有package.json的情况。如果想要自动生成package.json文件,可以使用npm init命令,按照屏幕提示回答所需模块的名称和版本即可:

npm init

如果已有的package.json文件不包括 Grunt 模块,可以在直接安装 Grunt 模块的时候加上--save-dev参数,该模块就会自动被加入package.json的devDependencies:

npm install <module> --save-dev

比如,对应上面package.json文件指定的模块,需要运行以下 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

仓库佐证:jstutorial 的 package.json 正是这一实践的落地。它的devDependencies锁定了grunt: "~0.4.2"、grunt-contrib-cssmin: "~0.7.0"、grunt-contrib-uglify: "~0.3.0"、grunt-contrib-watch: "~0.5.3",并在scripts中声明了"build": "grunt"——也就是说,npm run build等价于直接执行 Grunt 的默认任务。这说明 Grunt 0.4 时代的 API(initConfig/loadNpmTasks/registerTask)在本仓库中被完整使用,本指南中的配置示例也以 0.4 系语法为准。

命令脚本文件 Gruntfile.js

模块安装完以后,下一步在项目的根目录下新建脚本文件Gruntfile.js。它是 Grunt 的配置文件,就好像package.json是 npm 的配置文件一样。Gruntfile.js就是一般的 Node.js 模块写法:

module.exports = function(grunt) { // 配置Grunt各种模块的参数 grunt.initConfig({ jshint: { /* jshint的参数 */ }, concat: { /* concat的参数 */ }, uglify: { /* uglify的参数 */ }, watch: { /* watch的参数 */ } }); // 从node_modules目录加载模块文件 grunt.loadNpmTasks('grunt-contrib-jshint'); grunt.loadNpmTasks('grunt-contrib-concat'); grunt.loadNpmTasks('grunt-contrib-uglify'); grunt.loadNpmTasks('grunt-contrib-watch'); // 每行registerTask定义一个任务 grunt.registerTask('default', ['jshint', 'concat', 'uglify']); grunt.registerTask('check', ['jshint']); };

上面的代码用到了 Grunt 代码的三个方法:

  • grunt.initConfig:定义各种模块的参数,每一个成员项对应一个同名模块。
  • grunt.loadNpmTasks:加载完成任务所需的模块(从node_modules目录加载)。
  • grunt.registerTask:定义具体的任务。第一个参数为任务名,第二个参数是一个数组,表示该任务需要依次使用的模块。default任务名表示,如果直接输入grunt命令、后面不跟任何参数,此时所调用的模块(该例为 jshint、concat 和 uglify);该例的check任务则表示使用 jshint 插件对代码进行语法检查。

上面的代码一共加载了四个模块:jshint(检查语法错误)、concat(合并文件)、uglify(压缩代码)和 watch(自动执行)。接下来,有两种使用方法:

(1)命令行执行某个模块,比如:

grunt jshint

上面代码表示运行 jshint 模块。

(2)命令行执行某个任务,比如:

grunt check

上面代码表示运行 check 任务。如果运行成功,就会显示 "Done, without errors."。

如果没有给出任务名,只键入grunt,就表示执行默认的default任务。

Gruntfile.js 实例:grunt-contrib-cssmin 模块

下面通过cssmin模块,演示如何编写Gruntfile.js。cssmin模块的作用是最小化(压缩)CSS 文件。

首先,在项目的根目录下安装该模块:

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']); };

下面详细解释上面代码中涉及的三个方法。

(1)grunt.loadNpmTasks

grunt.loadNpmTasks方法载入模块文件:

grunt.loadNpmTasks('grunt-contrib-cssmin');

你需要使用几个模块,这里就要写几条grunt.loadNpmTasks语句,将各个模块一一加载。

如果加载的模块很多,这部分会非常冗长。而且还存在一个问题:凡是在这里加载的模块,也同时出现在package.json文件中。如果使用 npm 命令卸载模块以后,模块会自动从package.json文件中消失,但是必须手动从Gruntfile.js文件中清除,这样很不方便,一旦忘记,还会出现运行错误。

这里有一个解决办法:安装load-grunt-tasks模块,然后在Gruntfile.js中用下面的语句替代所有的grunt.loadNpmTasks语句:

require('load-grunt-tasks')(grunt);

这条语句的作用是自动分析package.json文件,自动加载所找到的 Grunt 模块。仓库佐证:jstutorial 的 Gruntfile.js 采用了显式逐条loadNpmTasks的写法,共加载了grunt-contrib-watch、grunt-contrib-uglify、grunt-contrib-cssmin三个模块,与 package.json 中的devDependencies一一对应,恰好印证了原文档提到的"重复声明"问题。

(2)grunt.initConfig:目标(target)与参数详解

grunt.initConfig方法用于模块配置,它接受一个对象作为参数,该对象的成员与使用的同名模块一一对应。由于我们要配置的是cssmin模块,所以里面有一个cssmin成员(属性)。

cssmin(属性)指向一个对象,该对象又包含多个成员。除了一些系统设定的成员(比如options),其他自定义的成员称为目标(target)。一个模块可以有多个目标,上面代码里面,cssmin模块共有两个目标:一个是minify,用于压缩 css 文件;另一个是combine,用于将多个 css 文件合并成一个文件。

每个目标的具体设置,需要参考对应模块的文档。就cssmin来讲,minify目标的参数具体含义如下:

  • expand:如果设为true,就表示下面文件名的占位符(即*号)都要扩展成具体的文件名;
  • cwd:需要处理的文件(input)所在的目录;
  • src:表示需要处理的文件。如果采用数组形式,数组的每一项就是一个文件名,可以使用通配符;
  • dest:表示处理后的文件名或所在目录;
  • ext:表示处理后的文件后缀名。

除了上面这些参数,还有一些参数也是 Grunt 所有模块通用的:

  • filter:一个返回布尔值的函数,用于过滤文件名。只有返回值为true的文件,才会被 Grunt 处理;
  • dot:是否匹配以点号(.)开头的系统文件;
  • makeBase:如果设置为true,就只匹配文件路径的最后一部分。比如,a?b可以匹配/xyz/123/acb,而不匹配/xyz/acb/123。

关于通配符,含义如下:

通配符含义
*匹配任意数量的字符,不包括/
?匹配单个字符,不包括/
**匹配任意数量的字符,包括/
{}允许使用逗号分隔的列表,表示 "or"(或)关系
!用于模式的开头,表示只返回不匹配的情况

比如,foo/*.js匹配foo目录下面文件名以.js结尾的文件;foo/**/*.js匹配foo目录和它的所有子目录下面文件名以.js结尾的文件;!*.css表示匹配所有后缀名不为.css的文件。

使用通配符设置src属性的更多例子:

{src: 'foo/th*.js'} {src: 'foo/{a,b}*.js'} {src: ['foo/a*.js', 'foo/b*.js']}

至于combine目标,就只有一个files参数:输出文件是css子目录下的out.min.css,输入文件则是css子目录下的part1.min.css和part2.min.css。

files参数的格式可以是一个对象,也可以是一个数组:

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目标和combine目标的属性设置有重合的部分,可以另行定义一个与minify、combine平行的options属性:

grunt.initConfig({ cssmin: { options: { /* ... */ }, minify: { /* ... */ }, combine: { /* ... */ } } });

(3)grunt.registerTask:任务编排

grunt.registerTask方法定义如何调用具体的任务。default任务表示如果不提供参数、直接输入grunt命令,则先运行cssmin:minify、后运行cssmin:combine,即先压缩再合并。如果只执行压缩,或者只执行合并,则需要在grunt命令后面指明"模块名:目标名":

grunt # 默认情况下,先压缩后合并 grunt cssmin:minify # 只压缩不合并 grunt cssmin:combine # 只合并不压缩

如果不指明目标、只指明模块,就表示将该模块的所有目标依次运行一遍:

grunt cssmin

常用模块设置

Grunt 的插件生态极其庞大且仍在快速增长。下面是一些常用模块(按字母排序):

  • grunt-contrib-clean:删除文件;
  • grunt-contrib-compass:使用 Compass 编译 Sass 文件;
  • grunt-contrib-concat:合并文件;
  • grunt-contrib-copy:复制文件;
  • grunt-contrib-cssmin:压缩以及合并 CSS 文件;
  • grunt-contrib-imagemin:图像压缩模块;
  • grunt-contrib-jshint:检查 JavaScript 语法;
  • grunt-contrib-uglify:压缩以及合并 JavaScript 文件;
  • grunt-contrib-watch:监视文件变动,做出相应动作。

模块的前缀如果是grunt-contrib,就表示该模块由 Grunt 开发团队维护;如果前缀是grunt(比如grunt-pakmanager),就表示由第三方开发者维护。

以下选几个模块,看看它们配置参数的写法,即如何在grunt.initConfig方法中配置各个模块。

grunt-contrib-jshint

jshint用来检查语法错误,比如分号的使用是否正确、有没有忘记写括号等等。它在grunt.initConfig方法里面的配置代码如下:

jshint: { options: { eqeqeq: true, trailing: true }, files: ['Gruntfile.js', 'lib/**/*.js'] },

上面代码先指定 jshint 的检查项目:eqeqeq表示要用严格相等运算符(===/!==)取代相等运算符(==/!=);trailing表示行尾不得有多余的空格。然后指定files属性,表示检查目标是Gruntfile.js文件,以及lib目录的所有子目录下面的 JavaScript 文件。

grunt-contrib-concat

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目标用于合并 JavaScript 文件,css目标用于合并 CSS 文件。两者的src属性指定需要合并的文件(input),dest属性指定输出的目标文件(output)。

grunt-contrib-uglify

uglify模块用来压缩 JavaScript 代码,减小文件体积:

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属性指定压缩后文件的文件头(banner),以及 sourceMap 设置;target目标指定输入和输出文件。仓库佐证:这段示例中的target配置(expand: true、cwd: 'js/origin'、src: '*.js'、dest: 'js/')与 jstutorial 真实 Gruntfile.js 中uglify.target完全一致,可以直接对照阅读。从构建产物看,js/origin下的三个未压缩源文件(main.js、toc.js、jquery.js)在 js 目录中均有对应的压缩版本,例如jquery.js从约 265 KB 压缩到约 93 KB。

grunt-contrib-copy

copy模块用于复制文件与目录:

copy: { main: { src: 'src/*', dest: 'dest/', }, },

上面代码将src子目录(只包含它下面的第一层文件和子目录)拷贝到dest子目录下面(即dest/src目录)。如果要更准确地控制拷贝行为,比如只拷贝文件、不拷贝目录、不保持目录结构,可以写成下面这样:

copy: { main: { expand: true, cwd: 'src/', src: '**', dest: 'dest/', flatten: true, filter: 'isFile', }, },

其中flatten: true表示展开目录结构(不保留子目录层级),filter: 'isFile'表示只处理文件。

grunt-contrib-watch

watch模块用来在后台运行,监听指定事件,然后自动运行指定的任务:

watch: { scripts: { files: '**/*.js', tasks: 'jshint', options: { livereload: true, }, }, css: { files: '**/*.sass', tasks: ['sass'], options: { livereload: true, }, }, },

设置好上面的代码,打开另一个进程,运行grunt watch。此后,任何 js 代码变动、文件保存后就会自动运行jshint任务;任何 sass 文件变动、文件保存后就会自动运行sass任务。

需要注意的是,这两个任务的options参数之中都设置了livereload: true,表示任务运行结束后自动在浏览器中重载(reload)。这需要在浏览器中安装 livereload 插件。安装后,livereload 的默认端口为localhost:35729,但是也可以用livereload: 1337的形式重设端口(localhost:1337)。

仓库佐证:jstutorial 的 Gruntfile.js 中的watch任务正是这一用法的精简版——js目标监听js/origin/*.js并触发uglify:target,css目标监听css/origin/*.css并触发cssmin:minify。也就是说,开发者只需把源码放进js/origin与css/origin,保存即自动压缩产出成品,这与仓库目录结构的设计完全闭环。

其他模块

下面是一些另外有用的模块。

(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

该模块用于在本机运行一个 Web Server:

connect: { server: { options: { port: 4000, base: 'build', hostname: '*' } } }

connect模块会随着 Grunt 运行结束而结束。为了使它一直处于运行状态,可以把它放在watch模块之前运行——因为watch模块需要手动中止,所以connect模块也就会一直运行。

(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 标记是否配对(tag-pair)、标记名和属性名是否小写(tagname-lowercase、attr-lowercase)、属性值是否包括在双引号之中(attr-value-double-quotes)、特殊字符是否转义(spec-char-escape)、HTML 元素的id属性是否为唯一值(id-unique)、head部分是否没有 script 标记(head-script-disabled)。

(5)grunt-contrib-sass

该模块用于将 SASS 文件转为 CSS 文件:

sass: { build: { options: { style: 'compressed' }, files: { 'build/css/master.css': 'assets/sass/master.scss' } } }

上面代码指定输出文件为build/css/master.css,输入文件为assets/sass/master.scss,style: 'compressed'表示采用压缩输出格式。

(6)grunt-markdown

该模块用于将 markdown 文档转为 HTML 文档:

markdown: { all: { files: [ { expand: true, src: '*.md', dest: 'docs/html/', ext: '.html' } ], options: { template: 'templates/index.html', } } },

上面代码指定将.md后缀名的文件转为docs/html/目录下的 html 文件。template属性指定转换时采用的模板,模板样式如下:

<!DOCTYPE html> <html> <head> <title>Document</title> </head> <body> <div id="main" class="container"> <%=content%> </div> </body> </html>

模板中的<%=content%>占位符会在转换时被替换为对应的 markdown 渲染结果。

仓库实战:jstutorial 的构建流水线全貌

将本指南的理论套用到 jstutorial 仓库,可以看到一个完整的"源码 → 压缩 → 监听"闭环。仓库的 Gruntfile.js 全文如下:

module.exports = function(grunt) { grunt.initConfig({ watch: { js: { files: 'js/origin/*.js', tasks: ['uglify:target'] }, css: { files: 'css/origin/*.css', tasks: ['cssmin:minify'], } }, uglify: { target : { expand: true, cwd: 'js/origin', src : '*.js', dest : 'js/' } }, cssmin: { minify: { expand: true, cwd: 'css/origin/', src: ['*.css', '!*.min.css'], dest: 'css/', ext: '.css' }, } }); grunt.loadNpmTasks('grunt-contrib-watch'); grunt.loadNpmTasks('grunt-contrib-uglify'); grunt.loadNpmTasks('grunt-contrib-cssmin'); grunt.registerTask('default',['uglify:target', 'cssmin:minify']); };

对照本指南可做如下解读:

  1. 依赖声明:package.json 的devDependencies声明了grunt ~0.4.2、grunt-contrib-cssmin ~0.7.0、grunt-contrib-uglify ~0.3.0、grunt-contrib-watch ~0.5.3,与Gruntfile.js中三条loadNpmTasks完全一致;
  2. 默认任务:default任务依次执行uglify:target(压缩 JS)与cssmin:minify(压缩 CSS),因此输入grunt(或npm run build)即完成全部构建;
  3. 监听任务:watch将源码变动与压缩任务串联,开发时运行grunt watch即可实现保存即构建;
  4. 输入输出结构:uglify与cssmin的cwd/dest分别指向js/origin→js、css/origin→css。从实际产物看:js/origin/main.js(约 3 KB)对应 js/main.js(约 2.3 KB),js/origin/jquery.js(约 265 KB)对应 js/jquery.js(约 93 KB),压缩效果显著;CSS 侧css/origin/foundation.css(约 100 KB)与 css/foundation.min.css(约 80 KB)等产物同样体现了压缩收益。

延伸阅读

  • 本指南对应仓库文档:tool/grunt.md
  • 仓库实际配置文件:Gruntfile.js、package.json
  • 构建输入与输出目录:js/origin / js、css/origin / css
  • 同类构建工具对比:tool/gulp.md、tool/browserify.md
  • 前端工程化相关:tool/requirejs.md、tool/sourcemap.md、tool/testing.md
  • 教程
  • 文档

【免费下载链接】jstutorial

Javascript tutorial book

项目地址:https://gitcode.com/gh_mirrors/js/jstutorial
点击查看免费下载

相关推荐

上一篇:Kakoune客户端-服务器架构揭秘:tmux、zellij、kitty分屏5种玩法与socket通信原理
下一篇:UniHacker终极指南:如何免费使用Unity全功能版

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询