有关 Grunt - 自动化构建工具的奇技淫巧

之因此想写有关前端自动化工具的文章出于如下几个缘由:javascript

  1. 自动化构建工具对于前端开发的重要性:高效、减小重复性操做、各类强大插件的支撑。php

  2. 构建工具的上手使用有必定的成本,其中也有很多坑踩,前端在掌握html/js/css三剑客的同时,还须要了解node.js、npm包管理器、构建工具的配置、语法糖以及插件的使用,也要学会当构建工具的使用日趋复杂庞大的时候如何优雅有效的组织代码,减小在使用工具的时候出现bug的几率。css

  3. 工做中遇到一些grunt相关的经常使用实例与奇技淫巧能够拿来品玩、解读,有助于更快速上手并定制一套强大的自动化工做方式。html

  4. 同类的构建工具例如gulp、webpack(严格意义上它应该是模块管理工具,但它依旧能够作一些构建的工做),甚至是扬言能够摈弃grunt与gulp的npm scripts,它们各有各的可取之处,刷新了我对构建工具的认识。而在我看来,与其争论个孰好孰坏,还不如用上一个本身以为顺手的、更贴合项目需求的工具库。前端

自动化构建工具 - Grunt

先说下在没有诞生这些工具以前写前端代码的一些痛点:java

  • “css写得好费劲啊,那些可复用的样式能不能存在一个变量或函数里直接调用啊”node

  • “样式里还要记得写上兼容不一样浏览器的前缀,ctrl+C/V手好累”android

  • “更改代码后每次都要按F5来刷新浏览器,若是要进行多台设备的调试,每台设备都要手动刷新下,想一想都以为心累~”webpack

  • “代码写完后要借用工具手动合并、压缩最后还要本身再拷贝到产品目录下,每次发布都要进行着重复的操做...”ios

  • “太好了,代码合并后如今页面只有一个script标签了,大幅度减小了请求数,可是却引入了其余页面才会使用到的代码,能不能拆分到它们各自须要的page view里啊...”

  • etc...

痛点实在太多,不胜枚举,小点的项目这么手动折腾下无伤大雅,可是到了大中型的程度依旧这么徒手操做,实在不敢想象。为了让前端的工做不那么枯燥,各路好汉纷纷支招,在node的光环照耀下,js的构建工具应运而生,逐渐成为前端生态下必不可少的一环。自动化的构建工具就是要让你在编写前端代码的时候对反复重复 枯燥无聊的工做 say no。

About Grunt

前面扯了那么多闲话,赶忙介绍今天的主角吧。Grunt,(说实话第一眼看到这个单词我居然想到的是魔兽争霸里我兽族的大G~) 为何要选择用grunt来做为首选的构建工具呢,首先仍是由于我的比较熟悉吧,也是用到的第一个构建框架,其次借用下官方说的推荐原因:

Grunt生态系统很是庞大,而且一直在增加。因为拥有数量庞大的插件可供选择,所以,你能够利用Grunt自动完成任何事,而且花费最少的代价。若是找不到你所须要的插件,那就本身动手创造一个Grunt插件,而后将其发布到npm上吧。
---- from grunt 官网介绍

是的,截止到目前为止grunt的插件数目已经达到5,500多个,拥有了这些插件就比如拥有了一把瑞士军刀,正所谓工欲善其事必先利其器,有关grunt的基本安装、配置、注册任务、etc..就不在此多作介绍,详情能够参照官网的快速入门指南,让咱们看下插件TOP100里,grunt是如何让咱们的武器更加锋利无比的。

Grunt 的基本套装

grunt自家利器:(grunt官方维护的插件)

包名称 说明
contrib-watch 监视文件的变化,能够指定发生变化时执行的任务
contrib-clean 清楚指定目录下的文件
contrib-jshint js语法规范提示,能够将规范写入配置文件,对不符合规范的代码予以提示
contrib-copy 拷贝文件到指定目录
contrib-uglify 压缩指定的js代码
contrib-concat 合并指定的js or css代码
contrib-cssmin 压缩指定的css代码
contrib-less 将less文件编译为css
contrib-htmlmin 压缩指定的html代码
contrib-imagemin 压缩指定的图片

家常必备神器:(经常使用的第三方插件,配合官方插件效果更佳)

