Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[译] Gulp 4 入门指南 #7

Open
Leooonard opened this issue Aug 19, 2015 · 1 comment

Comments

@Leooonard
Copy link

commented Aug 19, 2015

gulp4

原文地址:http://www.ociweb.com/resources/publications/sett/gulp-4/

介绍

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在监视过程中是否产生了错误。
  • 观察浏览器的刷新结果。
  • 就这么不停工作下去。

后续

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

总结

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

@cssmagic

This comment has been minimized.

Copy link

commented Nov 6, 2015

补充一下 Gulp v3 的中文文档:
https://github.com/lisposter/gulp-docs-zh-cn

以及 Gulp v4 的 API 中文文档:
cssmagic/blog#55

@zpbx zpbx changed the title [译] gulp4 [译] Gulp 4 入门指南 Dec 21, 2015

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
2 participants
You can’t perform that action at this time.