Grunt 入门从0到0.8

156 阅读5分钟

有一个 SDK 项目是使用的 Grunt 打包,最近在梳理代码,也"相对"深入的看一下 Grunt 到底是个什么东东。之所以是从 0 到 0.8 是因为目前只是看完概念,写 demo, 也能看懂项目上的配置,但是没有真正在在项目上自己配置过,还有一些插件开发,模板,高级 API 也是随便看了看,没有深入。

其实 Grunt 的官方文档也得还挺不错的,我这里也就勉强算个笔记吧!想学 Grunt 还是建议大家看官网的吧,如果是基本配置的话用不了多长时间的。

本文分为四个部分讲解 Grunt,请按需 PICK。

概述

Grunt 是一个 JS 构建工具。可以实现代码拼接,压缩,编译,单元测试等任务,并可以进行自动化操作。Grunt 有很多丰富的插件,其中有官方维护的,还有开发者维护的,可以用它来拓展功能。

Grunt 目前稳定版是 1.1.0。

安装

首先使用 npm 将 grunt-cli 安装到全局。

安装 cli 不等于安装了 Grunt! 它只是调用与 Gruntfile 在同一目录的 Grunt。通过这种设计,允许你在同一个系统上同时安装多个版本的 Grunt。

npm install -g grunt-cli

grunt 和 grunt 插件都可以使用 npm/yarn 安装,安装到 devDependencies 中。

一个简单的 Grunt 项目

一个简单的 Grunt 项目的话有 Gruntfile 和 package.json 即可。package.json 大家都熟,不做太多描述。我们主要来了解一下 Gruntfile。

Gruntfile 由四部分构成:

  • "wrapper" 函数
  • 项目与任务配置
  • 加载 grunt 插件和任务
  • 自定义任务

先看一个 Gruntfile 实例对其有一个大概的印象,然后我们再分步骤讲解。

module.exports = function(grunt) {

  // 项目配置
  grunt.initConfig({
    pkg: grunt.file.readJSON('package.json'),
    uglify: {
      options: {
        banner: '/*! <%= pkg.name %> <%= grunt.template.today("yyyy-mm-dd") %> */\n' 
      },
      build: {
        src: 'src/<%= pkg.name %>.js',
        dest: 'build/<%= pkg.name %>.min.js'
      }
    }
  });
  
  // 加载包含 "uglify" 任务的插件。 
  grunt.loadNpmTasks('grunt-contrib-uglify');
  
  // 默认被执行的任务列表。
  grunt.registerTask('default', ['uglify']);
};

"wrapper" 函数

module.exports = function(grunt) {
  // Do grunt-related things in here
};

每一份 Gruntfile 都遵循同样的格式,你所书写的 Grunt 代码必须放在此函数中。

项目和任务配置

grunt.initConfig({
  pkg: grunt.file.readJSON('package.json'),
  uglify: {
    options: {
      banner: '/*! <%= pkg.name %> <%= grunt.template.today("yyyy-mm-dd") %> */\n' 
    },
    build: {
      src: 'src/<%= pkg.name %>.js',
      dest: 'build/<%= pkg.name %>.min.js'
    }
  },
  meta: { name: 'test' }
});

传给 initConfig 的对象是任务配置其他任意数据。上面例子中

  • pkg 是读取的 package.json 数据。
  • uglify 是 grunt-contrib-uglify 的任务
  • meta 是自定义的数据

其中任务可以是外部的 npm 依赖对应的任务,也可能是我们自定义的多任务。

在任务中可以访问其他任务/数据中定义的变量

多任务

多任务可以通过任意命名的目标(target)来定义多个配置

grunt.initConfig({
  concat: {
    foo: {
      // concat task "foo" target options and files go here.
    },
    bar: {
      // concat task "bar" target options and files go here.
    },
  },
  uglify: {
    bar: {
      // uglify task "bar" target options and files go here.
    },
  },
});

options

在一个任务配置中,options 属性可以用来指定覆盖内置属性的默认值。target 级的 options 会覆盖 task 级的 options。

options 对象是可选的,如果不需要,可以忽略。

文件操作

由于大多任务都是执行文件操作,Grunt 有一个强大的抽象层用于声明任务应该操作哪些文件。Grunt 提供了多种定义 src-dest 文件映射的方式。这里只列出一个大概,在官网有详细描述。

  • 简洁格式
  • 文件对象格式
  • 文件数组格式
  • 自定义过滤函数
  • 通配符模式
  • 动态构建文件对象
  • 较老的格式(不推荐)

模板语言

配置可以使用模板语言,可以调用任何配置中的变量,还可以像上面的例子中一样引入外部数据。