包名称 说明
postcss css预处理工具,能够实现less or scss or stylus的css预处理器效果,也能够借助其强大的auto-prefix插件来为css代码自动添加兼容性浏览器厂商前缀
babel ES6语法转为ES5 js转换器
sync 相似contrib-copy,但只是拷贝那些被更改过的文件
webpack 强大的模块管理工具,其极具特点的loader功能可让你在js代码里引入几乎任何类型文件
jsdoc 经过写遵循约定好的语法格式的注释而自动生成文档的grunt插件
sails-linker 将css or js(一个或多个)文件自动插入到页面的指定位置
assets-linker 相似sails-linker,但其配置语法更为简洁
browser-sync 一个支持在多个设备间同步测试与调试的轻量版http开发服务器
time-grunt 能够直观的看到每一个grunt task的耗时,能够有效的优化构建工具
grunt-cdn 指定cdn路径,为css、js资源添加cdn路径
load-grunt-configs 能够将注册好的各个grunt task拆分到单独的文件里,在tasks数目比较大的时候能更方便组织与管理
load-grunt-tasks 自动将各个task载入到grunt.loadNpmTasks中,节省代码量

Grunt 全家桶的运用场景

在此,假定你已经掌握如何安装grunt、配置package.json文件、使用grunt插件以及注册grunt task等一系列基本操做,若是仍是不太清楚请猛戳官方介绍。紧接上面介绍的十几款经常使用的grunt插件,我想从项目的两种模式(开发与产品)里详细的列出它们的使用场景,但在此以前,有必要从一个基础的项目例子讲起,它的目录架构大致长这样:

├── your project
│   ├── Gruntfile.js
│   ├── package.json
│   ├── grunt
│   │   ├── watch.js
│   │   ├── clean.js
│   │   ├── ...
│   ├── assets
│   │   ├── js
│   │   │   ├── index.js
│   │   │   ├── ...
│   │   ├── less
│   │   │   ├── index.less
│   │   │   ├── ...
│   ├── www
│   │   ├── js
│   │   │   ├── index.js
│   │   │   ├── ...
│   │   ├── css
│   │   │   ├── index.css
│   │   │   ├── ...
│   ├── build
|   |   ├── min
│   │   |   ├── js
│   │   │   |   ├── index.js
│   │   │   |   ├── ...
│   │   |   ├── css
│   │   │   |   ├── index.css
│   │   │   |   ├── ...

其中想特别说明的是,在官网介绍的 Gruntfile.js 文件中,grunt 个插件的配置以及task的载入都是相似下面的方式书写的:

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

};

这其中只是引入了一个任务(uglify)的插件。想象一下,若是有几十个插件写入,Gruntfile.js 可就没那么好看咯。为了可以单独拆分每一个插件到不一样文件,分开管理,这里就须要引入 load-grunt-configsload-grunt-tasks 插件,它们分别实现grunt任务拆分到单独文件与自动加载包含对应的grunt任务。在它们的帮助下代码量将极大的减小,而且极大的提升grunt各任务的可维护性。若对比官方的写法,如今的代码能够是相似这般的优雅:

module.exports = fucntion(grunt){
    var options = {
        config : {
            src: "grunt/*.js"
        }
    };

    var configs = require('load-grunt-configs')(grunt, options);
    grunt.initConfig(configs);
    
    // Load grunt tasks automatically
    require('load-grunt-tasks')(grunt);
}

将各自的grunt任务写到单独的js文件里,以 watch task 为例,像这样:

module.exports.tasks = {
    watch: {
        js: {
            files: [
                'assets/js/**/*.js',
                'routes/**/*.js'
            ],
            tasks: ['copy:dev'],
            options: {
                livereload: true
            }
        },
        less: {
            files: ['assets/styles/**/*.less'],
            tasks: ['less:dev', 'postcss'],
            options: {
                livereload: true
            }
        },
        view: {
            files: ['templates/**/*'],
            options: {
                livereload: true
            }
        }
    }
};

把这些文件都放在 grunt 目录下,再在 load-grunt-configs 的 options 配置里指定好grunt目录位置,就能够轻松实现grunt任务写入单独文件。而经过 load-grunt-tasks,咱们只须要一行代码:

// Load grunt tasks automatically
require('load-grunt-tasks')(grunt);

就能够代替以下 n 行!

grunt.loadNpmTasks('grunt-shell');
grunt.loadNpmTasks('grunt-sass');
grunt.loadNpmTasks('grunt-recess');
grunt.loadNpmTasks('grunt-sizediff');
grunt.loadNpmTasks('grunt-svgmin');
grunt.loadNpmTasks('grunt-styl');
grunt.loadNpmTasks('grunt-php');
grunt.loadNpmTasks('grunt-eslint');
grunt.loadNpmTasks('grunt-concurrent');
grunt.loadNpmTasks('grunt-bower-requirejs');
...

而另外一个能够在 Gruntfile.js 中配合使用的插件 --- time-grunt,它能够很是直观的输出每一个grunt task的耗时以便你能够针对某项task作好构建时间的优化,以下所示:

clipboard.png

在开发模式下使用grunt

开发模式下的grunt任务主要包括源码预编译、代码修饰、代码规范检查、代码tag的自动注入等,这些如同为你配备了一把全能的瑞士军刀般的体验彻底能够解决以前提到的诸多痛点,结合grunt全家桶,下面一一介绍如何配置好一套适用于开发环境下的自动化流程:

首先回到以前的项目目录,能够看到分别有assets、www、build三个包含了相似文件的目录,

  • assets 用于存放项目前端代码的源码

  • www 里包含了编译、修饰过的、可供本地调试服务器上的网页直接访问的代码与静态资源

  • build 则是包含了产品模式下的全部打包过的代码与资源,用于放在cdn服务下

之因此这么划分是为了让grunt的职责与分工更加明确,也方便两种模式下的轻松切换与管理。

进入正题,下面列出的是一套开发模式下经常使用到的任务列表:

[
    'clean',
    'less',
    'postcss',
    'jshint',
    'copy',
    'asset-linker',
    'browserSync',
    'watch'
]

以上任务转换为天然语言就是:

  • 首先清空目标目录,确保下次再执行grunt任务时清空上次任务生成的文件,写入一个干净的目录下

  • 将less(css预编译语言,此处也能够是scss、stylus)编译成css

  • 修饰编译好的css(例如简化后的css、经过auto-prefix添加过兼容性前缀的css)

  • 检测js代码的规范性,是否有书写有误,是否足够规范

  • 将处理好的代码拷贝到目标目录(例如www、build)

  • 自动添加link、script标签到html或模板文件下

  • 检测指定目录下的文件,若有任何修改,则自动刷新浏览器,修改效果所见即所得

接下来,咱们只须要把这一些列任务注册到grunt dev这个指令下,每一个任务按照排列的前后顺序依次执行:

grunt.registerTask('dev', [
    'clean:dev',
    'less',
    'postcss',
    'jshint',
    'copy:dev',
    'asset-linker:linkCssDev',
    'asset-linker:linkJsDev',
    'browserSync',
    'watch'
]);

PS:大部分grunt任务都是支持多线程的,即每一个grunt任务下能够同时运行多个子任务,也能够单独只运行某个子任务,像'clean:dev',就运行了clean下的dev子任务。所以这里能够根据环境来分为dev与build

为了更直观的了解grunt任务的子任务,举个栗子就好啦:

module.exports.tasks = {
    clean: {
        dev: ['www'],
        build: ['build-res']
    }
};

注:当咱们在terminal输入grunt clean时,默认会执行clean下的全部子任务:dev与build

在上面的例子里经过registerTask注册过的任务集群,咱们只要在终端输入grunt dev,剩下的事就交给工具自行处理便可

在产品模式下使用grunt

在我看来,产品模式较之开发模式显得更为严谨精简。开发模式讲究的是开发者能够快速的调试与追踪本身的代码以及代码变动产生的所见即所得的效果,为的是更高效、更便捷的完成功能点的开发与测试。而产品模式则要求原来在开发模式下的代码更少出现错误、更小的体积(文件大小)更适于网络传播,不只如此,产品模式还须要考虑到每次发布版本的时候,经过加入代码的版本号,来保证版本更新的平滑过渡,而接下来,就一步步来介绍如何让grunt为咱们处理好这一切:

先献出一份产品模式下的tasks list:

grunt.registerTask('build', [
    'clean:dev',
    'less',
    'postcss',
    'jshint',
    'copy:dev',
    'asset-linker:linkCssDev',
    'asset-linker:linkJsDev',
    'cdn',
    'concat',
    'uglify',
    'cssmin',
    'asset-linker:linkCssProd',
    'asset-linker:linkJsProd',
    'clean:build',
    'copy:build'
    ]);

能够发现这份列表基本囊括了开发模式下的任务,为此咱们能够把这部分共有的task单独注册到一个叫作compileAssets里:

grunt.registerTask('compileAssets', [
    'clean:dev',
    'less',
    'postcss',
    'jshint',
    'copy:dev',
    'asset-linker:linkCssDev',
    'asset-linker:linkJsDev'
]);

grunt.registerTask('dev', [
    'compileAssets',
    'browserSync',
    'watch'
]);

grunt.registerTask('build', [
    'compileAssets',
    'cdn',
    'concat',
    'uglify',
    'cssmin',
    'asset-linker:linkCssProd',
    'asset-linker:linkJsProd',
    'clean:build',
    'copy:build'
]);
添加版本号

众所周知,每一个项目中的package.json都有一个version的字段来代表项目的版本号,而咱们要作的就是把这个版本号添加到相关的任务中:

相关任务

  • cdn

  • asset-linker

  • copy

关于添加版本号的位置,咱们能够把版本号添加到文件的末尾处,例如index.1.0.0.js,可是仔细想下,发布版本时,为了能保证新旧版本的文件能够同时保留到线上,必定会出现一个文件夹下有好多个带版本号的文件(当你保留的版本号比较多的时候),这样很显然不方便整理,为此最明智的选择是把版本号放到根目录下,例如http://your-web-site/1.0.1/index.js,如此一来一个版本就是一个目录,既美观又方便版本管理,想删掉其中一个版本,只要把整个目录除去掉便可。

