天天看点

前端构建实践02:用 Grunt 运行构建任务构建任务Grunt结论

上一篇文章介绍了作为前端构建的包管理工具的 Bower,并在文末提出了一个有待解决的问题,这篇则从这个问题展开,介绍一个前端自动化工具 Grunt。

构建任务

我们为什么需要前端的构建任务?

首先,同后端开发一样,前端开发实际也需要一些构建任务:LESS/SASS、CoffeeScript/TypeScript 的编译、上线前对 CSS/JavaScript 进行连接或压缩、执行 JavaScript 模块的单元测试等等。在以往,我们为这些任务写不同的批处理或者 Shell 脚本,然后调用它们来执行构建动作。但撰写脚本这件事本身就浪费了宝贵的开发时间,它是一种纯粹的技术活儿,对业务毫无帮助。因此,一些情况下,在按照模块进行开发的团队里,那些前后端包办的开发者干脆不关注前端的这些构建:不使用 LESS/SASS或CoffeeScript/TypeScript(即使它们在一些方面比原生的 CSS 和 JavaScript 更优秀),加载未经压缩的 CSS/JavaScript 文件,跳过前端模块的单元测试等。这种对前端的忽视,实际上为测试、用户体验、软件性能甚至是系统安全都带来不少的风险。

另一方面,体系化的构建是持续集成/部署/交付的重要一环。持续集成要求的速度太快,依靠人力很难做到一天进行数个甚至数十个版本的迭代还不保证出错。这迫使我们需要将那些重复的构建任务交给机器完成,腾出时间应对业务需求、及早地发现并调整问题。虽然我们平时可能已经或多或少用一些自动化做这些琐屑的事情:用一段服务器上运行的脚本进行构建,但这些脚本往往需要借助于各种工具才能正常运行,例如,假设你的持续集成服务器上缺失了某些组件,通过这些脚本进行的构建就无法成功,而你可能会在部署的包中带上这些工具,使得包变得臃肿不堪。那么我们是否能引入一些更加统一的方式管理构建任务呢?后端技术栈在这方面已经十分成熟,而这个系列要讨论的,是前端世界的构建。

Grunt

从上面可以看出,运行构建任务要克服两个问题。

  1. 构建任务功能要足够强大和灵活,但写的脚本要足够简单。
  2. 构建任务对外部的依赖是可以进行管理的。

接下来将会介绍一个名为 Grunt 的工具,这个工具提供了一些激动人心的功能,作为前端的构建任务运行工具简直再好不过了。

Grunt 是什么

Grunt 是一个在 Node.js 上运行的 JavaScript 构建工具。它满足了构建任务对工具的两个要求:

  1. Grunt 提供了丰富的任务插件和简单的 API,只要写一段 JavaScript 脚本,任务就能运行起来。
  2. Grunt 通过 npm 管理插件,所有依赖尽在掌握。

怎么使用 Grunt

安装 Grunt

安装

grunt-cli

前面说过,Grunt 依赖 Node.js,所以确保你已经安装了 Node.js。然后,我们先通过 npm 全局安装

grunt-cli

grunt-cli

是个 Grunt 命令行工具(注意,

grunt-cli

并不是 Grunt),它的作用是让你可以在任何目录使用 Grunt,并确保不同版本的 Grunt 隔离不互相影响。

安装

Grunt

安装了

grunt-cli

后,在我们的项目主目录下安装 Grunt:

--save-dev

向 npm 表明 Grunt 将为开发时的依赖,而非运行时的依赖。

配置

Grunt

在项目根目录下创建一个名为

Gruntfile.js

的文件,这个文件管理项目中的全部 Grunt 任务。

将下面的代码加到

Gruntfile.js

中:

module.exports = function(grunt) {
  grunt.initConfig({
    pkg: grunt.file.readJSON('package.json')
  })

  grunt.registerTask('default', [])
}
           

你可以将这段代码作为脚手架,放到每一个项目的初始化脚本中去。

测试 Grunt

在项目根目录下运行

grunt

grunt default

> grunt

Done.
           

输出“Done.”表明 Grunt 配置正确。

内建 Grunt 任务

Grunt 生态良好,有庞大的插件库,这些插件可以在 npm 上搜索并安装。这里列出了一些可用的 Grunt 插件列表。下面,作为案例,我将用 Grunt 处理上一篇文章末尾提出的问题:只提取

bower_components

目录中实际有用的文件到另一个存放库文件的目录

lib

下。

我在 npm 上找到了一个名为

grunt-bower-task

的插件,这个插件做的正是我想要的:将

bower_components

里有用的文件复制出来。

先安装这个插件:

修改

Gruntfile.js

,将

grunt.registerTask('default', [])

这一行删除,然后为替换成下面这句:

grunt.loadNpmTasks('grunt-bower-task')
           

保存后运行

grunt --help

,你会在 Grunt 输出的一大串帮助中看到一个名为

bower

的可用任务(Available task),表明

bower

任务被成功注册到 Grunt 中:

...
Options marked with * have methods exposed via the grunt API and should instead
be specified inside the Gruntfile wherever possible.

Available tasks
         bower  Install Bower packages. *

...
           

接下来我们按照这个插件的文档,对

bower

任务进行配置,达到我们一开始期望的效果。

编辑

Gruntfile.js

,在提供给

grunt.initConfig

函数的对象上加入一个

bower

属性:

grunt.initConfig({
  pkg: grunt.file.readJSON('package.json'),
  // bower-task
  bower: {
    install: {
      options: {

      }
    }
  }
})
           

保存

Gruntfile.js

,运行

grunt bower

Running "bower:install" (bower) task
>> Installed bower packages
>> Copied packages to *****\lib

Done.
           

运行成功。查看你的项目主目录,会发现多出一个

lib

目录,而里面正好是我们所需要的内容。

如果认真阅读文档,会发现

grunt-bower-task

其实是先完成了

bower install

的工作,再执行复制的(从原理上说,它复制的是

bower.json

main

配置指定的文件),插件的文档中还给出了很多可选的配置项,只用一些代码,能满足大部分 Bower 安装的需求。因此,可以用这个插件将 Bower 和 Grunt 集成。

自定义 Grunt 任务

如果插件不能满足你的需求,别担心,只要会写 JavaScript,稍微查下 Node.js 和 Grunt 的文档,你就能在

Gruntfile.js

中轻松地定义。下面的代码在

grunt.initConfig

初始化 Grunt 配置后,注册一个名为

foo

的自定义的任务:

grunt.task.registerTask('foo', 'A sample task that logs stuff.', function(arg1, arg2) {
  grunt.log.writeln(this.name + ", " + arg1 + " " + arg2)
})
           

要运行该任务,执行

grunt foo[:arg1[:arg2]]

arg1

arg2

分别表示传给

foo

任务的第一个和第二个参数)

> grunt foo:a:c

Running "foo:a:c" (foo) task
foo, a c

Done.
           

结论

本文引入了 Grunt 工具作为前端构建任务的基础设施,并通过一个案例简单地介绍如何使用 Grunt 。我没有具体地说明 Grunt 的命令应当如何使用,这些有不少教程都已经很细致地介绍了。通过 Grunt 的统一风格管理自动化的前端任务,软件开发人员在更为敏捷和更为规范的 持续化 道路上又前进了一步。