Gulp —— 另一种自动化流水线

很久没有撸文字了,恰好挤出了个小周末,略深入的折腾了下 gulp.js - the streaming build system,顺便写写一些感受。

Node 社区近年是鸡血满满,隔三差五的会抛出一些新玩具出来。Gulp 也不例外,发现其他工具( Grunt: The JavaScript Task Runner )让自己不爽,然后就弄个让自己爽的玩具。


当然,这个不爽,更多的是个人习惯的问题,Programme 还是 Configure 这个看自己的喜好了,先从别人的文章 ( No Need To Grunt, Take A Gulp Of Fresh Air )中引用两段代码,以比较两者的看上去的区别。


Gulp:

var gulp = require('gulp');
var jshint = require('gulp-jshint');
var concat = require('gulp-concat');
var rename = require('gulp-rename');
var uglify = require('gulp-uglify');

// Lint JS
gulp.task('lint', function() {
  return gulp.src('src/*.js')
    .pipe(jshint())
    .pipe(jshint.reporter('default'));
});

// Concat & Minify JS
gulp.task('minify', function(){
  return gulp.src('src/*.js')
    .pipe(concat('all.js'))
    .pipe(gulp.dest('dist'))
    .pipe(rename('all.min.js'))
    .pipe(uglify())
    .pipe(gulp.dest('dist'));
});

// Watch Our Files
gulp.task('watch', function() {
  gulp.watch('src/*.js', ['lint', 'minify']);
});

// Default
gulp.task('default', ['lint', 'minify', 'watch']);

Grunt:

module.exports = function(grunt) {
  grunt.initConfig({
    concat: {
      'dist/all.js': ['src/*.js']
    },
    uglify: {
      'dist/all.min.js': ['dist/all.js']
    },
    jshint: {
      files: ['gruntfile.js', 'src/*.js']
    },
    watch: {
      files: ['<%= jshint.files =>'],
      tasks: ['jshint', 'concat', 'uglify']
    }
  });
  // Load Our Plugins
  grunt.loadNpmTasks('grunt-contrib-jshint');
  grunt.loadNpmTasks('grunt-contrib-concat');
  grunt.loadNpmTasks('grunt-contrib-uglify');
  grunt.loadNpmTasks('grunt-contrib-watch');

  // Register Default Task
  grunt.registerTask('default', ['jshint', 'concat', 'uglify']);
};

同样是以插件的形式为核心功能做扩展,同样可以扩展 CLI,自定义自己的任务指令。

Gulp 更加遵循 CommonJS 的写法,require 照旧,然后定义 Task 也是通过 放进流水线管道 实现的。

而 Grunt 像是一堆配置文件的填写,加入插件的方式,更是自成一系( 不过,有人在 Grunt 中实现了类似 Gulp 的使用方法 —— Gulp-style stream piping in Grunt, or anywhere else ,只是感觉有点诡异)。

比较孰优孰劣,让时间去辨别吧。下面重点说说 Gulp。


## 结构

前端开发中一个很纠结的问题,易于管理的代码,如果直接放到浏览器中使用,会有诸如加载请求过多,文件过大而加载缓慢等乱七八糟的问题。

所以,在部署到生产环境的时候,往往会需要做一些代码合并压缩,图片大小优化等等这些事,放 CDN 然后打上不同的版本号。

这些乱七八糟的事,现在是可以借助 Grunt 或者 Gulp 直接完成的了,非常省事,虽然很久以前就有解决方案,但显然不是对前端工程师友好的方案。

同时,为了方便版本管理系统的代码整洁性,其实对源代码进行版本控制即可,最后的生产代码,显然是可以随用随生成的,非常的快速。

于是,先需要有一个合理的结构。

/.tmp        # 临时存放目录 (.ignore)
/public      # 生产代码 (.ignore)
/server      # 如果服务端也是 js 的话
/src         # 浏览器用的 js
/test        # 各种测试
/view        # 内容呈现相关
  /jade      # html 模版引擎
  /stylus    # css 预编译 换 less 或者 sass 随喜好
  /res       # 其他与 css 密切相关的 图片 字体 影像 等
gulpfile.js  # gulp 的入口文件
package.json # node 配置文件
  • /view/jade 内的内容需要被编译成 html 文件,甚至是转换为 js。
  • /view/stylus 内的内容会转换为一个或多个 css。
  • /res 里直接 copy 更新到 public 即可,部分图片等需要处理什么的。
  • /src 里的 js 也是需要打包压缩合并为一个或多个 js,在合并前需要跑 jshint 要跑单元测试等。

## 另一个约定

CommonJS、Jade、Stylus 等可以利用 require、import、include,定义好不同模块文件的依存关系,这样为了减少 gulp 配置的麻烦,可以统一 主文件 前缀 下划线,用于对应任务的入口,而对于 watch 是监听目录里的所有。

## 常用的组件

 "devDependencies": {
    "gulp": "*",                  // 基础
    "gulp-if": "*",               // 根据不同的环境,切换方法

    "gulp-util": "*",             // 如果有自定义方法,可能会用到
    "gulp-clean": "*",            // 清理路径下文件
    "gulp-rename": "*",           // 重命名文件,比如上节提到 _ 需要还原回去
    "gulp-concat": "*",           // 文件合并

    "gulp-jshint": "*",           // jshint 检查一些格式,这个是为了统一团队的代码风格的
    "gulp-browserify": "*",       // 利用 CommonJS 的格式,直接让浏览器也能用类似的方式
    "gulp-uglify": "*",           // 替换压缩

    "gulp-jade": "*",             // jade
    "gulp-stylus": "*",           // stylus

    "gulp-mocha": "*",            // 测试框架
    "chai": "*",
    "jscov": "*",

    "gulp-changed": "*"           // 有变化的才操作,没变化的就跳过,可进一步优化效率
  }

具体用法,可去 github 搜索对应库。

## Gulp 已知的小坑

  • watch 的时候路径不要用 './xxx',直接使用 'xxx' 即可,不然某个被 watch 的路径中新建文件是不能激活 watch 的。
  • gulp 对于 one after one 的任务链,需要加 return,比如 gulp clean

先这样吧,结合对应文档,搭建自己的脚手架大概也许没太大的问题了。

编辑于 2014-03-10

文章被以下专栏收录