Leooonard / Notes

2 stars 0 forks source link

gulp4翻译 #3

Open Leooonard opened 9 years ago

Leooonard commented 9 years ago

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来完成以下常见的任务:

关于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-cli#4.0 --save-dev
  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有大量的插件可用,下列是我推荐的一些优秀插件:

此外,我们经常使用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”。基本语法包括:

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的常见使用方法可以总结为以下步骤:

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

总结

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

hax commented 9 years ago

代码块没设语法高亮?

amio commented 9 years ago

General:

Specific:

Review 了一半(到 Defining gulp Tasks),你先把全文过一遍把语序不对劲的调整下吧。

另外文中介绍的 API 都是 4.0 的了(文章没说,但内容是。你可以加个译注),你测试代码需要装 4.0。

hax commented 9 years ago

@amio 看得很细致,@Leooonard 抽时间更新下呀!

Leooonard commented 9 years ago

@hax 好, 大师兄 这周更新完!

Leooonard commented 9 years ago

@amio @hax 大炜哥,大师兄,已按照上述建议进行了相应的更新。自己也添加了一些语言,让文章读起来更像一篇中文文章。

现在翻译过程中遇到几个比较难以翻译的地方,分别是:

  1. gulp configuration,出现在原文Introduction第三段,翻译成配置总觉得怪。
  2. gulp api中的option参数,还没想到适合的词来表述。
  3. require,还没想到适合的词来表述。读取?引入?

某些代码示例的标点符号不标准,无法执行,如 var foo = require(‘gulp-foo’);

大炜哥太细致了。。

amio commented 9 years ago

@Leooonard 先回答下问题,文章明天再看

  1. 这一部分翻译也没什么错误,不过不懂的人可能还是看不懂,这种时候可以加一些注释辅助,酌情意译:“配置方法是 gulp 和 Grunt 的主要差异之一。它们都用一个 JS 文件进行配置,gulp 采用 gulpfile.js,Grunt 则用 Gruntfile.js。gulp 通过调用不同的函数(gulp.taskgulp.watch等)进行配置;Grunt 则需要把一个对象传递给 grunt.initConfig 来完成配置。”
  2. “option” 可以翻成“选项”
  3. 代码中的 require 可以不翻译,正文中的 require 则要看你的上下文了。

PS,你括号改了,有一些单引号还是不对,比如……还是前面那个例子 var foo = require(‘gulp-foo’);

amio commented 9 years ago

这次改完整体上好多了,剩下的一些:

Leooonard commented 9 years ago

@hax , @amio,大师兄,大炜哥。

我全文过了一遍。这次主要修改了:

  1. 原文有些段落之间的关系比较弱,读起来会有些跳跃感,我帮他们加了小标题,小标题是我自己的理解。
  2. 大炜哥提出的问题进行了修改。
  3. 原文和代码有关的标点符号检查了一遍。
  4. 使语言上更符合中文的味道。
amio commented 9 years ago

btw,拿了风投的钱还能跳票跳得这么任性也是醉了

hax commented 9 years ago

没问题就发吧!

Leooonard commented 9 years ago

没问题就发吧!

好!