Gulp 4 入门指南

发布于 2022-04-25 12:54:09 字数 17445 浏览 1279 评论 0

gulp 是一个基于 JavaScript 的构建工具,它主要用于web 部署任务的自动化执行。gulp 可以自动化完成你通过 Node.js 做的任何事。由于 Node.js 可以执行 shell命令,所以 gulp 几乎可以自动化所有任务。但在实际使用中,gulp 主要用于 web 开发中。

gulp 与 Grunt

gulp 的主要竞争对手是 Grunt,它和 gulp 一样是免费和开源的。其他不怎么火的竞争者有:Broccoli,Brunch,Cake 和 Jake。

配置方法是 gulp 和 Grunt 的主要差异之一。它们都通过一个 JavaScript 文件进行配置,gulp 将其命名为 gulpfile.js,Grunt 将其命名为 Gruntfile.js。gulp 通过调用不同的函数(gulp.taks,gulp.watch)完成配置,而 grunt 则通过传递一个字面量对象({... :...})参数给 grunt.initConfig 函数来完成配置。

gulp 对比 Grunt 的优势之一是,gulp 的运行速度相比 Grunt 更快。这归功于它使用了 stream。对比两者执行连续任务的方式,Grunt 执行连续任务通过创建大量的临时文件(临时文件作为步骤i的输出以及步骤 i+1 的输入)。gulp 使用 stream 则允许我们更早的开始接下去的任务,只要 stream 中存在数据即可开始,而非必须等待整个文件被写完。

gulp与Node.js

如果你希望使用gulp,那么必须安装Node.js。当然,它同样可以在io.js上运行(io.js是Node.js的一个分支,它使用了更新版本的v8 javascript引擎,并且更新的更频繁)。就如同Node.js,gulp可以在多种平台下使用,例如windows,mac osx和其他unix系的操作系统。

在gulp中,我们同样可以使用es6中的特性(通过在Node.js或io.js,使用--harmony参数开启es6中的特性),
在*nix环境下,你需要这么做
创建一个别名alias gulp6='node --harmony $(which gulp)'(what is $() ?, what is which?),你也可以将这行代码放在.bashrc中,这样每次你打开终端时它都将生效。完成这些,你就可以使用gulp6命令代替gulp命令,开始享受es6带来的新特性。
在windows环境下:
你可以试着使用doskey命令来做类似的事。

本文的某些例子中将使用es6的一项新特性,箭头函数表达式。这个特性允许我们使用一种更简洁的方式来书写匿名函数,并且它还有其他的优点(详情可见:Arrow functions MDN)。对于本文,你只需知道function () { return expression; }在es6中等价于() => expression

gulp插件简介

gulp的一大优势是大量有用的插件,它们能够帮助我们完成各种任务。截止到2015.5.25,已经有1711个gulp插件可供使用。你可以试着在*nix系统中使用npm search gulpplugin | wc-l命令来查看最新的插件数目。

我们可以使用gulp来完成以下常见的任务:

  • 检查html,css,javascript和json文件的语法。
  • 将es6 javascript代码编译为es5(通过使用babel,traceur或者typescript)。
  • 运行单元测试和端到端测试。
  • 合并和压缩css,javascript文件。
  • 通过http来提供静态文件服务。
  • 执行shell命令。
  • 监视特定文件或者文件类型的改变,当监察到改变后运行特定任务。
  • 当文件改变后刷新浏览器(livereload)。

关于gulp插件,下文将更详细的讲述。

在我写这篇文章时,gulp最新的稳定版本为3。本文介绍的目标是新的开发版本4。版本4并不向后兼容版本3。如果你希望使用gulp4,那么你需要修改gulpfile.js。当然,大部分面向gulp3的插件仍然可在gulp4正常使用。

这篇文章中,如果你使用的是windows,那你需要将terminal替换为Command Prompt。

安装gulp

