【进阶系列】前端开发环境构建(五)JS -- Grunt

2.2 Grunt——js项目命令行构建工具

        Grunt 是一个基于任务的 JavaScript 项目命令行构建工具,运行于 Node.js 平台。Grunt 能够从模板快速创建项目,合并、压缩和校验 CSS & JS 文件,运行单元测试以及启动静态服务器。

http://www.gruntjs.net

2.2.1 安装Grunt

        推荐 Windows 用户使用 Git Shell 来进行命令行操作。安装 Windows 桌面版 GitHub 的时候会自动安装 Git Shell。

GitHub for Windows 下载地址:http://windows.github.com

        Grunt 运行于 Node.js 环境,这里假设你已经安装了 Node.js 和 NPM。

npm installgrunt

        为了便于操作,可以使用参数 -g 配置为全局安装:

sudo npm install -g grunt

JavaScript项目构建工具Grunt实践:安装和创建项目框架

http://www.cnblogs.com/lhb25/archive/2013/01/24/grunt-for-javascript-project-a.html

2.2.2 安装CLI

如果你是从Grunt 0.3 版本升级的,请查看 Grunt 0.3说明

        在继续学习前,你需要先将Grunt命令行(CLI)安装到全局环境中。安装时可能需要使用sudo(针对OSX、*nix、BSD等系统中)权限或者作为管理员(对于Windows环境)来执行以下命令。

sudo npm install -ggrunt-cli

        上述命令执行完后,grunt 命令就被加入到你的系统路径中了,以后就可以在任何目录下执行此命令了。

        注意,安装grunt-cli并不等于安装了 Grunt!Grunt CLI的任务很简单:调用与Gruntfile在同一目录中 Grunt。这样带来的好处是,允许你在同一个系统上同时安装多个版本的 Grunt。这样就能让多个版本的 Grunt 同时安装在同一台机器上。

2.2.3 CLI是如何工作的

        每次运行grunt 时,他就利用node提供的require()系统查找本地安装的 Grunt。正是由于这一机制,你可以在项目的任意子目录中运行grunt 。

        如果找到一份本地安装的 Grunt,CLI就将其加载,并传递Gruntfile中的配置信息,然后执行你所指定的任务。

为了更好的理解 Grunt CLI的执行原理,请参考源码。其实代码很短!

2.2.4 Webstormgrunt配置

Nodejs安装 grunt ,Webstorm grunt 配置

http://www.unjeep.com/q/452818856.htm

2.2.5 利用Grunt创建项目框架

        一般需要在你的项目中添加两份文件:package.json 和 Gruntfile。

package.json: 此文件被npm用于存储项目的元数据,以便将此项目发布为npm模块。你可以在此文件中列出项目依赖的grunt和Grunt插件,放置于devDependencies配置段内。

Gruntfile: 此文件被命名为 Gruntfile.js 或 Gruntfile.coffee,用来配置或定义任务(task)并加载Grunt插件的。

2.2.5.1 目录说明

node_modules:由命令npm install根据package.json生成

2.2.5.2 新建文件package.json

        package.json应当放置于项目的根目录中,与Gruntfile在同一目录中,并且应该与项目的源代码一起被提交。在上述目录(package.json所在目录)中运行npm install将依据package.json文件中所列出的每个依赖来自动安装适当版本的依赖。

        下面列出了几种为你的项目创建package.json文件的方式:

1、大部分 grunt-init 模版都会自动创建特定于项目的package.json文件。

2、npm init命令会创建一个基本的package.json文件。

3、复制下面的案例,并根据需要做扩充,参考此说明.

{

  “name”:”my-project-name”,

 “version”: “0.1.0”,

 “devDependencies”: {

       “grunt”: “~0.4.1”,

       “grunt-contrib-jshint”: “~0.6.0”,

       “grunt-contrib-nodeunit”: “~0.2.0”,

       “grunt-contrib-uglify”: “~0.2.2”

  }

}

        这个文件用来存储npm模块的依赖项(比如我们的打包若是依赖requireJS的插件,这里就需要配置),然后,我们会在里面配置一些不一样的信息,比如我们上面的file,这些数据都会放到package中.对于package的灵活配置,我们会在后面提到.