加载 grunt 插件和任务

// 加载包含 "uglify" 任务的插件。 
grunt.loadNpmTasks('grunt-contrib-uglify');

从指定的 Grunt 插件中加载任务。

自定义任务

用户可以在项目中自定义任务。

  • 按照配置个数可以分为: 单任务和多任务。
  • 按照执行可以分为: 同步任务和异步任务。

还可以设置任务别名为指定一组任务。执行该任务别名,就代表其指定的一组任务。

在任务中可以

  • 访问自身的名称和参数。
  • 执行其他任务
  • 检查其他任务的执行状态
  • 检查配置是否存在
  • 读取配置属性。
/**
 * 基本自定义任务
 */
// !! 基本任务无需配置
// 执行 grunt foo 打印 foo, no args
// 执行 grunt foo:arg1 打印 foo, arg1 undefined
// 执行 grunt foo:arg1:arg2 打印 foo, arg1 arg2
grunt.registerTask('foo', 'A sample task that logs stuff.', function(arg1, arg2) {
  if (arguments.length === 0) {
    grunt.log.writeln(this.name + ", no args");
  } else {
    grunt.log.writeln(this.name + ", " + arg1 + " " + arg2);
  }
});


/**
 * 多任务
 */
// !! 多任务必须在配置,否则报错
// >> No "log" targets found.
// Warning: Task "log" failed. Use --force to continue.
// Aborted due to warnings.
grunt.registerMultiTask('log', 'Log stuff.', function() {
  // 执行 grunt log:foo, 打印 foo: 1,2,3
  // 执行 grunt log:bar, 打印 bar: hello world
  grunt.log.writeln(this.target + ': ' + this.data);
});


/**
 * 找不到任务名
 */
// 执行 grunt fo
// 报错
// Warning: Task "fo" not found. Use --force to continue.
// Aborted due to warnings.


/**
 * 在任务内部调用其他任务
 */
grunt.registerTask('custom1', function () {
  // grunt.task.run('custom');
  // 或
  // grunt.task.run('concat', 'jshint');
  // 或
  grunt.task.run(['concat', 'jshint']); // 这个写法与上面的那种写法一致
});


/**
 * 异步任务
 */
grunt.registerTask('custom2', function() {
  // 需要这一行告知 grunt 这是 async 模式并返回 done 方法
  var done = this.async();
  // Run some sync stuff.
  grunt.log.writeln('Processing task...');
  // And some async stuff.
  setTimeout(function() {
    grunt.log.writeln('All done!');
    done();
  }, 2000);
});


/**
 * 任务出错
 */
// 任务出错,返回 false 后续任务都会被终止,除非指定 --force
// 不返回或返回其他值都没事
grunt.registerTask('a1', function() {
  grunt.log.writeln('执行 a1');
  return false;
});
grunt.registerTask('a11', function () {
  var done = this.async();
  setTimeout(function () {
    console.log('执行 a11');
    done(0);
  });
});
grunt.registerTask('a2', function() {
  grunt.log.writeln('执行 a2');
  return true;
});
grunt.registerTask('a', ['a1', 'a11', 'a2']);


/**
 * 依赖于其他任务成功执行
 */
grunt.registerTask('b1', function() {
  grunt.task.requires('a11');
  grunt.log.writeln('执行 b1');
  return true;
});
grunt.registerTask('b', ['a11', 'b1']);


/**
 * 依赖于属性
 */
grunt.registerTask('c1', function() {
  // grunt.config.requires('meta.name1');
  grunt.config.requires(['meta', 'name']); // 这两种写法是相同的
  grunt.log.writeln('执行 c1');
  return true;
});


/**
 * 访问属性
 */
grunt.registerTask('d1', function() {
  grunt.log.writeln('执行 d1', grunt.config('meta'));
  grunt.log.writeln('执行 d1 name1', grunt.config('meta.name1'));
  grunt.log.writeln('执行 d1 name', grunt.config(['meta', 'name']));
  return true;
});


/**
 * 任务别名
 */
// 定义 default 任务别名,执行 default 就是依次执行后面列表中的任务
// default 可以不传 直接执行 grunt
grunt.registerTask('default', ['concat', 'uglify', 'jshint']);
// 执行 grunt custom
grunt.registerTask('custom', ['concat', 'uglify', 'jshint']);
// 可以执行 grunt concat 只执行 concat 任务

其他

上述是 Grunt 的基本使用,除此之外还可以

  • 用户自己创建插件 参考
  • 使用 grunt-init脚手架,指定现有模板或自定义模板创建项目 参考
  • 使用 Grunt 命令行工具 参考
  • 了解 Grunt 更多 API 参考

参考链接