在终端键入npm install -g gulp,将安装最新,稳定的版本。如果你希望在gulp4成为稳定版之前就安装它,那么执行以下指令:

  1. 打开终端。
  2. 确定已正确安装git。
  3. 如果之前安装过gulp,使用npm uninstall -g gulp命令来卸载它。
  4. 使用npm install -g gulpjs/gulp-cli#4.0命令来安装gulp 4。

如果你希望现在就在你的项目中使用gulp4,那么执行以下命令:

  1. 打开终端。
  2. 进入到你项目的顶层目录。
  3. 使用npm init命令来新建package.json文件,回答新建过程中遇到的问题。
  4. 本地安装gulp并且将它作为一个依赖添加到package.json中,命令如下:npm install gulp --save-dev
  5. 如果之前已安装非4版本的gulp,那么使用npm uninstall gulp指令卸载它。
  6. 本地安装gulp4并且将它作为一个依赖添加到package.json中,命令如下:npm install gulpjs/gulp.git#4.0 --save-dev。(原文命令npm install gulpjs/gulp-cli#4.0 --save-dev,实验后并不能安装本地gulp4.0)
  7. 创建gulpfile.js

npm install的--save-dev参数将在package.json中添加一个开发依赖。这使团队内的其他开发者能够通过使用npm install命令来安装整个项目的依赖。

运行gulp

在尝试运行定义在gulpfile.js内的gulp任务之前,进入到项目目录的根目录或其子目录内。

键入gulp --help或者gulp -h来获取基本的帮助信息。

键入gulp --version或者gulp -v来查看本机安装的gulp的版本。它将显示全局安装和基于项目本地安装的gulp的版本。

键入gulp --tasks或者gulp -T来查看gulpfile.js中定义的所有任务。这将输出一个任务依赖树。如果你希望以平铺的形式查看定义的任务列表,键入gulp --tasks-simple

键入gulp --verify来检查是否依赖了被列入黑名单中的插件。这将检查列在package.json文件中的依赖。(gulp3.9中没有该命令?)

键入gulp [options] task1 task2...来运行定义在gulpfile.js中的任务。除了任务名外,我们还可以输入一些可选选项,但是通常来说我们不会使用它们。当我们同时输入了多个任务名,它们将被并行运行。如果希望它们能够串行的运行,那么在gulpfile.js中定义一个新任务,在该任务内依次执行它们,然后单独在命令行中运行那个新任务。如果没有指定任务名,那么default任务将运行,之后我们将看到如何定义一个默认任务。如果不指定具体的任务,并且没有定义default任务,那会显示一条错误信息。

大部分任务运行结束后gulp将退出。某些任务例如connect(用于提供http静态资源服务)和watch(用于监视文件是否有改变)将会一直运行直到任务被人为取消(或者出错退出),gulp将不会自动退出。你可以使用ctrl-c来退出任务并结束gulp。

gulp插件

gulp有大量的插件可用,下列是我推荐的一些优秀插件:

  • gulp-babel -将es6(JavaScript)编译为es5(JavaScript)。
  • gulp-changed -过滤掉比目标文件旧的文件(只处理有更新的文件)。
  • gulp-concat -合并css和javascript文件。
  • gulp-csslint -检验css文件正确性。
  • gulp-eslint -使用eslint检验javascript正确性。
  • gulp-jasmine -运行jasmine测试。
  • gulp-jshint -使用jshint检验javascript正确性。
  • gulp-jscs -使用jscs检查javascript代码风格。
  • gulp-less -将less文件编译为css。
  • gulp-livereload -当调用livereload方法时,刷新监听的浏览器。
  • gulp-plumber -允许gulp在发生错误之后继续运行。
  • gulp-sourcemaps -产生允许调试的sourcemap文件,用来调试被编译过的javascript文件。
  • gulp-uglify -压缩javascript文件。
  • gulp-usemin -将html文件中css,js文件的路径替换为其min版本。
  • gulp-watch -监视文件是否被修改并且在我们修改文件后执行指定任务。