gulp 与 npm scripts

原本这篇文章只想介绍grunt的内容,但既然你们都是自动化构建工具,也就不得不把这俩货搬出来聊聊。又由于前一阵子读到一篇《我为什么放弃Gulp与Grunt,转投npm scripts》的译文,可谓大开眼界,茅塞顿开,醍醐灌顶,心邻神会,如沐春风,不明觉厉... 既然都写到这了就简单介绍下二者吧

gulp

gulp给我最大的感觉就是:

  • 配置代码更简洁、更直观

  • 基于node.js的streams流工做方式,使其处理任务速度更快

gulp容许你把源文件灌入到管道内,期间能够配置一系列插件对管道内的文件逐一处理,最后输出到目标位置。像是工厂里的流水线同样,gulp直接把上一个流水线任务完成的output做为下一个流水线任务的input,这就意味着相比grunt而言,咱们不须要在每一个grunt任务里指定这个任务的input与output,这样就节省不少代码,说再啰嗦也敌不过一个赤裸裸的例子摆在你的面前:

Grunt

sass: {
  dist: {
    options: {
      style: 'expanded'
    },
    files: {
      'dist/assets/css/main.css': 'src/styles/main.scss',
    }
  }
},

autoprefixer: {
  dist: {
    options: {
      browsers: [
        'last 2 version', 'safari 5', 'ie 8', 'ie 9', 'opera 12.1', 'ios 6', 'android 4'
      ]
    },
    src: 'dist/assets/css/main.css',
    dest: 'dist/assets/css/main.css'
  }
},

grunt.registerTask('styles', ['sass', 'autoprefixer']);

让咱们看下一样的配置在Gulp下是怎么实现的:

Gulp

gulp.task('sass', function() {
  return sass('src/styles/main.scss', { style: 'expanded' })
    .pipe(autoprefixer('last 2 version', 'safari 5', 'ie 8', 'ie 9', 'opera 12.1', 'ios 6', 'android 4'))
    .pipe(gulp.dest('dist/assets/css'))
});

有木有一种眼前一亮的感受!这确实会让很多grunt的老玩家会毅然决定跳到gulp圈里。有关Gulp的配置与入门教程,能够参考这篇很是棒的入门文章,以上的代码例子也是引用这篇好文(好学生要注明摘要出处,尊重版权) --- Getting started with gulp

npm scripts

说实话,看完那篇《我为什么放弃Gulp与Grunt,转投npm scripts》,给我最最最形象的感觉是,就像听到一个大神说,编辑器我只用Vim,容我拜三下。固然啦,总的来讲npm scripts大法很好很强大,也须要必定的成本才能练就,就像文中所说的要使用npm scipts可能还须要学会一些命令行的指令与操做,这更像是高级玩家玩的游戏,一下post出一些文中提到的其强大之处:

npm scripts自己实际上是很是强大的。它提供了基于约定的pre与post钩子:

{
    name: "npm-scripts-example",
    version: "1.0.0",
    description: "npm scripts example",
    scripts: {
        prebuild: "echo I run before the build script",
        build: "cross-env NODE_ENV=production webpack",
        postbuild: "echo I run after the build script"
    }
}

此外,还能够经过在一个脚本中调用另外一个脚原本对大的问题进行分解:

{
  "name": "npm-scripts-example",
  "version": "1.0.0",
  "description": "npm scripts example",
  "scripts": {
    "clean": "rimraf ./dist && mkdir dist",
    "prebuild": "npm run clean",
    "build": "cross-env NODE_ENV=production webpack"
  }
}

若是一个命令很复杂,那还能够调用一个单独的文件:

{
  "name": "npm-scripts-example",
  "version": "1.0.0",
  "description": "npm scripts example",
  "scripts": {
    "build": "node build.js"
  }
}

总结

学会使用恰当的工具来解决问题必定会是一件大快人心的事,也会让工做变得更有趣、更具可玩性。文中提到的三种自动化构建工具基本是前端工程化工做中必不可少的须要掌握的除js、css、html外的工做技巧。grunt有其庞大的插件在背后支持,能够经过大量组合来支撑更为复杂的构建工做。gulp更符合小而美,快而精,less is more的准则,github上得到很多的点赞(比grunt多好多!),算是后起之秀。而npm scripts则脱离了一层没必要要的抽象,且不须要像grunt和gulp要依赖与其插件做者的维护,直接经过npm的指令便可完成大部分构建工做,为自动化构建流程提供了一种新的思路,有一种返璞归真的意思。因此,具体真的要选择哪种做为工做主打的工具,仍是那句话,就用你以为顺手的那个好啦~

相关文章
相关标签/搜索