2.2.5.3 安装Grunt和grunt插件

        向已经存在的package.json 文件中添加Grunt和grunt插件的最简单方式是通过npm install –save-dev命令。此命令不光安装了,还会自动将其添加到devDependencies 配置段中,遵循tilde version range格式。

        例如,下面这条命令将安装Grunt最新版本到项目目录中,并将其添加到devDependencies内:

npm install grunt –save-dev

        同样,grunt插件和其它node模块都可以按相同的方式安装。安装完成后一定要记得将被修改的package.json文件提交到源码管理器中。

2.2.5.4 新建文件Gruntfile

        Gruntfile.js 或 Gruntfile.coffee 文件是有效的 JavaScript 或 CoffeeScript 文件,应当放在你的项目根目录中,和package.json文件在同一目录层级,并和项目源码一起加入源码管理器。

        Gruntfile由以下几部分构成:

    1 “wrapper” 函数

    2 项目与任务配置

    3 加载grunt插件和任务

    4 自定义任务

        这个文件尤其关键,他一般干两件事情:

    ① 读取package信息

    ② 插件加载、注册任务,运行任务(grunt对外的接口全部写在这里面)

        Gruntfile一般由四个部分组成

    ① 包装函数

        这个包装函数没什么东西,意思就是我们所有的代码必须放到这个函数里面.

module.exports = function (grunt) {

    //你的代码

}

        这个不用知道为什么,直接将代码放入即可.

② 项目/任务配置

        我们在Gruntfile一般第一个用到的就是initConfig方法配置依赖信息

pkg: grunt.file.readJSON(‘package.json’)

        这里的grunt.file.readJSON就会将我们的配置文件读出,并且转换为json对象。然后我们在后面的地方就可以采用pkg.XXX的方式访问其中的数据了。值得注意的是这里使用的是underscore模板引擎,所以你在这里可以写很多东西。

        uglify是一个插件的,我们在package依赖项进行了配置,这个时候我们为系统配置了一个任务__uglify(压缩),他会干这几个事情:

    ① 在src中找到zepto进行压缩(具体名字在package中找到)

    ② 找到dest目录,没有就新建,然后将压缩文件搞进去

    ③ 在上面加几个描述语言

        这个任务配置其实就是一个方法接口调用,按照规范来就好,暂时不予关注,内幕后期来。

        这里只是定义了相关参数,但是并未加载实际函数,所以后面马上就有一句:

grunt.loadNpmTasks(‘grunt-contrib-uglify’);用于加载相关插件

        最后注册一个自定义任务(其实也是默认任务),所以我们下面的命令行是等效的:

grunt == grunt uglify

        至此,我们就简单解析了一番grunt的整个操作,下面来合并文件的例子

2.2.5.4.1 Gruntfile文件案例

在下面列出的这个 Gruntfile 中,package.json文件中的项目元数据(metadata)被导入到 Grunt 配置中, grunt-contrib-uglify 插件中的uglify 任务(task)被配置为压缩(minify)源码文件并依据上述元数据动态生成一个文件头注释。当在命令行中执行 grunt 命令时,uglify 任务将被默认执行。

module.exports =function(grunt) {

  // Project configuration.

  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’]);

};

        前面已经向你展示了整个 Gruntfile,接下来将详细解释其中的每一部分。

2.2.5.5 “wrapper”函数

        每一份 Gruntfile (和grunt插件)都遵循同样的格式,你所书写的Grunt代码必须放在此函数内:

module.exports =function(grunt) {

      // Do grunt-related things in here

};

2.2.5.6 项目和任务配置

        大部分的Grunt任务都依赖某些配置数据,这些数据被定义在一个object内,并传递给grunt.initConfig 方法。

        在下面的案例中,grunt.file.readJSON(‘package.json’) 将存储在package.json文件中的JSON元数据引入到grunt config中。 由于<% %>模板字符串可以引用任意的配置属性,因此可以通过这种方式来指定诸如文件路径和文件列表类型的配置数据,从而减少一些重复的工作。

        你可以在这个配置对象中(传递给initConfig()方法的对象)存储任意的数据,只要它不与你任务配置所需的属性冲突,否则会被忽略。此外,由于这本身就是JavaScript,你不仅限于使用JSON;你可以在这里使用任意的有效的JS代码。如果有必要,你甚至可以以编程的方式生成配置。

        与大多数task一样,grunt-contrib-uglify 插件中的uglify 任务要求它的配置被指定在一个同名属性中。在这里有一个例子, 我们指定了一个banner选项(用于在文件顶部生成一个注释),紧接着是一个单一的名为build的uglify目标,用于将一个js文件压缩为一个目标文件。

