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成为稳定版之前就安装它,那么执行以下指令:
- 打开终端。
- 确定已正确安装git。
- 如果之前安装过gulp,使用
npm uninstall -g gulp
命令来卸载它。 - 使用
npm install -g gulpjs/gulp-cli#4.0
命令来安装gulp 4。
如果你希望现在就在你的项目中使用gulp4,那么执行以下命令:
- 打开终端。
- 进入到你项目的顶层目录。
- 使用npm init命令来新建package.json文件,回答新建过程中遇到的问题。
- 本地安装gulp并且将它作为一个依赖添加到package.json中,命令如下:
npm install gulp --save-dev
- 如果之前已安装非4版本的gulp,那么使用
npm uninstall gulp
指令卸载它。 - 本地安装gulp4并且将它作为一个依赖添加到package.json中,命令如下:
npm install gulpjs/gulp.git#4.0 --save-dev
。(原文命令npm install gulpjs/gulp-cli#4.0 --save-dev
,实验后并不能安装本地gulp4.0) - 创建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-fs,https://github.com/wearefractal/vinyl和https://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-options和https://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。
你可以按下列步骤来使用该插件:
- 安装gulp-livereload插件。
- 添加script元素至html文件:
<script src="http://localhost:35729/liverload.js"></script>
。 - 在watch任务内调用livereload.listen()。
- 在需要触发刷新时调用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 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论