Gulp,一个基于流的构建工具。

这是自己写的一个构建的demo,只是一个纯演示的示例,并没有完成什么项目工作。下面根据这个demo介绍一下Gulp。

上代码:

gulpfile.js

'use strict';

/**
* 环境
*/ const env = process.argv.slice(6)[0] || 'development';
process.env.NODE_ENV = env;
const isDev = env == 'development'; /**
* 依赖
*/
const browserSync = require('browser-sync');
const reload = browserSync.reload; const watchify = require('watchify');
const browserify = require('browserify');
const babelify = require('babelify');
const envify = require('envify/custom'); const del = require('del');
const source = require('vinyl-source-stream');
const buffer = require('vinyl-buffer');
const gulp = require('gulp');
const $ = require('gulp-load-plugins')(); /**
* path
*/
const pathConf = require('./path.js');
const path = pathConf.path;
const filePath = pathConf.filePath; /**
* browserify & watchfy
*/
// browserify conf
const customOpts = {
entries: ['./src/main.js'],
debug: true
}; // watchfy conf
const opts = Object.assign({}, watchify.args, customOpts);
// init watchfy
const browserify_watch = watchify(browserify(opts));
browserify_watch.transform(babelify, {
presets: ["es2015", "stage-2"],
plugins: ["transform-runtime"]
});
browserify_watch.transform(envify({
NODE_ENV: env
})); function jsBundler (ids){
return browserify_watch
.bundle()
.on('error', $.util.log)
.pipe(source("build.js"))
.pipe(buffer())
.pipe($.if(isDev, $.sourcemaps.init({loadMaps: true})))
.pipe($.uglify())
.pipe($.if(isDev, $.sourcemaps.write(".")))
.pipe(gulp.dest(path.dist))
} browserify_watch.on('update', jsBundler);
browserify_watch.on('log', $.util.log); /**
* gulp task
*/
gulp.task('cleanAll', () => {
del([
path.temp + '**/.{html.js.css}',
path.dist + '**/.{html.js.css}',
'!' + path.dist + 'video/**'
]);
}); gulp.task('serve', () => {
browserSync({
server: {
// proxy: 'xx.xx.xx.xx',
baseDir: __dirname
}
});
}); gulp.task('jsBundle', jsBundler); gulp.task('less', () => {
return gulp.src(filePath.src_less)
.pipe($.less())
.on("error", function (error){
$.util.log(error);
this.emit("end");
})
.pipe(gulp.dest(path.temp + 'css/'));
}); gulp.task('cssBundle', ['less'], () => {
return gulp.src([filePath.temp_css])
.pipe($.if(isDev, $.sourcemaps.init({loadMaps: true})))
.pipe($.concat('build.css'))
.pipe($.minifyCss())
.pipe($.if(isDev, $.sourcemaps.write('.')))
.pipe(gulp.dest(path.dist));
}); gulp.task('watchLess', () => {
gulp.watch([filePath.src_less], ['cssBundle']);
}); gulp.task('watchDist', () => {
gulp.watch(['index.html', path.dist + '*.*'], reload);
}); /**
* cli
* '$ gulp build' for development
* '$ npm run dev' for development
* '$ npm run pro' for production
*/
gulp.task('build', $.sequence(
'cleanAll'
, ['jsBundle', 'cssBundle']
, ['watchLess', 'watchDist']
, 'serve'
));

package.json

