├── test ├── fixtures │ ├── stuff │ │ ├── run.dmc │ │ └── test.dmc │ ├── test.coffee │ ├── test │ │ └── run.jade │ └── copy │ │ └── example.txt ├── .eslintrc ├── taskTree.js ├── tasks.js ├── dest.js ├── src.js └── watch.js ├── .jscsrc ├── .eslintrc ├── .npmignore ├── .gitignore ├── .travis.yml ├── .editorconfig ├── completion ├── fish ├── README.md ├── zsh ├── bash └── powershell ├── lib ├── taskTree.js └── completion.js ├── docs ├── recipes │ ├── rebuild-only-files-that-change.md │ ├── exports-as-tasks.md │ ├── specifying-a-cwd.md │ ├── minified-and-non-minified.md │ ├── pass-arguments-from-cli.md │ ├── using-multiple-sources-in-one-task.md │ ├── mocha-test-runner-with-gulp.md │ ├── templating-with-swig-and-yaml-front-matter.md │ ├── only-pass-through-changed-files.md │ ├── using-external-config-file.md │ ├── combining-streams-to-handle-errors.md │ ├── handling-the-delete-event-on-watch.md │ ├── split-tasks-across-multiple-files.md │ ├── browserify-uglify-sourcemap.md │ ├── browserify-multiple-destination.md │ ├── browserify-transforms.md │ ├── maintain-directory-structure-while-globbing.md │ ├── run-grunt-tasks-from-gulp.md │ ├── README.md │ ├── incremental-builds-with-concatenate.md │ ├── running-task-steps-per-folder.md │ ├── sharing-streams-with-stream-factories.md │ ├── fast-browserify-builds-with-watchify.md │ ├── rollup-with-rollup-stream.md │ ├── delete-files-folder.md │ ├── browserify-with-globs.md │ ├── running-tasks-in-series.md │ ├── automate-release-workflow.md │ ├── server-with-livereload-and-css-injection.md │ └── make-stream-from-buffer.md ├── writing-a-plugin │ ├── recommended-modules.md │ ├── using-buffers.md │ ├── dealing-with-streams.md │ ├── testing.md │ ├── guidelines.md │ └── README.md ├── getting-started.md ├── CLI.md ├── FAQ.md ├── README.md └── API.md ├── LICENSE ├── index.js ├── package.json ├── CONTRIBUTING.md ├── README.md ├── bin └── gulp.js └── CHANGELOG.md /test/fixtures/stuff/run.dmc: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/fixtures/stuff/test.dmc: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.jscsrc: -------------------------------------------------------------------------------- 1 | { 2 | "preset": "gulp", 3 | } 4 | -------------------------------------------------------------------------------- /test/fixtures/test.coffee: -------------------------------------------------------------------------------- 1 | this is a test -------------------------------------------------------------------------------- /test/fixtures/test/run.jade: -------------------------------------------------------------------------------- 1 | test template -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "gulp" 3 | } 4 | -------------------------------------------------------------------------------- /test/fixtures/copy/example.txt: -------------------------------------------------------------------------------- 1 | this is a test -------------------------------------------------------------------------------- /test/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "gulp/test" 3 | } 4 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | *.log 3 | node_modules 4 | build 5 | *.node 6 | components 7 | coverage 8 | *.orig 9 | .idea 10 | sandbox 11 | test/out-fixtures/* 12 | test/watch-*.txt 13 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | *.log 3 | node_modules 4 | build 5 | *.node 6 | components 7 | coverage 8 | *.orig 9 | .idea 10 | sandbox 11 | test/out-fixtures/* 12 | test/watch-*.txt 13 | gulp.1 14 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | language: node_js 3 | node_js: 4 | - "0.10" 5 | - "0.12" 6 | - "4" 7 | - "5" 8 | - "6" 9 | after_script: 10 | - npm run coveralls 11 | git: 12 | depth: 10 13 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | charset = utf-8 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true 11 | 12 | [*.md] 13 | trim_trailing_whitespace = false 14 | -------------------------------------------------------------------------------- /completion/fish: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env fish 2 | 3 | # Usage: 4 | # 5 | # To enable fish completion for gulp, add the following line to 6 | # your ~/.config/fish/config.fish file: 7 | # 8 | # gulp --completion=fish | source 9 | 10 | complete -c gulp -a "(gulp --tasks-simple)" -f 11 | -------------------------------------------------------------------------------- /lib/taskTree.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = function(tasks) { 4 | return Object.keys(tasks) 5 | .reduce(function(prev, task) { 6 | prev.nodes.push({ 7 | label: task, 8 | nodes: tasks[task].dep, 9 | }); 10 | return prev; 11 | }, { 12 | nodes: [], 13 | }); 14 | }; 15 | -------------------------------------------------------------------------------- /docs/recipes/rebuild-only-files-that-change.md: -------------------------------------------------------------------------------- 1 | # Rebuild only files that change 2 | 3 | With [`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 | -------------------------------------------------------------------------------- /lib/completion.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var fs = require('fs'); 4 | var path = require('path'); 5 | 6 | module.exports = function(name) { 7 | if (typeof name !== 'string') { 8 | throw new Error('Missing completion type'); 9 | } 10 | var file = path.join(__dirname, '../completion', name); 11 | try { 12 | console.log(fs.readFileSync(file, 'utf8')); 13 | process.exit(0); 14 | } catch (err) { 15 | console.log( 16 | 'echo "gulp autocompletion rules for', 17 | '\'' + name + '\'', 18 | 'not found"' 19 | ); 20 | process.exit(5); 21 | } 22 | }; 23 | -------------------------------------------------------------------------------- /docs/recipes/exports-as-tasks.md: -------------------------------------------------------------------------------- 1 | # Exports as Tasks 2 | 3 | Using the ES2015 module syntax you can use your exports as tasks. 4 | 5 | ```js 6 | import gulp from 'gulp'; 7 | import babel from 'gulp-babel'; 8 | 9 | // named task 10 | export function build() { 11 | return gulp.src('src/*.js') 12 | .pipe(babel()) 13 | .pipe(gulp.dest('lib')); 14 | } 15 | 16 | // default task 17 | export default function dev() { 18 | gulp.watch('src/*.js', ['build']); 19 | } 20 | ``` 21 | 22 | This will **not** work with the gulp-cli version bundled with gulp 3.x. You must use the latest published version. 23 | -------------------------------------------------------------------------------- /docs/recipes/specifying-a-cwd.md: -------------------------------------------------------------------------------- 1 | # Specifying a new cwd (current working directory) 2 | 3 | This is helpful for projects using a nested directory structure, such as: 4 | 5 | ``` 6 | /project 7 | /layer1 8 | /layer2 9 | ``` 10 | 11 | You can use the gulp CLI option `--cwd`. 12 | 13 | From the `project/` directory: 14 | 15 | ```sh 16 | gulp --cwd layer1 17 | ``` 18 | 19 | If you only need to specify a cwd for a certain glob, you can use the `cwd` option on a [glob-stream](https://github.com/gulpjs/glob-stream): 20 | 21 | ```js 22 | gulp.src('./some/dir/**/*.js', { cwd: 'public' }); 23 | ``` 24 | -------------------------------------------------------------------------------- /completion/README.md: -------------------------------------------------------------------------------- 1 | # Completion for gulp 2 | > Thanks to grunt team and Tyler Kellen 3 | 4 | To enable tasks auto-completion in shell you should add `eval "$(gulp --completion=shell)"` in your `.shellrc` file. 5 | 6 | ## Bash 7 | 8 | Add `eval "$(gulp --completion=bash)"` to `~/.bashrc`. 9 | 10 | ## Zsh 11 | 12 | Add `eval "$(gulp --completion=zsh)"` to `~/.zshrc`. 13 | 14 | ## Powershell 15 | 16 | Add `Invoke-Expression ((gulp --completion=powershell) -join [System.Environment]::NewLine)` to `$PROFILE`. 17 | 18 | ## Fish 19 | 20 | Add `gulp --completion=fish | source` to `~/.config/fish/config.fish`. 21 | -------------------------------------------------------------------------------- /completion/zsh: -------------------------------------------------------------------------------- 1 | #!/bin/zsh 2 | 3 | # Borrowed from grunt-cli 4 | # http://gruntjs.com/ 5 | # 6 | # Copyright (c) 2012 Tyler Kellen, contributors 7 | # Licensed under the MIT license. 8 | # https://github.com/gruntjs/grunt/blob/master/LICENSE-MIT 9 | 10 | # Usage: 11 | # 12 | # To enable zsh completion for gulp, add the following line (minus the 13 | # leading #, which is the zsh comment character) to your ~/.zshrc file: 14 | # 15 | # eval "$(gulp --completion=zsh)" 16 | 17 | # Enable zsh autocompletion. 18 | function _gulp_completion() { 19 | # Grab tasks 20 | compls=$(gulp --tasks-simple) 21 | completions=(${=compls}) 22 | compadd -- $completions 23 | } 24 | 25 | compdef _gulp_completion gulp 26 | -------------------------------------------------------------------------------- /docs/writing-a-plugin/recommended-modules.md: -------------------------------------------------------------------------------- 1 | # Recommended Modules 2 | 3 | > Sticking to this curated list of recommended modules will make sure you don't violate the plugin guidelines and ensure consistency across plugins. 4 | 5 | [Writing a Plugin](README.md) > Recommended Modules 6 | 7 | #### Replacing a file extension 8 | 9 | Use [replace-ext](https://github.com/wearefractal/replace-ext) 10 | 11 | #### Errors 12 | 13 | Use [BetterError](https://github.com/contra/BetterError) when it is finished 14 | 15 | #### String colors 16 | 17 | Use [chalk](https://github.com/sindresorhus/chalk) 18 | 19 | #### Date formatting 20 | 21 | Use [dateformat](https://github.com/felixge/node-dateformat) 22 | 23 | Display as `HH:MM:ss` 24 | -------------------------------------------------------------------------------- /docs/recipes/minified-and-non-minified.md: -------------------------------------------------------------------------------- 1 | # Output both a minified and non-minified version 2 | 3 | Outputting both a minified and non-minified version of your combined JavaScript files can be achieved by using `gulp-rename` and piping to `dest` twice (once before minifying and once after minifying): 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 | // This will output the non-minified version 17 | .pipe(gulp.dest(DEST)) 18 | // This will minify and rename to foo.min.js 19 | .pipe(uglify()) 20 | .pipe(rename({ extname: '.min.js' })) 21 | .pipe(gulp.dest(DEST)); 22 | }); 23 | 24 | ``` 25 | -------------------------------------------------------------------------------- /docs/recipes/pass-arguments-from-cli.md: -------------------------------------------------------------------------------- 1 | # Pass arguments from the command line 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())) // only minify in production 22 | .pipe(gulp.dest('dist')); 23 | }); 24 | ``` 25 | 26 | Then run gulp with: 27 | 28 | ```sh 29 | $ gulp scripts --env development 30 | ``` 31 | -------------------------------------------------------------------------------- /completion/bash: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Borrowed from grunt-cli 4 | # http://gruntjs.com/ 5 | # 6 | # Copyright (c) 2012 Tyler Kellen, contributors 7 | # Licensed under the MIT license. 8 | # https://github.com/gruntjs/grunt/blob/master/LICENSE-MIT 9 | 10 | # Usage: 11 | # 12 | # To enable bash completion for gulp, add the following line (minus the 13 | # leading #, which is the bash comment character) to your ~/.bashrc file: 14 | # 15 | # eval "$(gulp --completion=bash)" 16 | 17 | # Enable bash autocompletion. 18 | function _gulp_completions() { 19 | # The currently-being-completed word. 20 | local cur="${COMP_WORDS[COMP_CWORD]}" 21 | #Grab tasks 22 | local compls=$(gulp --tasks-simple) 23 | # Tell complete what stuff to show. 24 | COMPREPLY=($(compgen -W "$compls" -- "$cur")) 25 | } 26 | 27 | complete -o default -F _gulp_completions gulp 28 | -------------------------------------------------------------------------------- /docs/recipes/using-multiple-sources-in-one-task.md: -------------------------------------------------------------------------------- 1 | # Using multiple sources in one task 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` will emit files in the order they were added: 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 | -------------------------------------------------------------------------------- /test/taskTree.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var taskTree = require('../lib/taskTree'); 4 | var should = require('should'); 5 | 6 | require('mocha'); 7 | 8 | describe('taskTree()', function() { 9 | it('should form a tree properly', function(done) { 10 | should.exist(taskTree); // Lol shutup jshint 11 | 12 | var tasks = { 13 | test: { 14 | dep: ['abc', 'def'], 15 | }, 16 | abc: { 17 | dep: ['def'], 18 | }, 19 | def: { 20 | dep: [], 21 | }, 22 | }; 23 | 24 | var expectTree = { 25 | nodes: [ 26 | { 27 | label: 'test', 28 | nodes: ['abc', 'def'], 29 | }, { 30 | label: 'abc', 31 | nodes: ['def'], 32 | }, { 33 | label: 'def', 34 | nodes: [], 35 | }, 36 | ], 37 | }; 38 | 39 | taskTree(tasks).should.eql(expectTree); 40 | done(); 41 | }); 42 | }); 43 | -------------------------------------------------------------------------------- /docs/recipes/mocha-test-runner-with-gulp.md: -------------------------------------------------------------------------------- 1 | # Mocha test-runner with gulp 2 | 3 | ### Passing shared module in all tests 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 | ### Running mocha tests when files change 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 | -------------------------------------------------------------------------------- /docs/recipes/templating-with-swig-and-yaml-front-matter.md: -------------------------------------------------------------------------------- 1 | # Templating with Swig and YAML front-matter 2 | Templating can be setup using `gulp-swig` and `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 | -------------------------------------------------------------------------------- /docs/recipes/only-pass-through-changed-files.md: -------------------------------------------------------------------------------- 1 | # Only pass through changed files 2 | 3 | Files are passed through the whole pipe chain on every run by default. By using [gulp-changed](https://github.com/sindresorhus/gulp-changed) only changed files will be passed through. This can speed up consecutive runs considerably. 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 | // we define some constants here so they can be reused 15 | var SRC = 'src/*.js'; 16 | var DEST = 'dist'; 17 | 18 | gulp.task('default', function() { 19 | return gulp.src(SRC) 20 | // the `changed` task needs to know the destination directory 21 | // upfront to be able to figure out which files changed 22 | .pipe(changed(DEST)) 23 | // only files that has changed will pass through here 24 | .pipe(jscs()) 25 | .pipe(uglify()) 26 | .pipe(gulp.dest(DEST)); 27 | }); 28 | ``` 29 | -------------------------------------------------------------------------------- /docs/recipes/using-external-config-file.md: -------------------------------------------------------------------------------- 1 | # Using external config file 2 | 3 | Beneficial because it's keeping tasks DRY and config.json can be used by another task runner, like `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 | -------------------------------------------------------------------------------- /docs/recipes/combining-streams-to-handle-errors.md: -------------------------------------------------------------------------------- 1 | # Combining streams to handle errors 2 | 3 | By default, emitting an error on a stream will cause it to be thrown unless it already has a listener attached to the `error` event. This gets a bit tricky when you're working with longer pipelines of streams. 4 | 5 | By using [stream-combiner2](https://github.com/substack/stream-combiner2) you can turn a series of streams into a single stream, meaning you only need to listen to the `error` event in one place in your code. 6 | 7 | Here's an example of using it in a 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 | // any errors in the above streams will get caught 22 | // by this listener, instead of being thrown: 23 | combined.on('error', console.error.bind(console)); 24 | 25 | return combined; 26 | }); 27 | ``` 28 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2013-2016 Fractal 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining 6 | a copy of this software and associated documentation files (the 7 | "Software"), to deal in the Software without restriction, including 8 | without limitation the rights to use, copy, modify, merge, publish, 9 | distribute, sublicense, and/or sell copies of the Software, and to 10 | permit persons to whom the Software is furnished to do so, subject to 11 | the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be 14 | included in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 19 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 20 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 22 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /docs/recipes/handling-the-delete-event-on-watch.md: -------------------------------------------------------------------------------- 1 | # Handling the Delete Event on Watch 2 | 3 | You can listen for `'change'` events to fire on the watcher returned from `gulp.watch`. 4 | 5 | Each change event has a `type` property. If `type` is `'deleted'`, you can delete the file 6 | from your destination directory, using something like: 7 | 8 | ```js 9 | 'use strict'; 10 | 11 | var del = require('del'); 12 | var path = require('path'); 13 | var gulp = require('gulp'); 14 | var header = require('gulp-header'); 15 | var footer = require('gulp-footer'); 16 | 17 | gulp.task('scripts', function() { 18 | return gulp.src('src/**/*.js', {base: 'src'}) 19 | .pipe(header('(function () {\r\n\t\'use strict\'\r\n')) 20 | .pipe(footer('\r\n})();')) 21 | .pipe(gulp.dest('build')); 22 | }); 23 | 24 | gulp.task('watch', function () { 25 | var watcher = gulp.watch('src/**/*.js', ['scripts']); 26 | 27 | watcher.on('change', function (event) { 28 | if (event.type === 'deleted') { 29 | // Simulating the {base: 'src'} used with gulp.src in the scripts task 30 | var filePathFromSrc = path.relative(path.resolve('src'), event.path); 31 | 32 | // Concatenating the 'build' absolute path used by gulp.dest in the scripts task 33 | var destFilePath = path.resolve('build', filePathFromSrc); 34 | 35 | del.sync(destFilePath); 36 | } 37 | }); 38 | }); 39 | ``` 40 | -------------------------------------------------------------------------------- /docs/recipes/split-tasks-across-multiple-files.md: -------------------------------------------------------------------------------- 1 | # Split tasks across multiple files 2 | 3 | If your `gulpfile.js` is starting to grow too large, you can split 4 | the tasks into separate files using one of the methods below. 5 | 6 | > Be advised, that this approach is [considered deprecated][deprecated] 7 | > and could lead to problems when migrating to the `gulp 4`. 8 | 9 | 10 | ## Using `gulp-require-tasks` 11 | 12 | You can use the [gulp-require-tasks][gulp-require-tasks] 13 | module to automatically load all your tasks from the individual files. 14 | 15 | Please see the [module's README][gulp-require-tasks] for up-to-date instructions. 16 | 17 | ## Using `require-dir` 18 | 19 | You can also use the [require-dir][require-dir] module to load your tasks manually. 20 | 21 | Imagine the following file structure: 22 | 23 | ``` 24 | gulpfile.js 25 | tasks/ 26 | ├── dev.js 27 | ├── release.js 28 | └── test.js 29 | ``` 30 | 31 | Install the `require-dir` module: 32 | 33 | ```sh 34 | npm install --save-dev require-dir 35 | ``` 36 | 37 | Add the following lines to your `gulpfile.js` file: 38 | 39 | ```js 40 | var requireDir = require('require-dir'); 41 | var tasks = requireDir('./tasks'); 42 | ``` 43 | 44 | 45 | [gulp-require-tasks]: https://github.com/betsol/gulp-require-tasks 46 | [require-dir]: https://github.com/aseemk/requireDir 47 | [deprecated]: https://github.com/gulpjs/gulp/pull/1554#issuecomment-202614391 48 | -------------------------------------------------------------------------------- /docs/recipes/browserify-uglify-sourcemap.md: -------------------------------------------------------------------------------- 1 | # Browserify + Uglify2 with sourcemaps 2 | 3 | [Browserify](http://github.com/substack/node-browserify) has become an important and indispensable 4 | tool but requires being wrapped before working well with gulp. Below is a simple recipe for using 5 | Browserify with full sourcemaps that resolve to the original individual files. 6 | 7 | See also: the [Combining Streams to Handle Errors](https://github.com/gulpjs/gulp/blob/master/docs/recipes/combining-streams-to-handle-errors.md) recipe for handling errors with browserify or uglify in your stream. 8 | 9 | ``` javascript 10 | 'use strict'; 11 | 12 | var browserify = require('browserify'); 13 | var gulp = require('gulp'); 14 | var source = require('vinyl-source-stream'); 15 | var buffer = require('vinyl-buffer'); 16 | var uglify = require('gulp-uglify'); 17 | var sourcemaps = require('gulp-sourcemaps'); 18 | var gutil = require('gulp-util'); 19 | 20 | gulp.task('javascript', function () { 21 | // set up the browserify instance on a task basis 22 | var b = browserify({ 23 | entries: './entry.js', 24 | debug: true 25 | }); 26 | 27 | return b.bundle() 28 | .pipe(source('app.js')) 29 | .pipe(buffer()) 30 | .pipe(sourcemaps.init({loadMaps: true})) 31 | // Add transformation tasks to the pipeline here. 32 | .pipe(uglify()) 33 | .on('error', gutil.log) 34 | .pipe(sourcemaps.write('./')) 35 | .pipe(gulp.dest('./dist/js/')); 36 | }); 37 | ``` 38 | -------------------------------------------------------------------------------- /docs/recipes/browserify-multiple-destination.md: -------------------------------------------------------------------------------- 1 | # Browserify + Globs (multiple destination) 2 | 3 | This example shows how to set up a task of bundling multiple entry points into multiple destinations using browserify. 4 | 5 | The below `js` task bundles all the `.js` files under `src/` as entry points and writes the results under `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}) // no need of reading file because browserify does. 20 | 21 | // transform file objects using gulp-tap plugin 22 | .pipe(tap(function (file) { 23 | 24 | gutil.log('bundling ' + file.path); 25 | 26 | // replace file contents with browserify's bundle stream 27 | file.contents = browserify(file.path, {debug: true}).bundle(); 28 | 29 | })) 30 | 31 | // transform streaming contents into buffer contents (because gulp-sourcemaps does not support streaming contents) 32 | .pipe(buffer()) 33 | 34 | // load and init sourcemaps 35 | .pipe(sourcemaps.init({loadMaps: true})) 36 | 37 | .pipe(uglify()) 38 | 39 | // write sourcemaps 40 | .pipe(sourcemaps.write('./')) 41 | 42 | .pipe(gulp.dest('dest')); 43 | 44 | }); 45 | ``` 46 | -------------------------------------------------------------------------------- /docs/getting-started.md: -------------------------------------------------------------------------------- 1 | # Getting Started 2 | 3 | #### 1. Install gulp globally: 4 | 5 | __If you have previously installed a version of gulp globally, please run `npm rm --global gulp` 6 | to make sure your old version doesn't collide with gulp-cli.__ 7 | 8 | ```sh 9 | $ npm install --global gulp-cli 10 | ``` 11 | 12 | #### 2. Initialize your project directory: 13 | 14 | ```sh 15 | $ npm init 16 | ``` 17 | 18 | #### 3. Install gulp in your project devDependencies: 19 | 20 | ```sh 21 | $ npm install --save-dev gulp 22 | ``` 23 | 24 | #### 4. Create a `gulpfile.js` at the root of your project: 25 | 26 | ```js 27 | var gulp = require('gulp'); 28 | 29 | gulp.task('default', function() { 30 | // place code for your default task here 31 | }); 32 | ``` 33 | 34 | #### 5. Run gulp: 35 | 36 | ```sh 37 | $ gulp 38 | ``` 39 | 40 | The default task will run and do nothing. 41 | 42 | To run individual tasks, use `gulp `. 43 | 44 | ## Where do I go now? 45 | 46 | You have an empty gulpfile and everything is installed. How do you REALLY get started? Check out the [recipes](recipes) and the [list of articles](README.md#articles) for more information. 47 | 48 | ## .src, .watch, .dest, CLI args - How do I use these things? 49 | 50 | For API specific documentation you can check out the [documentation for that](API.md). 51 | 52 | ## Available Plugins 53 | 54 | The gulp community is growing, with new plugins being added daily. See the [main website](http://gulpjs.com/plugins/) for a complete list. 55 | -------------------------------------------------------------------------------- /docs/recipes/browserify-transforms.md: -------------------------------------------------------------------------------- 1 | # Browserify + Transforms 2 | 3 | [Browserify](http://github.com/substack/node-browserify) has become an important and indispensable 4 | tool but requires being wrapped before working well with gulp. Below is a simple recipe for using 5 | Browserify with transforms. 6 | 7 | See also: the [Combining Streams to Handle Errors](https://github.com/gulpjs/gulp/blob/master/docs/recipes/combining-streams-to-handle-errors.md) recipe for handling errors with browserify or uglify in your stream. 8 | 9 | ``` javascript 10 | 'use strict'; 11 | 12 | var browserify = require('browserify'); 13 | var gulp = require('gulp'); 14 | var source = require('vinyl-source-stream'); 15 | var buffer = require('vinyl-buffer'); 16 | var gutil = require('gulp-util'); 17 | var uglify = require('gulp-uglify'); 18 | var sourcemaps = require('gulp-sourcemaps'); 19 | var reactify = require('reactify'); 20 | 21 | gulp.task('javascript', function () { 22 | // set up the browserify instance on a task basis 23 | var b = browserify({ 24 | entries: './entry.js', 25 | debug: true, 26 | // defining transforms here will avoid crashing your stream 27 | transform: [reactify] 28 | }); 29 | 30 | return b.bundle() 31 | .pipe(source('app.js')) 32 | .pipe(buffer()) 33 | .pipe(sourcemaps.init({loadMaps: true})) 34 | // Add transformation tasks to the pipeline here. 35 | .pipe(uglify()) 36 | .on('error', gutil.log) 37 | .pipe(sourcemaps.write('./')) 38 | .pipe(gulp.dest('./dist/js/')); 39 | }); 40 | ``` 41 | -------------------------------------------------------------------------------- /docs/recipes/maintain-directory-structure-while-globbing.md: -------------------------------------------------------------------------------- 1 | # Maintain Directory Structure while Globbing 2 | 3 | If you are planning to read a few files/folders from a directory and maintain their relative path, you need to pass `{base: '.'}` as the second argument to `gulp.src()`. 4 | 5 | 6 | For example, if you have a directory structure like 7 | 8 | ![Dev setup](https://cloud.githubusercontent.com/assets/2562992/3178498/bedf75b4-ec1a-11e3-8a71-a150ad94b450.png) 9 | 10 | and want to read only a few files say 11 | 12 | ```js 13 | [ 'index.html', 14 | 'css/**', 15 | 'js/**', 16 | 'lib/**', 17 | 'images/**', 18 | 'plugin/**' 19 | ] 20 | ``` 21 | 22 | In this case, Gulp will read all the sub-folders of (_say_) `css` folder and arrange them relative to your root folder and they will no longer be the sub-folder of `css`. The output after globbing would look like 23 | 24 | ![Zipped-Unzipped](https://cloud.githubusercontent.com/assets/2562992/3178614/27208c52-ec1c-11e3-852e-8bbb8e420c7f.png) 25 | 26 | If you want to maintain the structure, you need to pass `{base: '.'}` to `gulp.src()`. Like 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 | And the input to your `operation1()` will be a folder structure like 42 | 43 | ![with-base](https://cloud.githubusercontent.com/assets/2562992/3178607/053d6722-ec1c-11e3-9ba8-7ce39e1a480e.png) 44 | 45 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var util = require('util'); 4 | var Orchestrator = require('orchestrator'); 5 | var gutil = require('gulp-util'); 6 | var deprecated = require('deprecated'); 7 | var vfs = require('vinyl-fs'); 8 | 9 | function Gulp() { 10 | Orchestrator.call(this); 11 | } 12 | util.inherits(Gulp, Orchestrator); 13 | 14 | Gulp.prototype.task = Gulp.prototype.add; 15 | Gulp.prototype.run = function() { 16 | // `run()` is deprecated as of 3.5 and will be removed in 4.0 17 | // Use task dependencies instead 18 | 19 | // Impose our opinion of "default" tasks onto orchestrator 20 | var tasks = arguments.length ? arguments : ['default']; 21 | 22 | this.start.apply(this, tasks); 23 | }; 24 | 25 | Gulp.prototype.src = vfs.src; 26 | Gulp.prototype.dest = vfs.dest; 27 | Gulp.prototype.watch = function(glob, opt, fn) { 28 | if (typeof opt === 'function' || Array.isArray(opt)) { 29 | fn = opt; 30 | opt = null; 31 | } 32 | 33 | // Array of tasks given 34 | if (Array.isArray(fn)) { 35 | return vfs.watch(glob, opt, function() { 36 | this.start.apply(this, fn); 37 | }.bind(this)); 38 | } 39 | 40 | return vfs.watch(glob, opt, fn); 41 | }; 42 | 43 | // Let people use this class from our instance 44 | Gulp.prototype.Gulp = Gulp; 45 | 46 | // Deprecations 47 | deprecated.field('gulp.env has been deprecated. ' + 48 | 'Use your own CLI parser instead. ' + 49 | 'We recommend using yargs or minimist.', 50 | console.warn, 51 | Gulp.prototype, 52 | 'env', 53 | gutil.env 54 | ); 55 | 56 | Gulp.prototype.run = deprecated.method('gulp.run() has been deprecated. ' + 57 | 'Use task dependencies or gulp.watch task triggering instead.', 58 | console.warn, 59 | Gulp.prototype.run 60 | ); 61 | 62 | var inst = new Gulp(); 63 | module.exports = inst; 64 | -------------------------------------------------------------------------------- /docs/CLI.md: -------------------------------------------------------------------------------- 1 | ## gulp CLI docs 2 | 3 | ### Flags 4 | 5 | gulp has very few flags to know about. All other flags are for tasks to use if needed. 6 | 7 | - `-v` or `--version` will display the global and local gulp versions 8 | - `--require ` will require a module before running the gulpfile. This is useful for transpilers but also has other applications. You can use multiple `--require` flags 9 | - `--gulpfile ` will manually set path of gulpfile. Useful if you have multiple gulpfiles. This will set the CWD to the gulpfile directory as well 10 | - `--cwd ` will manually set the CWD. The search for the gulpfile, as well as the relativity of all requires will be from here 11 | - `-T` or `--tasks` will display the task dependency tree for the loaded gulpfile 12 | - `--tasks-simple` will display a plaintext list of tasks for the loaded gulpfile 13 | - `--color` will force gulp and gulp plugins to display colors even when no color support is detected 14 | - `--no-color` will force gulp and gulp plugins to not display colors even when color support is detected 15 | - `--silent` will disable all gulp logging 16 | 17 | The CLI adds process.env.INIT_CWD which is the original cwd it was launched from. 18 | 19 | #### Task specific flags 20 | 21 | Refer to this [StackOverflow](http://stackoverflow.com/questions/23023650/is-it-possible-to-pass-a-flag-to-gulp-to-have-it-run-tasks-in-different-ways) link for how to add task specific flags 22 | 23 | ### Tasks 24 | 25 | Tasks can be executed by running `gulp `. Just running `gulp` will execute the task you registered called `default`. If there is no `default` task gulp will error. 26 | 27 | ### Compilers 28 | 29 | You can find a list of supported languages at [interpret](https://github.com/tkellen/node-interpret#jsvariants). If you would like to add support for a new language send pull request/open issues there. 30 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "gulp", 3 | "description": "The streaming build system", 4 | "version": "3.9.1", 5 | "homepage": "http://gulpjs.com", 6 | "repository": "gulpjs/gulp", 7 | "author": "Fractal (http://wearefractal.com/)", 8 | "tags": [ 9 | "build", 10 | "stream", 11 | "system", 12 | "make", 13 | "tool", 14 | "asset", 15 | "pipeline" 16 | ], 17 | "files": [ 18 | "index.js", 19 | "lib", 20 | "bin", 21 | "completion", 22 | "gulp.1" 23 | ], 24 | "bin": { 25 | "gulp": "./bin/gulp.js" 26 | }, 27 | "man": "gulp.1", 28 | "dependencies": { 29 | "archy": "^1.0.0", 30 | "chalk": "^1.0.0", 31 | "deprecated": "^0.0.1", 32 | "gulp-util": "^3.0.0", 33 | "interpret": "^1.0.0", 34 | "liftoff": "^2.1.0", 35 | "minimist": "^1.1.0", 36 | "orchestrator": "^0.3.0", 37 | "pretty-hrtime": "^1.0.0", 38 | "semver": "^4.1.0", 39 | "tildify": "^1.0.0", 40 | "v8flags": "^2.0.2", 41 | "vinyl-fs": "^0.3.0" 42 | }, 43 | "devDependencies": { 44 | "coveralls": "^2.7.0", 45 | "eslint": "^1.7.3", 46 | "eslint-config-gulp": "^2.0.0", 47 | "graceful-fs": "^3.0.0", 48 | "istanbul": "^0.3.0", 49 | "jscs": "^2.3.5", 50 | "jscs-preset-gulp": "^1.0.0", 51 | "marked-man": "^0.1.3", 52 | "mkdirp": "^0.5.0", 53 | "mocha": "^2.0.1", 54 | "mocha-lcov-reporter": "^0.0.1", 55 | "q": "^1.0.0", 56 | "rimraf": "^2.2.5", 57 | "should": "^5.0.1" 58 | }, 59 | "scripts": { 60 | "prepublish": "marked-man --name gulp docs/CLI.md > gulp.1", 61 | "lint": "eslint . && jscs *.js bin/ lib/ test/", 62 | "pretest": "npm run lint", 63 | "test": "mocha --reporter spec", 64 | "coveralls": "istanbul cover _mocha --report lcovonly -- -R spec && cat ./coverage/lcov.info | coveralls && rm -rf ./coverage" 65 | }, 66 | "engines": { 67 | "node": ">= 0.9" 68 | }, 69 | "license": "MIT" 70 | } 71 | -------------------------------------------------------------------------------- /docs/FAQ.md: -------------------------------------------------------------------------------- 1 | # FAQ 2 | 3 | ## Why gulp? Why not ____? 4 | 5 | See the [gulp introduction slideshow] for a rundown on how gulp came to be. 6 | 7 | ## Is it "gulp" or "Gulp"? 8 | 9 | gulp is always lowercase. The only exception is in the gulp logo where gulp is capitalized. 10 | 11 | ## Where can I find a list of gulp plugins? 12 | 13 | gulp plugins always include the `gulpplugin` keyword. [Search gulp plugins][search-gulp-plugins] or [view all plugins][npm plugin search]. 14 | 15 | ## I want to write a gulp plugin, how do I get started? 16 | 17 | See the [Writing a gulp plugin] wiki page for guidelines and an example to get you started. 18 | 19 | ## My plugin does ____, is it doing too much? 20 | 21 | Probably. Ask yourself: 22 | 23 | 1. Is my plugin doing something that other plugins may need to do? 24 | - If so, that piece of functionality should be a separate plugin. [Check if it already exists on npm][npm plugin search]. 25 | 1. Is my plugin doing two, completely different things based on a configuration option? 26 | - If so, it may serve the community better to release it as two separate plugins 27 | - If the two tasks are different, but very closely related, it's probably OK 28 | 29 | ## How should newlines be represented in plugin output? 30 | 31 | Always use `\n` to prevent diff issues between operating systems. 32 | 33 | ## Where can I get updates on gulp? 34 | 35 | gulp updates can be found on the following twitters: 36 | 37 | - [@wearefractal](https://twitter.com/wearefractal) 38 | - [@eschoff](https://twitter.com/eschoff) 39 | - [@gulpjs](https://twitter.com/gulpjs) 40 | 41 | ## Does gulp have an IRC channel? 42 | 43 | Yes, come chat with us in #gulpjs on [Freenode]. 44 | 45 | [Writing a gulp plugin]: writing-a-plugin/README.md 46 | [gulp introduction slideshow]: 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 | -------------------------------------------------------------------------------- /docs/recipes/run-grunt-tasks-from-gulp.md: -------------------------------------------------------------------------------- 1 | # Run Grunt Tasks from Gulp 2 | 3 | It is possible to run Grunt tasks / Grunt plugins from within Gulp. This can be useful during a gradual migration from Grunt to Gulp or if there's a specific plugin that you need. With the described approach no Grunt CLI and no Gruntfile is required. 4 | 5 | **This approach requires Grunt >=1.0.0** 6 | 7 | very simple example `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'], //you can add more grunt tasks in this array 28 | {gruntfile: false}, //don't look for a Gruntfile - there is none. :-) 29 | function () {done();} 30 | ); 31 | }); 32 | 33 | ``` 34 | 35 | Now start the task with: 36 | `gulp copy` 37 | 38 | With the aforementioned approach the grunt tasks get registered within gulp's task system. **Keep in mind grunt tasks are usually blocking (unlike gulp), therefore no other task (not even a gulp task) can run until a grunt task is completed.** 39 | 40 | 41 | ### A few words on alternatives 42 | 43 | There's a *gulpfriendly* node module `gulp-grunt` [available](https://www.npmjs.org/package/gulp-grunt) which takes a different approach. It spawns child processes and within them the grunt tasks are executed. The way it works implies some limitations though: 44 | 45 | * It is at the moment not possible to pass options / cli args etc. to the grunt tasks via `gulp-grunt` 46 | * All grunt tasks have to be defined in a separate Gruntfile 47 | * You need to have the Grunt CLI installed 48 | * The output of some grunt tasks gets malformatted (.i.e. color coding). 49 | -------------------------------------------------------------------------------- /docs/recipes/README.md: -------------------------------------------------------------------------------- 1 | # Recipes 2 | 3 | * [Automate release workflow](automate-release-workflow.md) 4 | * [Combining streams to handle errors](combining-streams-to-handle-errors.md) 5 | * [Delete files and folders](delete-files-folder.md) 6 | * [Fast browserify builds with watchify](fast-browserify-builds-with-watchify.md) 7 | * [Incremental rebuilding, including operating on full file sets](incremental-builds-with-concatenate.md) 8 | * [Make stream from buffer (memory contents)](make-stream-from-buffer.md) 9 | * [Mocha test-runner with gulp](mocha-test-runner-with-gulp.md) 10 | * [Only pass through changed files](only-pass-through-changed-files.md) 11 | * [Pass parameters from the command line](pass-arguments-from-cli.md) 12 | * [Rebuild only files that change](rebuild-only-files-that-change.md) 13 | * [Generating a file per folder](running-task-steps-per-folder.md) 14 | * [Running tasks in series](running-tasks-in-series.md) 15 | * [Server with live-reloading and CSS injection](server-with-livereload-and-css-injection.md) 16 | * [Sharing streams with stream factories](sharing-streams-with-stream-factories.md) 17 | * [Specifying a new cwd (current working directory)](specifying-a-cwd.md) 18 | * [Split tasks across multiple files](split-tasks-across-multiple-files.md) 19 | * [Using external config file](using-external-config-file.md) 20 | * [Using multiple sources in one task](using-multiple-sources-in-one-task.md) 21 | * [Browserify + Uglify with sourcemaps](browserify-uglify-sourcemap.md) 22 | * [Browserify + Globs](browserify-with-globs.md) 23 | * [Browserify + Globs (multiple destination)](browserify-multiple-destination.md) 24 | * [Output both a minified and non-minified version](minified-and-non-minified.md) 25 | * [Templating with Swig and YAML front-matter](templating-with-swig-and-yaml-front-matter.md) 26 | * [Run Grunt Tasks from Gulp](run-grunt-tasks-from-gulp.md) 27 | * [Exports as tasks](exports-as-tasks.md) 28 | * [Rollup with rollup-stream](rollup-with-rollup-stream.md) 29 | -------------------------------------------------------------------------------- /docs/recipes/incremental-builds-with-concatenate.md: -------------------------------------------------------------------------------- 1 | # Incremental rebuilding, including operating on full file sets 2 | 3 | The trouble with incremental rebuilds is you often want to operate on _all_ processed files, not just single files. For example, you may want to lint and module-wrap just the file(s) that have changed, then concatenate it with all other linted and module-wrapped files. This is difficult without the use of temp files. 4 | 5 | Use [gulp-cached](https://github.com/wearefractal/gulp-cached) and [gulp-remember](https://github.com/ahaurw01/gulp-remember) to achieve this. 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')) // only pass through changed files 21 | .pipe(jshint()) // do special things to the changed files... 22 | .pipe(header('(function () {')) // e.g. jshinting ^^^ 23 | .pipe(footer('})();')) // and some kind of module wrapping 24 | .pipe(remember('scripts')) // add back all files to the stream 25 | .pipe(concat('app.js')) // do things that require all files 26 | .pipe(gulp.dest('public/')); 27 | }); 28 | 29 | gulp.task('watch', function () { 30 | var watcher = gulp.watch(scriptsGlob, ['scripts']); // watch the same files in our scripts task 31 | watcher.on('change', function (event) { 32 | if (event.type === 'deleted') { // if a file is deleted, forget about it 33 | delete cached.caches.scripts[event.path]; // gulp-cached remove api 34 | remember.forget('scripts', event.path); // gulp-remember remove api 35 | } 36 | }); 37 | }); 38 | ``` 39 | -------------------------------------------------------------------------------- /completion/powershell: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2014 Jason Jarrett 2 | # 3 | # Tab completion for the `gulp` 4 | # 5 | # Usage: 6 | # 7 | # To enable powershell completion for gulp you need to be running 8 | # at least PowerShell v3 or greater and add the below to your $PROFILE 9 | # 10 | # Invoke-Expression ((gulp --completion=powershell) -join [System.Environment]::NewLine) 11 | # 12 | # 13 | 14 | $gulp_completion_Process = { 15 | param($commandName, $parameterName, $wordToComplete, $commandAst, $fakeBoundParameter) 16 | 17 | 18 | # Load up an assembly to read the gulpfile's sha1 19 | if(-not $global:GulpSHA1Managed) { 20 | [Reflection.Assembly]::LoadWithPartialName("System.Security") | out-null 21 | $global:GulpSHA1Managed = new-Object System.Security.Cryptography.SHA1Managed 22 | } 23 | 24 | # setup a global (in-memory) cache 25 | if(-not $global:GulpfileShaCache) { 26 | $global:GulpfileShaCache = @{}; 27 | } 28 | 29 | $cache = $global:GulpfileShaCache; 30 | 31 | # Get the gulpfile's sha1 32 | $sha1gulpFile = (resolve-path gulpfile.js -ErrorAction Ignore | %{ 33 | $file = [System.IO.File]::Open($_.Path, "open", "read") 34 | [string]::join('', ($global:GulpSHA1Managed.ComputeHash($file) | %{ $_.ToString("x2") })) 35 | $file.Dispose() 36 | }) 37 | 38 | # lookup the sha1 for previously cached task lists. 39 | if($cache.ContainsKey($sha1gulpFile)){ 40 | $tasks = $cache[$sha1gulpFile]; 41 | } else { 42 | $tasks = (gulp --tasks-simple).split("`n"); 43 | $cache[$sha1gulpFile] = $tasks; 44 | } 45 | 46 | 47 | $tasks | 48 | where { $_.startswith($commandName) } 49 | Sort-Object | 50 | foreach { New-Object System.Management.Automation.CompletionResult $_, $_, 'ParameterValue', ('{0}' -f $_) } 51 | } 52 | 53 | if (-not $global:options) { 54 | $global:options = @{ 55 | CustomArgumentCompleters = @{}; 56 | NativeArgumentCompleters = @{} 57 | } 58 | } 59 | 60 | $global:options['NativeArgumentCompleters']['gulp'] = $gulp_completion_Process 61 | $function:tabexpansion2 = $function:tabexpansion2 -replace 'End\r\n{','End { if ($null -ne $options) { $options += $global:options} else {$options = $global:options}' 62 | -------------------------------------------------------------------------------- /docs/recipes/running-task-steps-per-folder.md: -------------------------------------------------------------------------------- 1 | # Generating a file per folder 2 | 3 | If you have a set of folders, and wish to perform a set of tasks on each, for instance... 4 | 5 | ``` 6 | /scripts 7 | /scripts/jquery/*.js 8 | /scripts/angularjs/*.js 9 | ``` 10 | 11 | ...and want to end up with... 12 | 13 | ``` 14 | /scripts 15 | /scripts/jquery.min.js 16 | /scripts/angularjs.min.js 17 | ``` 18 | 19 | ...you'll need to do something like the following... 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 | // concat into foldername.js 45 | .pipe(concat(folder + '.js')) 46 | // write to output 47 | .pipe(gulp.dest(scriptsPath)) 48 | // minify 49 | .pipe(uglify()) 50 | // rename to folder.min.js 51 | .pipe(rename(folder + '.min.js')) 52 | // write to output again 53 | .pipe(gulp.dest(scriptsPath)); 54 | }); 55 | 56 | // process all remaining files in scriptsPath root into main.js and main.min.js files 57 | var root = gulp.src(path.join(scriptsPath, '/*.js')) 58 | .pipe(concat('main.js')) 59 | .pipe(gulp.dest(scriptsPath)) 60 | .pipe(uglify()) 61 | .pipe(rename('main.min.js')) 62 | .pipe(gulp.dest(scriptsPath)); 63 | 64 | return merge(tasks, root); 65 | }); 66 | ``` 67 | 68 | A few notes: 69 | 70 | - `folders.map` - executes the function once per folder, and returns the async stream 71 | - `merge` - combines the streams and ends only when all streams emitted end 72 | -------------------------------------------------------------------------------- /docs/recipes/sharing-streams-with-stream-factories.md: -------------------------------------------------------------------------------- 1 | # Sharing streams with stream factories 2 | 3 | If you use the same plugins in multiple tasks you might find yourself getting that itch to DRY things up. This method will allow you to create factories to split out your commonly used stream chains. 4 | 5 | We'll use [lazypipe](https://github.com/OverZealous/lazypipe) to get the job done. 6 | 7 | This is our sample file: 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 | and our file after using lazypipe looks like this: 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 | // give 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 | You can see we split out our JavaScript pipeline (JSHint + Uglify) that was being reused in multiple tasks into a factory. These factories can be reused in as many tasks as you want. You can also nest factories and you can chain factories together for great effect. Splitting out each shared pipeline also gives you one central location to modify if you decide to change up your workflow. 65 | -------------------------------------------------------------------------------- /docs/recipes/fast-browserify-builds-with-watchify.md: -------------------------------------------------------------------------------- 1 | # Fast browserify builds with watchify 2 | 3 | As a [browserify](http://github.com/substack/node-browserify) project begins to expand, the time to bundle it slowly gets longer and longer. While it might start at 1 second, it's possible to be waiting 30 seconds for your project to build on particularly large projects. 4 | 5 | That's why [substack](http://github.com/substack) wrote [watchify](http://github.com/substack/watchify), a persistent browserify bundler that watches files for changes and *only rebuilds what it needs to*. This way, that first build might still take 30 seconds, but subsequent builds can still run in under 100ms – which is a huge improvement. 6 | 7 | Watchify doesn't have a gulp plugin, and it doesn't need one: you can use [vinyl-source-stream](http://github.com/hughsk/vinyl-source-stream) to pipe the bundle stream into your gulp pipeline. 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 | // add custom browserify options here 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 | // add transformations here 30 | // i.e. b.transform(coffeeify); 31 | 32 | gulp.task('js', bundle); // so you can run `gulp js` to build the file 33 | b.on('update', bundle); // on any dep update, runs the bundler 34 | b.on('log', gutil.log); // output build logs to terminal 35 | 36 | function bundle() { 37 | return b.bundle() 38 | // log errors if they happen 39 | .on('error', gutil.log.bind(gutil, 'Browserify Error')) 40 | .pipe(source('bundle.js')) 41 | // optional, remove if you don't need to buffer file contents 42 | .pipe(buffer()) 43 | // optional, remove if you dont want sourcemaps 44 | .pipe(sourcemaps.init({loadMaps: true})) // loads map from browserify file 45 | // Add transformation tasks to the pipeline here. 46 | .pipe(sourcemaps.write('./')) // writes .map file 47 | .pipe(gulp.dest('./dist')); 48 | } 49 | ``` 50 | -------------------------------------------------------------------------------- /docs/writing-a-plugin/using-buffers.md: -------------------------------------------------------------------------------- 1 | # Using buffers 2 | 3 | > Here is some information on creating gulp plugin that manipulates buffers. 4 | 5 | [Writing a Plugin](README.md) > Using buffers 6 | 7 | ## Using buffers 8 | If your plugin is relying on a buffer based library, you will probably choose to base your plugin around file.contents as a buffer. Let's implement a plugin prepending some text to files: 9 | 10 | ```js 11 | var through = require('through2'); 12 | var gutil = require('gulp-util'); 13 | var PluginError = gutil.PluginError; 14 | 15 | // consts 16 | const PLUGIN_NAME = 'gulp-prefixer'; 17 | 18 | // plugin level function (dealing with files) 19 | function gulpPrefixer(prefixText) { 20 | if (!prefixText) { 21 | throw new PluginError(PLUGIN_NAME, 'Missing prefix text!'); 22 | } 23 | 24 | prefixText = new Buffer(prefixText); // allocate ahead of time 25 | 26 | // creating a stream through which each file will pass 27 | var stream = through.obj(function(file, enc, cb) { 28 | if (file.isStream()) { 29 | this.emit('error', new PluginError(PLUGIN_NAME, 'Streams are not supported!')); 30 | return cb(); 31 | } 32 | 33 | if (file.isBuffer()) { 34 | file.contents = Buffer.concat([prefixText, file.contents]); 35 | } 36 | 37 | // make sure the file goes through the next gulp plugin 38 | this.push(file); 39 | 40 | // tell the stream engine that we are done with this file 41 | cb(); 42 | }); 43 | 44 | // returning the file stream 45 | return stream; 46 | }; 47 | 48 | // exporting the plugin main function 49 | module.exports = gulpPrefixer; 50 | ``` 51 | 52 | The above plugin can be used like this: 53 | 54 | ```js 55 | var gulp = require('gulp'); 56 | var gulpPrefixer = require('gulp-prefixer'); 57 | 58 | gulp.src('files/**/*.js') 59 | .pipe(gulpPrefixer('prepended string')) 60 | .pipe(gulp.dest('modified-files')); 61 | ``` 62 | 63 | ## Handling streams 64 | 65 | Unfortunately, the above plugin will error when using gulp.src in non-buffered (streaming) mode. You should support streams too if possible. See [Dealing with streams](dealing-with-streams.md) for more information. 66 | 67 | ## Some plugins based on buffers 68 | 69 | * [gulp-coffee](https://github.com/contra/gulp-coffee) 70 | * [gulp-svgmin](https://github.com/ben-eb/gulp-svgmin) 71 | * [gulp-marked](https://github.com/lmtm/gulp-marked) 72 | * [gulp-svg2ttf](https://github.com/nfroidure/gulp-svg2ttf) 73 | -------------------------------------------------------------------------------- /docs/recipes/rollup-with-rollup-stream.md: -------------------------------------------------------------------------------- 1 | # Rollup with rollup-stream 2 | 3 | Like Browserify, [Rollup](http://rollupjs.org/) is a bundler and thus only fits naturally into gulp if it's at the start of the pipeline. Unlike Browserify, Rollup doesn't natively produce a stream as output and needs to be wrapped before it can take this position. [rollup-stream](https://github.com/Permutatrix/rollup-stream) does this for you, producing output just like that of Browserify's `bundle()` method—as a result, most of the Browserify recipes here will also work with rollup-stream. 4 | 5 | ## Basic usage 6 | ```js 7 | // npm install --save-dev rollup-stream vinyl-source-stream 8 | var gulp = require('gulp'); 9 | var rollup = require('rollup-stream'); 10 | var source = require('vinyl-source-stream'); 11 | 12 | gulp.task('rollup', function() { 13 | return rollup({ 14 | entry: './src/main.js' 15 | }) 16 | 17 | // give the file the name you want to output with 18 | .pipe(source('app.js')) 19 | 20 | // and output to ./dist/app.js as normal. 21 | .pipe(gulp.dest('./dist')); 22 | }); 23 | ``` 24 | 25 | ## Usage with sourcemaps 26 | ```js 27 | // npm install --save-dev rollup-stream gulp-sourcemaps vinyl-source-stream vinyl-buffer 28 | // optional: npm install --save-dev gulp-rename 29 | var gulp = require('gulp'); 30 | var rollup = require('rollup-stream'); 31 | var sourcemaps = require('gulp-sourcemaps'); 32 | //var rename = require('gulp-rename'); 33 | var source = require('vinyl-source-stream'); 34 | var buffer = require('vinyl-buffer'); 35 | 36 | gulp.task('rollup', function() { 37 | return rollup({ 38 | entry: './src/main.js', 39 | sourceMap: true 40 | }) 41 | 42 | // point to the entry file. 43 | .pipe(source('main.js', './src')) 44 | 45 | // buffer the output. most gulp plugins, including gulp-sourcemaps, don't support streams. 46 | .pipe(buffer()) 47 | 48 | // tell gulp-sourcemaps to load the inline sourcemap produced by rollup-stream. 49 | .pipe(sourcemaps.init({loadMaps: true})) 50 | 51 | // transform the code further here. 52 | 53 | // if you want to output with a different name from the input file, use gulp-rename here. 54 | //.pipe(rename('index.js')) 55 | 56 | // write the sourcemap alongside the output file. 57 | .pipe(sourcemaps.write('.')) 58 | 59 | // and output to ./dist/main.js as normal. 60 | .pipe(gulp.dest('./dist')); 61 | }); 62 | ``` 63 | -------------------------------------------------------------------------------- /docs/recipes/delete-files-folder.md: -------------------------------------------------------------------------------- 1 | 2 | # Delete files and folders 3 | 4 | You might want to delete some files before running your build. Since deleting files doesn't work on the file contents, there's no reason to use a gulp plugin. An excellent opportunity to use a vanilla node module. 5 | 6 | Let's use the [`del`](https://github.com/sindresorhus/del) module for this example as it supports multiple files and [globbing](https://github.com/sindresorhus/multimatch#globbing-patterns): 7 | 8 | ```sh 9 | $ npm install --save-dev gulp del 10 | ``` 11 | 12 | Imagine the following file structure: 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 | In the gulpfile we want to clean out the contents of the `mobile` folder before running our build: 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 | // here we use a globbing pattern to match everything inside the `mobile` folder 36 | 'dist/mobile/**/*', 37 | // we don't want to clean this file though so we negate the pattern 38 | '!dist/mobile/deploy.json' 39 | ]); 40 | }); 41 | 42 | gulp.task('default', ['clean:mobile']); 43 | ``` 44 | 45 | 46 | ## Delete files in a pipeline 47 | 48 | You might want to delete some files after processing them in a pipeline. 49 | 50 | We'll use [vinyl-paths](https://github.com/sindresorhus/vinyl-paths) to easily get the file path of files in the stream and pass it to the `del` method. 51 | 52 | ```sh 53 | $ npm install --save-dev gulp del vinyl-paths 54 | ``` 55 | 56 | Imagine the following file structure: 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'); // only as an example 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(vinylPaths(del)) 75 | .pipe(stripDebug()) 76 | .pipe(gulp.dest('dist')); 77 | }); 78 | 79 | gulp.task('default', ['clean:tmp']); 80 | ``` 81 | 82 | This will only delete the tmp dir. 83 | 84 | 85 | Only do this if you're already using other plugins in the pipeline, otherwise just use the module directly as `gulp.src` is costly. 86 | -------------------------------------------------------------------------------- /docs/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) shows how to setup a basic gulp task to bundle a JavaScript file with its dependencies, and minify the bundle with UglifyJS while preserving source maps. 4 | It does not, however, show how one may use gulp and Browserify with multiple entry files. 5 | 6 | See also: the [Combining Streams to Handle Errors](https://github.com/gulpjs/gulp/blob/master/docs/recipes/combining-streams-to-handle-errors.md) recipe for handling errors with Browserify or UglifyJS in your stream. 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 expects tasks to return a stream, so we create one here. 24 | var bundledStream = through(); 25 | 26 | bundledStream 27 | // turns the output bundle stream into a stream containing 28 | // the normal attributes gulp plugins expect. 29 | .pipe(source('app.js')) 30 | // the rest of the gulp task, as you would normally write it. 31 | // here we're copying from the Browserify + Uglify2 recipe. 32 | .pipe(buffer()) 33 | .pipe(sourcemaps.init({loadMaps: true})) 34 | // Add gulp plugins to the pipeline here. 35 | .pipe(uglify()) 36 | .on('error', gutil.log) 37 | .pipe(sourcemaps.write('./')) 38 | .pipe(gulp.dest('./dist/js/')); 39 | 40 | // "globby" replaces the normal "gulp.src" as Browserify 41 | // creates it's own readable stream. 42 | globby(['./entries/*.js']).then(function(entries) { 43 | // create the Browserify instance. 44 | var b = browserify({ 45 | entries: entries, 46 | debug: true, 47 | transform: [reactify] 48 | }); 49 | 50 | // pipe the Browserify stream into the stream we created earlier 51 | // this starts our gulp pipeline. 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 | // finally, we return the stream, so gulp knows when this task is done. 59 | return bundledStream; 60 | }); 61 | ``` 62 | -------------------------------------------------------------------------------- /docs/writing-a-plugin/dealing-with-streams.md: -------------------------------------------------------------------------------- 1 | # Dealing with streams 2 | 3 | > It is highly recommended to write plugins supporting streams. Here is some information on creating a gulp plugin that supports streams. 4 | 5 | > Make sure to follow the best practice regarding error handling and add the line that make the gulp plugin re-emit the first error caught during the transformation of the content 6 | 7 | [Writing a Plugin](README.md) > Writing stream based plugins 8 | 9 | ## Dealing with streams 10 | 11 | Let's implement a plugin prepending some text to files. This plugin supports all possible forms of file.contents. 12 | 13 | ```js 14 | var through = require('through2'); 15 | var gutil = require('gulp-util'); 16 | var PluginError = gutil.PluginError; 17 | 18 | // consts 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 | // plugin level function (dealing with files) 28 | function gulpPrefixer(prefixText) { 29 | if (!prefixText) { 30 | throw new PluginError(PLUGIN_NAME, 'Missing prefix text!'); 31 | } 32 | 33 | prefixText = new Buffer(prefixText); // allocate ahead of time 34 | 35 | // creating a stream through which each file will pass 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 | // define the streamer that will transform the content 44 | var streamer = prefixStream(prefixText); 45 | // catch errors from the streamer and emit a gulp plugin error 46 | streamer.on('error', this.emit.bind(this, 'error')); 47 | // start the transformation 48 | file.contents = file.contents.pipe(streamer); 49 | } 50 | 51 | // make sure the file goes through the next gulp plugin 52 | this.push(file); 53 | // tell the stream engine that we are done with this file 54 | cb(); 55 | }); 56 | 57 | // returning the file stream 58 | return stream; 59 | } 60 | 61 | // exporting the plugin main function 62 | module.exports = gulpPrefixer; 63 | ``` 64 | 65 | The above plugin can be used like this: 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 | ## Some plugins using streams 77 | 78 | * [gulp-svgicons2svgfont](https://github.com/nfroidure/gulp-svgiconstosvgfont) 79 | 80 | -------------------------------------------------------------------------------- /docs/recipes/running-tasks-in-series.md: -------------------------------------------------------------------------------- 1 | # Running tasks in series, i.e. Task Dependency 2 | 3 | By default, tasks run with maximum concurrency -- e.g. it launches all the tasks at once and waits for nothing. If you want to create a series where tasks run in a particular order, you need to do two things: 4 | 5 | - give it a hint to tell it when the task is done, 6 | - and give it a hint that a task depends on completion of another. 7 | 8 | For these examples, let's presume you have two tasks, "one" and "two" that you specifically want to run in this order: 9 | 10 | 1. In task "one" you add a hint to tell it when the task is done. Either take in a callback and call it when you're done or return a promise or stream that the engine should wait to resolve or end respectively. 11 | 12 | 2. In task "two" you add a hint telling the engine that it depends on completion of the first task. 13 | 14 | So this example would look like: 15 | 16 | ```js 17 | var gulp = require('gulp'); 18 | 19 | // takes in a callback so the engine knows when it'll be done 20 | gulp.task('one', function (cb) { 21 | // do stuff -- async or otherwise 22 | fs.writeFile('filename', 'data', opts, function (err) { 23 | cb(err); // if err is not null and not undefined, the orchestration will stop, and 'two' will not run 24 | }); 25 | }); 26 | 27 | // identifies a dependent task must be complete before this one begins 28 | gulp.task('two', ['one'], function() { 29 | // task 'one' is done now 30 | }); 31 | 32 | gulp.task('default', ['one', 'two']); 33 | // alternatively: gulp.task('default', ['two']); 34 | ``` 35 | 36 | Another example, which returns the stream instead of using a callback: 37 | 38 | ```js 39 | var gulp = require('gulp'); 40 | var del = require('del'); // rm -rf 41 | 42 | gulp.task('clean', function() { 43 | return del(['output']); 44 | }); 45 | 46 | gulp.task('templates', ['clean'], function() { 47 | var stream = gulp.src(['src/templates/*.hbs']) 48 | // do some concatenation, minification, etc. 49 | .pipe(gulp.dest('output/templates/')); 50 | return stream; // return the stream as the completion hint 51 | 52 | }); 53 | 54 | gulp.task('styles', ['clean'], function() { 55 | var stream = gulp.src(['src/styles/app.less']) 56 | // do some hinting, minification, etc. 57 | .pipe(gulp.dest('output/css/app.css')); 58 | return stream; 59 | }); 60 | 61 | gulp.task('build', ['templates', 'styles']); 62 | 63 | // templates and styles will be processed in parallel. 64 | // clean will be guaranteed to complete before either start. 65 | // clean will not be run twice, even though it is called as a dependency twice. 66 | 67 | gulp.task('default', ['build']); 68 | ``` 69 | -------------------------------------------------------------------------------- /docs/recipes/automate-release-workflow.md: -------------------------------------------------------------------------------- 1 | # Automate release workflow 2 | 3 | If your project follows a semantic versioning, it may be a good idea to automatize the steps needed to do a release. 4 | Below you have a simple recipe that bumps the project version, commits the changes to git and creates a new 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' // Or to any other commit message convention you use. 23 | })) 24 | .pipe(gulp.dest('./')); 25 | }); 26 | 27 | gulp.task('github-release', function(done) { 28 | conventionalGithubReleaser({ 29 | type: "oauth", 30 | token: '0126af95c0e2d9b0a7c78738c4c00a860b04acc8' // change this to your own GitHub token or use an environment variable 31 | }, { 32 | preset: 'angular' // Or to any other commit message convention you use. 33 | }, done); 34 | }); 35 | 36 | gulp.task('bump-version', function () { 37 | // We hardcode the version change type to 'patch' but it may be a good idea to 38 | // use minimist (https://www.npmjs.com/package/minimist) to determine with a 39 | // command argument whether you are doing a 'major', 'minor' or a 'patch' change. 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.add()) 48 | .pipe(git.commit('[Prerelease] Bumped version number')); 49 | }); 50 | 51 | gulp.task('push-changes', function (cb) { 52 | git.push('origin', 'master', cb); 53 | }); 54 | 55 | gulp.task('create-new-tag', function (cb) { 56 | var version = getPackageJsonVersion(); 57 | git.tag(version, 'Created Tag for version: ' + version, function (error) { 58 | if (error) { 59 | return cb(error); 60 | } 61 | git.push('origin', 'master', {args: '--tags'}, cb); 62 | }); 63 | 64 | function getPackageJsonVersion () { 65 | // We parse the json file instead of using require because require caches 66 | // multiple calls so the version number won't be updated 67 | return JSON.parse(fs.readFileSync('./package.json', 'utf8')).version; 68 | }; 69 | }); 70 | 71 | gulp.task('release', function (callback) { 72 | runSequence( 73 | 'bump-version', 74 | 'changelog', 75 | 'commit-changes', 76 | 'push-changes', 77 | 'create-new-tag', 78 | 'github-release', 79 | function (error) { 80 | if (error) { 81 | console.log(error.message); 82 | } else { 83 | console.log('RELEASE FINISHED SUCCESSFULLY'); 84 | } 85 | callback(error); 86 | }); 87 | }); 88 | 89 | ``` 90 | -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | # gulp documentation 2 | 3 | * [Getting Started](getting-started.md) - How to get going with gulp 4 | * [API documentation](API.md) - Learn the ins and outs of using gulp 5 | * [CLI documentation](CLI.md) - Learn how to call tasks and use compilers 6 | * [Writing a Plugin](writing-a-plugin/README.md) - So you're writing a gulp plugin? Go here for the essential dos and don'ts. 7 | * [Spanish documentation][SpanishDocs] - gulp en Español. 8 | * [Simplified Chinese documentation][SimplifiedChineseDocs] - gulp 简体中文文档. 9 | * [Korean documentation][KoreanDocs] - gulp 한국어 참조 문서. 10 | 11 | 12 | ## FAQ 13 | 14 | See the [FAQ](FAQ.md) for the answers to commonly asked questions. 15 | 16 | 17 | ## Recipes 18 | 19 | The community has written [recipes](recipes#recipes) for common gulp use-cases. 20 | 21 | 22 | ## Still got questions? 23 | 24 | Post on [StackOverflow with a #gulp tag](http://stackoverflow.com/questions/tagged/gulp), or come chat with us in [#gulpjs](http://webchat.freenode.net/?channels=gulpjs) on [Freenode](http://freenode.net/). 25 | 26 | 27 | ## Books 28 | * [Developing a gulp Edge](http://shop.oreilly.com/product/9781939902146.do) 29 | 30 | 31 | ## Articles 32 | * [Tagtree intro to gulp video](http://tagtree.tv/gulp) 33 | * [Introduction to node.js streams](https://github.com/substack/stream-handbook) 34 | * [Video introduction to node.js streams](http://www.youtube.com/watch?v=QgEuZ52OZtU) 35 | * [Getting started with gulp (by @markgdyr)](http://markgoodyear.com/2014/01/getting-started-with-gulp/) 36 | * [A cheatsheet for gulp](https://github.com/osscafe/gulp-cheatsheet) 37 | * [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) 38 | * [Inspiration (slides) about why gulp was made](http://slid.es/contra/gulp) 39 | * [Building With Gulp](http://www.smashingmagazine.com/2014/06/11/building-with-gulp/) 40 | * [Gulp - The Basics (screencast)](https://www.youtube.com/watch?v=dwSLFai8ovQ) 41 | * [Get started with gulp (video series)](http://www.youtube.com/playlist?list=PLRk95HPmOM6PN-G1xyKj9q6ap_dc9Yckm) 42 | * [Optimize your web code with gulp](http://www.linuxuser.co.uk/tutorials/optimise-your-web-code-with-gulp-js) 43 | * [Automate Your Tasks Easily with Gulp.js ](https://scotch.io/tutorials/automate-your-tasks-easily-with-gulp-js) 44 | 45 | 46 | ## Examples 47 | 48 | - [Web Starter Kit gulpfile](https://github.com/google/web-starter-kit/blob/master/gulpfile.babel.js) 49 | 50 | 51 | ## License 52 | 53 | All the documentation is covered by the CC0 license *(do whatever you want with it - public domain)*. 54 | 55 | [![CC0](http://i.creativecommons.org/p/zero/1.0/88x31.png)](http://creativecommons.org/publicdomain/zero/1.0/) 56 | 57 | To the extent possible under law, [Fractal](http://wearefractal.com) has waived all copyright and related or neighboring rights to this work. 58 | 59 | [SpanishDocs]: https://github.com/bucaran/gulp-docs-es 60 | [SimplifiedChineseDocs]: https://github.com/lisposter/gulp-docs-zh-cn 61 | [KoreanDocs]: https://github.com/preco21/gulp-docs-ko 62 | -------------------------------------------------------------------------------- /docs/writing-a-plugin/testing.md: -------------------------------------------------------------------------------- 1 | # Testing 2 | 3 | > Testing your plugin is the only way to ensure quality. It brings confidence to your users and makes your life easier. 4 | 5 | [Writing a Plugin](README.md) > Testing 6 | 7 | 8 | ## Tooling 9 | 10 | Most plugins use [mocha](https://github.com/mochajs/mocha), [should](https://github.com/shouldjs/should.js) and [event-stream](https://github.com/dominictarr/event-stream) to help them test. The following examples will use these tools. 11 | 12 | 13 | ## Testing plugins for streaming mode 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 | // create the fake file 27 | var fakeFile = new File({ 28 | contents: es.readArray(['stream', 'with', 'those', 'contents']) 29 | }); 30 | 31 | // Create a prefixer plugin stream 32 | var myPrefixer = prefixer('prependthis'); 33 | 34 | // write the fake file to it 35 | myPrefixer.write(fakeFile); 36 | 37 | // wait for the file to come back out 38 | myPrefixer.once('data', function(file) { 39 | // make sure it came out the same way it went in 40 | assert(file.isStream()); 41 | 42 | // buffer the contents to make sure it got prepended to 43 | file.contents.pipe(es.wait(function(err, data) { 44 | // check the contents 45 | assert.equal(data, 'prependthisstreamwiththosecontents'); 46 | done(); 47 | })); 48 | }); 49 | 50 | }); 51 | 52 | }); 53 | }); 54 | ``` 55 | 56 | 57 | ## Testing plugins for buffer mode 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 | // create the fake file 71 | var fakeFile = new File({ 72 | contents: new Buffer('abufferwiththiscontent') 73 | }); 74 | 75 | // Create a prefixer plugin stream 76 | var myPrefixer = prefixer('prependthis'); 77 | 78 | // write the fake file to it 79 | myPrefixer.write(fakeFile); 80 | 81 | // wait for the file to come back out 82 | myPrefixer.once('data', function(file) { 83 | // make sure it came out the same way it went in 84 | assert(file.isBuffer()); 85 | 86 | // check the contents 87 | assert.equal(file.contents.toString('utf8'), 'prependthisabufferwiththiscontent'); 88 | done(); 89 | }); 90 | 91 | }); 92 | 93 | }); 94 | }); 95 | ``` 96 | 97 | 98 | ## Some plugins with high-quality Testing 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 | -------------------------------------------------------------------------------- /docs/recipes/server-with-livereload-and-css-injection.md: -------------------------------------------------------------------------------- 1 | # Server with live-reloading and CSS injection 2 | 3 | With [BrowserSync](http://browsersync.io) and gulp, you can easily create a development server that is accessible to any device on the same WiFi network. BrowserSync also has live-reload built in, so there's nothing else to configure. 4 | 5 | First install the module: 6 | 7 | ```sh 8 | $ npm install --save-dev browser-sync 9 | ``` 10 | 11 | Then, considering the following file structure... 12 | 13 | ``` 14 | gulpfile.js 15 | app/ 16 | styles/ 17 | main.css 18 | scripts/ 19 | main.js 20 | index.html 21 | ``` 22 | 23 | ... you can easily serve files from the `app` directory and have all browsers reload when any of them change with the following in `gulpfile.js`: 24 | 25 | ```js 26 | var gulp = require('gulp'); 27 | var browserSync = require('browser-sync'); 28 | var reload = browserSync.reload; 29 | 30 | // watch files for changes and reload 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 | and including the CSS in `index.html`: 44 | 45 | ```html 46 | 47 | 48 | ... 49 | 50 | ... 51 | 52 | ``` 53 | 54 | to serve your files and launch a browser window pointing to the default URL (http://localhost:3000) run: 55 | 56 | ```bash 57 | gulp serve 58 | ``` 59 | 60 | 61 | ## + CSS pre-processors 62 | 63 | A common use-case is to reload CSS files after they've been pre-processed. Using Sass as an example, this is how you can instruct browsers to reload the CSS without doing a full-page refresh. 64 | 65 | Considering this updated file structure... 66 | 67 | ``` 68 | gulpfile.js 69 | app/ 70 | scss/ 71 | main.scss 72 | scripts/ 73 | main.js 74 | index.html 75 | ``` 76 | ... you can easily watch Sass files from the `scss` directory and have all browsers reload when any of them change with the following in `gulpfile.js`: 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 | // watch Sass files for changes, run the Sass preprocessor with the 'sass' task and reload 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 | and including the pre-processed CSS in `index.html`: 103 | 104 | ```html 105 | 106 | 107 | ... 108 | 109 | ... 110 | 111 | ``` 112 | 113 | to serve your files and launch a browser window pointing to the default URL (http://localhost:3000) run: 114 | 115 | ```bash 116 | gulp serve 117 | ``` 118 | 119 | ## Extras 120 | 121 | - Live reload, CSS injection and scroll/form syncing works seamlessly inside of [BrowserStack](http://www.browserstack.com/) virtual machines. 122 | - Set `tunnel: true` to view your local site at a public URL (complete with all BrowserSync features). 123 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Request for contributions 2 | 3 | Please contribute to this repository if any of the following is true: 4 | - You have expertise in community development, communication, or education 5 | - You want open source communities to be more collaborative and inclusive 6 | - You want to help lower the burden to first time contributors 7 | 8 | # How to contribute 9 | 10 | Prerequisites: 11 | 12 | - familiarity with [GitHub PRs](https://help.github.com/articles/using-pull-requests) (pull requests) and issues 13 | - knowledge of Markdown for editing `.md` documents 14 | 15 | In particular, this community seeks the following types of contributions: 16 | 17 | - ideas: participate in an Issues thread or start your own to have your voice 18 | heard 19 | - resources: submit a PR to add to [docs README.md](/docs/README.md) with links to related content 20 | - outline sections: help us ensure that this repository is comprehensive. If 21 | there is a topic that is overlooked, please add it, even if it is just a stub 22 | in the form of a header and single sentence. Initially, most things fall into 23 | this category 24 | - write: contribute your expertise in an area by helping us expand the included 25 | content 26 | - copy editing: fix typos, clarify language, and generally improve the quality 27 | of the content 28 | - formatting: help keep content easy to read with consistent formatting 29 | - code: Fix issues or contribute new features to this or any related projects 30 | 31 | # Conduct 32 | 33 | We are committed to providing a friendly, safe and welcoming environment for 34 | all, regardless of gender, sexual orientation, disability, ethnicity, religion, 35 | or similar personal characteristic. 36 | 37 | On IRC, please avoid using overtly sexual nicknames or other nicknames that 38 | might detract from a friendly, safe and welcoming environment for all. 39 | 40 | Please be kind and courteous. There's no need to be mean or rude. 41 | Respect that people have differences of opinion and that every design or 42 | implementation choice carries a trade-off and numerous costs. There is seldom 43 | a right answer, merely an optimal answer given a set of values and 44 | circumstances. 45 | 46 | Please keep unstructured critique to a minimum. If you have solid ideas you 47 | want to experiment with, make a fork and see how it works. 48 | 49 | We will exclude you from interaction if you insult, demean or harass anyone. 50 | That is not welcome behavior. We interpret the term "harassment" as 51 | including the definition in the 52 | [Citizen Code of Conduct](http://citizencodeofconduct.org/); 53 | if you have any lack of clarity about what might be included in that concept, 54 | please read their definition. In particular, we don't tolerate behavior that 55 | excludes people in socially marginalized groups. 56 | 57 | Private harassment is also unacceptable. No matter who you are, if you feel 58 | you have been or are being harassed or made uncomfortable by a community 59 | member, please contact one of the channel ops or any of the 60 | [gulpjs](https://github.com/orgs/gulpjs/people) core team 61 | immediately. Whether you're a regular contributor or a newcomer, we care about 62 | making this community a safe place for you and we've got your back. 63 | 64 | Likewise any spamming, trolling, flaming, baiting or other attention-stealing 65 | behavior is not welcome. 66 | 67 | 68 | # Communication 69 | 70 | There is an IRC channel on irc.freenode.net, channel `#gulpjs`. You're 71 | welcome to drop in and ask questions, discuss bugs and such. The channel is 72 | not currently logged. 73 | 74 | GitHub issues are the primary way for communicating about specific proposed 75 | changes to this project. 76 | 77 | In both contexts, please follow the conduct guidelines above. Language issues 78 | are often contentious and we'd like to keep discussion brief, civil and focused 79 | on what we're actually doing, not wandering off into too much imaginary stuff. 80 | 81 | # Frequently Asked Questions 82 | 83 | See [the FAQ docs page](/docs/FAQ.md) 84 | -------------------------------------------------------------------------------- /test/tasks.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var gulp = require('../'); 4 | var Q = require('q'); 5 | var should = require('should'); 6 | require('mocha'); 7 | 8 | describe('gulp tasks', function() { 9 | describe('task()', function() { 10 | it('should define a task', function(done) { 11 | var fn; 12 | fn = function() {}; 13 | gulp.task('test', fn); 14 | should.exist(gulp.tasks.test); 15 | gulp.tasks.test.fn.should.equal(fn); 16 | gulp.reset(); 17 | done(); 18 | }); 19 | }); 20 | describe('run()', function() { 21 | it('should run multiple tasks', function(done) { 22 | var a, fn, fn2; 23 | a = 0; 24 | fn = function() { 25 | this.should.equal(gulp); 26 | ++a; 27 | }; 28 | fn2 = function() { 29 | this.should.equal(gulp); 30 | ++a; 31 | }; 32 | gulp.task('test', fn); 33 | gulp.task('test2', fn2); 34 | gulp.run('test', 'test2'); 35 | a.should.equal(2); 36 | gulp.reset(); 37 | done(); 38 | }); 39 | it('should run all tasks when call run() multiple times', function(done) { 40 | var a, fn, fn2; 41 | a = 0; 42 | fn = function() { 43 | this.should.equal(gulp); 44 | ++a; 45 | }; 46 | fn2 = function() { 47 | this.should.equal(gulp); 48 | ++a; 49 | }; 50 | gulp.task('test', fn); 51 | gulp.task('test2', fn2); 52 | gulp.run('test'); 53 | gulp.run('test2'); 54 | a.should.equal(2); 55 | gulp.reset(); 56 | done(); 57 | }); 58 | it('should run all async promise tasks', function(done) { 59 | var a, fn, fn2; 60 | a = 0; 61 | fn = function() { 62 | var deferred = Q.defer(); 63 | setTimeout(function() { 64 | ++a; 65 | deferred.resolve(); 66 | }, 1); 67 | return deferred.promise; 68 | }; 69 | fn2 = function() { 70 | var deferred = Q.defer(); 71 | setTimeout(function() { 72 | ++a; 73 | deferred.resolve(); 74 | }, 1); 75 | return deferred.promise; 76 | }; 77 | gulp.task('test', fn); 78 | gulp.task('test2', fn2); 79 | gulp.run('test'); 80 | gulp.run('test2', function() { 81 | gulp.isRunning.should.equal(false); 82 | a.should.equal(2); 83 | gulp.reset(); 84 | done(); 85 | }); 86 | gulp.isRunning.should.equal(true); 87 | }); 88 | it('should run all async callback tasks', function(done) { 89 | var a, fn, fn2; 90 | a = 0; 91 | fn = function(cb) { 92 | setTimeout(function() { 93 | ++a; 94 | cb(null); 95 | }, 1); 96 | }; 97 | fn2 = function(cb) { 98 | setTimeout(function() { 99 | ++a; 100 | cb(null); 101 | }, 1); 102 | }; 103 | gulp.task('test', fn); 104 | gulp.task('test2', fn2); 105 | gulp.run('test'); 106 | gulp.run('test2', function() { 107 | gulp.isRunning.should.equal(false); 108 | a.should.equal(2); 109 | gulp.reset(); 110 | done(); 111 | }); 112 | gulp.isRunning.should.equal(true); 113 | }); 114 | it('should emit task_not_found and throw an error when task is not defined', function(done) { 115 | gulp.on('task_not_found', function(err) { 116 | should.exist(err); 117 | should.exist(err.task); 118 | err.task.should.equal('test'); 119 | gulp.reset(); 120 | done(); 121 | }); 122 | try { 123 | gulp.run('test'); 124 | } catch (err) { 125 | should.exist(err); 126 | } 127 | }); 128 | it('should run task scoped to gulp', function(done) { 129 | var a, fn; 130 | a = 0; 131 | fn = function() { 132 | this.should.equal(gulp); 133 | ++a; 134 | }; 135 | gulp.task('test', fn); 136 | gulp.run('test'); 137 | a.should.equal(1); 138 | gulp.isRunning.should.equal(false); 139 | gulp.reset(); 140 | done(); 141 | }); 142 | it('should run default task scoped to gulp', function(done) { 143 | var a, fn; 144 | a = 0; 145 | fn = function() { 146 | this.should.equal(gulp); 147 | ++a; 148 | }; 149 | gulp.task('default', fn); 150 | gulp.run(); 151 | a.should.equal(1); 152 | gulp.isRunning.should.equal(false); 153 | gulp.reset(); 154 | done(); 155 | }); 156 | }); 157 | }); 158 | -------------------------------------------------------------------------------- /docs/recipes/make-stream-from-buffer.md: -------------------------------------------------------------------------------- 1 | # Make stream from buffer (memory contents) 2 | 3 | Sometimes you may need to start a stream with files that their contents are in a variable and not in a physical file. In other words, how to start a 'gulp' stream without using `gulp.src()`. 4 | 5 | Let's say for example that we have a directory with js lib files and another directory with versions of some module. The target of the build would be to create one js file for each version, containing all the libs and the version of the module concatenated. 6 | 7 | Logically we would break it down like this: 8 | 9 | * load the lib files 10 | * concatenate the lib file contents 11 | * load the versions files 12 | * for each version file, concatenate the libs' contents and the version file contents 13 | * for each version file, output the result in a file 14 | 15 | Imagine this file structure: 16 | 17 | ```sh 18 | ├── libs 19 | │   ├── lib1.js 20 | │   └── lib2.js 21 | └── versions 22 | ├── version.1.js 23 | └── version.2.js 24 | ``` 25 | 26 | You should get: 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 | A simple and modular way to do this would be the following: 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 = {}; // we'll keep our assets in memory 48 | 49 | // task of loading the files' contents in memory 50 | gulp.task('load-lib-files', function() { 51 | // read the lib files from the disk 52 | return gulp.src('src/libs/*.js') 53 | // concatenate all lib files into one 54 | .pipe(concat('libs.concat.js')) 55 | // tap into the stream to get each file's data 56 | .pipe(tap(function(file) { 57 | // save the file contents in memory 58 | memory[path.basename(file.path)] = file.contents.toString(); 59 | })); 60 | }); 61 | 62 | gulp.task('load-versions', function() { 63 | memory.versions = {}; 64 | // read the version files from the disk 65 | return gulp.src('src/versions/version.*.js') 66 | // tap into the stream to get each file's data 67 | .pipe( tap(function(file) { 68 | // save the file contents in the assets 69 | memory.versions[path.basename(file.path)] = file.contents.toString(); 70 | })); 71 | }); 72 | 73 | gulp.task('write-versions', function() { 74 | // we store all the different version file names in an array 75 | var availableVersions = Object.keys(memory.versions); 76 | // we make an array to store all the stream promises 77 | var streams = []; 78 | 79 | availableVersions.forEach(function(v) { 80 | // make a new stream with fake file name 81 | var stream = source('final.' + v); 82 | 83 | var streamEnd = stream; 84 | 85 | // we load the data from the concatenated libs 86 | var fileContents = memory['libs.concat.js'] + 87 | // we add the version's data 88 | '\n' + memory.versions[v]; 89 | 90 | // write the file contents to the stream 91 | stream.write(fileContents); 92 | 93 | process.nextTick(function() { 94 | // in the next process cycle, end the stream 95 | stream.end(); 96 | }); 97 | 98 | streamEnd = streamEnd 99 | // transform the raw data into the stream, into a vinyl object/file 100 | .pipe(vinylBuffer()) 101 | //.pipe(tap(function(file) { /* do something with the file contents here */ })) 102 | .pipe(gulp.dest('output')); 103 | 104 | // add the end of the stream, otherwise the task would finish before all the processing 105 | // is done 106 | streams.push(streamEnd); 107 | 108 | }); 109 | 110 | return es.merge.apply(this, streams); 111 | }); 112 | 113 | //============================================ our main task 114 | gulp.task('default', function(taskDone) { 115 | runSequence( 116 | ['load-lib-files', 'load-versions'], // load the files in parallel 117 | 'write-versions', // ready to write once all resources are in memory 118 | taskDone // done 119 | ); 120 | }); 121 | 122 | //============================================ our watcher task 123 | // only watch after having run 'default' once so that all resources 124 | // are already in memory 125 | gulp.task('watch', ['default'], function() { 126 | gulp.watch('./src/libs/*.js', function() { 127 | runSequence( 128 | 'load-lib-files', // we only have to load the changed files 129 | 'write-versions' 130 | ); 131 | }); 132 | 133 | gulp.watch('./src/versions/*.js', function() { 134 | runSequence( 135 | 'load-versions', // we only have to load the changed files 136 | 'write-versions' 137 | ); 138 | }); 139 | }); 140 | ``` 141 | -------------------------------------------------------------------------------- /test/dest.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var gulp = require('../'); 4 | var should = require('should'); 5 | var join = require('path').join; 6 | var rimraf = require('rimraf'); 7 | var fs = require('graceful-fs'); 8 | 9 | require('mocha'); 10 | 11 | var outpath = join(__dirname, './out-fixtures'); 12 | 13 | describe('gulp output stream', function() { 14 | describe('dest()', function() { 15 | beforeEach(rimraf.bind(null, outpath)); 16 | afterEach(rimraf.bind(null, outpath)); 17 | 18 | it('should return a stream', function(done) { 19 | var stream = gulp.dest(join(__dirname, './fixtures/')); 20 | should.exist(stream); 21 | should.exist(stream.on); 22 | done(); 23 | }); 24 | 25 | it('should return a output stream that writes files', function(done) { 26 | var instream = gulp.src(join(__dirname, './fixtures/**/*.txt')); 27 | var outstream = gulp.dest(outpath); 28 | instream.pipe(outstream); 29 | 30 | outstream.on('error', done); 31 | outstream.on('data', function(file) { 32 | // Data should be re-emitted right 33 | should.exist(file); 34 | should.exist(file.path); 35 | should.exist(file.contents); 36 | join(file.path, '').should.equal(join(outpath, './copy/example.txt')); 37 | String(file.contents).should.equal('this is a test'); 38 | }); 39 | outstream.on('end', function() { 40 | fs.readFile(join(outpath, 'copy', 'example.txt'), function(err, contents) { 41 | should.not.exist(err); 42 | should.exist(contents); 43 | String(contents).should.equal('this is a test'); 44 | done(); 45 | }); 46 | }); 47 | }); 48 | 49 | it('should return a output stream that does not write non-read files', function(done) { 50 | var instream = gulp.src(join(__dirname, './fixtures/**/*.txt'), { read: false }); 51 | var outstream = gulp.dest(outpath); 52 | instream.pipe(outstream); 53 | 54 | outstream.on('error', done); 55 | outstream.on('data', function(file) { 56 | // Data should be re-emitted right 57 | should.exist(file); 58 | should.exist(file.path); 59 | should.not.exist(file.contents); 60 | join(file.path, '').should.equal(join(outpath, './copy/example.txt')); 61 | }); 62 | outstream.on('end', function() { 63 | fs.readFile(join(outpath, 'copy', 'example.txt'), function(err, contents) { 64 | should.exist(err); 65 | should.not.exist(contents); 66 | done(); 67 | }); 68 | }); 69 | }); 70 | 71 | it('should return a output stream that writes streaming files', function(done) { 72 | var instream = gulp.src(join(__dirname, './fixtures/**/*.txt'), { buffer: false }); 73 | var outstream = instream.pipe(gulp.dest(outpath)); 74 | 75 | outstream.on('error', done); 76 | outstream.on('data', function(file) { 77 | // Data should be re-emitted right 78 | should.exist(file); 79 | should.exist(file.path); 80 | should.exist(file.contents); 81 | join(file.path, '').should.equal(join(outpath, './copy/example.txt')); 82 | }); 83 | outstream.on('end', function() { 84 | fs.readFile(join(outpath, 'copy', 'example.txt'), function(err, contents) { 85 | should.not.exist(err); 86 | should.exist(contents); 87 | String(contents).should.equal('this is a test'); 88 | done(); 89 | }); 90 | }); 91 | }); 92 | 93 | it('should return a output stream that writes streaming files into new directories', function(done) { 94 | testWriteDir({}, done); 95 | }); 96 | 97 | it('should return a output stream that writes streaming files into new directories (buffer: false)', function(done) { 98 | testWriteDir({ buffer: false }, done); 99 | }); 100 | 101 | it('should return a output stream that writes streaming files into new directories (read: false)', function(done) { 102 | testWriteDir({ read: false }, done); 103 | }); 104 | 105 | it('should return a output stream that writes streaming files into new directories (read: false, buffer: false)', function(done) { 106 | testWriteDir({ buffer: false, read: false }, done); 107 | }); 108 | 109 | function testWriteDir(srcOptions, done) { 110 | var instream = gulp.src(join(__dirname, './fixtures/stuff'), srcOptions); 111 | var outstream = instream.pipe(gulp.dest(outpath)); 112 | 113 | outstream.on('error', done); 114 | outstream.on('data', function(file) { 115 | // Data should be re-emitted right 116 | should.exist(file); 117 | should.exist(file.path); 118 | join(file.path, '').should.equal(join(outpath, './stuff')); 119 | }); 120 | outstream.on('end', function() { 121 | fs.exists(join(outpath, 'stuff'), function(exists) { 122 | /* Stinks that ok is an expression instead of a function call */ 123 | /* jshint expr: true */ 124 | should(exists).be.ok; 125 | /* jshint expr: false */ 126 | done(); 127 | }); 128 | }); 129 | } 130 | 131 | }); 132 | }); 133 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 | 4 | 5 |

