Pages

Thursday, 23 February 2017

Grunt.js 在前端项目中的实战

Grunt是一个基于JavaScript的任务运行工具。
为什么要使用Grunt,简而言之是为了“自动”,用工具自动完成压缩、编译、单元测试、拼写检查等重复性工作。
Grunt的社区壮大非常快,现在支持的模块有:CoffeeScripthandlerbarsjadeJSHintLessRequireJSSassstylus等。

Grunt基本配置

Grunt及其插件都是用npm管理的,npm是Node.js的包管理程序,所以在使用Grunt之前,你需要先安装NodeJS。

安装CLI

首先需要在全局环境中安装Grunt command line interface (CLI),在Mac等系统中需要sudo来执行下面的命令:
  1. npm install -g grunt-cli
这会将grunt命令安装在系统path中,这样就可以从任何目录执行了。需要注意的是,安装了grunt-cli并没有安装任务管理工具。CLI的职责很简单,就是运行Gruntfile中定义的Grunt版本,这样你就可以在一台机器运行多个版本的Grunt。
如果从0.3版本升级,需要先卸载旧版:
  1. npm uninstall -g grunt

已存在Grunt的项目

对于已经使用了Grunt的项目,搭建本地环境是非常方便的,只需要切换到该项目目录,然后执行:
  1. npm install
再使用grunt命令运行Grunt即可。

新建Grunt项目

最基本的步骤就是给项目添加两个文件package.jsonGruntfile
  • package.json:在这个文件里你可以列出项目所需的Grunt插件,npm会自动下载。
  • Grunfile:这个文件命名为Gruntfile.js或者Gruntfile.coffee,用来描述你所需要的grunt任务。

package.json

package.json文件需要放置在项目的根目录,和代码一起提交。运行npm install命令,会安装package.json中列出的依赖插件的正确版本。
创建package.json有以下几种办法:
  • 大部分grunt-init模板,会创建项目相关的package.json文件
  • npm init命令会创建基本的package.json文件
  • 也可以下面这个范本创建,更多用法可以看specification
  1. {
  2. "name": "my-project-name",
  3. "version": "0.1.0",
  4. "devDependencies": {
  5. "grunt": "~0.4.1",
  6. "grunt-contrib-jshint": "~0.6.0",
  7. "grunt-contrib-nodeunit": "~0.2.0",
  8. "grunt-contrib-uglify": "~0.2.2"
  9. }
  10. }

安装Grunt和插件

对于已存在package.json文件的项目,最简单的安装方法就是npm install <module> --save-dev,例如:
  1. npm install grunt --save-dev
这个命令会安装最新版的grunt,并且把对这个插件的依赖写入package.json。很方便。

Gruntfile

package.json文件一样,Gruntfile.js或者Gruntfile.coffee需要放在根目录下和源码一起提交。
Gruntfile由以下几个部分组成:
  • wrapper函数
  • 项目和任务配置
  • 加载Grunt插件和任务
  • 自定义任务

示例Gruntfile

在下面这个例子中,项目信息引自package.json,grunt-contrib-uglify插件的uglify任务用来压缩js文件,并且根据项目的metadata生成一条注释。当grunt运行时,uglify任务会默认执行。
  1. module.exports = function(grunt) {
  2. // Project configuration.
  3. grunt.initConfig({
  4. pkg: grunt.file.readJSON('package.json'),
  5. uglify: {
  6. options: {
  7. banner: '/*! <%= pkg.name %> <%= grunt.template.today("yyyy-mm-dd") %> */\n'
  8. },
  9. build: {
  10. src: 'src/<%= pkg.name %>.js',
  11. dest: 'build/<%= pkg.name %>.min.js'
  12. }
  13. }
  14. });
  15. // Load the plugin that provides the "uglify" task.
  16. grunt.loadNpmTasks('grunt-contrib-uglify');
  17. // Default task(s).
  18. grunt.registerTask('default', ['uglify']);
  19. };
这就是一个完整的Gruntfile,我们仔细研究下。

wrapper函数

每个Gruntfile(包括插件)使用这个默认的格式,你的所有的Grunt代码也必须写在这个函数中:
  1. module.export = function(grunt){
  2. //Do grunt-related things in here
  3. };

项目和任务配置