// Project configuration.

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’

    }

  }

});

2.2.5.7 加载Grunt插件和任务

        像 concatenation、[minification]、grunt-contrib-uglify 和 linting这些常用的任务(task)都已经以grunt插件的形式被开发出来了。只要在 package.json 文件中被列为dependency(依赖)的包,并通过npm install安装之后,都可以在Gruntfile中以简单命令的形式使用:

// 加载能够提供”uglify”任务的插件。

grunt.loadNpmTasks(‘grunt-contrib-uglify’);

        注意: grunt –help 命令将列出所有可用的任务。

2.2.5.8 插件使用示例

        任务:合并src下的js文件到build目录,合并后文件名为built.js。

grunt.initConfig({

       concat: {

           options: {

                //文件内容的分隔符

                separator:’;’

           },

           dist: {

                src: [‘src/*.js’],

                dest:’build/built.js’

           }

       }

    });

        向文件追加一些额外信息:

grunt.initConfig({

       pkg: grunt.file.readJSON(‘package.json’),

       concat: {

           options: {

                //文件内容的分隔符

                separator:’;’,

                stripBanners: true,

                banner:’/*! <%= pkg.name %> – v<%= pkg.version %> – ‘+

                            ‘<%= grunt.template.today(“yyyy-mm-dd”) %> */’

           },

           dist: {

           }

       }

});

        自定义进程函数,比如你需要在合并文件前,对文件名进行处理等。

grunt.initConfig({

       pkg: grunt.file.readJSON(‘package.json’),

       concat: {

           options: {

                 // Replace all’use strict’statements inthe code with a single one at the top

                banner:”‘use strict’;\n”,

                process: function(src,filepath) {

                        return ‘// Source: ‘ + filepath + ‘\n’+ src.replace(/(^|\n)[ \t]*(‘use strict’|”use strict”);?\s*/g,’$1’);

                }

           },

           dist: {

           }

       }

    });

2.2.5.9 自定义任务

        通过定义 default 任务,可以让Grunt默认执行一个或多个任务。在下面的这个案例中,执行 grunt 命令时如果不指定一个任务的话,将会执行uglify任务。这和执行grunt uglify 或者 grunt default的效果一样。default任务列表数组中可以指定任意数目的任务(可以带参数)。

// Default task(s).

grunt.registerTask(‘default’,[‘uglify’]);

        如果Grunt插件中的任务(task)不能满足你的项目需求,你还可以在Gruntfile中自定义任务(task)。例如,在下面的 Gruntfile 中自定义了一个default 任务,并且他甚至不依赖任务配置:

module.exports =function(grunt) {

    // A very basic default task.

    grunt.registerTask(‘default’, ‘Log some stuff.’, function() {

    grunt.log.write(‘Logging some stuff…’).ok();

  });

};

特定于项目的任务不必在 Gruntfile 中定义。他们可以定义在外部.js 文件中,并通过grunt.loadTasks 方法加载。

2.2.5.10 合并文件

        合并文件依赖于grunt-contrib-concat插件,所以我们的package依赖项要新增一项

“devDependencies”: {

     “grunt”: “~0.4.1”,

     “grunt-contrib-jshint”: “~0.6.3”,

     “grunt-contrib-concat”: “~0.3.0”,

     “grunt-contrib-uglify”: “~0.2.1”,

     “grunt-contrib-requirejs”: “~0.4.1”,

     “grunt-contrib-copy”: “~0.4.1”,

     “grunt-contrib-clean”: “~0.5.0”,

     “grunt-strip”: “~0.2.1”

},

        然后再将代码写成这个样子

module.exports = function(grunt) {

    // 项目配置

   grunt.initConfig({

       pkg: grunt.file.readJSON(‘package.json’),

       concat: {

         options: {

           separator: ‘;’

       },

       dist: {

           src: [‘src/zepto.js’, ‘src/underscore.js’, ‘src/backbone.js’],

           dest: ‘dest/libs.js’

       }

    }

  });

 grunt.loadNpmTasks(‘grunt-contrib-concat’);

  // 默认任务

  grunt.registerTask(‘default’, [‘concat’]);

}

    运行后,神奇的一幕发生了:

        三个文件被压缩成了一个,但是没有压缩,所以,我们这里再加一步操作,将之压缩后再合并.

module.exports = function(grunt) {

  // 项目配置

 grunt.initConfig({

   pkg: grunt.file.readJSON(‘package.json’),

   concat: {

     options: {

       separator: ‘;’

     },

     dist: {

       src: [‘src/zepto.js’, ‘src/underscore.js’, ‘src/backbone.js’],

       dest: ‘dest/libs.js’

     }

   },

   uglify: {

     build: {

       src: ‘dest/libs.js’,

       dest: ‘dest/libs.min.js’

     }

    }

  });

 grunt.loadNpmTasks(‘grunt-contrib-uglify’);

 grunt.loadNpmTasks(‘grunt-contrib-concat’);

  // 默认任务

 grunt.registerTask(‘default’, [‘concat’, ‘uglify’]);

}

        我这里的做法是先合并形成一个libs,然后再将libs压缩成libs.min.js

        所以我们这里换个做法,先压缩再合并,其实unglify已经干了这些事情了

module.exports = function(grunt) {

  // 项目配置

 grunt.initConfig({

   pkg: grunt.file.readJSON(‘package.json’),

   uglify: {

     “my_target”: {

       “files”: {

         ‘dest/libs.min.js’: [‘src/zepto.js’, ‘src/underscore.js’,’src/backbone.js’]

       }

     }

    }

  });

 grunt.loadNpmTasks(‘grunt-contrib-uglify’);

  // 默认任务

 grunt.registerTask(‘default’, [‘uglify’]);

}

        所以,我们就暂时不去关注concat了。

【grunt第一弹】30分钟学会使用grunt打包前端代码

http://www.cnblogs.com/yexiaochai/p/3594561.html

2.2.6 常用插件

2.2.6.1 js压缩插件grunt-contrib-uglify

使用UglifyJS压缩js文件:grunt-contrib-uglify

module.exports = function(grunt) {

   //项目配置

   grunt.initConfig({

       pkg : grunt.file.readJSON(‘package.json’),

       uglify : {

           options : {

                banner : ‘/*! <%= pkg.file%> <%= grunt.template.today(“yyyy-mm-dd”) %> */\n’

           },

           build : {

                src : ‘src/<%=pkg.file%>.js’,

                dest : ‘dest/<%= pkg.file%>.min.js’

           }

       }

   });

   //加载提供”uglify”任务的插件

   grunt.loadNpmTasks(‘grunt-contrib-uglify’);

   //默认任务

   grunt.registerTask(‘default’, [‘uglify’]);

};

2.2.6.2 文件合并插件grunt-contrib-concat

        用于合并任意文件,用法也非常简单:

npm install grunt-contrib-concat –save-dev

grunt.loadNpmTasks(‘grunt-contrib-concat’);

合并并压缩文件:

        方式一、先合并再压缩,首先将三个文件合并成一个文件libs.js,然后再将文件libs.js压缩成libs.min.js

module.exports = function(grunt) {

   //项目配置

   grunt.initConfig({

       pkg : grunt.file.readJSON(‘package.json’),

       concat : {

           options : {

                separator : ‘;’

           },

           dist : {

                src : [‘src/zepto.js’, ‘src/underscore.js’, ‘src/backbone.js’],

                dest : ‘dest/libs.js’

           }

       },

       uglify : {

           build : {

                src : ‘dest/libs.js’,

                dest : ‘dest/libs.min.js’

           }

       }

   });

   grunt.loadNpmTasks(‘grunt-contrib-uglify’);

   grunt.loadNpmTasks(‘grunt-contrib-concat’);

   //默认任务

   grunt.registerTask(‘default’, [‘concat’, ‘uglify’]);

}

        方式二:先压缩后合并,其实这里只需要grunt-contrib-uglify一个插件就可以完成代码的压缩及合并。

module.exports = function(grunt) {

   //项目配置

   grunt.initConfig({

       pkg : grunt.file.readJSON(‘package.json’),

       uglify : {

           “target” : {

                “files” : {

                    ‘dest/libs.min.js’ : [‘src/zepto.js’, ‘src/underscore.js’, ‘src/backbone.js’]

                }

           }

       }

    });

   grunt.loadNpmTasks(‘grunt-contrib-uglify’);

   //默认任务

   grunt.registerTask(‘default’, [‘uglify’]);

}

2.2.6.3 复制文件或目录插件grunt-contrib-copy

        顾名思义,用于复制文件或目录的插件。

copy: {

 main: {

   files: [

     {src: [‘path/*’], dest: ‘dest/’, filter: ‘isFile’}, // 复制path目录下的所有文件

     {src: [‘path/**’], dest: ‘dest/’}, // 复制path目录下的所有目录和文件

    ]

  }

}

2.2.6.4 删除插件grunt-contrib-clean

        有复制,必然有删除。

grunt-contrib-clean

clean: {

  build: {

   src: [“path/to/dir/one”, “path/to/dir/two”]

  }

}

2.2.6.5 压缩插件grunt-contrib-compress

        用于压缩文件和目录成为zip包,不是很常用。

compress: {

 main: {

   options: {

     archive:’archive.zip’

   },

   files: [

     {src: [‘path/*’], dest: ‘internal_folder/’, filter: ‘isFile’}, path下所有的js

     {src: [‘path/**’], dest: ‘internal_folder2/’}, // path下的所有目录和文件

    ]

  }

}

2.2.6.6 代码检查插件grunt-contrib-jshint

        jshint用于javascript代码检查(并会给出建议),发布js代码前执行jshint任务,可以避免出现一些低级语法问题。

        jshint拥有非常丰富的配置,可以自由控制检验的级别。

module.exports = function(grunt) {

    // 构建任务配置

    grunt.initConfig({

       //读取package.json的内容,形成个json数据

       pkg: grunt.file.readJSON(‘package.json’),

       jshint:{

           options:{

                //大括号包裹

                curly:true,

                //对于简单类型,使用===和!==,而不是==和!=

                eqeqeq:true,

                //对于首字母大写的函数(声明的类),强制使用new

                newcap:true,

                //禁用arguments.caller和arguments.callee

                noarg:true,

                //对于属性使用aaa.bbb而不是aaa[‘bbb’]

                sub:true,

                //查找所有未定义变量

                undef:true,

                //查找类似与if(a = 0)这样的代码

                boss:true,

                //指定运行环境为node.js

                node:true

           },

           //具体任务配置

           files:{

                src: [‘src/*.js’]

           }

       }

   });

    // 加载指定插件任务

   grunt.loadNpmTasks(‘grunt-contrib-jshint’);

    // 默认执行的任务

   grunt.registerTask(‘default’, [‘jshint’]);

};

        配置的含义,明河都写在代码注释中,就不再累述了。

jshint比较有意思的是还可以结合 grunt-contrib-concat 插件使用,在合并文件前(后)对js进行检查。

grunt.initConfig({

  concat: {

   dist: {

     src: [‘src/foo.js’, ‘src/bar.js’],

     dest:’dist/output.js’

    }

  },

  jshint: {

    beforeconcat: [‘src/foo.js’, ‘src/bar.js’],

   afterconcat: [‘dist/output.js’]

  }

});

        类似的还有个 grunt-contrib-csslint 插件,用于css代码检查,用法基本一样,只是配置上存在差异,貌似css检查的需求有些鸡肋,就不再演示了。

2.2.6.7 css压缩插件grunt-contrib-mincss

        非常常用的插件,用于css压缩。用法相对于grunt-contrib-uglify简单很多:

mincss: {

  compress: {

   files: {

     “path/to/output.css”: [“path/to/input_one.css”, “path/to/input_two.css”]

    }

  }

}

2.2.6.8 grunt-css-combo

        我的同事紫英写的css合并的插件,用于css分模块书写时的合并(如果你不使用less、sass、stylus,建议使用这个插件)。

grunt.initConfig({

  css_combo: {

   files: {

     ‘dest/index.combo.css’: [‘src/index.css’],

   },

  },

})

文件目录的demo请看 github 。

        src/index.css的代码如下:

@import “./mods/mod1.css”;

@import “./mods/mod2.css”;

#content{}

        通过 @import 来合并模块css文件。

2.2.6.9 清理文件和文件夹:grunt-contrib-clean

module.exports = function(grunt) {

   grunt.initConfig({

       clean : {

           build : [‘.tmp’, ‘build’],

           release : [‘release’]

       }

   });

   grunt.loadNpmTasks(‘grunt-contrib-clean’);

   grunt.registerTask(‘default’, [‘clean’]);

};

2.2.6.10 图片压缩grunt-contrib-imagemin

grunt-contrib-imagemin:图片压缩。

2.2.6.11 grunt-usemin

grunt-useminReplaces references to non-optimizedscripts or stylesheets into a set of HTML files (or any templates/views).

2.2.6.12 Html压缩grunt-contrib-htmlmin

grunt-contrib-htmlminMinify HTML

2.2.6.13 其他

Grunt plugin: Plugins – Grunt: The JavaScript Task Runner

CSS

gruntjs/grunt-contrib-compass ツキ GitHub 平常主要用Scss

gruntjs/grunt-contrib-less ツキ GitHub 因为要用Bootstrap

JS

gruntjs/grunt-contrib-coffee ツキ GitHub 平常主要用Coffee

gruntjs/grunt-contrib-jshint ツキ GitHub 检查代码中糟糕的部分,大家都用

Automation自动化

sindresorhus/load-grunt-tasksGitHub

        Load multiple grunt tasks using globbing patterns. 有了这个你就不用手动加载Grunt任务了

sindresorhus/time-gruntGitHub

        Displays the elapsed execution time of grunt tasks 计算任务执行的时间

gruntjs/grunt-contrib-watch ツキ GitHub

        Run predefined tasks whenever watched file patterns are added, changed or deleted. 自动刷新,这个应该每个人都用的吧

sindresorhus/grunt-concurrent GitHub

        Run grunt tasks concurrently 并发执行任务

gruntjs/grunt-contrib-clean ツキ GitHub

        Clean files and folders. 做清洁工作,不然会残留很多没有用的文件

yeoman/grunt-usemin GitHub

        怎么说呢,反正一个很好用的东西:)

cbas/grunt-rev GitHub

gruntjs/grunt-contrib-copy · GitHub

        Copy files and folders. 自动复制粘贴文件的

gruntjs/grunt-contrib-uglify ツキ GitHub

        Concat and Minify JavaScript files with UglifyJS. 压缩JS文件的

gruntjs/grunt-contrib-htmlmin ツキ GitHub

        Minify HTML

gruntjs/grunt-contrib-imagemin ツキ GitHub

        Minify PNG and JPEG images 优化图片的,其实没怎么用

gruntjs/grunt-contrib-connect ツキ GitHub

        Start a connect web server. 在本地模拟一个web server

    • jsoverson/grunt-open · GitHub

        Open urls and files from a grunt task 自动打开,很好用 :)

2.2.7 扩展阅读

• The Installing gruntguide has detailed information about installing specific, production or in-development, versions of Grunt and grunt-cli.

• The ConfiguringTasks guide has an in-depth explanation on how to configure tasks, targets, options and files inside the Grunt file, along with an explanation of templates, globbing patterns and importing external data.

• The Creating Tasksguide lists the differences between the types of Grunt tasks and shows a number of sample tasks and configurations.

        For more information about writing custom

tasks or Grunt plugins, check out the developer documentation.

2.2.8 参考链接

grunt快速入门

http://www.gruntjs.net/docs/getting-started/

Grunt常用插件整理

http://www.jianshu.com/p/2d5249914460

上百个必备的Grunt插件

http://www.open-open.com/lib/view/open1424570955018.html

Grunt配置文件编写技巧及示范

http://www.xuanfengge.com/grunt-profile-writing-tips-and-demonstrations-2.html

    原文作者:Kevin_Junbaozi
    原文地址: https://www.jianshu.com/p/d705b636964d
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