和我一起学Gulp

小鱼二

准备工作

​​

// 安装 node 4
$ nvm install 4
$ nvm use 4   // 使用  node v4
$ nvm alias default 4 // 默认是 v4, 下次打开terminal还是4

// 安装nrm
$ npm install -g nrm
$ nrm ls 
$ nrm use cnpm // 选择淘宝镜像 cnpm

// 删除之前安装过的 gulp命令行工具
$ npm uninstall -g gulp
// 安装最新的gulp命令行工具
$ npm install -g gulpjs/gulp-cli#4.0

// 下载这个练手项目
$ git clone git@github.com:xiaoyu2er/automating-your-workflow-with-gulp.git
$ cd automating-your-workflow-with-gulp
$ npm install

资料汇总

什么是Gulp?

Automate and enhance your workflow

 

不仅仅是自动化的构建工具

为什么使用Gulp

自动化构建 

提升代码质量

好钢用在刀刃上 (专注于代码)

早下班

升职加薪

有哪些东西可以自动化?

  • 代码分析 (jsHint, jsCs, eslinet, cssLint)
  • 编译

    • sass, less -> css 

    • vendor-prefix

    • es6, typeScript, coffee -> js

  • 拼接文件(css, js)
  • 压缩 (css, 图片, 雪碧图, 图标SVG)

  • 混淆 (uglify)

  • sourcemaps

  • 立即执行函数

  • Angular(templateCache, 依赖注入)

  • HTML生成 引用的注入(css,js)

  • 文件重命名 

  • 全局文本的替换

  • 版本号 文件hash;

  • 测试

  • 删除文件

  • 文档自动生成

  • 自动上传, 部署

  • git commit hook (commit记录的格式化)

  • 其他:

    • ​源文件有哪些?
    • 生成的文件多大?

    • 生成的文件版本号汇总?

    • 开发阶段出错系统提示?

上述的一切 都可以使用Gulp自动化完成

 

而且可以快的让你飞起来!

 

Automate and enhance your workflow

 

work smarter, not harder

Gulp vs Grunt

简单总结

  • Gulp是基于代码的, Grunt是基于配置的

  • Gulp是基于流的, Grunt是基于文件的

  • 基于代码可以借助node模块, 更加灵活

  • 更加简洁

  • 更好的debug

  • 快 [微笑]

如何使用

在Gulp4.0之前, Gulp一共只有4个API:

 

Gulp4.0不向下兼容, 共8个API

经笔者实践, Gulp4.0的API简化了任务间的依赖关系

思维复杂度降低

因此

我会按照Gulp4.0的API进行讲解

注意 Gulp 4.0 尚未正式发布

所以在项目中安装依赖的时候, 写法稍微有点不同:

npm install --save-dev gulpjs/gulp#4.0

gulp.task([name,] fn)

定义Gulp任务

// name: gulp任务的名字, 如果不提供, 将用 fn.name 或者 fn.displayName 来作为任务的名字
// fn: 异步函数; fn 可以接收一个callback, 在异步操作结束后调用;
// 或者 fn可以返回一个 promise, stream, child process, RxJS observable 以示任务结束
// fn.name, fn.displayName. 由于fn.name不可修改, 
// 针对匿名函数可以提供一个 displayName属性来充当任务的名称
// fn.description 使用 gulp --tasks 时, 此字段会出现在task的名字后面
//
gulp.task('task', function someTask(done) {
    
    // 1. done(); 后续举例
    // 2. return a promise; 后续举例
    // 3. return stream; 后续举例
    // 4. return child process; 未研究
    // 5. return RxJs observable; 未研究

});

function taskName() {}

gulp.task(taskName);

小试牛刀

$ gulp hello
[19:37:48] Using gulpfile ~/Tech/workspace/github/automating-your-workflow-with-gulp/demo/gulpfile.js
[19:37:48] Starting 'hello'...
hello
[19:37:48] Finished 'hello' after 1.89 ms
var gulp = require('gulp');
gulp.task('hello', function (done) {
    console.log('hello');
    done(); // 显示的调用回调函数表示任务完成; 尝试将这里去掉
});
var del = require('del');