大多Grunt的任务依赖于grunt.initConfig方法中定义的配置。
在这个例子中,Grunt通过grunt.file.readJSON('package.json')引入了package.json中定义的Grunt配置。因为<% %>模板变量可以引用任何配置,所以像文件路径、文件列表这些内容应该存储在变量中,以减少重复。
和其他任务一样,任务的配置需要和任务名字一样的变量,具体的参数可以查询各任务的文档。
  1. // Project configuration.
  2. grunt.initConfig({
  3. pkg: grunt.file.readJSON('package.json'),
  4. uglify: {
  5. options: {
  6. banner: '/*! <%= pkg.name %> <%= grunt.template.today("yyyy-mm-dd") %> */\n'
  7. },
  8. build: {
  9. src: 'src/<%= pkg.name %>.js',
  10. dest: 'build/<%= pkg.name %>.min.js'
  11. }
  12. }
  13. });

加载Grunt的插件和任务

很多常用的任务比如concatenationminificationlinting都有Grung插件。只要在package.json中声明,然后通过npm install安装,就可以在Gruntfile中配置使用了。
  1. // Load the plugin that provides the "uglify" task.
  2. grunt.loadNpmTasks('grunt-contrib-uglify');
grunt --help可以查看所有可用的任务。

自定义任务

你可以配置让Grunt运行一个或多个默认任务。在例子中,运行grunt不带任何参数就会执行uglify任务。这和grunt uglify或者grunt default是一样的效果。数组的长度任意。
  1. // Default task(s).
  2. grunt.registerTask('default', ['uglify']);
如果你需要的任务并没有插件提供,那么也可以自定义,自定义的任务可以不写任务配置
  1. module.exports = function(grunt) {
  2. // A very basic default task.
  3. grunt.registerTask('default', 'Log some stuff.', function() {
  4. grunt.log.write('Logging some stuff...').ok();
  5. });
  6. };
自定义的任务也不必一定写在Gruntfile中,也可以定义在外部的.js文件中,然后通过grunt.loadTasks来加载。

实战

grunt插件中有contrib前缀的是Grunt团队自行开发的插件,也是推荐使用的,下面挑选几个在前端项目中必用的插件,在实际例子中介绍一下使用方法。

grunt-contrib-compass

CompassSASS的一个框架,就像jQuery之于Javascript、Rails之于Ruby。具体的用法可以参考阮一峰的这两篇Blog:
首先,安装grunt-contrib-compass
  1. npm install grunt-contrib-compass --save-dev
如前所述,--save-dev可以在安装插件的过程中,将对这个插件的依赖自动写入package.json文件中,方便。
Compass并没有暴露所有的设置给Grunt,如果有别的需要,可以在config里面指定config.rb给Compass编译使用。看一个配置的例子:
  1. module.exports = function(grunt){
  2. grunt.initConfig({
  3. compass: { // compass任务
  4. dist: { // 一个子任务
  5. options: { // 任务的设置
  6. sassDir: 'sass',
  7. cssDir: 'css',
  8. environment: 'production'
  9. }
  10. },
  11. dev: { // 另一个子任务
  12. options: {
  13. sassDir: 'sass',
  14. cssDir: 'style'
  15. }
  16. }
  17. }
  18. });
  19. grunt.loadNpmTasks('grunt-contrib-compass');
  20. grunt.registerTask('default', ['compass']);
  21. }
如果要使用外部文件的配置:
  1. grunt.initConfig({
  2. compass: {
  3. dist: {
  4. options: {
  5. config: 'config/config.rb'
  6. }
  7. }
  8. }
  9. });

grunt-contrib-concat

grunt-contrib-concat是一个合并文件的插件,可以将多个css或js文件合并为一个,以节省链接数。同样的,安装:
  1. npm install grunt-contrib-concat --save-dev
这个插件有一下几个常用配置:
  • seperator:被合并的文件会用这个参数来join,例如你在合并压缩后的js文件时,可以加个;防止出错
  • banner:在合并后的文件头部加一些额外信息
  • footer:在合并后的文件尾部加一些额外信息
再看一下用法:
  1. grunt.initConfig({
  2. pkg: grunt.file.readJSON('package.json'),
  3. concat: {
  4. options: { //配置
  5. stripBanners: true,
  6. banner: '/*! <%= pkg.name %> - v<%= pkg.version %> - ' + //添加自定义的banner
  7. '<%= grunt.template.today("yyyy-mm-dd") %> */'
  8. },
  9. dist: { //任务
  10. src: ['src/intro.js', 'src/project.js', 'src/outro.js'], //源目录文件
  11. dest: 'dist/built.js' //合并后的文件
  12. },
  13. basic_and_extras: { //另一个任务
  14. files: { //另一种更简便的写法
  15. 'dist/basic.js': ['src/main.js'],
  16. 'dist/with_extras.js': ['src/main.js', 'src/extras.js']
  17. }
  18. }
  19. }
  20. });
  21. grunt.loadNpmTasks('grunt-contrib-concat');
