├── .gitignore ├── API.md ├── CLI.md ├── FAQ.md ├── README.md ├── getting-started.md ├── recipes ├── README.md ├── automate-release-workflow.md ├── browserify-multiple-destination.md ├── browserify-transforms.md ├── browserify-uglify-sourcemap.md ├── browserify-with-globs.md ├── combining-streams-to-handle-errors.md ├── delete-files-folder.md ├── exports-as-tasks.md ├── fast-browserify-builds-with-watchify.md ├── handling-the-delete-event-on-watch.md ├── incremental-builds-with-concatenate.md ├── maintain-directory-structure-while-globbing.md ├── make-stream-from-buffer.md ├── minified-and-non-minified.md ├── mocha-test-runner-with-gulp.md ├── only-pass-through-changed-files.md ├── pass-arguments-from-cli.md ├── rebuild-only-files-that-change.md ├── run-grunt-tasks-from-gulp.md ├── running-task-steps-per-folder.md ├── running-tasks-in-series.md ├── server-with-livereload-and-css-injection.md ├── sharing-streams-with-stream-factories.md ├── specifying-a-cwd.md ├── split-tasks-across-multiple-files.md ├── templating-with-swig-and-yaml-front-matter.md ├── using-external-config-file.md └── using-multiple-sources-in-one-task.md └── writing-a-plugin ├── README.md ├── dealing-with-streams.md ├── guidelines.md ├── recommended-modules.md ├── testing.md └── using-buffers.md /.gitignore: -------------------------------------------------------------------------------- 1 | Thumbs.db 2 | ehthumbs.db 3 | [Dd]esktop.ini 4 | $RECYCLE.BIN/ 5 | .DS_Store 6 | .klive 7 | .dropbox.cache 8 | 9 | *.tmp 10 | *.bak 11 | *.swp 12 | *.lnk 13 | 14 | .svn 15 | .idea 16 | 17 | node_modules/ 18 | bower_components/ 19 | npm-debug.log 20 | 21 | *.zip 22 | *.gz 23 | -------------------------------------------------------------------------------- /API.md: -------------------------------------------------------------------------------- 1 | ## gulp API 文档 2 | 3 | 跳转: 4 | [gulp.src](#gulpsrcglobs-options) | 5 | [gulp.dest](#gulpdestpath-options) | 6 | [gulp.task](#gulptaskname--deps--fn) | 7 | [gulp.watch](#gulpwatchglob--opts-tasks-or-gulpwatchglob--opts-cb) 8 | 9 | ### gulp.src(globs[, options]) 10 | 11 | 输出(Emits)符合所提供的匹配模式(glob)或者匹配模式的数组(array of globs)的文件。 12 | 将返回一个 [Vinyl files](https://github.com/gulpjs/vinyl-fs) 的 [stream](http://nodejs.org/api/stream.html) 13 | 它可以被 [piped](http://nodejs.org/api/stream.html#stream_readable_pipe_destination_options) 到别的插件中。 14 | 15 | ```javascript 16 | gulp.src('client/templates/*.jade') 17 | .pipe(jade()) 18 | .pipe(minify()) 19 | .pipe(gulp.dest('build/minified_templates')); 20 | ``` 21 | 22 | `glob` 请参考 [node-glob 语法](https://github.com/isaacs/node-glob) 或者,你也可以直接写文件的路径。 23 | 24 | #### globs 25 | 类型: `String` 或 `Array` 26 | 27 | 所读取的 glob 或者 glob 数组,除了 negation(`!`) 以外的 [node-glob 语法] 均被支持。 28 | 29 | 一个 `!` 开头的 glob 会在结果中排除掉到这个地方为止的所匹配到的文件。举个例子,考虑如下的目录结构: 30 | 31 | client/ 32 | a.js 33 | bob.js 34 | bad.js 35 | 36 | 下面的表达式匹配到的结果是 `a.js` 和 `bad.js`: 37 | 38 | gulp.src(['client/*.js', '!client/b*.js', 'client/bad.js']) 39 | 40 | #### options 41 | 类型: `Object` 42 | 43 | 通过 [glob-stream] 所传递给 [node-glob] 的参数。 44 | 45 | 除了 [node-glob][node-glob 文档] 和 [glob-stream] 所支持的参数(除了 `ignore`)外,gulp 增加了一些额外的选项参数: 46 | 47 | ##### options.buffer 48 | 类型: `Boolean` 49 | 默认值: `true` 50 | 51 | 如果该项被设置为 `false`,那么将会以 stream 方式返回 `file.contents` 而不是文件 buffer 的形式。这在处理一些大文件的时候将会很有用。**注意:**插件可能并不会实现对 stream 的支持。 52 | 53 | ##### options.read 54 | 类型: `Boolean` 55 | 默认值: `true` 56 | 57 | 如果该项被设置为 `false`, 那么 `file.contents` 会返回空值(null),也就是并不会去读取文件。 58 | 59 | ##### options.base 60 | 类型: `String` 61 | 默认值: 将会加在 glob 之前 (请看 [glob2base]) 62 | 63 | 如, 请想像一下在一个路径为 `client/js/somedir` 的目录中,有一个文件叫 `somefile.js` : 64 | 65 | ```js 66 | gulp.src('client/js/**/*.js') // 匹配 'client/js/somedir/somefile.js' 并且将 `base` 解析为 `client/js/` 67 | .pipe(minify()) 68 | .pipe(gulp.dest('build')); // 写入 'build/somedir/somefile.js' 69 | 70 | gulp.src('client/js/**/*.js', { base: 'client' }) 71 | .pipe(minify()) 72 | .pipe(gulp.dest('build')); // 写入 'build/js/somedir/somefile.js' 73 | ``` 74 | 75 | ### gulp.dest(path[, options]) 76 | 77 | 能被 pipe 进来,并且将会写文件。并且重新输出(emits)所有数据,因此你可以将它 pipe 到多个文件夹。如果某文件夹不存在,将会自动创建它。 78 | 79 | ```javascript 80 | gulp.src('./client/templates/*.jade') 81 | .pipe(jade()) 82 | .pipe(gulp.dest('./build/templates')) 83 | .pipe(minify()) 84 | .pipe(gulp.dest('./build/minified_templates')); 85 | ``` 86 | 87 | 文件被写入的路径是以所给的相对路径根据所给的目标目录计算而来。类似的,相对路径也可以根据所给的 base 来计算。 88 | 请查看上述的 `gulp.src` 来了解更多信息。 89 | 90 | #### path 91 | 类型: `String` or `Function` 92 | 93 | 文件将被写入的路径(输出目录)。也可以传入一个函数,在函数中返回相应路径,这个函数也可以由 [vinyl 文件实例](https://github.com/gulpjs/vinyl) 来提供。 94 | 95 | #### options 96 | 类型: `Object` 97 | 98 | ##### options.cwd 99 | 类型: `String` 100 | 默认值: `process.cwd()` 101 | 102 | 输出目录的 `cwd` 参数,只在所给的输出目录是相对路径时候有效。 103 | 104 | ##### options.mode 105 | 类型: `String` 106 | 默认值: `0777` 107 | 108 | 八进制权限字符,用以定义所有在输出目录中所创建的目录的权限。 109 | 110 | ### gulp.task(name [, deps] [, fn]) 111 | 112 | 定义一个使用 [Orchestrator] 实现的任务(task)。 113 | 114 | ```js 115 | gulp.task('somename', function() { 116 | // 做一些事 117 | }); 118 | ``` 119 | 120 | #### name 121 | 类型:`String` 122 | 123 | 任务的名字,如果你需要在命令行中运行你的某些任务,那么,请不要在名字中使用空格。 124 | 125 | #### deps 126 | 类型: `Array` 127 | 128 | 一个包含任务列表的数组,这些任务会在你当前任务运行之前完成。 129 | 130 | ```js 131 | gulp.task('mytask', ['array', 'of', 'task', 'names'], function() { 132 | // 做一些事 133 | }); 134 | ``` 135 | 136 | **注意:** 你的任务是否在这些前置依赖的任务完成之前运行了?请一定要确保你所依赖的任务列表中的任务都使用了正确的异步执行方式:使用一个 callback,或者返回一个 promise 或 stream。 137 | 138 | 你也可以省略最后那个函数,如果你只是想要执行依赖的任务: 139 | 140 | ```js 141 | gulp.task('mytask', ['array', 'of', 'task', 'names']); 142 | ``` 143 | 144 | **注意:** 这些任务会一次并发执行,因此,请不要假定他们会按顺序开始和结束。 145 | 146 | #### fn 147 | 类型:`Function` 148 | 149 | 该函数定义任务所要执行的主要操作。通常来说,它会是这种形式: 150 | 151 | ```js 152 | gulp.task('buildStuff', function() { 153 | // Do something that "builds stuff" 154 | var stream = gulp.src(/*some source path*/) 155 | .pipe(somePlugin()) 156 | .pipe(someOtherPlugin()) 157 | .pipe(gulp.dest(/*some destination*/)); 158 | 159 | return stream; 160 | }); 161 | ``` 162 | 163 | #### 异步任务支持 164 | 165 | 任务可以异步执行,如果 `fn` 能做到以下其中一点: 166 | 167 | ##### 接受一个 callback 168 | 169 | ```javascript 170 | // 在 shell 中运行一个命令 171 | var exec = require('child_process').exec; 172 | gulp.task('jekyll', function(cb) { 173 | // 构建 Jekyll 174 | exec('jekyll build', function(err) { 175 | if (err) return cb(err); // return error 176 | cb(); // 完成 task 177 | }); 178 | }); 179 | 180 | // 在 pipe 中使用异步的结果 181 | gulp.task('somename', function(cb) { 182 | getFilesAsync(function(err, res) { 183 | if (err) return cb(err); 184 | var stream = gulp.src(res) 185 | .pipe(minify()) 186 | .pipe(gulp.dest('build')) 187 | .on('end', cb); 188 | }); 189 | }); 190 | ``` 191 | 192 | ##### 返回一个 stream 193 | 194 | ```js 195 | gulp.task('somename', function() { 196 | var stream = gulp.src('client/**/*.js') 197 | .pipe(minify()) 198 | .pipe(gulp.dest('build')); 199 | return stream; 200 | }); 201 | ``` 202 | 203 | ##### 返回一个 promise 204 | 205 | ```javascript 206 | var Q = require('q'); 207 | 208 | gulp.task('somename', function() { 209 | var deferred = Q.defer(); 210 | 211 | // 执行异步的操作 212 | setTimeout(function() { 213 | deferred.resolve(); 214 | }, 1); 215 | 216 | return deferred.promise; 217 | }); 218 | ``` 219 | 220 | **注意:** 默认的,task 将以最大的并发数执行,也就是说,gulp 会一次性运行所有的 task 并且不做任何等待。如果你想要创建一个序列化的 task 队列,并以特定的顺序执行,你需要做两件事: 221 | 222 | - 给出一个提示,来告知 task 什么时候执行完毕, 223 | - 并且再给出一个提示,来告知一个 task 依赖另一个 task 的完成。 224 | 225 | 对于这个例子,让我们先假定你有两个 task,"one" 和 "two",并且你希望它们按照这个顺序执行: 226 | 227 | 1. 在 "one" 中,你加入一个提示,来告知什么时候它会完成:可以再完成时候返回一个 callback,或者返回一个 promise 或 stream,这样系统会去等待它完成。 228 | 229 | 2. 在 "two" 中,你需要添加一个提示来告诉系统它需要依赖第一个 task 完成。 230 | 231 | 因此,这个例子的实际代码将会是这样: 232 | 233 | ```js 234 | var gulp = require('gulp'); 235 | 236 | // 返回一个 callback,因此系统可以知道它什么时候完成 237 | gulp.task('one', function(cb) { 238 | // 做一些事 -- 异步的或者其他的 239 | cb(err); // 如果 err 不是 null 或 undefined,则会停止执行,且注意,这样代表执行失败了 240 | }); 241 | 242 | // 定义一个所依赖的 task 必须在这个 task 执行之前完成 243 | gulp.task('two', ['one'], function() { 244 | // 'one' 完成后 245 | }); 246 | 247 | gulp.task('default', ['one', 'two']); 248 | ``` 249 | 250 | 251 | ### gulp.watch(glob [, opts], tasks) 或 gulp.watch(glob [, opts, cb]) 252 | 253 | 监视文件,并且可以在文件发生改动时候做一些事情。它总会返回一个 EventEmitter 来发射(emit) `change` 事件。 254 | 255 | ### gulp.watch(glob[, opts], tasks) 256 | 257 | #### glob 258 | 类型: `String` or `Array` 259 | 260 | 一个 glob 字符串,或者一个包含多个 glob 字符串的数组,用来指定具体监控哪些文件的变动。 261 | 262 | #### opts 263 | 类型: `Object` 264 | 265 | 传给 [`gaze`](https://github.com/shama/gaze) 的参数。 266 | 267 | #### tasks 268 | 类型: `Array` 269 | 270 | 需要在文件变动后执行的一个或者多个通过 `gulp.task()` 创建的 task 的名字, 271 | 272 | ```js 273 | var watcher = gulp.watch('js/**/*.js', ['uglify','reload']); 274 | watcher.on('change', function(event) { 275 | console.log('File ' + event.path + ' was ' + event.type + ', running tasks...'); 276 | }); 277 | ``` 278 | 279 | ### gulp.watch(glob[, opts, cb]) 280 | 281 | #### glob 282 | 类型: `String` or `Array` 283 | 284 | 一个 glob 字符串,或者一个包含多个 glob 字符串的数组,用来指定具体监控哪些文件的变动。 285 | 286 | #### opts 287 | 类型: `Object` 288 | 289 | 传给 [`gaze`](https://github.com/shama/gaze) 的参数。 290 | 291 | #### cb(event) 292 | 类型: `Function` 293 | 294 | 每次变动需要执行的 callback。 295 | 296 | ```js 297 | gulp.watch('js/**/*.js', function(event) { 298 | console.log('File ' + event.path + ' was ' + event.type + ', running tasks...'); 299 | }); 300 | ``` 301 | 302 | callback 会被传入一个名为 `event` 的对象。这个对象描述了所监控到的变动: 303 | 304 | ##### event.type 305 | 类型: `String` 306 | 307 | 发生的变动的类型:`added`, `changed`, `deleted` 或者 `renamed`。 308 | 309 | ##### event.path 310 | 类型: `String` 311 | 312 | 触发了该事件的文件的路径。 313 | 314 | [node-glob]: https://github.com/isaacs/node-glob 315 | [node-glob 文档]: https://github.com/isaacs/node-glob#options 316 | [node-glob 语法]: https://github.com/isaacs/node-glob 317 | [gulp-if]: https://github.com/robrich/gulp-if 318 | [Orchestrator]: https://github.com/robrich/orchestrator 319 | [glob2base]: https://github.com/wearefractal/glob2base 320 | -------------------------------------------------------------------------------- /CLI.md: -------------------------------------------------------------------------------- 1 | ## gulp 命令行(CLI)文档 2 | 3 | ### 参数标记 4 | 5 | gulp 只有你需要熟知的参数标记,其他所有的参数标记只在一些任务需要的时候使用。 6 | 7 | - `-v` 或 `--version` 会显示全局和项目本地所安装的 gulp 版本号 8 | - `--require ` 将会在执行之前 reqiure 一个模块。这对于一些语言编译器或者需要其他应用的情况来说来说很有用。你可以使用多个`--require` 9 | - `--gulpfile ` 手动指定一个 gulpfile 的路径,这在你有很多个 gulpfile 的时候很有用。这也会将 CWD 设置到该 gulpfile 所在目录 10 | - `--cwd ` 手动指定 CWD。定义 gulpfile 查找的位置,此外,所有的相应的依赖(require)会从这里开始计算相对路径 11 | - `-T` 或 `--tasks` 会显示所指定 gulpfile 的 task 依赖树 12 | - `--tasks-simple` 会以纯文本的方式显示所载入的 gulpfile 中的 task 列表 13 | - `--color` 强制 gulp 和 gulp 插件显示颜色,即便没有颜色支持 14 | - `--no-color` 强制不显示颜色,即便检测到有颜色支持 15 | - `--silent` 禁止所有的 gulp 日志 16 | 17 | 命令行会在 process.env.INIT_CW 中记录它是从哪里被运行的。 18 | 19 | #### Task 特定的参数标记 20 | 21 | 请参考 [StackOverflow](http://stackoverflow.com/questions/23023650/is-it-possible-to-pass-a-flag-to-gulp-to-have-it-run-tasks-in-different-ways) 了解如何增加任务特定的参数标记。 22 | 23 | ### Tasks 24 | 25 | Task 可以通过 `gulp ` 方式来执行。如果只运行 `gulp` 命令,则会执行所注册的名为 `default` 的 task,如果没有这个 task,那么 gulp 会报错。 26 | 27 | ### 编译器 28 | 29 | 你可以在 [interpret](https://github.com/tkellen/node-interpret#jsvariants) 找到所支持的语言列表。如果你想要增加一个语言的支持,请在这里提交一个 pull request 或者 issue。 30 | -------------------------------------------------------------------------------- /FAQ.md: -------------------------------------------------------------------------------- 1 | # FAQ 2 | 3 | ## 为什用 gulp 而不是 ____? 4 | 5 | 请先看 [gulp 介绍幻灯片] 来大致了解下 gulp 是怎么来的。 6 | 7 | ## 是 "gulp" 还是 "Gulp"? 8 | 9 | gulp 一直都是小写的。除了在 gulp 的 logo 中是用大写的。 10 | 11 | ## 去哪里可以找到 gulp 插件的列表? 12 | 13 | gulp 插件总是会包含 `gulpplugin` 关键字。在这[搜索 gulp 插件][search-gulp-plugins] 或者 在 npm [查看所有插件][npm plugin search]。 14 | 15 | ## 我想写一个 gulp 插件,我应该从哪里开始呢? 16 | 17 | 请查看 [编写插件] wiki 页面来阅读一些指导以及一些例子。 18 | 19 | ## 我的插件将做 ____, 它是不是做的太多了? 20 | 21 | 有可能。可以先自问下: 22 | 23 | 1. 我的插件是否做了一些其他插件可能需要做的事情? 24 | - 如果是,那么那一段功能应该作为一个独立的插件。[查看是否已经有相应的插件存在了][npm plugin search]. 25 | 1. 我的插件是否做了两件事,两件根据配置的不同而截然不同的事情? 26 | - 如果是,那么为了社区的良好发展,最好是分开为两个插件发布 27 | - 如果两个任务是不同的,但是差别非常细微,那实际上是允许的 28 | 29 | ## 换行符在插件输出中应该如何表示? 30 | 31 | 请总是使用 `\n` 以避免不同的操作系统带来的兼容性问题。 32 | 33 | ## 我可以从哪里获取 gulp 的最新信息? 34 | 35 | gulp 的更新信息可以通过关注以下的 twitter 来获取: 36 | 37 | - [@wearefractal](https://twitter.com/wearefractal) 38 | - [@eschoff](https://twitter.com/eschoff) 39 | - [@gulpjs](https://twitter.com/gulpjs) 40 | 41 | ## gulp 是否有 IRC 频道? 42 | 43 | 有的,欢迎来 [Freenode] 上的 #gulpjs 来交流。 44 | 45 | [编写插件]: writing-a-plugin/README.md 46 | [gulp 介绍幻灯片]: http://slid.es/contra/gulp 47 | [Freenode]: http://freenode.net/ 48 | [search-gulp-plugins]: http://gulpjs.com/plugins/ 49 | [npm plugin search]: https://npmjs.org/browse/keyword/gulpplugin 50 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # [gulp](https://github.com/gulpjs/gulp) 简体中文文档 2 | 3 | * [入门指南](getting-started.md) - 如何开始使用 gulp 4 | * [API 文档](API.md) - 学习 gulp 的输入和输出方式 5 | * [CLI 文档](CLI.md) - 学习如何执行任务(task)以及如何使用一些编译工具 6 | * [编写插件](writing-a-plugin/README.md) - 所以,你已经在写一个 gulp 插件了么? 去这儿看一些基本文档,并了解下什么样的事情不应该做 7 | * [英文文档][EnglishDocs] - gulp Elglish documentation. 8 | * [西班牙语文档][SpanishDocs] - gulp en Español. 9 | * [韩文文档][KoreanDocs] - gulp 한국어 참조 문서. 10 | 11 | ## 常见问题 12 | 13 | 常见问题,请查看 [FAQ](FAQ.md)。 14 | 15 | 16 | ## 秘籍 17 | 18 | 社区中对于常用的一些 gulp 应用场景已经有了一些现成的秘籍 [recipes](recipes#recipes) 19 | 20 | 21 | ## 还是有问题? 22 | 23 | 到 [StackOverflow](http://stackoverflow.com/questions/tagged/gulp) 发一个带有 #gulp 标签的问题,或者在 [Freenode](http://freenode.net/) 上的 [#gulpjs](http://webchat.freenode.net/?channels=gulpjs) IRC 频道寻求帮助。 24 | 25 | 26 | ## 书籍 27 | * [Developing a gulp Edge](http://shop.oreilly.com/product/9781939902146.do) 28 | 29 | 30 | ## 文章(英文) 31 | * [Tagtree intro to gulp video](http://tagtree.io/gulp) 32 | * [Introduction to node.js streams](https://github.com/substack/stream-handbook) 33 | * [Video introduction to node.js streams](http://www.youtube.com/watch?v=QgEuZ52OZtU) 34 | * [Getting started with gulp (by @markgdyr)](http://markgoodyear.com/2014/01/getting-started-with-gulp/) 35 | * [A cheatsheet for gulp](https://github.com/osscafe/gulp-cheatsheet) 36 | * [Why you shouldn’t create a gulp plugin (or, how to stop worrying and learn to love existing node packages)](http://blog.overzealous.com/post/74121048393/why-you-shouldnt-create-a-gulp-plugin-or-how-to-stop) 37 | * [Inspiration (slides) about why gulp was made](http://slid.es/contra/gulp) 38 | * [Building With Gulp](http://www.smashingmagazine.com/2014/06/11/building-with-gulp/) 39 | * [Gulp - The Basics (screencast)](https://www.youtube.com/watch?v=dwSLFai8ovQ) 40 | * [Get started with gulp (video series)](http://www.youtube.com/playlist?list=PLRk95HPmOM6PN-G1xyKj9q6ap_dc9Yckm) 41 | * [Optimize your web code with gulp](http://www.linuxuser.co.uk/tutorials/optimise-your-web-code-with-gulp-js) 42 | * [Automate Your Tasks Easily with Gulp.js ](https://scotch.io/tutorials/automate-your-tasks-easily-with-gulp-js) 43 | 44 | 45 | ## 例子 46 | 47 | - [Web Starter Kit gulpfile](https://github.com/google/web-starter-kit/blob/master/gulpfile.babel.js) 48 | 49 | 50 | ## License 51 | 52 | All the documentation is covered by the CC0 license *(do whatever you want with it - public domain)*. 53 | 54 | [![CC0](http://i.creativecommons.org/p/zero/1.0/88x31.png)](http://creativecommons.org/publicdomain/zero/1.0/) 55 | 56 | To the extent possible under law, [Fractal](http://wearefractal.com) has waived all copyright and related or neighboring rights to this work. 57 | 58 | [EnglishDocs]: https://github.com/gulpjs/gulp/tree/master/docs 59 | [SpanishDocs]: https://github.com/bucaran/gulp-docs-es 60 | [KoreanDocs]: https://github.com/preco21/gulp-docs-ko 61 | -------------------------------------------------------------------------------- /getting-started.md: -------------------------------------------------------------------------------- 1 | # 入门指南 2 | 3 | #### 1. 全局安装 gulp: 4 | 5 | __如果你之前有全局安装过一个版本的 gulp,请执行一下 `npm rm --global gulp` 来避免和 gulp-cli 冲突__ 6 | 7 | ```sh 8 | $ npm install --global gulp-cli 9 | ``` 10 | 11 | #### 2. 作为项目的开发依赖(devDependencies)安装: 12 | 13 | ```sh 14 | $ npm install --save-dev gulp 15 | ``` 16 | 17 | #### 3. 在项目根目录下创建一个名为 `gulpfile.js` 的文件: 18 | 19 | ```js 20 | var gulp = require('gulp'); 21 | 22 | gulp.task('default', function() { 23 | // 将你的默认的任务代码放在这 24 | }); 25 | ``` 26 | 27 | #### 4. 运行 gulp: 28 | 29 | ```sh 30 | $ gulp 31 | ``` 32 | 33 | 默认的名为 default 的任务(task)将会被运行,在这里,这个任务并未做任何事情。 34 | 35 | 想要单独执行特定的任务(task),请输入 `gulp `。 36 | 37 | ## 下一步做什么呢? 38 | 39 | 你已经安装了所有必要的东西,并且拥有了一个空的 gulpfile。那怎样才算是__真的__入门了呢?可以查看这些 [秘籍](recipes) 和这个 [文章列表](README.md#articles) 来学习更多的内容。 40 | 41 | ## .src, .watch, .dest, CLI 参数 - 我该怎么去用这些东西呢? 42 | 43 | 要了解 API 规范文档,请查看 [API 文档](API.md). 44 | 45 | ## 可用的插件 46 | 47 | gulp 开发社区正在快速成长,每天都会有新的插件诞生。在 [主站](http://gulpjs.com/plugins/) 上可以查看完整的列表。 48 | 49 | -------------------------------------------------------------------------------- /recipes/README.md: -------------------------------------------------------------------------------- 1 | # Recipes 2 | 3 | * [自动发布工作流](automate-release-workflow.md) 4 | * [整合 streams 来处理错误](combining-streams-to-handle-errors.md) 5 | * [删除文件和文件夹](delete-files-folder.md) 6 | * [使用 watchify 加速 browserify 编译](fast-browserify-builds-with-watchify.md) 7 | * [增量编译打包,包括处理整所涉及的所有文件](incremental-builds-with-concatenate.md) 8 | * [将 buffer 变为 stream (内存中的内容)](make-stream-from-buffer.md) 9 | * [在 gulp 中运行 Mocha 测试](mocha-test-runner-with-gulp.md) 10 | * [仅仅传递更改过的文件](only-pass-through-changed-files.md) 11 | * [从命令行传递参数](pass-arguments-from-cli.md) 12 | * [只重新编译被更改过的文件](rebuild-only-files-that-change.md) 13 | * [每个文件夹生成单独一个文件](running-task-steps-per-folder.md) 14 | * [串行方式运行任务](running-tasks-in-series.md) 15 | * [拥有实时重载(live-reloading)和 CSS 注入的服务器](server-with-livereload-and-css-injection.md) 16 | * [通过 stream 工厂来共享 stream](sharing-streams-with-stream-factories.md) 17 | * [指定一个新的 cwd (当前工作目录)](specifying-a-cwd.md) 18 | * [分离任务到多个文件中](split-tasks-across-multiple-files.md) 19 | * [使用外部配置文件](using-external-config-file.md) 20 | * [在一个任务中使用多个文件来源](using-multiple-sources-in-one-task.md) 21 | * [Browserify + Uglify2 和 sourcemaps](browserify-uglify-sourcemap.md) 22 | * [Browserify + Globs](browserify-with-globs.md) 23 | * [Browserify + Globs (多个目标路径)](browserify-multiple-destination.md) 24 | * [同时输出一个压缩过和一个未压缩版本的文件](minified-and-non-minified.md) 25 | * [Swig 以及 YAML front-matter 模板](templating-with-swig-and-yaml-front-matter.md) 26 | * [在 Gulp 中执行 Grunt 任务](run-grunt-tasks-from-gulp.md) 27 | * [使用 ES2015 的 export 来写 tasks](exports-as-tasks.md) 28 | -------------------------------------------------------------------------------- /recipes/automate-release-workflow.md: -------------------------------------------------------------------------------- 1 | # 自动化发布工作流 2 | 3 | 如果你的项目遵循语义化版本规则,那么将发布版本需要做的一些事情自动化处理,将会是个很不错的主意。 4 | 下面有一个简单的秘籍,包含了更新版本号,提交(commit)更改到 git 以及创建一个 tag。 5 | 6 | ``` javascript 7 | 8 | var gulp = require('gulp'); 9 | var runSequence = require('run-sequence'); 10 | var conventionalChangelog = require('gulp-conventional-changelog'); 11 | var conventionalGithubReleaser = require('conventional-github-releaser'); 12 | var bump = require('gulp-bump'); 13 | var gutil = require('gulp-util'); 14 | var git = require('gulp-git'); 15 | var fs = require('fs'); 16 | 17 | gulp.task('changelog', function () { 18 | return gulp.src('CHANGELOG.md', { 19 | buffer: false 20 | }) 21 | .pipe(conventionalChangelog({ 22 | preset: 'angular' // 或者其他任何你在使用的 commit 信息规范 23 | })) 24 | .pipe(gulp.dest('./')); 25 | }); 26 | 27 | gulp.task('github-release', function(done) { 28 | conventionalGithubReleaser({ 29 | type: "oauth", 30 | token: '0126af95c0e2d9b0a7c78738c4c00a860b04acc8' // 将这个更改至你自己的 GitHub token,或者使用一个环境变量 31 | }, { 32 | preset: 'angular' // 或者其他任何你在使用的 commit 信息规范 33 | }, done); 34 | }); 35 | 36 | gulp.task('bump-version', function () { 37 | // 这里我们硬编码了版本更新类型为 'patch',但是更好的方式是 38 | // 使用 (https://www.npmjs.com/package/minimist) 模块,定义一个 39 | // 命令行参数来确定你是需要 'major','minor',还是 'patch' 类型的更新 40 | return gulp.src(['./bower.json', './package.json']) 41 | .pipe(bump({type: "patch"}).on('error', gutil.log)) 42 | .pipe(gulp.dest('./')); 43 | }); 44 | 45 | gulp.task('commit-changes', function () { 46 | return gulp.src('.') 47 | .pipe(git.commit('[Prerelease] Bumped version number')); 48 | }); 49 | 50 | gulp.task('push-changes', function (cb) { 51 | git.push('origin', 'master', cb); 52 | }); 53 | 54 | gulp.task('create-new-tag', function (cb) { 55 | var version = getPackageJsonVersion(); 56 | git.tag(version, 'Created Tag for version: ' + version, function (error) { 57 | if (error) { 58 | return cb(error); 59 | } 60 | git.push('origin', 'master', {args: '--tags'}, cb); 61 | }); 62 | 63 | function getPackageJsonVersion () { 64 | // 我们自己解析这个 json 文件,因为使用 require 会有缓存 65 | // 在多次调用的时候,版本号并不会改变 66 | return JSON.parse(fs.readFileSync('./package.json', 'utf8')).version; 67 | }; 68 | }); 69 | 70 | gulp.task('release', function (callback) { 71 | runSequence( 72 | 'bump-version', 73 | 'changelog', 74 | 'commit-changes', 75 | 'push-changes', 76 | 'create-new-tag', 77 | 'github-release', 78 | function (error) { 79 | if (error) { 80 | console.log(error.message); 81 | } else { 82 | console.log('RELEASE FINISHED SUCCESSFULLY'); 83 | } 84 | callback(error); 85 | }); 86 | }); 87 | 88 | ``` 89 | -------------------------------------------------------------------------------- /recipes/browserify-multiple-destination.md: -------------------------------------------------------------------------------- 1 | # Browserify + Globs (多个目标路径) 2 | 3 | 这个例子用来展示如何设置一个使用 browserify 打包多个输入并且有多个输出目的地的任务。 4 | 5 | 下边的 `js` 任务会打包所有在 `src/` 目录下所有的 `.js` 文件,然后输出每一个对应的文件到 `dest/` 目录。 6 | 7 | 8 | ```js 9 | var gulp = require('gulp'); 10 | var browserify = require('browserify'); 11 | var gutil = require('gulp-util'); 12 | var tap = require('gulp-tap'); 13 | var buffer = require('gulp-buffer'); 14 | var sourcemaps = require('gulp-sourcemaps'); 15 | var uglify = require('gulp-uglify'); 16 | 17 | gulp.task('js', function () { 18 | 19 | return gulp.src('src/**/*.js', {read: false}) // 不需要读取文件内容,browserify 会处理这个问题 20 | 21 | // 使用 gulp-tap 转换文件内容 22 | .pipe(tap(function (file) { 23 | 24 | gutil.log('bundling ' + file.path); 25 | 26 | // 使用 browserify 的打包 stream 来替换文件 27 | file.contents = browserify(file.path, {debug: true}).bundle(); 28 | 29 | })) 30 | 31 | // 转换 stram 内容为 buff 内容(因为 gulp-sourcemaps 不支持 stream 形式的内容) 32 | .pipe(buffer()) 33 | 34 | // 载入并初始化 sourcemaps 35 | .pipe(sourcemaps.init({loadMaps: true})) 36 | 37 | .pipe(uglify()) 38 | 39 | // 写入 sourcemaps 40 | .pipe(sourcemaps.write('./')) 41 | 42 | .pipe(gulp.dest('dest')); 43 | 44 | }); 45 | ``` 46 | -------------------------------------------------------------------------------- /recipes/browserify-transforms.md: -------------------------------------------------------------------------------- 1 | # Browserify + Transforms 2 | 3 | [Browserify](http://github.com/substack/node-browserify) 现在已经成为了一个不可或缺的重要工具了,然后要让它能完美的和 gulp 一起协作,还得需要做一些封装处理。下面便是一个使用 Browserify 和 Transforms 的例子。 4 | 5 | 同时请看: [组合 Streams 来处理错误](https://github.com/gulpjs/gulp/blob/master/docs/recipes/combining-streams-to-handle-errors.md) 范例来查看如何处理你的 stream 中 browserify 或者 uglify 的错误。 6 | 7 | ``` javascript 8 | 'use strict'; 9 | 10 | var browserify = require('browserify'); 11 | var gulp = require('gulp'); 12 | var source = require('vinyl-source-stream'); 13 | var buffer = require('vinyl-buffer'); 14 | var gutil = require('gulp-util'); 15 | var uglify = require('gulp-uglify'); 16 | var sourcemaps = require('gulp-sourcemaps'); 17 | var reactify = require('reactify'); 18 | 19 | gulp.task('javascript', function () { 20 | // 在一个基础的 task 中创建一个 browserify 实例 21 | var b = browserify({ 22 | entries: './entry.js', 23 | debug: true, 24 | // 在这里定义 transforms 能避免让你的 stream 崩溃 25 | transform: [reactify] 26 | }); 27 | 28 | return b.bundle() 29 | .pipe(source('app.js')) 30 | .pipe(buffer()) 31 | .pipe(sourcemaps.init({loadMaps: true})) 32 | // 在这里将转换任务加入管道 33 | .pipe(uglify()) 34 | .on('error', gutil.log) 35 | .pipe(sourcemaps.write('./')) 36 | .pipe(gulp.dest('./dist/js/')); 37 | }); 38 | ``` 39 | -------------------------------------------------------------------------------- /recipes/browserify-uglify-sourcemap.md: -------------------------------------------------------------------------------- 1 | # Browserify + Uglify2 和 sourcemaps 2 | 3 | [Browserify](http://github.com/substack/node-browserify) 现在已经成为了一个不可或缺的重要工具了,然后要让它能完美的和 gulp 一起协作,还得需要做一些封装处理。先面便是一个使用 Browserify 并且增加一个完整的 sourcemap 来对应到单独的每个源文件。 4 | 5 | 同时请看: [组合 Streams 来处理错误](https://github.com/gulpjs/gulp/blob/master/docs/recipes/combining-streams-to-handle-errors.md) 范例来查看如何处理你的 stream 中 browserify 或者 uglify 的错误。 6 | 7 | ``` javascript 8 | 'use strict'; 9 | 10 | var browserify = require('browserify'); 11 | var gulp = require('gulp'); 12 | var source = require('vinyl-source-stream'); 13 | var buffer = require('vinyl-buffer'); 14 | var uglify = require('gulp-uglify'); 15 | var sourcemaps = require('gulp-sourcemaps'); 16 | var gutil = require('gulp-util'); 17 | 18 | gulp.task('javascript', function () { 19 | // 在一个基础的 task 中创建一个 browserify 实例 20 | var b = browserify({ 21 | entries: './entry.js', 22 | debug: true 23 | }); 24 | 25 | return b.bundle() 26 | .pipe(source('app.js')) 27 | .pipe(buffer()) 28 | .pipe(sourcemaps.init({loadMaps: true})) 29 | // 在这里将转换任务加入管道 30 | .pipe(uglify()) 31 | .on('error', gutil.log) 32 | .pipe(sourcemaps.write('./')) 33 | .pipe(gulp.dest('./dist/js/')); 34 | }); 35 | ``` 36 | -------------------------------------------------------------------------------- /recipes/browserify-with-globs.md: -------------------------------------------------------------------------------- 1 | # Browserify + Globs 2 | 3 | [Browserify + Uglify2](https://github.com/gulpjs/gulp/blob/master/docs/recipes/browserify-uglify-sourcemap.md) 展示了如何设置一个基础的 gulp 任务来把一个 JavaScript 文件以及它的依赖打包,并且使用 UglifyJS 压缩并且保留 source map。 4 | 然而这还不够,这里还将会展示如何使用 gulp 和 Browserify 将多个文件打包到一起。 5 | 6 | 同时请看: [组合 Streams 来处理错误](https://github.com/gulpjs/gulp/blob/master/docs/recipes/combining-streams-to-handle-errors.md) 范例来查看如何处理你的 stream 中 browserify 或者 uglify 的错误。 7 | 8 | ``` javascript 9 | 'use strict'; 10 | 11 | var browserify = require('browserify'); 12 | var gulp = require('gulp'); 13 | var source = require('vinyl-source-stream'); 14 | var buffer = require('vinyl-buffer'); 15 | var globby = require('globby'); 16 | var through = require('through2'); 17 | var gutil = require('gulp-util'); 18 | var uglify = require('gulp-uglify'); 19 | var sourcemaps = require('gulp-sourcemaps'); 20 | var reactify = require('reactify'); 21 | 22 | gulp.task('javascript', function () { 23 | // gulp 希望任务能返回一个 stream,因此我们在这里创建一个 24 | var bundledStream = through(); 25 | 26 | bundledStream 27 | // 将输出的 stream 转化成为一个包含 gulp 插件所期许的一些属性的 stream 28 | .pipe(source('app.js')) 29 | // 剩下的部分,和你往常缩写的一样。 30 | // 这里我们直接拷贝 Browserify + Uglify2 范例的代码。 31 | .pipe(buffer()) 32 | .pipe(sourcemaps.init({loadMaps: true})) 33 | // 在这里将相应 gulp 插件加入管道 34 | .pipe(uglify()) 35 | .on('error', gutil.log) 36 | .pipe(sourcemaps.write('./')) 37 | .pipe(gulp.dest('./dist/js/')); 38 | 39 | // "globby" 替换了往常的 "gulp.src" 为 Browserify 40 | // 创建的可读 stream。 41 | globby(['./entries/*.js']).then(function(entries) { 42 | 43 | // 创建 Browserify 实例 44 | var b = browserify({ 45 | entries: entries, 46 | debug: true, 47 | transform: [reactify] 48 | }); 49 | 50 | // 将 Browserify stream 接入到我们之前创建的 stream 中去 51 | // 这里是 gulp 式管道正式开始的地方 52 | b.bundle().pipe(bundledStream); 53 | }).catch(function(err) { 54 | // ensure any errors from globby are handled 55 | bundledStream.emit('error', err); 56 | }); 57 | 58 | // 最后,我们返回这个 stream,这样 gulp 会知道什么时候这个任务会完成 59 | return bundledStream; 60 | }); 61 | ``` 62 | -------------------------------------------------------------------------------- /recipes/combining-streams-to-handle-errors.md: -------------------------------------------------------------------------------- 1 | # 整合 streams 来处理错误 2 | 3 | 默认情况下,在 stream 中发生一个错误的话,它会被直接抛出,除非已经有一个事件监听器监听着 `error` 事件。 这在处理一个比较长的管道操作的时候会显得比较棘手。 4 | 5 | 通过使用 [stream-combiner2](https://github.com/substack/stream-combiner2),你可以将一系列的 stream 合并成一个,这意味着,你只需要在你的代码中一个地方添加监听器监听 `error` 事件就可以了。 6 | 7 | 这里是一个在 gulpfile 中使用它的例子: 8 | 9 | ```js 10 | var combiner = require('stream-combiner2'); 11 | var uglify = require('gulp-uglify'); 12 | var gulp = require('gulp'); 13 | 14 | gulp.task('test', function() { 15 | var combined = combiner.obj([ 16 | gulp.src('bootstrap/js/*.js'), 17 | uglify(), 18 | gulp.dest('public/bootstrap') 19 | ]); 20 | 21 | // 任何在上面的 stream 中发生的错误,都不会抛出, 22 | // 而是会被监听器捕获 23 | combined.on('error', console.error.bind(console)); 24 | 25 | return combined; 26 | }); 27 | ``` 28 | -------------------------------------------------------------------------------- /recipes/delete-files-folder.md: -------------------------------------------------------------------------------- 1 | 2 | # 删除文件和文件夹 3 | 4 | 你也许会想要在编译文件之前删除一些文件。由于删除文件和文件内容并没有太大关系,所以,我们没必要去用一个 gulp 插件。最好的一个选择就是使用一个原生的 node 模块。 5 | 6 | 因为 [`del`](https://github.com/sindresorhus/del) 模块支持多个文件以及 [globbing](https://github.com/sindresorhus/multimatch#globbing-patterns),因此,在这个例子中,我们将使用它来删除文件: 7 | 8 | ```sh 9 | $ npm install --save-dev gulp del 10 | ``` 11 | 12 | 假想有如下的文件结构: 13 | 14 | ``` 15 | . 16 | ├── dist 17 | │ ├── report.csv 18 | │ ├── desktop 19 | │ └── mobile 20 | │ ├── app.js 21 | │ ├── deploy.json 22 | │ └── index.html 23 | └── src 24 | ``` 25 | 26 | 在 gulpfile 中,我们希望在运行我们的编译任务之前,将 `mobile` 文件的内容先清理掉: 27 | 28 | ```js 29 | var gulp = require('gulp'); 30 | var del = require('del'); 31 | 32 | gulp.task('clean:mobile', function () { 33 | return del([ 34 | 'dist/report.csv', 35 | // 这里我们使用一个通配模式来匹配 `mobile` 文件夹中的所有东西 36 | 'dist/mobile/**/*', 37 | // 我们不希望删掉这个文件,所以我们取反这个匹配模式 38 | '!dist/mobile/deploy.json' 39 | ]); 40 | }); 41 | 42 | gulp.task('default', ['clean:mobile']); 43 | ``` 44 | 45 | 46 | ## 在管道中删除文件 47 | 48 | 你可能需要在管道中将一些处理过的文件删除掉。 49 | 50 | 我们使用 [vinyl-paths](https://github.com/sindresorhus/vinyl-paths) 模块来简单地获取 stream 中每个文件的路径,然后传给 `del` 方法。 51 | 52 | ```sh 53 | $ npm install --save-dev gulp del vinyl-paths 54 | ``` 55 | 56 | 假想有如下的文件结构: 57 | 58 | ``` 59 | . 60 | ├── tmp 61 | │ ├── rainbow.js 62 | │ └── unicorn.js 63 | └── dist 64 | ``` 65 | 66 | ```js 67 | var gulp = require('gulp'); 68 | var stripDebug = require('gulp-strip-debug'); // 仅用于本例做演示 69 | var del = require('del'); 70 | var vinylPaths = require('vinyl-paths'); 71 | 72 | gulp.task('clean:tmp', function () { 73 | return gulp.src('tmp/*') 74 | .pipe(stripDebug()) 75 | .pipe(gulp.dest('dist')) 76 | .pipe(vinylPaths(del)); 77 | }); 78 | 79 | gulp.task('default', ['clean:tmp']); 80 | ``` 81 | 82 | 只有在已经使用了其他的插件之后才需要这样做,否则,请直接使用 `gulp.src` 来代替。 83 | -------------------------------------------------------------------------------- /recipes/exports-as-tasks.md: -------------------------------------------------------------------------------- 1 | # 使用 ES2015 的 export 来写 tasks 2 | 3 | 使用 ES2015 模块语法,你可以使用 export 来写 task。 4 | 5 | ```js 6 | import gulp from 'gulp'; 7 | import babel from 'gulp-babel'; 8 | 9 | // 命名的 task 10 | export function build() { 11 | return gulp.src('src/*.js') 12 | .pipe(babel()) 13 | .pipe(gulp.dest('lib')); 14 | } 15 | 16 | // 默认 task 17 | export default function dev() { 18 | gulp.watch('src/*.js', ['build']); 19 | } 20 | ``` 21 | 22 | 这个写法 **无法** 在 gulp 3.x 所打包的 gulp-cli 上工作。你必须使用最新版来尝试这个功能。 23 | -------------------------------------------------------------------------------- /recipes/fast-browserify-builds-with-watchify.md: -------------------------------------------------------------------------------- 1 | # 使用 watchify 加速 browserify 编译 2 | 3 | 当一个 [browserify](http://github.com/substack/node-browserify) 项目开始变大的时候,编译打包的时间也会慢慢变得长起来。虽然开始的时候可能只需花 1 秒,然后当你的项目需要建立在一些流行的大型项目的基础上时,它很有可能就变成 30 秒了。 4 | 5 | 这就是为什么 [substack](http://github.com/substack) 写了 [watchify](http://github.com/substack/watchify) 的原因,一个持续监视文件的改动,并且 *只重新打包必要的文件* 的 browserify 打包工具。用这种方法,第一次打包的时候可能会还是会花 30 秒,但是后续的编译打包工作将一直保持在 100 毫秒以下 —— 这是一个极大的提升。 6 | 7 | watchify 并没有一个相应的 gulp 插件,并且也不需要有:你可以使用 [vinyl-source-stream](http://github.com/hughsk/vinyl-source-stream) 来把你的用于打包的 stream 连接到 gulp 管道中。 8 | 9 | ``` javascript 10 | 'use strict'; 11 | 12 | var watchify = require('watchify'); 13 | var browserify = require('browserify'); 14 | var gulp = require('gulp'); 15 | var source = require('vinyl-source-stream'); 16 | var buffer = require('vinyl-buffer'); 17 | var gutil = require('gulp-util'); 18 | var sourcemaps = require('gulp-sourcemaps'); 19 | var assign = require('lodash.assign'); 20 | 21 | // 在这里添加自定义 browserify 选项 22 | var customOpts = { 23 | entries: ['./src/index.js'], 24 | debug: true 25 | }; 26 | var opts = assign({}, watchify.args, customOpts); 27 | var b = watchify(browserify(opts)); 28 | 29 | // 在这里加入变换操作 30 | // 比如: b.transform(coffeeify); 31 | 32 | gulp.task('js', bundle); // 这样你就可以运行 `gulp js` 来编译文件了 33 | b.on('update', bundle); // 当任何依赖发生改变的时候,运行打包工具 34 | b.on('log', gutil.log); // 输出编译日志到终端 35 | 36 | function bundle() { 37 | return b.bundle() 38 | // 如果有错误发生,记录这些错误 39 | .on('error', gutil.log.bind(gutil, 'Browserify Error')) 40 | .pipe(source('bundle.js')) 41 | // 可选项,如果你不需要缓存文件内容,就删除 42 | .pipe(buffer()) 43 | // 可选项,如果你不需要 sourcemaps,就删除 44 | .pipe(sourcemaps.init({loadMaps: true})) // 从 browserify 文件载入 map 45 | // 在这里将变换操作加入管道 46 | .pipe(sourcemaps.write('./')) // 写入 .map 文件 47 | .pipe(gulp.dest('./dist')); 48 | } 49 | ``` 50 | -------------------------------------------------------------------------------- /recipes/handling-the-delete-event-on-watch.md: -------------------------------------------------------------------------------- 1 | # 在监控时处理文件删除这个事件 2 | 3 | 你可以通过监听来自 `gulp.watch` 的 `'change'` 事件来触发相应的监视器函数。 4 | 5 | 每一个 change 事件都有一个 `type` 事件。如果 `type` 是 `'deleted'`,你可以同时把你的目标文件夹中的对应文件也删除掉,可以是使用如下方法: 6 | 7 | ```js 8 | 'use strict'; 9 | 10 | var del = require('del'); 11 | var path = require('path'); 12 | var gulp = require('gulp'); 13 | var header = require('gulp-header'); 14 | var footer = require('gulp-footer'); 15 | 16 | gulp.task('scripts', function() { 17 | return gulp.src('src/**/*.js', {base: 'src'}) 18 | .pipe(header('(function () {\r\n\t\'use strict\'\r\n')) 19 | .pipe(footer('\r\n})();')) 20 | .pipe(gulp.dest('build')); 21 | }); 22 | 23 | gulp.task('watch', function () { 24 | var watcher = gulp.watch('src/**/*.js', ['scripts']); 25 | 26 | watcher.on('change', function (event) { 27 | if (event.type === 'deleted') { 28 | // 模拟在 scripts 任务中 gulp.src 所用的 {base: 'src'} 29 | var filePathFromSrc = path.relative(path.resolve('src'), event.path); 30 | 31 | // 拼接在 scripts 任务重 gulp.dest 所用的 'build' 绝对路径 32 | var destFilePath = path.resolve('build', filePathFromSrc); 33 | 34 | del.sync(destFilePath); 35 | } 36 | }); 37 | }); 38 | ``` 39 | -------------------------------------------------------------------------------- /recipes/incremental-builds-with-concatenate.md: -------------------------------------------------------------------------------- 1 | # 增量编译打包,包括处理整所涉及的所有文件 2 | 3 | 在做增量编译打包的时候,有一个比较麻烦的事情,那就是你常常希望操作的是 _所有_ 处理过的文件,而不仅仅是单个的文件。举个例子,你想要只对更改的文件做代码 lint 操作,以及一些模块封装的操作,然后将他们与其他已经 lint 过的,以及已经进行过模块封装的文件合并到一起。如果不用到临时文件的话,这将会非常困难。 4 | 5 | 使用 [gulp-cached](https://github.com/wearefractal/gulp-cached) 以及 [gulp-remember](https://github.com/ahaurw01/gulp-remember) 来解决这个问题。 6 | 7 | ```js 8 | var gulp = require('gulp'); 9 | var header = require('gulp-header'); 10 | var footer = require('gulp-footer'); 11 | var concat = require('gulp-concat'); 12 | var jshint = require('gulp-jshint'); 13 | var cached = require('gulp-cached'); 14 | var remember = require('gulp-remember'); 15 | 16 | var scriptsGlob = 'src/**/*.js'; 17 | 18 | gulp.task('scripts', function() { 19 | return gulp.src(scriptsGlob) 20 | .pipe(cached('scripts')) // 只传递更改过的文件 21 | .pipe(jshint()) // 对这些更改过的文件做一些特殊的处理... 22 | .pipe(header('(function () {')) // 比如 jshinting ^^^ 23 | .pipe(footer('})();')) // 增加一些类似模块封装的东西 24 | .pipe(remember('scripts')) // 把所有的文件放回 stream 25 | .pipe(concat('app.js')) // 做一些需要所有文件的操作 26 | .pipe(gulp.dest('public/')); 27 | }); 28 | 29 | gulp.task('watch', function () { 30 | var watcher = gulp.watch(scriptsGlob, ['scripts']); // 监视与 scripts 任务中同样的文件 31 | watcher.on('change', function (event) { 32 | if (event.type === 'deleted') { // 如果一个文件被删除了,则将其忘记 33 | delete cached.caches.scripts[event.path]; // gulp-cached 的删除 api 34 | remember.forget('scripts', event.path); // gulp-remember 的删除 api 35 | } 36 | }); 37 | }); 38 | ``` 39 | -------------------------------------------------------------------------------- /recipes/maintain-directory-structure-while-globbing.md: -------------------------------------------------------------------------------- 1 | # 在使用通配符的时候维持目录结构 2 | 3 | 如果你打算从一个目录中读取一系列的文件或文件夹,并且想要维持原来的目录结构,那么你需要把 `{base: '.'}` 以第二个参数传递给 `gulp.src()`。 4 | 5 | 6 | 举个例子,如果你有一个像这样的目录结构 7 | 8 | ![Dev setup](https://cloud.githubusercontent.com/assets/2562992/3178498/bedf75b4-ec1a-11e3-8a71-a150ad94b450.png) 9 | 10 | 并且你想要读取像这样的文件 11 | 12 | ```js 13 | [ 'index.html', 14 | 'css/**', 15 | 'js/**', 16 | 'lib/**', 17 | 'images/**', 18 | 'plugin/**' 19 | ] 20 | ``` 21 | 22 | 在这个例子中,gulp 将会读取所有(_所说的_)`css` 的子目录,然后将它们相对于你的根目录放置,并且它们将不在作为 `css` 的子目录存在。通配操作后,输出目录将会像是这样 23 | 24 | ![Zipped-Unzipped](https://cloud.githubusercontent.com/assets/2562992/3178614/27208c52-ec1c-11e3-852e-8bbb8e420c7f.png) 25 | 26 | 如果你想要维持这个目录结果,你需要把 `{base: '.'}` 传递给 `gulp.src()`。像这样 27 | 28 | ```js 29 | gulp.task('task', function () { 30 | gulp.src(['index.html', 31 | 'css/**', 32 | 'js/**', 33 | 'lib/**', 34 | 'images/**', 35 | 'plugin/**' 36 | ], {base: '.'}) 37 | .pipe(operation1()) 38 | .pipe(operation2()); 39 | }); 40 | ``` 41 | 然后,输入到 `operation1()` 的目录结构将会是这样子 42 | 43 | ![with-base](https://cloud.githubusercontent.com/assets/2562992/3178607/053d6722-ec1c-11e3-9ba8-7ce39e1a480e.png) 44 | 45 | -------------------------------------------------------------------------------- /recipes/make-stream-from-buffer.md: -------------------------------------------------------------------------------- 1 | # 将 buffer 变为 stream (内存中的内容) 2 | 3 | 有时候,你会需要这样一个 stream,它们的内容保存在一个变量中,而不是在一个实际的文件中。换言之,怎么不使用 `gulp.src()` 而创建一个 'gulp' stream。 4 | 5 | 我们来举一个例子,我们拥有一个包含 js 库文件的目录,以及一个包含一些模块的不同版本文件的目录。编译的目标是为每个版本创建一个 js 文件,其中包含所有库文件以及相应版本的模块文件拼接后的结果。 6 | 7 | 逻辑上我们将把这个拆分为如下步骤: 8 | 9 | * 载入库文件 10 | * 拼接库文件的内容 11 | * 载入不同版本的文件 12 | * 对于每个版本的文件,将其和库文件的内容拼接 13 | * 对于每个版本的文件,将结果输出到一个文件 14 | 15 | 想象如下的文件结构: 16 | 17 | ```sh 18 | ├── libs 19 | │   ├── lib1.js 20 | │   └── lib2.js 21 | └── versions 22 | ├── version.1.js 23 | └── version.2.js 24 | ``` 25 | 26 | 你应该要得到这样的结果: 27 | 28 | ```sh 29 | └── output 30 | ├── version.1.complete.js # lib1.js + lib2.js + version.1.js 31 | └── version.2.complete.js # lib1.js + lib2.js + version.2.js 32 | ``` 33 | 34 | 一个简单的模块化处理方式将会像下面这样: 35 | 36 | ```js 37 | var gulp = require('gulp'); 38 | var runSequence = require('run-sequence'); 39 | var source = require('vinyl-source-stream'); 40 | var vinylBuffer = require('vinyl-buffer'); 41 | var tap = require('gulp-tap'); 42 | var concat = require('gulp-concat'); 43 | var size = require('gulp-size'); 44 | var path = require('path'); 45 | var es = require('event-stream'); 46 | 47 | var memory = {}; // 我们会将 assets 保存到内存中 48 | 49 | // 载入内存中文件内容的任务 50 | gulp.task('load-lib-files', function() { 51 | // 从磁盘中读取库文件 52 | return gulp.src('src/libs/*.js') 53 | // 将所有库文件拼接到一起 54 | .pipe(concat('libs.concat.js')) 55 | // 接入 stream 来获取每个文件的数据 56 | .pipe(tap(function(file) { 57 | // 保存文件的内容到内存 58 | memory[path.basename(file.path)] = file.contents.toString(); 59 | })); 60 | }); 61 | 62 | gulp.task('load-versions', function() { 63 | memory.versions = {}; 64 | // 从磁盘中读取文件 65 | return gulp.src('src/versions/version.*.js') 66 | // 接入 stream 来获取每个文件的数据 67 | .pipe( tap(function(file) { 68 | // 在 assets 中保存文件的内容 69 | memory.versions[path.basename(file.path)] = file.contents.toString(); 70 | })); 71 | }); 72 | 73 | gulp.task('write-versions', function() { 74 | // 我们将不容版本的文件的名字保存到一个数组中 75 | var availableVersions = Object.keys(memory.versions); 76 | // 我们创建一个数组来保存所有的 stream 的 promise 77 | var streams = []; 78 | 79 | availableVersions.forEach(function(v) { 80 | // 以一个假文件名创建一个新的 stream 81 | var stream = source('final.' + v); 82 | 83 | var streamEnd = stream; 84 | 85 | // 从拼接后的文件中读取数据 86 | var fileContents = memory['libs.concat.js'] + 87 | // 增加版本文件的数据 88 | '\n' + memory.versions[v]; 89 | 90 | // 将文件的内容写入 stream 91 | stream.write(fileContents); 92 | 93 | process.nextTick(function() { 94 | // 在下一次处理循环中结束 stream 95 | stream.end(); 96 | }); 97 | 98 | streamEnd = streamEnd 99 | // 转换原始数据到 stream 中去,到一个 vinyl 对象/文件 100 | .pipe(vinylBuffer()) 101 | //.pipe(tap(function(file) { /* 这里可以做一些对文件内容的处理操作 */ })) 102 | .pipe(gulp.dest('output')); 103 | 104 | // 加到 stream 的尾部,不然,task 会在这些处理完成之前被结束 105 | streams.push(streamEnd); 106 | }); 107 | 108 | return es.merge.apply(this, streams); 109 | }); 110 | 111 | //============================================ 我们的主任务 112 | gulp.task('default', function(taskDone) { 113 | runSequence( 114 | ['load-lib-files', 'load-versions'], // 并行载入文件 115 | 'write-versions', // 一旦所有资源进入内存便可以做写入操作了 116 | taskDone // 完成 117 | ); 118 | }); 119 | 120 | //============================================ 我们的监控任务 121 | // 只在运行完 'default' 任务后运行, 122 | // 这样所有的资源都已经在内存中了 123 | gulp.task('watch', ['default'], function() { 124 | gulp.watch('./src/libs/*.js', function() { 125 | runSequence( 126 | 'load-lib-files', // 我们只需要载入更改过的文件 127 | 'write-versions' 128 | ); 129 | }); 130 | 131 | gulp.watch('./src/versions/*.js', function() { 132 | runSequence( 133 | 'load-versions', // 我们只需要载入更改过的文件 134 | 'write-versions' 135 | ); 136 | }); 137 | }); 138 | ``` 139 | -------------------------------------------------------------------------------- /recipes/minified-and-non-minified.md: -------------------------------------------------------------------------------- 1 | # 同时输出一个压缩过和一个未压缩版本的文件 2 | 3 | 同时输出压缩过的和未压缩版本的文件可以通过使用 `gulp-rename` 然后 pipe 到 `dest` 两次来实现 (一次是压缩之前的,一次是压缩后的): 4 | 5 | ```js 6 | 'use strict'; 7 | 8 | var gulp = require('gulp'); 9 | var rename = require('gulp-rename'); 10 | var uglify = require('gulp-uglify'); 11 | 12 | var DEST = 'build/'; 13 | 14 | gulp.task('default', function() { 15 | return gulp.src('foo.js') 16 | // 这会输出一个未压缩过的版本 17 | .pipe(gulp.dest(DEST)) 18 | // 这会输出一个压缩过的并且重命名未 foo.min.js 的文件 19 | .pipe(uglify()) 20 | .pipe(rename({ extname: '.min.js' })) 21 | .pipe(gulp.dest(DEST)); 22 | }); 23 | 24 | ``` 25 | -------------------------------------------------------------------------------- /recipes/mocha-test-runner-with-gulp.md: -------------------------------------------------------------------------------- 1 | # 在 gulp 中运行 Mocha 测试 2 | 3 | ### 运行所有的测试用例 4 | 5 | ```js 6 | // npm install gulp gulp-mocha 7 | 8 | var gulp = require('gulp'); 9 | var mocha = require('gulp-mocha'); 10 | 11 | gulp.task('default', function() { 12 | return gulp.src(['test/test-*.js'], { read: false }) 13 | .pipe(mocha({ 14 | reporter: 'spec', 15 | globals: { 16 | should: require('should') 17 | } 18 | })); 19 | }); 20 | ``` 21 | 22 | ### 在文件改动时候运行 mocha 测试用例 23 | 24 | ```js 25 | // npm install gulp gulp-mocha gulp-util 26 | 27 | var gulp = require('gulp'); 28 | var mocha = require('gulp-mocha'); 29 | var gutil = require('gulp-util'); 30 | 31 | gulp.task('default', function() { 32 | gulp.watch(['lib/**', 'test/**'], ['mocha']); 33 | }); 34 | 35 | gulp.task('mocha', function() { 36 | return gulp.src(['test/*.js'], { read: false }) 37 | .pipe(mocha({ reporter: 'list' })) 38 | .on('error', gutil.log); 39 | }); 40 | ``` 41 | -------------------------------------------------------------------------------- /recipes/only-pass-through-changed-files.md: -------------------------------------------------------------------------------- 1 | # 仅仅传递更改过的文件 2 | 3 | 默认情况下,每次运行时候所有的文件都会传递并通过整个管道。通过使用 [gulp-changed](https://github.com/sindresorhus/gulp-changed) 可以只让更改过的文件传递过管道。这可以大大加快连续多次的运行。 4 | 5 | 6 | ```js 7 | // npm install --save-dev gulp gulp-changed gulp-jscs gulp-uglify 8 | 9 | var gulp = require('gulp'); 10 | var changed = require('gulp-changed'); 11 | var jscs = require('gulp-jscs'); 12 | var uglify = require('gulp-uglify'); 13 | 14 | // 我们在这里定义一些常量以供使用 15 | var SRC = 'src/*.js'; 16 | var DEST = 'dist'; 17 | 18 | gulp.task('default', function() { 19 | return gulp.src(SRC) 20 | // `changed` 任务需要提前知道目标目录位置 21 | // 才能找出哪些文件是被修改过的 22 | .pipe(changed(DEST)) 23 | // 只有被更改过的文件才会通过这里 24 | .pipe(jscs()) 25 | .pipe(uglify()) 26 | .pipe(gulp.dest(DEST)); 27 | }); 28 | ``` 29 | -------------------------------------------------------------------------------- /recipes/pass-arguments-from-cli.md: -------------------------------------------------------------------------------- 1 | # 从命令行传递参数 2 | 3 | ```js 4 | // npm install --save-dev gulp gulp-if gulp-uglify minimist 5 | 6 | var gulp = require('gulp'); 7 | var gulpif = require('gulp-if'); 8 | var uglify = require('gulp-uglify'); 9 | 10 | var minimist = require('minimist'); 11 | 12 | var knownOptions = { 13 | string: 'env', 14 | default: { env: process.env.NODE_ENV || 'production' } 15 | }; 16 | 17 | var options = minimist(process.argv.slice(2), knownOptions); 18 | 19 | gulp.task('scripts', function() { 20 | return gulp.src('**/*.js') 21 | .pipe(gulpif(options.env === 'production', uglify())) // 仅在生产环境时候进行压缩 22 | .pipe(gulp.dest('dist')); 23 | }); 24 | ``` 25 | 26 | 然后,通过如下命令运行 gulp: 27 | 28 | ```sh 29 | $ gulp scripts --env development 30 | ``` 31 | -------------------------------------------------------------------------------- /recipes/rebuild-only-files-that-change.md: -------------------------------------------------------------------------------- 1 | # 只重新编译被更改过的文件 2 | 3 | 通过使用 [`gulp-watch`](https://github.com/floatdrop/gulp-watch): 4 | 5 | ```js 6 | var gulp = require('gulp'); 7 | var sass = require('gulp-sass'); 8 | var watch = require('gulp-watch'); 9 | 10 | gulp.task('default', function() { 11 | return gulp.src('sass/*.scss') 12 | .pipe(watch('sass/*.scss')) 13 | .pipe(sass()) 14 | .pipe(gulp.dest('dist')); 15 | }); 16 | ``` 17 | -------------------------------------------------------------------------------- /recipes/run-grunt-tasks-from-gulp.md: -------------------------------------------------------------------------------- 1 | # 在 Gulp 中执行 Grunt 任务 2 | 3 | 在 Gulp 中运行 Grunt 任务或者 Grunt 插件是可能的。这在从 Grunt 迁移到 Gulp 阶段或者你对某个 Grunt 插件有需求的时候会非常有用。按照如下描述的方法,不需要引入 Grunt 命令行工具或者 Gruntfile。 4 | 5 | **这个方法需要 Grunt 版本 >=1.0.0** 6 | 7 | 非常简单的 `gulpfile.js` 示例: 8 | 9 | ```js 10 | // npm install gulp grunt grunt-contrib-copy --save-dev 11 | 12 | var gulp = require('gulp'); 13 | var grunt = require('grunt'); 14 | 15 | grunt.initConfig({ 16 | copy: { 17 | main: { 18 | src: 'src/*', 19 | dest: 'dest/' 20 | } 21 | } 22 | }); 23 | grunt.loadNpmTasks('grunt-contrib-copy'); 24 | 25 | gulp.task('copy', function (done) { 26 | grunt.tasks( 27 | ['copy:main'], // 你可以在这个数组中加入更多的 Grunt 任务 28 | {gruntfile: false}, // 不查找 Gruntfile - 因为并没有. :-) 29 | function () {done();} 30 | ); 31 | }); 32 | 33 | ``` 34 | 35 | 现在开始执行任务: 36 | `gulp copy` 37 | 38 | 通过上述方式,grunt 任务会被注册到 gulp 的任务系统中。**需要注意的是,grunt 任务通常是阻塞的(不像 gulp),因此在 grunt 任务执行完成之前,没有任何其他的任务可以执行(甚至 gulp 任务也不可以)**。 39 | 40 | 41 | ### A few words on alternatives 42 | 43 | 有一个 *gulp友好* 的 node 模块 `gulp-grunt` [可以使用](https://www.npmjs.org/package/gulp-grunt) ,它使用了一个不同的方案。它会创建一些子进程来执行 grunt 任务,这意味着它有一些限制: 44 | 45 | * 到现在为止,使用 `gulp-grunt` 还不能把类似命令行参数或者其他的选项参数传递到 grunt 任务中 46 | * 所有的 grunt 任务都需要被定义到一个独立的 Gruntfile 47 | * 你需要安装 Grunt 命令行 48 | * 一些 grunt 任务的输出可能会有奇怪的格式(.i.e. color coding). 49 | -------------------------------------------------------------------------------- /recipes/running-task-steps-per-folder.md: -------------------------------------------------------------------------------- 1 | # 每个文件夹生成单独一个文件 2 | 3 | 如果你有一整套的文件目录,并且希望执行相应的一套任务,比如... 4 | 5 | ``` 6 | /scripts 7 | /scripts/jquery/*.js 8 | /scripts/angularjs/*.js 9 | ``` 10 | 11 | ...然后希望完成如下的结果h... 12 | 13 | ``` 14 | /scripts 15 | /scripts/jquery.min.js 16 | /scripts/angularjs.min.js 17 | ``` 18 | 19 | ...你将会需要像下面所示的东西... 20 | 21 | ``` javascript 22 | var fs = require('fs'); 23 | var path = require('path'); 24 | var merge = require('merge-stream'); 25 | var gulp = require('gulp'); 26 | var concat = require('gulp-concat'); 27 | var rename = require('gulp-rename'); 28 | var uglify = require('gulp-uglify'); 29 | 30 | var scriptsPath = 'src/scripts'; 31 | 32 | function getFolders(dir) { 33 | return fs.readdirSync(dir) 34 | .filter(function(file) { 35 | return fs.statSync(path.join(dir, file)).isDirectory(); 36 | }); 37 | } 38 | 39 | gulp.task('scripts', function() { 40 | var folders = getFolders(scriptsPath); 41 | 42 | var tasks = folders.map(function(folder) { 43 | return gulp.src(path.join(scriptsPath, folder, '/*.js')) 44 | // 拼接进 foldername.js 45 | .pipe(concat(folder + '.js')) 46 | // 写入输出 47 | .pipe(gulp.dest(scriptsPath)) 48 | // 代码压缩 49 | .pipe(uglify()) 50 | // 重命名为 folder.min.js 51 | .pipe(rename(folder + '.min.js')) 52 | // 再一次写入输出 53 | .pipe(gulp.dest(scriptsPath)); 54 | }); 55 | 56 | return merge(tasks); 57 | }); 58 | ``` 59 | 60 | 注: 61 | 62 | - `folders.map` - 在每一个文件夹中分别执行一次函数,并且返回异步 stream 63 | - `merge` - 汇总 stream,并且在所有的 stream 都完成后完成 64 | -------------------------------------------------------------------------------- /recipes/running-tasks-in-series.md: -------------------------------------------------------------------------------- 1 | # 串行方式运行任务,亦即,任务依赖 2 | 3 | 默认情况下,任务会以最大的并发数同时运行 -- 也就是说,它会不做任何等待地将所有的任务同时开起来。如果你希望创建一个有特定顺序的串行的任务链,你需要做两件事: 4 | 5 | - 给它一个提示,用以告知任务在什么时候完成, 6 | - 而后,再给一个提示,用以告知某任务需要依赖另一个任务的完成。 7 | 8 | 举个例子,我们假设你有两个任务,"one" 和 "two",并且你明确的希望他们就以这样的顺序运行: 9 | 10 | 1. 在任务 "one" 中,你添加的一个提示,来告知何时它会完成。你可以传入一个回调函数,然后在完成后执行回调函数,也可以通过返回一个 promise 或者 stream 来让引擎等待它们分别地被解决掉。 11 | 12 | 2. 在任务 "two" 中,你添加一个提示,来告知引擎它需要依赖第一个任务的完成。 13 | 14 | 因此,这个例子将会是像这样: 15 | 16 | ```js 17 | var gulp = require('gulp'); 18 | 19 | // 传入一个回调函数,因此引擎可以知道何时它会被完成 20 | gulp.task('one', function(cb) { 21 | // 做一些事 -- 异步的或者其他任何的事 22 | cb(err); // 如果 err 不是 null 和 undefined,流程会被结束掉,'two' 不会被执行 23 | }); 24 | 25 | // 标注一个依赖,依赖的任务必须在这个任务开始之前被完成 26 | gulp.task('two', ['one'], function() { 27 | // 现在任务 'one' 已经完成了 28 | }); 29 | 30 | gulp.task('default', ['one', 'two']); 31 | // 也可以这么写:gulp.task('default', ['two']); 32 | ``` 33 | 34 | 另一个例子,通过返回一个 stream 来取代使用回调函数的方法: 35 | 36 | ```js 37 | var gulp = require('gulp'); 38 | var del = require('del'); // rm -rf 39 | 40 | gulp.task('clean', function() { 41 | return del(['output']); 42 | }); 43 | 44 | gulp.task('templates', ['clean'], function() { 45 | var stream = gulp.src(['src/templates/*.hbs']) 46 | // 执行拼接,压缩,等。 47 | .pipe(gulp.dest('output/templates/')); 48 | return stream; // 返回一个 stream 来表示它已经被完成 49 | 50 | }); 51 | 52 | gulp.task('styles', ['clean'], function() { 53 | var stream = gulp.src(['src/styles/app.less']) 54 | // 执行一些代码检查,压缩,等 55 | .pipe(gulp.dest('output/css/app.css')); 56 | return stream; 57 | }); 58 | 59 | gulp.task('build', ['templates', 'styles']); 60 | 61 | // templates 和 styles 将会并行处理 62 | // clean 将会保证在任一个任务开始之前完成 63 | // clean 并不会被执行两次,尽管它被作为依赖调用了两次 64 | 65 | gulp.task('default', ['build']); 66 | ``` 67 | -------------------------------------------------------------------------------- /recipes/server-with-livereload-and-css-injection.md: -------------------------------------------------------------------------------- 1 | # 拥有实时重载(live-reloading)和 CSS 注入的服务器 2 | 3 | 使用 [BrowserSync](http://browsersync.io) 和 gulp,你可以轻松地创建一个开发服务器,然后同一个 WiFi 中的任何设备都可以方便地访问到。BrowserSync 同时集成了 live-reload 所以不需要另外做配置了。 4 | 5 | 首先安装模块: 6 | 7 | ```sh 8 | $ npm install --save-dev browser-sync 9 | ``` 10 | 11 | 然后,考虑拥有如下的目录结构... 12 | 13 | ``` 14 | gulpfile.js 15 | app/ 16 | styles/ 17 | main.css 18 | scripts/ 19 | main.js 20 | index.html 21 | ``` 22 | 23 | ... 通过如下的 `gulpfile.js`,你可以轻松地将 `app` 目录中的文件加到服务器中,并且所有的浏览器都会在文件发生改变之后自动刷新: 24 | 25 | ```js 26 | var gulp = require('gulp'); 27 | var browserSync = require('browser-sync'); 28 | var reload = browserSync.reload; 29 | 30 | // 监视文件改动并重新载入 31 | gulp.task('serve', function() { 32 | browserSync({ 33 | server: { 34 | baseDir: 'app' 35 | } 36 | }); 37 | 38 | gulp.watch(['*.html', 'styles/**/*.css', 'scripts/**/*.js'], {cwd: 'app'}, reload); 39 | }); 40 | 41 | ``` 42 | 43 | 在 `index.html` 中引入 CSS: 44 | 45 | ```html 46 | 47 | 48 | ... 49 | 50 | ... 51 | 52 | ``` 53 | 54 | 通过如下命令启动服务,并且打开一个浏览器,访问默认的 URL (http://localhost:3000): 55 | 56 | ```bash 57 | gulp serve 58 | ``` 59 | 60 | 61 | ## + CSS 预处理器 62 | 63 | 一个常见的使用案例是当 CSS 文件文件预处理之后重载它们。以 sass 为例,这便是你如何指示浏览器无需刷新整个页面而只是重载 CSS。 64 | 65 | 考虑有如下的文件目录结构... 66 | 67 | ``` 68 | gulpfile.js 69 | app/ 70 | scss/ 71 | main.scss 72 | scripts/ 73 | main.js 74 | index.html 75 | ``` 76 | ... 通过如下的 `gulpfile.js`,你可以轻松地监视 `scss` 目录中的文件,并且所有的浏览器都会在文件发生改变之后自动刷新: 77 | 78 | ```js 79 | var gulp = require('gulp'); 80 | var sass = require('gulp-ruby-sass'); 81 | var browserSync = require('browser-sync'); 82 | var reload = browserSync.reload; 83 | 84 | gulp.task('sass', function() { 85 | return sass('scss/styles.scss') 86 | .pipe(gulp.dest('app/css')) 87 | .pipe(reload({ stream:true })); 88 | }); 89 | 90 | // 监视 Sass 文件的改动,如果发生变更,运行 'sass' 任务,并且重载文件 91 | gulp.task('serve', ['sass'], function() { 92 | browserSync({ 93 | server: { 94 | baseDir: 'app' 95 | } 96 | }); 97 | 98 | gulp.watch('app/scss/*.scss', ['sass']); 99 | }); 100 | ``` 101 | 102 | 在 `index.html` 文件中引入预处理后的 CSS 文件: 103 | 104 | ```html 105 | 106 | 107 | ... 108 | 109 | ... 110 | 111 | ``` 112 | 113 | 通过如下命令启动服务,并且打开一个浏览器,访问默认的 URL (http://localhost:3000): 114 | 115 | ```bash 116 | gulp serve 117 | ``` 118 | 119 | ## 附注: 120 | 121 | - 实时重载(Live reload),CSS 注入以及同步滚动可以在 [BrowserStack](http://www.browserstack.com/) 虚拟机里无缝执行。 122 | - 设置 `tunnel: true` 来使用一个公开的 URL 来访问你本地的站点 (支持所有 BrowserSync 功能)。 123 | -------------------------------------------------------------------------------- /recipes/sharing-streams-with-stream-factories.md: -------------------------------------------------------------------------------- 1 | # 通过 stream 工厂来共享 stream 2 | 3 | 如果你在多个任务中使用了相同的插件,你可能发现你很想把这些东西以 DRY 的原则去处理。这个方法可以创建一些工厂来把你经常使用的 stream 链分离出来。 4 | 5 | 我们将使用 [lazypipe](https://github.com/OverZealous/lazypipe) 来完成这件事。 6 | 7 | 这是我们的例子: 8 | 9 | ```js 10 | var gulp = require('gulp'); 11 | var uglify = require('gulp-uglify'); 12 | var coffee = require('gulp-coffee'); 13 | var jshint = require('gulp-jshint'); 14 | var stylish = require('jshint-stylish'); 15 | 16 | gulp.task('bootstrap', function() { 17 | return gulp.src('bootstrap/js/*.js') 18 | .pipe(jshint()) 19 | .pipe(jshint.reporter(stylish)) 20 | .pipe(uglify()) 21 | .pipe(gulp.dest('public/bootstrap')); 22 | }); 23 | 24 | gulp.task('coffee', function() { 25 | return gulp.src('lib/js/*.coffee') 26 | .pipe(coffee()) 27 | .pipe(jshint()) 28 | .pipe(jshint.reporter(stylish)) 29 | .pipe(uglify()) 30 | .pipe(gulp.dest('public/js')); 31 | }); 32 | ``` 33 | 34 | 然后,使用了 lazypipe 之后,将会是这样: 35 | 36 | ```js 37 | var gulp = require('gulp'); 38 | var uglify = require('gulp-uglify'); 39 | var coffee = require('gulp-coffee'); 40 | var jshint = require('gulp-jshint'); 41 | var stylish = require('jshint-stylish'); 42 | var lazypipe = require('lazypipe'); 43 | 44 | // 赋给 lazypipe 45 | var jsTransform = lazypipe() 46 | .pipe(jshint) 47 | .pipe(jshint.reporter, stylish) 48 | .pipe(uglify); 49 | 50 | gulp.task('bootstrap', function() { 51 | return gulp.src('bootstrap/js/*.js') 52 | .pipe(jsTransform()) 53 | .pipe(gulp.dest('public/bootstrap')); 54 | }); 55 | 56 | gulp.task('coffee', function() { 57 | return gulp.src('lib/js/*.coffee') 58 | .pipe(coffee()) 59 | .pipe(jsTransform()) 60 | .pipe(gulp.dest('public/js')); 61 | }); 62 | ``` 63 | 64 | 你可以看到,我们把多个任务中都在使用的 JavaScript 管道(JSHint + Uglify)分离到了一个工厂。工厂可以在任意多的任务中重用。你也可以嵌套这些工厂,或者把它们连接起来,已达到更好的效果。分离出每个共享的管道,也可以让你能够集中地管理,当你的工作流程更改后,你只需要修改一个地方即可。 65 | -------------------------------------------------------------------------------- /recipes/specifying-a-cwd.md: -------------------------------------------------------------------------------- 1 | # 指定一个新的 cwd (当前工作目录) 2 | 3 | 在一个多层嵌套的项目中,这是非常有用的,比如: 4 | 5 | ``` 6 | /project 7 | /layer1 8 | /layer2 9 | ``` 10 | 11 | 你可以使用 gulp 的 CLI 参数 `--cwd`. 12 | 13 | 在 `project/` 目中中: 14 | 15 | ```sh 16 | gulp --cwd layer1 17 | ``` 18 | 19 | 如果你需要对特定的匹配指定一个 cwd,你可以使用 [glob-stream](https://github.com/gulpjs/glob-stream) 的 `cwd` 选项: 20 | 21 | ```js 22 | gulp.src('./some/dir/**/*.js', { cwd: 'public' }); 23 | ``` 24 | -------------------------------------------------------------------------------- /recipes/split-tasks-across-multiple-files.md: -------------------------------------------------------------------------------- 1 | # 分离任务到多个文件中 2 | 3 | 如果你的 `gulpfile.js` 开始变得很大,你可以通过使用下面的方法之一将任务分离到多个文件: 4 | 5 | > 注意,这个方法 [考虑弃用][deprecated] 了 6 | > 并且当迁移到 `gulp 4` 后会引发一些问题。 7 | 8 | ## 使用 `gulp-require-tasks` 9 | 10 | 你可以使用 [gulp-require-tasks][gulp-require-tasks] 11 | 模块来自动载入所有单独文件中的任务。 12 | 13 | 请查看 [模块的 README][gulp-require-tasks] 获取最新的说明。 14 | 15 | ## 使用 `require-dir` 16 | 17 | 你可以使用 [require-dir][require-dir] 模块来手动载入任务。 18 | 19 | 想象如下的文件结构: 20 | 21 | ``` 22 | gulpfile.js 23 | tasks/ 24 | ├── dev.js 25 | ├── release.js 26 | └── test.js 27 | ``` 28 | 29 | 安装 `require-dir`: 30 | 31 | ```sh 32 | npm install --save-dev require-dir 33 | ``` 34 | 35 | 在你的 `gulpfile.js` 文件中加入以下代码: 36 | 37 | ```js 38 | var requireDir = require('require-dir'); 39 | var tasks = requireDir('./tasks'); 40 | ``` 41 | 42 | 43 | [gulp-require-tasks]: https://github.com/betsol/gulp-require-tasks 44 | [require-dir]: https://github.com/aseemk/requireDir 45 | [deprecated]: https://github.com/gulpjs/gulp/pull/1554#issuecomment-202614391 46 | -------------------------------------------------------------------------------- /recipes/templating-with-swig-and-yaml-front-matter.md: -------------------------------------------------------------------------------- 1 | # Swig 以及 YAML front-matter 模板 2 | 模板可以使用 `gulp-swig` 和 `gulp-front-matter` 来设置: 3 | 4 | ##### `page.html` 5 | 6 | ```html 7 | --- 8 | title: Things to do 9 | todos: 10 | - First todo 11 | - Another todo item 12 | - A third todo item 13 | --- 14 | 15 | 16 | {{ title }} 17 | 18 | 19 |