/**
 * 删除build目录下的所有文件
 */
gulp.task('clean', function () {
    // del操作是一个promise
    return del(config.buildDir);
});

gulp.src(globs[, options])

获得后续操作的文件流

// globs: String or Array 
// 表示输入文件的通配符, 可以是字符串, 也可以是字符串数组
// 如 gulp.src(['*.js', '!b*.js', 'bad.js'])
gulp.src('globs', {
    // cwd, 表示 blobs 的当前目录, 很有用哦!
    // Default: process.cwd()
    cwd: '',
    // base, 表示输入文件的基准地址;

    // 默认是第一个通配符前的地址 如: gulp.src('client/js/**/*.js') base 就是 client/js/
    // 在保证输出目录结构的时候有用, 后续会讲解
    base: '', 

    // buffer表示文件在内存中的存储形式, 默认是true,
    // 如果是false, 那么返回的文件形式将是 file.contents, 大型文件有用, 原理未研究.
    buffer: true, 
    // 文件最后修改时间小于这个值得将会忽略掉
    // 这里可以使用 gulp.lastRun, 后续会讲解.
    since: 'Date or Number',  
    // 允许空文件, 否则src不存在的时候会报错
    allowEmpty: false, 
    // 是否真正读取文件, 如果为false, 只有文件的meta信息会被读取, 后续会举例
    read: true, 

    passthrough: false // 没研究
});

注意事项

gulp.src(glob, options) 中的option 还有其他选项, 详细请看: 

 

https://github.com/gulpjs/glob-stream#options

https://github.com/isaacs/node-glob#options

gulp.dest(path[, options])

输出文件 

    gulp.src('')
    
    // path: String or a function(file) (可以是个函数欧!)
    // 输出文件的目录
    .pipe(gulp.dest('path', {
        // cwd, 当前目录, 只有path是相对路径的时候才会用到
        cwd: 'process.cwd()', 
        
        // mode, 输出文件的mode,
        // 默认和源文件一致 或者 和process的mode一致; 如: "0744", 0744 or 484
        mode: 'String or Number', 
        
        // dirMode, 输出文件夹的mode
        // 默认是process的mode
        dirMode: 'String or Number', 

        // overwrite, 是否覆盖已经存在的文件
        overwrite: true 
    }))

小试牛刀

/**
 * 拷贝.css文件到build目录下
 */
gulp.task('css', function () {

    // 这里的cwd参数表示css/目录的父级目录;
    // 这里的base参数, 是为了保证源目录和生成目录的目录结构一致; 
    // 可以尝试删除看看有何异同 // 你会发现目录结构被破坏掉了!

    return gulp.src('css/**/*.css', {cwd: 'src', base: 'src'})
        .pipe(gulp.dest(config.buildDir));
});

gulp.parallel(...tasks) 构造并行任务
gulp.series(...tasks) 构造串行任务

gulp.task('one', function (done) {
    done();
});

function two(done) {
    done();
}

gulp.task('three', function (done) {
    done();
});

function four(done) {
    done();
}

gulp.task('five', function (done) {
    done();
});


gulp.task('series¶llel', gulp.parallel(
    'one', 
    gulp.series(two, 'three'), 
    gulp.parallel(four, 'five')
));

  gulp.symlink(folder[, options])

与 gulp.dest 功能一致, 只不过是生成软连接

// folder: String or Function(file)
.pipe(gulp.symlink('folder', {
    cwd: 'process.cwd()', // 只有path是相对路径的时候才会用到
    dirMode: 'String or Number' // 默认是process的mode
}))

gulp.watch(glob[, opts][, fn])

监听文件

// glob: String or Array;
var watcher = gulp.watch('glob', {
    ignored: 'glob', // 忽略的文件
    usePolling: false, // 一般用于网络文件或者虚拟机里的文件
    cwd: '', // glob的base路径
    alwaysStat: false // 如果需要 fs.Stats 的所有事件可以设置为true, 稍微影响性能
}, function () {

    // do something here
});