The streaming build system

6 |

7 | 8 | [![NPM version][npm-image]][npm-url] [![Downloads][downloads-image]][npm-url] [![Build Status][travis-image]][travis-url] [![Coveralls Status][coveralls-image]][coveralls-url] [![OpenCollective Backers][backer-badge]][backer-url] [![OpenCollective Sponsors][sponsor-badge]][sponsor-url] [![Gitter chat][gitter-image]][gitter-url] 9 | 10 | 11 | ## What is gulp? 12 | 13 | - **Automation** - gulp is a toolkit that helps you automate painful or time-consuming tasks in your development workflow. 14 | - **Platform-agnostic** - Integrations are built into all major IDEs and people are using gulp with PHP, .NET, Node.js, Java, and other platforms. 15 | - **Strong Ecosystem** - Use npm modules to do anything you want + over 2000 curated plugins for streaming file transformations 16 | - **Simple** - By providing only a minimal API surface, gulp is easy to learn and simple to use 17 | 18 | ## Documentation 19 | 20 | For a Getting started guide, API docs, recipes, making a plugin, etc. check out our docs! 21 | 22 | - Need something reliable? Check out the [documentation for the current release](/docs/README.md)! 23 | - Want to help us test the latest and greatest? Check out the [documentation for the next release](https://github.com/gulpjs/gulp/tree/4.0)! 24 | 25 | ## Sample `gulpfile.js` 26 | 27 | This file will give you a taste of what gulp does. 28 | 29 | ```js 30 | var gulp = require('gulp'); 31 | var coffee = require('gulp-coffee'); 32 | var concat = require('gulp-concat'); 33 | var uglify = require('gulp-uglify'); 34 | var imagemin = require('gulp-imagemin'); 35 | var sourcemaps = require('gulp-sourcemaps'); 36 | var del = require('del'); 37 | 38 | var paths = { 39 | scripts: ['client/js/**/*.coffee', '!client/external/**/*.coffee'], 40 | images: 'client/img/**/*' 41 | }; 42 | 43 | // Not all tasks need to use streams 44 | // A gulpfile is just another node program and you can use any package available on npm 45 | gulp.task('clean', function() { 46 | // You can use multiple globbing patterns as you would with `gulp.src` 47 | return del(['build']); 48 | }); 49 | 50 | gulp.task('scripts', ['clean'], function() { 51 | // Minify and copy all JavaScript (except vendor scripts) 52 | // with sourcemaps all the way down 53 | return gulp.src(paths.scripts) 54 | .pipe(sourcemaps.init()) 55 | .pipe(coffee()) 56 | .pipe(uglify()) 57 | .pipe(concat('all.min.js')) 58 | .pipe(sourcemaps.write()) 59 | .pipe(gulp.dest('build/js')); 60 | }); 61 | 62 | // Copy all static images 63 | gulp.task('images', ['clean'], function() { 64 | return gulp.src(paths.images) 65 | // Pass in options to the task 66 | .pipe(imagemin({optimizationLevel: 5})) 67 | .pipe(gulp.dest('build/img')); 68 | }); 69 | 70 | // Rerun the task when a file changes 71 | gulp.task('watch', function() { 72 | gulp.watch(paths.scripts, ['scripts']); 73 | gulp.watch(paths.images, ['images']); 74 | }); 75 | 76 | // The default task (called when you run `gulp` from cli) 77 | gulp.task('default', ['watch', 'scripts', 'images']); 78 | ``` 79 | 80 | ## Incremental Builds 81 | 82 | We recommend these plugins: 83 | 84 | - [gulp-changed](https://github.com/sindresorhus/gulp-changed) - only pass through changed files 85 | - [gulp-cached](https://github.com/contra/gulp-cached) - in-memory file cache, not for operation on sets of files 86 | - [gulp-remember](https://github.com/ahaurw01/gulp-remember) - pairs nicely with gulp-cached 87 | - [gulp-newer](https://github.com/tschaub/gulp-newer) - pass through newer source files only, supports many:1 source:dest 88 | 89 | ## Want to contribute? 90 | 91 | Anyone can help make this project better - check out our [Contributing guide](/CONTRIBUTING.md)! 92 | 93 | ## Backers 94 | 95 | Support us with a monthly donation and help us continue our activities. 96 | 97 | [![Backers][backers-image]][members-url] [![Become a Backer][become-backer-image]][support-url] 98 | 99 | ## Sponsors 100 | 101 | Become a sponsor to get your logo on our README on Github. 102 | 103 | [![Sponsors][sponsors-image]][members-url] [![Become a Sponsor][become-sponsor-image]][support-url] 104 | 105 | [downloads-image]: https://img.shields.io/npm/dm/gulp.svg 106 | [npm-url]: https://www.npmjs.com/package/gulp 107 | [npm-image]: https://img.shields.io/npm/v/gulp.svg 108 | 109 | [travis-url]: https://travis-ci.org/gulpjs/gulp 110 | [travis-image]: https://img.shields.io/travis/gulpjs/gulp/master.svg 111 | 112 | [coveralls-url]: https://coveralls.io/r/gulpjs/gulp 113 | [coveralls-image]: https://img.shields.io/coveralls/gulpjs/gulp/master.svg 114 | 115 | [gitter-url]: https://gitter.im/gulpjs/gulp 116 | [gitter-image]: https://badges.gitter.im/gulpjs/gulp.svg 117 | 118 | [backer-url]: #backers 119 | [backer-badge]: https://opencollective.com/gulpjs/backers/badge.svg?color=blue 120 | [sponsor-url]: #sponsors 121 | [sponsor-badge]: https://opencollective.com/gulpjs/sponsors/badge.svg?color=blue 122 | 123 | [members-url]: https://opencollective.com/gulpjs#members-wall 124 | [support-url]: https://opencollective.com/gulpjs#support 125 | 126 | [backers-image]: https://opencollective.com/gulpjs/backers.svg 127 | [become-backer-image]: https://opencollective.com/static/images/become_backer.svg 128 | [sponsors-image]: https://opencollective.com/gulpjs/sponsors.svg 129 | [become-sponsor-image]: https://opencollective.com/static/images/become_sponsor.svg 130 | -------------------------------------------------------------------------------- /test/src.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var gulp = require('../'); 4 | var should = require('should'); 5 | var join = require('path').join; 6 | 7 | require('mocha'); 8 | 9 | describe('gulp input stream', function() { 10 | describe('src()', function() { 11 | it('should return a stream', function(done) { 12 | var stream = gulp.src(join(__dirname, './fixtures/*.coffee')); 13 | should.exist(stream); 14 | should.exist(stream.on); 15 | done(); 16 | }); 17 | it('should return a input stream from a flat glob', function(done) { 18 | var stream = gulp.src(join(__dirname, './fixtures/*.coffee')); 19 | stream.on('error', done); 20 | stream.on('data', function(file) { 21 | should.exist(file); 22 | should.exist(file.path); 23 | should.exist(file.contents); 24 | join(file.path, '').should.equal(join(__dirname, './fixtures/test.coffee')); 25 | String(file.contents).should.equal('this is a test'); 26 | }); 27 | stream.on('end', function() { 28 | done(); 29 | }); 30 | }); 31 | 32 | it('should return a input stream for multiple globs', function(done) { 33 | var globArray = [ 34 | join(__dirname, './fixtures/stuff/run.dmc'), 35 | join(__dirname, './fixtures/stuff/test.dmc'), 36 | ]; 37 | var stream = gulp.src(globArray); 38 | 39 | var files = []; 40 | stream.on('error', done); 41 | stream.on('data', function(file) { 42 | should.exist(file); 43 | should.exist(file.path); 44 | files.push(file); 45 | }); 46 | stream.on('end', function() { 47 | files.length.should.equal(2); 48 | files[0].path.should.equal(globArray[0]); 49 | files[1].path.should.equal(globArray[1]); 50 | done(); 51 | }); 52 | }); 53 | 54 | it('should return a input stream for multiple globs, with negation', function(done) { 55 | var expectedPath = join(__dirname, './fixtures/stuff/run.dmc'); 56 | var globArray = [ 57 | join(__dirname, './fixtures/stuff/*.dmc'), 58 | '!' + join(__dirname, './fixtures/stuff/test.dmc'), 59 | ]; 60 | var stream = gulp.src(globArray); 61 | 62 | var files = []; 63 | stream.on('error', done); 64 | stream.on('data', function(file) { 65 | should.exist(file); 66 | should.exist(file.path); 67 | files.push(file); 68 | }); 69 | stream.on('end', function() { 70 | files.length.should.equal(1); 71 | files[0].path.should.equal(expectedPath); 72 | done(); 73 | }); 74 | }); 75 | 76 | it('should return a input stream with no contents when read is false', function(done) { 77 | var stream = gulp.src(join(__dirname, './fixtures/*.coffee'), { read: false }); 78 | stream.on('error', done); 79 | stream.on('data', function(file) { 80 | should.exist(file); 81 | should.exist(file.path); 82 | should.not.exist(file.contents); 83 | join(file.path, '').should.equal(join(__dirname, './fixtures/test.coffee')); 84 | }); 85 | stream.on('end', function() { 86 | done(); 87 | }); 88 | }); 89 | it('should return a input stream with contents as stream when buffer is false', function(done) { 90 | var stream = gulp.src(join(__dirname, './fixtures/*.coffee'), { buffer: false }); 91 | stream.on('error', done); 92 | stream.on('data', function(file) { 93 | should.exist(file); 94 | should.exist(file.path); 95 | should.exist(file.contents); 96 | var buf = ''; 97 | file.contents.on('data', function(d) { 98 | buf += d; 99 | }); 100 | file.contents.on('end', function() { 101 | buf.should.equal('this is a test'); 102 | done(); 103 | }); 104 | join(file.path, '').should.equal(join(__dirname, './fixtures/test.coffee')); 105 | }); 106 | }); 107 | it('should return a input stream from a deep glob', function(done) { 108 | var stream = gulp.src(join(__dirname, './fixtures/**/*.jade')); 109 | stream.on('error', done); 110 | stream.on('data', function(file) { 111 | should.exist(file); 112 | should.exist(file.path); 113 | should.exist(file.contents); 114 | join(file.path, '').should.equal(join(__dirname, './fixtures/test/run.jade')); 115 | String(file.contents).should.equal('test template'); 116 | }); 117 | stream.on('end', function() { 118 | done(); 119 | }); 120 | }); 121 | it('should return a input stream from a deeper glob', function(done) { 122 | var stream = gulp.src(join(__dirname, './fixtures/**/*.dmc')); 123 | var a = 0; 124 | stream.on('error', done); 125 | stream.on('data', function() { 126 | ++a; 127 | }); 128 | stream.on('end', function() { 129 | a.should.equal(2); 130 | done(); 131 | }); 132 | }); 133 | 134 | it('should return a file stream from a flat path', function(done) { 135 | var a = 0; 136 | var stream = gulp.src(join(__dirname, './fixtures/test.coffee')); 137 | stream.on('error', done); 138 | stream.on('data', function(file) { 139 | ++a; 140 | should.exist(file); 141 | should.exist(file.path); 142 | should.exist(file.contents); 143 | join(file.path, '').should.equal(join(__dirname, './fixtures/test.coffee')); 144 | String(file.contents).should.equal('this is a test'); 145 | }); 146 | stream.on('end', function() { 147 | a.should.equal(1); 148 | done(); 149 | }); 150 | }); 151 | }); 152 | }); 153 | -------------------------------------------------------------------------------- /test/watch.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var gulp = require('../'); 4 | var fs = require('graceful-fs'); 5 | var rimraf = require('rimraf'); 6 | var mkdirp = require('mkdirp'); 7 | var path = require('path'); 8 | 9 | var should = require('should'); 10 | require('mocha'); 11 | 12 | var outpath = path.join(__dirname, './out-fixtures'); 13 | 14 | describe('gulp', function() { 15 | describe('watch()', function() { 16 | beforeEach(rimraf.bind(null, outpath)); 17 | beforeEach(mkdirp.bind(null, outpath)); 18 | afterEach(rimraf.bind(null, outpath)); 19 | 20 | var tempFileContent = 'A test generated this file and it is safe to delete'; 21 | 22 | var writeTimeout = 125; // Wait for it to get to the filesystem 23 | var writeFileWait = function(name, content, cb) { 24 | if (!cb) { 25 | cb = function() {}; 26 | } 27 | setTimeout(function() { 28 | fs.writeFile(name, content, cb); 29 | }, writeTimeout); 30 | }; 31 | 32 | it('should call the function when file changes: no options', function(done) { 33 | 34 | // Arrange 35 | var tempFile = path.join(outpath, 'watch-func.txt'); 36 | fs.writeFile(tempFile, tempFileContent, function() { 37 | 38 | // Assert: it works if it calls done 39 | var watcher = gulp.watch(tempFile, function(evt) { 40 | should.exist(evt); 41 | should.exist(evt.path); 42 | should.exist(evt.type); 43 | evt.type.should.equal('changed'); 44 | evt.path.should.equal(path.resolve(tempFile)); 45 | watcher.end(); 46 | done(); 47 | }); 48 | 49 | // Act: change file 50 | writeFileWait(tempFile, tempFileContent + ' changed'); 51 | }); 52 | }); 53 | 54 | it('should call the function when file changes: w/ options', function(done) { 55 | 56 | // Arrange 57 | var tempFile = path.join(outpath, 'watch-func-options.txt'); 58 | fs.writeFile(tempFile, tempFileContent, function() { 59 | 60 | // Assert: it works if it calls done 61 | var watcher = gulp.watch(tempFile, { debounceDelay: 5 }, function(evt) { 62 | should.exist(evt); 63 | should.exist(evt.path); 64 | should.exist(evt.type); 65 | evt.type.should.equal('changed'); 66 | evt.path.should.equal(path.resolve(tempFile)); 67 | watcher.end(); 68 | done(); 69 | }); 70 | 71 | // Act: change file 72 | writeFileWait(tempFile, tempFileContent + ' changed'); 73 | }); 74 | }); 75 | 76 | it('should not drop options when no callback specified', function(done) { 77 | // Arrange 78 | var tempFile = path.join(outpath, 'watch-func-nodrop-options.txt'); 79 | // By passing a cwd option, ensure options are not lost to gaze 80 | var relFile = '../watch-func-nodrop-options.txt'; 81 | var cwd = outpath + '/subdir'; 82 | fs.writeFile(tempFile, tempFileContent, function() { 83 | 84 | // Assert: it works if it calls done 85 | var watcher = gulp.watch(relFile, { debounceDelay: 5, cwd: cwd }) 86 | .on('change', function(evt) { 87 | should.exist(evt); 88 | should.exist(evt.path); 89 | should.exist(evt.type); 90 | evt.type.should.equal('changed'); 91 | evt.path.should.equal(path.resolve(tempFile)); 92 | watcher.end(); 93 | done(); 94 | }); 95 | 96 | // Act: change file 97 | writeFileWait(tempFile, tempFileContent + ' changed'); 98 | }); 99 | }); 100 | 101 | it('should run many tasks: w/ options', function(done) { 102 | // Arrange 103 | var tempFile = path.join(outpath, 'watch-task-options.txt'); 104 | var task1 = 'task1'; 105 | var task2 = 'task2'; 106 | var task3 = 'task3'; 107 | var a = 0; 108 | var timeout = writeTimeout * 2.5; 109 | 110 | fs.writeFile(tempFile, tempFileContent, function() { 111 | 112 | gulp.task(task1, function() { 113 | a++; 114 | }); 115 | gulp.task(task2, function() { 116 | a += 10; 117 | }); 118 | gulp.task(task3, function() { 119 | throw new Error('task3 called!'); 120 | }); 121 | 122 | // It works if it calls the task 123 | var config = { debounceDelay: timeout / 2 }; 124 | var watcher = gulp.watch(tempFile, config, [task1, task2]); 125 | 126 | // Assert 127 | setTimeout(function() { 128 | a.should.equal(11); // Task1 and task2 129 | 130 | gulp.reset(); 131 | watcher.end(); 132 | done(); 133 | }, timeout); 134 | 135 | // Act: change file 136 | writeFileWait(tempFile, tempFileContent + ' changed'); 137 | }); 138 | }); 139 | 140 | it('should run many tasks: no options', function(done) { 141 | // Arrange 142 | var tempFile = path.join(outpath, 'watch-many-tasks-no-options.txt'); 143 | var task1 = 'task1'; 144 | var task2 = 'task2'; 145 | var task3 = 'task3'; 146 | var a = 0; 147 | var timeout = writeTimeout * 2.5; 148 | 149 | fs.writeFile(tempFile, tempFileContent, function() { 150 | 151 | gulp.task(task1, function() { 152 | a++; 153 | }); 154 | gulp.task(task2, function() { 155 | a += 10; 156 | }); 157 | gulp.task(task3, function() { 158 | throw new Error('task3 called!'); 159 | }); 160 | 161 | // It works if it calls the task 162 | var watcher = gulp.watch(tempFile, [task1, task2]); 163 | 164 | // Assert 165 | setTimeout(function() { 166 | a.should.equal(11); // Task1 and task2 167 | 168 | gulp.reset(); 169 | watcher.end(); 170 | done(); 171 | }, timeout); 172 | 173 | // Act: change file 174 | writeFileWait(tempFile, tempFileContent + ' changed'); 175 | }); 176 | }); 177 | 178 | }); 179 | }); 180 | -------------------------------------------------------------------------------- /docs/writing-a-plugin/guidelines.md: -------------------------------------------------------------------------------- 1 | # Guidelines 2 | 3 | > While these guidelines are totally optional, we **HIGHLY** recommend that everyone follows them. Nobody wants to use a bad plugin. These guidelines will actually help make your life easier by giving you assurance that your plugin fits well within gulp. 4 | 5 | [Writing a Plugin](README.md) > Guidelines 6 | 7 | 1. Your plugin should not do something that can be done easily with an existing node module 8 | - For example: deleting a folder does not need to be a gulp plugin. Use a module like [del](https://github.com/sindresorhus/del) within a task instead. 9 | - Wrapping every possible thing just for the sake of wrapping it will pollute the ecosystem with low quality plugins that don't make sense within the gulp paradigm. 10 | - gulp plugins are for file-based operations! If you find yourself shoehorning a complex process into streams just make a normal node module instead. 11 | - A good example of a gulp plugin would be something like gulp-coffee. The coffee-script module does not work with Vinyl out of the box, so we wrap it to add this functionality and abstract away pain points to make it work well within gulp. 12 | 1. Your plugin should only do **one thing**, and do it well. 13 | - Avoid config options that make your plugin do completely different tasks 14 | - For example: A JS minification plugin should not have an option that adds a header as well 15 | 1. Your plugin shouldn't do things that other plugins are responsible for 16 | - It should not concat, [gulp-concat](https://github.com/contra/gulp-concat) does that 17 | - It should not add headers, [gulp-header](https://github.com/godaddy/gulp-header) does that 18 | - It should not add footers, [gulp-footer](https://github.com/godaddy/gulp-footer) does that 19 | - If it's a common but optional use case, document that your plugin is often used with another plugin 20 | - Make use of other plugins within your plugin! This reduces the amount of code you have to write and ensures a stable ecosystem. 21 | 1. Your plugin **must be tested** 22 | - Testing a gulp plugin is easy, you don't even need gulp to test it 23 | - Look at other plugins for examples 24 | 1. Add `gulpplugin` as a keyword in your `package.json` so you show up on our search 25 | 1. Do not throw errors inside a stream 26 | - Instead, you should emit it as an **error** event. 27 | - If you encounter an error **outside** the stream, such as invalid configuration while creating the stream, you may throw it. 28 | 1. Prefix any errors with the name of your plugin 29 | - For example: `gulp-replace: Cannot do regexp replace on a stream` 30 | - Use gulp-util's [PluginError](https://github.com/gulpjs/gulp-util#new-pluginerrorpluginname-message-options) class to make this easy 31 | 1. Name your plugin appropriately: it should begin with "gulp-" if it is a gulp plugin 32 | - If it is not a gulp plugin, it should not begin with "gulp-" 33 | 1. The type of `file.contents` should always be the same going out as it was when it came in 34 | - If file.contents is null (non-read) just ignore the file and pass it along 35 | - If file.contents is a Stream and you don't support that just emit an error 36 | - Do not buffer a stream to shoehorn your plugin to work with streams. This will cause horrible things to happen. 37 | 1. Do not pass the `file` object downstream until you are done with it 38 | 1. Use [`file.clone()`](https://github.com/gulpjs/vinyl#clone) when cloning a file or creating a new one based on a file. 39 | 1. Use modules from our [recommended modules page](recommended-modules.md) to make your life easier 40 | 1. Do NOT require `gulp` as a dependency or peerDependency in your plugin 41 | - Using gulp to test or automate your plugin workflow is totally cool, just make sure you put it as a devDependency 42 | - Requiring gulp as a dependency of your plugin means that anyone who installs your plugin is also installing a new gulp and its entire dependency tree. 43 | - There is no reason you should be using gulp within your actual plugin code. If you find yourself doing this open an issue so we can help you out. 44 | 45 | ## Why are these guidelines so strict? 46 | 47 | gulp aims to be simple for users. By providing strict guidelines we are able to provide a consistent and high-quality ecosystem for everyone. While this does add a little more work and thought for plugin authors, it removes a lot of problems later down the road. 48 | 49 | ### What happens if I don't follow them? 50 | 51 | npm is open for everyone, and you are free to make whatever you want but these guidelines were prescribed for a reason. There are acceptance tests coming soon that will be integrated into the plugin search. If you fail to adhere to the plugin guidelines it will be publicly visible/sortable via a scoring system. People will always prefer to use plugins that match "the gulp way". 52 | 53 | ### What does a good plugin look like? 54 | 55 | ```js 56 | // through2 is a thin wrapper around node transform streams 57 | var through = require('through2'); 58 | var gutil = require('gulp-util'); 59 | var PluginError = gutil.PluginError; 60 | 61 | // Consts 62 | const PLUGIN_NAME = 'gulp-prefixer'; 63 | 64 | function prefixStream(prefixText) { 65 | var stream = through(); 66 | stream.write(prefixText); 67 | return stream; 68 | } 69 | 70 | // Plugin level function(dealing with files) 71 | function gulpPrefixer(prefixText) { 72 | 73 | if (!prefixText) { 74 | throw new PluginError(PLUGIN_NAME, 'Missing prefix text!'); 75 | } 76 | prefixText = new Buffer(prefixText); // allocate ahead of time 77 | 78 | // Creating a stream through which each file will pass 79 | return through.obj(function(file, enc, cb) { 80 | if (file.isNull()) { 81 | // return empty file 82 | return cb(null, file); 83 | } 84 | if (file.isBuffer()) { 85 | file.contents = Buffer.concat([prefixText, file.contents]); 86 | } 87 | if (file.isStream()) { 88 | file.contents = file.contents.pipe(prefixStream(prefixText)); 89 | } 90 | 91 | cb(null, file); 92 | 93 | }); 94 | 95 | } 96 | 97 | // Exporting the plugin main function 98 | module.exports = gulpPrefixer; 99 | ``` 100 | -------------------------------------------------------------------------------- /bin/gulp.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | 'use strict'; 4 | var gutil = require('gulp-util'); 5 | var prettyTime = require('pretty-hrtime'); 6 | var chalk = require('chalk'); 7 | var semver = require('semver'); 8 | var archy = require('archy'); 9 | var Liftoff = require('liftoff'); 10 | var tildify = require('tildify'); 11 | var interpret = require('interpret'); 12 | var v8flags = require('v8flags'); 13 | var completion = require('../lib/completion'); 14 | var argv = require('minimist')(process.argv.slice(2)); 15 | var taskTree = require('../lib/taskTree'); 16 | 17 | // Set env var for ORIGINAL cwd 18 | // before anything touches it 19 | process.env.INIT_CWD = process.cwd(); 20 | 21 | var cli = new Liftoff({ 22 | name: 'gulp', 23 | completions: completion, 24 | extensions: interpret.jsVariants, 25 | v8flags: v8flags, 26 | }); 27 | 28 | // Exit with 0 or 1 29 | var failed = false; 30 | process.once('exit', function(code) { 31 | if (code === 0 && failed) { 32 | process.exit(1); 33 | } 34 | }); 35 | 36 | // Parse those args m8 37 | var cliPackage = require('../package'); 38 | var versionFlag = argv.v || argv.version; 39 | var tasksFlag = argv.T || argv.tasks; 40 | var tasks = argv._; 41 | var toRun = tasks.length ? tasks : ['default']; 42 | 43 | // This is a hold-over until we have a better logging system 44 | // with log levels 45 | var simpleTasksFlag = argv['tasks-simple']; 46 | var shouldLog = !argv.silent && !simpleTasksFlag; 47 | 48 | if (!shouldLog) { 49 | gutil.log = function() {}; 50 | } 51 | 52 | cli.on('require', function(name) { 53 | gutil.log('Requiring external module', chalk.magenta(name)); 54 | }); 55 | 56 | cli.on('requireFail', function(name) { 57 | gutil.log(chalk.red('Failed to load external module'), chalk.magenta(name)); 58 | }); 59 | 60 | cli.on('respawn', function(flags, child) { 61 | var nodeFlags = chalk.magenta(flags.join(', ')); 62 | var pid = chalk.magenta(child.pid); 63 | gutil.log('Node flags detected:', nodeFlags); 64 | gutil.log('Respawned to PID:', pid); 65 | }); 66 | 67 | cli.launch({ 68 | cwd: argv.cwd, 69 | configPath: argv.gulpfile, 70 | require: argv.require, 71 | completion: argv.completion, 72 | }, handleArguments); 73 | 74 | // The actual logic 75 | function handleArguments(env) { 76 | if (versionFlag && tasks.length === 0) { 77 | gutil.log('CLI version', cliPackage.version); 78 | if (env.modulePackage && typeof env.modulePackage.version !== 'undefined') { 79 | gutil.log('Local version', env.modulePackage.version); 80 | } 81 | process.exit(0); 82 | } 83 | 84 | if (!env.modulePath) { 85 | gutil.log( 86 | chalk.red('Local gulp not found in'), 87 | chalk.magenta(tildify(env.cwd)) 88 | ); 89 | gutil.log(chalk.red('Try running: npm install gulp')); 90 | process.exit(1); 91 | } 92 | 93 | if (!env.configPath) { 94 | gutil.log(chalk.red('No gulpfile found')); 95 | process.exit(1); 96 | } 97 | 98 | // Check for semver difference between cli and local installation 99 | if (semver.gt(cliPackage.version, env.modulePackage.version)) { 100 | gutil.log(chalk.red('Warning: gulp version mismatch:')); 101 | gutil.log(chalk.red('Global gulp is', cliPackage.version)); 102 | gutil.log(chalk.red('Local gulp is', env.modulePackage.version)); 103 | } 104 | 105 | // Chdir before requiring gulpfile to make sure 106 | // we let them chdir as needed 107 | if (process.cwd() !== env.cwd) { 108 | process.chdir(env.cwd); 109 | gutil.log( 110 | 'Working directory changed to', 111 | chalk.magenta(tildify(env.cwd)) 112 | ); 113 | } 114 | 115 | // This is what actually loads up the gulpfile 116 | require(env.configPath); 117 | gutil.log('Using gulpfile', chalk.magenta(tildify(env.configPath))); 118 | 119 | var gulpInst = require(env.modulePath); 120 | logEvents(gulpInst); 121 | 122 | process.nextTick(function() { 123 | if (simpleTasksFlag) { 124 | return logTasksSimple(env, gulpInst); 125 | } 126 | if (tasksFlag) { 127 | return logTasks(env, gulpInst); 128 | } 129 | gulpInst.start.apply(gulpInst, toRun); 130 | }); 131 | } 132 | 133 | function logTasks(env, localGulp) { 134 | var tree = taskTree(localGulp.tasks); 135 | tree.label = 'Tasks for ' + chalk.magenta(tildify(env.configPath)); 136 | archy(tree) 137 | .split('\n') 138 | .forEach(function(v) { 139 | if (v.trim().length === 0) { 140 | return; 141 | } 142 | gutil.log(v); 143 | }); 144 | } 145 | 146 | function logTasksSimple(env, localGulp) { 147 | console.log(Object.keys(localGulp.tasks) 148 | .join('\n') 149 | .trim()); 150 | } 151 | 152 | // Format orchestrator errors 153 | function formatError(e) { 154 | if (!e.err) { 155 | return e.message; 156 | } 157 | 158 | // PluginError 159 | if (typeof e.err.showStack === 'boolean') { 160 | return e.err.toString(); 161 | } 162 | 163 | // Normal error 164 | if (e.err.stack) { 165 | return e.err.stack; 166 | } 167 | 168 | // Unknown (string, number, etc.) 169 | return new Error(String(e.err)).stack; 170 | } 171 | 172 | // Wire up logging events 173 | function logEvents(gulpInst) { 174 | 175 | // Total hack due to poor error management in orchestrator 176 | gulpInst.on('err', function() { 177 | failed = true; 178 | }); 179 | 180 | gulpInst.on('task_start', function(e) { 181 | // TODO: batch these 182 | // so when 5 tasks start at once it only logs one time with all 5 183 | gutil.log('Starting', '\'' + chalk.cyan(e.task) + '\'...'); 184 | }); 185 | 186 | gulpInst.on('task_stop', function(e) { 187 | var time = prettyTime(e.hrDuration); 188 | gutil.log( 189 | 'Finished', '\'' + chalk.cyan(e.task) + '\'', 190 | 'after', chalk.magenta(time) 191 | ); 192 | }); 193 | 194 | gulpInst.on('task_err', function(e) { 195 | var msg = formatError(e); 196 | var time = prettyTime(e.hrDuration); 197 | gutil.log( 198 | '\'' + chalk.cyan(e.task) + '\'', 199 | chalk.red('errored after'), 200 | chalk.magenta(time) 201 | ); 202 | gutil.log(msg); 203 | }); 204 | 205 | gulpInst.on('task_not_found', function(err) { 206 | gutil.log( 207 | chalk.red('Task \'' + err.task + '\' is not in your gulpfile') 208 | ); 209 | gutil.log('Please check the documentation for proper gulpfile formatting'); 210 | process.exit(1); 211 | }); 212 | } 213 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # gulp changelog 2 | 3 | ## 3.9.1 4 | 5 | - update interpret to 1.0.0 (support for babel-register) 6 | - fix to include manpages in published tarball 7 | - documentation/recipe updates 8 | 9 | ## 3.9.0 10 | 11 | - add babel support 12 | - add transpiler fallback support 13 | - add support for some renamed transpilers (livescript, etc) 14 | - add JSCS 15 | - update dependecies (liftoff, interpret) 16 | - documentation tweaks 17 | 18 | ## 3.8.11 19 | 20 | - fix node 0.12/iojs problems 21 | - add node 0.12 and iojs to travis 22 | - update dependencies (liftoff, v8flags) 23 | - documentation tweaks 24 | 25 | ## 3.8.10 26 | 27 | - add link to spanish docs 28 | - update dependencies (archy, semver, mocha, etc) 29 | - documentation tweaks 30 | 31 | ## 3.8.9 32 | 33 | - fix local version undefined output 34 | - add completion for fish shell 35 | - fix powershell completion line splitting 36 | - add support for arbitrary node flags (oops, should have been a minor bump) 37 | - add v8flags dependency 38 | - update dependencies (liftoff) 39 | - documentation tweaks 40 | 41 | ## 3.8.8 42 | 43 | - update dependencies (minimist, tildify) 44 | - documentation tweaks 45 | 46 | ## 3.8.7 47 | 48 | - handle errors a bit better 49 | - update dependencies (gulp-util, semver, etc) 50 | - documentation tweaks 51 | 52 | ## 3.8.6 53 | 54 | - remove executable flag from LICENSE 55 | - update dependencies (chalk, minimist, liftoff, etc) 56 | - documentation tweaks 57 | 58 | ## 3.8.5 59 | 60 | - simplify --silent and --tasks-simple 61 | - fix bug in autocomplete where errors would come out 62 | 63 | ## 3.8.4 64 | 65 | - CLI will use exit code 1 on exit when any task fails during the lifetime of the process 66 | 67 | 68 | ## 3.8.3 69 | 70 | - Tweak error formatting to work better with PluginErrors and strings 71 | 72 | ## 3.8.2 73 | 74 | - add manpage generation 75 | 76 | ## 3.8.1 77 | 78 | - the CLI now adds process.env.INIT_CWD which is the original cwd it was launched from 79 | 80 | ## 3.8.0 81 | 82 | - update vinyl-fs 83 | - gulp.src is now a writable passthrough, this means you can use it to add files to your pipeline at any point 84 | - gulp.dest can now take a function to determine the folder 85 | 86 | This is now possible! 87 | 88 | ```js 89 | gulp.src('lib/*.js') 90 | .pipe(uglify()) 91 | .pipe(gulp.src('styles/*.css')) 92 | .pipe(gulp.dest(function(file){ 93 | // I don't know, you can do something cool here 94 | return 'build/whatever'; 95 | })); 96 | ``` 97 | 98 | ## 3.7.0 99 | 100 | - update vinyl-fs to remove BOM from UTF8 files 101 | - add --tasks-simple flag for plaintext task listings 102 | - updated autocomplete scripts to be simpler and use new --tasks-simple flag 103 | - added support for transpilers via liftoff 0.11 and interpret 104 | - just npm install your compiler (coffee-script for example) and it will work out of the box 105 | 106 | ## 3.5.5 107 | 108 | - update deps 109 | - gulp.dest now support mode option, uses source file mode by default (file.stat.mode) 110 | - use chalk for colors in bin 111 | - update gulp.env deprecation msg to be more helpful 112 | 113 | 114 | ## 3.5.2 115 | 116 | - add -V for version on CLI (unix standard) 117 | - -v is deprecated, use -V 118 | - add -T as an alias for --tasks 119 | - documentation 120 | 121 | ## 3.5 122 | 123 | - added `gulp.watch(globs, tasksArray)` sugar 124 | - remove gulp.taskQueue 125 | - deprecate gulp.run 126 | - deprecate gulp.env 127 | - add engineStrict to prevent people with node < 0.9 from installing 128 | 129 | ## 3.4 130 | 131 | - added `--tasks` that prints out the tree of tasks + deps 132 | - global cli + local install mismatch is no longer fatal 133 | - remove tests for fs stuff 134 | - switch core src, dest, and watch to vinyl-fs 135 | - internal cleaning 136 | 137 | ## 3.3.4 138 | 139 | - `--base` is now `--cwd` 140 | 141 | ## 3.3.3 142 | 143 | - support for `--base` CLI arg to change where the search for gulpfile/`--require`s starts 144 | - support for `--gulpfile` CLI arg to point to a gulpfile specifically 145 | 146 | ## 3.3.0 147 | 148 | - file.contents streams are no longer paused coming out of src 149 | - dest now passes files through before they are empty to fix passing to multiple dests 150 | 151 | ## 3.2.4 152 | 153 | - Bug fix - we didn't have any CLI tests 154 | 155 | ## 3.2.3 156 | 157 | - Update dependencies for bug fixes 158 | - autocomplete stuff in the completion folder 159 | 160 | ## 3.2 161 | 162 | - File object is now [vinyl](https://github.com/wearefractal/vinyl) 163 | - .watch() is now [glob-watcher](https://github.com/wearefractal/glob-watcher) 164 | - Fix CLI -v when no gulpfile found 165 | - gulp-util updated 166 | - Logging moved to CLI bin file 167 | - Will cause double logging if you update global CLI to 3.2 but not local 168 | - Will cause no logging if you update local to 3.1 but not global CLI 169 | - Drop support for < 0.9 170 | 171 | ## 3.1.3 172 | 173 | - Move isStream and isBuffer to gulp-util 174 | 175 | ## 3.1 176 | 177 | - Move file class to gulp-util 178 | 179 | ## 3.0 180 | 181 | - Ability to pass multiple globs and glob negations to glob-stream 182 | - Breaking change to the way glob-stream works 183 | - File object is now a class 184 | - file.shortened changed to file.relative 185 | - file.cwd added 186 | - Break out getStats to avoid nesting 187 | - Major code reorganization 188 | 189 | ## 2.7 190 | 191 | - Breaking change to the way options are passed to glob-stream 192 | - Introduce new File object to ease pain of computing shortened names (now a getter) 193 | 194 | ## 2.4 - 2.6 195 | 196 | - Moved stuff to gulp-util 197 | - Quit exposing createGlobStream (just use the glob-stream module) 198 | - More logging 199 | - Prettier time durations 200 | - Tons of documentation changes 201 | - gulp.trigger(tasks...) as a through stream 202 | 203 | ## 1.2-2.4 (11/12/13) 204 | 205 | - src buffer=false fixed for 0.8 and 0.9 (remember to .resume() on these versions before consuming) 206 | - CLI completely rewritten 207 | - Colorful logging 208 | - Uses local version of gulp to run tasks 209 | - Uses findup to locate gulpfile (so you can run it anywhere in your project) 210 | - chdir to gulpfile directory before loading it 211 | - Correct exit codes on errors 212 | - silent flag added to gulp to disable logging 213 | - Fixes to task orchestration (3rd party) 214 | - Better support for globbed directories (thanks @robrich) 215 | 216 | ## 1.2 (10/28/13) 217 | 218 | - Can specify buffer=false on src streams to make file.content a stream 219 | - Can specify read=false on src streams to disable file.content 220 | 221 | ## 1.1 (10/21/13) 222 | 223 | - Can specify run callback 224 | - Can specify task dependencies 225 | - Tasks can accept callback or return promise 226 | - `gulp.verbose` exposes run-time internals 227 | 228 | ## 1.0 (9/26/13) 229 | 230 | - Specify dependency versions 231 | - Updated docs 232 | 233 | ## 0.2 (8/6/13) 234 | 235 | - Rename .files() to .src() and .folder() to .dest() 236 | 237 | ## 0.1 (7/18/13) 238 | 239 | - Initial Release 240 | -------------------------------------------------------------------------------- /docs/writing-a-plugin/README.md: -------------------------------------------------------------------------------- 1 | # Writing a plugin 2 | 3 | If you plan to create your own Gulp plugin, you will save time by reading the full documentation. 4 | 5 | * [Guidelines](guidelines.md) (a MUST read) 6 | * [Using buffers](using-buffers.md) 7 | * [Dealing with streams](dealing-with-streams.md) 8 | * [Testing](testing.md) 9 | 10 | ## What it does 11 | 12 | ### Streaming file objects 13 | 14 | A gulp plugin always returns a stream in [object mode](http://nodejs.org/api/stream.html#stream_object_mode) that does the following: 15 | 16 | 1. Takes in [vinyl File objects](http://github.com/gulpjs/vinyl) 17 | 2. Outputs [vinyl File objects](http://github.com/gulpjs/vinyl) (via `transform.push()` and/or the plugin's callback function) 18 | 19 | These are known as [transform streams](http://nodejs.org/api/stream.html#stream_class_stream_transform_1) 20 | (also sometimes called through streams). 21 | Transform streams are streams that are readable and writable; they manipulate objects as they're being passed through. 22 | 23 | All gulp plugins essentially boil down to this: 24 | ```js 25 | var Transform = require('transform'); 26 | 27 | module.exports = function() { 28 | // Monkey patch Transform or create your own subclass, 29 | // implementing `_transform()` and optionally `_flush()` 30 | var transformStream = new Transform({objectMode: true}); 31 | /** 32 | * @param {Buffer|string} file 33 | * @param {string=} encoding - ignored if file contains a Buffer 34 | * @param {function(Error, object)} callback - Call this function (optionally with an 35 | * error argument and data) when you are done processing the supplied chunk. 36 | */ 37 | transformStream._transform = function(file, encoding, callback) { 38 | var error = null, 39 | output = doSomethingWithTheFile(file); 40 | callback(error, output); 41 | }; 42 | 43 | return transformStream; 44 | }; 45 | ``` 46 | 47 | Many plugins use the [through2](https://github.com/rvagg/through2/) module to simplify their code: 48 | 49 | ```js 50 | var through = require('through2'); // npm install --save through2 51 | 52 | module.exports = function() { 53 | return through.obj(function(file, encoding, callback) { 54 | callback(null, doSomethingWithTheFile(file)); 55 | }); 56 | }; 57 | ``` 58 | 59 | The stream returned from `through()` (and `this` within your transform function) is an instance of the [Transform](https://github.com/iojs/readable-stream/blob/master/lib/_stream_transform.js) 60 | class, which extends [Duplex](https://github.com/iojs/readable-stream/blob/master/lib/_stream_duplex.js), 61 | [Readable](https://github.com/iojs/readable-stream/blob/master/lib/_stream_readable.js) 62 | (and parasitically from Writable) and ultimately [Stream](https://nodejs.org/api/stream.html). 63 | If you need to parse additional options, you can call the `through()` function directly: 64 | 65 | ```js 66 | return through({objectMode: true /* other options... */}, function(file, encoding, callback) { ... 67 | ``` 68 | 69 | Supported options include: 70 | 71 | * highWaterMark (defaults to 16) 72 | * defaultEncoding (defaults to 'utf8') 73 | * encoding - 'utf8', 'base64', 'utf16le', 'ucs2' etc. 74 | If specified, a [StringDecoder](https://github.com/rvagg/string_decoder/blob/master/index.js) `decoder` will be attached to the stream. 75 | * readable {boolean} 76 | * writable {boolean} 77 | * allowHalfOpen {boolean} If set to false, then the stream will automatically end the readable side when the writable side ends and vice versa. 78 | 79 | ### Modifying file content 80 | 81 | The function parameter that you pass to `through.obj()` is a [_transform](https://nodejs.org/api/stream.html#stream_transform_transform_chunk_encoding_callback) 82 | function which will operate on the input `file`. You may also provide an optional [_flush](https://nodejs.org/api/stream.html#stream_transform_flush_callback) 83 | function if you need to emit a bit more data at the end of the stream. 84 | 85 | From within your transform function call `this.push(file)` 0 or more times to pass along transformed/cloned files. 86 | You don't need to call `this.push(file)` if you provide all output to the `callback()` function. 87 | 88 | Call the `callback` function only when the current file (stream/buffer) is completely consumed. 89 | If an error is encountered, pass it as the first argument to the callback, otherwise set it to null. 90 | If you have passed all output data to `this.push()` you can omit the second argument to the callback. 91 | 92 | Generally, a gulp plugin would update `file.contents` and then choose to either: 93 | 94 | - call `callback(null, file)` 95 | _or_ 96 | - make one call to `this.push(file)` 97 | 98 | If a plugin creates multiple files from a single input file, it would make multiple calls to `this.push()` - eg: 99 | 100 | ```js 101 | module.exports = function() { 102 | /** 103 | * @this {Transform} 104 | */ 105 | var transform = function(file, encoding, callback) { 106 | var files = splitFile(file); 107 | this.push(files[0]); 108 | this.push(files[1]); 109 | callback(); 110 | }; 111 | 112 | return through.obj(transform); 113 | }; 114 | ``` 115 | 116 | The [gulp-unzip](https://github.com/suisho/gulp-unzip/blob/master/index.js) plugin provides a good example of making 117 | multiple calls to `push()`. It also uses a chunk transform stream with a `_flush()` function _within_ the Vinyl transform function. 118 | 119 | Vinyl files can have 3 possible forms for the contents attribute: 120 | 121 | - [Streams](dealing-with-streams.md) 122 | - [Buffers](using-buffers.md) 123 | - Empty (null) - Useful for things like rimraf, clean, where contents is not needed. 124 | 125 | A simple example showing how to detect & handle each form is provided below, for a more detailed explanation of each 126 | approach follow the links above. 127 | 128 | ```js 129 | var PluginError = require('gulp-util').PluginError; 130 | 131 | // consts 132 | var PLUGIN_NAME = 'gulp-example'; 133 | 134 | module.exports = function() { 135 | return through.obj(function(file, encoding, callback) { 136 | if (file.isNull()) { 137 | // nothing to do 138 | return callback(null, file); 139 | } 140 | 141 | if (file.isStream()) { 142 | // file.contents is a Stream - https://nodejs.org/api/stream.html 143 | this.emit('error', new PluginError(PLUGIN_NAME, 'Streams not supported!')); 144 | 145 | // or, if you can handle Streams: 146 | //file.contents = file.contents.pipe(... 147 | //return callback(null, file); 148 | } else if (file.isBuffer()) { 149 | // file.contents is a Buffer - https://nodejs.org/api/buffer.html 150 | this.emit('error', new PluginError(PLUGIN_NAME, 'Buffers not supported!')); 151 | 152 | // or, if you can handle Buffers: 153 | //file.contents = ... 154 | //return callback(null, file); 155 | } 156 | }); 157 | }; 158 | ``` 159 | 160 | Note: When looking through the code of other gulp plugins (and the example above), you may notice that the transform functions will return the result of the callback: 161 | 162 | ```js 163 | return callback(null, file); 164 | ``` 165 | 166 | ...don't be confused - gulp ignores any return value of your transform function. The code above is simply a short-hand form of: 167 | 168 | ```js 169 | if (someCondition) { 170 | callback(null, file); 171 | return; 172 | } 173 | // further execution... 174 | ``` 175 | 176 | 177 | ## Useful resources 178 | 179 | * [File object](https://github.com/gulpjs/gulp-util/#new-fileobj) 180 | * [PluginError](https://github.com/gulpjs/gulp-util#new-pluginerrorpluginname-message-options) 181 | * [event-stream](https://github.com/dominictarr/event-stream) 182 | * [BufferStream](https://github.com/nfroidure/BufferStream) 183 | * [gulp-util](https://github.com/gulpjs/gulp-util) 184 | 185 | 186 | ## Sample plugins 187 | 188 | * [sindresorhus' gulp plugins](https://github.com/search?q=%40sindresorhus+gulp-) 189 | * [contra's gulp plugins](https://github.com/search?q=%40contra+gulp-) 190 | * [gulp-replace](https://github.com/lazd/gulp-replace) 191 | 192 | 193 | ## About streams 194 | 195 | If you're unfamiliar with streams, you will need to read up on them: 196 | 197 | * https://github.com/substack/stream-handbook (a MUST read) 198 | * http://nodejs.org/api/stream.html 199 | 200 | Other libraries that are not file manipulating through streams but are made for use with gulp are tagged with the [gulpfriendly](https://npmjs.org/browse/keyword/gulpfriendly) keyword on npm. 201 | -------------------------------------------------------------------------------- /docs/API.md: -------------------------------------------------------------------------------- 1 | ## gulp API docs 2 | 3 | Jump to: 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 files matching provided glob or an array of globs. 12 | Returns a [stream](http://nodejs.org/api/stream.html) of [Vinyl files](https://github.com/gulpjs/vinyl-fs) 13 | that can be [piped](http://nodejs.org/api/stream.html#stream_readable_pipe_destination_options) 14 | to plugins. 15 | 16 | ```javascript 17 | gulp.src('client/templates/*.jade') 18 | .pipe(jade()) 19 | .pipe(minify()) 20 | .pipe(gulp.dest('build/minified_templates')); 21 | ``` 22 | 23 | #### globs 24 | Type: `String` or `Array` 25 | 26 | Glob or array of globs to read. Globs use [node-glob syntax] except that negation is fully supported. 27 | 28 | A glob that begins with `!` excludes matching files from the glob results up to that point. For example, consider this directory structure: 29 | 30 | client/ 31 | a.js 32 | bob.js 33 | bad.js 34 | 35 | The following expression matches `a.js` and `bad.js`: 36 | 37 | gulp.src(['client/*.js', '!client/b*.js', 'client/bad.js']) 38 | 39 | 40 | #### options 41 | Type: `Object` 42 | 43 | Options to pass to [node-glob] through [glob-stream]. 44 | 45 | gulp supports all [options supported by node-glob][node-glob documentation] and [glob-stream] except `ignore` and adds the following options. 46 | 47 | ##### options.buffer 48 | Type: `Boolean` 49 | Default: `true` 50 | 51 | Setting this to `false` will return `file.contents` as a stream and not buffer files. This is useful when working with large files. **Note:** Plugins might not implement support for streams. 52 | 53 | ##### options.read 54 | Type: `Boolean` 55 | Default: `true` 56 | 57 | Setting this to `false` will return `file.contents` as null and not read the file at all. 58 | 59 | ##### options.base 60 | Type: `String` 61 | Default: everything before a glob starts (see [glob2base]) 62 | 63 | E.g., consider `somefile.js` in `client/js/somedir`: 64 | 65 | ```js 66 | gulp.src('client/js/**/*.js') // Matches 'client/js/somedir/somefile.js' and resolves `base` to `client/js/` 67 | .pipe(minify()) 68 | .pipe(gulp.dest('build')); // Writes 'build/somedir/somefile.js' 69 | 70 | gulp.src('client/js/**/*.js', { base: 'client' }) 71 | .pipe(minify()) 72 | .pipe(gulp.dest('build')); // Writes 'build/js/somedir/somefile.js' 73 | ``` 74 | 75 | ### gulp.dest(path[, options]) 76 | 77 | Can be piped to and it will write files. Re-emits all data passed to it so you can pipe to multiple folders. Folders that don't exist will be created. 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 | The write path is calculated by appending the file relative path to the given 88 | destination directory. In turn, relative paths are calculated against the file base. 89 | See `gulp.src` above for more info. 90 | 91 | #### path 92 | Type: `String` or `Function` 93 | 94 | The path (output folder) to write files to. Or a function that returns it, the function will be provided a [vinyl File instance](https://github.com/gulpjs/vinyl). 95 | 96 | #### options 97 | Type: `Object` 98 | 99 | ##### options.cwd 100 | Type: `String` 101 | Default: `process.cwd()` 102 | 103 | `cwd` for the output folder, only has an effect if provided output folder is relative. 104 | 105 | ##### options.mode 106 | Type: `String` 107 | Default: `0777` 108 | 109 | Octal permission string specifying mode for any folders that need to be created for output folder. 110 | 111 | ### gulp.task(name [, deps] [, fn]) 112 | 113 | Define a task using [Orchestrator]. 114 | 115 | ```js 116 | gulp.task('somename', function() { 117 | // Do stuff 118 | }); 119 | ``` 120 | 121 | #### name 122 | Type: `String` 123 | 124 | The name of the task. Tasks that you want to run from the command line should not have spaces in them. 125 | 126 | #### deps 127 | Type: `Array` 128 | 129 | An array of tasks to be executed and completed before your task will run. 130 | 131 | ```js 132 | gulp.task('mytask', ['array', 'of', 'task', 'names'], function() { 133 | // Do stuff 134 | }); 135 | ``` 136 | 137 | **Note:** Are your tasks running before the dependencies are complete? Make sure your dependency tasks are correctly using the async run hints: take in a callback or return a promise or event stream. 138 | 139 | You can also omit the function if you only want to run a bundle of dependency tasks: 140 | 141 | ```js 142 | gulp.task('build', ['array', 'of', 'task', 'names']); 143 | ``` 144 | 145 | **Note:** The tasks will run in parallel (all at once), so don't assume that the tasks will start/finish in order. 146 | 147 | #### fn 148 | Type: `Function` 149 | 150 | The function performs the task's main operations. Generally this takes the form of: 151 | 152 | ```js 153 | gulp.task('buildStuff', function() { 154 | // Do something that "builds stuff" 155 | var stream = gulp.src(/*some source path*/) 156 | .pipe(somePlugin()) 157 | .pipe(someOtherPlugin()) 158 | .pipe(gulp.dest(/*some destination*/)); 159 | 160 | return stream; 161 | }); 162 | ``` 163 | 164 | #### Async task support 165 | 166 | Tasks can be made asynchronous if its `fn` does one of the following: 167 | 168 | ##### Accept a callback 169 | 170 | ```javascript 171 | // run a command in a shell 172 | var exec = require('child_process').exec; 173 | gulp.task('jekyll', function(cb) { 174 | // build Jekyll 175 | exec('jekyll build', function(err) { 176 | if (err) return cb(err); // return error 177 | cb(); // finished task 178 | }); 179 | }); 180 | 181 | // use an async result in a pipe 182 | gulp.task('somename', function(cb) { 183 | getFilesAsync(function(err, res) { 184 | if (err) return cb(err); 185 | var stream = gulp.src(res) 186 | .pipe(minify()) 187 | .pipe(gulp.dest('build')) 188 | .on('end', cb); 189 | }); 190 | }); 191 | ``` 192 | 193 | ##### Return a stream 194 | 195 | ```js 196 | gulp.task('somename', function() { 197 | var stream = gulp.src('client/**/*.js') 198 | .pipe(minify()) 199 | .pipe(gulp.dest('build')); 200 | return stream; 201 | }); 202 | ``` 203 | 204 | ##### Return a promise 205 | 206 | ```javascript 207 | var Q = require('q'); 208 | 209 | gulp.task('somename', function() { 210 | var deferred = Q.defer(); 211 | 212 | // do async stuff 213 | setTimeout(function() { 214 | deferred.resolve(); 215 | }, 1); 216 | 217 | return deferred.promise; 218 | }); 219 | ``` 220 | 221 | **Note:** By default, tasks run with maximum concurrency -- e.g. it launches all the tasks at once and waits for nothing. If you want to create a series where tasks run in a particular order, you need to do two things: 222 | 223 | - give it a hint to tell it when the task is done, 224 | - and give it a hint that a task depends on completion of another. 225 | 226 | For these examples, let's presume you have two tasks, "one" and "two" that you specifically want to run in this order: 227 | 228 | 1. In task "one" you add a hint to tell it when the task is done. Either take in a callback and call it when you're 229 | done or return a promise or stream that the engine should wait to resolve or end respectively. 230 | 231 | 2. In task "two" you add a hint telling the engine that it depends on completion of the first task. 232 | 233 | So this example would look like this: 234 | 235 | ```js 236 | var gulp = require('gulp'); 237 | 238 | // takes in a callback so the engine knows when it'll be done 239 | gulp.task('one', function(cb) { 240 | // do stuff -- async or otherwise 241 | cb(err); // if err is not null and not undefined, the run will stop, and note that it failed 242 | }); 243 | 244 | // identifies a dependent task must be complete before this one begins 245 | gulp.task('two', ['one'], function() { 246 | // task 'one' is done now 247 | }); 248 | 249 | gulp.task('default', ['one', 'two']); 250 | ``` 251 | 252 | 253 | ### gulp.watch(glob [, opts], tasks) or gulp.watch(glob [, opts, cb]) 254 | 255 | Watch files and do something when a file changes. This always returns an EventEmitter that emits `change` events. 256 | 257 | ### gulp.watch(glob[, opts], tasks) 258 | 259 | #### glob 260 | Type: `String` or `Array` 261 | 262 | A single glob or array of globs that indicate which files to watch for changes. 263 | 264 | #### opts 265 | Type: `Object` 266 | 267 | Options, that are passed to [`gaze`](https://github.com/shama/gaze). 268 | 269 | #### tasks 270 | Type: `Array` 271 | 272 | Names of task(s) to run when a file changes, added with `gulp.task()` 273 | 274 | ```js 275 | var watcher = gulp.watch('js/**/*.js', ['uglify','reload']); 276 | watcher.on('change', function(event) { 277 | console.log('File ' + event.path + ' was ' + event.type + ', running tasks...'); 278 | }); 279 | ``` 280 | 281 | ### gulp.watch(glob[, opts, cb]) 282 | 283 | #### glob 284 | Type: `String` or `Array` 285 | 286 | A single glob or array of globs that indicate which files to watch for changes. 287 | 288 | #### opts 289 | Type: `Object` 290 | 291 | Options, that are passed to [`gaze`](https://github.com/shama/gaze). 292 | 293 | #### cb(event) 294 | Type: `Function` 295 | 296 | Callback to be called on each change. 297 | 298 | ```js 299 | gulp.watch('js/**/*.js', function(event) { 300 | console.log('File ' + event.path + ' was ' + event.type + ', running tasks...'); 301 | }); 302 | ``` 303 | 304 | The callback will be passed an object, `event`, that describes the change: 305 | 306 | ##### event.type 307 | Type: `String` 308 | 309 | The type of change that occurred, either `added`, `changed`, `deleted` or `renamed`. 310 | 311 | ##### event.path 312 | Type: `String` 313 | 314 | The path to the file that triggered the event. 315 | 316 | 317 | [node-glob]: https://github.com/isaacs/node-glob 318 | [node-glob documentation]: https://github.com/isaacs/node-glob#options 319 | [node-glob syntax]: https://github.com/isaacs/node-glob 320 | [glob-stream]: https://github.com/gulpjs/glob-stream 321 | [gulp-if]: https://github.com/robrich/gulp-if 322 | [Orchestrator]: https://github.com/robrich/orchestrator 323 | [glob2base]: https://github.com/wearefractal/glob2base 324 | --------------------------------------------------------------------------------