{
"name": "gulp",
"description": "gulp",
"author": "james",
"scripts": {
"dev": "gulp build --gulpfile gulpfile.js --env development",
"pro": "gulp build --gulpfile gulpfile.js --env production"
},
"dependencies": {
"jquery": "^3.1.1"
},
"devDependencies": {
"babel-core": "^6.17.0",
"babel-plugin-transform-runtime": "^6.15.0",
"babel-preset-es2015": "^6.16.0",
"babel-preset-stage-2": "^6.17.0",
"babel-preset-stage-3": "^6.17.0",
"babel-runtime": "^6.11.6",
"babelify": "^7.3.0",
"browser-sync": "^2.17.3",
"browserify": "^13.1.0",
"del": "^2.2.2",
"envify": "^3.4.1",
"gulp": "^3.9.1",
"gulp-changed": "^1.3.2",
"gulp-concat": "^2.6.0",
"gulp-debug": "^2.1.2",
"gulp-filter": "^4.0.0",
"gulp-if": "^2.0.1",
"gulp-inject": "^4.1.0",
"gulp-jshint": "^2.0.1",
"gulp-less": "^3.1.0",
"gulp-load-plugins": "^1.3.0",
"gulp-minify-css": "^1.2.4",
"gulp-minify-html": "^1.0.6",
"gulp-notify": "^2.2.0",
"gulp-rename": "^1.2.2",
"gulp-rev-all": "^0.9.7",
"gulp-sequence": "^0.4.6",
"gulp-sourcemaps": "^2.0.1",
"gulp-uglify": "^2.0.0",
"gulp-util": "^3.0.7",
"vinyl-buffer": "^1.0.0",
"vinyl-source-stream": "^1.1.0",
"watchify": "^3.7.0"
}
}

文件结构如下:

结构说明:

main.js 为入口文件

path.js 定义了一些路径变量

src 文件夹存放原始代码

dis 文件夹存放打包后的代码

temp 文件夹存放零食文件

通过browserify转译commjs和es6语法,通过gulp-less转译less语法,把转译后的文件输出到dist文件夹。

下面通过一些主要代码介绍一下Gulp的工作方式:

先看这段代码:

function jsBundler (ids){
return browserify_watch
.bundle()
.on('error', $.util.log)
.pipe(source("build.js"))
.pipe(buffer())
.pipe($.if(isDev, $.sourcemaps.init({loadMaps: true})))
.pipe($.uglify())
.pipe($.if(isDev, $.sourcemaps.write(".")))
.pipe(gulp.dest(path.dist))
}

它主要是在watchify监听到依赖的js文件有变动之后,会自动调用的一个回调函数。函数内部实现步骤为:

1.通过browserify以main.js为入口文件,打包依赖的所有js,并通过browserify的es2015预设插件来转译es6语法。最后将生成的流通过管道传输给vinyl-source-stream这个插件

2.普通流通过vinyl-source-stream处理以后再传输给vinyl-buffer

3.vinyl-buffer处理以后的流再传输给gulp-sourcemaps插件(如果环境条件判断通过的话),生成原始文件和打包后的文件映射关系(便于调试)

4.再传输给gulp-uglify插件进行压缩和混淆

5.输出soucemap

6.输出打包后的js文件到dist文件夹

看到这个流程不免有些疑问:为啥browserify传输出流不能直接给gulp插件使用呢,要先通过vinyl插件的系列处理才可以?

原因是:browserify处理后输出的流只是普通的node.js流,gulp的插件需要的是经过处理的vinyl流。vinyl可以把普通的node.js的流转换为Vinyl File Object Stream。这样,相当于把普通的流接入到了Gulp的处理体系内。如果需要完整流,可以通过vinyl-buffer插件接收完整个流转成二进制数据以后再进行处理,通常gulp-soucemaps、gulp-uglify这些需要完整文件进行映射或者替换的插件都需要buffer一次。否则在任务运行的时候会报流不支持的错误:

vinyl流的特点是它是Object风格的,除了流的内容content,还有path等属性。记录了文件的内容和文件名、文件路径的信息。

一个很好的例子可以解释为什么需要这样:

gulp.task("css", () => {
gulp.src("./src/**/*.css")
.pipe(gulp.dest("./dist/css/"));
});

这个Gulp任务很简单,就是把src文件夹下面所有的css(无论子文件夹层级)都'搬运"到./dist/css/这个路径下。任务执行结果当然表明这是一次成功的搬运。

单仔细看看不难发现一个蹊跷的事情:./src/下的文件夹是不定的,可以是很多层,也可以只有一层,每个css文件可以是任何名字,那么在dest到./dist/css/还能对应上文件夹层级和文件名,这是怎么做到的呢?