{{ title }}

20 |
    {% for todo in todos %} 21 |
  • {{ todo }}
  • 22 | {% endfor %}
23 | 24 | 25 | ``` 26 | 27 | ##### `gulpfile.js` 28 | 29 | ```js 30 | var gulp = require('gulp'); 31 | var swig = require('gulp-swig'); 32 | var frontMatter = require('gulp-front-matter'); 33 | 34 | gulp.task('compile-page', function() { 35 | gulp.src('page.html') 36 | .pipe(frontMatter({ property: 'data' })) 37 | .pipe(swig()) 38 | .pipe(gulp.dest('build')); 39 | }); 40 | 41 | gulp.task('default', ['compile-page']); 42 | ``` 43 | -------------------------------------------------------------------------------- /recipes/using-external-config-file.md: -------------------------------------------------------------------------------- 1 | # 使用外部配置文件 2 | 3 | 这有很多好处,因为它能让任务更加符合 DRY 原则,并且 config.json 可以被其他的任务运行器使用,比如 `grunt`。 4 | 5 | - 6 | 7 | ###### `config.json` 8 | 9 | ```json 10 | { 11 | "desktop" : { 12 | "src" : [ 13 | "dev/desktop/js/**/*.js", 14 | "!dev/desktop/js/vendor/**" 15 | ], 16 | "dest" : "build/desktop/js" 17 | }, 18 | "mobile" : { 19 | "src" : [ 20 | "dev/mobile/js/**/*.js", 21 | "!dev/mobile/js/vendor/**" 22 | ], 23 | "dest" : "build/mobile/js" 24 | } 25 | } 26 | ``` 27 | 28 | - 29 | 30 | ###### `gulpfile.js` 31 | 32 | ```js 33 | // npm install --save-dev gulp gulp-uglify 34 | var gulp = require('gulp'); 35 | var uglify = require('gulp-uglify'); 36 | var config = require('./config.json'); 37 | 38 | function doStuff(cfg) { 39 | return gulp.src(cfg.src) 40 | .pipe(uglify()) 41 | .pipe(gulp.dest(cfg.dest)); 42 | } 43 | 44 | gulp.task('dry', function() { 45 | doStuff(config.desktop); 46 | doStuff(config.mobile); 47 | }); 48 | ``` 49 | -------------------------------------------------------------------------------- /recipes/using-multiple-sources-in-one-task.md: -------------------------------------------------------------------------------- 1 | # 在一个任务中使用多个文件来源 2 | 3 | ```js 4 | // npm install --save-dev gulp merge-stream 5 | 6 | var gulp = require('gulp'); 7 | var merge = require('merge-stream'); 8 | 9 | gulp.task('test', function() { 10 | var bootstrap = gulp.src('bootstrap/js/*.js') 11 | .pipe(gulp.dest('public/bootstrap')); 12 | 13 | var jquery = gulp.src('jquery.cookie/jquery.cookie.js') 14 | .pipe(gulp.dest('public/jquery')); 15 | 16 | return merge(bootstrap, jquery); 17 | }); 18 | ``` 19 | 20 | `gulp.src` 会以文件被添加的顺序来 emit: 21 | 22 | ```js 23 | // npm install gulp gulp-concat 24 | 25 | var gulp = require('gulp'); 26 | var concat = require('gulp-concat'); 27 | 28 | gulp.task('default', function() { 29 | return gulp.src(['foo/*', 'bar/*']) 30 | .pipe(concat('result.txt')) 31 | .pipe(gulp.dest('build')); 32 | }); 33 | -------------------------------------------------------------------------------- /writing-a-plugin/README.md: -------------------------------------------------------------------------------- 1 | # 编写插件 2 | 3 | 如果你打算自己写一个 Gulp 插件,为了节约你的时间,你可以先完整地阅读下这个文档。 4 | 5 | * [导览](guidelines.md) (a MUST read) 6 | * [使用 buffers](using-buffers.md) 7 | * [使用 streams 来处理](dealing-with-streams.md) 8 | * [测试](testing.md) 9 | 10 | ## 它要做什么? 11 | 12 | ### 流式处理文件对象(Streaming file objects) 13 | 14 | gulp 插件总是返回一个 [object mode](http://nodejs.org/api/stream.html#stream_object_mode) 形式的 stream 来做这些事情: 15 | 16 | 1. 接收 [vinyl File 对象](http://github.com/gulpjs/vinyl) 17 | 2. 输出 [vinyl File 对象](http://github.com/gulpjs/vinyl) (通过 `transform.push()` 以及/或者插件的回调函数) 18 | 19 | 这通常被叫做 [transform streams](http://nodejs.org/api/stream.html#stream_class_stream_transform_1) (有时候也叫做 through streams)。 20 | transform streams 是双工可读又可写的,它会对传给它的对象做一些转换的操作。 21 | 22 | 所有的插件本质上都可以归结成如此: 23 | 24 | ```js 25 | var Transform = require('transform'); 26 | 27 | module.exports = function() { 28 | // 加 Monkey patch 或者创建你自己的子类 29 | // 实现 `_transform()` 和可选的 `_flush()` 30 | var transformStream = new Transform({objectMode: true}); 31 | /** 32 | * @param {Buffer|string} 文件 33 | * @param {string=} 编码 - 如果文件包含 Buffer 则忽略 34 | * @param {function(Error, object)} 回调 - 当对传递进来的 chunk 处理完成后执行(可选的一个错误参数和数据) 35 | */ 36 | transformStream._transform = function(file, encoding, callback) { 37 | var error = null, 38 | output = doSomethingWithTheFile(file); 39 | callback(error, output); 40 | }; 41 | 42 | return transformStream; 43 | }; 44 | ``` 45 | 46 | 很多插件使用 [through2](https://github.com/rvagg/through2/) 模块来简化代码: 47 | 48 | ```js 49 | var through = require('through2'); // npm install --save through2 50 | 51 | module.exports = function() { 52 | return through.obj(function(file, encoding, callback) { 53 | callback(null, doSomethingWithTheFile(file)); 54 | }); 55 | }; 56 | ``` 57 | 58 | `through()` 所返回的 stream (以及你 transform 函数中的 `this`) 都是 [Transform](https://github.com/iojs/readable-stream/blob/master/lib/_stream_transform.js) 类的实例,它继承自 [Duplex](https://github.com/iojs/readable-stream/blob/master/lib/_stream_duplex.js), 59 | [Readable](https://github.com/iojs/readable-stream/blob/master/lib/_stream_readable.js) 60 | (并寄生自 Writable) 最终继承 [Stream](https://nodejs.org/api/stream.html). 61 | 如果你需要处理额外的参数,你可以直接调用 `through()` 函数: 62 | 63 | ```js 64 | return through({objectMode: true /* other options... */}, function(file, encoding, callback) { ... 65 | ``` 66 | 67 | 支持的参数包括: 68 | 69 | * highWaterMark (defaults to 16) 70 | * defaultEncoding (defaults to 'utf8') 71 | * encoding - 'utf8', 'base64', 'utf16le', 'ucs2' etc. 72 | 指定后, 一个 [StringDecoder](https://github.com/rvagg/string_decoder/blob/master/index.js) `decoder` 会被加到这个 stream。 73 | * readable {boolean} 74 | * writable {boolean} 75 | * allowHalfOpen {boolean} 如果为 false,那么 stream 会在 sriteable 结束的时候自动结束 readable,反之亦然。 76 | 77 | ### 修改文内容 78 | 79 | 传递给 `through.obj()` 的参数是一个 [_transform](https://nodejs.org/api/stream.html#stream_transform_transform_chunk_encoding_callback) 80 | 函数,它将对输入的 `file` 做一些操作,你可能也将提供一个可选 [_flush](https://nodejs.org/api/stream.html#stream_transform_flush_callback) 81 | 函数,如果你需要在结束时候发送更多的数据的话。 82 | 83 | 从 transform 函数内部调用 `this.push(file)` 0 次或者多次来传递 transformed/cloned 的文件. 84 | 如果你把所有输出提供给 `callback()` 那么,你不需要调用 `this.push(file)`。 85 | 86 | 只在当前文件(stream/buffer) 被完全的消费后才调用 `callback` 函数 87 | 如果发生了一个错误,则将它以第一个参数的形式传递给回调函数,否则,将第一个参数设置为 null。 88 | 如果你已经将所有输出的内容传递给了 `this.push()` 那么,你可以省略回调函数的第二个参数。 89 | 90 | 通常,一个 gulp 插件会更新 `file.contents` 然后,选择以下一个: 91 | 92 | - call `callback(null, file)` 93 | _或_ 94 | - 调用 `this.push(file)` 95 | 96 | 如果一个插件为一个输入的文件创建了多个输出文件,它会多次调用 `this.push()` - 如: 97 | 98 | ```js 99 | module.exports = function() { 100 | /** 101 | * @this {Transform} 102 | */ 103 | var transform = function(file, encoding, callback) { 104 | var files = splitFile(file); 105 | this.push(files[0]); 106 | this.push(files[1]); 107 | callback(); 108 | }; 109 | 110 | return through.obj(transform); 111 | }; 112 | ``` 113 | 114 | [gulp-unzip](https://github.com/suisho/gulp-unzip/blob/master/index.js) 插件提供了一个很好的例子,来演示如何多次调用 `push()`。它同时也使用了一个 chunk transform stream 和 _在_ Vinyl transform 函数中. `_flush()` 函数 115 | 116 | Vinyl 文件可以通过三种不同形式来访问文件内容: 117 | 118 | - [Streams](dealing-with-streams.md) 119 | - [Buffers](using-buffers.md) 120 | - 空 (null) - 对于删除, 清理, 等操作来说,会很有用,因为这时候内容是不需要处理的。 121 | 122 | 以下是一个简单的例子来展示如何检测和处理每一种形式,更多的细节请参考上面的链接。 123 | 124 | ```js 125 | var PluginError = require('gulp-util').PluginError; 126 | 127 | // 常量声明 128 | var PLUGIN_NAME = 'gulp-example'; 129 | 130 | module.exports = function() { 131 | return through.obj(function(file, encoding, callback) { 132 | if (file.isNull()) { 133 | // 不做处理 134 | return callback(null, file); 135 | } 136 | 137 | if (file.isStream()) { 138 | // file.contents 是一个 Stream - https://nodejs.org/api/stream.html 139 | this.emit('error', new PluginError(PLUGIN_NAME, 'Streams not supported!')); 140 | 141 | // 或者, 你可以这样处理: 142 | //file.contents = file.contents.pipe(... 143 | //return callback(null, file); 144 | } else if (file.isBuffer()) { 145 | // file.contents 是 Buffer - https://nodejs.org/api/buffer.html 146 | this.emit('error', new PluginError(PLUGIN_NAME, 'Buffers not supported!')); 147 | 148 | // 或者, 你可以这样处理: 149 | //file.contents = ... 150 | //return callback(null, file); 151 | } 152 | }); 153 | }; 154 | ``` 155 | 156 | 注意:当你查看其他的插件的代码时候(以及上面的例子),你会注意到, transform 函数会返回 callback: 157 | 158 | ```js 159 | return callback(null, file); 160 | ``` 161 | 162 | ...不要被困扰 - gulp 会忽略所有 transform 函数返回的结果,上面的例子只是以下代码的简写方式: 163 | 164 | ```js 165 | if (someCondition) { 166 | callback(null, file); 167 | return; 168 | } 169 | // 进一步执行... 170 | ``` 171 | 172 | ## 有用的资源 173 | 174 | * [File object](https://github.com/gulpjs/gulp-util/#new-fileobj) 175 | * [PluginError](https://github.com/gulpjs/gulp-util#new-pluginerrorpluginname-message-options) 176 | * [event-stream](https://github.com/dominictarr/event-stream) 177 | * [BufferStream](https://github.com/nfroidure/BufferStream) 178 | * [gulp-util](https://github.com/gulpjs/gulp-util) 179 | 180 | 181 | ## 插件范例 182 | 183 | * [sindresorhus' gulp plugins](https://github.com/search?q=%40sindresorhus+gulp-) 184 | * [contra's gulp plugins](https://github.com/search?q=%40contra+gulp-) 185 | * [gulp-replace](https://github.com/lazd/gulp-replace) 186 | 187 | 188 | ## 关于 streams 189 | 190 | 如果你不熟悉 stream,你可以阅读这些来 191 | 192 | * https://github.com/substack/stream-handbook (必读) 193 | * http://nodejs.org/api/stream.html 194 | 195 | 其他的一些为 gulp 创建的和使用的,但又并非通过 stream 去处理的库,在 npm 上都会被打上 [gulpfriendly](https://npmjs.org/browse/keyword/gulpfriendly) 标签。 196 | -------------------------------------------------------------------------------- /writing-a-plugin/dealing-with-streams.md: -------------------------------------------------------------------------------- 1 | # 使用 Stream 处理 2 | 3 | > 极力推荐让你所写的插件支持 stream。这里有一些关于让插件支持 stream 的一些有用信息。 4 | 5 | > 请确保使用处理错误的最佳实践,并且加入一行代码,使得 gulp 能在转换内容的期间在捕获到第一个错误时候正确报出错误。 6 | 7 | [编写插件](README.md) > 编写以 stream 为基础的插件 8 | 9 | ## 使用 stream 处理 10 | 11 | 让我们来实现一个用于在文件头部插入一些文本的插件,这个插件支持 file.contents 所有可能的形式。 12 | 13 | ```js 14 | var through = require('through2'); 15 | var gutil = require('gulp-util'); 16 | var PluginError = gutil.PluginError; 17 | 18 | // 常量 19 | const PLUGIN_NAME = 'gulp-prefixer'; 20 | 21 | function prefixStream(prefixText) { 22 | var stream = through(); 23 | stream.write(prefixText); 24 | return stream; 25 | } 26 | 27 | // 插件级别函数 (处理文件) 28 | function gulpPrefixer(prefixText) { 29 | if (!prefixText) { 30 | throw new PluginError(PLUGIN_NAME, 'Missing prefix text!'); 31 | } 32 | 33 | prefixText = new Buffer(prefixText); // 预先分配 34 | 35 | // 创建一个让每个文件通过的 stream 通道 36 | var stream = through.obj(function(file, enc, cb) { 37 | if (file.isBuffer()) { 38 | this.emit('error', new PluginError(PLUGIN_NAME, 'Buffers not supported!')); 39 | return cb(); 40 | } 41 | 42 | if (file.isStream()) { 43 | // 定义转换内容的 streamer 44 | var streamer = prefixStream(prefixText); 45 | // 从 streamer 中捕获错误,并发出一个 gulp的错误 46 | streamer.on('error', this.emit.bind(this, 'error')); 47 | // 开始转换 48 | file.contents = file.contents.pipe(streamer); 49 | } 50 | 51 | // 确保文件进去下一个插件 52 | this.push(file); 53 | // 告诉 stream 转换工作完成 54 | cb(); 55 | }); 56 | 57 | // 返回文件 stream 58 | return stream; 59 | } 60 | 61 | // 暴露(export)插件的主函数 62 | module.exports = gulpPrefixer; 63 | ``` 64 | 65 | 上面的插件可以像这样使用: 66 | 67 | ```js 68 | var gulp = require('gulp'); 69 | var gulpPrefixer = require('gulp-prefixer'); 70 | 71 | gulp.src('files/**/*.js', { buffer: false }) 72 | .pipe(gulpPrefixer('prepended string')) 73 | .pipe(gulp.dest('modified-files')); 74 | ``` 75 | 76 | ## 一些使用 stream 的插件 77 | 78 | * [gulp-svgicons2svgfont](https://github.com/nfroidure/gulp-svgiconstosvgfont) 79 | 80 | -------------------------------------------------------------------------------- /writing-a-plugin/guidelines.md: -------------------------------------------------------------------------------- 1 | # 指导 2 | 3 | > 这个指导实际上不是必须的,但是我们强烈建议每一个人来遵守。因为没有人会喜欢用一个不好的插件。这个指导能在某种意义上确保你的插件能很好的适应 gulp,以此来让你的生活变得更将轻松。 4 | 5 | [编写插件](README.md) > 指导 6 | 7 | 1. 你的插件不应该去做一些现有 node 模块已经能很容易做到的事情 8 | - 比如:删除一个文件夹并不需要做成一个 gulp 插件,在 task 里使用一个类似 [del](https://github.com/sindresorhus/del) 这样的插件即可。 9 | - 只是为了封装而封装一些的东西进去,这只会增加很多低质量的插件到生态中,这不符合 gulp 的期望。 10 | - gulp 插件都是以文件为基础操作的,如果你发现你正在把一些很复杂的操作塞进 stream 中去,那么,请直接写一个 node 模块就好。 11 | - 一个好的 gulp 插件例子像是 gulp-coffee,coffee-script 模块并不能直接和 vinyl 做很好的适配,因此,才去封装它来使用相应的功能,并且将一些比较痛苦的操作抽象出来,做成更简单的 gulp 插件来使用。 12 | 1. 你的插件应该只做**一件事**,并且做好。 13 | - 避免使用配置选项,使得你的插件能胜任不同场合的任务。 14 | - 比如:一个 JS 压缩插件不应该有一个加头部的选项 15 | 1. 你的插件不能去做一些其他插件做的事: 16 | - 不应该去拼接,用 [gulp-concat](https://github.com/contra/gulp-concat) 去做 17 | - 不应该去增加头部,用 [gulp-header](https://github.com/godaddy/gulp-header) 去做 18 | - 不应该去增加尾部,用 [gulp-footer](https://github.com/godaddy/gulp-footer) 去做 19 | - 如果是一个常用的可选的操作,那么,请在文档中注明你的插件通常和其他某个插件一起使用 20 | - 在你的插件中使用其他的插件,这能大大减少你的代码量,并保证生态系统的稳定。 21 | 1. 你的插件必须被**测试过** 22 | - 测试一个插件很简单,你甚至不需要 gulp 就能测试 23 | - 参考其他的插件是怎么做的 24 | 1. 在 `package.json` 中增加一个名为 `gulpplugin` 的关键字,这可以让它能在我们的搜索中出现 25 | 1. 不要在 stream 里面抛出错误 26 | - 你应该以触发 **error** 事件来代替 27 | - 如果你在 stream 外面遇到错误,比如在创建 stream 时候发现错误的配置选项等,那么你应该抛出它。 28 | 1. 错误需要加上以你插件名字作为前缀 29 | - 比如: `gulp-replace: Cannot do regexp replace on a stream` 30 | - 使用 gulp-util 的 [PluginError](https://github.com/gulpjs/gulp-util#new-pluginerrorpluginname-message-options) 类来完成它 31 | 1. `file.contents` 的类型需要总是在输入输出中保持一致 32 | - 如果 file.contents 为空 (不可读) 请将他忽略,并传过去 33 | - 如果 file.contents 是一个 stream,但是你不支持,那么请触发一个错误 34 | - 不要把 stream 硬转成 buffer 来使你的插件支持 stream,这会引发很严重的问题。 35 | 1. 在你处理完成之前,不要将 `file` 传到下游去 36 | 1. 使用 [`file.clone()`](https://github.com/gulpjs/vinyl#clone) 来复制一个文件或者创建另一个以此为基础的文件 37 | 1. 使用我们 [模块推荐页](recommended-modules.md) 上列举的模块来让你的开发更加轻松 38 | 1. 不要把 `gulp` 作为一个依赖 39 | - 使用 gulp 来测试你的插件的工作流这的确很酷,但请务必确保你将它放到 devDependency 中 40 | - 在你的插件中依赖 gulp,这意味着安装你的插件的用户将会重新安装一遍 gulp 以及所有它所依赖的东西。 41 | - 没有任何理由说明你需要将 gulp 写到你的插件代码中去,如果你发现你必须这么做,那么请开一个 issue,我们会帮你解决。 42 | 43 | ## 为什么这些指导这么严格? 44 | 45 | gulp 的目标是为了让用户觉得简单,通过提供一些严格的指导,我们就能提供一致并且高质量的生态系统给大家。不过,这确实给插件作者增加了一些需要考虑的东西,但是也确保了后面的问题会更少。 46 | 47 | ### 如果我不遵守这些,会发生什么? 48 | 49 | npm 对每个人来说是免费的,你可以开发任何你想要开发的东西出来,并且不需要遵守这个规定。我们承诺测试机制将会很快建立起来,并且加入我们的插件搜索中。如果你坚持不遵守插件导览,那么这会反应在我们的打分/排名系统上,人们都会更加喜欢去使用一个 "更加 gulp" 的插件。 50 | 51 | ### 一个插件大概会是怎么样的? 52 | 53 | ```js 54 | // through2 是一个对 node 的 transform streams 简单封装 55 | var through = require('through2'); 56 | var gutil = require('gulp-util'); 57 | var PluginError = gutil.PluginError; 58 | 59 | // 常量 60 | const PLUGIN_NAME = 'gulp-prefixer'; 61 | 62 | function prefixStream(prefixText) { 63 | var stream = through(); 64 | stream.write(prefixText); 65 | return stream; 66 | } 67 | 68 | // 插件级别函数 (处理文件) 69 | function gulpPrefixer(prefixText) { 70 | 71 | if (!prefixText) { 72 | throw new PluginError(PLUGIN_NAME, 'Missing prefix text!'); 73 | } 74 | prefixText = new Buffer(prefixText); // 预先分配 75 | 76 | // 创建一个让每个文件通过的 stream 通道 77 | return through.obj(function(file, enc, cb) { 78 | if (file.isNull()) { 79 | // 返回空文件 80 | cb(null, file); 81 | } 82 | if (file.isBuffer()) { 83 | file.contents = Buffer.concat([prefixText, file.contents]); 84 | } 85 | if (file.isStream()) { 86 | file.contents = file.contents.pipe(prefixStream(prefixText)); 87 | } 88 | 89 | cb(null, file); 90 | 91 | }); 92 | 93 | }; 94 | 95 | // 暴露(export)插件主函数 96 | module.exports = gulpPrefixer; 97 | ``` 98 | -------------------------------------------------------------------------------- /writing-a-plugin/recommended-modules.md: -------------------------------------------------------------------------------- 1 | # 模块推荐 2 | 3 | > 遵循这个推荐的模块列表,将会保证你不违反导览的规范,并且保证插件之间的一致性 4 | 5 | [编写插件](README.md) > 模块推荐 6 | 7 | #### 替换文件后缀名 8 | 9 | 使用 [replace-ext](https://github.com/wearefractal/replace-ext) 10 | 11 | #### 错误 12 | 13 | 使用 [BetterError](https://github.com/contra/BetterError) 如果完成了的话。 14 | 15 | #### 字符串颜色 16 | 17 | 使用 [chalk](https://github.com/sindresorhus/chalk) 18 | 19 | #### 时间格式 20 | 21 | 使用 [dateformat](https://github.com/felixge/node-dateformat) 22 | 23 | 以 `HH:MM:ss` 格式显示 24 | -------------------------------------------------------------------------------- /writing-a-plugin/testing.md: -------------------------------------------------------------------------------- 1 | # 测试 2 | 3 | > 测试是保证你的插件质量的唯一途径。这能使你的用户有信心去使用,且能让你更加轻松。 4 | 5 | [编写插件](README.md) > Testing 6 | 7 | 8 | ## 工具 9 | 10 | 大多数的插件使用 [mocha](https://github.com/visionmedia/mocha),[should](https://github.com/visionmedia/should.js) 以及 [event-stream](https://github.com/dominictarr/event-stream) 来做测试。下面的例子也将会使用这些工具。 11 | 12 | 13 | ## 测试插件的流处理(streaming)模式 14 | 15 | ```js 16 | var assert = require('assert'); 17 | var es = require('event-stream'); 18 | var File = require('vinyl'); 19 | var prefixer = require('../'); 20 | 21 | describe('gulp-prefixer', function() { 22 | describe('in streaming mode', function() { 23 | 24 | it('should prepend text', function(done) { 25 | 26 | // 创建伪文件 27 | var fakeFile = new File({ 28 | contents: es.readArray(['stream', 'with', 'those', 'contents']) 29 | }); 30 | 31 | // 创建一个 prefixer 流(stream) 32 | var myPrefixer = prefixer('prependthis'); 33 | 34 | // 将伪文件写入 35 | myPrefixer.write(fakeFile); 36 | 37 | // 等文件重新出来 38 | myPrefixer.once('data', function(file) { 39 | // 确保它以相同的方式出来 40 | assert(file.isStream()); 41 | 42 | // 缓存内容来确保它已经被处理过(加前缀内容) 43 | file.contents.pipe(es.wait(function(err, data) { 44 | // 检查内容 45 | assert.equal(data, 'prependthisstreamwiththosecontents'); 46 | done(); 47 | })); 48 | }); 49 | 50 | }); 51 | 52 | }); 53 | }); 54 | ``` 55 | 56 | 57 | ## 测试插件的 buffer 模式 58 | 59 | ```js 60 | var assert = require('assert'); 61 | var es = require('event-stream'); 62 | var File = require('vinyl'); 63 | var prefixer = require('../'); 64 | 65 | describe('gulp-prefixer', function() { 66 | describe('in buffer mode', function() { 67 | 68 | it('should prepend text', function(done) { 69 | 70 | // 创建伪文件 71 | var fakeFile = new File({ 72 | contents: new Buffer('abufferwiththiscontent') 73 | }); 74 | 75 | // 创建一个 prefixer 流(stream) 76 | var myPrefixer = prefixer('prependthis'); 77 | 78 | // 将伪文件写入 79 | myPrefixer.write(fakeFile); 80 | 81 | // 等文件重新出来 82 | myPrefixer.once('data', function(file) { 83 | // 确保它以相同的方式出来 84 | assert(file.isBuffer()); 85 | 86 | // 检查内容 87 | assert.equal(file.contents.toString('utf8'), 'prependthisabufferwiththiscontent'); 88 | done(); 89 | }); 90 | 91 | }); 92 | 93 | }); 94 | }); 95 | ``` 96 | 97 | 98 | ## 一些拥有高质量的测试用例的插件 99 | 100 | * [gulp-cat](https://github.com/ben-eb/gulp-cat/blob/master/test.js) 101 | * [gulp-concat](https://github.com/contra/gulp-concat/blob/master/test/main.js) 102 | -------------------------------------------------------------------------------- /writing-a-plugin/using-buffers.md: -------------------------------------------------------------------------------- 1 | # 使用 buffer 2 | 3 | > 这里有些关于如何创建一个使用 buffer 来处理的插件的有用信息。 4 | 5 | [编写插件](README.md) > 使用 buffer 6 | 7 | ## 使用 buffer 8 | 9 | 如果你的插件依赖着一个基于 buffer 处理的库,你可能会选择让你的插件以 buffer 的形式来处理 file.contents。让我们来实现一个在文件头部插入额外文本的插件: 10 | 11 | ```js 12 | var through = require('through2'); 13 | var gutil = require('gulp-util'); 14 | var PluginError = gutil.PluginError; 15 | 16 | // 常量 17 | const PLUGIN_NAME = 'gulp-prefixer'; 18 | 19 | // 插件级别的函数(处理文件) 20 | function gulpPrefixer(prefixText) { 21 | if (!prefixText) { 22 | throw new PluginError(PLUGIN_NAME, 'Missing prefix text!'); 23 | } 24 | 25 | prefixText = new Buffer(prefixText); // 提前分配 26 | 27 | // 创建一个 stream 通道,以让每个文件通过 28 | var stream = through.obj(function(file, enc, cb) { 29 | if (file.isStream()) { 30 | this.emit('error', new PluginError(PLUGIN_NAME, 'Streams are not supported!')); 31 | return cb(); 32 | } 33 | 34 | if (file.isBuffer()) { 35 | file.contents = Buffer.concat([prefixText, file.contents]); 36 | } 37 | 38 | // 确保文件进入下一个 gulp 插件 39 | this.push(file); 40 | 41 | // 告诉 stream 引擎,我们已经处理完了这个文件 42 | cb(); 43 | }); 44 | 45 | // 返回文件 stream 46 | return stream; 47 | }; 48 | 49 | // 导出插件主函数 50 | module.exports = gulpPrefixer; 51 | ``` 52 | 53 | 上述的插件可以这样使用: 54 | 55 | ```js 56 | var gulp = require('gulp'); 57 | var gulpPrefixer = require('gulp-prefixer'); 58 | 59 | gulp.src('files/**/*.js') 60 | .pipe(gulpPrefixer('prepended string')) 61 | .pipe(gulp.dest('modified-files')); 62 | ``` 63 | 64 | ## 处理 stream 65 | 66 | 不幸的是,当 gulp.src 如果是以 stream 的形式,而不是 buffer,那么,上面的插件就会报错。如果可以,你也应该让他支持 stream 形式。请查看[使用 Stream 处理](dealing-with-streams.md) 获取更多信息。 67 | 68 | ## 一些基于 buffer 的插件 69 | 70 | * [gulp-coffee](https://github.com/contra/gulp-coffee) 71 | * [gulp-svgmin](https://github.com/ben-eb/gulp-svgmin) 72 | * [gulp-marked](https://github.com/lmtm/gulp-marked) 73 | * [gulp-svg2ttf](https://github.com/nfroidure/gulp-svg2ttf) 74 | --------------------------------------------------------------------------------