此外,我们经常使用npm的del模块来删除指定的目录和文件。

你可以通过访问http://gulpjs.com/plugins来搜索gulp插件。该站将列出那些带有gulpplugin关键词,且被发布到npm的插件,通过点击插件名链接可以直接访问插件文档。另一个搜索插件的方法是使用npm search命令。比如,可以键入npm search gulpplugin lint来搜索有linting功能的插件。

我们可以键入npm install plugin-name --save-dev来安装一个插件。这将安装插件到项目的node_modules目录。一旦插件安装完成,就可以修改gulpfile.js来require插件并且在一个或多个任务使用它。比如,var foo = require('gulp-foo');

一个更好的require插件的方法是使用gulp-load-plugins插件,它为我们提供了更好的require功能。这让我们不必为添加每个插件使用require。gulp-load-plugins读取package.json中那些名字以gulp-开始的依赖,并返回一个对象,对象的属性是依赖的名字。gulp-load-plugins有lazy load特性,它直到插件被使用时才会读取插件,而不用到的插件不会被读取。

var pi = require('gulp-load-plugins')(); //我们可以在任务的定义内使用pi.name来引用插件。

gulp 方法(下列 API 皆为 gulp4.0 版本)

gulp 和 undertaker 类中定义了gulp所提供的方法。