其实就是通过vinyl流(对象)的path那些属性来知道文件夹层级和文件名的。普通的node.js流只传输String或Buffer类型,也就是只关注content。

Gulp不只用到了文件的内容,而且还用到了这个文件的相关信息。因此,vinyl流就会有contents、path这样的多个属性了。

但是Gulp本身并不能直接生成vinyl流对象,而是依赖了一个叫做vinyl-fs的node模块,这是一个类似于文件适配器的模块。它提供三个方法:.src()、.dest()和.watch(),其中.src()将生成vinyl流对象,而.dest()将使用vinyl流,进行写入操作。

在Gulp的包中可以看到源码:

var vfs = require('vinyl-fs');

// ...

Gulp.prototype.src = vfs.src;
Gulp.prototype.dest = vfs.dest; // ...

可以看到gulp的src和dest方法其实就是vinyl-fs的src和dest方法。

深入探究之后可以发现这么一条依赖关系:gulp -> vinyl-fs ->vinyl -> glob-stream -> node-glob

从gulp.src开始到gulp.dest结束这一系列的工作流程大概是这样的:

  首先通过node-glob来匹配路径和文件,然后glob-stream把匹配到的文件流式读取, 再通过vinyl输出Gulp体系需要用到的vinyl流对象,src完成工作后 流被pipe下去,直到gulp.dest时候,通过先前已知的文件路径和文件名进行写入操作。

如果是从webpack/browserify的普通流开始,会有它们自己的一套文件流失读取方式和输出,只要通过vinyl改造后输出vinyl流对象就能接入进Gulp体系。

值得注意的是:写入是一个异步的操作。任务结束以后,不一定表示文件已经全部写入!

Gulp比起Grunt更具的node.js风格, 而且其依赖的orchestrator的特性就是最大并发的执行任务。在提供了大并发和node.js异步操作特性的同时也带来了不好的一点:链式任务不好控制。

这也是为什么build这个task是通过gulp-sequence来严格按照既定顺序执行的(通过返回流和其他异步处理的方式也可以达到效果):

gulp.task('build', $.sequence(
'cleanAll'
, ['jsBundle', 'cssBundle']
, ['watchLess', 'watchDist']
, 'serve'
));

我们一定不会希望这类事情发生:一边写入文件一边又在删除;下一个任务需要上个任务提供必要文件,但在执行的时候文件还没有写入完;等等....

Gulp常被用于和老前辈Grunt相比较,一般都说Gulp的速度快,这其实只是Gulp基于管道流的处理速度相对于Grunt不停地生成临时文件要快。读取和写入(I/O)操作还要看机器自身情况。

Gulp意为"狼吐虎咽",即拥有基于流的快速与支持高并发的特性。

