gulp相关

Posted by Qz on April 22, 2019

“Yeah It’s on. ”

最近在做一个基于原生小程序的框架,一开始选择的打包工具是webpack,但在使用的过程中发现webpack必须指定入口文件,且无法做到复制一个文件夹,并对它里面的文件进行处理,查看官方组件cli模板,其采用打包工具glup,所以现在也换到gulp。

由于,基本上对gulp只有简单的认知,写起来非常地苦逼。

gulp中文文档太旧,如果使用gulp4一定要看英文文档

https://gulpjs.com/

正文

https://www.gulpjs.com.cn/docs/api/

网页链接

gulp是前端开发过程中对代码进行构建的工具,是自动化项目的构建利器;她不仅能对网站资源进行优化,而且在开发过程中很多重复的任务能够使用正确的工具自动完成;使用她,我们不仅可以很愉快的编写代码,而且大大提高我们的工作效率。

gulp是基于Nodejs的自动任务运行器, 她能自动化地完成 javascript/coffee/sass/less/html/image/css 等文件的的测试、检查、合并、压缩、格式化、浏览器自动刷新、部署文件生成,并监听文件在改动后重复指定的这些步骤。在实现上,她借鉴了Unix操作系统的管道(pipe)思想,前一级的输出,直接变成后一级的输入,使得在操作上非常简单。通过本文,我们将学习如何使用Gulp来改变开发流程,从而使开发更加快速高效。

gulp 和 grunt 非常类似,但相比于 grunt 的频繁 IO 操作,gulp 的流操作,能更快地更便捷地完成构建工作。

起步

作为项目的开发依赖(devDependencies)安装

$ npm install --save-dev gulp

在项目根目录下创建一个名为 gulpfile.js 的文件:

var gulp = require('gulp');
gulp.task('default', function() {
  // 将你的默认的任务代码放在这
});

运行 gulp:

$ gulp

gulp.src

gulp.src(globs[, options])

输出(Emits)符合所提供的匹配模式(glob)或者匹配模式的数组(array of globs)的文件。 将返回一个 Vinyl files 的 stream 它可以被 piped 到别的插件中。

options.base

类型: String 默认值: 将会加在 glob 之前 (请看 glob2base)

如, 请想像一下在一个路径为 client/js/somedir 的目录中,有一个文件叫 somefile.js :

gulp.src('client/js/**/*.js') // 匹配 'client/js/somedir/somefile.js' 并且将 `base` 解析为 `client/js/`
  .pipe(minify())
  .pipe(gulp.dest('build'));  // 写入 'build/somedir/somefile.js'

gulp.src('client/js/**/*.js', { base: 'client' })
  .pipe(minify())
  .pipe(gulp.dest('build'));  //  // 写入 'build/js/somedir/somefile.js'

options.cwd 类型: String 默认值: process.cwd()

输出目录的 cwd 参数,只在所给的输出目录是相对路径时候有效。

task return 的作用

https://segmentfault.com/a/1190000016971285?utm_source=tag-newest

Without return the task system wouldn’t know when it finished

task和里面的gulp流是异步执行的

