gulp

作者: _旁观者_ | 来源:发表于2021-04-14 14:30 被阅读0次

    1 环境
    npm install --global gulp-cli // 安装 gulp 命令行工具
    npm init -y // 创建项目
    npm install --save-dev gulp
    
    • 根目录下 创建 gulpfile.js 或 gulpfile文件夹(文件夹时默认使用 index.js)
    function defaultTask(cb) {
      // place code for your default task here
      cb();
    }
    
    exports.default = defaultTask
    
    • 测试
    gulp
    

    2 创建任务(task)
    任务(tasks)可以是 public(公开) 或 private(私有) 类型的。
    • 公开任务(Public tasks) 从 gulpfile 中被导出(export),可以通过 gulp 命令直接调用。
    • 私有任务(Private tasks) 被设计为在内部使用,通常作为 series() 或 parallel() 组合的组成部分。

    一个私有(private)类型的任务(task)在外观和行为上和其他任务(task)是一样的,但是不能够被用户直接调用。如需将一个任务(task)注册为公开(public)类型的,只需从 gulpfile 中导出(export)即可

    const { series } = require('gulp');
    
    // `clean` 函数并未被导出(export),因此被认为是私有任务(private task)。
    // 它仍然可以被用在 `series()` 组合中。
    function clean(cb) {
      // body omitted
      cb();
    }
    
    // `build` 函数被导出(export)了,因此它是一个公开任务(public task),并且可以被 `gulp` 命令直接调用。
    // 它也仍然可以被用在 `series()` 组合中。
    function build(cb) {
      // body omitted
      cb();
    }
    
    exports.build = build;
    exports.default = series(clean, build);
    
    组合任务

    Gulp 提供了两个强大的组合方法: series()parallel(),允许将多个独立的任务组合为一个更大的操作。这两个方法都可以接受任意数目的任务(task)函数或已经组合的操作。series() 和 parallel() 可以互相嵌套至任意深度

    • 如果需要让任务(task)按顺序执行,请使用 series() 方法
    const { series } = require('gulp');
    
    function transpile(cb) {
      // body omitted
      cb();
    }
    
    function bundle(cb) {
      // body omitted
      cb();
    }
    
    exports.build = series(transpile, bundle); // 执行顺序 transpile --> bundle
    
    • 希望以最大并发来运行的任务(tasks),可以使用 parallel() 方法将它们组合起来。
    const { parallel } = require('gulp');
    
    function javascript(cb) {
      // body omitted
      cb();
    }
    
    function css(cb) {
      // body omitted
      cb();
    }
    
    exports.build = parallel(javascript, css);
    

    案例

    const { series } = require('gulp');
    
    function minify(cb) {
      // body omitted
      cb();
    }
    
    
    function transpile(cb) {
      // body omitted
      cb();
    }
    
    function livereload(cb) {
      // body omitted
      cb();
    }
    // 根据环境 执行不同的任务
    if (process.env.NODE_ENV === 'production') {
      exports.build = series(transpile, minify);
    } else {
      exports.build = series(transpile, livereload);
    }
    
    const { series, parallel } = require('gulp');
    
    function clean(cb) {
      // body omitted
      cb();
    }
    
    function cssTranspile(cb) {
      // body omitted
      cb();
    }
    
    function cssMinify(cb) {
      // body omitted
      cb();
    }
    
    function jsTranspile(cb) {
      // body omitted
      cb();
    }
    
    function jsBundle(cb) {
      // body omitted
      cb();
    }
    
    function jsMinify(cb) {
      // body omitted
      cb();
    }
    
    function publish(cb) {
      // body omitted
      cb();
    }
    
    exports.build = series(
      clean,
      parallel(
        cssTranspile,
        series(jsTranspile, jsBundle)
      ),
      parallel(cssMinify, jsMinify),
      publish
    );
    // 嵌套使用 series ,parallel
    // 执行顺序 clean --> 并行 cssTranspile, series(jsTranspile, jsBundle) -->  并行 cssMinify, jsMinify --> publish
    
    const { series, parallel } = require('gulp');
    
    function clean(cb) {
      // body omitted
      cb();
    }
    
    function css(cb) {
      // body omitted
      cb();
    }
    
    function javascript(cb) {
      // body omitted
      cb();
    }
    
    exports.build = series(clean, parallel(css, javascript)); 
    // 执行顺序 clean --> 并行 css  javascript
    

    3 异步执行
    当使用 series() 组合多个任务(task)时,任何一个任务(task)的错误将导致整个任务组合结束,并且不会进一步执行其他任务。

    当使用 parallel() 组合多个任务(task)时,一个任务的错误将结束整个任务组合的结束,但是其他并行的任务(task)可能会执行完,也可能没有执行完。
    gulp 不再支持同步任务(Synchronous tasks)了。因为同步任务常常会导致难以调试的细微错误,例如忘记从任务(task)中返回 stream。
    当你看到 "Did you forget to signal async completion?" 警告时,说明你并未使用前面提到的返回方式。你需要使用 callback 或返回 stream、promise、event emitter、child process、observable 来解决此问题


    4 处理文件
    gulp 暴露了 src()dest() 方法用于处理计算机上存放的文件
    const { src, dest } = require('gulp');
    // `src()` 接受 [glob]参数,并从文件系统中读取文件然后生成一个 Node 流(stream)。它将所有匹配的文件读取到内存中并通过流(stream)进行处理 
    // output 创建output 文件夹 再生成*.js 文件
    exports.default = function() {
      return src('src/*.js')
        .pipe(dest('output/'));
    }
    
    • 流(stream)所提供的主要的 API 是 .pipe() 方法,用于连接转换流(Transform streams)或可写流(Writable streams)
    const { src, dest } = require('gulp');
    const babel = require('gulp-babel');
    
    exports.default = function() {
      return src('src/*.js')
        .pipe(babel())
        .pipe(dest('output/'));
    }
    
    • 向流(stream)中添加文件
      src() 也可以放在管道(pipeline)的中间,以根据给定的 glob 向流(stream)中添加文件。新加入的文件只对后续的转换可用。如果 glob 匹配的文件与之前的有重复,仍然会再次添加文件。

      这对于在添加普通的 JavaScript 文件之前先转换部分文件的场景很有用,添加新的文件后可以对所有文件统一进行压缩并混淆(uglifying)

    const { src, dest } = require('gulp');
    const babel = require('gulp-babel');
    const uglify = require('gulp-uglify');
    
    exports.default = function() {
      return src('src/*.js') // 加载src 文件夹下的 js文件
        .pipe(babel()) // es6 转化
        .pipe(src('vendor/*.js')) // 加载vendor文件夹下的 js文件
        .pipe(uglify()) // 压缩文件
        .pipe(dest('output/')); // 输出到output 文件下
    }
    
    • 分阶段输出
      dest() 可以用在管道(pipeline)中间用于将文件的中间状态写入文件系统。当接收到一个文件时,当前状态的文件将被写入文件系统,文件路径也将被修改以反映输出文件的新位置,然后该文件继续沿着管道(pipeline)传输。
    const { src, dest } = require('gulp');
    const babel = require('gulp-babel');
    const uglify = require('gulp-uglify');
    const rename = require('gulp-rename');
    
    exports.default = function() {
      return src('src/*.js') // 1加载src 文件夹下的 js文件
        .pipe(babel()) // 2 es6 转化
        .pipe(src('vendor/*.js')) // 3加载vendor文件夹下的 js文件
        .pipe(dest('output/')) // 4 输出到output 文件下
        .pipe(uglify()) // 5 压缩文件(文件就是第 4 步的文件)
        .pipe(rename({ extname: '.min.js' })) // 6 重命名后缀加 .min.js
        .pipe(dest('output/'));// 输出到output 文件下
    }
    

    此功能可用于在同一个管道(pipeline)中创建未压缩(unminified)和已压缩(minified)的文件。

    • 模式:流动(streaming)、缓冲(buffered)和空(empty)模式
      src() 可以工作在三种模式下:缓冲(buffering)、流动(streaming)和空(empty)模式。这些模式可以通过对 src()bufferread 参数 进行设置。

      • 缓冲(Buffering)模式是默认模式,将文件内容加载内存中。插件通常运行在缓冲(buffering)模式下,并且许多插件不支持流动(streaming)模式。
      • 流动(Streaming)模式的存在主要用于操作无法放入内存中的大文件,例如巨幅图像或电影。文件内容从文件系统中以小块的方式流式传输,而不是一次性全部加载。如果需要流动(streaming)模式,请查找支持此模式的插件或自己编写。
      • 空(Empty)模式不包含任何内容,仅在处理文件元数据时有用。

    5 Glob 详解

    glob 是由普通字符和/或通配字符组成的字符串,用于匹配文件路径
    可以利用一个或多个 glob 在文件系统中定位文件
    glob 是数组时, 会按照数组中的位置依次执行匹配

    • 注意
      \\作为分隔符的 Windows 操作系统中。在 glob 中,\\ 字符被保留作为转义符使用。
      如下, * 被转义了,因此,* 将被作为一个普通字符使用,而不再是通配符了。
    'glob_with_uncommon_\\*_character.js' //   \*  被转义了
    

    避免使用 Node 的 path 类方法来创建 glob,例如 path.join。在 Windows 中,由于 Node 使用 \ 作为路径分隔符,因此将会产生一个无效的 glob。还要避免使用 __dirname 和 __filename 全局变量,由于同样的原因,process.cwd() 方法也要避免使用。

    const invalidGlob = path.join(__dirname, 'src/*.js');
    
    • 特殊字符: * (一个星号)

    在一个字符串片段中匹配任意数量的字符,包括零个匹配。对于匹配单级目录下的文件很有用

    '*.js' // 能够匹配类似 index.js 的文件,但是不能匹配类似 scripts/index.js 或 scripts/nested/index.js 的文件
    
    • 特殊字符: ** (两个星号)
    // 下面这个 glob 被适当地限制在 scripts/ 目录下。它将匹配类似 scripts/index.js、scripts/nested/index.js 和 scripts/nested/twice/index.js 的文件。
    'scripts/**/*.js'
    
    • 特殊字符: ! (取反)
      由于 glob 匹配时是按照每个 glob 在数组中的位置依次进行匹配操作的,所以 glob 数组中的取反(negative)glob必须跟在一个非取反(non-negative)的 glob 后面。第一个 glob 匹配到一组匹配项,然后后面的取反 glob 删除这些匹配项中的一部分。如果取反 glob 只是由普通字符组成的字符串,则执行效率是最高的
    ['script/**/*.js', '!scripts/vendor/']
    

    如果任何非取反(non-negative)的 glob 跟随着一个取反(negative) glob,任何匹配项都不会被删除

    ['script/**/*.js', '!scripts/vendor/', 'scripts/vendor/react.js']
    

    取反(negative) glob 可以作为对带有两个星号的 glob 的限制手段

    ['**/*.js', '!node_modules/']
    

    在上面的示例中,如果取反(negative)glob 是 !node_modules/*/.js,那么各匹配项都必须与取反 glob 进行比较,这将导致执行速度极慢

    • 匹配重叠(Overlapping globs)
      两个或多个 glob 故意或无意匹配了相同的文件就被认为是匹配重叠(overlapping)了。如果在同一个 src() 中使用了会产生匹配重叠的 glob,gulp 将尽力去除重叠部分,但是在多个 src() 调用时产生的匹配重叠是不会被去重的。

    6 使用插件

    Gulp 插件实质上是 Node 转换流(Transform Streams), 它封装了通过管道(pipeline)转换文件的常见功能,通常是使用 .pipe() 方法并放在 src() 和 dest() 之间。他们可以更改经过流(stream)的每个文件的文件名、元数据或文件内容。

    const { src, dest } = require('gulp');
    const uglify = require('gulp-uglify');
    const rename = require('gulp-rename');
    
    exports.default = function() {
      return src('src/*.js')
        // gulp-uglify 插件并不改变文件名
        .pipe(uglify())
        // 因此使用 gulp-rename 插件修改文件的扩展名
        .pipe(rename({ extname: '.min.js' }))
        .pipe(dest('output/'));
    }
    
    • 条件插件
    const { src, dest } = require('gulp');
    const gulpif = require('gulp-if');
    const uglify = require('gulp-uglify');
    
    function isJavaScript(file) {
      // 判断文件的扩展名是否是 '.js'
      return file.extname === '.js';
    }
    
    exports.default = function() {
      // 在同一个管道(pipeline)上处理 JavaScript 和 CSS 文件
      return src(['src/*.js', 'src/*.css'])
        // 只对 JavaScript 文件应用 gulp-uglify 插件
        .pipe(gulpif(isJavaScript, uglify()))
        .pipe(dest('output/'));
    }
    
    • 内联插件
      内联插件是一次性的转换流(Transform Streams),你可以通过在 gulpfile 文件直接书写需要的功能。
    const { src, dest } = require('gulp');
    const uglify = require('uglify-js');
    const through2 = require('through2');
    
    exports.default = function() {
      return src('src/*.js')
        // 创建一个内联插件,从而避免使用 gulp-uglify 插件
        .pipe(through2.obj(function(file, _, cb) {
          if (file.isBuffer()) {
            const code = uglify.minify(file.contents.toString())
            file.contents = Buffer.from(code)
          }
          cb(null, file);
        }))
        .pipe(dest('output/'));
    }
    

    避免自己创建并维护插件。
    避免 fork 一个已经存在的插件并添加自己所需的功能。


    7 文件监控

    gulp api 中的 watch() 方法利用文件系统的监控程序(file system watcher)将 globs任务(task) 进行关联。它对匹配 glob 的文件进行监控,如果有文件被修改了就执行关联的任务(task)。如果被执行的任务(task)没有触发 异步完成 信号,它将永远不会再次运行了

    const { watch, series } = require('gulp');
    
    function clean(cb) {
      // body omitted
      cb();
    }
    
    function javascript(cb) {
      // body omitted
      cb();
    }
    
    function css(cb) {
      // body omitted
      cb();
    }
    
    // 可以只关联一个任务
    watch('src/*.css', css);
    // 或者关联一个任务组合
    watch('src/*.js', series(clean, javascript));
    
    • 可监控的事件
      默认情况下,只要创建、更改或删除文件,文件监控程序就会执行关联的任务(task)。 如果你需要使用不同的事件,你可以在调用 watch() 方法时通过 events 参数进行指定。可用的事件有 addaddDirchangeunlinkunlinkDirreadyerror。此外,还有一个 all 事件,它表示除readyerror之外的所有事件。
    const { watch } = require('gulp');
    
    // 所有事件都将被监控
    watch('src/*.js', { events: 'all' }, function(cb) {
      // body omitted
      cb();
    });
    
    • 初次执行
      调用 watch() 之后,关联的任务(task)是不会被立即执行的,而是要等到第一次文件修之后才执行。

      如需在第一次文件修改之前执行,也就是调用 watch() 之后立即执行,请将 ignoreInitial 参数设置为false

    const { watch } = require('gulp');
    
    // 关联的任务(task)将在启动时执行
    watch('src/*.js', { ignoreInitial: false }, function(cb) {
      // body omitted
      cb();
    });
    
    • 队列
      watch() 方法能够保证当前执行的任务(task)不会再次并发执行。当文件监控程序关联的任务(task)正在运行时又有文件被修改了,那么所关联任务的这次新的执行将被放到执行队列中等待,直到上一次关联任务执行完之后才能运行。每一次文件修改只产生一次关联任务的执行并放入队列中。

      如需禁止队列,请将 queue 参数设置为 false。

    const { watch } = require('gulp');
    
    // 每次文件修改之后关联任务都将执行(有可能并发执行)
    watch('src/*.js', { queue: false }, function(cb) {
      // body omitted
      cb();
    });
    
    • 延迟
      文件更改之后,只有经过 200 毫秒的延迟之后,文件监控程序所关联的任务(task)才会被执行。这是为了避免在同使更改许多文件时(例如查找和替换操作)过早启动任务(taks)的执行。

      如需调整延迟时间,请为 delay 参数设置一个正整数。

    const { watch } = require('gulp');
    
    // 文件第一次修改之后要等待 500 毫秒才执行关联的任务
    watch('src/*.js', { delay: 500 }, function(cb) {
      // body omitted
      cb();
    });
    

    相关文章

      网友评论

          本文标题:gulp

          本文链接:https://www.haomeiwen.com/subject/kduclltx.html