Gulp介绍与入门实践的更多相关文章

  1. 一、ELKStack介绍与入门实践

    第1章 ELKStack 对于日志来说,最常见的需求就是收集.存储.查询.展示,开源社区正好有相对应的开源项目:logstash(收集).elasticsearch(存储+搜索).kibana(展示) ...

  2. webpack的入门实践,看这篇就够了

    webpack的入门实践 我会将所有的读者概括为初学者,即使你可能有基础,学习本节之前我希望你具有一定的JavaScript和node基础 文中的 ... ...代表省略掉部分代码,和上面的代码相同 ...

  3. react native 入门实践

    上周末开始接触react native,版本为0.37,边学边看写了个demo,语法使用es6/7和jsx.准备分享一下这个过程.之前没有native开发和react的使用经验,不对之处烦请指出.希望 ...

  4. sass、less和stylus的安装使用和入门实践

    刚 开始的时候,说实话,我很反感使用css预处理器这种新玩意的,因为其中涉及到了编程的东西,私以为很复杂,而且考虑到项目不是一天能够完成的,也很少是 一个人完成的,对于这种团队的项目开发,前端实践用c ...

  5. React Native (一) 入门实践

    上周末开始接触react native,版本为0.37,边学边看写了个demo,语法使用es6/7和jsx.准备分享一下这个过程.之前没有native开发和react的使用经验,不对之处烦请指出.笔者 ...

  6. 这是一次 docker 入门实践

    前言 其实接触 docker 也有一段时间了,但是一直没有做下总结,现在网上关于 docker 的介绍也有很多了,本着好记性不如烂笔头的原则,还是自己再记录一波吧. 实现目标 安装 docker ce ...

  7. MongoDB入门实践

    MongoDB入门实践 简单介绍MongoDB,包括MongoDB的使用场景.和MySQL的对比.安装部署.Java客户端访问及总结 MongoDB? 我们遵循需求驱动技术的原则,通过一个场景来引入M ...

  8. Vuex入门实践(中)-多module中的state、mutations、actions和getters

    一.前言 上一篇文章<Vuex入门实践(上)>,我们一共实践了vuex的这些内容: 1.在state中定义共享属性,在组件中可使用[$store.state.属性名]访问共享属性 2.在m ...

  9. Rxjs入门实践-各种排序算法排序过程的可视化展示

    Rxjs入门实践-各种排序算法排序过程的可视化展示 这几天学习下<算法>的排序章节,具体见对排序的总结,想着做点东西,能将各种排序算法的排序过程使用Rxjs通过可视化的方式展示出来,正好练 ...

随机推荐

  1. 十分钟了解HTTPS

    一.为什么要用HTTPS——HTTP协议的缺陷 通信使用明文(不加密),内容可能会被窃听 不能验证通信方的身份,所以请求和响应都有可能是攻击者发送的 数据包在由A到B的过程中,可能经历很多次路由转发, ...

  2. 1.3 正则表达式和python语言-1.3.7 匹配任何单个字符

    1.3.7 匹配任何单个字符 (2018-05-08) 点号(.)不能匹配一个换行符\n 或者非字符,也就是说,一个空字符串 搜索一个真正的句点(小数点), 而我们通过使用一个反斜线对句点的功能进行转 ...

  3. OI暑假集训游记

    莞中OI集训游记 Written BY Jum Leon. I        又是一载夏,本蒟蒻以特长生考入莞中,怀着忐忑的心情到了8月,是集训之际.怀着对算法学习的向往心情被大佬暴虐的一丝恐惧来到了 ...

  4. Rabin-Karp ACM训练

    求解问题 寻找S中T出现的位置或次数.假设S的长度为n, T的长度为m, 通过枚举S长度为m的字串的hash值与T的hash值比较.此时使用滚动hash的优化使复杂度不为O(mn). 算法说明 滚动h ...

  5. IntelliJ IDEA 2016.2 配置Tomcat 运行Web项目

    1.可能会出现的问题 Run-->Edit Configurations 中点击"+"号没有tomcat server... 解决办法:File--->Setting- ...

  6. LeetCode 80 Remove Duplicates from Sorted Array II [Array/auto] <c++>

    LeetCode 80 Remove Duplicates from Sorted Array II [Array/auto] <c++> 给出排序好的一维数组,如果一个元素重复出现的次数 ...

  7. 20175324 2018-2019-2 《Java程序设计》第5周学习总结

    20175324 2018-2019-2 <Java程序设计>第5周学习总结 教材学习内容总结 抽象类和具体类的区别在于抽象类中有抽象方法而具体类中没有.且抽象类不能实例化. 接口:如果一 ...

  8. 干掉safedog命令

    sc delete safedogguardcenter    shutdown -r -t 00 两条命令搞定

  9. python 可迭代对象 迭代器 生成器总结

    可迭代对象 只要有魔法方法__iter__的就是可迭代对象  list和tuple和dict都是可迭代对象 迭代器 只要有魔法方法__iter__和__next__的就是可迭代对象 生成器 只要含有y ...

  10. Spark 异步Action

    异步不保序,但大作业执行时间后移. .set("spark.scheduler.mode", "FAIR") 公平调度,充分使用集群资源. Spark Doc ...