gulp类提供了src,dest和watch方法。它定义在gulp的github repo中的顶层文件index.js中(现在的地址为https://github.com/gulpjs/gulp/tree/4.0)。这个类继承于undertaker类。

undertaker类提供了task,series,parallel,get,set,tree和registry方法。它定义在undertaker的github repo中的顶层文件index.js中(https://github.com/phated/undertaker)。undertaker类继承于node核心类eventemitter(https://nodejs.org/api/events.html)。

如果只是为了使用gulp,并不一定要了解这些继承关系。但是理解这些关系将有助于我们理解如何使用其中某些方法。

gulp使用的另一个关键的npm模块是vinyl-fs。vinyl-fs使用vinyl对象来存储用于描述文件的metadata。vinyl适配器提供了通过stream来访问vinyl对象内容的方法。源stream产生文件对象,目标stream使用这些文件对象。更具体的可以参见https://github.com/wearefractal/vinyl-fshttps://github.com/wearefractal/vinylhttps://medium.com/@contrahacks/gulp-3828e8126466

下面这行代码可以帮助我们获取一个gulp对象:

var gulp = require('gulp');

这个对象支持gulp类,undertaker类和eventemitter类中定义的所有方法。

在介绍具体的方法之前,我们需要先简单理解下通配符。gulp中的许多方法接受通配符参数。这可以是一个字符串或者一个由字符串组成的数组。字符串可以包含通配符。底层的实现由npm模块node-glob提供。更详细的语法参见“glob primer”。基本语法包括:

  • **?**代表任一字符。
  • * 代表0个或多个任意字符。
  • ** 在路径中表示任意数目的目录。

src 方法

src方法提供了一个vinyl对象组成的stream,这些stream将传递给插件使用。它接受一个通配符表达式和一个选项对象(可选)。通配符表达式指明了将要处理的输入文件。选项则将传递给glob模块。详情请参见https://github.com/gulpjs/gulp/blob/master/docs/API.md#gulpsrcglobs-optionshttps://github.com/isaacs/node-glob

dest 方法

dest方法接受输送来的stream数据并且将它输出至文件。所有流向它的数据都存在备份**(原文re-emmited)**,这允许我们多次调用dest方法来将结果输出至多个文件。他接受一个目标路径参数和一个选项对象(可选)。目标路径参数指定了输出文件或者目录的路径。对于选项的详细介绍,请参见https://github.com/gulpjs/gulp/blob/master/docs/API.md#gulpdestpath-options

watch 方法

watch方法将监视文件并且当它们被改变时调用指定任务。它接受一个通配符表达式,一个选项对象(可选)和一个方法。通配符表达式指定了需要监视的文件。这个方法通过npm模块gaze来实现。详情请参见https://github.com/shama/gaze

task 方法

task方法定义了一个任务。它接受一个任务名字符串和一个方法。当任务开始运行,这个方法也将开始运行。方法可以是匿名函数或者声明在任意位置的函数。如果没有传入方法对象,将返回先前定义的任务方法。

series 方法

series方法将返回一个函数。当你调用这个函数时,它将串行执行你定义在series方法中的任务。它接受任意数目的参数,参数可以是任务名或函数。因为它返回一个函数,它可以作为参数使用在其他方法中(如task方法)。

parallel 方法

parallel 方法将返回一个函数,当你调用这个函数时,他将并行执行你定义在parallel方法中的任务。它接受任意数目的参数,参数可以是任务名和函数。因为它返回一个函数,它可以作为参数使用在其他方法中(如task方法)。

下列所述的undertaker类中的方法通常不会直接使用在gulpfile.js中。

get 方法

get 方法将返回一个和传入的任务名相关的函数,它接受一个任务名作为参数。

set 方法

set方法设置或者改变和传入的任务名相关的函数。它接受一个任务名和一个函数作为参数,如果该任务已经定义了任务执行函数,那么该函数将被替换。

tree 方法

tree方法返回一个数组,该数组将包括已经定义的任务名字符串。它接受一个额外参数对象。如果deep参数设置为true,那么返回的数组还将包括每个任务的依赖关系。这个方法等于在命令行使用gulp --tasks或者gulp --tasks-simple。

registry 方法

registry方法获取或者设置任务名和任务执行函数的映射。

定义 gulp 任务

因为gulp运行在Node.js上,gulpfile.js中可以包含任何Node.js可以处理的代码。这代表所有的Node.js核心模块和npm模块都可以使用。

下面是一个简单的定义gulp任务的例子:

var gulp = require('gulp');
gulp.task('hello', function () {
    console.log('Hello, World!');
});

下面是这个例子在es6中的实现:

let gulp = require('gulp');
gulp.task('hello', () => console.log('Hello, World!'));

然后键入gulp hello,你将会看到屏幕上打印出'Hello,World!'。

以下三种形式都将定义一个gulp任务:

gulp.task(name, function () { ... });
gulp.task(name, gulp.series(...));
gulp.task(name, gulp.parallel(...));

一个gulp任务通常来说会读取特定的文件,对文件内容采取一个或多个的操作,然后生成一个或多个输出文件。下面是一个普通的gulp任务:

gulp.task(name, function () {
    return gulp.src(srcPath).
    pipe(somePluginFn()).
    pipe(anotherPluginFn()).
    pipe(gulp.dest(destPath));
});

在es6中,我们也可以这么写:

gulp.task(name, () =>
    gulp.src(srcPath).
    pipe(somePluginFn()).
    pipe(anotherPluginFn()).
    pipe(gulp.dest(destPath)));

使用gulp提供静态资源服务

有许多npm模块通过http协议来向外提供静态文件。一个常见选择是connect。安装必要模块的命令如下:

    npm install connect --save
    npm install serve-static -save

下面是一个gulp任务从项目目录顶层提供静态文件的例子:

var connect = require('connect');
var http = require('http'); // a Node.js core module
var serveStatic = require('serveStatic');
gulp.task('connect', function () {
    var app = connect();
    app.use(serveStatic(__dirname));
    var port = 8080;
    http.createServer(app).listen(port);
});

__dirname是一个Node.js全局变量,它存放了当前目录的路径。如果在该目录内有一个index.html文件,那么我们可以通过在浏览器地址栏键入httpL//localhost:8080来访问它。

如果要运行这个任务,键入gulp connect

监听文件

gulp可以监听文件的改变或新文件的创建。但如果是gulpfile.js本身被改变了,那么必须重启gulp来使用被修改过的gulpfile.js。

下面是一个使用gulp来监听less文件改变的例子。当gulp到改变后,它将运行less和csslint任务。

gulp.task('watch', function () {
    gulp.watch('styles/*.less', gulp.series('less', 'csslint'));
})

Live Reload

不仅如此,gulp甚至可以让浏览器自动刷新。这个功能对于那些浏览器读取的文件很有用,比如html,css,javascript文件等。现在,有许多gulp插件支持这个功能。最常见的是gulp-livereload。这个插件最好与chrome协作使用,并且需要安装livereload chrome插件。如果你希望安装该插件,访问https://chrome.google.com/webstore/category/apps然后搜索livereload。

你可以按下列步骤来使用该插件:

  1. 安装gulp-livereload插件。
  2. 添加script元素至html文件:<script src="http://localhost:35729/liverload.js"></script>
  3. 在watch任务内调用livereload.listen()。
  4. 在需要触发刷新时调用livereload()。

下面的gulpfile.js作为示例,展示了上述所述的最后两步。他定义了许多gulp任务,这些在我们日常的web应用中都十分常见并且有用。

gulpfile.js示例

var connect = require('connect');
var del = require('del');
var gulp = require('gulp');
var http = require('http');
var pi = require('gulp-load-plugins')();
var serveStatic = require('serve-static');

var paths = {
  build: 'build',
  css: 'build/**/*.css',
  html: ['index.html', 'src/**/*.html'],
  js: ['src/**/*.js'],
  jsPlusTests: ['src/**/*.js', 'test/**/*.js'],
  less: 'src/**/*.less',
  test: 'build/**/*-test.js'
};

// This just demonstrates the simplest possible task.
gulp.task('hello', function () {
  console.log('Hello, World!'));
});

// This deletes all generated files.
// In tasks that do something asynchronously, the function
// passed to task should take a callback function and
// invoke it when the asynchronous action completes.
// This is how gulp knows when the task has completed.
gulp.task('clean', function (cb) {
  del(paths.build, cb);
});

// This starts a simple HTTP file server.
gulp.task('connect', function () {
  var app = connect();
  app.use(serveStatic(__dirname));
  http.createServer(app).listen(1919);
});

// This validates all CSS files.
// In this example, the CSS files are generated from LESS files.
gulp.task('csslint', function () {
  return gulp.src(paths.css).
    pipe(pi.csslint({ids: false})).
    pipe(pi.csslint.reporter());
});

// This validates JavaScript files using ESLint.
gulp.task('eslint', function () {
  return gulp.src(paths.jsPlusTests).
    pipe(pi.changed(paths.build)).
    pipe(pi.eslint({
      envs: ['browser', 'es6', 'node'],
      rules: {
        curly: [2, 'multi-line'],
        indent: [2, 2]
      }
    })).
    pipe(pi.eslint.format());
});

// This is used by the "watch" task to
// reload the browser when an HTML file is modified.
gulp.task('html', function () {
  return gulp.src(paths.html).
    pipe(pi.livereload());
});

// This validates JavaScript files using JSHint.
gulp.task('jshint', function () {
  return gulp.src(paths.jsPlusTests).
    pipe(pi.changed(paths.build)).
    pipe(pi.jshint()).
    pipe(pi.jshint.reporter('default'));
});

// This compiles LESS files to CSS files.
gulp.task('less', function () {
  return gulp.src(paths.less).
    pipe(pi.changed(paths.build)).
    pipe(pi.less()).
    pipe(gulp.dest(paths.build)).
    pipe(pi.livereload());
});

// This compiles ES6 JavaScript files to ES5 JavaScript files.
// "transpile" is a term used to describe compiling
// one syntax to a different version of itself.
// Compiling ES6 code to ES5 fits this description.
gulp.task('transpile-dev', function () {
  return gulp.src(paths.jsPlusTests).
    pipe(pi.changed(paths.build)).
    pipe(pi.sourcemaps.init()).
    pipe(pi.babel()).
    pipe(pi.sourcemaps.write('.')).
    pipe(gulp.dest(paths.build)).
    pipe(pi.livereload());
});

// This does the same as the previous task, but also
// concatenates and minimizes the resulting JavaScript files.
gulp.task('transpile-prod', function () {
  return gulp.src(paths.js).
    pipe(pi.sourcemaps.init()).
    pipe(pi.babel()).
    pipe(pi.concat('all.js')).
    pipe(pi.uglify()).
    pipe(pi.sourcemaps.write('.')).
    pipe(gulp.dest(paths.build));
});

// This is not meant to be used directly.
// Use the "test" task instead.
gulp.task('jasmine', function () {
  return gulp.src(paths.test).
    pipe(pi.plumber()).
    pipe(pi.jasmine());
});

gulp.task('test', gulp.series('transpile-dev', 'jasmine'));

// This watches HTML, LESS, and JavaScript files for changes
// and processes them when they do.
// It also reloads the web browser.
gulp.task('watch', function () {
  pi.livereload.listen();
  gulp.watch(paths.html, gulp.series('html'));
  gulp.watch(paths.less, gulp.series('less', 'csslint'));
  gulp.watch(paths.jsPlusTests,
    gulp.series('eslint', 'jshint', 'transpile-dev'));
});

// This compiles LESS and ES5 JavaScript files in parallel.
gulp.task('build-dev', gulp.parallel('less', 'transpile-dev'));

// This does the same as the previous tasks, but also
// concatenates and minimizes the resulting JavaScript files.
gulp.task('build-prod', gulp.parallel('less', 'transpile-prod'));

// This is used when gulp is run without specifying a task.
// It runs the "build-dev" task and then
// starts the "connect" and "watch" tasks in parallel.
// This is the most commonly used task during development.
gulp.task('default',
  gulp.series('build-dev', gulp.parallel('connect', 'watch')));

gulp 的通常用法

对于 gulpfile.js 的常见使用方法可以总结为以下步骤:

  • 打开一个终端,并且进入到项目文件,运行gulp。这将开启本地http服务器并且监视文件的变化。
  • 保持终端是可见的,这样你能观察到最新的输出。
  • 在支持livereload的浏览器中浏览自己编写的程序。
  • 使用编辑器或IDE来编辑代码。
  • 观察gulp在监视过程中是否产生了错误。
  • 观察浏览器的刷新结果。
  • 就这么不停工作下去。

后续

我们都很关心 gulp 4 何时能够代替 gulp 3 成为最新的稳定版本。在 2015.1.29,gulp 4 的主要开发者发推文说“gulp4 本可以在 31 号问世,但是很不幸,我得了流感,我感觉很不好,所以对不起了伙计们。”。于是在 2015.4.28,历时三个月的等待后,我向他问道,“gulp4 有何最新进展嘛?虽然我们现在已经能够使用它,但是我们更希望他能成为一个官方版本。”。令人遗憾的是,我收到的回复十分简单,“没有。”。在 2015.5.19,gulp 的主要开发者发推文表示,“他们获得了 1000 万对于 @gulpjs 的赞助。”。我希望这表示他们将会继续新版本的开发工作。

总结

gulp是一款十分流行并且功能强大的自动化web部署工具。看起来,它现在已经比Grunt火多了。如果你现在还没在使用gulp,那还等什么?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据

关于作者

JSmiles

生命进入颠沛而奔忙的本质状态,并将以不断告别和相遇的陈旧方式继续下去。

0 文章
0 评论
84961 人气
更多

推荐作者

醉城メ夜风

文章 0 评论 0

远昼

文章 0 评论 0

平生欢

文章 0 评论 0

微凉

文章 0 评论 0

Honwey

文章 0 评论 0

qq_ikhFfg

文章 0 评论 0

    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文