// gulp.watch 返回一个 FSWatcher; 可以接收 add, change, unlink 事件
// 函数参数:
// path: 文件的路径, 如果上述opts.cwd指定, path为相对路径
// stats: 一个 fileStat 对象, 可能会被提供; 如果opts.alwaysStat == true, 那么stats一定会被提供;

watcher.on('add|change|unlink|', function (path, stats) {
    console.log('File ' + path + ' was added|changed|removed');
});

// watcher的一些方法
watcher.close(); // 关闭文件监听
watcher.add('glob'); // 继续添加监听文件
watcher.unwatch('glob'); // 不再监听glob所代表的文件, 其余继续

举个栗子

var del = require('del');

gulp.task('watch', function () {

    var watcher = gulp.watch('css/**/*.css', {cwd: 'src'}, 
                        gulp.series('build:css', 'build:inject'));
 
    watcher.on('add', (filePath) => console.log('add', filePath));
    watcher.on('change', (filePath) => console.log('change', filePath));
    // 当文件删除的时候, 同时删除目标目录下的文件, 这样保证 build:inject 的时候不会引入已经删除的文件
    watcher.on('unlink', (filePath) => {
        console.log('delete', filePath);
        var src = path.relative(path.resolve('src'), filePath);
        var dest = path.resolve(config.buildDir, src);
        return del(dest);
    });
});

跟我一起重写Demo中的gulpfile

├── gulpfile.js
├── build/ 生产目录
└── src/ 源文件目录
    ├── css
    │   ├── add.scss
    │   ├── delete.scss
    │   ├── main.scss
    │   └── normalize.css
    ├── img
    │   └── gulp.png
    ├── index.html
    ├── js
    │   ├── app.js
    │   ├── controllers
    │   │   ├── add.controller.js
    │   │   └── delete.controller.js
    │   ├── directives
    │   │   ├── add.directive.js
    │   │   └── delete.directive.js
    │   ├── filters
    │   │   ├── lowerCase.js
    │   │   └── upperCase.js
    │   └── services
    │       └── counter.js
    ├── lib
    │   └── angular.js
    └── views
        ├── add.tpl.html
        └── delete.tpl.html

任务分析

开发阶段

  • 删除build目录下的所有文件

  • 拷贝.css文件到build目录下

  • 编译.scss文件到build目录下

  • 为.js文件添加angular的注解, 且包裹一个立即执行函数,输出到build目录

  • 拷贝图片和lib下的文件到build目录

  • 将angular的模板文件经ngTemplate处理合并为一个js文件, 输出到build/js目录下

  • 将css,js文件插入到index.html中, 注意js的顺序, 输出到build目录下

  • 监听以上文件, 并作出增量修改

任务分析

部署阶段

 

  • CSS

    • 将css与编译好的sass文件合并压缩;

    • 添加sourcemaps

    • 还可以添加诸如autoprefixer等插件

  • JS

    • 将js文件添加angular注解,和立即执行函数, 和template文件合并压缩;

    • 并且生成sourcemaps

  • 其他静态资源

    • 拷贝资源到build目录

    • 这里可以根据实际情况对不同的资源进行不同的处理

    • 比如对image进行压缩处理等

  • Index

    • 将css,js文件插入到index.html中, 注意js的顺序, 输出到build目录下

 

用到的插件

var gulp = require('gulp');
var del = require('del');
var inject = require('gulp-inject');
var order = require("gulp-order");
var cleanCSS = require('gulp-clean-css');
var sourcemaps = require('gulp-sourcemaps');
var concat = require('gulp-concat');
var uglify = require('gulp-uglify');
var size = require('gulp-size');
var rev = require('gulp-rev');
var htmlmin = require('gulp-htmlmin');
var cached = require('gulp-cached');
var sass = require('gulp-sass');
var iife = require('gulp-iife');
var print = require('gulp-print');
var eventStream = require('event-stream');
var ngAnnotate = require('gulp-ng-annotate');
var ngTemplates = require('gulp-ng-templates');

逐个讲解

还可以做哪些优化?

1. 增量编译

2. 一些钩子

一些好用的插件和recipies 

谢谢

小鱼二

Made with Slides.com