最后在default事件中添加concat就会默认执行了。

grunt-contrib-uglify

grunt-contrib-uglify用来压缩js文件,用法与concat类似,先安装:
  1. npm install grunt-contrib-uglify --save-dev
然后写入相应的配置:
  1. grunt.initConfig({
  2. uglify: {
  3. options: {
  4. banner: '/*! This is uglify test - ' +
  5. '<%= grunt.template.today("yyyy-mm-dd") %> */'
  6. },
  7. app_task: {
  8. files: {
  9. 'dist/app.min.js': ['js/app.js', 'js/render.js']
  10. }
  11. }
  12. }
  13. });
  14. grunt.loadNpmTasks('grunt-contrib-uglify');
恩,经过如此处理,你的js代码已经丑陋到无法直视了。

grunt-contrib-watch

grunt-contrib-watch是开发必备插件,用来监控文件的修改,然后自动运行grunt任务,省去一遍遍手动执行Grunt命令,安装照旧:
  1. npm install grunt-contrib-watch --save-dev
使用watch插件时,需要注意一点,被watch的文件,可以分开写,这样可以提高watch的性能,不用每次把没修改的文件也执行一遍任务,看看例子:
  1. grunt.initConfig({
  2. watch: {
  3. css: {
  4. files: ['public/scss/*.scss'],
  5. tasks: ['compass'],
  6. options: {
  7. // Start a live reload server on the default port 35729
  8. livereload: true,
  9. },
  10. },
  11. another: {
  12. files: ['lib/*.js'],
  13. tasks: ['anothertask'],
  14. options: {
  15. // Start another live reload server on port 1337
  16. livereload: 1337,
  17. },
  18. }
  19. }
  20. });
  21. grunt.loadNpmTasks('grunt-contrib-watch');
然后运行grunt watch命令,修改文件,就会看到设定的任务执行了。

源码

Grunt的基本使用就是这些了,当然还有一些搭建脚手架等等的功能,等待你自己去学习使用吧,更多的Grunt 插件也等待你去发现。
贴出来源码,整体看一下:

package.json

  1. {
  2. "name": "Grunt-in-action",
  3. "devDependencies": {
  4. "grunt": "~0.4.1",
  5. "grunt-contrib-compass": "~0.3.0",
  6. "grunt-contrib-watch": "~0.4.4",
  7. "grunt-contrib-concat": "~0.3.0",
  8. "grunt-contrib-uglify": "~0.2.2"
  9. }
  10. }

Gruntfile.js

  1. module.exports = function(grunt){
  2. grunt.initConfig({
  3. compass: { // Task
  4. dist: { // Target
  5. options: { // Target options
  6. sassDir: 'sass',
  7. cssDir: 'css',
  8. environment: 'production'
  9. }
  10. },
  11. dev: { // Another target
  12. options: {
  13. sassDir: 'sass',
  14. cssDir: 'style'
  15. }
  16. }
  17. },
  18. concat: {
  19. options: { //配置
  20. stripBanners:true,
  21. banner: '/*! This is the grunt test ' + //添加自定义的banner
  22. '<%= grunt.template.today("yyyy-mm-dd") %> */'
  23. },
  24. basic: { //另一个任务
  25. files: { //另一种更简便的写法
  26. 'dist/style.css': ['style/screen.css','style/ie.css','style/print.css']
  27. }
  28. }
  29. },
  30. uglify: {
  31. options: {
  32. banner: '/*! This is uglify test - ' +
  33. '<%= grunt.template.today("yyyy-mm-dd") %> */'
  34. },
  35. app_task: {
  36. files: {
  37. 'dist/app.min.js': ['js/app.js', 'js/render.js']
  38. }
  39. }
  40. },
  41. watch: {
  42. css: {
  43. files: ['sass/*.scss'],
  44. tasks: ['compass', 'concat']
  45. },
  46. another: {
  47. files: ['js/*.js'],
  48. tasks: ['uglify']
  49. }
  50. }
  51. });
  52. grunt.loadNpmTasks('grunt-contrib-compass');
  53. grunt.loadNpmTasks('grunt-contrib-concat');
  54. grunt.loadNpmTasks('grunt-contrib-uglify');
  55. grunt.loadNpmTasks('grunt-contrib-watch');
  56. grunt.registerTask('default', ['compass','concat', 'uglify']);
  57. }

No comments:

Post a Comment