结论: gulp的task都要保证有return或者callback,去通知系统任务结束。(make sure they either return a stream or promise, or handle the callback

gulp.task('build-clean', function() {
    // Return the Promise from del()
    return del([BUILD_DIRECTORY]);
//  ^^^^^^
//   This is the key here, to make sure asynchronous tasks are done!
});
gulp.task('build-scripts', function() {
    // Return the stream from gulp
    return gulp.src(SCRIPTS_SRC).pipe(...)...
//  ^^^^^^
//   This is the key here, to make sure tasks run to completion!
});

gulp.task('callback-example', function(callback) {
    // Use the callback in the async function
    fs.readFile('...', function(err, file) {
        console.log(file);
        callback();
//      ^^^^^^^^^^
//       This is what lets gulp know this task is complete!
    });
});

gulp.watch

gulp.watch(glob [, opts], tasks) 或 gulp.watch(glob [, opts, cb]) 监视文件,并且可以在文件发生改动时候做一些事情。它总会返回一个 EventEmitter 来发射(emit) change 事件。

glob 类型: String or Array 一个 glob 字符串,或者一个包含多个 glob 字符串的数组,用来指定具体监控哪些文件的变动。

opts 类型: Object 传给 gaze 的参数。

tasks 类型: Array 需要在文件变动后执行的一个或者多个通过 gulp.task() 创建的 task 的名字,

var watcher = gulp.watch('js/**/*.js', ['uglify','reload']);
watcher.on('change', function(event) {
  console.log('File ' + event.path + ' was ' + event.type + ', running tasks...');
});

提示:Error: watching index.html: watch task has to be a function (optionally generated by using gulp.parallel or gulp.series)

gulp 4.0之后不能按照之前的写法了 大有改变

4.0之前的写法

Gulp.watch(“监听的文件”,[任务名字符串])

    // gulp.task("watch",function(){
    //     gulp.watch("index.html",["copyindex"]);
    //     gulp.watch("js/**/*",["copyjs"]);
    //     gulp.watch("css/**/*.scss",["copycss"]);
    //     gulp.watch("html/**/*",["copyhtml"]);
    //     gulp.watch("php/**/*",["copyphp"]);
    //     gulp.watch("img/**/*",["copyimg"]);
    // })

4.0之后的写法

Gulp.watch(“监听的文件”,回调函数)。

 gulp.task("watch-all",async ()=>{
 
            gulp.watch("index.html",async ()=>{
                gulp.src("index.html")
                .pipe(gulp.dest("D:\\myphp_www\\PHPTutorial\\WWW\\baidu"));
            });
            gulp.watch("js/**/*",async ()=>{
                gulp.src("js/**/*")
                .pipe(gulp.dest("D:\\myphp_www\\PHPTutorial\\WWW\\baidu\\js"));
            });
            gulp.watch("css/**/*.scss",async ()=>{
                gulp.src("css/**/*.scss")
                .pipe(sass())
                .pipe(gulp.dest("D:\\myphp_www\\PHPTutorial\\WWW\\baidu\\css"));
            });
            gulp.watch("html/**/*",async ()=>{
                gulp.src("html/**/*")
                .pipe(gulp.dest("D:\\myphp_www\\PHPTutorial\\WWW\\baidu\\html"));
            });
            gulp.watch("php/**/*",async ()=>{
                gulp.src("php/**/*")
                .pipe(gulp.dest("D:\\myphp_www\\PHPTutorial\\WWW\\baidu\\php"));
            });
            gulp.watch("img/**/*",async ()=>{
                gulp.src("img/**/*")
                .pipe(gulp.dest("D:\\myphp_www\\PHPTutorial\\WWW\\baidu\\img"));
 
            });
        
        }); 

或者

gulp.task('watch', function() {
  gulp.watch('app/css/*.css', gulp.series('styles'));
  gulp.watch('app/js/*.js', gulp.series('scripts'));
  gulp.watch('app/img/*', gulp.series('images'));
});

Options

cwd

The directory that will be combined with any relative path to form an absolute path. Is ignored for absolute paths. Use to avoid combining globs with path.join().

将与任何相对路径组合以形成绝对路径的目录。对于绝对路径忽略。用于避免将globs与path.join()相结合。

也就是如果存在了cwd,我们就不使用glob语法,而是用绝对路径

  jsFiles: [`${srcPath}/**/*.js`, `!${srcPath}/node_modules/**/*.js`],

      gulp.watch(jsFiles, {cwd: srcPath}, gulp.series('js'))
        .on('unlink', (curPath) => {
          console.log("curPath", curPath)
          let targetPath = path.resolve(distPath, curPath);
          console.log("targetPath", targetPath)
          _.delPath(targetPath);
        });

plugin 插件

  • gulp-autoprefixer (css自动补全前缀)
  • gulp-babel (babel编译器)
  • gulp-changed (Only pass through changed files,减少监听的文件数量,节约资源)
  • gulp-clean (删除文件)
  • gulp-clean-css (css压缩)
  • gulp-eslint (eslint检查)
  • gulp-eslint-auto-fix (fix文件,使之满足eslint规范)
  • gulp-if (判断,通常用于区分开发环境和生产环境)
  • gulp-imagemin (图片压缩)
  • gulp-install (安装依赖)
  • gulp-less (编译less文件)
  • gulp-rename (重命名文件)
  • gulp-sourcemaps (生成sourcemaps)
  • gulp-uglify (压缩js)

gulp-sourcemaps

gulp.task('sourcemap',function() {
    gulp.src('./src/*.js')  
    .pipe( sourceMap.init() )
    .pipe( concat('all.js') )  
    .pipe( uglify() )  
    .pipe( sourceMap.write('../maps/',{addComment: false}) )
    .pipe( gulp.dest('./dist/') ) 
})

sourceMap.init( ) 启用sourcemaps功能

sourceMap.write( ) 生成记录位置信息的sourcemaps文件

经过 concat 和 uglify ,将生成的all.js 与 源文件( src 下的所有js文件 )之间的位置映射信息,生成sourcemaps文件。

sourceMap.write( ),不传参,将会直接在 all.js 尾部,生成sourcemaps信息。

sourceMap.write( path ),将会在指定的 path,生成独立的sourcemaps信息文件。如果指定的是相对路径,是相对于 all.js 的路径。

无法指定路径为 src 目录,否则,sourcemaps文件会生成在 dist 目录下。

addComment : true / false ; 是控制处理后的文件(本例是 all.js ),尾部是否显示关于sourcemaps信息的注释。

注意:sourceMap.init() 和 sourceMap.write() 之间使用的插件,必须支持 gulp-sourcemaps 插件

gulp-changed

Only pass through changed files

仅仅传递更改过的文件

默认情况下,每次运行时候所有的文件都会传递并通过整个管道。通过使用 gulp-changed 可以只让更改过的文件传递过管道。这可以大大加快连续多次的运行。

// npm install --save-dev gulp gulp-changed gulp-jscs gulp-uglify

var gulp = require('gulp');
var changed = require('gulp-changed');
var jscs = require('gulp-jscs');
var uglify = require('gulp-uglify');

// 我们在这里定义一些常量以供使用
var SRC = 'src/*.js';
var DEST = 'dist';

gulp.task('default', function() {
	return gulp.src(SRC)
		// `changed` 任务需要提前知道目标目录位置
		// 才能找出哪些文件是被修改过的
		.pipe(changed(DEST))
		// 只有被更改过的文件才会通过这里
		.pipe(jscs())
		.pipe(uglify())
		.pipe(gulp.dest(DEST));
});

gulp-uglify

https://www.npmjs.com/package/gulp-uglify

gulp-minify-css

这个已经被废弃了 请使用 gulp-clean-css

https://www.npmjs.com/package/gulp-clean-css

gulp-imagemin

https://www.npmjs.com/package/gulp-imagemin

经过测试,不加options压缩效果很不明显。

var gulp = require('gulp'),
    imagemin = require('gulp-imagemin');
 
gulp.task('testImagemin', function () {
    gulp.src('src/img/*.{png,jpg,gif,ico}')
        .pipe(imagemin({
            optimizationLevel: 5, //类型:Number  默认:3  取值范围:0-7(优化等级)
            progressive: true, //类型:Boolean 默认:false 无损压缩jpg图片
            interlaced: true, //类型:Boolean 默认:false 隔行扫描gif进行渲染
            multipass: true //类型:Boolean 默认:false 多次优化svg直到完全优化
        }))
        .pipe(gulp.dest('dist/img'));
});

https://github.com/sindresorhus/gulp-imagemin#user-content-options

gulp-autoprefixer

https://www.npmjs.com/package/gulp-autoprefixer

gulp-eslint

https://www.npmjs.com/package/gulp-eslint

gulp-babel

https://www.npmjs.com/package/gulp-babel

gulp-cache

A temp file based caching proxy task for gulp.

https://www.npmjs.com/package/gulp-cache

优化

gulp watch 分开

不要一次watch太多文件,分开成几个watch任务

    /**
     * 构建相关任务
     */
    gulp.task(`${id}-watch`, () => {
      gulp.watch(config.jsFiles, {cwd: srcPath}, gulp.series('js'))
        .on('unlink', (curPath) => {
          let targetPath = path.resolve(distPath, curPath)
          _.delPath(targetPath)
        })

      gulp.watch(config.jsonFiles, {cwd: srcPath}, gulp.series('json'))
        .on('change', (path) => {
          if (/package/.test(path)) {
            install()
          }
        })
        .on('unlink', (curPath) => {
          let targetPath = path.resolve(distPath, curPath)
          _.delPath(targetPath)
        })

      gulp.watch(config.wxmlFiles, {cwd: srcPath}, gulp.series('wxml'))
        .on('unlink', (curPath) => {
          let targetPath = path.resolve(distPath, curPath)
          _.delPath(targetPath)
        })

      gulp.watch(config.lessFiles, {cwd: srcPath}, gulp.series('wxss'))
        .on('unlink', (curPath) => {
          let targetPath = path.resolve(distPath, curPath)
          if (/\.less/.test(targetPath)) {
            targetPath = targetPath.replace('.less', '.wxss')
          }
          _.delPath(targetPath)
        })

      gulp.watch(config.imgFiles, {cwd: srcPath}, gulp.series('img'))
        .on('unlink', (curPath) => {
          let targetPath = path.resolve(distPath, curPath)
          _.delPath(targetPath)
        })
    })

task的异步执行加上return

https://segmentfault.com/a/1190000004936739

https://segmentfault.com/a/1190000016971285?utm_source=tag-newest

原理

through2原理解析

https://segmentfault.com/a/1190000011740894

Vinyl

https://github.com/gulpjs/vinyl-fs

Vinyl is a very simple metadata object that describes a file. When you think of a file, two attributes come to mind: path and contents. These are the main attributes on a Vinyl object. A file does not necessarily represent something on your computer’s file system. You have files on S3, FTP, Dropbox, Box, CloudThingly.io and other services. Vinyl can be used to describe files from all of these sources.

乙烯基是一个描述文件的非常简单的元数据对象。当您想到一个文件时,会想到两个属性:path和contents。这些是乙烯基物体的主要属性。文件不一定表示计算机文件系统中的内容。你在S3, FTP, Dropbox, Box上都有文件。io和其他服务。乙烯基可以用来描述所有这些来源的文件。

vinyl-paths

https://www.npmjs.com/package/vinyl-paths

Get the file paths in a vinyl stream

Useful when you need to use the file paths from a Gulp pipeline in an async Node.js package.

当您需要在async Node.js包中使用来自Gulp管道的文件路径时,它非常有用。

Simply pass an async function such as del and this package will provide each path in the stream as the first argument.

简单地传递一个异步函数,如del,这个包将提供流中的每个路径作为第一个参数。

// gulpfile.js
const gulp = require('gulp');
const stripDebug = require('gulp-strip-debug');
const del = require('del');
const vinylPaths = require('vinyl-paths');
 
// Log file paths in the stream
gulp.task('log', =>
    gulp.src('app/*')
        .pipe(stripDebug())
        .pipe(vinylPaths(async paths => {
            console.log('Paths:', paths);
        })
);
 
// Delete files in the stream
gulp.task('delete', =>
    gulp.src('app/*')
        .pipe(stripDebug())
        .pipe(vinylPaths(del))
);

补充

gulp4 和 gulp3 之间的区别

https://www.jianshu.com/p/40b99bed3127

AssertionError [ERR_ASSERTION]: Task function must be specified
    at Gulp.set [as _setTask] (F:\前端项目\xhw-native\node_modules\undertaker\lib\set-task.js:10:3)
    at Gulp.task (F:\前端项目\xhw-native\node_modules\undertaker\lib\task.js:13:8)
    at BuildTask.init (F:\前端项目\xhw-native\build\build.js:59:10)
    at new BuildTask (F:\前端项目\xhw-native\build\build.js:45:10)
    at Object.<anonymous> (F:\前端项目\xhw-native\gulpfile.js:10:1)
    at Module._compile (internal/modules/cjs/loader.js:701:30)
    at Object.Module._extensions..js (internal/modules/cjs/loader.js:712:10)
    at Module.load (internal/modules/cjs/loader.js:600:32)
    at tryModuleLoad (internal/modules/cjs/loader.js:539:12)
    at Function.Module._load (internal/modules/cjs/loader.js:531:3)

4.0错误的写法:

gulp.task('default', ['del'], function() {
    // default task code here
});

修改为最新的正确写法:

gulp.task('default', gulp.series('del', function() { 
    // default task code here
}));

Gulp 4最大的变化就是你不能像以前那样传递一个依赖任务列表。

Gulp3如果有一个任务AB和C的列表你想在一个序列中运行确保A在B开始之前完成而B在C开始之前完成),代码如下
gulp.task('a', function () {
  // Do something.
});
gulp.task('b', ['a'], function () {
  // Do some stuff.
});
gulp.task('c', ['b'], function () {
    // Do some more stuff.
});

在Gulp 4中,你不能再这样做了。

不要用Gulp3的方式指定依赖任务,你需要使用gulp.series和gulp.parallel,因为gulp任务现在只有两个参数。

  • gulp.series:按照顺序执行
  • gulp.paralle:可以并行计算
gulp.task('my-tasks', gulp.series('a', 'b', 'c', function() {
  // Do something after a, b, and c are finished.
}));
gulp.task('build', gulp.parallel('styles', 'scripts', 'images', function () {
  // Build the website.
}));

或者这样

gulp.task('my-tasks', gulp.series('a', gulp.parallel('styles','scripts', 'images'), 'b', 'c', function() {
  // Do something after a, b, and c are finished.
}));

相关任务必须在被调用之前发生。

报错did you forget to signal async completion

https://blog.csdn.net/weixin_40817115/article/details/81079507

解决方法,使用 async 和 await。

const gulp = require('gulp');
gulp.task('testGulp', async() => {
   await console.log('Hello World!');
});

在不使用文件流的情况下,向task的函数里传入一个名叫done的回调函数,以结束task,如下代码所示:

gulp.task('testGulp', done => {
  console.log('Hello World!');
  done();
});

done回调函数的作用是在task完成时通知Gulp(而不是返回一个流),而task里的所有其他功能都纯粹依赖Node来实现。

报错 GulpUglifyError: unable to minify JavaScript

gulp-uglify无法压缩带有es6语法的js,先要用babel把es6转为es5。

这就很坑了Orz

crash on error

最近写babel插件,发现在gulp-babel中很难定位错误。

所以这里引出一个非常重要的知识点,通常情况gulp报错的信息非常模糊,我们很难定位到具体的错误,所以我们要自己监听error事件

Yep, you need to add an error handler to your gulp task.

var gulp = require("gulp");
var babel = require('gulp-babel');
var sourcemaps = require('gulp-sourcemaps');

gulp.task("default", function(){
  gulp.watch("src/js/**/*.js", ["babel"]);
});

gulp.task("babel", function(){
  gulp.src("src/js/**/*.js")
    .pipe(sourcemaps.init())
    .pipe(babel())
    .on('error', console.error.bind(console));
    .pipe(gulp.dest("www/js/"));
});

非常重要

.on(‘error’, console.error.bind(console));

打印出很清晰的信息

{ SyntaxError: F:\前端项目\xhwBase\src\config\baseConfig.js: Unexpected token (15:18)
  13 | switch (baseConfig.env) {
  14 |   case "development":
> 15 |     baseConfig = {...baseConfig, ...devConfig};
     |                   ^
  16 |     break;
  17 |   case "production":
  18 |     baseConfig = {...baseConfig, ...proConfig};
    at Parser.pp$5.raise (F:\前端项目\xhwBase\node_modules\babylon\lib\index.js:4454:13)
    at Parser.pp.unexpected (F:\前端项目\xhwBase\node_modules\babylon\lib\index.js:1761:8)
    at Parser.pp$3.parseIdentifier (F:\前端项目\xhwBase\node_modules\babylon\lib\index.js:4332:10)
    at Parser.pp$3.parsePropertyName (F:\前端项目\xhwBase\node_modules\babylon\lib\index.js:4156:96)
    at Parser.pp$3.parseObj (F:\前端项目\xhwBase\node_modules\babylon\lib\index.js:4045:12)
    at Parser.pp$3.parseExprAtom (F:\前端项目\xhwBase\node_modules\babylon\lib\index.js:3719:19)
    at Parser.pp$3.parseExprSubscripts (F:\前端项目\xhwBase\node_modules\babylon\lib\index.js:3494:19)
    at Parser.pp$3.parseMaybeUnary (F:\前端项目\xhwBase\node_modules\babylon\lib\index.js:3474:19)
    at Parser.pp$3.parseExprOps (F:\前端项目\xhwBase\node_modules\babylon\lib\index.js:3404:19)
    at Parser.pp$3.parseMaybeConditional (F:\前端项目\xhwBase\node_modules\babylon\lib\index.js:3381:19)
  pos: 379,
  loc: Position { line: 15, column: 18 },
  _babel: true,
  codeFrame:

webpack-stream

https://www.npmjs.com/package/webpack-stream

Run webpack as a stream to conveniently integrate with gulp.

以流的形式运行webpack,方便地与gulp集成。

const gulp = require('gulp');
const webpack = require('webpack-stream');

gulp.task('default', function() {
  return gulp.src('src/entry.js')
    .pipe(webpack())
    .pipe(gulp.dest('dist/'));
});

gulp-clean效率太慢

https://www.npmjs.com/package/gulp-clean

Option read:false prevents gulp from reading the contents of the file and makes this task a lot faster. If you need the file and its contents after cleaning in the same stream, do not set the read option to false.

选项read:false阻止gulp读取文件的内容,并使这个任务快得多。如果在同一流中清理后需要文件及其内容,请不要将read选项设置为false。


For safety files and folders outside the current working directory can be removed only with option force set to true.

对于当前工作目录之外的安全文件和文件夹,只能在将选项force设置为true时删除。

    /**
     * 清空目标目录
     */
    gulp.task('clean', () => {
      return gulp.src(distPath, {read: false, allowEmpty: true})
        .pipe(clean({force: true}))
    })

 Error: File not found with singular glob: F:\前端项目\xhw-native\dist (if this was p
urposeful, use `allowEmpty` option)

allowEmpty为true,允许distPath不存在,不加这个的话,如果distPath不存在会报错

gulp-imagemin无法处理小图片

https://github.com/sindresorhus/gulp-imagemin

原本想build的时候,对图片进行压缩处理,但是如果gulp-imagemin处理的是几kb的图片(例如:小图标),这种图片就会消失不见。

经过各种尝试,和查看issue,最终还是没有倒找到解决办法,最终放弃压缩图片

基本上大图片都放在腾讯云桶或者cdn上,所以这里不优化也没有太大问题