diff --git a/.editorconfig b/.editorconfig
index 5d126348..e7b73a7a 100644
--- a/.editorconfig
+++ b/.editorconfig
@@ -1,13 +1,13 @@
-# editorconfig.org
+# https://editorconfig.org
root = true
[*]
indent_style = space
indent_size = 2
-end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
+end_of_line = lf
[*.md]
trim_trailing_whitespace = false
diff --git a/.gitattributes b/.gitattributes
new file mode 100644
index 00000000..1de36465
--- /dev/null
+++ b/.gitattributes
@@ -0,0 +1,5 @@
+* text eol=lf
+
+# Denote all files that are truly binary and should not be modified.
+*.png binary
+*.jpg binary
diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md
deleted file mode 100644
index 1ec2c2c7..00000000
--- a/.github/ISSUE_TEMPLATE.md
+++ /dev/null
@@ -1,28 +0,0 @@
-This tracker is for bug reports only.
-
-
-Before opening an issue, please make sure you've checked the following:
-
-- For support requests, please use Stack Overflow (stackoverflow.com) or Gitter (see the README).
-
-- If the bug is in a plugin, open an issue on the plugin repository, not the gulp repository.
-
-- If you're getting a deprecated module warning, don't worry about it: we're aware of it and it's not an issue. To make it go away, update to Gulp 4.0.
-
-- If you're asking about the status of Gulp 4, please don't! You can see the remaining issues on the gulp4 label: https://github.com/gulpjs/gulp/issues?q=is%3Aissue+is%3Aopen+label%3Agulp4
-
-----
-
-**What were you expecting to happen?**
-
-**What actually happened?**
-
-**Please post a sample of your gulpfile (preferably reduced to just the bit that's not working)**
-
-```js
-gulp.task(function () {});
-```
-
-**What version of gulp are you using?**
-
-**What versions of npm and node are you using?**
diff --git a/.github/SECURITY.md b/.github/SECURITY.md
new file mode 100644
index 00000000..1a362ffa
--- /dev/null
+++ b/.github/SECURITY.md
@@ -0,0 +1,14 @@
+# Security Policy
+
+## Supported Versions
+
+| Version | Supported |
+| ------- | ------------------ |
+| 4.x.x | :white_check_mark: |
+| < 4.0 | :x: |
+
+## Reporting a Vulnerability
+
+To report a security vulnerability, please use the
+[Tidelift security contact](https://tidelift.com/security).
+Tidelift will coordinate the fix and disclosure.
diff --git a/.github/workflows/dev.yml b/.github/workflows/dev.yml
new file mode 100644
index 00000000..4dfe1927
--- /dev/null
+++ b/.github/workflows/dev.yml
@@ -0,0 +1,75 @@
+name: dev
+on:
+ pull_request:
+ push:
+ branches:
+ - master
+ - main
+env:
+ CI: true
+
+jobs:
+ prettier:
+ name: Format code
+ runs-on: ubuntu-latest
+ if: ${{ github.event_name == 'push' }}
+
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v2
+
+ - name: Prettier
+ uses: gulpjs/prettier_action@v3.0
+ with:
+ commit_message: 'chore: Run prettier'
+ prettier_options: '--write .'
+
+ test:
+ name: Tests for Node ${{ matrix.node }} on ${{ matrix.os }}
+ runs-on: ${{ matrix.os }}
+
+ strategy:
+ fail-fast: false
+ matrix:
+ node: [10, 12, 14, 16, 18, 20, 22, 24]
+ os: [ubuntu-latest, windows-latest, macos-13]
+
+ steps:
+ - name: Clone repository
+ uses: actions/checkout@v2
+
+ - name: Set Node.js version
+ uses: actions/setup-node@v2
+ with:
+ node-version: ${{ matrix.node }}
+
+ - run: node --version
+ - run: npm --version
+
+ - name: Install npm dependencies
+ run: npm install
+
+ - name: Run lint
+ run: npm run lint
+
+ - name: Run tests
+ run: npm test
+
+ - name: Coveralls
+ uses: coverallsapp/github-action@v1.1.2
+ with:
+ github-token: ${{ secrets.GITHUB_TOKEN }}
+ flag-name: ${{matrix.os}}-node-${{ matrix.node }}
+ parallel: true
+
+ coveralls:
+ needs: test
+ name: Finish up
+
+ runs-on: ubuntu-latest
+ steps:
+ - name: Coveralls Finished
+ uses: coverallsapp/github-action@v1.1.2
+ with:
+ github-token: ${{ secrets.GITHUB_TOKEN }}
+ parallel-finished: true
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
new file mode 100644
index 00000000..87cd13c0
--- /dev/null
+++ b/.github/workflows/release.yml
@@ -0,0 +1,16 @@
+name: release
+on:
+ push:
+ branches:
+ - master
+ - main
+
+jobs:
+ release-please:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: GoogleCloudPlatform/release-please-action@v2
+ with:
+ token: ${{ secrets.GITHUB_TOKEN }}
+ release-type: node
+ package-name: release-please-action
diff --git a/.gitignore b/.gitignore
index cef23f00..466085e1 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,13 +1,35 @@
-.DS_Store
+# Logs
+logs
*.log
-node_modules
-build
-*.node
-components
+
+# Runtime data
+pids
+*.pid
+*.seed
+
+# Directory for instrumented libs generated by jscoverage/JSCover
+lib-cov
+
+# Coverage directory used by tools like istanbul
coverage
-*.orig
-.idea
-sandbox
-test/out-fixtures/*
-test/watch-*.txt
-gulp.1
+.nyc_output
+
+# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
+.grunt
+
+# Compiled binary addons (https://nodejs.org/api/addons.html)
+build/Release
+
+# Dependency directory
+# Commenting this out is preferred by some people, see
+# https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git-
+node_modules
+
+# Users Environment Variables
+.lock-wscript
+
+# Garbage files
+.DS_Store
+
+# Test results
+test.xunit
diff --git a/.jscsrc b/.jscsrc
deleted file mode 100644
index d9e4b9a1..00000000
--- a/.jscsrc
+++ /dev/null
@@ -1,3 +0,0 @@
-{
- "preset": "gulp",
-}
diff --git a/.npmignore b/.npmignore
deleted file mode 100644
index f93c3951..00000000
--- a/.npmignore
+++ /dev/null
@@ -1,12 +0,0 @@
-.DS_Store
-*.log
-node_modules
-build
-*.node
-components
-coverage
-*.orig
-.idea
-sandbox
-test/out-fixtures/*
-test/watch-*.txt
diff --git a/.npmrc b/.npmrc
new file mode 100644
index 00000000..43c97e71
--- /dev/null
+++ b/.npmrc
@@ -0,0 +1 @@
+package-lock=false
diff --git a/.prettierignore b/.prettierignore
new file mode 100644
index 00000000..c96ebe0c
--- /dev/null
+++ b/.prettierignore
@@ -0,0 +1,3 @@
+coverage/
+.nyc_output/
+CHANGELOG.md
diff --git a/.tidelift.yml b/.tidelift.yml
new file mode 100644
index 00000000..3540e8ba
--- /dev/null
+++ b/.tidelift.yml
@@ -0,0 +1,15 @@
+ci:
+ platform:
+ NPM:
+ # We use an older version that doesn't use ES6+ features to support back to node 0.10
+ eslint:
+ tests:
+ outdated: skip
+ # We use an older version that doesn't use ES6+ features to support back to node 0.10
+ expect:
+ tests:
+ outdated: skip
+ # We use an older version that doesn't use ES6+ features to support back to node 0.10
+ mocha:
+ tests:
+ outdated: skip
diff --git a/.travis.yml b/.travis.yml
deleted file mode 100644
index 8cf5aded..00000000
--- a/.travis.yml
+++ /dev/null
@@ -1,14 +0,0 @@
-sudo: false
-language: node_js
-node_js:
- - "0.10"
- - "0.12"
- - "4"
- - "5"
- - "6"
- - "7"
- - "8"
-after_script:
- - npm run coveralls
-git:
- depth: 10
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 74e9767d..8dcf8c08 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,147 @@
# gulp changelog
+### [5.0.1](https://www.github.com/gulpjs/gulp/compare/v5.0.0...v5.0.1) (2025-06-01)
+
+
+### Bug Fixes
+
+* Avoid globbing before read stream is opened ([#2839](https://www.github.com/gulpjs/gulp/issues/2839)) ([19122f3](https://www.github.com/gulpjs/gulp/commit/19122f3d9eefccaadcf0e96313a7d3b83348348b))
+* Avoid Node.js deprecation warning for `fs.Stats` ([#2838](https://www.github.com/gulpjs/gulp/issues/2838)) ([69a5d0e](https://www.github.com/gulpjs/gulp/commit/69a5d0e904278dde61c835a0b198d7d1c5a15b95))
+* Support top-level await on Node 22.12+ ([#2836](https://www.github.com/gulpjs/gulp/issues/2836)) ([04b4a74](https://www.github.com/gulpjs/gulp/commit/04b4a74aec63302f603f0cca3826f75b1bda64ad))
+
+## [5.0.0](https://www.github.com/gulpjs/gulp/compare/v4.0.2...v5.0.0) (2024-03-29)
+
+We've tried to provide a high-level changelog for gulp v5 below, but it
+doesn't contain all changes from the 60+ dependencies that we maintain.
+
+Please see [individual changelogs](#individual-changelogs) to drill down
+into all changes that were made.
+
+### ⚠ BREAKING CHANGES
+
+* Drop support for Node.js <10.13
+* Default stream encoding to UTF-8
+* Standardized on `anymatch` library for globbing paths. All globs should work the same between `src` and `watch` now!
+* Removed support for ordered globs. This aligns with the chokidar globbing implementation. If you need your globs to be ordered, you can use `ordered-read-stream`
+* All globs and paths are normalized to unix-like filepaths
+* Only allow JS variants for `.gulp.*` config files
+* Removed support for alpha releases of v4 from `gulp-cli`
+* Removed the `--verify` flag
+* Renamed the `--require` flag to `--preload` to avoid conflicting with Node.js flags
+* Removed many legacy and deprecated loaders
+* Upgrade to chokidar v3
+* Clone `Vinyl` objects with stream contents using `teex`, but no longer wait for all streams to flow before cloned streams will receive data
+* Stop using `process.umask()` to make directories, instead falling back to Node's default mode
+* Throw on non-function, non-string option coercers
+* Drop support of Node.js snake_case flags
+* Use a Symbol for attaching the `gulplog` namespace to the store
+* Use a Symbol for attaching the `gulplog` store to the global
+* Use sha256 to hash the `v8flags` cache into a filename
+
+### Features
+
+* Streamlined the dependency tree
+* Switch all streams implementation to Streamx
+* Rewrote `glob-stream` to use a custom directory walk that relies on newer Node.js features and is more performant than old implementation
+* Implement translation support for all CLI messages and all messages passing through gulplog
+* Allow users to customize or remove the timestamp from their logs
+* Upgraded gulplog to v2. Messages logged via v1 will also display a deprecated warning. Plugins should update to v2 as the community upgrades to gulp 5
+* Added support for `gulpile.cjs` and `gulpfile.mjs`
+* Add support for `swc`, `esbuild`, `sucrase`, and `mdx` loaders
+* Provide an ESM export ([#2760](https://www.github.com/gulpjs/gulp/issues/2760)) ([b00de68](https://www.github.com/gulpjs/gulp/commit/b00de681f5ef6ade283d544f62f770f6b27a9e52))
+* Support sourcemap handling on streaming `Vinyl` contents
+* Support `extends` syntax for `.gulp.*` config file
+* Allow overriding `gulpfile` and `preloads` via `.gulp.*` config file
+
+### Bug Fixes
+
+* Resolve bugs related to symlinks on various platforms
+* Resolved some reported ReDoS CVEs and improved performance in glob-parent
+* Rework errors surfaced when encountering files or symlinks when trying to create directories
+* Ensure watch allows japanese characters in globs ([72668c6](https://www.github.com/gulpjs/gulp/commit/72668c61e445c81fad23bc6ed24967a3238a648d))
+* Ensure watch does not trigger on negated globs ([72668c6](https://www.github.com/gulpjs/gulp/commit/72668c61e445c81fad23bc6ed24967a3238a648d))
+* Improve handling of BOM at the beginning of a stream
+* Properly handle function coercer in array of option coercers
+* Fork `to-absolute-glob` to:
+ - Check negative patterns before trimming
+ - Ensure glob-like characters are escaped in cwd & root options
+ - Resolve `../` at the beginning of globs
+
+### Miscellaneous Chores
+
+* Remove lazystream dependency
+* Updated various stream test suites to test against Node.js core `stream`, `readable-stream`, and `streamx`
+* Normalize repository, dropping node <10.13 support ([#2758](https://www.github.com/gulpjs/gulp/issues/2758)) ([72668c6](https://www.github.com/gulpjs/gulp/commit/72668c61e445c81fad23bc6ed24967a3238a648d))
+
+### Individual Changelogs
+
+We created and maintain various projects that gulp depends upon. You can find their changelogs linked below:
+
+* [undertaker](https://github.com/gulpjs/undertaker/blob/master/CHANGELOG.md#200-2024-03-22)
+* [vinyl-fs](https://github.com/gulpjs/vinyl-fs/blob/master/CHANGELOG.md#400-2023-06-11)
+* [glob-stream](https://github.com/gulpjs/glob-stream/blob/master/CHANGELOG.md#801-2024-03-25)
+* [gulp-cli](https://github.com/gulpjs/gulp-cli/blob/master/CHANGELOG.md#300-2024-03-24)
+* [interpret](https://github.com/gulpjs/interpret/blob/master/CHANGELOG.md#311-2022-06-29)
+* [glob-parent](https://github.com/gulpjs/glob-parent/blob/main/CHANGELOG.md#602-2021-09-29)
+* [glob-watcher](https://github.com/gulpjs/glob-watcher/blob/master/CHANGELOG.md#600-2023-05-31)
+* [vinyl](https://github.com/gulpjs/vinyl/blob/master/CHANGELOG.md#300-2022-09-26)
+* [fs-mkdirp-stream](https://github.com/gulpjs/fs-mkdirp-stream/blob/master/CHANGELOG.md#201-2022-09-17)
+* [lead](https://github.com/gulpjs/lead/blob/master/CHANGELOG.md#400-2022-09-22)
+* [vinyl-sourcemap](https://github.com/gulpjs/vinyl-sourcemap/blob/master/CHANGELOG.md#200-2022-10-17)
+* [to-through](https://github.com/gulpjs/to-through/blob/master/CHANGELOG.md#300-2022-09-07)
+* [resolve-options](https://github.com/gulpjs/resolve-options/blob/master/CHANGELOG.md#200-2022-06-24)
+* [remove-bom-stream](https://github.com/gulpjs/remove-bom-stream/blob/master/CHANGELOG.md#200-2022-04-19)
+* [value-or-function](https://github.com/gulpjs/value-or-function/blob/master/CHANGELOG.md#400-2022-01-30)
+* [now-and-later](https://github.com/gulpjs/now-and-later/blob/master/CHANGELOG.md#300-2022-06-25)
+* [@gulpjs/to-absolute-glob](https://github.com/gulpjs/to-absolute-glob/blob/master/CHANGELOG.md#400-2023-01-03)
+* [fined](https://github.com/gulpjs/fined/blob/master/CHANGELOG.md#200-2021-10-31)
+* [mute-stdout](https://github.com/gulpjs/mute-stdout/blob/master/CHANGELOG.md#200-2021-11-22)
+* [semver-greatest-satisfied-range](https://github.com/gulpjs/semver-greatest-satisfied-range/blob/master/CHANGELOG.md#200-2022-01-31)
+* [flagged-respawn](https://github.com/gulpjs/flagged-respawn/blob/master/CHANGELOG.md#200-2021-11-21)
+* [rechoir](https://github.com/gulpjs/rechoir/blob/master/CHANGELOG.md#080-2021-07-24)
+* [gulplog](https://github.com/gulpjs/gulplog/blob/master/CHANGELOG.md#220-2024-03-23)
+* [glogg](https://github.com/gulpjs/glogg/blob/master/CHANGELOG.md#220-2024-03-23)
+* [@gulpjs/messages](https://github.com/gulpjs/messages/blob/master/CHANGELOG.md#110-2024-03-24)
+* [sparkles](https://github.com/gulpjs/sparkles/blob/master/CHANGELOG.md#210-2024-03-23)
+* [liftoff](https://github.com/gulpjs/liftoff/blob/main/CHANGELOG.md#500-2024-03-16)
+* [v8flags](https://github.com/gulpjs/v8flags/blob/master/CHANGELOG.md#401-2023-09-03)
+* [bach](https://github.com/gulpjs/bach/blob/master/CHANGELOG.md#201-2022-08-29)
+* [undertaker-registry](https://github.com/gulpjs/undertaker-registry/blob/master/CHANGELOG.md#200-2021-12-29)
+* [async-settle](https://github.com/gulpjs/async-settle/blob/master/CHANGELOG.md#200-2022-06-25)
+* [last-run](https://github.com/gulpjs/last-run/blob/master/CHANGELOG.md#200-2022-01-10)
+* [async-done](https://github.com/gulpjs/async-done/blob/master/CHANGELOG.md#200-2022-06-25)
+* [replace-homedir](https://github.com/gulpjs/replace-homedir/blob/master/CHANGELOG.md#200-2022-01-31)
+
+## 4.0.0
+
+### Task system changes
+
+- replaced 3.x task system (orchestrator) with new task system (bach)
+ - removed gulp.reset
+ - removed 3 argument syntax for `gulp.task`
+ - `gulp.task` should only be used when you will call the task with the CLI
+ - added `gulp.series` and `gulp.parallel` methods for composing tasks. Everything must use these now.
+ - added single argument syntax for `gulp.task` which allows a named function to be used as the name of the task and task function.
+ - added `gulp.tree` method for retrieving the task tree. Pass `{ deep: true }` for an `archy` compatible node list.
+ - added `gulp.registry` for setting custom registries.
+
+### CLI changes
+
+- split CLI out into a module if you want to save bandwidth/disk space. you can install the gulp CLI using either `npm install gulp -g` or `npm install gulp-cli -g`, where gulp-cli is the smaller one (no module code included)
+- add `--tasks-json` flag to CLI to dump the whole tree out for other tools to consume
+- added `--verify` flag to check the dependencies in package.json against the plugin blacklist.
+
+### vinyl/vinyl-fs changes
+
+- added `gulp.symlink` which functions exactly like `gulp.dest`, but symlinks instead.
+- added `dirMode` param to `gulp.dest` and `gulp.symlink` which allows better control over the mode of the destination folder that is created.
+- globs passed to `gulp.src` will be evaluated in order, which means this is possible `gulp.src(['*.js', '!b*.js', 'bad.js'])` (exclude every JS file that starts with a b except bad.js)
+- performance for gulp.src has improved massively
+ - `gulp.src(['**/*', '!b.js'])` will no longer eat CPU since negations happen during walking now
+- added `since` option to `gulp.src` which lets you only match files that have been modified since a certain date (for incremental builds)
+- fixed `gulp.src` not following symlinks
+- added `overwrite` option to `gulp.dest` which allows you to enable or disable overwriting of existing files
+
## 3.9.1
- update interpret to 1.0.0 (support for babel-register)
@@ -10,9 +152,9 @@
- add babel support
- add transpiler fallback support
-- add support for some renamed transpilers (livescript, etc)
+- add support for some renamed transpilers: livescript, etc
- add JSCS
-- update dependecies (liftoff, interpret)
+- update dependencies (liftoff, interpret)
- documentation tweaks
## 3.8.11
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 7d8f4d5c..3eff64bb 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -28,6 +28,53 @@ of the content
- formatting: help keep content easy to read with consistent formatting
- code: Fix issues or contribute new features to this or any related projects
+# Project structure
+
+Gulp itself is tiny: index.js contains [very few lines of code](https://github.com/gulpjs/gulp/blob/master/index.js).
+It is powered by a few other libraries which each handle a few specific tasks
+each.
+
+You can view all issues with the "help wanted" label across all gulp projects
+here: https://github.com/issues?utf8=%E2%9C%93&q=is%3Aopen+is%3Aissue+user%3Agulpjs+label%3A%22help+wanted%22+
+
+## Undertaker: task management
+
+Undertaker handles task management in Gulp: the `gulp.task()`, `gulp.series()`
+and `gulp.parallel()` functions. `gulp.series()` and `gulp.parallel()` are in
+turn powered by Bach.
+
+- https://github.com/gulpjs/undertaker
+- https://github.com/gulpjs/bach
+
+## vinyl-fs: file streams
+
+vinyl-fs powers the `gulp.src()` and `gulp.dest()` functions: they take files
+and globs specified by the user, turns them into a stream of file objects,
+and then puts them back into the filesystem when `gulp.dest()` is called.
+
+The file objects themselves are vinyl objects: that's another library (a simple
+one!)
+
+- https://github.com/gulpjs/vinyl-fs
+- https://github.com/gulpjs/vinyl
+
+## chokidar: file watching
+
+`gulp.watch()` is using chokidar for file watching. It's actually wrapped in a
+small library on the gulp organization, glob-watcher.
+
+- https://github.com/paulmillr/chokidar
+- https://github.com/gulpjs/glob-watcher
+
+## gulp-cli: running gulp
+
+Finally, we have gulp-cli. This uses liftoff to take what people run in the
+command line and run the correct tasks. It works with both gulp 4 and older
+versions of gulp.
+
+- https://github.com/gulpjs/gulp-cli
+- https://github.com/js-cli/js-liftoff
+
# Conduct
We are committed to providing a friendly, safe and welcoming environment for
diff --git a/EXPENSE_POLICY.md b/EXPENSE_POLICY.md
new file mode 100644
index 00000000..93308aa0
--- /dev/null
+++ b/EXPENSE_POLICY.md
@@ -0,0 +1,16 @@
+# Expense Policy
+
+## Funding can be requested for significant changes made by Core Members.
+* Discuss the changes in the private gulp team forum.
+* Include a cost estimation with either a fixed price or hours + rate (suggested $50 per hour).
+* Notify the team before you exceed an estimate.
+
+## Bug bounties may be assigned at the Core Members’ discretion to issues of significant importance - usually issues outstanding for at least 6 months.
+* Issues with bug bounties will be labeled “Bug Bounty: $x”.
+* In order to claim a bug bounty, create a Pull Request that fixes an issue with a “Bug Bounty” label.
+* The Pull Request must be reviewed and merged by a Core Member. If competing submissions exist, the best solution will be chosen by a Core Member. All else equal, the first submission will be chosen.
+* Once your Pull Request is merged, you can submit an expense to our [Open Collective](https://opencollective.com/gulpjs/expenses/new) which includes the link to your submission in the description (e.g. $100 bug bounty claim for https://github.com/gulpjs/gulp/pull/2226). You will also need to provide an invoice, see the [Open Collective Expense FAQ](https://opencollective.com/faq/expenses) for more details and to get a Google Docs template that you can use.
+* Then, add a comment on your Pull Request, noting that you’ve claimed the money, with a link to your Open Collective expense. This is to ensure the same person who fixed the issue is claiming the money.
+* Your expense will be validated by a Core Member and then your payment will be dispersed by Open Collective the following Friday.
+
+## If you're doing other good things for gulp that end up costing you real money, feel free to reach out and we can discuss helping with those expenses!
diff --git a/LICENSE b/LICENSE
index ee25a909..7980c1d4 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,22 +1,21 @@
The MIT License (MIT)
-Copyright (c) 2013-2016 Fractal
+Copyright (c) 2013-2024 Blaine Bublitz and Eric Schoffstall
-Permission is hereby granted, free of charge, to any person obtaining
-a copy of this software and associated documentation files (the
-"Software"), to deal in the Software without restriction, including
-without limitation the rights to use, copy, modify, merge, publish,
-distribute, sublicense, and/or sell copies of the Software, and to
-permit persons to whom the Software is furnished to do so, subject to
-the following conditions:
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
-The above copyright notice and this permission notice shall be
-included in all copies or substantial portions of the Software.
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
-EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
-MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
-NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
-LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
-OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
-WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/README.md b/README.md
index 8a5ddeba..75b9af80 100644
--- a/README.md
+++ b/README.md
@@ -1,26 +1,32 @@
-
+
The streaming build system
-[![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]
-
+[![NPM version][npm-image]][npm-url] [![Downloads][downloads-image]][npm-url] [![Build Status][ci-image]][ci-url] [![Coveralls Status][coveralls-image]][coveralls-url]
## What is gulp?
- **Automation** - gulp is a toolkit that helps you automate painful or time-consuming tasks in your development workflow.
- **Platform-agnostic** - Integrations are built into all major IDEs and people are using gulp with PHP, .NET, Node.js, Java, and other platforms.
-- **Strong Ecosystem** - Use npm modules to do anything you want + over 2000 curated plugins for streaming file transformations
-- **Simple** - By providing only a minimal API surface, gulp is easy to learn and simple to use
+- **Strong Ecosystem** - Use npm modules to do anything you want + over 3000 curated plugins for streaming file transformations.
+- **Simple** - By providing only a minimal API surface, gulp is easy to learn and simple to use.
+
+## Installation
+
+Follow our [Quick Start guide][quick-start].
+
+## Roadmap
+
+Find out about all our work-in-progress and outstanding issues at https://github.com/orgs/gulpjs/projects.
## Documentation
-For a Getting started guide, API docs, recipes, making a plugin, etc. check out our docs!
+Check out the [Getting Started guide][getting-started-guide] and [API docs][api-docs] on our website!
-- Need something reliable? Check out the [documentation for the current release](/docs/README.md)!
-- 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)!
+__Excuse our dust! All other docs will be behind until we get everything updated. Please open an issue if something isn't working.__
## Sample `gulpfile.js`
@@ -28,109 +34,199 @@ This file will give you a taste of what gulp does.
```js
var gulp = require('gulp');
-var coffee = require('gulp-coffee');
+var less = require('gulp-less');
+var babel = require('gulp-babel');
var concat = require('gulp-concat');
var uglify = require('gulp-uglify');
-var imagemin = require('gulp-imagemin');
-var sourcemaps = require('gulp-sourcemaps');
+var rename = require('gulp-rename');
+var cleanCSS = require('gulp-clean-css');
var del = require('del');
var paths = {
- scripts: ['client/js/**/*.coffee', '!client/external/**/*.coffee'],
- images: 'client/img/**/*'
+ styles: {
+ src: 'src/styles/**/*.less',
+ dest: 'assets/styles/'
+ },
+ scripts: {
+ src: 'src/scripts/**/*.js',
+ dest: 'assets/scripts/'
+ }
};
-// Not all tasks need to use streams
-// A gulpfile is just another node program and you can use any package available on npm
-gulp.task('clean', function() {
- // You can use multiple globbing patterns as you would with `gulp.src`
- return del(['build']);
-});
-
-gulp.task('scripts', ['clean'], function() {
- // Minify and copy all JavaScript (except vendor scripts)
- // with sourcemaps all the way down
- return gulp.src(paths.scripts)
- .pipe(sourcemaps.init())
- .pipe(coffee())
- .pipe(uglify())
- .pipe(concat('all.min.js'))
- .pipe(sourcemaps.write())
- .pipe(gulp.dest('build/js'));
-});
-
-// Copy all static images
-gulp.task('images', ['clean'], function() {
- return gulp.src(paths.images)
- // Pass in options to the task
- .pipe(imagemin({optimizationLevel: 5}))
- .pipe(gulp.dest('build/img'));
-});
-
-// Rerun the task when a file changes
-gulp.task('watch', function() {
- gulp.watch(paths.scripts, ['scripts']);
- gulp.watch(paths.images, ['images']);
-});
-
-// The default task (called when you run `gulp` from cli)
-gulp.task('default', ['watch', 'scripts', 'images']);
+/* Not all tasks need to use streams, a gulpfile is just another node program
+ * and you can use all packages available on npm, but it must return either a
+ * Promise, a Stream or take a callback and call it
+ */
+function clean() {
+ // You can use multiple globbing patterns as you would with `gulp.src`,
+ // for example if you are using del 2.0 or above, return its promise
+ return del([ 'assets' ]);
+}
+
+/*
+ * Define our tasks using plain functions
+ */
+function styles() {
+ return gulp.src(paths.styles.src)
+ .pipe(less())
+ .pipe(cleanCSS())
+ // pass in options to the stream
+ .pipe(rename({
+ basename: 'main',
+ suffix: '.min'
+ }))
+ .pipe(gulp.dest(paths.styles.dest));
+}
+
+function scripts() {
+ return gulp.src(paths.scripts.src, { sourcemaps: true })
+ .pipe(babel())
+ .pipe(uglify())
+ .pipe(concat('main.min.js'))
+ .pipe(gulp.dest(paths.scripts.dest));
+}
+
+function watch() {
+ gulp.watch(paths.scripts.src, scripts);
+ gulp.watch(paths.styles.src, styles);
+}
+
+/*
+ * Specify if tasks run in series or parallel using `gulp.series` and `gulp.parallel`
+ */
+var build = gulp.series(clean, gulp.parallel(styles, scripts));
+
+/*
+ * You can use CommonJS `exports` module notation to declare tasks
+ */
+exports.clean = clean;
+exports.styles = styles;
+exports.scripts = scripts;
+exports.watch = watch;
+exports.build = build;
+/*
+ * Define default task that can be called by just running `gulp` from cli
+ */
+exports.default = build;
```
-## Incremental Builds
+## Use latest JavaScript version in your gulpfile
-We recommend these plugins:
+Gulp provides a wrapper that will be loaded in your ESM code, so you can name your gulpfile as `gulpfile.mjs` or with `"type": "module"` specified in your `package.json` file.
-- [gulp-changed](https://github.com/sindresorhus/gulp-changed) - only pass through changed files
-- [gulp-cached](https://github.com/contra/gulp-cached) - in-memory file cache, not for operation on sets of files
-- [gulp-remember](https://github.com/ahaurw01/gulp-remember) - pairs nicely with gulp-cached
-- [gulp-newer](https://github.com/tschaub/gulp-newer) - pass through newer source files only, supports many:1 source:dest
+And here's the same sample from above written in **ESNext**.
-## Want to test the latest and greatest?
-
-We're hard at work on our latest release, but we need your help testing it!
+```js
+import { src, dest, watch } from 'gulp';
+import less from 'gulp-less';
+import babel from 'gulp-babel';
+import concat from 'gulp-concat';
+import uglify from 'gulp-uglify';
+import rename from 'gulp-rename';
+import cleanCSS from 'gulp-clean-css';
+import {deleteAsync} from 'del';
+
+const paths = {
+ styles: {
+ src: 'src/styles/**/*.less',
+ dest: 'assets/styles/'
+ },
+ scripts: {
+ src: 'src/scripts/**/*.js',
+ dest: 'assets/scripts/'
+ }
+};
-```sh
-npm install gulpjs/gulp#4.0
+/*
+ * For small tasks you can export arrow functions
+ */
+export const clean = () => deleteAsync([ 'assets' ]);
+
+/*
+ * You can also declare named functions and export them as tasks
+ */
+export function styles() {
+ return src(paths.styles.src)
+ .pipe(less())
+ .pipe(cleanCSS())
+ // pass in options to the stream
+ .pipe(rename({
+ basename: 'main',
+ suffix: '.min'
+ }))
+ .pipe(dest(paths.styles.dest));
+}
+
+export function scripts() {
+ return src(paths.scripts.src, { sourcemaps: true })
+ .pipe(babel())
+ .pipe(uglify())
+ .pipe(concat('main.min.js'))
+ .pipe(dest(paths.scripts.dest));
+}
+
+ /*
+ * You could even use `export as` to rename exported tasks
+ */
+function watchFiles() {
+ watch(paths.scripts.src, scripts);
+ watch(paths.styles.src, styles);
+}
+export { watchFiles as watch };
+
+const build = gulp.series(clean, gulp.parallel(styles, scripts));
+/*
+ * Export a default task
+ */
+export default build;
```
-There's a slew of major (wonderful) changes in 4.0, so make sure you check out the [docs on that branch](https://github.com/gulpjs/gulp/tree/4.0)!
+## Incremental Builds
+
+You can filter out unchanged files between runs of a task using
+the `gulp.src` function's `since` option and `gulp.lastRun`:
+```js
+const paths = {
+ ...
+ images: {
+ src: 'src/images/**/*.{jpg,jpeg,png}',
+ dest: 'build/img/'
+ }
+}
+
+function images() {
+ return gulp.src(paths.images.src, {since: gulp.lastRun(images)})
+ .pipe(imagemin())
+ .pipe(gulp.dest(paths.images.dest));
+}
+
+function watch() {
+ gulp.watch(paths.images.src, images);
+}
+```
+Task run times are saved in memory and are lost when gulp exits. It will only
+save time during the `watch` task when running the `images` task
+for a second time.
## Want to contribute?
Anyone can help make this project better - check out our [Contributing guide](/CONTRIBUTING.md)!
-## Backers
-
-Support us with a monthly donation and help us continue our activities.
-
-[![Backers][backers-image]][support-url]
-
-## Sponsors
-
-Become a sponsor to get your logo on our README on Github.
+
+[quick-start]: https://gulpjs.com/docs/en/getting-started/quick-start
+[getting-started-guide]: https://gulpjs.com/docs/en/getting-started/quick-start
+[api-docs]: https://gulpjs.com/docs/en/api/concepts
+[esm-module]: https://github.com/standard-things/esm
+
-[![Sponsors][sponsors-image]][support-url]
-
-[downloads-image]: https://img.shields.io/npm/dm/gulp.svg
+
+[downloads-image]: https://img.shields.io/npm/dm/gulp.svg?style=flat-square
[npm-url]: https://www.npmjs.com/package/gulp
-[npm-image]: https://img.shields.io/npm/v/gulp.svg
+[npm-image]: https://img.shields.io/npm/v/gulp.svg?style=flat-square
-[travis-url]: https://travis-ci.org/gulpjs/gulp
-[travis-image]: https://img.shields.io/travis/gulpjs/gulp/master.svg
+[ci-url]: https://github.com/gulpjs/gulp/actions?query=workflow:dev
+[ci-image]: https://img.shields.io/github/actions/workflow/status/gulpjs/gulp/dev.yml?branch=master&style=flat-square
[coveralls-url]: https://coveralls.io/r/gulpjs/gulp
-[coveralls-image]: https://img.shields.io/coveralls/gulpjs/gulp/master.svg
-
-[gitter-url]: https://gitter.im/gulpjs/gulp
-[gitter-image]: https://badges.gitter.im/gulpjs/gulp.svg
-
-[backer-url]: #backers
-[backer-badge]: https://opencollective.com/gulpjs/backers/badge.svg?color=blue
-[sponsor-url]: #sponsors
-[sponsor-badge]: https://opencollective.com/gulpjs/sponsors/badge.svg?color=blue
-
-[support-url]: https://opencollective.com/gulpjs#support
-
-[backers-image]: https://opencollective.com/gulpjs/backers.svg
-[sponsors-image]: https://opencollective.com/gulpjs/sponsors.svg
+[coveralls-image]: https://img.shields.io/coveralls/gulpjs/gulp/master.svg?style=flat-square
+
diff --git a/bin/gulp.js b/bin/gulp.js
index a5374c11..f4b80cc4 100755
--- a/bin/gulp.js
+++ b/bin/gulp.js
@@ -1,212 +1,3 @@
#!/usr/bin/env node
-'use strict';
-var gutil = require('gulp-util');
-var prettyTime = require('pretty-hrtime');
-var chalk = require('chalk');
-var semver = require('semver');
-var archy = require('archy');
-var Liftoff = require('liftoff');
-var tildify = require('tildify');
-var interpret = require('interpret');
-var v8flags = require('v8flags');
-var completion = require('../lib/completion');
-var argv = require('minimist')(process.argv.slice(2));
-var taskTree = require('../lib/taskTree');
-
-// Set env var for ORIGINAL cwd
-// before anything touches it
-process.env.INIT_CWD = process.cwd();
-
-var cli = new Liftoff({
- name: 'gulp',
- completions: completion,
- extensions: interpret.jsVariants,
- v8flags: v8flags,
-});
-
-// Exit with 0 or 1
-var failed = false;
-process.once('exit', function(code) {
- if (code === 0 && failed) {
- process.exit(1);
- }
-});
-
-// Parse those args m8
-var cliPackage = require('../package');
-var versionFlag = argv.v || argv.version;
-var tasksFlag = argv.T || argv.tasks;
-var tasks = argv._;
-var toRun = tasks.length ? tasks : ['default'];
-
-// This is a hold-over until we have a better logging system
-// with log levels
-var simpleTasksFlag = argv['tasks-simple'];
-var shouldLog = !argv.silent && !simpleTasksFlag;
-
-if (!shouldLog) {
- gutil.log = function() {};
-}
-
-cli.on('require', function(name) {
- gutil.log('Requiring external module', chalk.magenta(name));
-});
-
-cli.on('requireFail', function(name) {
- gutil.log(chalk.red('Failed to load external module'), chalk.magenta(name));
-});
-
-cli.on('respawn', function(flags, child) {
- var nodeFlags = chalk.magenta(flags.join(', '));
- var pid = chalk.magenta(child.pid);
- gutil.log('Node flags detected:', nodeFlags);
- gutil.log('Respawned to PID:', pid);
-});
-
-cli.launch({
- cwd: argv.cwd,
- configPath: argv.gulpfile,
- require: argv.require,
- completion: argv.completion,
-}, handleArguments);
-
-// The actual logic
-function handleArguments(env) {
- if (versionFlag && tasks.length === 0) {
- gutil.log('CLI version', cliPackage.version);
- if (env.modulePackage && typeof env.modulePackage.version !== 'undefined') {
- gutil.log('Local version', env.modulePackage.version);
- }
- process.exit(0);
- }
-
- if (!env.modulePath) {
- gutil.log(
- chalk.red('Local gulp not found in'),
- chalk.magenta(tildify(env.cwd))
- );
- gutil.log(chalk.red('Try running: npm install gulp'));
- process.exit(1);
- }
-
- if (!env.configPath) {
- gutil.log(chalk.red('No gulpfile found'));
- process.exit(1);
- }
-
- // Check for semver difference between cli and local installation
- if (semver.gt(cliPackage.version, env.modulePackage.version)) {
- gutil.log(chalk.red('Warning: gulp version mismatch:'));
- gutil.log(chalk.red('Global gulp is', cliPackage.version));
- gutil.log(chalk.red('Local gulp is', env.modulePackage.version));
- }
-
- // Chdir before requiring gulpfile to make sure
- // we let them chdir as needed
- if (process.cwd() !== env.cwd) {
- process.chdir(env.cwd);
- gutil.log(
- 'Working directory changed to',
- chalk.magenta(tildify(env.cwd))
- );
- }
-
- // This is what actually loads up the gulpfile
- require(env.configPath);
- gutil.log('Using gulpfile', chalk.magenta(tildify(env.configPath)));
-
- var gulpInst = require(env.modulePath);
- logEvents(gulpInst);
-
- process.nextTick(function() {
- if (simpleTasksFlag) {
- return logTasksSimple(env, gulpInst);
- }
- if (tasksFlag) {
- return logTasks(env, gulpInst);
- }
- gulpInst.start.apply(gulpInst, toRun);
- });
-}
-
-function logTasks(env, localGulp) {
- var tree = taskTree(localGulp.tasks);
- tree.label = 'Tasks for ' + chalk.magenta(tildify(env.configPath));
- archy(tree)
- .split('\n')
- .forEach(function(v) {
- if (v.trim().length === 0) {
- return;
- }
- gutil.log(v);
- });
-}
-
-function logTasksSimple(env, localGulp) {
- console.log(Object.keys(localGulp.tasks)
- .join('\n')
- .trim());
-}
-
-// Format orchestrator errors
-function formatError(e) {
- if (!e.err) {
- return e.message;
- }
-
- // PluginError
- if (typeof e.err.showStack === 'boolean') {
- return e.err.toString();
- }
-
- // Normal error
- if (e.err.stack) {
- return e.err.stack;
- }
-
- // Unknown (string, number, etc.)
- return new Error(String(e.err)).stack;
-}
-
-// Wire up logging events
-function logEvents(gulpInst) {
-
- // Total hack due to poor error management in orchestrator
- gulpInst.on('err', function() {
- failed = true;
- });
-
- gulpInst.on('task_start', function(e) {
- // TODO: batch these
- // so when 5 tasks start at once it only logs one time with all 5
- gutil.log('Starting', '\'' + chalk.cyan(e.task) + '\'...');
- });
-
- gulpInst.on('task_stop', function(e) {
- var time = prettyTime(e.hrDuration);
- gutil.log(
- 'Finished', '\'' + chalk.cyan(e.task) + '\'',
- 'after', chalk.magenta(time)
- );
- });
-
- gulpInst.on('task_err', function(e) {
- var msg = formatError(e);
- var time = prettyTime(e.hrDuration);
- gutil.log(
- '\'' + chalk.cyan(e.task) + '\'',
- chalk.red('errored after'),
- chalk.magenta(time)
- );
- gutil.log(msg);
- });
-
- gulpInst.on('task_not_found', function(err) {
- gutil.log(
- chalk.red('Task \'' + err.task + '\' is not in your gulpfile')
- );
- gutil.log('Please check the documentation for proper gulpfile formatting');
- process.exit(1);
- });
-}
+require('gulp-cli')();
diff --git a/completion/README.md b/completion/README.md
deleted file mode 100644
index c0e8c913..00000000
--- a/completion/README.md
+++ /dev/null
@@ -1,20 +0,0 @@
-# Completion for gulp
-> Thanks to grunt team and Tyler Kellen
-
-To enable tasks auto-completion in shell you should add `eval "$(gulp --completion=shell)"` in your `.shellrc` file.
-
-## Bash
-
-Add `eval "$(gulp --completion=bash)"` to `~/.bashrc`.
-
-## Zsh
-
-Add `eval "$(gulp --completion=zsh)"` to `~/.zshrc`.
-
-## Powershell
-
-Add `Invoke-Expression ((gulp --completion=powershell) -join [System.Environment]::NewLine)` to `$PROFILE`.
-
-## Fish
-
-Add `gulp --completion=fish | source` to `~/.config/fish/config.fish`.
diff --git a/completion/bash b/completion/bash
deleted file mode 100644
index 704c27c1..00000000
--- a/completion/bash
+++ /dev/null
@@ -1,27 +0,0 @@
-#!/bin/bash
-
-# Borrowed from grunt-cli
-# http://gruntjs.com/
-#
-# Copyright (c) 2012 Tyler Kellen, contributors
-# Licensed under the MIT license.
-# https://github.com/gruntjs/grunt/blob/master/LICENSE-MIT
-
-# Usage:
-#
-# To enable bash completion for gulp, add the following line (minus the
-# leading #, which is the bash comment character) to your ~/.bashrc file:
-#
-# eval "$(gulp --completion=bash)"
-
-# Enable bash autocompletion.
-function _gulp_completions() {
- # The currently-being-completed word.
- local cur="${COMP_WORDS[COMP_CWORD]}"
- #Grab tasks
- local compls=$(gulp --tasks-simple)
- # Tell complete what stuff to show.
- COMPREPLY=($(compgen -W "$compls" -- "$cur"))
-}
-
-complete -o default -F _gulp_completions gulp
diff --git a/completion/fish b/completion/fish
deleted file mode 100644
index f27f2248..00000000
--- a/completion/fish
+++ /dev/null
@@ -1,10 +0,0 @@
-#!/usr/bin/env fish
-
-# Usage:
-#
-# To enable fish completion for gulp, add the following line to
-# your ~/.config/fish/config.fish file:
-#
-# gulp --completion=fish | source
-
-complete -c gulp -a "(gulp --tasks-simple)" -f
diff --git a/completion/powershell b/completion/powershell
deleted file mode 100644
index 08ec4382..00000000
--- a/completion/powershell
+++ /dev/null
@@ -1,61 +0,0 @@
-# Copyright (c) 2014 Jason Jarrett
-#
-# Tab completion for the `gulp`
-#
-# Usage:
-#
-# To enable powershell completion for gulp you need to be running
-# at least PowerShell v3 or greater and add the below to your $PROFILE
-#
-# Invoke-Expression ((gulp --completion=powershell) -join [System.Environment]::NewLine)
-#
-#
-
-$gulp_completion_Process = {
- param($commandName, $parameterName, $wordToComplete, $commandAst, $fakeBoundParameter)
-
-
- # Load up an assembly to read the gulpfile's sha1
- if(-not $global:GulpSHA1Managed) {
- [Reflection.Assembly]::LoadWithPartialName("System.Security") | out-null
- $global:GulpSHA1Managed = new-Object System.Security.Cryptography.SHA1Managed
- }
-
- # setup a global (in-memory) cache
- if(-not $global:GulpfileShaCache) {
- $global:GulpfileShaCache = @{};
- }
-
- $cache = $global:GulpfileShaCache;
-
- # Get the gulpfile's sha1
- $sha1gulpFile = (resolve-path gulpfile.js -ErrorAction Ignore | %{
- $file = [System.IO.File]::Open($_.Path, "open", "read")
- [string]::join('', ($global:GulpSHA1Managed.ComputeHash($file) | %{ $_.ToString("x2") }))
- $file.Dispose()
- })
-
- # lookup the sha1 for previously cached task lists.
- if($cache.ContainsKey($sha1gulpFile)){
- $tasks = $cache[$sha1gulpFile];
- } else {
- $tasks = (gulp --tasks-simple).split("`n");
- $cache[$sha1gulpFile] = $tasks;
- }
-
-
- $tasks |
- where { $_.startswith($commandName) }
- Sort-Object |
- foreach { New-Object System.Management.Automation.CompletionResult $_, $_, 'ParameterValue', ('{0}' -f $_) }
-}
-
-if (-not $global:options) {
- $global:options = @{
- CustomArgumentCompleters = @{};
- NativeArgumentCompleters = @{}
- }
-}
-
-$global:options['NativeArgumentCompleters']['gulp'] = $gulp_completion_Process
-$function:tabexpansion2 = $function:tabexpansion2 -replace 'End\r\n{','End { if ($null -ne $options) { $options += $global:options} else {$options = $global:options}'
diff --git a/completion/zsh b/completion/zsh
deleted file mode 100644
index 8169b22d..00000000
--- a/completion/zsh
+++ /dev/null
@@ -1,25 +0,0 @@
-#!/bin/zsh
-
-# Borrowed from grunt-cli
-# http://gruntjs.com/
-#
-# Copyright (c) 2012 Tyler Kellen, contributors
-# Licensed under the MIT license.
-# https://github.com/gruntjs/grunt/blob/master/LICENSE-MIT
-
-# Usage:
-#
-# To enable zsh completion for gulp, add the following line (minus the
-# leading #, which is the zsh comment character) to your ~/.zshrc file:
-#
-# eval "$(gulp --completion=zsh)"
-
-# Enable zsh autocompletion.
-function _gulp_completion() {
- # Grab tasks
- compls=$(gulp --tasks-simple)
- completions=(${=compls})
- compadd -- $completions
-}
-
-compdef _gulp_completion gulp
diff --git a/docs/API.md b/docs/API.md
deleted file mode 100644
index d06906f1..00000000
--- a/docs/API.md
+++ /dev/null
@@ -1,323 +0,0 @@
-## gulp API docs
-
-Jump to:
- [gulp.src](#gulpsrcglobs-options) |
- [gulp.dest](#gulpdestpath-options) |
- [gulp.task](#gulptaskname--deps--fn) |
- [gulp.watch](#gulpwatchglob--opts-tasks-or-gulpwatchglob--opts-cb)
-
-### gulp.src(globs[, options])
-
-Emits files matching provided glob or an array of globs.
-Returns a [stream](http://nodejs.org/api/stream.html) of [Vinyl files](https://github.com/gulpjs/vinyl-fs)
-that can be [piped](http://nodejs.org/api/stream.html#stream_readable_pipe_destination_options)
-to plugins.
-
-```javascript
-gulp.src('client/templates/*.jade')
- .pipe(jade())
- .pipe(minify())
- .pipe(gulp.dest('build/minified_templates'));
-```
-
-#### globs
-Type: `String` or `Array`
-
-Glob or array of globs to read. Globs use [node-glob syntax] except that negation is fully supported.
-
-A glob that begins with `!` excludes matching files from the glob results up to that point. For example, consider this directory structure:
-
- client/
- a.js
- bob.js
- bad.js
-
-The following expression matches `a.js` and `bad.js`:
-
- gulp.src(['client/*.js', '!client/b*.js', 'client/bad.js'])
-
-
-#### options
-Type: `Object`
-
-Options to pass to [node-glob] through [glob-stream].
-
-gulp supports all [options supported by node-glob][node-glob documentation] and [glob-stream] except `ignore` and adds the following options.
-
-##### options.buffer
-Type: `Boolean`
-Default: `true`
-
-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.
-
-##### options.read
-Type: `Boolean`
-Default: `true`
-
-Setting this to `false` will return `file.contents` as null and not read the file at all.
-
-##### options.base
-Type: `String`
-Default: everything before a glob starts (see [glob2base])
-
-E.g., consider `somefile.js` in `client/js/somedir`:
-
-```js
-gulp.src('client/js/**/*.js') // Matches 'client/js/somedir/somefile.js' and resolves `base` to `client/js/`
- .pipe(minify())
- .pipe(gulp.dest('build')); // Writes 'build/somedir/somefile.js'
-
-gulp.src('client/js/**/*.js', { base: 'client' })
- .pipe(minify())
- .pipe(gulp.dest('build')); // Writes 'build/js/somedir/somefile.js'
-```
-
-### gulp.dest(path[, options])
-
-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.
-
-```javascript
-gulp.src('./client/templates/*.jade')
- .pipe(jade())
- .pipe(gulp.dest('./build/templates'))
- .pipe(minify())
- .pipe(gulp.dest('./build/minified_templates'));
-```
-
-The write path is calculated by appending the file relative path to the given
-destination directory. In turn, relative paths are calculated against the file base.
-See `gulp.src` above for more info.
-
-#### path
-Type: `String` or `Function`
-
-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).
-
-#### options
-Type: `Object`
-
-##### options.cwd
-Type: `String`
-Default: `process.cwd()`
-
-`cwd` for the output folder, only has an effect if provided output folder is relative.
-
-##### options.mode
-Type: `String`
-Default: `0777`
-
-Octal permission string specifying mode for any folders that need to be created for output folder.
-
-### gulp.task(name [, deps] [, fn])
-
-Define a task using [Orchestrator].
-
-```js
-gulp.task('somename', function() {
- // Do stuff
-});
-```
-
-#### name
-Type: `String`
-
-The name of the task. Tasks that you want to run from the command line should not have spaces in them.
-
-#### deps
-Type: `Array`
-
-An array of tasks to be executed and completed before your task will run.
-
-```js
-gulp.task('mytask', ['array', 'of', 'task', 'names'], function() {
- // Do stuff
-});
-```
-
-**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.
-
-You can also omit the function if you only want to run a bundle of dependency tasks:
-
-```js
-gulp.task('build', ['array', 'of', 'task', 'names']);
-```
-
-**Note:** The tasks will run in parallel (all at once), so don't assume that the tasks will start/finish in order.
-
-#### fn
-Type: `Function`
-
-The function performs the task's main operations. Generally this takes the form of:
-
-```js
-gulp.task('buildStuff', function() {
- // Do something that "builds stuff"
- var stream = gulp.src(/*some source path*/)
- .pipe(somePlugin())
- .pipe(someOtherPlugin())
- .pipe(gulp.dest(/*some destination*/));
-
- return stream;
- });
-```
-
-#### Async task support
-
-Tasks can be made asynchronous if its `fn` does one of the following:
-
-##### Accept a callback
-
-```javascript
-// run a command in a shell
-var exec = require('child_process').exec;
-gulp.task('jekyll', function(cb) {
- // build Jekyll
- exec('jekyll build', function(err) {
- if (err) return cb(err); // return error
- cb(); // finished task
- });
-});
-
-// use an async result in a pipe
-gulp.task('somename', function(cb) {
- getFilesAsync(function(err, res) {
- if (err) return cb(err);
- var stream = gulp.src(res)
- .pipe(minify())
- .pipe(gulp.dest('build'))
- .on('end', cb);
- });
-});
-```
-
-##### Return a stream
-
-```js
-gulp.task('somename', function() {
- var stream = gulp.src('client/**/*.js')
- .pipe(minify())
- .pipe(gulp.dest('build'));
- return stream;
-});
-```
-
-##### Return a promise
-
-```javascript
-var Q = require('q');
-
-gulp.task('somename', function() {
- var deferred = Q.defer();
-
- // do async stuff
- setTimeout(function() {
- deferred.resolve();
- }, 1);
-
- return deferred.promise;
-});
-```
-
-**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:
-
-- give it a hint to tell it when the task is done,
-- and give it a hint that a task depends on completion of another.
-
-For these examples, let's presume you have two tasks, "one" and "two" that you specifically want to run in this order:
-
-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.
-
-2. In task "two" you add a hint telling the engine that it depends on completion of the first task.
-
-So this example would look like this:
-
-```js
-var gulp = require('gulp');
-
-// takes in a callback so the engine knows when it'll be done
-gulp.task('one', function(cb) {
- // do stuff -- async or otherwise
- cb(err); // if err is not null and not undefined, the run will stop, and note that it failed
-});
-
-// identifies a dependent task must be complete before this one begins
-gulp.task('two', ['one'], function() {
- // task 'one' is done now
-});
-
-gulp.task('default', ['one', 'two']);
-```
-
-
-### gulp.watch(glob [, opts], tasks) or gulp.watch(glob [, opts, cb])
-
-Watch files and do something when a file changes. This always returns an EventEmitter that emits `change` events.
-
-### gulp.watch(glob[, opts], tasks)
-
-#### glob
-Type: `String` or `Array`
-
-A single glob or array of globs that indicate which files to watch for changes.
-
-#### opts
-Type: `Object`
-
-Options, that are passed to [`gaze`](https://github.com/shama/gaze).
-
-#### tasks
-Type: `Array`
-
-Names of task(s) to run when a file changes, added with `gulp.task()`
-
-```js
-var watcher = gulp.watch('js/**/*.js', ['uglify','reload']);
-watcher.on('change', function(event) {
- console.log('File ' + event.path + ' was ' + event.type + ', running tasks...');
-});
-```
-
-### gulp.watch(glob[, opts, cb])
-
-#### glob
-Type: `String` or `Array`
-
-A single glob or array of globs that indicate which files to watch for changes.
-
-#### opts
-Type: `Object`
-
-Options, that are passed to [`gaze`](https://github.com/shama/gaze).
-
-#### cb(event)
-Type: `Function`
-
-Callback to be called on each change.
-
-```js
-gulp.watch('js/**/*.js', function(event) {
- console.log('File ' + event.path + ' was ' + event.type + ', running tasks...');
-});
-```
-
-The callback will be passed an object, `event`, that describes the change:
-
-##### event.type
-Type: `String`
-
-The type of change that occurred, either `added`, `changed`, `deleted` or `renamed`.
-
-##### event.path
-Type: `String`
-
-The path to the file that triggered the event.
-
-
-[node-glob]: https://github.com/isaacs/node-glob
-[node-glob documentation]: https://github.com/isaacs/node-glob#options
-[node-glob syntax]: https://github.com/isaacs/node-glob
-[glob-stream]: https://github.com/gulpjs/glob-stream
-[gulp-if]: https://github.com/robrich/gulp-if
-[Orchestrator]: https://github.com/robrich/orchestrator
-[glob2base]: https://github.com/wearefractal/glob2base
diff --git a/docs/CLI.md b/docs/CLI.md
index a536c913..68510412 100644
--- a/docs/CLI.md
+++ b/docs/CLI.md
@@ -8,8 +8,9 @@ gulp has very few flags to know about. All other flags are for tasks to use if n
- `--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
- `--gulpfile ` will manually set path of gulpfile. Useful if you have multiple gulpfiles. This will set the CWD to the gulpfile directory as well
- `--cwd ` will manually set the CWD. The search for the gulpfile, as well as the relativity of all requires will be from here
-- `-T` or `--tasks` will display the task dependency tree for the loaded gulpfile
+- `-T` or `--tasks` will display the task dependency tree for the loaded gulpfile. It will include the task names and their [description](./API.md#fndescription).
- `--tasks-simple` will display a plaintext list of tasks for the loaded gulpfile
+- `--verify` will verify plugins referenced in project's package.json against the plugins blacklist
- `--color` will force gulp and gulp plugins to display colors even when no color support is detected
- `--no-color` will force gulp and gulp plugins to not display colors even when color support is detected
- `--silent` will disable all gulp logging
@@ -18,7 +19,7 @@ The CLI adds process.env.INIT_CWD which is the original cwd it was launched from
#### Task specific flags
-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
+Refer to this [StackOverflow](https://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
### Tasks
@@ -40,3 +41,75 @@ Just running `gulp` will execute the task `default`. If there is no
### Compilers
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.
+
+### Examples
+
+#### Example gulpfile
+
+```js
+gulp.task('one', function(done) {
+ // do stuff
+ done();
+});
+
+gulp.task('two', function(done) {
+ // do stuff
+ done();
+});
+
+gulp.task('three', three);
+
+function three(done) {
+ done();
+}
+three.description = "This is the description of task three";
+
+gulp.task('four', gulp.series('one', 'two'));
+
+gulp.task('five',
+ gulp.series('four',
+ gulp.parallel('three', function(done) {
+ // do more stuff
+ done();
+ })
+ )
+);
+```
+
+### `-T` or `--tasks`
+
+Command: `gulp -T` or `gulp --tasks`
+
+Output:
+```shell
+[20:58:55] Tasks for ~\exampleProject\gulpfile.js
+[20:58:55] ├── one
+[20:58:55] ├── two
+[20:58:55] ├── three This is the description of task three
+[20:58:55] ├─┬ four
+[20:58:55] │ └─┬
+[20:58:55] │ ├── one
+[20:58:55] │ └── two
+[20:58:55] ├─┬ five
+[20:58:55] │ └─┬
+[20:58:55] │ ├─┬ four
+[20:58:55] │ │ └─┬
+[20:58:55] │ │ ├── one
+[20:58:55] │ │ └── two
+[20:58:55] │ └─┬
+[20:58:55] │ ├── three
+[20:58:55] │ └──
+```
+
+### `--tasks-simple`
+
+Command: `gulp --tasks-simple`
+
+Output:
+```shell
+one
+two
+three
+four
+five
+```
diff --git a/docs/FAQ.md b/docs/FAQ.md
index aa8886ab..80729961 100644
--- a/docs/FAQ.md
+++ b/docs/FAQ.md
@@ -30,6 +30,10 @@ Probably. Ask yourself:
Always use `\n` to prevent diff issues between operating systems.
+## I installed gulp as a dependency from package.json file by running `npm install` but I keep getting `command not found` whenever I try running a gulp command, why doesn't it work?
+
+Upon installing gulp as a project dependency, you need to add that to your PATH environment variable so that when you run a command, the system can find it. An easy solution is to install gulp globally, so that its binaries end up in your PATH environment variable. To install gulp globally, use the command `npm install gulp-cli -g`
+
## Where can I get updates on gulp?
gulp updates can be found on the following twitters:
@@ -38,12 +42,12 @@ gulp updates can be found on the following twitters:
- [@eschoff](https://twitter.com/eschoff)
- [@gulpjs](https://twitter.com/gulpjs)
-## Does gulp have an IRC channel?
+## Does gulp have an chat channel?
-Yes, come chat with us in #gulpjs on [Freenode].
+Yes, come chat with us on [Gitter](https://gitter.im/gulpjs/gulp).
[Writing a gulp plugin]: writing-a-plugin/README.md
-[gulp introduction slideshow]: http://slid.es/contra/gulp
-[Freenode]: http://freenode.net/
-[search-gulp-plugins]: http://gulpjs.com/plugins/
+[gulp introduction slideshow]: https://slid.es/contra/gulp
+[Freenode]: https://freenode.net/
+[search-gulp-plugins]: https://gulpjs.com/plugins/
[npm plugin search]: https://npmjs.org/browse/keyword/gulpplugin
diff --git a/docs/README.md b/docs/README.md
index 39dc8469..939c8285 100644
--- a/docs/README.md
+++ b/docs/README.md
@@ -1,12 +1,13 @@
# gulp documentation
-* [Getting Started](getting-started.md) - Get started with gulp
-* [API documentation](API.md) - The programming interface, defined
+* [Getting Started](getting-started/) - Get started with gulp
+* [API documentation](api/) - The programming interface, defined
* [CLI documentation](CLI.md) - Learn how to call tasks and use compilers
* [Writing a Plugin](writing-a-plugin/) - The essentials of writing a gulp plugin
* [Why Use Pump?](why-use-pump/README.md) - Why to use the `pump` module instead of calling `.pipe` yourself
* [Simplified Chinese documentation][SimplifiedChineseDocs] - gulp 简体中文文档
* [Korean documentation][KoreanDocs] - gulp 한국어 참조 문서
+* [Polish documentation](/docs/locale/pl_PL/README.md) - gulp Dokumentacja
## FAQ
@@ -21,8 +22,10 @@ The community has written [recipes](recipes#recipes) for common gulp use-cases.
## Still got questions?
-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/).
+Post on [StackOverflow with a #gulp tag](https://stackoverflow.com/questions/tagged/gulp) or come chat with us in [#gulpjs](https://webchat.freenode.net/?channels=gulpjs) on [Freenode](https://freenode.net/).
+## Videos
+* [Intro to Gulp 4](https://youtu.be/N42LQ2dLoA8) presented by @addyosmani and @gauntface
## Books
* [Developing a gulp Edge](http://shop.oreilly.com/product/9781939902146.do)
@@ -32,17 +35,17 @@ Post on [StackOverflow with a #gulp tag](http://stackoverflow.com/questions/tagg
## Articles
* [Tagtree intro to gulp video](http://tagtree.io/gulp)
* [Introduction to node.js streams](https://github.com/substack/stream-handbook)
-* [Video introduction to node.js streams](http://www.youtube.com/watch?v=QgEuZ52OZtU)
-* [Getting started with gulp (by @markgdyr)](http://markgoodyear.com/2014/01/getting-started-with-gulp/)
+* [Video introduction to node.js streams](https://www.youtube.com/watch?v=QgEuZ52OZtU)
+* [Getting started with gulp (by @markgdyr)](https://markgoodyear.com/2014/01/getting-started-with-gulp/)
* [A cheatsheet for gulp](https://github.com/osscafe/gulp-cheatsheet)
* [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)
* [Inspiration (slides) about why gulp was made](http://slid.es/contra/gulp)
* [Building With Gulp](http://www.smashingmagazine.com/2014/06/11/building-with-gulp/)
* [Gulp - The Basics (screencast)](https://www.youtube.com/watch?v=dwSLFai8ovQ)
-* [Get started with gulp (video series)](http://www.youtube.com/playlist?list=PLRk95HPmOM6PN-G1xyKj9q6ap_dc9Yckm)
+* [Get started with gulp (video series)](https://www.youtube.com/playlist?list=PLRk95HPmOM6PN-G1xyKj9q6ap_dc9Yckm)
* [Optimize your web code with gulp](http://www.linuxuser.co.uk/tutorials/optimise-your-web-code-with-gulp-js)
* [Automate Your Tasks Easily with Gulp.js ](https://scotch.io/tutorials/automate-your-tasks-easily-with-gulp-js)
-
+* [How to upgrade to Gulp v4](https://www.liquidlight.co.uk/blog/article/how-do-i-update-to-gulp-4/)
## Examples
@@ -53,7 +56,7 @@ Post on [StackOverflow with a #gulp tag](http://stackoverflow.com/questions/tagg
All the documentation is covered by the CC0 license *(do whatever you want with it - public domain)*.
-[](http://creativecommons.org/publicdomain/zero/1.0/)
+[](https://creativecommons.org/publicdomain/zero/1.0/)
To the extent possible under law, [Fractal](http://wearefractal.com) has waived all copyright and related or neighboring rights to this work.
diff --git a/docs/advanced/creating-custom-registries.md b/docs/advanced/creating-custom-registries.md
new file mode 100644
index 00000000..6fcdbeb6
--- /dev/null
+++ b/docs/advanced/creating-custom-registries.md
@@ -0,0 +1,207 @@
+
+
+# Creating Custom Registries
+
+Allows custom registries to be plugged into the task system, which can provide shared tasks or augmented functionality. Registries are registered using [`registry()`][registry-api-docs].
+
+## Structure
+
+In order to be accepted by gulp, custom registries must follow a specific format.
+
+```js
+// as a function
+function TestRegistry() {}
+
+TestRegistry.prototype.init = function (gulpInst) {}
+TestRegistry.prototype.get = function (name) {}
+TestRegistry.prototype.set = function (name, fn) {}
+TestRegistry.prototype.tasks = function () {}
+
+// as a class
+class TestRegistry {
+ init(gulpInst) {}
+
+ get(name) {}
+
+ set(name, fn) {}
+
+ tasks() {}
+}
+```
+
+If a registry instance passed to `registry()` doesn't have all four methods, an error will be thrown.
+
+## Registration
+
+If we want to register our example registry from above, we will need to pass an instance of it to `registry()`.
+
+```js
+const { registry } = require('gulp');
+
+// ... TestRegistry setup code
+
+// good!
+registry(new TestRegistry())
+
+// bad!
+registry(TestRegistry())
+// This will trigger an error: 'Custom registries must be instantiated, but it looks like you passed a constructor'
+```
+
+## Methods
+
+### `init(gulpInst)`
+
+The `init()` method of a registry is called at the very end of the `registry()` function. The gulp instance passed as the only argument (`gulpInst`) can be used to pre-define tasks using
+`gulpInst.task(taskName, fn)`.
+
+#### Parameters
+
+| parameter | type | note |
+|:---------:|:----:|------|
+| gulpInst | object | Instance of gulp. |
+
+### `get(name)`
+
+The `get()` method receives a task `name` for the custom registry to resolve and return, or `undefined` if no task with that name exists.
+
+#### Parameters
+
+| parameter | type | note |
+|:---------:|:----:|------|
+| name | string | Name of the task to be retrieved. |
+
+### `set(name, fn)`
+
+The `set()` method receives a task `name` and `fn`. This is called internally by `task()` to provide user-registered tasks to custom registries.
+
+#### Parameters
+
+| parameter | type | note |
+|:---------:|:----:|------|
+| name | string | Name of the task to be set. |
+| fn | function | Task function to be set. |
+
+### `tasks()`
+
+Must return an object listing all tasks in the registry.
+
+## Use Cases
+
+### Sharing Tasks
+
+To share common tasks with all your projects, you can expose an `init` method on the registry and it will receive an instance of gulp as the only argument. You can then use `gulpInst.task(name, fn)` to register pre-defined tasks.
+
+For example, you might want to share a `clean` task:
+
+```js
+const fs = require('fs');
+const util = require('util');
+
+const DefaultRegistry = require('undertaker-registry');
+const del = require('del');
+
+function CommonRegistry(opts){
+ DefaultRegistry.call(this);
+
+ opts = opts || {};
+
+ this.buildDir = opts.buildDir || './build';
+}
+
+util.inherits(CommonRegistry, DefaultRegistry);
+
+CommonRegistry.prototype.init = function(gulpInst) {
+ const buildDir = this.buildDir;
+ const exists = fs.existsSync(buildDir);
+
+ if(exists){
+ throw new Error('Cannot initialize common tasks. ' + buildDir + ' directory exists.');
+ }
+
+ gulpInst.task('clean', function(){
+ return del([buildDir]);
+ });
+}
+
+module.exports = CommonRegistry;
+```
+
+Then to use it in a project:
+
+```js
+const { registry, series, task } = require('gulp');
+const CommonRegistry = require('myorg-common-tasks');
+
+registry(new CommonRegistry({ buildDir: '/dist' }));
+
+task('build', series('clean', function build(cb) {
+ // do things
+ cb();
+}));
+```
+
+### Sharing Functionality
+
+By controlling how tasks are added to the registry, you can decorate them.
+
+For example, if you wanted all tasks to share some data, you can use a custom registry to bind them to that data. Be sure to return the altered task, as per the description of registry methods above:
+
+```js
+const { registry, series, task } = require('gulp');
+const util = require('util');
+const DefaultRegistry = require('undertaker-registry');
+
+// Some task defined somewhere else
+const BuildRegistry = require('./build.js');
+const ServeRegistry = require('./serve.js');
+
+function ConfigRegistry(config){
+ DefaultRegistry.call(this);
+ this.config = config;
+}
+
+util.inherits(ConfigRegistry, DefaultRegistry);
+
+ConfigRegistry.prototype.set = function set(name, fn) {
+ var bound = fn.bind(this.config);
+ // Preserve internal properties and task metadata.
+ var task = Object.assign(bound, fn);
+ // The `DefaultRegistry` uses `this._tasks` for storage.
+ this._tasks[name] = task;
+ return task;
+};
+
+registry(new BuildRegistry());
+registry(new ServeRegistry());
+
+// `registry` will reset each task in the registry with
+// `ConfigRegistry.prototype.set` which will bind them to the config object.
+registry(new ConfigRegistry({
+ src: './src',
+ build: './build',
+ bindTo: '0.0.0.0:8888'
+}));
+
+task('default', series('clean', 'build', 'serve', function(cb) {
+ console.log('Server bind to ' + this.bindTo);
+ console.log('Serving' + this.build);
+ cb();
+}));
+```
+
+## Examples
+
+* [undertaker-registry][undertaker-registry-example]: The Gulp 4 default registry.
+* [undertaker-common-tasks][undertaker-common-tasks-example]: Proof-of-concept custom registry that pre-defines tasks.
+* [undertaker-task-metadata][undertaker-task-metadata-example]: Proof-of-concept custom registry that attaches metadata to each task.
+
+[registry-api-docs]: ../api/registry.md
+[undertaker-registry-example]: https://github.com/gulpjs/undertaker-registry
+[undertaker-common-tasks-example]: https://github.com/gulpjs/undertaker-common-tasks
+[undertaker-task-metadata-example]: https://github.com/gulpjs/undertaker-task-metadata
diff --git a/docs/api/README.md b/docs/api/README.md
new file mode 100644
index 00000000..7f2cc0cb
--- /dev/null
+++ b/docs/api/README.md
@@ -0,0 +1,16 @@
+## Table of Contents
+
+* [API Concepts](concepts.md)
+* [src()](src.md)
+* [dest()](dest.md)
+* [symlink()](symlink.md)
+* [lastRun()](last-run.md)
+* [series()](series.md)
+* [parallel()](parallel.md)
+* [watch()](watch.md)
+* [task()](task.md)
+* [registry()](registry.md)
+* [tree()](tree.md)
+* [Vinyl](vinyl.md)
+* [Vinyl.isVinyl()](vinyl-isvinyl.md)
+* [Vinyl.isCustomProp()](vinyl-iscustomprop.md)
diff --git a/docs/api/concepts.md b/docs/api/concepts.md
new file mode 100644
index 00000000..3f152905
--- /dev/null
+++ b/docs/api/concepts.md
@@ -0,0 +1,84 @@
+
+
+# Concepts
+
+The following concepts are prerequisites to understanding the API docs. They will be referenced throughout, refer back to this page for detailed explanations.
+
+If you're new here, begin with the [Getting Started Guide][quick-start-docs].
+
+## Vinyl
+
+Vinyl is a metadata object that describes a file. The main properties of a Vinyl instance are `path` and `contents` - core aspects of a file on your file system. Vinyl objects can be used to describe files from many sources - on a local file system or any remote storage option.
+
+## Vinyl adapters
+
+While Vinyl provides a way to describe a file, a way to access these files is needed. Each file source is accessed using a Vinyl adapter.
+
+An adapter exposes:
+* A method with the signature `src(globs, [options])` and returns a stream that produces Vinyl objects.
+* A method with the signature `dest(folder, [options])` and returns a stream that consumes Vinyl objects.
+* Any extra methods specific to their input/output medium - such as the `symlink` method `vinyl-fs` provides. They should always return streams that produce and/or consume Vinyl objects.
+
+## Tasks
+
+Each gulp task is an asynchronous JavaScript function that either accepts an error-first callback or returns a stream, promise, event emitter, child process, or observable. Due to some platform limitations, synchronous tasks aren't supported.
+
+For a more detailed explanation, see [Creating Tasks][creating-tasks-doc].
+
+## Globs
+
+A glob is a string of literal and/or wildcard characters, like `*`, `**`, or `!`, used to match filepaths. Globbing is the act of locating files on a file system using one or more globs.
+
+If you don't have experience with globs, see [Explaining Globs][explaining-globs-docs].
+
+## Glob base
+
+A glob base - sometimes called glob parent - is the path segment before any special characters in a glob string. As such, the glob base of `/src/js/**.js` is `/src/js/`. All paths that match the glob are guaranteed to share the glob base - that path segment can't be variable.
+
+Vinyl instances generated by `src()` are constructed with the glob base set as their `base` property. When written to the file system with `dest()`, the `base` will be removed from the output path to preserve directory structures.
+
+For more in depth information, see the [glob-parent][glob-parent-external] repository.
+
+## File system stats
+
+File metadata is provided as an instance of Node's [`fs.Stats`][fs-stats-external]. It is available as the `stat` property on your Vinyl instances and used internally to determine if a Vinyl object represents a directory or symbolic link. When written to the file system, permissions and time values are synchronized from the Vinyl object's `stat` property.
+
+## File system modes
+
+File system modes determine what permissions exist for a file. Most files and directories on your file system will have a fairly permissive mode, allowing gulp to read/write/update files on your behalf. By default, gulp will create files with the same permissions as the running process, but you can configure the modes through options in `src()`, `dest()`, etc. If you're experiencing permission (EPERM) issues, check the modes on your files.
+
+## Modules
+
+Gulp is made up of many small modules that are pulled together to work cohesively. By utilizing [semver][semver-external] within the small modules, we can release bug fixes and features without publishing new versions of gulp. Often, when you don't see progress on the main repository, work is being done in one of these modules.
+
+If you're having trouble, ensure your current modules are updated using the `npm update` command. If the problem persists, open an issue on the individual project repository.
+
+* [undertaker][undertaker-external] - the task registration system
+* [vinyl][vinyl-external] - the virtual file objects
+* [vinyl-fs][vinyl-fs-external] - a vinyl adapter to your local file system
+* [glob-watcher][glob-watcher-external] - the file watcher
+* [bach][bach-external] - task orchestration using `series()` and `parallel()`
+* [last-run][last-run-external] - tracks the last run time of a task
+* [vinyl-sourcemap][vinyl-sourcemap-external] - built-in sourcemap support
+* [gulp-cli][gulp-cli-external] - the command line interface for interacting with gulp
+
+
+[quick-start-docs]: ../getting-started/1-quick-start.md
+[creating-tasks-doc]: ../getting-started/3-creating-tasks.md
+[explaining-globs-docs]: ../getting-started/6-explaining-globs.md
+[undertaker-external]: https://github.com/gulpjs/undertaker
+[vinyl-external]: https://github.com/gulpjs/vinyl
+[vinyl-fs-external]: https://github.com/gulpjs/vinyl-fs
+[glob-watcher-external]: https://github.com/gulpjs/glob-watcher
+[bach-external]: https://github.com/gulpjs/bach
+[last-run-external]: https://github.com/gulpjs/last-run
+[vinyl-sourcemap-external]: https://github.com/gulpjs/vinyl-sourcemap
+[gulp-cli-external]: https://github.com/gulpjs/gulp-cli
+[semver-external]: https://semver.org
+[fs-stats-external]: https://nodejs.org/api/fs.html#fs_class_fs_stats
+[glob-parent-external]: https://github.com/es128/glob-parent
diff --git a/docs/api/dest.md b/docs/api/dest.md
new file mode 100644
index 00000000..e9d8bb35
--- /dev/null
+++ b/docs/api/dest.md
@@ -0,0 +1,122 @@
+
+
+# dest()
+
+Creates a stream for writing [Vinyl][vinyl-concepts] objects to the file system.
+
+## Usage
+
+```js
+const { src, dest } = require('gulp');
+
+function copy() {
+ return src('input/*.js')
+ .pipe(dest('output/'));
+}
+
+exports.copy = copy;
+```
+
+## Signature
+
+```js
+dest(directory, [options])
+```
+
+### Parameters
+
+| parameter | type | note |
+|:--------------:|:-----:|--------|
+| directory
**(required)** | string
function | The path of the output directory where files will be written. If a function is used, the function will be called with each Vinyl object and must return a string directory path. |
+| options | object | Detailed in [Options][options-section] below. |
+
+### Returns
+
+A stream that can be used in the middle or at the end of a pipeline to create files on the file system.
+Whenever a Vinyl object is passed through the stream, it writes the contents and other details out to the file system at the given directory. If the Vinyl object has a `symlink` property, a symbolic link will be created instead of writing the contents. After the file is created, its [metadata will be updated][metadata-updates-section] to match the Vinyl object.
+
+Whenever a file is created on the file system, the Vinyl object will be modified.
+* The `cwd`, `base`, and `path` properties will be updated to match the created file.
+* The `stat` property will be updated to match the file on the file system.
+* If the `contents` property is a stream, it will be reset so it can be read again.
+
+### Errors
+
+When `directory` is an empty string, throws an error with the message, "Invalid dest() folder argument. Please specify a non-empty string or a function."
+
+When `directory` is not a string or function, throws an error with the message, "Invalid dest() folder argument. Please specify a non-empty string or a function."
+
+When `directory` is a function that returns an empty string or `undefined`, emits an error with the message, "Invalid output folder".
+
+### Options
+
+
+**For options that accept a function, the passed function will be called with each Vinyl object and must return a value of another listed type.**
+
+| name | type | default | note |
+|:-------:|:------:|-----------|-------|
+| cwd | string
function | `process.cwd()` | The directory that will be combined with any relative path to form an absolute path. Is ignored for absolute paths. Use to avoid combining `directory` with `path.join()`. |
+| mode | number
function | `stat.mode` of the Vinyl object | The mode used when creating files. If not set and `stat.mode` is missing, the process' mode will be used instead. |
+| dirMode | number
function | | The mode used when creating directories. If not set, the process' mode will be used. |
+| overwrite | boolean
function | true | When true, overwrites existing files with the same path. |
+| append | boolean
function | false | If true, adds contents to the end of the file, instead of replacing existing contents. |
+| sourcemaps | boolean
string
function | false | If true, writes inline sourcemaps to the output file. Specifying a `string` path will write external [sourcemaps][sourcemaps-section] at the given path. |
+| relativeSymlinks | boolean
function | false | When false, any symbolic links created will be absolute.
**Note**: Ignored if a junction is being created, as they must be absolute. |
+| useJunctions | boolean
function | true | This option is only relevant on Windows and ignored elsewhere. When true, creates directory symbolic link as a junction. Detailed in [Symbolic links on Windows][symbolic-links-section] below. |
+
+## Metadata updates
+
+Whenever the `dest()` stream creates a file, the Vinyl object's `mode`, `mtime`, and `atime` are compared to the created file. If they differ, the created file will be updated to reflect the Vinyl object's metadata. If those properties are the same, or gulp doesn't have permissions to make changes, the attempt is skipped silently.
+
+This functionality is disabled on Windows or other operating systems that don't support Node's `process.getuid()` or `process.geteuid()` methods. This is due to Windows having unexpected results through usage of `fs.fchmod()` and `fs.futimes()`.
+
+**Note**: The `fs.futimes()` method internally converts `mtime` and `atime` timestamps to seconds. This division by 1000 may cause some loss of precision on 32-bit operating systems.
+
+## Sourcemaps
+
+Sourcemap support is built directly into `src()` and `dest()`, but it is disabled by default. Enable it to produce inline or external sourcemaps.
+
+Inline sourcemaps:
+```js
+const { src, dest } = require('gulp');
+const uglify = require('gulp-uglify');
+
+src('input/**/*.js', { sourcemaps: true })
+ .pipe(uglify())
+ .pipe(dest('output/', { sourcemaps: true }));
+```
+
+External sourcemaps:
+```js
+const { src, dest } = require('gulp');
+const uglify = require('gulp-uglify');
+
+src('input/**/*.js', { sourcemaps: true })
+ .pipe(uglify())
+ .pipe(dest('output/', { sourcemaps: '.' }));
+```
+
+## Symbolic links on Windows
+
+When creating symbolic links on Windows, a `type` argument is passed to Node's `fs.symlink()` method which specifies the kind of target being linked. The link type is set to:
+* `'file'` when the target is a regular file
+* `'junction'` when the target is a directory
+* `'dir'` when the target is a directory and the user disables the `useJunctions` option
+
+
+If you try to create a dangling (pointing to a non-existent target) link, the link type can't be determined automatically. In these cases, behavior will vary depending on whether the dangling link is being created via `symlink()` or via `dest()`.
+
+For dangling links created via `symlink()`, the incoming Vinyl object represents the target, so its stats will determine the desired link type. If `isDirectory()` returns false then a `'file'` link is created, otherwise a `'junction'` or a `'dir'` link is created depending on the value of the `useJunctions` option.
+
+For dangling links created via `dest()`, the incoming Vinyl object represents the link - typically loaded from disk via `src(..., { resolveSymlinks: false })`. In this case, the link type can't be reasonably determined and defaults to using `'file'`. This may cause unexpected behavior if you are creating a dangling link to a directory. **Avoid this scenario.**
+
+[sourcemaps-section]: #sourcemaps
+[symbolic-links-section]: #symbolic-links-on-windows
+[options-section]: #options
+[metadata-updates-section]: #metadata-updates
+[vinyl-concepts]: ../api/concepts.md#vinyl
diff --git a/docs/api/last-run.md b/docs/api/last-run.md
new file mode 100644
index 00000000..4e970721
--- /dev/null
+++ b/docs/api/last-run.md
@@ -0,0 +1,81 @@
+
+
+# lastRun()
+
+Retrieves the last time a task was successfully completed during the current running process. Most useful on subsequent task runs while a watcher is running.
+
+When combined with `src()`, enables incremental builds to speed up execution times by skipping files that haven't changed since the last successful task completion.
+
+## Usage
+
+```js
+const { src, dest, lastRun, watch } = require('gulp');
+const imagemin = require('gulp-imagemin');
+
+function images() {
+ return src('src/images/**/*.jpg', { since: lastRun(images) })
+ .pipe(imagemin())
+ .pipe(dest('build/img/'));
+}
+
+exports.default = function() {
+ watch('src/images/**/*.jpg', images);
+};
+```
+
+
+## Signature
+
+```js
+lastRun(task, [precision])
+```
+
+### Parameters
+
+| parameter | type | note |
+|:--------------:|:------:|-------|
+| task
**(required)** | function
string | The task function or the string alias of a registered task. |
+| precision | number | Default: `1000` on Node v0.10, `0` on Node v0.12+. Detailed in [Timestamp precision][timestamp-precision-section] section below. |
+
+### Returns
+
+A timestamp (in milliseconds), matching the last completion time of the task. If the task has not been run or has failed, returns `undefined`.
+
+To avoid an invalid state being cached, the returned value will be `undefined` if a task errors.
+
+### Errors
+
+When called with a value other than a string or function, throws an error with the message, "Only functions can check lastRun".
+
+When called on a non-extensible function and Node is missing WeakMap, throws an error with the message, "Only extensible functions can check lastRun".
+
+## Timestamp precision
+
+While there are sensible defaults for the precision of timestamps, they can be rounded using the `precision` parameter. Useful if your file system or Node version has a lossy precision on file time attributes.
+
+* `lastRun(someTask)` returns 1426000001111
+* `lastRun(someTask, 100)` returns 1426000001100
+* `lastRun(someTask, 1000)` returns 1426000001000
+
+A file's [mtime stat][fs-stats-concepts] precision may vary depending on the node version and/or the file system used.
+
+
+| platform | precision |
+|:-----------:|:------------:|
+| Node v0.10 | 1000ms |
+| Node v0.12+ | 1ms |
+| FAT32 file system | 2000ms |
+| HFS+ or Ext3 file systems | 1000ms |
+| NTFS using Node v0.10 | 1s |
+| NTFS using Node 0.12+ | 100ms |
+| Ext4 using Node v0.10 | 1000ms |
+| Ext4 using Node 0.12+ | 1ms |
+
+
+[timestamp-precision-section]: #timestamp-precision
+[fs-stats-concepts]: ../api/concepts.md#file-system-stats
diff --git a/docs/api/parallel.md b/docs/api/parallel.md
new file mode 100644
index 00000000..2df31640
--- /dev/null
+++ b/docs/api/parallel.md
@@ -0,0 +1,113 @@
+
+
+# parallel()
+
+Combines task functions and/or composed operations into larger operations that will be executed simultaneously. There are no imposed limits on the nesting depth of composed operations using `series()` and `parallel()`.
+
+## Usage
+
+```js
+const { parallel } = require('gulp');
+
+function javascript(cb) {
+ // body omitted
+ cb();
+}
+
+function css(cb) {
+ // body omitted
+ cb();
+}
+
+exports.build = parallel(javascript, css);
+```
+
+## Signature
+
+```js
+parallel(...tasks)
+```
+
+### Parameters
+
+| parameter | type | note |
+|:--------------:|:------:|-------|
+| tasks
**(required)** | function
string | Any number of task functions can be passed as individual arguments. Strings can be used if you've registered tasks previously, but this is not recommended. |
+
+### Returns
+
+A composed operation to be registered as a task or nested within other `series` and/or `parallel` compositions.
+
+When the composed operation is executed, all tasks will be run at maximum concurrency. If an error occurs in one task, other tasks nondeterministically may or may not complete.
+
+### Errors
+
+When no tasks are passed, throws an error with the message, "One or more tasks should be combined using series or parallel".
+
+When invalid tasks or unregistered tasks are passed, throws an error with the message, "Task never defined".
+
+## Forward references
+
+A forward reference is when you compose tasks, using string references, that haven't been registered yet. This was a common practice in older versions, but this feature was removed to achieve faster task runtime and promote the use of named functions.
+
+In newer versions, you'll get an error, with the message "Task never defined", if you try to use forward references. You may experience this when trying to use `exports` for task registration _and_ composing tasks by string. In this situation, use named functions instead of string references.
+
+During migration, you may need the [forward reference registry][undertaker-forward-reference-external]. This will add an extra closure to every task reference and dramatically slow down your build. **Don't rely on this fix for very long**.
+
+## Avoid duplicating tasks
+
+When a composed operation is run, each task will be executed every time it was supplied.
+
+A `clean` task referenced in two different compositions would be run twice and lead to undesired results. Instead, refactor the `clean` task to be specified in the final composition.
+
+If you have code like this:
+```js
+// This is INCORRECT
+const { series, parallel } = require('gulp');
+
+const clean = function(cb) {
+ // body omitted
+ cb();
+};
+
+const css = series(clean, function(cb) {
+ // body omitted
+ cb();
+});
+
+const javascript = series(clean, function(cb) {
+ // body omitted
+ cb();
+});
+
+exports.build = parallel(css, javascript);
+```
+
+Migrate to this:
+```js
+const { series, parallel } = require('gulp');
+
+function clean(cb) {
+ // body omitted
+ cb();
+}
+
+function css(cb) {
+ // body omitted
+ cb();
+}
+
+function javascript(cb) {
+ // body omitted
+ cb();
+}
+
+exports.build = series(clean, parallel(css, javascript));
+```
+
+[undertaker-forward-reference-external]: https://github.com/gulpjs/undertaker-forward-reference
diff --git a/docs/api/registry.md b/docs/api/registry.md
new file mode 100644
index 00000000..d1b2123d
--- /dev/null
+++ b/docs/api/registry.md
@@ -0,0 +1,82 @@
+
+
+# registry()
+
+Allows custom registries to be plugged into the task system, which can provide shared tasks or augmented functionality.
+
+**Note:** Only tasks registered with `task()` will be provided to the custom registry. The task functions passed directly to `series()` or `parallel()` will not be provided - if you need to customize the registry behavior, compose tasks with string references.
+
+When assigning a new registry, each task from the current registry will be transferred and the current registry will be replaced with the new one. This allows for adding multiple custom registries in sequential order.
+
+See [Creating Custom Registries][creating-custom-registries] for details.
+
+## Usage
+
+```js
+const { registry, task, series } = require('gulp');
+const FwdRef = require('undertaker-forward-reference');
+
+registry(FwdRef());
+
+task('default', series('forward-ref'));
+
+task('forward-ref', function(cb) {
+ // body omitted
+ cb();
+});
+```
+
+## Signature
+
+```js
+registry([registryInstance])
+```
+
+### Parameters
+
+| parameter | type | note |
+|:--------------:|:-----:|--------|
+| registryInstance | object | An instance - not the class - of a custom registry. |
+
+### Returns
+
+If a `registryInstance` is passed, nothing will be returned. If no arguments are passed, returns the current registry instance.
+
+### Errors
+
+#### Incorrect parameter
+
+When a constructor (instead of an instance) is passed as `registryInstance`, throws an error with the message:
+
+> Custom registries must be instantiated, but it looks like you passed a constructor.
+
+#### Missing `get` method
+
+When a registry without a `get` method is passed as `registryInstance`, throws an error with the message:
+
+> Custom registry must have `get` function.
+
+#### Missing `set` method
+
+When a registry without a `set` method is passed as `registryInstance`, throws an error with the message:
+
+> Custom registry must have `set` function.
+
+#### Missing `init` method
+
+When a registry without an `init` method is passed as `registryInstance`, throws an error with the message:
+
+> Custom registry must have `init` function"
+
+#### Missing `tasks` method
+
+When a registry without a `tasks` method is passed as `registryInstance`, throws an error with the message:
+
+> Custom registry must have `tasks` function.
+
+[creating-custom-registries]: ../advanced/creating-custom-registries.md
diff --git a/docs/api/series.md b/docs/api/series.md
new file mode 100644
index 00000000..a0495175
--- /dev/null
+++ b/docs/api/series.md
@@ -0,0 +1,113 @@
+
+
+# series()
+
+Combines task functions and/or composed operations into larger operations that will be executed one after another, in sequential order. There are no imposed limits on the nesting depth of composed operations using `series()` and `parallel()`.
+
+## Usage
+
+```js
+const { series } = require('gulp');
+
+function javascript(cb) {
+ // body omitted
+ cb();
+}
+
+function css(cb) {
+ // body omitted
+ cb();
+}
+
+exports.build = series(javascript, css);
+```
+
+## Signature
+
+```js
+series(...tasks)
+```
+
+### Parameters
+
+| parameter | type | note |
+|:--------------:|:------:|-------|
+| tasks
**(required)** | function
string | Any number of task functions can be passed as individual arguments. Strings can be used if you've registered tasks previously, but this is not recommended. |
+
+### Returns
+
+A composed operation to be registered as a task or nested within other `series` and/or `parallel` compositions.
+
+When the composed operation is executed, all tasks will be run sequentially. If an error occurs in one task, no subsequent tasks will be run.
+
+### Errors
+
+When no tasks are passed, throws an error with the message, "One or more tasks should be combined using series or parallel".
+
+When invalid tasks or unregistered tasks are passed, throws an error with the message, "Task never defined".
+
+## Forward references
+
+A forward reference is when you compose tasks, using string references, that haven't been registered yet. This was a common practice in older versions, but this feature was removed to achieve faster task runtime and promote the use of named functions.
+
+In newer versions, you'll get an error, with the message "Task never defined", if you try to use forward references. You may experience this when trying to use `exports` for your task registration *and* composing tasks by string. In this situation, use named functions instead of string references.
+
+During migration, you may need to use the [forward reference registry][undertaker-forward-reference-external]. This will add an extra closure to every task reference and dramatically slow down your build. **Don't rely on this fix for very long**.
+
+## Avoid duplicating tasks
+
+When a composed operation is run, each task will be executed every time it was supplied.
+
+A `clean` task referenced in two different compositions would be run twice and lead to undesired results. Instead, refactor the `clean` task to be specified in the final composition.
+
+If you have code like this:
+```js
+// This is INCORRECT
+const { series, parallel } = require('gulp');
+
+const clean = function(cb) {
+ // body omitted
+ cb();
+};
+
+const css = series(clean, function(cb) {
+ // body omitted
+ cb();
+});
+
+const javascript = series(clean, function(cb) {
+ // body omitted
+ cb();
+});
+
+exports.build = parallel(css, javascript);
+```
+
+Migrate to this:
+```js
+const { series, parallel } = require('gulp');
+
+function clean(cb) {
+ // body omitted
+ cb();
+}
+
+function css(cb) {
+ // body omitted
+ cb();
+}
+
+function javascript(cb) {
+ // body omitted
+ cb();
+}
+
+exports.build = series(clean, parallel(css, javascript));
+```
+
+[undertaker-forward-reference-external]: https://github.com/gulpjs/undertaker-forward-reference
diff --git a/docs/api/src.md b/docs/api/src.md
new file mode 100644
index 00000000..a4d9105e
--- /dev/null
+++ b/docs/api/src.md
@@ -0,0 +1,112 @@
+
+
+# src()
+
+Creates a stream for reading [Vinyl][vinyl-concepts] objects from the file system.
+
+**Note:** BOMs (byte order marks) have no purpose in UTF-8 and will be removed from UTF-8 files read by `src()`, unless disabled using the `removeBOM` option.
+
+## Usage
+
+```javascript
+const { src, dest } = require('gulp');
+
+function copy() {
+ return src('input/*.js')
+ .pipe(dest('output/'));
+}
+
+exports.copy = copy;
+```
+
+
+## Signature
+
+```js
+src(globs, [options])
+```
+
+### Parameters
+
+| parameter | type | note |
+|:--------------:|:------:|-------|
+| globs | string
array | [Globs][globs-concepts] to watch on the file system. |
+| options | object | Detailed in [Options][options-section] below. |
+
+### Returns
+
+A stream that can be used at the beginning or in the middle of a pipeline to add files based on the given globs.
+
+### Errors
+
+When the `globs` argument can only match one file (such as `foo/bar.js`) and no match is found, throws an error with the message, "File not found with singular glob". To suppress this error, set the `allowEmpty` option to `true`.
+
+When an invalid glob is given in `globs`, throws an error with the message, "Invalid glob argument".
+
+### Options
+
+**For options that accept a function, the passed function will be called with each Vinyl object and must return a value of another listed type.**
+
+
+| name | type | default | note |
+|:--------:|:------:|------------|--------|
+| encoding | string
boolean | "utf8" | When false, file contents are treated as binary. When a string, this is used as the text encoding. |
+| buffer | boolean
function | true | When true, file contents are buffered into memory. If false, the Vinyl object's `contents` property will be a paused stream. It may not be possible to buffer the contents of large files.
**Note:** Plugins may not implement support for streaming contents. |
+| read | boolean
function | true | If false, files will be not be read and their Vinyl objects won't be writable to disk via `.dest()`. |
+| since | date
timestamp
function | | When set, only creates Vinyl objects for files modified since the specified time. |
+| removeBOM | boolean
function | true | When true, removes the BOM from UTF-8 encoded files. If false, ignores a BOM. |
+| sourcemaps | boolean
function | false | If true, enables [sourcemaps][sourcemaps-section] support on Vinyl objects created. Loads inline sourcemaps and resolves external sourcemap links. |
+| resolveSymlinks | boolean
function | true | When true, recursively resolves symbolic links to their targets. If false, preserves the symbolic links and sets the Vinyl object's `symlink` property to the original file's path. |
+| cwd | string | `process.cwd()` | The directory that will be combined with any relative path to form an absolute path. Is ignored for absolute paths. Use to avoid combining `globs` with `path.join()`.
_This option is passed directly to [glob-stream][glob-stream-external]._ |
+| base | string | | Explicitly set the `base` property on created Vinyl objects. Detailed in [API Concepts][glob-base-concepts].
_This option is passed directly to [glob-stream][glob-stream-external]._ |
+| cwdbase | boolean | false | If true, `cwd` and `base` options should be aligned.
_This option is passed directly to [glob-stream][glob-stream-external]._ |
+| root | string | | The root path that `globs` are resolved against.
_This option is passed directly to [glob-stream][glob-stream-external]._ |
+| allowEmpty | boolean | false | When false, `globs` which can only match one file (such as `foo/bar.js`) causes an error to be thrown if they don't find a match. If true, suppresses glob failures.
_This option is passed directly to [glob-stream][glob-stream-external]._ |
+| uniqueBy | string
function | `'path'` | Remove duplicates from the stream by comparing the string property name or the result of the function.
**Note:** When using a function, the function receives the streamed data (objects containing `cwd`, `base`, `path` properties). |
+| dot | boolean | false | If true, compare globs against dot files, like `.gitignore`.
_This option is passed directly to [anymatch][anymatch-external]._ |
+| nounique | boolean | false | When false, prevents duplicate files in the result set.
_This option is passed directly to [anymatch][anymatch-external]._ |
+| debug | boolean | false | If true, debugging information will be logged to the command line.
_This option is passed directly to [anymatch][anymatch-external]._ |
+| nobrace | boolean | false | If true, avoids expanding brace sets - e.g. `{a,b}` or `{1..3}`.
_This option is passed directly to [anymatch][anymatch-external]._ |
+| noglobstar | boolean | false | If true, treats double-star glob character as single-star glob character.
_This option is passed directly to [anymatch][anymatch-external]._ |
+| noext | boolean | false | If true, avoids matching [extglob][extglob-docs] patterns - e.g. `+(ab)`.
_This option is passed directly to [anymatch][anymatch-external]._ |
+| nocase | boolean | false | If true, performs a case-insensitive match.
**Note:** On case-insensitive file systems, non-magic patterns will match by default.
_This option is passed directly to [anymatch][anymatch-external]._ |
+| matchBase | boolean | false | If true and globs don't contain any `/` characters, traverses all directories and matches that glob - e.g. `*.js` would be treated as equivalent to `**/*.js`.
_This option is passed directly to [anymatch][anymatch-external]._ |
+| ignore | string
array | | Globs to exclude from matches. This option is combined with negated `globs`.
**Note:** These globs are always matched against dot files, regardless of any other settings.
_This option is passed directly to [anymatch][anymatch-external]._ |
+
+## Sourcemaps
+
+Sourcemap support is built directly into `src()` and `dest()`, but is disabled by default. Enable it to produce inline or external sourcemaps.
+
+Inline sourcemaps:
+```js
+const { src, dest } = require('gulp');
+const uglify = require('gulp-uglify');
+
+src('input/**/*.js', { sourcemaps: true })
+ .pipe(uglify())
+ .pipe(dest('output/', { sourcemaps: true }));
+```
+
+External sourcemaps:
+```js
+const { src, dest } = require('gulp');
+const uglify = require('gulp-uglify');
+
+src('input/**/*.js', { sourcemaps: true })
+ .pipe(uglify())
+ .pipe(dest('output/', { sourcemaps: '.' }));
+```
+
+[sourcemaps-section]: #sourcemaps
+[options-section]: #options
+[vinyl-concepts]: ../api/concepts.md#vinyl
+[glob-base-concepts]: ../api/concepts.md#glob-base
+[globs-concepts]: ../api/concepts.md#globs
+[extglob-docs]: ../documentation-missing.md
+[anymatch-external]: https://github.com/micromatch/anymatch
+[glob-stream-external]: https://github.com/gulpjs/glob-stream
diff --git a/docs/api/symlink.md b/docs/api/symlink.md
new file mode 100644
index 00000000..4080a344
--- /dev/null
+++ b/docs/api/symlink.md
@@ -0,0 +1,88 @@
+
+
+# symlink()
+
+Creates a stream for linking [Vinyl][vinyl-concepts] objects to the file system.
+
+## Usage
+
+```js
+const { src, symlink } = require('gulp');
+
+function link() {
+ return src('input/*.js')
+ .pipe(symlink('output/'));
+}
+
+exports.link = link;
+```
+
+## Signature
+
+```js
+symlink(directory, [options])
+```
+
+### Parameters
+
+| parameter | type | note |
+|:--------------:|:-----:|--------|
+| directory
**(required)** | string
function | The path of the output directory where symbolic links will be created. If a function is used, the function will be called with each Vinyl object and must return a string directory path. |
+| options | object | Detailed in [Options][options-section] below. |
+
+### Returns
+
+A stream that can be used in the middle or at the end of a pipeline to create symbolic links on the file system.
+Whenever a Vinyl object is passed through the stream, it creates a symbolic link to the original file on the file system at the given directory.
+
+Whenever a symbolic link is created on the file system, the Vinyl object will be modified.
+* The `cwd`, `base`, and `path` properties will be updated to match the created symbolic link.
+* The `stat` property will be updated to match the symbolic link on the file system.
+* The `contents` property will be set to `null`.
+* The `symlink` property will be added or replaced with original path.
+
+**Note:** On Windows, directory links are created using junctions by default. The `useJunctions` option disables this behavior.
+
+
+### Errors
+
+When `directory` is an empty string, throws an error with the message, "Invalid symlink() folder argument. Please specify a non-empty string or a function."
+
+When `directory` is not a string or function, throws an error with the message, "Invalid symlink() folder argument. Please specify a non-empty string or a function."
+
+When `directory` is a function that returns an empty string or `undefined`, emits an error with the message, "Invalid output folder".
+
+### Options
+
+**For options that accept a function, the passed function will be called with each Vinyl object and must return a value of another listed type.**
+
+| name | type | default | note |
+|:-------:|:------:|-----------|-------|
+| cwd | string
function | `process.cwd()` |The directory that will be combined with any relative path to form an absolute path. Is ignored for absolute paths. Use to avoid combining `directory` with `path.join()`. |
+| dirMode | number
function | | The mode used when creating directories. If not set, the process' mode will be used. |
+| overwrite | boolean
function | true | When true, overwrites existing files with the same path. |
+| relativeSymlinks | boolean
function | false | When false, any symbolic links created will be absolute.
**Note**: Ignored if a junction is being created, as they must be absolute. |
+| useJunctions | boolean
function | true | This option is only relevant on Windows and ignored elsewhere. When true, creates directory symbolic link as a junction. Detailed in [Symbolic links on Windows][symbolic-links-section] below. |
+
+## Symbolic links on Windows
+
+When creating symbolic links on Windows, a `type` argument is passed to Node's `fs.symlink()` method which specifies the type of target being linked. The link type is set to:
+* `'file'` when the target is a regular file
+* `'junction'` when the target is a directory
+* `'dir'` when the target is a directory and the user disables the `useJunctions` option
+
+
+If you try to create a dangling (pointing to a non-existent target) link, the link type can't be determined automatically. In these cases, behavior will vary depending on whether the dangling link is being created via `symlink()` or via `dest()`.
+
+For dangling links created via `symlink()`, the incoming Vinyl object represents the target, so its stats will determine the desired link type. If `isDirectory()` returns false then a `'file'` link is created, otherwise a `'junction'` or `'dir'` link is created depending on the value of the `useJunctions` option.
+
+For dangling links created via `dest()`, the incoming Vinyl object represents the link - typically loaded from disk via `src(..., { resolveSymlinks: false })`. In this case, the link type can't be reasonably determined and defaults to using `'file'`. This may cause unexpected behavior when creating a dangling link to a directory. **Avoid this scenario.**
+
+[options-section]: #options
+[symbolic-links-section]: #symbolic-links-on-windows
+[vinyl-concepts]: ../api/concepts.md#vinyl
diff --git a/docs/api/task.md b/docs/api/task.md
new file mode 100644
index 00000000..de3c5462
--- /dev/null
+++ b/docs/api/task.md
@@ -0,0 +1,110 @@
+
+
+# task()
+
+**Reminder**: This API isn't the recommended pattern anymore - [export your tasks][creating-tasks-docs].
+
+Defines a task within the task system. The task can then be accessed from the command line and the `series()`, `parallel()`, and `lastRun()` APIs.
+
+## Usage
+
+Register a named function as a task:
+```js
+const { task } = require('gulp');
+
+function build(cb) {
+ // body omitted
+ cb();
+}
+
+task(build);
+```
+
+Register an anonymous function as a task:
+```js
+const { task } = require('gulp');
+
+task('build', function(cb) {
+ // body omitted
+ cb();
+});
+```
+
+Retrieve a task that has been registered previously:
+```js
+const { task } = require('gulp');
+
+task('build', function(cb) {
+ // body omitted
+ cb();
+});
+
+const build = task('build');
+```
+
+## Signature
+
+```js
+task([taskName], taskFunction)
+```
+
+### Parameters
+
+If the `taskName` is not provided, the task will be referenced by the `name` property of a named function or a user-defined `displayName` property. The `taskName` parameter must be used for anonymous functions missing a `displayName` property.
+
+Since any registered task can be run from the command line, avoid using spaces in task names.
+
+| parameter | type | note |
+|:--------------:|:------:|-------|
+| taskName | string | An alias for the task function within the task system. Not needed when using named functions for `taskFunction`. |
+| taskFunction
**(required)** | function | A [task function][task-concepts] or composed task - generated by `series()` and `parallel()`. Ideally a named function. [Task metadata][task-metadata-section] can be attached to provide extra information to the command line. |
+
+### Returns
+
+When registering a task, nothing is returned.
+
+When retrieving a task, a wrapped task (not the original function) registered as `taskName` will be returned. The wrapped task has an `unwrap()` method that will return the original function.
+
+### Errors
+
+When registering a task where `taskName` is missing and `taskFunction` is anonymous, will throw an error with the message, "Task name must be specified".
+
+## Task metadata
+
+| property | type | note |
+|:--------------:|:------:|-------|
+| name | string | A special property of named functions. Used to register the task.
**Note:** [`name`][function-name-external] is not writable; it cannot be set or changed. |
+| displayName | string | When attached to a `taskFunction` creates an alias for the task. If using characters that aren't allowed in function names, use this property. |
+| description | string | When attached to a `taskFunction` provides a description to be printed by the command line when listing tasks. |
+| flags | object | When attached to a `taskFunction` provides flags to be printed by the command line when listing tasks. The keys of the object represent the flags and the values are their descriptions. |
+
+```js
+const { task } = require('gulp');
+
+const clean = function(cb) {
+ // body omitted
+ cb();
+};
+clean.displayName = 'clean:all';
+
+task(clean);
+
+function build(cb) {
+ // body omitted
+ cb();
+}
+build.description = 'Build the project';
+build.flags = { '-e': 'An example flag' };
+
+task(build);
+```
+
+[task-metadata-section]: #task-metadata
+[task-concepts]: ../api/concepts.md#tasks
+[creating-tasks-docs]: ../getting-started/3-creating-tasks.md
+[function-name-external]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/name
diff --git a/docs/api/tree.md b/docs/api/tree.md
new file mode 100644
index 00000000..1dcfed6d
--- /dev/null
+++ b/docs/api/tree.md
@@ -0,0 +1,178 @@
+
+
+# tree()
+
+Fetches the current task dependency tree - in the rare case that it is needed.
+
+Generally, `tree()` won't be used by gulp consumers, but it is exposed so the CLI can show the dependency graph of the tasks defined in a gulpfile.
+
+## Usage
+
+Example gulpfile:
+```js
+
+const { series, parallel } = require('gulp');
+
+function one(cb) {
+ // body omitted
+ cb();
+}
+
+function two(cb) {
+ // body omitted
+ cb();
+}
+
+function three(cb) {
+ // body omitted
+ cb();
+}
+
+const four = series(one, two);
+
+const five = series(four,
+ parallel(three, function(cb) {
+ // Body omitted
+ cb();
+ })
+);
+
+module.exports = { one, two, three, four, five };
+```
+
+Output for `tree()`:
+```js
+{
+ label: 'Tasks',
+ nodes: [ 'one', 'two', 'three', 'four', 'five' ]
+}
+```
+
+
+Output for `tree({ deep: true })`:
+```js
+{
+ label: "Tasks",
+ nodes: [
+ {
+ label: "one",
+ type: "task",
+ nodes: []
+ },
+ {
+ label: "two",
+ type: "task",
+ nodes: []
+ },
+ {
+ label: "three",
+ type: "task",
+ nodes: []
+ },
+ {
+ label: "four",
+ type: "task",
+ nodes: [
+ {
+ label: "",
+ type: "function",
+ branch: true,
+ nodes: [
+ {
+ label: "one",
+ type: "function",
+ nodes: []
+ },
+ {
+ label: "two",
+ type: "function",
+ nodes: []
+ }
+ ]
+ }
+ ]
+ },
+ {
+ label: "five",
+ type: "task",
+ nodes: [
+ {
+ label: "",
+ type: "function",
+ branch: true,
+ nodes: [
+ {
+ label: "",
+ type: "function",
+ branch: true,
+ nodes: [
+ {
+ label: "one",
+ type: "function",
+ nodes: []
+ },
+ {
+ label: "two",
+ type: "function",
+ nodes: []
+ }
+ ]
+ },
+ {
+ label: "",
+ type: "function",
+ branch: true,
+ nodes: [
+ {
+ label: "three",
+ type: "function",
+ nodes: []
+ },
+ {
+ label: "",
+ type: "function",
+ nodes: []
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ }
+ ]
+}
+```
+
+## Signature
+
+```js
+tree([options])
+```
+
+### Parameters
+
+| parameter | type | note |
+|:--------------:|------:|--------|
+| options | object | Detailed in [Options][options-section] below. |
+
+### Returns
+
+An object detailing the tree of registered tasks - containing nested objects with `'label'` and `'nodes'` properties (which is [archy][archy-external] compatible).
+
+Each object may have a `type` property that can be used to determine if the node is a `task` or `function`.
+
+Each object may have a `branch` property that, when `true`, indicates the node was created using `series()` or `parallel()`.
+
+### Options
+
+| name | type | default | note |
+|:-------:|:-------:|------------|--------|
+| deep | boolean | false | If true, the entire tree will be returned. When false, only top level tasks will be returned. |
+
+[options-section]: #options
+[archy-external]: https://www.npmjs.com/package/archy
diff --git a/docs/api/vinyl-iscustomprop.md b/docs/api/vinyl-iscustomprop.md
new file mode 100644
index 00000000..b2f6accf
--- /dev/null
+++ b/docs/api/vinyl-iscustomprop.md
@@ -0,0 +1,68 @@
+
+
+# Vinyl.isCustomProp()
+
+Determines if a property is internally managed by Vinyl. Used by Vinyl when setting values inside the constructor or when copying properties in the `clone()` instance method.
+
+This method is useful when extending the Vinyl class. Detailed in [Extending Vinyl][extending-vinyl-section] below.
+
+## Usage
+
+```js
+const Vinyl = require('vinyl');
+
+Vinyl.isCustomProp('sourceMap') === true;
+Vinyl.isCustomProp('path') === false;
+```
+
+## Signature
+
+```js
+Vinyl.isCustomProp(property)
+```
+
+### Parameters
+
+| parameter | type | note |
+|:--------------:|:------:|-------|
+| property | string | The property name to check. |
+
+### Returns
+
+True if the property is not internally managed.
+
+## Extending Vinyl
+
+When custom properties are managed internally, the static `isCustomProp` method must be extended and return false when one of the custom properties is queried.
+
+```js
+const Vinyl = require('vinyl');
+
+const builtInProps = ['foo', '_foo'];
+
+class SuperFile extends Vinyl {
+ constructor(options) {
+ super(options);
+ this._foo = 'example internal read-only value';
+ }
+
+ get foo() {
+ return this._foo;
+ }
+
+ static isCustomProp(name) {
+ return super.isCustomProp(name) && builtInProps.indexOf(name) === -1;
+ }
+}
+```
+
+In the example above, `foo` and `_foo` will not be assigned to the new object when cloning or passed in `options` to `new SuperFile(options)`.
+
+If your custom properties or logic require special handling during cloning, override the `clone` method while extending Vinyl.
+
+[extending-vinyl-section]: #extending-vinyl
diff --git a/docs/api/vinyl-isvinyl.md b/docs/api/vinyl-isvinyl.md
new file mode 100644
index 00000000..ed1d7d92
--- /dev/null
+++ b/docs/api/vinyl-isvinyl.md
@@ -0,0 +1,41 @@
+
+
+# Vinyl.isVinyl()
+
+Determines if an object is a Vinyl instance. Use this method instead of `instanceof`.
+
+**Note**: This method uses an internal property that some older versions of Vinyl didn't expose resulting in a false negative if using an outdated version.
+
+## Usage
+
+```js
+const Vinyl = require('vinyl');
+
+const file = new Vinyl();
+const notAFile = {};
+
+Vinyl.isVinyl(file) === true;
+Vinyl.isVinyl(notAFile) === false;
+```
+
+## Signature
+
+```js
+Vinyl.isVinyl(file);
+```
+
+### Parameters
+
+| parameter | type | note |
+|:--------------:|:------:|-------|
+| file | object | The object to check. |
+
+### Returns
+
+True if the `file` object is a Vinyl instance.
+
diff --git a/docs/api/vinyl.md b/docs/api/vinyl.md
new file mode 100644
index 00000000..da8418f7
--- /dev/null
+++ b/docs/api/vinyl.md
@@ -0,0 +1,141 @@
+
+
+# Vinyl
+
+A virtual file format. When a file is read by `src()`, a Vinyl object is generated to represent the file - including the path, contents, and other metadata.
+
+Vinyl objects can have transformations applied using [plugins][using-plugins-docs]. They may also be persisted to the file system using `dest()`.
+
+When creating your own Vinyl objects - instead of generating with `src()` - use the external `vinyl` module, as shown in Usage below.
+
+## Usage
+
+```js
+const Vinyl = require('vinyl');
+
+const file = new Vinyl({
+ cwd: '/',
+ base: '/test/',
+ path: '/test/file.js',
+ contents: new Buffer('var x = 123')
+});
+
+file.relative === 'file.js';
+
+file.dirname === '/test';
+file.dirname = '/specs';
+file.path === '/specs/file.js';
+
+file.basename === 'file.js';
+file.basename = 'file.txt';
+file.path === '/specs/file.txt';
+
+file.stem === 'file';
+file.stem = 'foo';
+file.path === '/specs/foo.txt';
+file.extname === '.txt';
+file.extname = '.js';
+file.path === '/specs/foo.js';
+```
+
+## Signature
+
+```js
+new Vinyl([options])
+```
+
+### Parameters
+
+| parameter | type | note |
+|:--------------:|:------:|-------|
+| options | object | Detailed in [Options][options-section] below. |
+
+### Returns
+
+An instance of the Vinyl class representing a single virtual file, detailed in [Vinyl instance][vinyl-instance-section] below.
+
+### Errors
+
+When any passed options don't conform to the [instance property definitions][instance-properties-section] (like if `path` is set to a number) throws as defined in the table.
+
+### Options
+
+| name | type | default | note |
+|:-------:|:------:|-----------|--------|
+| cwd | string | `process.cwd()` | The directory from which relative paths will be derived. Will be [normalized][normalization-and-concatenation-section] and have trailing separators removed. |
+| base | string | | Used to calculate the `relative` instance property. Falls back to the value of `cwd` if not set. Typically set to the [glob base][glob-base-concepts]. Will be [normalized][normalization-and-concatenation-section] and have trailing separators removed.|
+| path | string | | The full, absolute file path. Will be [normalized][normalization-and-concatenation-section] and have trailing separators removed. |
+| history | array | `[ ]` | An array of paths to pre-populate the `history` of a Vinyl instance. Usually comes from deriving a new Vinyl object from a previous Vinyl object. If `path` and `history` are both passed, `path` is appended to `history`. Each item will be [normalized][normalization-and-concatenation-section] and have trailing separators removed. |
+| stat | object | | An instance of `fs.Stats`, usually the result of calling `fs.stat()` on a file. Used to determine if a Vinyl object represents a directory or symbolic link. |
+| contents | ReadableStream
Buffer
`null` | `null` | The contents of the file. If `contents` is a ReadableStream, it is wrapped in a [cloneable-readable][cloneable-readable-external] stream. |
+
+Any other properties on `options` will be directly assigned to the Vinyl instance.
+
+```js
+const Vinyl = require('vinyl');
+
+const file = new Vinyl({ foo: 'bar' });
+file.foo === 'bar';
+```
+
+## Vinyl instance
+
+Each instance of a Vinyl object will have properties and methods to access and/or modify information about the virtual file.
+
+### Instance properties
+
+All internally managed paths - any instance property except `contents` and `stat` - are normalized and have trailing separators removed. See [Normalization and concatenation][normalization-and-concatenation-section] for more information.
+
+| property | type | description | throws |
+|:-----------:|:------:|----------------|----------|
+| contents | ReadableStream
Buffer
`null` | Gets and sets the contents of the virtual file. If set to a ReadableStream, it is wrapped in a [cloneable-readable][cloneable-readable-external] stream. | If set to any value other than a ReadableStream, a Buffer, or `null`. |
+| stat | object | Gets and sets an instance of [`fs.Stats`][fs-stats-concepts]. Used when determining if a Vinyl object represents a directory or symbolic link. | |
+| cwd | string | Gets and sets the current working directory. Used for deriving relative paths. | If set to an empty string or any non-string value. |
+| base | string | Gets and sets the base directory. Used to calculate the `relative` instance property. On a Vinyl object generated by `src()` will be set to the [glob base][glob-base-concepts]. If set to `null` or `undefined`, falls back to the value of the `cwd` instance property. | If set to an empty string or any non-string value (except `null` or `undefined`). |
+| path | string | Gets and sets the full, absolute file path. Setting to a value different from the current `path` appends the new path to the `history` instance property. | If set to any non-string value. |
+| history | array | Array of all `path` values the Vinyl object has been assigned. The first element is the original path and the last element is the current path. This property and its elements should be treated as read-only and only altered indirectly by setting the `path` instance property. | |
+| relative | string | Gets the relative path segment between the `base` and the `path` instance properties. | If set to any value. If accessed when `path` is not available. |
+| dirname | string | Gets and sets the directory of the `path` instance property. | If accessed when `path` is not available. |
+| stem | string | Gets and sets the stem (filename without extension) of the `path` instance property. | If accessed when `path` is not available. |
+| extname | string | Gets and sets the extension of the `path` instance property. | If accessed when `path` is not available. |
+| basename | string | Gets and sets the filename (`stem + extname`) of the `path` instance property. | If accessed when `path` is not available. |
+| symlink | string | Gets and sets the reference path of a symbolic link. | If set to any non-string value. |
+
+### Instance methods
+
+| method | return type | returns |
+|:----------:|:--------------:|--------|
+| `isBuffer()` | boolean | If the `contents` instance property is a Buffer, returns true. |
+| `isStream()` | boolean | If the `contents` instance property is a Stream, returns true. |
+| `isNull()` | boolean | If the `contents` instance property is `null`, returns true. |
+| `isDirectory()` | boolean | If the instance represents a directory, returns true. An instance is considered a directory when `isNull()` returns true, the `stat` instance property is an object, and `stat.isDirectory()` returns true. This assumes a Vinyl object was constructed with a valid (or properly mocked) `fs.Stats` object. |
+| `isSymbolic()` | boolean | If the instance represents a symbolic link, returns true. An instance is considered symbolic when `isNull()` returns true, the `stat` instance property is an object, and `stat.isSymbolicLink()` returns true. This assumes a Vinyl object was constructed with a valid (or properly mocked) `fs.Stats` object. |
+| `clone([options])` | object | A new Vinyl object with all properties cloned. By default custom properties are deep cloned. If the `deep` option is false, custom attributes will be shallow cloned. If the `contents` option is false and the `contents` instance property is a Buffer, the Buffer will be reused instead of cloned. |
+| `inspect()` | string | Returns a formatted interpretation of the Vinyl object. Automatically called by Node's console.log. |
+
+## Normalization and concatenation
+
+All path properties are normalized by their setters. Concatenate paths with `/`, instead of using `path.join()`, and normalization will occur properly on all platforms. Never concatenate with `\` - it is a valid filename character on POSIX system.
+
+```js
+const file = new File();
+file.path = '/' + 'test' + '/' + 'foo.bar';
+
+console.log(file.path);
+// posix => /test/foo.bar
+// win32 => \\test\\foo.bar
+```
+
+[options-section]: #options
+[vinyl-instance-section]: #vinyl-instance
+[instance-properties-section]: #instance-properties
+[normalization-and-concatenation-section]: #normalization-and-concatenation
+[glob-base-concepts]: ../api/concepts.md#glob-base
+[fs-stats-concepts]: ../api/concepts.md#file-system-stats
+[using-plugins-docs]: ../getting-started/7-using-plugins.md
+[cloneable-readable-external]: https://github.com/mcollina/cloneable-readable
diff --git a/docs/api/watch.md b/docs/api/watch.md
new file mode 100644
index 00000000..bc9d601e
--- /dev/null
+++ b/docs/api/watch.md
@@ -0,0 +1,137 @@
+
+
+# watch()
+
+Allows watching globs and running a task when a change occurs. Tasks are handled uniformly with the rest of the task system.
+
+## Usage
+
+```js
+const { watch } = require('gulp');
+
+watch(['input/*.js', '!input/something.js'], function(cb) {
+ // body omitted
+ cb();
+});
+```
+
+## Signature
+
+```js
+watch(globs, [options], [task])
+```
+
+### Parameters
+
+| parameter | type | note |
+|:--------------:|:-----:|--------|
+| globs
**(required)** | string
array | [Globs][globs-concepts] to watch on the file system. |
+| options | object | Detailed in [Options][options-section] below. |
+| task | function
string | A [task function][tasks-concepts] or composed task - generated by `series()` and `parallel()`. |
+
+### Returns
+
+An instance of [chokidar][chokidar-instance-section] for fine-grained control over your watch setup.
+
+### Errors
+
+When a non-string or array with any non-strings is passed as `globs`, throws an error with the message, "Non-string provided as watch path".
+
+When a string or array is passed as `task`, throws an error with the message, "watch task has to be a function (optionally generated by using gulp.parallel or gulp.series)".
+
+### Options
+
+| name | type | default | note |
+|:-------:|:------:|-----------|--------|
+| ignoreInitial | boolean | true | If false, the task is called during instantiation as file paths are discovered. Use to trigger the task during startup.
**Note:** This option is passed to [chokidar][chokidar-external] but is defaulted to `true` instead of `false`. |
+| delay | number | 200 | The millisecond delay between a file change and task execution. Allows for waiting on many changes before executing a task, e.g. find-and-replace on many files. |
+| queue | boolean | true | When true and the task is already running, any file changes will queue a single task execution. Keeps long running tasks from overlapping. |
+| events | string
array | [ 'add',
'change',
'unlink' ] | The events being watched to trigger task execution. Can be `'add'`, `'addDir'`, `'change'`, `'unlink'`, `'unlinkDir'`, `'ready'`, and/or `'error'`. Additionally `'all'` is available, which represents all events other than `'ready'` and `'error'`.
_This option is passed directly to [chokidar][chokidar-external]._ |
+| persistent | boolean | true | If false, the watcher will not keep the Node process running. Disabling this option is not recommended.
_This option is passed directly to [chokidar][chokidar-external]._ |
+| ignored | array
string
RegExp
function | | Defines globs to be ignored. If a function is provided, it will be called twice per path - once with just the path, then with the path and the `fs.Stats` object of that file.
_This option is passed directly to [chokidar][chokidar-external]._ |
+| followSymlinks | boolean | true | When true, changes to both symbolic links and the linked files trigger events. If false, only changes to the symbolic links trigger events.
_This option is passed directly to [chokidar][chokidar-external]._ |
+| cwd | string | | The directory that will be combined with any relative path to form an absolute path. Is ignored for absolute paths. Use to avoid combining `globs` with `path.join()`.
_This option is passed directly to [chokidar][chokidar-external]._ |
+| disableGlobbing | boolean | false | If true, all `globs` are treated as literal path names, even if they have special characters.
_This option is passed directly to [chokidar][chokidar-external]._ |
+| usePolling | boolean | false | When false, the watcher will use `fs.watch()` (or [fsevents][fsevents-external] on Mac) for watching. If true, use `fs.watchFile()` polling instead - needed for successfully watching files over a network or other non-standard situations. Overrides the `useFsEvents` default.
_This option is passed directly to [chokidar][chokidar-external]._ |
+| interval | number | 100 | Combine with `usePolling: true`. Interval of file system polling.
_This option is passed directly to [chokidar][chokidar-external]._ |
+| binaryInterval | number | 300 | Combine with `usePolling: true`. Interval of file system polling for binary files.
_This option is passed directly to [chokidar][chokidar-external]._ |
+| useFsEvents | boolean | true | When true, uses fsevents for watching if available. If explicitly set to true, supersedes the `usePolling` option. If set to false, automatically sets `usePolling` to true.
_This option is passed directly to [chokidar][chokidar-external]._ |
+| alwaysStat | boolean | false | If true, always calls `fs.stat()` on changed files - will slow down file watcher. The `fs.Stat` object is only available if you are using the chokidar instance directly.
_This option is passed directly to [chokidar][chokidar-external]._ |
+| depth | number | | Indicates how many nested levels of directories will be watched.
_This option is passed directly to [chokidar][chokidar-external]._ |
+| awaitWriteFinish | boolean | false | Do not use this option, use `delay` instead.
_This option is passed directly to [chokidar][chokidar-external]._ |
+| ignorePermissionErrors | boolean | false | Set to true to watch files that don't have read permissions. Then, if watching fails due to EPERM or EACCES errors, they will be skipped silently.
_This option is passed directly to [chokidar][chokidar-external]._ |
+| atomic | number | 100 | Only active if `useFsEvents` and `usePolling` are false. Automatically filters out artifacts that occur from "atomic writes" by some editors. If a file is re-added within the specified milliseconds of being deleted, a change event - instead of unlink then add - will be emitted.
_This option is passed directly to [chokidar][chokidar-external]._ |
+
+## Chokidar instance
+
+The `watch()` method returns the underlying instance of [chokidar][chokidar-external], providing fine-grained control over your watch setup. Most commonly used to register individual event handlers that provide the `path` or `stats` of the changed files.
+
+**When using the chokidar instance directly, you will not have access to the task system integrations, including async completion, queueing, and delay.**
+
+```js
+const { watch } = require('gulp');
+
+const watcher = watch(['input/*.js']);
+
+watcher.on('change', function(path, stats) {
+ console.log(`File ${path} was changed`);
+});
+
+watcher.on('add', function(path, stats) {
+ console.log(`File ${path} was added`);
+});
+
+watcher.on('unlink', function(path, stats) {
+ console.log(`File ${path} was removed`);
+});
+
+watcher.close();
+```
+
+
+`watcher.on(eventName, eventHandler)`
+
+Registers `eventHandler` functions to be called when the specified event occurs.
+
+| parameter | type | note |
+|:--------------:|:-----:|--------|
+| eventName | string | The events that may be watched are `'add'`, `'addDir'`, `'change'`, `'unlink'`, `'unlinkDir'`, `'ready'`, `'error'`, or `'all'`. |
+| eventHandler | function | Function to be called when the specified event occurs. Arguments detailed in the table below. |
+
+| argument | type | note |
+|:-------------:|:-----:|--------|
+| path | string | The path of the file that changed. If the `cwd` option was set, the path will be made relative by removing the `cwd`. |
+| stats | object | An [fs.Stat][fs-stats-concepts] object, but could be `undefined`. If the `alwaysStat` option was set to `true`, `stats` will always be provided. |
+
+`watcher.close()`
+
+Shuts down the file watcher. Once shut down, no more events will be emitted.
+
+`watcher.add(globs)`
+
+Adds additional globs to an already-running watcher instance.
+
+| parameter | type | note |
+|:-------------:|:-----:|--------|
+| globs | string
array | The additional globs to be watched. |
+
+`watcher.unwatch(globs)`
+
+Removes globs that are being watched, while the watcher continues with the remaining paths.
+
+| parameter | type | note |
+|:-------------:|:-----:|--------|
+| globs | string
array | The globs to be removed. |
+
+[chokidar-instance-section]: #chokidar-instance
+[options-section]: #options
+[tasks-concepts]: ../api/concepts.md#tasks
+[globs-concepts]: ../api/concepts.md#globs
+[fs-stats-concepts]: ../api/concepts.md#file-system-stats
+[chokidar-external]: https://github.com/paulmillr/chokidar
+[fsevents-external]: https://github.com/strongloop/fsevents
diff --git a/docs/documentation-missing.md b/docs/documentation-missing.md
new file mode 100644
index 00000000..12473028
--- /dev/null
+++ b/docs/documentation-missing.md
@@ -0,0 +1,11 @@
+
+
+# Excuse our dust!
+
+We're in the process of rewriting **all** our documentation and some of the links we've added to completed docs haven't been written yet. You've likely clicked on one of those to end up here. We're sorry about that but please check back later on the topic you're interested in. If you want to help out, we'll happily accept a Pull Request for this missing documentation.
+
+-The Gulp Team
diff --git a/docs/getting-started.md b/docs/getting-started.md
index 4ebaa5ab..c3ec6e46 100644
--- a/docs/getting-started.md
+++ b/docs/getting-started.md
@@ -1,70 +1,5 @@
-# Getting Started
+## This documentation has moved!
-*If you've previously installed gulp globally, run `npm rm --global gulp` before following these instructions.* For more information, read this [Sip](https://medium.com/gulpjs/gulp-sips-command-line-interface-e53411d4467).
+You can find the new documentation in our [Quick Start](getting-started/1-quick-start.md) guide.
-#### Check for Node and npm
-Make sure that you've installed Node and npm before attempting to install gulp.
-
-```sh
-node --version
-```
-```sh
-npm --version
-```
-
-#### Install the `gulp` command
-
-```sh
-npm install --global gulp-cli
-```
-
-#### Create a `package.json`
-If you don't have a package.json, create one. If you need help, run an `npm init` which will walk you through giving it a name, version, description, etc.
-
-
-#### Install `gulp` in your devDependencies
-
-Run this command in your project directory:
-
-```sh
-npm install --save-dev gulp
-```
-
-#### Create a `gulpfile`
-
-In your project directory, create a file named `gulpfile.js` in your project root with these contents:
-
-```js
-var gulp = require('gulp');
-
-gulp.task('default', function() {
- // place code for your default task here
-});
-```
-
-#### Test it out
-
-Run the gulp command in your project directory:
-
-```sh
-gulp
-```
-
-To run multiple tasks, you can use `gulp `.
-
-#### Result
-
-Voila! The default task will run and do nothing.
-
-```sh
-Using gulpfile ~/my-project/gulpfile.js
-[11:15:51] Starting 'default'...
-[11:15:51] Finished 'default' after 103 μs
-```
-
-## Where do I go now?
-
-- [API Documentation](API.md) - The programming interface, defined
-- [Recipes](recipes) - Specific examples from the community
-- [In Depth Help](https://travismaynard.com/writing/getting-started-with-gulp) - A tutorial from the the guy who wrote the book
-- [Plugins](http://gulpjs.com/plugins/) - Building blocks for your gulp file
+While you are there, check out our expanded [Getting Started](getting-started/) documentation.
diff --git a/docs/getting-started/1-quick-start.md b/docs/getting-started/1-quick-start.md
new file mode 100644
index 00000000..6953e7f5
--- /dev/null
+++ b/docs/getting-started/1-quick-start.md
@@ -0,0 +1,92 @@
+
+
+# Quick Start
+
+If you've previously installed gulp globally, run `npm rm --global gulp` before following these instructions. For more information, read this [Sip][sip-article].
+
+## Check for node, npm, and npx
+```sh
+node --version
+```
+![Output: v8.11.1][img-node-version-command]
+```sh
+npm --version
+```
+![Output: 5.6.0][img-npm-version-command]
+```sh
+npx --version
+```
+![Output: 9.7.1][img-npx-version-command]
+
+If they are not installed, follow the instructions [here][node-install].
+
+## Install the gulp command line utility
+```sh
+npm install --global gulp-cli
+```
+
+
+## Create a project directory and navigate into it
+```sh
+npx mkdirp my-project
+```
+```sh
+cd my-project
+```
+
+## Create a package.json file in your project directory
+```sh
+npm init
+```
+
+This will guide you through giving your project a name, version, description, etc.
+
+## Install the gulp package in your devDependencies
+```sh
+npm install --save-dev gulp
+```
+
+## Verify your gulp versions
+
+```sh
+gulp --version
+```
+
+Ensure the output matches the screenshot below or you might need to restart the steps in this guide.
+
+![Output: CLI version 2.0.1 & Local version 4.0.0][img-gulp-version-command]
+
+## Create a gulpfile
+Using your text editor, create a file named gulpfile.js in your project root with these contents:
+```js
+function defaultTask(cb) {
+ // place code for your default task here
+ cb();
+}
+
+exports.default = defaultTask
+```
+
+## Test it
+Run the gulp command in your project directory:
+```sh
+gulp
+```
+To run multiple tasks, you can use `gulp `.
+
+## Result
+The default task will run and do nothing.
+![Output: Starting default & Finished default][img-gulp-command]
+
+[sip-article]: https://medium.com/gulpjs/gulp-sips-command-line-interface-e53411d4467
+[node-install]: https://nodejs.org/en/
+[img-node-version-command]: https://gulpjs.com/img/docs-node-version-command.png
+[img-npm-version-command]: https://gulpjs.com/img/docs-npm-version-command.png
+[img-npx-version-command]: https://gulpjs.com/img/docs-npx-version-command.png
+[img-gulp-version-command]: https://gulpjs.com/img/docs-gulp-version-command.png
+[img-gulp-command]: https://gulpjs.com/img/docs-gulp-command.png
diff --git a/docs/getting-started/2-javascript-and-gulpfiles.md b/docs/getting-started/2-javascript-and-gulpfiles.md
new file mode 100644
index 00000000..a15fad11
--- /dev/null
+++ b/docs/getting-started/2-javascript-and-gulpfiles.md
@@ -0,0 +1,38 @@
+
+
+# JavaScript and Gulpfiles
+
+Gulp allows you to use existing JavaScript knowledge to write gulpfiles or to use your experience with gulpfiles to write plain JavaScript. Although a few utilities are provided to simplify working with the filesystem and command line, everything else you write is pure JavaScript.
+
+## Gulpfile explained
+
+A gulpfile is a file in your project directory titled `gulpfile.js` (or capitalized as `Gulpfile.js`, like Makefile), that automatically loads when you run the `gulp` command. Within this file, you'll often see gulp APIs, like `src()`, `dest()`, `series()`, or `parallel()` but any vanilla JavaScript or Node modules can be used. Any exported functions will be registered into gulp's task system.
+
+## Transpilation
+
+You can write a gulpfile using a language that requires transpilation, like TypeScript or Babel, by changing the extension on your `gulpfile.js` to indicate the language and install the matching transpiler module.
+
+* For TypeScript, rename to `gulpfile.ts` and install the [ts-node][ts-node-module] module.
+* For Babel, rename to `gulpfile.babel.js` and install the [@babel/register][babel-register-module] module.
+
+__Most new versions of node support most features that TypeScript or Babel provide, except the `import`/`export` syntax. When only that syntax is desired, rename to `gulpfile.esm.js` and install the [esm][esm-module] module.__
+
+For a more advanced dive into this topic and the full list of supported extensions, see our [gulpfile transpilation][gulpfile-transpilation-advanced] documentation.
+
+## Splitting a gulpfile
+
+Many users start by adding all logic to a gulpfile. If it ever grows too big, it can be refactored into separate files.
+
+Each task can be split into its own file, then imported into your gulpfile for composition. Not only does this keep things organized, but it allows you to test each task independently or vary composition based on conditions.
+
+Node's module resolution allows you to replace your `gulpfile.js` file with a directory named `gulpfile.js` that contains an `index.js` file which is treated as a `gulpfile.js`. This directory could then contain your individual modules for tasks. If you are using a transpiler, name the folder and file accordingly.
+
+[gulpfile-transpilation-advanced]: ../documentation-missing.md
+[ts-node-module]: https://www.npmjs.com/package/ts-node
+[babel-register-module]: https://www.npmjs.com/package/@babel/register
+[esm-module]: https://www.npmjs.com/package/esm
diff --git a/docs/getting-started/3-creating-tasks.md b/docs/getting-started/3-creating-tasks.md
new file mode 100644
index 00000000..79f64487
--- /dev/null
+++ b/docs/getting-started/3-creating-tasks.md
@@ -0,0 +1,215 @@
+
+
+# Creating Tasks
+
+Each gulp task is an asynchronous JavaScript function - a function that accepts an error-first callback or returns a stream, promise, event emitter, child process, or observable ([more on that later][async-completion-docs]). Due to some platform limitations, synchronous tasks aren't supported, though there is a pretty nifty [alternative][using-async-await-docs].
+
+## Exporting
+
+Tasks can be considered **public** or **private**.
+
+* **Public tasks** are exported from your gulpfile, which allows them to be run by the `gulp` command.
+* **Private tasks** are made to be used internally, usually used as part of `series()` or `parallel()` composition.
+
+A private task looks and acts like any other task, but an end-user can't ever execute it independently. To register a task publicly, export it from your gulpfile.
+
+```js
+const { series } = require('gulp');
+
+// The `clean` function is not exported so it can be considered a private task.
+// It can still be used within the `series()` composition.
+function clean(cb) {
+ // body omitted
+ cb();
+}
+
+// The `build` function is exported so it is public and can be run with the `gulp` command.
+// It can also be used within the `series()` composition.
+function build(cb) {
+ // body omitted
+ cb();
+}
+
+exports.build = build;
+exports.default = series(clean, build);
+```
+
+![ALT TEXT MISSING][img-gulp-tasks-command]
+
+In the past, `task()` was used to register your functions as tasks. While that API is still available, exporting should be the primary registration mechanism, except in edge cases where exports won't work.
+
+## Compose tasks
+
+Gulp provides two powerful composition methods, `series()` and `parallel()`, allowing individual tasks to be composed into larger operations. Both methods accept any number of task functions or composed operations. `series()` and `parallel()` can be nested within themselves or each other to any depth.
+
+To have your tasks execute in order, use the `series()` method.
+```js
+const { series } = require('gulp');
+
+function transpile(cb) {
+ // body omitted
+ cb();
+}
+
+function bundle(cb) {
+ // body omitted
+ cb();
+}
+
+exports.build = series(transpile, bundle);
+```
+
+For tasks to run at maximum concurrency, combine them with the `parallel()` method.
+```js
+const { parallel } = require('gulp');
+
+function javascript(cb) {
+ // body omitted
+ cb();
+}
+
+function css(cb) {
+ // body omitted
+ cb();
+}
+
+exports.build = parallel(javascript, css);
+```
+
+Tasks are composed immediately when either `series()` or `parallel()` is called. This allows variation in the composition instead of conditional behavior inside individual tasks.
+
+```js
+const { series } = require('gulp');
+
+function minify(cb) {
+ // body omitted
+ cb();
+}
+
+
+function transpile(cb) {
+ // body omitted
+ cb();
+}
+
+function livereload(cb) {
+ // body omitted
+ cb();
+}
+
+if (process.env.NODE_ENV === 'production') {
+ exports.build = series(transpile, minify);
+} else {
+ exports.build = series(transpile, livereload);
+}
+```
+
+`series()` and `parallel()` can be nested to any arbitrary depth.
+
+```js
+const { series, parallel } = require('gulp');
+
+function clean(cb) {
+ // body omitted
+ cb();
+}
+
+function cssTranspile(cb) {
+ // body omitted
+ cb();
+}
+
+function cssMinify(cb) {
+ // body omitted
+ cb();
+}
+
+function jsTranspile(cb) {
+ // body omitted
+ cb();
+}
+
+function jsBundle(cb) {
+ // body omitted
+ cb();
+}
+
+function jsMinify(cb) {
+ // body omitted
+ cb();
+}
+
+function publish(cb) {
+ // body omitted
+ cb();
+}
+
+exports.build = series(
+ clean,
+ parallel(
+ cssTranspile,
+ series(jsTranspile, jsBundle)
+ ),
+ parallel(cssMinify, jsMinify),
+ publish
+);
+```
+
+When a composed operation is run, each task will be executed every time it was referenced. For example, a `clean` task referenced before two different tasks would be run twice and lead to undesired results. Instead, refactor the `clean` task to be specified in the final composition.
+
+If you have code like this:
+
+```js
+// This is INCORRECT
+const { series, parallel } = require('gulp');
+
+const clean = function(cb) {
+ // body omitted
+ cb();
+};
+
+const css = series(clean, function(cb) {
+ // body omitted
+ cb();
+});
+
+const javascript = series(clean, function(cb) {
+ // body omitted
+ cb();
+});
+
+exports.build = parallel(css, javascript);
+```
+
+Migrate to this:
+
+```js
+const { series, parallel } = require('gulp');
+
+function clean(cb) {
+ // body omitted
+ cb();
+}
+
+function css(cb) {
+ // body omitted
+ cb();
+}
+
+function javascript(cb) {
+ // body omitted
+ cb();
+}
+
+exports.build = series(clean, parallel(css, javascript));
+```
+
+[async-completion-docs]: ../getting-started/4-async-completion.md
+[using-async-await-docs]: ../getting-started/4-async-completion.md#using-async-await
+[img-gulp-tasks-command]: https://gulpjs.com/img/docs-gulp-tasks-command.png
+[async-once]: https://github.com/gulpjs/async-once
diff --git a/docs/getting-started/4-async-completion.md b/docs/getting-started/4-async-completion.md
new file mode 100644
index 00000000..a3a550d0
--- /dev/null
+++ b/docs/getting-started/4-async-completion.md
@@ -0,0 +1,144 @@
+
+
+# Async Completion
+
+Node libraries handle asynchronicity in a variety of ways. The most common pattern is [error-first callbacks][node-api-error-first-callbacks], but you might also encounter [streams][stream-docs], [promises][promise-docs], [event emitters][event-emitter-docs], [child processes][child-process-docs], or [observables][observable-docs]. Gulp tasks normalize all these types of asynchronicity.
+
+## Signal task completion
+
+When a stream, promise, event emitter, child process, or observable is returned from a task, the success or error informs gulp whether to continue or end. If a task errors, gulp will end immediately and show that error.
+
+When composing tasks with `series()`, an error will end the composition and no further tasks will be executed. When composing tasks with `parallel()`, an error will end the composition but the other parallel tasks may or may not complete.
+
+### Returning a stream
+
+```js
+const { src, dest } = require('gulp');
+
+function streamTask() {
+ return src('*.js')
+ .pipe(dest('output'));
+}
+
+exports.default = streamTask;
+```
+
+### Returning a promise
+
+```js
+function promiseTask() {
+ return Promise.resolve('the value is ignored');
+}
+
+exports.default = promiseTask;
+```
+
+### Returning an event emitter
+
+```js
+const { EventEmitter } = require('events');
+
+function eventEmitterTask() {
+ const emitter = new EventEmitter();
+ // Emit has to happen async otherwise gulp isn't listening yet
+ setTimeout(() => emitter.emit('finish'), 250);
+ return emitter;
+}
+
+exports.default = eventEmitterTask;
+```
+
+### Returning a child process
+
+```js
+const { exec } = require('child_process');
+
+function childProcessTask() {
+ return exec('date');
+}
+
+exports.default = childProcessTask;
+```
+
+### Returning an observable
+
+```js
+const { of } = require('rxjs');
+
+function observableTask() {
+ return of(1, 2, 3);
+}
+
+exports.default = observableTask;
+```
+
+### Using an error-first callback
+
+If nothing is returned from your task, you must use the error-first callback to signal completion. The callback will be passed to your task as the only argument - named `cb()` in the examples below.
+
+```js
+function callbackTask(cb) {
+ // `cb()` should be called by some async work
+ cb();
+}
+
+exports.default = callbackTask;
+```
+
+To indicate to gulp that an error occurred in a task using an error-first callback, call it with an `Error` as the only argument.
+
+```js
+function callbackError(cb) {
+ // `cb()` should be called by some async work
+ cb(new Error('kaboom'));
+}
+
+exports.default = callbackError;
+```
+
+However, you'll often pass this callback to another API instead of calling it yourself.
+
+```js
+const fs = require('fs');
+
+function passingCallback(cb) {
+ fs.access('gulpfile.js', cb);
+}
+
+exports.default = passingCallback;
+```
+
+## No synchronous tasks
+
+Synchronous tasks are no longer supported. They often led to subtle mistakes that were hard to debug, like forgetting to return your streams from a task.
+
+When you see the _"Did you forget to signal async completion?"_ warning, none of the techniques mentioned above were used. You'll need to use the error-first callback or return a stream, promise, event emitter, child process, or observable to resolve the issue.
+
+## Using async/await
+
+When not using any of the previous options, you can define your task as an [`async` function][async-await-docs], which wraps your task in a promise. This allows you to work with promises synchronously using `await` and use other synchronous code.
+
+```js
+const fs = require('fs');
+
+async function asyncAwaitTask() {
+ const { version } = JSON.parse(fs.readFileSync('package.json', 'utf8'));
+ console.log(version);
+ await Promise.resolve('some result');
+}
+
+exports.default = asyncAwaitTask;
+```
+
+[node-api-error-first-callbacks]: https://nodejs.org/api/errors.html#errors_error_first_callbacks
+[stream-docs]: https://nodejs.org/api/stream.html#stream_stream
+[promise-docs]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Using_promises
+[event-emitter-docs]: https://nodejs.org/api/events.html#events_events
+[child-process-docs]: https://nodejs.org/api/child_process.html#child_process_child_process
+[observable-docs]: https://github.com/tc39/proposal-observable/blob/master/README.md
+[async-await-docs]: https://developers.google.com/web/fundamentals/primers/async-functions
diff --git a/docs/getting-started/5-working-with-files.md b/docs/getting-started/5-working-with-files.md
new file mode 100644
index 00000000..9ac33379
--- /dev/null
+++ b/docs/getting-started/5-working-with-files.md
@@ -0,0 +1,98 @@
+
+
+# Working with Files
+
+The `src()` and `dest()` methods are exposed by gulp to interact with files on your computer.
+
+`src()` is given a [glob][explaining-globs-docs] to read from the file system and produces a [Node stream][node-streams-docs]. It locates all matching files and reads them into memory to pass through the stream.
+
+The stream produced by `src()` should be returned from a task to signal async completion, as mentioned in [Creating Tasks][creating-tasks-docs].
+
+```js
+const { src, dest } = require('gulp');
+
+exports.default = function() {
+ return src('src/*.js')
+ .pipe(dest('output/'));
+}
+```
+
+The main API of a stream is the `.pipe()` method for chaining Transform or Writable streams.
+
+```js
+const { src, dest } = require('gulp');
+const babel = require('gulp-babel');
+
+exports.default = function() {
+ return src('src/*.js')
+ .pipe(babel())
+ .pipe(dest('output/'));
+}
+```
+
+`dest()` is given an output directory string and also produces a [Node stream][node-streams-docs] which is generally used as a terminator stream. When it receives a file passed through the pipeline, it writes the contents and other details out to the filesystem at a given directory. The `symlink()` method is also available and operates like `dest()`, but creates links instead of files (see [`symlink()`][symlink-api-docs] for details).
+
+Most often plugins will be placed between `src()` and `dest()` using the `.pipe()` method and will transform the files within the stream.
+
+## Adding files to the stream
+
+`src()` can also be placed in the middle of a pipeline to add files to the stream based on the given globs. The additional files will only be available to transformations later in the stream. If [globs overlap][overlapping-globs-docs], the files will be added again.
+
+This can be useful for transpiling some files before adding plain JavaScript files to the pipeline and uglifying everything.
+
+```js
+const { src, dest } = require('gulp');
+const babel = require('gulp-babel');
+const uglify = require('gulp-uglify');
+
+exports.default = function() {
+ return src('src/*.js')
+ .pipe(babel())
+ .pipe(src('vendor/*.js'))
+ .pipe(uglify())
+ .pipe(dest('output/'));
+}
+```
+
+## Output in phases
+
+`dest()` can be used in the middle of a pipeline to write intermediate states to the filesystem. When a file is received, the current state is written out to the filesystem, the path is updated to represent the new location of the output file, then that file continues down the pipeline.
+
+This feature can be useful to create unminified and minified files with the same pipeline.
+
+```js
+const { src, dest } = require('gulp');
+const babel = require('gulp-babel');
+const uglify = require('gulp-uglify');
+const rename = require('gulp-rename');
+
+exports.default = function() {
+ return src('src/*.js')
+ .pipe(babel())
+ .pipe(src('vendor/*.js'))
+ .pipe(dest('output/'))
+ .pipe(uglify())
+ .pipe(rename({ extname: '.min.js' }))
+ .pipe(dest('output/'));
+}
+```
+
+## Modes: streaming, buffered, and empty
+
+`src()` can operate in three modes: buffering, streaming, and empty. These are configured with the `buffer` and `read` [options][src-options-api-docs] on `src()`.
+
+* Buffering mode is the default and loads the file contents into memory. Plugins usually operate in buffering mode and many don't support streaming mode.
+* Streaming mode exists mainly to operate on large files that can't fit in memory, like giant images or movies. The contents are streamed from the filesystem in small chunks instead of loaded all at once. If you need to use streaming mode, look for a plugin that supports it or write your own.
+* Empty mode contains no contents and is useful when only working with file metadata.
+
+[explaining-globs-docs]: ../getting-started/6-explaining-globs.md
+[creating-tasks-docs]: ../getting-started/3-creating-tasks.md
+[overlapping-globs-docs]: ../getting-started/6-explaining-globs.md#overlapping-globs
+[node-streams-docs]: https://nodejs.org/api/stream.html
+[symlink-api-docs]: ../api/symlink.md
+[src-options-api-docs]: ../api/src.md#options
diff --git a/docs/getting-started/6-explaining-globs.md b/docs/getting-started/6-explaining-globs.md
new file mode 100644
index 00000000..4517d8aa
--- /dev/null
+++ b/docs/getting-started/6-explaining-globs.md
@@ -0,0 +1,100 @@
+
+
+# Explaining Globs
+
+A glob is a string of literal and/or wildcard characters used to match filepaths. Globbing is the act of locating files on a filesystem using one or more globs.
+
+The `src()` method expects a single glob string or an array of globs to determine which files your pipeline will operate on. At least one match must be found for your glob(s) otherwise `src()` will error. When an array of globs is used, any negative globs will remove matches from any positive glob.
+
+## Segments and separators
+
+A segment is everything between separators. The separator in a glob is always the `/` character - regardless of the operating system - even in Windows where the path separator is `\\`. In a glob, `\\` is reserved as the escape character.
+
+Here, the * is escaped, so it is treated as a literal instead of a wildcard character.
+```js
+'glob_with_uncommon_\\*_character.js'
+```
+
+Avoid using Node's `path` methods, like `path.join`, to create globs. On Windows, it produces an invalid glob because Node uses `\\` as the separator. Also avoid the `__dirname` global, `__filename` global, or `process.cwd()` for the same reasons.
+
+```js
+const invalidGlob = path.join(__dirname, 'src/*.js');
+```
+
+## Special character: * (single-star)
+
+Matches any amount - including none - of characters within a single segment. Useful for globbing files within one directory.
+
+This glob will match files like `index.js`, but not files like `scripts/index.js` or `scripts/nested/index.js`
+```js
+'*.js'
+```
+
+## Special character: ** (double-star)
+
+Matches any amount - including none - of characters across segments. Useful for globbing files in nested directories. Make sure to appropriately restrict your double-star globs, to avoid matching large directories unnecessarily.
+
+Here, the glob is appropriately restricted to the `scripts/` directory. It will match files like `scripts/index.js`, `scripts/nested/index.js`, and `scripts/nested/twice/index.js`.
+
+```js
+'scripts/**/*.js'
+```
+
+In the previous example, if `scripts/` wasn't prefixed, all dependencies in `node_modules` or other directories would also be matched.
+
+## Special character: ! (negative)
+
+Globs prefixed with the `!` character will "negate" the glob, excluding the match completely. All negative globs are applied to every positive glob, which is a departure from gulp versions before v5.
+
+Here, the `scripts/` directory will be traversed for all files ending in `.js`, but all files from the `scripts/vendor/` directory will be excluded.
+
+```js
+['scripts/**/*.js', '!scripts/vendor/**']
+```
+
+Negative globs can be used as an alternative for restricting double-star globs.
+
+```js
+['**/*.js', '!node_modules/**']
+```
+
+## Ordered globs
+
+Versions of gulp before v5 allowed "ordered globs"; however, that has been removed to align with most globbing libraries in the ecosystem.
+
+If you need the "ordered glob" functionality, you can use the [ordered-read-streams][ordered-read-streams-docs] library to combine streams:
+
+```js
+const order = require("ordered-read-streams");
+
+exports.default = function () {
+ return order([
+ gulp.src("input/jquery/dist/jquery.js"),
+ gulp.src("input/detect_swipe/jquery.detect_swipe.js"),
+ ]).pipe(gulp.dest('output/'));
+}
+```
+
+## Overlapping globs
+
+Two or more globs that (un)intentionally match the same file are considered overlapping. When overlapping globs are used within a single `src()`, gulp does its best to remove the duplicates, but doesn't attempt to deduplicate across separate `src()` calls.
+
+## Advanced resources
+
+Most of what you'll need to work with globs in gulp is covered here. If you'd like to get more in depth, here are a few resources.
+
+* [Micromatch Documentation][micromatch-docs]
+* [node-glob's Glob Primer][glob-primer-docs]
+* [Begin's Globbing Documentation][begin-globbing-docs]
+* [Wikipedia's Glob Page][wikipedia-glob]
+
+[micromatch-docs]: https://github.com/micromatch/micromatch
+[glob-primer-docs]: https://github.com/isaacs/node-glob#glob-primer
+[begin-globbing-docs]: https://github.com/begin/globbing#what-is-globbing
+[wikipedia-glob]: https://en.wikipedia.org/wiki/Glob_(programming)
+[ordered-read-streams-docs]: https://github.com/gulpjs/ordered-read-streams#orderedstreams-options
diff --git a/docs/getting-started/7-using-plugins.md b/docs/getting-started/7-using-plugins.md
new file mode 100644
index 00000000..20864827
--- /dev/null
+++ b/docs/getting-started/7-using-plugins.md
@@ -0,0 +1,114 @@
+
+
+# Using Plugins
+
+Gulp plugins are [Node Transform Streams][through2-docs] that encapsulate common behavior to transform files in a pipeline - often placed between `src()` and `dest()` using the `.pipe()` method. They can change the filename, metadata, or contents of every file that passes through the stream.
+
+Plugins from npm - using the "gulpplugin" and "gulpfriendly" keywords - can be browsed and searched on the [plugin search page][gulp-plugin-site].
+
+Each plugin should only do a small amount of work, so you can connect them like building blocks. You may need to combine a bunch of them to get the desired result.
+
+```js
+const { src, dest } = require('gulp');
+const uglify = require('gulp-uglify');
+const rename = require('gulp-rename');
+
+exports.default = function() {
+ return src('src/*.js')
+ // The gulp-uglify plugin won't update the filename
+ .pipe(uglify())
+ // So use gulp-rename to change the extension
+ .pipe(rename({ extname: '.min.js' }))
+ .pipe(dest('output/'));
+}
+```
+
+## Do you need a plugin?
+
+Not everything in gulp should use plugins. They are a quick way to get started, but many operations are improved by using a module or library instead.
+
+```js
+const { rollup } = require('rollup');
+
+// Rollup's promise API works great in an `async` task
+exports.default = async function() {
+ const bundle = await rollup({
+ input: 'src/index.js'
+ });
+
+ return bundle.write({
+ file: 'output/bundle.js',
+ format: 'iife'
+ });
+}
+```
+
+Plugins should always transform files. Use a (non-plugin) Node module or library for any other operations.
+
+```js
+const del = require('delete');
+
+exports.default = function(cb) {
+ // Use the `delete` module directly, instead of using gulp-rimraf
+ del(['output/*.js'], cb);
+}
+```
+
+## Conditional plugins
+
+Since plugin operations shouldn't be file-type-aware, you may need a plugin like [gulp-if][gulp-if-package] to transform subsets of files.
+
+```js
+const { src, dest } = require('gulp');
+const gulpif = require('gulp-if');
+const uglify = require('gulp-uglify');
+
+function isJavaScript(file) {
+ // Check if file extension is '.js'
+ return file.extname === '.js';
+}
+
+exports.default = function() {
+ // Include JavaScript and CSS files in a single pipeline
+ return src(['src/*.js', 'src/*.css'])
+ // Only apply gulp-uglify plugin to JavaScript files
+ .pipe(gulpif(isJavaScript, uglify()))
+ .pipe(dest('output/'));
+}
+```
+
+## Inline plugins
+
+Inline plugins are one-off Transform Streams you define inside your gulpfile by writing the desired behavior.
+
+There are two situations where creating an inline plugin is helpful:
+* Instead of creating and maintaining your own plugin.
+* Instead of forking a plugin that exists to add a feature you want.
+
+```js
+const { src, dest } = require('gulp');
+const uglify = require('uglify-js');
+const through2 = require('through2');
+
+exports.default = function() {
+ return src('src/*.js')
+ // Instead of using gulp-uglify, you can create an inline plugin
+ .pipe(through2.obj(function(file, _, cb) {
+ if (file.isBuffer()) {
+ const code = uglify.minify(file.contents.toString())
+ file.contents = Buffer.from(code.code)
+ }
+ cb(null, file);
+ }))
+ .pipe(dest('output/'));
+}
+```
+
+[gulp-plugin-site]: https://gulpjs.com/plugins/
+[through2-docs]: https://github.com/rvagg/through2
+[gulp-if-package]: https://www.npmjs.com/package/gulp-if
diff --git a/docs/getting-started/8-watching-files.md b/docs/getting-started/8-watching-files.md
new file mode 100644
index 00000000..bb3bf994
--- /dev/null
+++ b/docs/getting-started/8-watching-files.md
@@ -0,0 +1,132 @@
+
+
+# Watching Files
+
+The `watch()` API connects [globs][globs-docs] to [tasks][creating-tasks-docs] using a file system watcher. It watches for changes to files that match the globs and executes the task when a change occurs. If the task doesn't signal [Async Completion][async-completion-doc], it will never be run a second time.
+
+This API provides built-in delay and queueing based on most-common-use defaults.
+
+```js
+const { watch, series } = require('gulp');
+
+function clean(cb) {
+ // body omitted
+ cb();
+}
+
+function javascript(cb) {
+ // body omitted
+ cb();
+}
+
+function css(cb) {
+ // body omitted
+ cb();
+}
+
+exports.default = function() {
+ // You can use a single task
+ watch('src/*.css', css);
+ // Or a composed task
+ watch('src/*.js', series(clean, javascript));
+};
+```
+
+## Warning: avoid synchronous
+
+A watcher's task cannot be synchronous, like tasks registered into the task system. If you pass a sync task, the completion can't be determined and the task won't run again - it is assumed to still be running.
+
+There is no error or warning message provided because the file watcher keeps your Node process running. Since the process doesn't exit, it cannot be determined whether the task is done or just taking a really, really long time to run.
+
+## Watched events
+
+By default, the watcher executes tasks whenever a file is created, changed, or deleted.
+If you need to use different events, you can use the `events` option when calling `watch()`. The available events are `'add'`, `'addDir'`, `'change'`, `'unlink'`, `'unlinkDir'`, `'ready'`, `'error'`. Additionally `'all'` is available, which represents all events other than `'ready'` and `'error'`.
+
+```js
+const { watch } = require('gulp');
+
+exports.default = function() {
+ // All events will be watched
+ watch('src/*.js', { events: 'all' }, function(cb) {
+ // body omitted
+ cb();
+ });
+};
+```
+
+## Initial execution
+
+Upon calling `watch()`, the tasks won't be executed, instead they'll wait for the first file change.
+
+To execute tasks before the first file change, set the `ignoreInitial` option to `false`.
+
+```js
+const { watch } = require('gulp');
+
+exports.default = function() {
+ // The task will be executed upon startup
+ watch('src/*.js', { ignoreInitial: false }, function(cb) {
+ // body omitted
+ cb();
+ });
+};
+```
+
+## Queueing
+
+Each `watch()` guarantees that its currently running task won't execute again concurrently. When a file change is made while a watcher task is running, another execution will queue up to run when the task finishes. Only one run can be queued up at a time.
+
+To disable queueing, set the `queue` option to `false`.
+
+```js
+const { watch } = require('gulp');
+
+exports.default = function() {
+ // The task will be run (concurrently) for every change made
+ watch('src/*.js', { queue: false }, function(cb) {
+ // body omitted
+ cb();
+ });
+};
+```
+
+## Delay
+
+Upon file change, a watcher task won't run until a 200ms delay has elapsed. This is to avoid starting a task too early when many files are being changed at once - like find-and-replace.
+
+To adjust the delay duration, set the `delay` option to a positive integer.
+
+```js
+const { watch } = require('gulp');
+
+exports.default = function() {
+ // The task won't be run until 500ms have elapsed since the first change
+ watch('src/*.js', { delay: 500 }, function(cb) {
+ // body omitted
+ cb();
+ });
+};
+```
+
+## Using the watcher instance
+
+You likely won't use this feature, but if you need full control over changed files - like access to paths or metadata - use the [chokidar][chokidar-module-package] instance returned from `watch()`.
+
+__Be careful:__ The returned chokidar instance doesn't have queueing, delay, or async completion features.
+
+## Optional dependency
+
+Gulp has an optional dependency called [fsevents][fsevents-package], which is a Mac-specific file watcher. If you see an installation warning for fsevents - _"npm WARN optional SKIPPING OPTIONAL DEPENDENCY: fsevents"_ - it is not an issue.
+If fsevents installation is skipped, a fallback watcher will be used and any errors occurring in your gulpfile aren't related to this warning.
+
+[globs-docs]: ../getting-started/6-explaining-globs.md
+[creating-tasks-docs]: ../getting-started/3-creating-tasks.md
+[async-completion-doc]: ../getting-started/4-async-completion.md
+[chokidar-module-package]: https://www.npmjs.com/package/chokidar
+[fsevents-package]: https://www.npmjs.com/package/fsevents
diff --git a/docs/getting-started/README.md b/docs/getting-started/README.md
new file mode 100644
index 00000000..fe599a90
--- /dev/null
+++ b/docs/getting-started/README.md
@@ -0,0 +1,10 @@
+# Getting Started
+
+1. [Quick Start](1-quick-start.md)
+2. [JavaScript and Gulpfiles](2-javascript-and-gulpfiles.md)
+3. [Creating Tasks](3-creating-tasks.md)
+4. [Async Completion](4-async-completion.md)
+5. [Working with Files](5-working-with-files.md)
+6. [Explaining Globs](6-explaining-globs.md)
+7. [Using Plugins](7-using-plugins.md)
+8. [Watching Files](8-watching-files.md)
diff --git a/docs/recipes/README.md b/docs/recipes/README.md
index 52c3bcd1..6748fa54 100644
--- a/docs/recipes/README.md
+++ b/docs/recipes/README.md
@@ -1,22 +1,16 @@
# Recipes
-* [Automate release workflow](automate-release-workflow.md)
+* [Automate release workflow](automate-releases.md)
* [Combining streams to handle errors](combining-streams-to-handle-errors.md)
* [Delete files and folders](delete-files-folder.md)
* [Fast browserify builds with watchify](fast-browserify-builds-with-watchify.md)
* [Incremental rebuilding, including operating on full file sets](incremental-builds-with-concatenate.md)
* [Make stream from buffer (memory contents)](make-stream-from-buffer.md)
* [Mocha test-runner with gulp](mocha-test-runner-with-gulp.md)
-* [Only pass through changed files](only-pass-through-changed-files.md)
* [Pass parameters from the command line](pass-arguments-from-cli.md)
-* [Rebuild only files that change](rebuild-only-files-that-change.md)
* [Generating a file per folder](running-task-steps-per-folder.md)
-* [Running tasks in series](running-tasks-in-series.md)
* [Server with live-reloading and CSS injection](server-with-livereload-and-css-injection.md)
* [Sharing streams with stream factories](sharing-streams-with-stream-factories.md)
-* [Specifying a new cwd (current working directory)](specifying-a-cwd.md)
-* [Split tasks across multiple files](split-tasks-across-multiple-files.md)
-* [Using external config file](using-external-config-file.md)
* [Using multiple sources in one task](using-multiple-sources-in-one-task.md)
* [Browserify + Uglify with sourcemaps](browserify-uglify-sourcemap.md)
* [Browserify + Globs](browserify-with-globs.md)
@@ -24,6 +18,5 @@
* [Output both a minified and non-minified version](minified-and-non-minified.md)
* [Templating with Swig and YAML front-matter](templating-with-swig-and-yaml-front-matter.md)
* [Run Grunt Tasks from Gulp](run-grunt-tasks-from-gulp.md)
-* [Exports as tasks](exports-as-tasks.md)
* [Rollup with rollup-stream](rollup-with-rollup-stream.md)
* [Run gulp task via cron job](cron-task.md)
diff --git a/docs/recipes/automate-release-workflow.md b/docs/recipes/automate-release-workflow.md
deleted file mode 100644
index 650691c9..00000000
--- a/docs/recipes/automate-release-workflow.md
+++ /dev/null
@@ -1,89 +0,0 @@
-# Automate release workflow
-
-If your project follows a semantic versioning, it may be a good idea to automatize the steps needed to do a release.
-Below you have a simple recipe that bumps the project version, commits the changes to git and creates a new tag.
-
-``` javascript
-
-var gulp = require('gulp');
-var runSequence = require('run-sequence');
-var conventionalChangelog = require('gulp-conventional-changelog');
-var conventionalGithubReleaser = require('conventional-github-releaser');
-var bump = require('gulp-bump');
-var gutil = require('gulp-util');
-var git = require('gulp-git');
-var fs = require('fs');
-
-gulp.task('changelog', function () {
- return gulp.src('CHANGELOG.md', {
- buffer: false
- })
- .pipe(conventionalChangelog({
- preset: 'angular' // Or to any other commit message convention you use.
- }))
- .pipe(gulp.dest('./'));
-});
-
-gulp.task('github-release', function(done) {
- conventionalGithubReleaser({
- type: "oauth",
- token: '0126af95c0e2d9b0a7c78738c4c00a860b04acc8' // change this to your own GitHub token or use an environment variable
- }, {
- preset: 'angular' // Or to any other commit message convention you use.
- }, done);
-});
-
-gulp.task('bump-version', function () {
-// We hardcode the version change type to 'patch' but it may be a good idea to
-// use minimist (https://www.npmjs.com/package/minimist) to determine with a
-// command argument whether you are doing a 'major', 'minor' or a 'patch' change.
- return gulp.src(['./bower.json', './package.json'])
- .pipe(bump({type: "patch"}).on('error', gutil.log))
- .pipe(gulp.dest('./'));
-});
-
-gulp.task('commit-changes', function () {
- return gulp.src('.')
- .pipe(git.add())
- .pipe(git.commit('[Prerelease] Bumped version number'));
-});
-
-gulp.task('push-changes', function (cb) {
- git.push('origin', 'master', cb);
-});
-
-gulp.task('create-new-tag', function (cb) {
- var version = getPackageJsonVersion();
- git.tag(version, 'Created Tag for version: ' + version, function (error) {
- if (error) {
- return cb(error);
- }
- git.push('origin', 'master', {args: '--tags'}, cb);
- });
-
- function getPackageJsonVersion () {
- // We parse the json file instead of using require because require caches
- // multiple calls so the version number won't be updated
- return JSON.parse(fs.readFileSync('./package.json', 'utf8')).version;
- };
-});
-
-gulp.task('release', function (callback) {
- runSequence(
- 'bump-version',
- 'changelog',
- 'commit-changes',
- 'push-changes',
- 'create-new-tag',
- 'github-release',
- function (error) {
- if (error) {
- console.log(error.message);
- } else {
- console.log('RELEASE FINISHED SUCCESSFULLY');
- }
- callback(error);
- });
-});
-
-```
diff --git a/docs/recipes/automate-releases.md b/docs/recipes/automate-releases.md
new file mode 100644
index 00000000..4e64bb53
--- /dev/null
+++ b/docs/recipes/automate-releases.md
@@ -0,0 +1,100 @@
+
+
+# Automate Releases
+
+If your project follows a semantic versioning, it may be a good idea to automatize the steps needed to do a release.
+The recipe below bumps the project version, commits the changes to git and creates a new GitHub release.
+
+For publishing a GitHub release you'll need to [create a personal access token](https://docs.github.com/en/free-pro-team@latest/github/authenticating-to-github/creating-a-personal-access-token) and add it to your project. However, we don't want to commit it, so we'll use [`dotenv`](https://www.npmjs.com/package/dotenv) to load it from a git-ignored `.env` file:
+
+```
+GH_TOKEN=ff34885...
+```
+
+Don't forget to add `.env` to your `.gitignore`.
+
+Next, install all the necessary dependencies for this recipe:
+
+```sh
+npm install --save-dev conventional-recommended-bump conventional-changelog-cli conventional-github-releaser dotenv execa
+```
+
+Based on your environment, setup and preferences, your release workflow might look something like this:
+
+``` js
+const gulp = require('gulp');
+const conventionalRecommendedBump = require('conventional-recommended-bump');
+const conventionalGithubReleaser = require('conventional-github-releaser');
+const execa = require('execa');
+const fs = require('fs');
+const { promisify } = require('util');
+const dotenv = require('dotenv');
+
+// load environment variables
+const result = dotenv.config();
+
+if (result.error) {
+ throw result.error;
+}
+
+// Conventional Changelog preset
+const preset = 'angular';
+// print output of commands into the terminal
+const stdio = 'inherit';
+
+async function bumpVersion() {
+ // get recommended version bump based on commits
+ const { releaseType } = await promisify(conventionalRecommendedBump)({ preset });
+ // bump version without committing and tagging
+ await execa('npm', ['version', releaseType, '--no-git-tag-version'], {
+ stdio,
+ });
+}
+
+async function changelog() {
+ await execa(
+ 'npx',
+ [
+ 'conventional-changelog',
+ '--preset',
+ preset,
+ '--infile',
+ 'CHANGELOG.md',
+ '--same-file',
+ ],
+ { stdio }
+ );
+}
+
+async function commitTagPush() {
+ // even though we could get away with "require" in this case, we're taking the safe route
+ // because "require" caches the value, so if we happen to use "require" again somewhere else
+ // we wouldn't get the current value, but the value of the last time we called "require"
+ const { version } = JSON.parse(await promisify(fs.readFile)('package.json'));
+ const commitMsg = `chore: release ${version}`;
+ await execa('git', ['add', '.'], { stdio });
+ await execa('git', ['commit', '--message', commitMsg], { stdio });
+ await execa('git', ['tag', `v${version}`], { stdio });
+ await execa('git', ['push', '--follow-tags'], { stdio });
+}
+
+function githubRelease(done) {
+ conventionalGithubReleaser(
+ { type: 'oauth', token: process.env.GH_TOKEN },
+ { preset },
+ done
+ );
+}
+
+exports.release = gulp.series(
+ bumpVersion,
+ changelog,
+ commitTagPush,
+ githubRelease
+);
+```
diff --git a/docs/recipes/browserify-multiple-destination.md b/docs/recipes/browserify-multiple-destination.md
index 6a9af199..474f5828 100644
--- a/docs/recipes/browserify-multiple-destination.md
+++ b/docs/recipes/browserify-multiple-destination.md
@@ -8,7 +8,7 @@ The below `js` task bundles all the `.js` files under `src/` as entry points and
```js
var gulp = require('gulp');
var browserify = require('browserify');
-var gutil = require('gulp-util');
+var log = require('gulplog');
var tap = require('gulp-tap');
var buffer = require('gulp-buffer');
var sourcemaps = require('gulp-sourcemaps');
@@ -21,7 +21,7 @@ gulp.task('js', function () {
// transform file objects using gulp-tap plugin
.pipe(tap(function (file) {
- gutil.log('bundling ' + file.path);
+ log.info('bundling ' + file.path);
// replace file contents with browserify's bundle stream
file.contents = browserify(file.path, {debug: true}).bundle();
diff --git a/docs/recipes/browserify-transforms.md b/docs/recipes/browserify-transforms.md
index e1f11dbc..58c74bc3 100644
--- a/docs/recipes/browserify-transforms.md
+++ b/docs/recipes/browserify-transforms.md
@@ -1,6 +1,6 @@
# Browserify + Transforms
-[Browserify](http://github.com/substack/node-browserify) has become an important and indispensable
+[Browserify](https://github.com/browserify/browserify) has become an important and indispensable
tool but requires being wrapped before working well with gulp. Below is a simple recipe for using
Browserify with transforms.
@@ -13,9 +13,8 @@ var browserify = require('browserify');
var gulp = require('gulp');
var source = require('vinyl-source-stream');
var buffer = require('vinyl-buffer');
-var gutil = require('gulp-util');
+var log = require('gulplog');
var uglify = require('gulp-uglify');
-var sourcemaps = require('gulp-sourcemaps');
var reactify = require('reactify');
gulp.task('javascript', function () {
@@ -28,13 +27,11 @@ gulp.task('javascript', function () {
});
return b.bundle()
- .pipe(source('app.js'))
+ .pipe(source('app.js', { sourcemaps: true }))
.pipe(buffer())
- .pipe(sourcemaps.init({loadMaps: true}))
// Add transformation tasks to the pipeline here.
.pipe(uglify())
- .on('error', gutil.log)
- .pipe(sourcemaps.write('./'))
- .pipe(gulp.dest('./dist/js/'));
+ .on('error', log.error)
+ .pipe(gulp.dest('./dist/js/', { sourcemaps: '../sourcemaps/' }));
});
```
diff --git a/docs/recipes/browserify-uglify-sourcemap.md b/docs/recipes/browserify-uglify-sourcemap.md
index 756b89ac..58e62229 100644
--- a/docs/recipes/browserify-uglify-sourcemap.md
+++ b/docs/recipes/browserify-uglify-sourcemap.md
@@ -1,10 +1,10 @@
# Browserify + Uglify2 with sourcemaps
-[Browserify](http://github.com/substack/node-browserify) has become an important and indispensable
+[Browserify](https://github.com/browserify/browserify) has become an important and indispensable
tool but requires being wrapped before working well with gulp. Below is a simple recipe for using
Browserify with full sourcemaps that resolve to the original individual files.
-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.
+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.
A simple `gulpfile.js` file for Browserify + Uglify2 with sourcemaps:
@@ -17,7 +17,7 @@ var source = require('vinyl-source-stream');
var buffer = require('vinyl-buffer');
var uglify = require('gulp-uglify');
var sourcemaps = require('gulp-sourcemaps');
-var gutil = require('gulp-util');
+var log = require('gulplog');
gulp.task('javascript', function () {
// set up the browserify instance on a task basis
@@ -32,7 +32,7 @@ gulp.task('javascript', function () {
.pipe(sourcemaps.init({loadMaps: true}))
// Add transformation tasks to the pipeline here.
.pipe(uglify())
- .on('error', gutil.log)
+ .on('error', log.error)
.pipe(sourcemaps.write('./'))
.pipe(gulp.dest('./dist/js/'));
});
diff --git a/docs/recipes/browserify-with-globs.md b/docs/recipes/browserify-with-globs.md
index ad7df6f8..72695fb6 100644
--- a/docs/recipes/browserify-with-globs.md
+++ b/docs/recipes/browserify-with-globs.md
@@ -14,7 +14,7 @@ var source = require('vinyl-source-stream');
var buffer = require('vinyl-buffer');
var globby = require('globby');
var through = require('through2');
-var gutil = require('gulp-util');
+var log = require('gulplog');
var uglify = require('gulp-uglify');
var sourcemaps = require('gulp-sourcemaps');
var reactify = require('reactify');
@@ -33,7 +33,7 @@ gulp.task('javascript', function () {
.pipe(sourcemaps.init({loadMaps: true}))
// Add gulp plugins to the pipeline here.
.pipe(uglify())
- .on('error', gutil.log)
+ .on('error', log.error)
.pipe(sourcemaps.write('./'))
.pipe(gulp.dest('./dist/js/'));
diff --git a/docs/recipes/combining-streams-to-handle-errors.md b/docs/recipes/combining-streams-to-handle-errors.md
index e376f6db..7c38654a 100644
--- a/docs/recipes/combining-streams-to-handle-errors.md
+++ b/docs/recipes/combining-streams-to-handle-errors.md
@@ -12,16 +12,13 @@ var uglify = require('gulp-uglify');
var gulp = require('gulp');
gulp.task('test', function() {
- var combined = combiner.obj([
- gulp.src('bootstrap/js/*.js'),
- uglify(),
- gulp.dest('public/bootstrap')
- ]);
-
- // any errors in the above streams will get caught
- // by this listener, instead of being thrown:
- combined.on('error', console.error.bind(console));
-
- return combined;
+ return combiner.obj([
+ gulp.src('bootstrap/js/*.js'),
+ uglify(),
+ gulp.dest('public/bootstrap')
+ ])
+ // any errors in the above streams will get caught
+ // by this listener, instead of being thrown:
+ .on('error', console.error.bind(console));
});
```
diff --git a/docs/recipes/cron-task.md b/docs/recipes/cron-task.md
index 030fb334..316e0c9b 100644
--- a/docs/recipes/cron-task.md
+++ b/docs/recipes/cron-task.md
@@ -3,7 +3,7 @@
While logged in via a user that has privileges to run `gulp`, run the following:
crontab -e
-
+
to edit your current "[crontab](https://en.wikipedia.org/wiki/Cron)" file.
Typically, within a cron job, you want to run any binary using absolute paths,
diff --git a/docs/recipes/delete-files-folder.md b/docs/recipes/delete-files-folder.md
index 05e37762..d974c628 100644
--- a/docs/recipes/delete-files-folder.md
+++ b/docs/recipes/delete-files-folder.md
@@ -1,4 +1,3 @@
-
# Delete files and folders
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.
@@ -39,7 +38,7 @@ gulp.task('clean:mobile', function () {
]);
});
-gulp.task('default', ['clean:mobile']);
+gulp.task('default', gulp.series('clean:mobile'));
```
@@ -76,7 +75,7 @@ gulp.task('clean:tmp', function () {
.pipe(gulp.dest('dist'));
});
-gulp.task('default', ['clean:tmp']);
+gulp.task('default', gulp.series('clean:tmp'));
```
This will only delete the tmp dir.
diff --git a/docs/recipes/exports-as-tasks.md b/docs/recipes/exports-as-tasks.md
deleted file mode 100644
index 2eaea490..00000000
--- a/docs/recipes/exports-as-tasks.md
+++ /dev/null
@@ -1,22 +0,0 @@
-# Exports as Tasks
-
-Using the ES2015 module syntax you can use your exports as tasks.
-
-```js
-import gulp from 'gulp';
-import babel from 'gulp-babel';
-
-// named task
-export function build() {
- return gulp.src('src/*.js')
- .pipe(babel())
- .pipe(gulp.dest('lib'));
-}
-
-// default task
-export default function dev() {
- gulp.watch('src/*.js', ['build']);
-}
-```
-
-This will **not** work with the gulp-cli version bundled with gulp 3.x. You must use the latest published version.
diff --git a/docs/recipes/fast-browserify-builds-with-watchify.md b/docs/recipes/fast-browserify-builds-with-watchify.md
index ef65e768..5513068c 100644
--- a/docs/recipes/fast-browserify-builds-with-watchify.md
+++ b/docs/recipes/fast-browserify-builds-with-watchify.md
@@ -1,10 +1,10 @@
# Fast browserify builds with watchify
-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.
+As a [browserify](https://github.com/browserify/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.
-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.
+That's why [substack](https://github.com/substack) wrote [watchify](https://github.com/browserify/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.
-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.
+Watchify doesn't have a gulp plugin, and it doesn't need one: you can use [vinyl-source-stream](https://github.com/hughsk/vinyl-source-stream) to pipe the bundle stream into your gulp pipeline.
``` javascript
'use strict';
@@ -14,7 +14,7 @@ var browserify = require('browserify');
var gulp = require('gulp');
var source = require('vinyl-source-stream');
var buffer = require('vinyl-buffer');
-var gutil = require('gulp-util');
+var log = require('gulplog');
var sourcemaps = require('gulp-sourcemaps');
var assign = require('lodash.assign');
@@ -24,19 +24,19 @@ var customOpts = {
debug: true
};
var opts = assign({}, watchify.args, customOpts);
-var b = watchify(browserify(opts));
+var b = watchify(browserify(opts));
// add transformations here
// i.e. b.transform(coffeeify);
gulp.task('js', bundle); // so you can run `gulp js` to build the file
b.on('update', bundle); // on any dep update, runs the bundler
-b.on('log', gutil.log); // output build logs to terminal
+b.on('log', log.info); // output build logs to terminal
function bundle() {
return b.bundle()
// log errors if they happen
- .on('error', gutil.log.bind(gutil, 'Browserify Error'))
+ .on('error', log.error.bind(log, 'Browserify Error'))
.pipe(source('bundle.js'))
// optional, remove if you don't need to buffer file contents
.pipe(buffer())
diff --git a/docs/recipes/handling-the-delete-event-on-watch.md b/docs/recipes/handling-the-delete-event-on-watch.md
index 40cf115f..05bd683a 100644
--- a/docs/recipes/handling-the-delete-event-on-watch.md
+++ b/docs/recipes/handling-the-delete-event-on-watch.md
@@ -1,9 +1,8 @@
# Handling the Delete Event on Watch
-You can listen for `'change'` events to fire on the watcher returned from `gulp.watch`.
-
-Each change event has a `type` property. If `type` is `'deleted'`, you can delete the file
-from your destination directory, using something like:
+You can listen for `'unlink'` events to fire on the watcher returned from `gulp.watch`.
+This gets fired when files are removed, so you can delete the file from your destination
+directory, using something like:
```js
'use strict';
@@ -24,16 +23,11 @@ gulp.task('scripts', function() {
gulp.task('watch', function () {
var watcher = gulp.watch('src/**/*.js', ['scripts']);
- watcher.on('change', function (event) {
- if (event.type === 'deleted') {
- // Simulating the {base: 'src'} used with gulp.src in the scripts task
- var filePathFromSrc = path.relative(path.resolve('src'), event.path);
-
- // Concatenating the 'build' absolute path used by gulp.dest in the scripts task
- var destFilePath = path.resolve('build', filePathFromSrc);
-
- del.sync(destFilePath);
- }
+ watcher.on('unlink', function (filepath) {
+ var filePathFromSrc = path.relative(path.resolve('src'), filepath);
+ // Concatenating the 'build' absolute path used by gulp.dest in the scripts task
+ var destFilePath = path.resolve('build', filePathFromSrc);
+ del.sync(destFilePath);
});
});
```
diff --git a/docs/recipes/incremental-builds-with-concatenate.md b/docs/recipes/incremental-builds-with-concatenate.md
index a1435eb8..c9fdaa0d 100644
--- a/docs/recipes/incremental-builds-with-concatenate.md
+++ b/docs/recipes/incremental-builds-with-concatenate.md
@@ -27,7 +27,7 @@ gulp.task('scripts', function() {
});
gulp.task('watch', function () {
- var watcher = gulp.watch(scriptsGlob, ['scripts']); // watch the same files in our scripts task
+ var watcher = gulp.watch(scriptsGlob, gulp.series('scripts')); // watch the same files in our scripts task
watcher.on('change', function (event) {
if (event.type === 'deleted') { // if a file is deleted, forget about it
delete cached.caches.scripts[event.path]; // gulp-cached remove api
diff --git a/docs/recipes/maintain-directory-structure-while-globbing.md b/docs/recipes/maintain-directory-structure-while-globbing.md
index 95d64469..431d30b3 100644
--- a/docs/recipes/maintain-directory-structure-while-globbing.md
+++ b/docs/recipes/maintain-directory-structure-while-globbing.md
@@ -3,7 +3,7 @@
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()`.
-For example, if you have a directory structure like
+For example, if you have a directory structure like

@@ -27,18 +27,17 @@ If you want to maintain the structure, you need to pass `{base: '.'}` to `gulp.s
```js
gulp.task('task', function () {
- gulp.src(['index.html',
- 'css/**',
- 'js/**',
- 'lib/**',
- 'images/**',
+ return gulp.src(['index.html',
+ 'css/**',
+ 'js/**',
+ 'lib/**',
+ 'images/**',
'plugin/**'
], {base: '.'})
.pipe(operation1())
.pipe(operation2());
});
```
-And the input to your `operation1()` will be a folder structure like
+And the input to your `operation1()` will be a folder structure like

-
diff --git a/docs/recipes/make-stream-from-buffer.md b/docs/recipes/make-stream-from-buffer.md
index 1125451f..b7e44b95 100644
--- a/docs/recipes/make-stream-from-buffer.md
+++ b/docs/recipes/make-stream-from-buffer.md
@@ -35,7 +35,6 @@ A simple and modular way to do this would be the following:
```js
var gulp = require('gulp');
-var runSequence = require('run-sequence');
var source = require('vinyl-source-stream');
var vinylBuffer = require('vinyl-buffer');
var tap = require('gulp-tap');
@@ -50,13 +49,13 @@ var memory = {}; // we'll keep our assets in memory
gulp.task('load-lib-files', function() {
// read the lib files from the disk
return gulp.src('src/libs/*.js')
- // concatenate all lib files into one
- .pipe(concat('libs.concat.js'))
- // tap into the stream to get each file's data
- .pipe(tap(function(file) {
- // save the file contents in memory
- memory[path.basename(file.path)] = file.contents.toString();
- }));
+ // concatenate all lib files into one
+ .pipe(concat('libs.concat.js'))
+ // tap into the stream to get each file's data
+ .pipe(tap(function(file) {
+ // save the file contents in memory
+ memory[path.basename(file.path)] = file.contents.toString();
+ }));
});
gulp.task('load-versions', function() {
@@ -79,9 +78,9 @@ gulp.task('write-versions', function() {
availableVersions.forEach(function(v) {
// make a new stream with fake file name
var stream = source('final.' + v);
-
+
var streamEnd = stream;
-
+
// we load the data from the concatenated libs
var fileContents = memory['libs.concat.js'] +
// we add the version's data
@@ -100,41 +99,40 @@ gulp.task('write-versions', function() {
.pipe(vinylBuffer())
//.pipe(tap(function(file) { /* do something with the file contents here */ }))
.pipe(gulp.dest('output'));
-
+
// add the end of the stream, otherwise the task would finish before all the processing
// is done
streams.push(streamEnd);
-
+
});
return es.merge.apply(this, streams);
});
//============================================ our main task
-gulp.task('default', function(taskDone) {
- runSequence(
- ['load-lib-files', 'load-versions'], // load the files in parallel
- 'write-versions', // ready to write once all resources are in memory
- taskDone // done
- );
-});
+gulp.task('default', gulp.series(
+ // load the files in parallel
+ gulp.parallel('load-lib-files', 'load-versions'),
+ // ready to write once all resources are in memory
+ 'write-versions'
+ )
+);
//============================================ our watcher task
// only watch after having run 'default' once so that all resources
// are already in memory
-gulp.task('watch', ['default'], function() {
- gulp.watch('./src/libs/*.js', function() {
- runSequence(
- 'load-lib-files', // we only have to load the changed files
+gulp.task('watch', gulp.series(
+ 'default',
+ function() {
+ gulp.watch('./src/libs/*.js', gulp.series(
+ 'load-lib-files',
'write-versions'
- );
- });
+ ));
- gulp.watch('./src/versions/*.js', function() {
- runSequence(
- 'load-versions', // we only have to load the changed files
+ gulp.watch('./src/versions/*.js', gulp.series(
+ 'load-lib-files',
'write-versions'
- );
- });
-});
+ ));
+ }
+));
```
diff --git a/docs/recipes/minimal-browsersync-setup-with-gulp4.md b/docs/recipes/minimal-browsersync-setup-with-gulp4.md
new file mode 100644
index 00000000..82acb674
--- /dev/null
+++ b/docs/recipes/minimal-browsersync-setup-with-gulp4.md
@@ -0,0 +1,161 @@
+# Minimal BrowserSync setup with Gulp 4
+
+[BrowserSync](https://www.browsersync.io/) is a great tool to streamline
+the development process with the ability to reflect code changes instantaneously
+in the browser through live-reloading. Setting up a live-reloading
+BrowserSync server with Gulp 4 is very clean and easy.
+
+## Step 1: Install the dependencies
+
+```
+npm install --save-dev browser-sync
+```
+
+## Step 2: Setup the project structure
+
+```
+src/
+ scripts/
+ |__ index.js
+dist/
+ scripts/
+index.html
+gulpfile.babel.js
+```
+
+The goal here is to be able to:
+- Build the source script file in `src/scripts/`, e.g. compiling with babel, minifying, etc.
+- Put the compiled version in `dist/scripts` for use in `index.html`
+- Watch for changes in the source file and rebuild the `dist` package
+- With each rebuild of the `dist` package, reload the browser to immediately reflect the changes
+
+## Step 3: Write the gulpfile
+
+The gulpfile could be broken in 3 parts.
+
+### 1. Write the task to prepare the dist package as usual
+
+Refer to the main [README](https://github.com/gulpjs/gulp/blob/master/docs/README.md)
+for more information.
+
+```javascript
+import babel from 'gulp-babel';
+import concat from 'gulp-concat';
+import del from 'del';
+import gulp from 'gulp';
+import uglify from 'gulp-uglify';
+
+const paths = {
+ scripts: {
+ src: 'src/scripts/*.js',
+ dest: 'dist/scripts/'
+ }
+};
+
+const clean = () => del(['dist']);
+
+function scripts() {
+ return gulp.src(paths.scripts.src, { sourcemaps: true })
+ .pipe(babel())
+ .pipe(uglify())
+ .pipe(concat('index.min.js'))
+ .pipe(gulp.dest(paths.scripts.dest));
+}
+```
+
+### 2. Setup the BrowserSync server
+
+And write the tasks to serve and reload the server accordingly.
+
+```javascript
+import browserSync from 'browser-sync';
+const server = browserSync.create();
+
+function reload(done) {
+ server.reload();
+ done();
+}
+
+function serve(done) {
+ server.init({
+ server: {
+ baseDir: './'
+ }
+ });
+ done();
+}
+```
+
+### 3. Watch for source change, rebuild the scripts and reload the server
+
+This is trivially accomplished with `gulp.series`
+
+```javascript
+const watch = () => gulp.watch(paths.scripts.src, gulp.series(scripts, reload));
+```
+
+## Step 4: Bring it all together
+
+The last step is to expose the default task
+
+```javascript
+const dev = gulp.series(clean, scripts, serve, watch);
+export default dev;
+```
+
+And profit
+
+```bash
+$ gulp
+```
+
+Now if you go to [http://localhost:3000](http://localhost:3000), which is the default address of the
+BrowserSync server, you will see that the end result in the browser is updated everytime you change
+the content of the source file. Here is the whole gulpfile:
+
+```javascript
+import babel from 'gulp-babel';
+import concat from 'gulp-concat';
+import del from 'del';
+import gulp from 'gulp';
+import uglify from 'gulp-uglify';
+import browserSync from 'browser-sync';
+
+const server = browserSync.create();
+
+const paths = {
+ scripts: {
+ src: 'src/scripts/*.js',
+ dest: 'dist/scripts/'
+ }
+};
+
+const clean = () => del(['dist']);
+
+function scripts() {
+ return gulp.src(paths.scripts.src, { sourcemaps: true })
+ .pipe(babel())
+ .pipe(uglify())
+ .pipe(concat('index.min.js'))
+ .pipe(gulp.dest(paths.scripts.dest));
+}
+
+function reload(done) {
+ server.reload();
+ done();
+}
+
+function serve(done) {
+ server.init({
+ server: {
+ baseDir: './'
+ }
+ });
+ done();
+}
+
+const watch = () => gulp.watch(paths.scripts.src, gulp.series(scripts, reload));
+
+const dev = gulp.series(clean, scripts, serve, watch);
+export default dev;
+```
diff --git a/docs/recipes/mocha-test-runner-with-gulp.md b/docs/recipes/mocha-test-runner-with-gulp.md
index 69788aad..4a6b2179 100644
--- a/docs/recipes/mocha-test-runner-with-gulp.md
+++ b/docs/recipes/mocha-test-runner-with-gulp.md
@@ -22,19 +22,19 @@ gulp.task('default', function() {
### Running mocha tests when files change
```js
-// npm install gulp gulp-mocha gulp-util
+// npm install gulp gulp-mocha gulplog
var gulp = require('gulp');
var mocha = require('gulp-mocha');
-var gutil = require('gulp-util');
-
-gulp.task('default', function() {
- gulp.watch(['lib/**', 'test/**'], ['mocha']);
-});
+var log = require('gulplog');
gulp.task('mocha', function() {
return gulp.src(['test/*.js'], { read: false })
.pipe(mocha({ reporter: 'list' }))
- .on('error', gutil.log);
+ .on('error', log.error);
+});
+
+gulp.task('watch-mocha', function() {
+ gulp.watch(['lib/**', 'test/**'], gulp.series('mocha'));
});
```
diff --git a/docs/recipes/only-pass-through-changed-files.md b/docs/recipes/only-pass-through-changed-files.md
deleted file mode 100644
index f2db5972..00000000
--- a/docs/recipes/only-pass-through-changed-files.md
+++ /dev/null
@@ -1,28 +0,0 @@
-# Only pass through changed files
-
-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.
-
-
-```js
-// npm install --save-dev gulp gulp-changed gulp-jscs gulp-uglify
-
-var gulp = require('gulp');
-var changed = require('gulp-changed');
-var jscs = require('gulp-jscs');
-var uglify = require('gulp-uglify');
-
-// we define some constants here so they can be reused
-var SRC = 'src/*.js';
-var DEST = 'dist';
-
-gulp.task('default', function() {
- return gulp.src(SRC)
- // the `changed` task needs to know the destination directory
- // upfront to be able to figure out which files changed
- .pipe(changed(DEST))
- // only files that has changed will pass through here
- .pipe(jscs())
- .pipe(uglify())
- .pipe(gulp.dest(DEST));
-});
-```
diff --git a/docs/recipes/rebuild-only-files-that-change.md b/docs/recipes/rebuild-only-files-that-change.md
deleted file mode 100644
index 245ddc4f..00000000
--- a/docs/recipes/rebuild-only-files-that-change.md
+++ /dev/null
@@ -1,16 +0,0 @@
-# Rebuild only files that change
-
-With [`gulp-watch`](https://github.com/floatdrop/gulp-watch):
-
-```js
-var gulp = require('gulp');
-var sass = require('gulp-sass');
-var watch = require('gulp-watch');
-
-gulp.task('default', function() {
- return gulp.src('sass/*.scss')
- .pipe(watch('sass/*.scss'))
- .pipe(sass())
- .pipe(gulp.dest('dist'));
-});
-```
diff --git a/docs/recipes/rollup-with-rollup-stream.md b/docs/recipes/rollup-with-rollup-stream.md
index abee2dd3..ef5dd4b4 100644
--- a/docs/recipes/rollup-with-rollup-stream.md
+++ b/docs/recipes/rollup-with-rollup-stream.md
@@ -1,17 +1,17 @@
# Rollup with rollup-stream
-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.
+Like Browserify, [Rollup](https://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.
## Basic usage
```js
-// npm install --save-dev rollup-stream vinyl-source-stream
+// npm install --save-dev gulp @rollup/stream@1 vinyl-source-stream
var gulp = require('gulp');
var rollup = require('rollup-stream');
var source = require('vinyl-source-stream');
gulp.task('rollup', function() {
return rollup({
- entry: './src/main.js'
+ input: './src/main.js'
})
// give the file the name you want to output with
@@ -24,7 +24,7 @@ gulp.task('rollup', function() {
## Usage with sourcemaps
```js
-// npm install --save-dev rollup-stream gulp-sourcemaps vinyl-source-stream vinyl-buffer
+// npm install --save-dev gulp @rollup/stream@1 gulp-sourcemaps vinyl-source-stream vinyl-buffer
// optional: npm install --save-dev gulp-rename
var gulp = require('gulp');
var rollup = require('rollup-stream');
@@ -35,8 +35,9 @@ var buffer = require('vinyl-buffer');
gulp.task('rollup', function() {
return rollup({
- entry: './src/main.js',
- sourceMap: true
+ input: './src/main.js',
+ sourcemap: true,
+ format: 'umd'
})
// point to the entry file.
diff --git a/docs/recipes/run-grunt-tasks-from-gulp.md b/docs/recipes/run-grunt-tasks-from-gulp.md
index cdd4f686..df2ca177 100644
--- a/docs/recipes/run-grunt-tasks-from-gulp.md
+++ b/docs/recipes/run-grunt-tasks-from-gulp.md
@@ -32,7 +32,7 @@ gulp.task('copy', function (done) {
```
-Now start the task with:
+Now start the task with:
`gulp copy`
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.**
diff --git a/docs/recipes/running-task-steps-per-folder.md b/docs/recipes/running-task-steps-per-folder.md
index 905f697b..03ff5007 100644
--- a/docs/recipes/running-task-steps-per-folder.md
+++ b/docs/recipes/running-task-steps-per-folder.md
@@ -36,21 +36,21 @@ function getFolders(dir) {
});
}
-gulp.task('scripts', function() {
+gulp.task('scripts', function(done) {
var folders = getFolders(scriptsPath);
-
+ if (folders.length === 0) return done(); // nothing to do!
var tasks = folders.map(function(folder) {
return gulp.src(path.join(scriptsPath, folder, '/**/*.js'))
// concat into foldername.js
.pipe(concat(folder + '.js'))
// write to output
- .pipe(gulp.dest(scriptsPath))
+ .pipe(gulp.dest(scriptsPath))
// minify
- .pipe(uglify())
+ .pipe(uglify())
// rename to folder.min.js
- .pipe(rename(folder + '.min.js'))
+ .pipe(rename(folder + '.min.js'))
// write to output again
- .pipe(gulp.dest(scriptsPath));
+ .pipe(gulp.dest(scriptsPath));
});
// process all remaining files in scriptsPath root into main.js and main.min.js files
diff --git a/docs/recipes/running-tasks-in-series.md b/docs/recipes/running-tasks-in-series.md
deleted file mode 100644
index 4ac2e6c4..00000000
--- a/docs/recipes/running-tasks-in-series.md
+++ /dev/null
@@ -1,68 +0,0 @@
-# Running tasks in series, i.e. Task Dependency
-
-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:
-
-- give it a hint to tell it when the task is done,
-- and give it a hint that a task depends on completion of another.
-
-For these examples, let's presume you have two tasks, "one" and "two" that you specifically want to run in this order:
-
-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.
-
-2. In task "two" you add a hint telling the engine that it depends on completion of the first task.
-
-So this example would look like:
-
-```js
-var gulp = require('gulp');
-
-// takes in a callback so the engine knows when it'll be done
-gulp.task('one', function (cb) {
- // do stuff -- async or otherwise
- fs.writeFile('filename', 'data', opts, function (err) {
- cb(err); // if err is not null and not undefined, the orchestration will stop, and 'two' will not run
- });
-});
-
-// identifies a dependent task must be complete before this one begins
-gulp.task('two', ['one'], function() {
- // task 'one' is done now
-});
-
-gulp.task('default', ['one', 'two']);
-// alternatively: gulp.task('default', ['two']);
-```
-
-Another example, which returns the stream instead of using a callback:
-
-```js
-var gulp = require('gulp');
-var del = require('del'); // rm -rf
-
-gulp.task('clean', function() {
- return del(['output']);
-});
-
-gulp.task('templates', ['clean'], function() {
- var stream = gulp.src(['src/templates/*.hbs'])
- // do some concatenation, minification, etc.
- .pipe(gulp.dest('output/templates/'));
- return stream; // return the stream as the completion hint
-
-});
-
-gulp.task('styles', ['clean'], function() {
- var stream = gulp.src(['src/styles/app.less'])
- // do some hinting, minification, etc.
- .pipe(gulp.dest('output/css/app.css'));
- return stream;
-});
-
-gulp.task('build', ['templates', 'styles']);
-
-// templates and styles will be processed in parallel.
-// clean will be guaranteed to complete before either start.
-// clean will not be run twice, even though it is called as a dependency twice.
-
-gulp.task('default', ['build']);
-```
diff --git a/docs/recipes/server-with-livereload-and-css-injection.md b/docs/recipes/server-with-livereload-and-css-injection.md
index 78a9b38f..ee400b8f 100644
--- a/docs/recipes/server-with-livereload-and-css-injection.md
+++ b/docs/recipes/server-with-livereload-and-css-injection.md
@@ -1,11 +1,11 @@
# Server with live-reloading and CSS injection
-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.
+With [BrowserSync](https://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.
-First install the module:
+First install the modules:
```sh
-$ npm install --save-dev browser-sync
+$ npm install --save-dev gulp browser-sync
```
Then, considering the following file structure...
@@ -88,15 +88,15 @@ gulp.task('sass', function() {
});
// watch Sass files for changes, run the Sass preprocessor with the 'sass' task and reload
-gulp.task('serve', ['sass'], function() {
+gulp.task('serve', gulp.series('sass', function() {
browserSync({
server: {
baseDir: 'app'
}
});
- gulp.watch('app/scss/*.scss', ['sass']);
-});
+ gulp.watch('scss/*.scss', gulp.series('sass'));
+}));
```
and including the pre-processed CSS in `index.html`:
@@ -118,5 +118,5 @@ gulp serve
## Extras
-- Live reload, CSS injection and scroll/form syncing works seamlessly inside of [BrowserStack](http://www.browserstack.com/) virtual machines.
+- Live reload, CSS injection and scroll/form syncing works seamlessly inside of [BrowserStack](https://www.browserstack.com/) virtual machines.
- Set `tunnel: true` to view your local site at a public URL (complete with all BrowserSync features).
diff --git a/docs/recipes/specifying-a-cwd.md b/docs/recipes/specifying-a-cwd.md
deleted file mode 100644
index eba0ceca..00000000
--- a/docs/recipes/specifying-a-cwd.md
+++ /dev/null
@@ -1,23 +0,0 @@
-# Specifying a new cwd (current working directory)
-
-This is helpful for projects using a nested directory structure, such as:
-
-```
-/project
- /layer1
- /layer2
-```
-
-You can use the gulp CLI option `--cwd`.
-
-From the `project/` directory:
-
-```sh
-gulp --cwd layer1
-```
-
-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):
-
-```js
-gulp.src('./some/dir/**/*.js', { cwd: 'public' });
-```
diff --git a/docs/recipes/split-tasks-across-multiple-files.md b/docs/recipes/split-tasks-across-multiple-files.md
deleted file mode 100644
index fb820ad1..00000000
--- a/docs/recipes/split-tasks-across-multiple-files.md
+++ /dev/null
@@ -1,47 +0,0 @@
-# Split tasks across multiple files
-
-If your `gulpfile.js` is starting to grow too large, you can split
-the tasks into separate files using one of the methods below.
-
-> Be advised, that this approach is [considered deprecated][deprecated]
-> and could lead to problems when migrating to the `gulp 4`.
-
-
-## Using `gulp-require-tasks`
-
-You can use the [gulp-require-tasks][gulp-require-tasks]
-module to automatically load all your tasks from the individual files.
-
-Please see the [module's README][gulp-require-tasks] for up-to-date instructions.
-
-## Using `require-dir`
-
-You can also use the [require-dir][require-dir] module to load your tasks manually.
-
-Imagine the following file structure:
-
-```
-gulpfile.js
-tasks/
-├── dev.js
-├── release.js
-└── test.js
-```
-
-Install the `require-dir` module:
-
-```sh
-npm install --save-dev require-dir
-```
-
-Add the following lines to your `gulpfile.js` file:
-
-```js
-var requireDir = require('require-dir');
-var tasks = requireDir('./tasks');
-```
-
-
- [gulp-require-tasks]: https://github.com/betsol/gulp-require-tasks
- [require-dir]: https://github.com/aseemk/requireDir
- [deprecated]: https://github.com/gulpjs/gulp/pull/1554#issuecomment-202614391
diff --git a/docs/recipes/using-external-config-file.md b/docs/recipes/using-external-config-file.md
deleted file mode 100644
index 5c83779d..00000000
--- a/docs/recipes/using-external-config-file.md
+++ /dev/null
@@ -1,48 +0,0 @@
-# Using external config file
-
-Beneficial because it's keeping tasks DRY and config.json can be used by another task runner, like `grunt`.
-
--
-
-###### `config.json`
-
-```json
-{
- "desktop" : {
- "src" : [
- "dev/desktop/js/**/*.js",
- "!dev/desktop/js/vendor/**"
- ],
- "dest" : "build/desktop/js"
- },
- "mobile" : {
- "src" : [
- "dev/mobile/js/**/*.js",
- "!dev/mobile/js/vendor/**"
- ],
- "dest" : "build/mobile/js"
- }
-}
-```
-
--
-
-###### `gulpfile.js`
-
-```js
-// npm install --save-dev gulp gulp-uglify
-var gulp = require('gulp');
-var uglify = require('gulp-uglify');
-var config = require('./config.json');
-
-function doStuff(cfg) {
- return gulp.src(cfg.src)
- .pipe(uglify())
- .pipe(gulp.dest(cfg.dest));
-}
-
-gulp.task('dry', function() {
- doStuff(config.desktop);
- doStuff(config.mobile);
-});
-```
diff --git a/docs/support/for-enterprise.md b/docs/support/for-enterprise.md
new file mode 100644
index 00000000..2e2b2694
--- /dev/null
+++ b/docs/support/for-enterprise.md
@@ -0,0 +1,53 @@
+
+
+# Gulp for enterprise
+
+Available as part of the Tidelift Subscription.
+
+Tidelift is working with the maintainers of Gulp and thousands of other
+open source projects to deliver commercial support and maintenance for the open source dependencies you use to build your applications. Save time, reduce risk, and improve code health, while paying the maintainers of the exact dependencies you use.
+
+Learn more
+
+Request a demo
+
+## Enterprise-ready open source software—managed for you
+
+The Tidelift Subscription is a managed open source subscription for application dependencies covering millions of open source projects across JavaScript, Python, Java, PHP, Ruby, .NET, and more.
+
+Your subscription includes:
+
+* **Security updates**
+
+ Tidelift’s security response team coordinates patches for new breaking security vulnerabilities and alerts immediately through a private channel, so your software supply chain is always secure.
+
+* **Licensing verification and indemnification**
+
+ Tidelift verifies license information to enable easy policy enforcement and adds intellectual property indemnification to cover creators and users in case something goes wrong. You always have a 100% up-to-date bill of materials for your dependencies to share with your legal team, customers, or partners.
+
+* **Maintenance and code improvement**
+
+ Tidelift ensures the software you rely on keeps working as long as you need it to work. Your managed dependencies are actively maintained and we recruit additional maintainers where required.
+
+* **Package selection and version guidance**
+
+ We help you choose the best open source packages from the start—and then guide you through updates to stay on the best releases as new issues arise.
+
+* **Roadmap input**
+
+ Take a seat at the table with the creators behind the software you use. Tidelift’s participating maintainers earn more income as their software is used by more subscribers, so they’re interested in knowing what you need.
+
+* **Tooling and cloud integration**
+
+ Tidelift works with GitHub, GitLab, BitBucket, and more. We support every cloud platform (and other deployment targets, too).
+
+The end result? All of the capabilities you expect from commercial-grade software, for the full breadth of open source you use. That means less time grappling with esoteric open source trivia, and more time building your own applications—and your business.
+
+Learn more
+
+Request a demo
diff --git a/docs/writing-a-plugin/README.md b/docs/writing-a-plugin/README.md
index c301b695..bf2b54b6 100644
--- a/docs/writing-a-plugin/README.md
+++ b/docs/writing-a-plugin/README.md
@@ -11,18 +11,18 @@ If you plan to create your own Gulp plugin, you will save time by reading the fu
### Streaming file objects
-A gulp plugin always returns a stream in [object mode](http://nodejs.org/api/stream.html#stream_object_mode) that does the following:
+A gulp plugin always returns a stream in [object mode](https://nodejs.org/api/stream.html#stream_object_mode) that does the following:
-1. Takes in [vinyl File objects](http://github.com/gulpjs/vinyl)
-2. Outputs [vinyl File objects](http://github.com/gulpjs/vinyl) (via `transform.push()` and/or the plugin's callback function)
+1. Takes in [vinyl File objects](https://github.com/gulpjs/vinyl)
+2. Outputs [vinyl File objects](https://github.com/gulpjs/vinyl) (via `transform.push()` and/or the plugin's callback function)
-These are known as [transform streams](http://nodejs.org/api/stream.html#stream_class_stream_transform_1)
+These are known as [transform streams](https://nodejs.org/api/stream.html#stream_class_stream_transform_1)
(also sometimes called through streams).
Transform streams are streams that are readable and writable; they manipulate objects as they're being passed through.
All gulp plugins essentially boil down to this:
```js
-var Transform = require('transform');
+var Transform = require('stream').Transform;
module.exports = function() {
// Monkey patch Transform or create your own subclass,
@@ -44,7 +44,7 @@ module.exports = function() {
};
```
-Many plugins use the [through2](https://github.com/rvagg/through2/) module to simplify their code:
+Alternatively you could pass your transform and flush functions to the `Transform` constructor or even extend `Transform` with ES6 classes, as described by the [Node.js docs](https://nodejs.org/docs/latest/api/stream.html#stream_implementing_a_transform_stream). However, many plugins prefer to use the [through2](https://github.com/rvagg/through2/) module to simplify their code:
```js
var through = require('through2'); // npm install --save through2
@@ -126,7 +126,7 @@ A simple example showing how to detect & handle each form is provided below, for
approach follow the links above.
```js
-var PluginError = require('gulp-util').PluginError;
+var PluginError = require('plugin-error');
// consts
var PLUGIN_NAME = 'gulp-example';
@@ -176,11 +176,10 @@ if (someCondition) {
## Useful resources
-* [File object](https://github.com/gulpjs/gulp-util/#new-fileobj)
-* [PluginError](https://github.com/gulpjs/gulp-util#new-pluginerrorpluginname-message-options)
-* [event-stream](https://github.com/dominictarr/event-stream)
-* [BufferStream](https://github.com/nfroidure/BufferStream)
-* [gulp-util](https://github.com/gulpjs/gulp-util)
+* [File object](https://github.com/gulpjs/vinyl)
+* [PluginError](https://github.com/gulpjs/plugin-error)
+* [through2](https://www.npmjs.com/package/through2)
+* [bufferstreams](https://www.npmjs.com/package/bufferstreams)
## Sample plugins
@@ -194,7 +193,7 @@ if (someCondition) {
If you're unfamiliar with streams, you will need to read up on them:
-* https://github.com/substack/stream-handbook (a MUST read)
-* http://nodejs.org/api/stream.html
+* https://www.freecodecamp.org/news/node-js-streams-everything-you-need-to-know-c9141306be93/
+* https://nodejs.org/api/stream.html
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.
diff --git a/docs/writing-a-plugin/dealing-with-streams.md b/docs/writing-a-plugin/dealing-with-streams.md
index 57d390b9..777b11bf 100644
--- a/docs/writing-a-plugin/dealing-with-streams.md
+++ b/docs/writing-a-plugin/dealing-with-streams.md
@@ -12,8 +12,7 @@ Let's implement a plugin prepending some text to files. This plugin supports all
```js
var through = require('through2');
-var gutil = require('gulp-util');
-var PluginError = gutil.PluginError;
+var PluginError = require('plugin-error');
// consts
const PLUGIN_NAME = 'gulp-prefixer';
@@ -76,4 +75,3 @@ gulp.src('files/**/*.js', { buffer: false })
## Some plugins using streams
* [gulp-svgicons2svgfont](https://github.com/nfroidure/gulp-svgiconstosvgfont)
-
diff --git a/docs/writing-a-plugin/guidelines.md b/docs/writing-a-plugin/guidelines.md
index 8d97e4e9..b810ad8a 100644
--- a/docs/writing-a-plugin/guidelines.md
+++ b/docs/writing-a-plugin/guidelines.md
@@ -30,7 +30,7 @@
- If you encounter an error **outside** the stream, such as invalid configuration while creating the stream, you may throw it.
1. Prefix any errors with the name of your plugin
- For example: `gulp-replace: Cannot do regexp replace on a stream`
- - Use gulp-util's [PluginError](https://github.com/gulpjs/gulp-util#new-pluginerrorpluginname-message-options) class to make this easy
+ - Use [PluginError](https://github.com/gulpjs/plugin-error) module to make this easy
1. Name your plugin appropriately: it should begin with "gulp-" if it is a gulp plugin
- If it is not a gulp plugin, it should not begin with "gulp-"
1. The type of `file.contents` should always be the same going out as it was when it came in
@@ -58,8 +58,7 @@ npm is open for everyone, and you are free to make whatever you want but these g
```js
// through2 is a thin wrapper around node transform streams
var through = require('through2');
-var gutil = require('gulp-util');
-var PluginError = gutil.PluginError;
+var PluginError = require('plugin-error');
// Consts
const PLUGIN_NAME = 'gulp-prefixer';
diff --git a/docs/writing-a-plugin/using-buffers.md b/docs/writing-a-plugin/using-buffers.md
index ae021448..e35e41b5 100644
--- a/docs/writing-a-plugin/using-buffers.md
+++ b/docs/writing-a-plugin/using-buffers.md
@@ -9,8 +9,7 @@ If your plugin is relying on a buffer based library, you will probably choose to
```js
var through = require('through2');
-var gutil = require('gulp-util');
-var PluginError = gutil.PluginError;
+var PluginError = require('plugin-error');
// consts
const PLUGIN_NAME = 'gulp-prefixer';
diff --git a/index.js b/index.js
index 42bc69b3..8b735212 100644
--- a/index.js
+++ b/index.js
@@ -1,63 +1,55 @@
'use strict';
var util = require('util');
-var Orchestrator = require('orchestrator');
-var gutil = require('gulp-util');
-var deprecated = require('deprecated');
+var Undertaker = require('undertaker');
var vfs = require('vinyl-fs');
+var watch = require('glob-watcher');
function Gulp() {
- Orchestrator.call(this);
+ Undertaker.call(this);
+
+ // Bind the functions for destructuring
+ this.watch = this.watch.bind(this);
+ this.task = this.task.bind(this);
+ this.series = this.series.bind(this);
+ this.parallel = this.parallel.bind(this);
+ this.registry = this.registry.bind(this);
+ this.tree = this.tree.bind(this);
+ this.lastRun = this.lastRun.bind(this);
+ this.src = this.src.bind(this);
+ this.dest = this.dest.bind(this);
+ this.symlink = this.symlink.bind(this);
}
-util.inherits(Gulp, Orchestrator);
-
-Gulp.prototype.task = Gulp.prototype.add;
-Gulp.prototype.run = function() {
- // `run()` is deprecated as of 3.5 and will be removed in 4.0
- // Use task dependencies instead
-
- // Impose our opinion of "default" tasks onto orchestrator
- var tasks = arguments.length ? arguments : ['default'];
-
- this.start.apply(this, tasks);
-};
+util.inherits(Gulp, Undertaker);
Gulp.prototype.src = vfs.src;
Gulp.prototype.dest = vfs.dest;
-Gulp.prototype.watch = function(glob, opt, fn) {
- if (typeof opt === 'function' || Array.isArray(opt)) {
- fn = opt;
- opt = null;
+Gulp.prototype.symlink = vfs.symlink;
+Gulp.prototype.watch = function(glob, opt, task) {
+ if (typeof opt === 'string' || typeof task === 'string' ||
+ Array.isArray(opt) || Array.isArray(task)) {
+ throw new Error('watching ' + glob + ': watch task has to be ' +
+ 'a function (optionally generated by using gulp.parallel ' +
+ 'or gulp.series)');
}
- // Array of tasks given
- if (Array.isArray(fn)) {
- return vfs.watch(glob, opt, function() {
- this.start.apply(this, fn);
- }.bind(this));
+ if (typeof opt === 'function') {
+ task = opt;
+ opt = {};
}
- return vfs.watch(glob, opt, fn);
+ opt = opt || {};
+
+ var fn;
+ if (typeof task === 'function') {
+ fn = this.parallel(task);
+ }
+
+ return watch(glob, opt, fn);
};
// Let people use this class from our instance
Gulp.prototype.Gulp = Gulp;
-// Deprecations
-deprecated.field('gulp.env has been deprecated. ' +
- 'Use your own CLI parser instead. ' +
- 'We recommend using yargs or minimist.',
- console.warn,
- Gulp.prototype,
- 'env',
- gutil.env
-);
-
-Gulp.prototype.run = deprecated.method('gulp.run() has been deprecated. ' +
- 'Use task dependencies or gulp.watch task triggering instead.',
- console.warn,
- Gulp.prototype.run
-);
-
var inst = new Gulp();
module.exports = inst;
diff --git a/index.mjs b/index.mjs
new file mode 100644
index 00000000..f1d808dd
--- /dev/null
+++ b/index.mjs
@@ -0,0 +1,16 @@
+import gulp from "./index.js";
+
+// These are bound to the gulp instance in our CommonJS file
+// so it is okay to reassign them to export
+export const watch = gulp.watch;
+export const task = gulp.task;
+export const series = gulp.series;
+export const parallel = gulp.parallel;
+export const registry = gulp.registry;
+export const tree = gulp.tree;
+export const lastRun = gulp.lastRun;
+export const src = gulp.src;
+export const dest = gulp.dest;
+export const symlink = gulp.symlink;
+
+export default gulp;
diff --git a/lib/completion.js b/lib/completion.js
deleted file mode 100644
index 7000250b..00000000
--- a/lib/completion.js
+++ /dev/null
@@ -1,22 +0,0 @@
-'use strict';
-
-var fs = require('fs');
-var path = require('path');
-
-module.exports = function(name) {
- if (typeof name !== 'string') {
- throw new Error('Missing completion type');
- }
- var file = path.join(__dirname, '../completion', name);
- try {
- console.log(fs.readFileSync(file, 'utf8'));
- process.exit(0);
- } catch (err) {
- console.log(
- 'echo "gulp autocompletion rules for',
- '\'' + name + '\'',
- 'not found"'
- );
- process.exit(5);
- }
-};
diff --git a/lib/taskTree.js b/lib/taskTree.js
deleted file mode 100644
index accb1a77..00000000
--- a/lib/taskTree.js
+++ /dev/null
@@ -1,14 +0,0 @@
-'use strict';
-
-module.exports = function(tasks) {
- return Object.keys(tasks)
- .reduce(function(prev, task) {
- prev.nodes.push({
- label: task,
- nodes: tasks[task].dep,
- });
- return prev;
- }, {
- nodes: [],
- });
-};
diff --git a/package.json b/package.json
index edf2e2e8..9263e217 100644
--- a/package.json
+++ b/package.json
@@ -1,70 +1,74 @@
{
"name": "gulp",
- "description": "The streaming build system",
- "version": "3.9.1",
- "homepage": "http://gulpjs.com",
- "repository": "gulpjs/gulp",
- "author": "Fractal (http://wearefractal.com/)",
- "keywords": [
- "build",
- "stream",
- "system",
- "make",
- "tool",
- "asset",
- "pipeline"
+ "version": "5.0.1",
+ "description": "The streaming build system.",
+ "homepage": "https://gulpjs.com",
+ "author": "Gulp Team (https://gulpjs.com/)",
+ "contributors": [
+ "Eric Schoffstall ",
+ "Blaine Bublitz "
],
+ "repository": "gulpjs/gulp",
+ "license": "MIT",
+ "engines": {
+ "node": ">=10.13.0"
+ },
+ "main": "index.js",
+ "exports": {
+ ".": {
+ "import": "./index.mjs",
+ "require": "./index.js"
+ }
+ },
"files": [
+ "LICENSE",
"index.js",
- "lib",
- "bin",
- "completion",
- "gulp.1"
+ "index.mjs",
+ "bin"
],
"bin": {
"gulp": "./bin/gulp.js"
},
- "man": "gulp.1",
+ "scripts": {
+ "lint": "eslint .",
+ "pretest": "npm run lint",
+ "test": "nyc mocha --async-only"
+ },
"dependencies": {
- "archy": "^1.0.0",
- "chalk": "^1.0.0",
- "deprecated": "^0.0.1",
- "gulp-util": "^3.0.0",
- "interpret": "^1.0.0",
- "liftoff": "^2.1.0",
- "minimist": "^1.1.0",
- "orchestrator": "^0.3.0",
- "pretty-hrtime": "^1.0.0",
- "semver": "^4.1.0",
- "tildify": "^1.0.0",
- "v8flags": "^2.0.2",
- "vinyl-fs": "^0.3.0"
+ "glob-watcher": "^6.0.0",
+ "gulp-cli": "^3.1.0",
+ "undertaker": "^2.0.0",
+ "vinyl-fs": "^4.0.2"
},
"devDependencies": {
- "coveralls": "^2.7.0",
- "eslint": "^1.7.3",
- "eslint-config-gulp": "^2.0.0",
- "graceful-fs": "^3.0.0",
- "istanbul": "^0.3.0",
- "jscs": "^2.3.5",
- "jscs-preset-gulp": "^1.0.0",
- "marked-man": "^0.1.3",
- "mkdirp": "^0.5.0",
- "mocha": "^2.0.1",
- "mocha-lcov-reporter": "^0.0.1",
- "q": "^1.0.0",
- "rimraf": "^2.2.5",
- "should": "^5.0.1"
+ "eslint": "^7.0.0",
+ "eslint-config-gulp": "^5.0.0",
+ "eslint-plugin-node": "^11.1.0",
+ "expect": "^27.0.0",
+ "mkdirp": "^3.0.1",
+ "mocha": "^8.0.0",
+ "nyc": "^15.0.0",
+ "rimraf": "^3.0.0"
},
- "scripts": {
- "prepublish": "marked-man --name gulp docs/CLI.md > gulp.1",
- "lint": "eslint . && jscs *.js bin/ lib/ test/",
- "pretest": "npm run lint",
- "test": "mocha --reporter spec",
- "coveralls": "istanbul cover _mocha --report lcovonly -- -R spec && cat ./coverage/lcov.info | coveralls && rm -rf ./coverage"
+ "nyc": {
+ "reporter": [
+ "lcov",
+ "text-summary"
+ ]
},
- "engines": {
- "node": ">= 0.9"
+ "prettier": {
+ "singleQuote": true
},
- "license": "MIT"
+ "keywords": [
+ "build",
+ "stream",
+ "system",
+ "make",
+ "tool",
+ "asset",
+ "pipeline",
+ "series",
+ "parallel",
+ "streaming"
+ ]
}
diff --git a/test/.eslintrc b/test/.eslintrc
deleted file mode 100644
index 06b940f7..00000000
--- a/test/.eslintrc
+++ /dev/null
@@ -1,3 +0,0 @@
-{
- "extends": "gulp/test"
-}
diff --git a/test/.gitkeep b/test/.gitkeep
new file mode 100644
index 00000000..e69de29b
diff --git a/test/dest.js b/test/dest.js
index 90dcb9d0..d3d2dfb0 100644
--- a/test/dest.js
+++ b/test/dest.js
@@ -1,132 +1,136 @@
'use strict';
-var gulp = require('../');
-var should = require('should');
-var join = require('path').join;
+var fs = require('fs');
+var path = require('path');
+
+var expect = require('expect');
var rimraf = require('rimraf');
-var fs = require('graceful-fs');
-require('mocha');
+var gulp = require('../');
-var outpath = join(__dirname, './out-fixtures');
+var outpath = path.join(__dirname, './out-fixtures');
-describe('gulp output stream', function() {
- describe('dest()', function() {
- beforeEach(rimraf.bind(null, outpath));
- afterEach(rimraf.bind(null, outpath));
+describe('gulp.dest()', function() {
+ before(function () {
+ if (process.versions.node.startsWith("10.")) {
+ this.skip();
+ return;
+ }
+ });
- it('should return a stream', function(done) {
- var stream = gulp.dest(join(__dirname, './fixtures/'));
- should.exist(stream);
- should.exist(stream.on);
- done();
- });
+ beforeEach(rimraf.bind(null, outpath));
+ afterEach(rimraf.bind(null, outpath));
- it('should return a output stream that writes files', function(done) {
- var instream = gulp.src(join(__dirname, './fixtures/**/*.txt'));
- var outstream = gulp.dest(outpath);
- instream.pipe(outstream);
-
- outstream.on('error', done);
- outstream.on('data', function(file) {
- // Data should be re-emitted right
- should.exist(file);
- should.exist(file.path);
- should.exist(file.contents);
- join(file.path, '').should.equal(join(outpath, './copy/example.txt'));
- String(file.contents).should.equal('this is a test');
- });
- outstream.on('end', function() {
- fs.readFile(join(outpath, 'copy', 'example.txt'), function(err, contents) {
- should.not.exist(err);
- should.exist(contents);
- String(contents).should.equal('this is a test');
- done();
- });
+ it('should return a stream', function(done) {
+ var stream = gulp.dest(path.join(__dirname, './fixtures/'));
+ expect(stream).toBeDefined();
+ expect(stream.on).toBeDefined();
+ done();
+ });
+
+ it('should return a output stream that writes files', function(done) {
+ var instream = gulp.src('./fixtures/**/*.txt', { cwd: __dirname });
+ var outstream = gulp.dest(outpath);
+ instream.pipe(outstream);
+
+ var expectedContents = Buffer.from('this is a test');
+
+ outstream.on('error', done);
+ outstream.on('data', function(file) {
+ // Data should be re-emitted right
+ expect(file).toBeDefined();
+ expect(file.path).toBeDefined();
+ expect(file.contents).toBeDefined();
+ expect(file.path).toEqual(path.join(outpath, './copy/example.txt'));
+ expect(file.contents).toEqual(expectedContents);
+ });
+ outstream.on('end', function() {
+ fs.readFile(path.join(outpath, 'copy', 'example.txt'), function(err, contents) {
+ expect(err).toBeNull();
+ expect(contents).toBeDefined();
+ expect(contents).toEqual(expectedContents);
+ done();
});
});
+ });
- it('should return a output stream that does not write non-read files', function(done) {
- var instream = gulp.src(join(__dirname, './fixtures/**/*.txt'), { read: false });
- var outstream = gulp.dest(outpath);
- instream.pipe(outstream);
-
- outstream.on('error', done);
- outstream.on('data', function(file) {
- // Data should be re-emitted right
- should.exist(file);
- should.exist(file.path);
- should.not.exist(file.contents);
- join(file.path, '').should.equal(join(outpath, './copy/example.txt'));
- });
- outstream.on('end', function() {
- fs.readFile(join(outpath, 'copy', 'example.txt'), function(err, contents) {
- should.exist(err);
- should.not.exist(contents);
- done();
- });
+ it('should return a output stream that does not write non-read files', function(done) {
+ var instream = gulp.src('./fixtures/**/*.txt', { read: false, cwd: __dirname });
+ var outstream = gulp.dest(outpath);
+ instream.pipe(outstream);
+
+ outstream.on('error', done);
+ outstream.on('data', function(file) {
+ // Data should be re-emitted right
+ expect(file).toBeDefined();
+ expect(file.path).toBeDefined();
+ expect(file.contents).toBeNull();
+ expect(file.path).toEqual(path.join(outpath, './copy/example.txt'));
+ });
+ outstream.on('end', function() {
+ fs.readFile(path.join(outpath, 'copy', 'example.txt'), function(err, contents) {
+ expect(err).toBeDefined();
+ expect(contents).toBeUndefined();
+ done();
});
});
+ });
- it('should return a output stream that writes streaming files', function(done) {
- var instream = gulp.src(join(__dirname, './fixtures/**/*.txt'), { buffer: false });
- var outstream = instream.pipe(gulp.dest(outpath));
-
- outstream.on('error', done);
- outstream.on('data', function(file) {
- // Data should be re-emitted right
- should.exist(file);
- should.exist(file.path);
- should.exist(file.contents);
- join(file.path, '').should.equal(join(outpath, './copy/example.txt'));
- });
- outstream.on('end', function() {
- fs.readFile(join(outpath, 'copy', 'example.txt'), function(err, contents) {
- should.not.exist(err);
- should.exist(contents);
- String(contents).should.equal('this is a test');
- done();
- });
+ it('should return a output stream that writes streaming files', function(done) {
+ var instream = gulp.src('./fixtures/**/*.txt', { buffer: false, cwd: __dirname });
+ var outstream = instream.pipe(gulp.dest(outpath));
+
+ outstream.on('error', done);
+ outstream.on('data', function(file) {
+ // Data should be re-emitted right
+ expect(file).toBeDefined();
+ expect(file.path).toBeDefined();
+ expect(file.contents).toBeDefined();
+ expect(file.path).toEqual(path.join(outpath, './copy/example.txt'));
+ });
+ outstream.on('end', function() {
+ fs.readFile(path.join(outpath, 'copy', 'example.txt'), function(err, contents) {
+ expect(err).toBeNull();
+ expect(contents).toBeDefined();
+ expect(contents).toEqual(Buffer.from('this is a test'));
+ done();
});
});
+ });
- it('should return a output stream that writes streaming files into new directories', function(done) {
- testWriteDir({}, done);
- });
+ it('should return a output stream that writes streaming files into new directories', function(done) {
+ testWriteDir({ cwd: __dirname }, done);
+ });
- it('should return a output stream that writes streaming files into new directories (buffer: false)', function(done) {
- testWriteDir({ buffer: false }, done);
- });
+ it('should return a output stream that writes streaming files into new directories (buffer: false)', function(done) {
+ testWriteDir({ buffer: false, cwd: __dirname }, done);
+ });
- it('should return a output stream that writes streaming files into new directories (read: false)', function(done) {
- testWriteDir({ read: false }, done);
- });
+ it('should return a output stream that writes streaming files into new directories (read: false)', function(done) {
+ testWriteDir({ read: false, cwd: __dirname }, done);
+ });
- it('should return a output stream that writes streaming files into new directories (read: false, buffer: false)', function(done) {
- testWriteDir({ buffer: false, read: false }, done);
- });
+ it('should return a output stream that writes streaming files into new directories (read: false, buffer: false)', function(done) {
+ testWriteDir({ buffer: false, read: false, cwd: __dirname }, done);
+ });
- function testWriteDir(srcOptions, done) {
- var instream = gulp.src(join(__dirname, './fixtures/stuff'), srcOptions);
- var outstream = instream.pipe(gulp.dest(outpath));
+ function testWriteDir(srcOptions, done) {
+ var instream = gulp.src('./fixtures/stuff', srcOptions);
+ var outstream = instream.pipe(gulp.dest(outpath));
- outstream.on('error', done);
- outstream.on('data', function(file) {
- // Data should be re-emitted right
- should.exist(file);
- should.exist(file.path);
- join(file.path, '').should.equal(join(outpath, './stuff'));
- });
- outstream.on('end', function() {
- fs.exists(join(outpath, 'stuff'), function(exists) {
- /* Stinks that ok is an expression instead of a function call */
- /* jshint expr: true */
- should(exists).be.ok;
- /* jshint expr: false */
- done();
- });
+ outstream.on('error', done);
+ outstream.on('data', function(file) {
+ // Data should be re-emitted right
+ expect(file).toBeDefined();
+ expect(file.path).toBeDefined();
+ expect(file.path).toEqual(path.join(outpath, './stuff'));
+ });
+ outstream.on('end', function() {
+ fs.exists(path.join(outpath, 'stuff'), function(exists) {
+ expect(exists).toBeDefined();
+ done();
});
- }
+ });
+ }
- });
});
diff --git a/test/fixtures/gulpfiles/cjs/gulpfile.cjs b/test/fixtures/gulpfiles/cjs/gulpfile.cjs
new file mode 100644
index 00000000..b7e1a2d3
--- /dev/null
+++ b/test/fixtures/gulpfiles/cjs/gulpfile.cjs
@@ -0,0 +1,3 @@
+exports.default = function (done) {
+ done()
+}
diff --git a/test/fixtures/gulpfiles/mjs/gulpfile.mjs b/test/fixtures/gulpfiles/mjs/gulpfile.mjs
new file mode 100644
index 00000000..332e027c
--- /dev/null
+++ b/test/fixtures/gulpfiles/mjs/gulpfile.mjs
@@ -0,0 +1,30 @@
+import assert from "assert";
+import EventEmitter from "events";
+
+import gulp, {
+ watch,
+ task,
+ series,
+ parallel,
+ registry,
+ tree,
+ lastRun,
+ src,
+ dest,
+ symlink,
+} from 'gulp';
+
+export default function (done) {
+ assert(typeof watch === 'function');
+ assert(typeof task === 'function');
+ assert(typeof series === 'function');
+ assert(typeof parallel === 'function');
+ assert(typeof registry === 'function');
+ assert(typeof tree === 'function');
+ assert(typeof lastRun === 'function');
+ assert(typeof src === 'function');
+ assert(typeof dest === 'function');
+ assert(typeof symlink === 'function');
+ assert(gulp instanceof EventEmitter);
+ done();
+}
diff --git a/test/index.test.js b/test/index.test.js
new file mode 100644
index 00000000..de5f68c4
--- /dev/null
+++ b/test/index.test.js
@@ -0,0 +1,94 @@
+'use strict';
+
+var cp = require('child_process');
+var path = require('path');
+
+var expect = require('expect');
+
+var gulp = require('../');
+
+describe('gulp', function() {
+
+ describe('hasOwnProperty', function() {
+ it('src', function(done) {
+ expect(Object.prototype.hasOwnProperty.call(gulp, 'src')).toEqual(true);
+ done();
+ });
+
+ it('dest', function(done) {
+ expect(Object.prototype.hasOwnProperty.call(gulp, 'dest')).toEqual(true);
+ done();
+ });
+
+ it('symlink', function(done) {
+ expect(Object.prototype.hasOwnProperty.call(gulp, 'symlink')).toEqual(true);
+ done();
+ });
+
+ it('watch', function(done) {
+ expect(Object.prototype.hasOwnProperty.call(gulp, 'watch')).toEqual(true);
+ done();
+ });
+
+ it('task', function(done) {
+ expect(Object.prototype.hasOwnProperty.call(gulp, 'task')).toEqual(true);
+ done();
+ });
+
+ it('series', function(done) {
+ expect(Object.prototype.hasOwnProperty.call(gulp, 'series')).toEqual(true);
+ done();
+ });
+
+ it('parallel', function(done) {
+ expect(Object.prototype.hasOwnProperty.call(gulp, 'parallel')).toEqual(true);
+ done();
+ });
+
+ it('tree', function(done) {
+ expect(Object.prototype.hasOwnProperty.call(gulp, 'tree')).toEqual(true);
+ done();
+ });
+
+ it('lastRun', function(done) {
+ expect(Object.prototype.hasOwnProperty.call(gulp, 'lastRun')).toEqual(true);
+ done();
+ });
+
+ it('registry', function(done) {
+ expect(Object.prototype.hasOwnProperty.call(gulp, 'registry')).toEqual(true);
+ done();
+ });
+ });
+
+ it('can run against gulpfile.cjs', function (done) {
+ this.timeout(5000);
+
+ var cli = path.join(__dirname, '../bin/gulp.js');
+ var opts = { cwd: path.join(__dirname, 'fixtures/gulpfiles/cjs' ) };
+ cp.exec('node ' + cli, opts, function (err, stdout, stderr) {
+ expect(err).toBeNull();
+ expect(stdout).toMatch('gulpfile.cjs');
+ expect(stderr).toEqual('');
+ done();
+ });
+ });
+
+ it('can run against gulpfile.mjs', function (done) {
+ // Node v10 didn't support `exports` in package.json
+ if (process.version.startsWith('v10.')) {
+ this.skip();
+ }
+
+ this.timeout(5000);
+
+ var cli = path.join(__dirname, '../bin/gulp.js');
+ var opts = { cwd: path.join(__dirname, 'fixtures/gulpfiles/mjs' ) };
+ cp.exec('node ' + cli, opts, function (err, stdout, stderr) {
+ expect(err).toBeNull();
+ expect(stdout).toMatch('gulpfile.mjs');
+ expect(stderr).toEqual('');
+ done();
+ });
+ });
+});
diff --git a/test/src.js b/test/src.js
index a85f237d..d2c81611 100644
--- a/test/src.js
+++ b/test/src.js
@@ -1,152 +1,161 @@
'use strict';
+var path = require('path');
+
+var expect = require('expect');
+
var gulp = require('../');
-var should = require('should');
-var join = require('path').join;
-require('mocha');
+describe('gulp.src()', function() {
+ before(function () {
+ if (process.versions.node.startsWith("10.")) {
+ this.skip();
+ return;
+ }
+ });
+
+ it('should return a stream', function(done) {
+ var stream = gulp.src('./fixtures/*.coffee', { cwd: __dirname });
+ expect(stream).toBeDefined();
+ expect(stream.on).toBeDefined();
+ done();
+ });
-describe('gulp input stream', function() {
- describe('src()', function() {
- it('should return a stream', function(done) {
- var stream = gulp.src(join(__dirname, './fixtures/*.coffee'));
- should.exist(stream);
- should.exist(stream.on);
+ it('should return a input stream from a flat glob', function(done) {
+ var stream = gulp.src('./fixtures/*.coffee', { cwd: __dirname });
+ stream.on('error', done);
+ stream.on('data', function(file) {
+ expect(file).toBeDefined();
+ expect(file.path).toBeDefined();
+ expect(file.contents).toBeDefined();
+ expect(file.path).toEqual(path.join(__dirname, './fixtures/test.coffee'));
+ expect(file.contents).toEqual(Buffer.from('this is a test'));
+ });
+ stream.on('end', function() {
done();
});
- it('should return a input stream from a flat glob', function(done) {
- var stream = gulp.src(join(__dirname, './fixtures/*.coffee'));
- stream.on('error', done);
- stream.on('data', function(file) {
- should.exist(file);
- should.exist(file.path);
- should.exist(file.contents);
- join(file.path, '').should.equal(join(__dirname, './fixtures/test.coffee'));
- String(file.contents).should.equal('this is a test');
- });
- stream.on('end', function() {
- done();
- });
+ });
+
+ it('should return a input stream for multiple globs', function(done) {
+ var globArray = [
+ './fixtures/stuff/run.dmc',
+ './fixtures/stuff/test.dmc',
+ ];
+ var stream = gulp.src(globArray, { cwd: __dirname });
+
+ var files = [];
+ stream.on('error', done);
+ stream.on('data', function(file) {
+ expect(file).toBeDefined();
+ expect(file.path).toBeDefined();
+ files.push(file);
});
+ stream.on('end', function() {
+ expect(files.length).toEqual(2);
+ expect(files[0].path).toEqual(path.join(__dirname, globArray[0]));
+ expect(files[1].path).toEqual(path.join(__dirname, globArray[1]));
+ done();
+ });
+ });
- it('should return a input stream for multiple globs', function(done) {
- var globArray = [
- join(__dirname, './fixtures/stuff/run.dmc'),
- join(__dirname, './fixtures/stuff/test.dmc'),
- ];
- var stream = gulp.src(globArray);
+ it('should return a input stream for multiple globs, with negation', function(done) {
+ var expectedPath = path.join(__dirname, './fixtures/stuff/run.dmc');
+ var globArray = [
+ './fixtures/stuff/*.dmc',
+ '!fixtures/stuff/test.dmc',
+ ];
+ var stream = gulp.src(globArray, { cwd: __dirname });
- var files = [];
- stream.on('error', done);
- stream.on('data', function(file) {
- should.exist(file);
- should.exist(file.path);
- files.push(file);
- });
- stream.on('end', function() {
- files.length.should.equal(2);
- files[0].path.should.equal(globArray[0]);
- files[1].path.should.equal(globArray[1]);
- done();
- });
+ var files = [];
+ stream.on('error', done);
+ stream.on('data', function(file) {
+ expect(file).toBeDefined();
+ expect(file.path).toBeDefined();
+ files.push(file);
});
+ stream.on('end', function() {
+ expect(files.length).toEqual(1);
+ expect(files[0].path).toEqual(expectedPath);
+ done();
+ });
+ });
- it('should return a input stream for multiple globs, with negation', function(done) {
- var expectedPath = join(__dirname, './fixtures/stuff/run.dmc');
- var globArray = [
- join(__dirname, './fixtures/stuff/*.dmc'),
- '!' + join(__dirname, './fixtures/stuff/test.dmc'),
- ];
- var stream = gulp.src(globArray);
+ it('should return a input stream with no contents when read is false', function(done) {
+ var stream = gulp.src('./fixtures/*.coffee', { read: false, cwd: __dirname });
+ stream.on('error', done);
+ stream.on('data', function(file) {
+ expect(file).toBeDefined();
+ expect(file.path).toBeDefined();
+ expect(file.contents).toBeNull();
+ expect(file.path).toEqual(path.join(__dirname, './fixtures/test.coffee'));
+ });
+ stream.on('end', function() {
+ done();
+ });
+ });
- var files = [];
- stream.on('error', done);
- stream.on('data', function(file) {
- should.exist(file);
- should.exist(file.path);
- files.push(file);
+ it('should return a input stream with contents as stream when buffer is false', function(done) {
+ var stream = gulp.src('./fixtures/*.coffee', { buffer: false, cwd: __dirname });
+ stream.on('error', done);
+ stream.on('data', function(file) {
+ expect(file).toBeDefined();
+ expect(file.path).toBeDefined();
+ expect(file.contents).toBeDefined();
+ var buf = '';
+ file.contents.on('data', function(d) {
+ buf += d;
});
- stream.on('end', function() {
- files.length.should.equal(1);
- files[0].path.should.equal(expectedPath);
+ file.contents.on('end', function() {
+ expect(buf).toEqual('this is a test');
done();
});
+ expect(file.path).toEqual(path.join(__dirname, './fixtures/test.coffee'));
});
+ });
- it('should return a input stream with no contents when read is false', function(done) {
- var stream = gulp.src(join(__dirname, './fixtures/*.coffee'), { read: false });
- stream.on('error', done);
- stream.on('data', function(file) {
- should.exist(file);
- should.exist(file.path);
- should.not.exist(file.contents);
- join(file.path, '').should.equal(join(__dirname, './fixtures/test.coffee'));
- });
- stream.on('end', function() {
- done();
- });
+ it('should return a input stream from a deep glob', function(done) {
+ var stream = gulp.src('./fixtures/**/*.jade', { cwd: __dirname });
+ stream.on('error', done);
+ stream.on('data', function(file) {
+ expect(file).toBeDefined();
+ expect(file.path).toBeDefined();
+ expect(file.contents).toBeDefined();
+ expect(file.path).toEqual(path.join(__dirname, './fixtures/test/run.jade'));
+ expect(file.contents).toEqual(Buffer.from('test template'));
});
- it('should return a input stream with contents as stream when buffer is false', function(done) {
- var stream = gulp.src(join(__dirname, './fixtures/*.coffee'), { buffer: false });
- stream.on('error', done);
- stream.on('data', function(file) {
- should.exist(file);
- should.exist(file.path);
- should.exist(file.contents);
- var buf = '';
- file.contents.on('data', function(d) {
- buf += d;
- });
- file.contents.on('end', function() {
- buf.should.equal('this is a test');
- done();
- });
- join(file.path, '').should.equal(join(__dirname, './fixtures/test.coffee'));
- });
+ stream.on('end', function() {
+ done();
});
- it('should return a input stream from a deep glob', function(done) {
- var stream = gulp.src(join(__dirname, './fixtures/**/*.jade'));
- stream.on('error', done);
- stream.on('data', function(file) {
- should.exist(file);
- should.exist(file.path);
- should.exist(file.contents);
- join(file.path, '').should.equal(join(__dirname, './fixtures/test/run.jade'));
- String(file.contents).should.equal('test template');
- });
- stream.on('end', function() {
- done();
- });
+ });
+
+ it('should return a input stream from a deeper glob', function(done) {
+ var stream = gulp.src('./fixtures/**/*.dmc', { cwd: __dirname });
+ var a = 0;
+ stream.on('error', done);
+ stream.on('data', function() {
+ ++a;
});
- it('should return a input stream from a deeper glob', function(done) {
- var stream = gulp.src(join(__dirname, './fixtures/**/*.dmc'));
- var a = 0;
- stream.on('error', done);
- stream.on('data', function() {
- ++a;
- });
- stream.on('end', function() {
- a.should.equal(2);
- done();
- });
+ stream.on('end', function() {
+ expect(a).toEqual(2);
+ done();
});
+ });
- it('should return a file stream from a flat path', function(done) {
- var a = 0;
- var stream = gulp.src(join(__dirname, './fixtures/test.coffee'));
- stream.on('error', done);
- stream.on('data', function(file) {
- ++a;
- should.exist(file);
- should.exist(file.path);
- should.exist(file.contents);
- join(file.path, '').should.equal(join(__dirname, './fixtures/test.coffee'));
- String(file.contents).should.equal('this is a test');
- });
- stream.on('end', function() {
- a.should.equal(1);
- done();
- });
+ it('should return a file stream from a flat path', function(done) {
+ var a = 0;
+ var stream = gulp.src(path.join(__dirname, './fixtures/test.coffee'));
+ stream.on('error', done);
+ stream.on('data', function(file) {
+ ++a;
+ expect(file).toBeDefined();
+ expect(file.path).toBeDefined();
+ expect(file.contents).toBeDefined();
+ expect(file.path).toEqual(path.join(__dirname, './fixtures/test.coffee'));
+ expect(file.contents).toEqual(Buffer.from('this is a test'));
+ });
+ stream.on('end', function() {
+ expect(a).toEqual(1);
+ done();
});
});
});
diff --git a/test/taskTree.js b/test/taskTree.js
deleted file mode 100644
index 199eb1f2..00000000
--- a/test/taskTree.js
+++ /dev/null
@@ -1,42 +0,0 @@
-'use strict';
-
-var taskTree = require('../lib/taskTree');
-var should = require('should');
-
-require('mocha');
-
-describe('taskTree()', function() {
- it('should form a tree properly', function(done) {
- should.exist(taskTree); // Lol shutup jshint
-
- var tasks = {
- test: {
- dep: ['abc', 'def'],
- },
- abc: {
- dep: ['def'],
- },
- def: {
- dep: [],
- },
- };
-
- var expectTree = {
- nodes: [
- {
- label: 'test',
- nodes: ['abc', 'def'],
- }, {
- label: 'abc',
- nodes: ['def'],
- }, {
- label: 'def',
- nodes: [],
- },
- ],
- };
-
- taskTree(tasks).should.eql(expectTree);
- done();
- });
-});
diff --git a/test/tasks.js b/test/tasks.js
deleted file mode 100644
index d5dcea19..00000000
--- a/test/tasks.js
+++ /dev/null
@@ -1,157 +0,0 @@
-'use strict';
-
-var gulp = require('../');
-var Q = require('q');
-var should = require('should');
-require('mocha');
-
-describe('gulp tasks', function() {
- describe('task()', function() {
- it('should define a task', function(done) {
- var fn;
- fn = function() {};
- gulp.task('test', fn);
- should.exist(gulp.tasks.test);
- gulp.tasks.test.fn.should.equal(fn);
- gulp.reset();
- done();
- });
- });
- describe('run()', function() {
- it('should run multiple tasks', function(done) {
- var a, fn, fn2;
- a = 0;
- fn = function() {
- this.should.equal(gulp);
- ++a;
- };
- fn2 = function() {
- this.should.equal(gulp);
- ++a;
- };
- gulp.task('test', fn);
- gulp.task('test2', fn2);
- gulp.run('test', 'test2');
- a.should.equal(2);
- gulp.reset();
- done();
- });
- it('should run all tasks when call run() multiple times', function(done) {
- var a, fn, fn2;
- a = 0;
- fn = function() {
- this.should.equal(gulp);
- ++a;
- };
- fn2 = function() {
- this.should.equal(gulp);
- ++a;
- };
- gulp.task('test', fn);
- gulp.task('test2', fn2);
- gulp.run('test');
- gulp.run('test2');
- a.should.equal(2);
- gulp.reset();
- done();
- });
- it('should run all async promise tasks', function(done) {
- var a, fn, fn2;
- a = 0;
- fn = function() {
- var deferred = Q.defer();
- setTimeout(function() {
- ++a;
- deferred.resolve();
- }, 1);
- return deferred.promise;
- };
- fn2 = function() {
- var deferred = Q.defer();
- setTimeout(function() {
- ++a;
- deferred.resolve();
- }, 1);
- return deferred.promise;
- };
- gulp.task('test', fn);
- gulp.task('test2', fn2);
- gulp.run('test');
- gulp.run('test2', function() {
- gulp.isRunning.should.equal(false);
- a.should.equal(2);
- gulp.reset();
- done();
- });
- gulp.isRunning.should.equal(true);
- });
- it('should run all async callback tasks', function(done) {
- var a, fn, fn2;
- a = 0;
- fn = function(cb) {
- setTimeout(function() {
- ++a;
- cb(null);
- }, 1);
- };
- fn2 = function(cb) {
- setTimeout(function() {
- ++a;
- cb(null);
- }, 1);
- };
- gulp.task('test', fn);
- gulp.task('test2', fn2);
- gulp.run('test');
- gulp.run('test2', function() {
- gulp.isRunning.should.equal(false);
- a.should.equal(2);
- gulp.reset();
- done();
- });
- gulp.isRunning.should.equal(true);
- });
- it('should emit task_not_found and throw an error when task is not defined', function(done) {
- gulp.on('task_not_found', function(err) {
- should.exist(err);
- should.exist(err.task);
- err.task.should.equal('test');
- gulp.reset();
- done();
- });
- try {
- gulp.run('test');
- } catch (err) {
- should.exist(err);
- }
- });
- it('should run task scoped to gulp', function(done) {
- var a, fn;
- a = 0;
- fn = function() {
- this.should.equal(gulp);
- ++a;
- };
- gulp.task('test', fn);
- gulp.run('test');
- a.should.equal(1);
- gulp.isRunning.should.equal(false);
- gulp.reset();
- done();
- });
- it('should run default task scoped to gulp', function(done) {
- var a, fn;
- a = 0;
- fn = function() {
- this.should.equal(gulp);
- ++a;
- };
- gulp.task('default', fn);
- gulp.run();
- a.should.equal(1);
- gulp.isRunning.should.equal(false);
- gulp.reset();
- done();
- });
- });
-});
diff --git a/test/watch.js b/test/watch.js
index e299cf90..eab1ec01 100644
--- a/test/watch.js
+++ b/test/watch.js
@@ -1,179 +1,257 @@
'use strict';
-var gulp = require('../');
-var fs = require('graceful-fs');
-var rimraf = require('rimraf');
-var mkdirp = require('mkdirp');
+/* eslint-disable no-use-before-define */
+
+var fs = require('fs');
var path = require('path');
-var should = require('should');
-require('mocha');
+var expect = require('expect');
+var rimraf = require('rimraf');
+var mkdirp = require('mkdirp').mkdirp;
+
+var gulp = require('../');
var outpath = path.join(__dirname, './out-fixtures');
-describe('gulp', function() {
- describe('watch()', function() {
- beforeEach(rimraf.bind(null, outpath));
- beforeEach(mkdirp.bind(null, outpath));
- afterEach(rimraf.bind(null, outpath));
-
- var tempFileContent = 'A test generated this file and it is safe to delete';
-
- var writeTimeout = 125; // Wait for it to get to the filesystem
- var writeFileWait = function(name, content, cb) {
- if (!cb) {
- cb = function() {};
- }
- setTimeout(function() {
- fs.writeFile(name, content, cb);
- }, writeTimeout);
- };
-
- it('should call the function when file changes: no options', function(done) {
-
- // Arrange
- var tempFile = path.join(outpath, 'watch-func.txt');
- fs.writeFile(tempFile, tempFileContent, function() {
-
- // Assert: it works if it calls done
- var watcher = gulp.watch(tempFile, function(evt) {
- should.exist(evt);
- should.exist(evt.path);
- should.exist(evt.type);
- evt.type.should.equal('changed');
- evt.path.should.equal(path.resolve(tempFile));
- watcher.end();
- done();
- });
-
- // Act: change file
- writeFileWait(tempFile, tempFileContent + ' changed');
- });
+var tempFileContent = 'A test generated this file and it is safe to delete';
+
+function createTempFile(path) {
+ fs.writeFileSync(path, tempFileContent);
+}
+
+function updateTempFile(path) {
+ setTimeout(function() {
+ fs.appendFileSync(path, ' changed');
+ }, 125);
+}
+
+function removeTempFile(path) {
+ setTimeout(function() {
+ fs.unlinkSync(path);
+ }, 125);
+}
+
+describe('gulp.watch()', function() {
+ beforeEach(function (done) {
+ rimraf(outpath, done);
+ });
+ beforeEach(function () {
+ return mkdirp(outpath);
+ });
+ afterEach(function (done) {
+ rimraf(outpath, done);
+ });
+
+ it('should call the function when file changes: no options', function(done) {
+ var tempFile = path.join(outpath, 'watch-func.txt');
+
+ createTempFile(tempFile);
+
+ var watcher = gulp.watch('watch-func.txt', { cwd: outpath }, function(cb) {
+ watcher.close();
+ cb();
+ done();
});
- it('should call the function when file changes: w/ options', function(done) {
-
- // Arrange
- var tempFile = path.join(outpath, 'watch-func-options.txt');
- fs.writeFile(tempFile, tempFileContent, function() {
-
- // Assert: it works if it calls done
- var watcher = gulp.watch(tempFile, { debounceDelay: 5 }, function(evt) {
- should.exist(evt);
- should.exist(evt.path);
- should.exist(evt.type);
- evt.type.should.equal('changed');
- evt.path.should.equal(path.resolve(tempFile));
- watcher.end();
- done();
- });
-
- // Act: change file
- writeFileWait(tempFile, tempFileContent + ' changed');
- });
+ updateTempFile(tempFile);
+ });
+
+ it('should execute the gulp.parallel tasks', function(done) {
+ var tempFile = path.join(outpath, 'watch-func.txt');
+
+ createTempFile(tempFile);
+
+ gulp.task('test', function(cb) {
+ watcher.close();
+ cb();
+ done();
});
- it('should not drop options when no callback specified', function(done) {
- // Arrange
- var tempFile = path.join(outpath, 'watch-func-nodrop-options.txt');
- // By passing a cwd option, ensure options are not lost to gaze
- var relFile = '../watch-func-nodrop-options.txt';
- var cwd = outpath + '/subdir';
- fs.writeFile(tempFile, tempFileContent, function() {
-
- // Assert: it works if it calls done
- var watcher = gulp.watch(relFile, { debounceDelay: 5, cwd: cwd })
- .on('change', function(evt) {
- should.exist(evt);
- should.exist(evt.path);
- should.exist(evt.type);
- evt.type.should.equal('changed');
- evt.path.should.equal(path.resolve(tempFile));
- watcher.end();
- done();
- });
-
- // Act: change file
- writeFileWait(tempFile, tempFileContent + ' changed');
- });
+ var watcher = gulp.watch('watch-func.txt', { cwd: outpath }, gulp.parallel('test'));
+
+ updateTempFile(tempFile);
+ });
+
+ it('should work with destructuring', function(done) {
+ var tempFile = path.join(outpath, 'watch-func.txt');
+ var watch = gulp.watch;
+ var parallel = gulp.parallel;
+ var task = gulp.task;
+ createTempFile(tempFile);
+
+ task('test', function(cb) {
+ watcher.close();
+ cb();
+ done();
});
- it('should run many tasks: w/ options', function(done) {
- // Arrange
- var tempFile = path.join(outpath, 'watch-task-options.txt');
- var task1 = 'task1';
- var task2 = 'task2';
- var task3 = 'task3';
- var a = 0;
- var timeout = writeTimeout * 2.5;
-
- fs.writeFile(tempFile, tempFileContent, function() {
-
- gulp.task(task1, function() {
- a++;
- });
- gulp.task(task2, function() {
- a += 10;
- });
- gulp.task(task3, function() {
- throw new Error('task3 called!');
- });
-
- // It works if it calls the task
- var config = { debounceDelay: timeout / 2 };
- var watcher = gulp.watch(tempFile, config, [task1, task2]);
-
- // Assert
- setTimeout(function() {
- a.should.equal(11); // Task1 and task2
-
- gulp.reset();
- watcher.end();
- done();
- }, timeout);
-
- // Act: change file
- writeFileWait(tempFile, tempFileContent + ' changed');
+ var watcher = watch('watch-func.txt', { cwd: outpath }, parallel('test'));
+
+ updateTempFile(tempFile);
+ });
+
+ it('should not call the function when no file changes: no options', function(done) {
+ var tempFile = path.join(outpath, 'watch-func.txt');
+
+ createTempFile(tempFile);
+
+ setTimeout(function() {
+ // Chokidar seems to pick up the file we just created, so we wait briefly before setup
+ // I wonder if node hasn't fully flushed the file or something...
+ var watcher = gulp.watch('watch-func.txt', { cwd: outpath }, function() {
+ done(new Error('should not each here!'));
});
+
+ setTimeout(function () {
+ watcher.close();
+ done();
+ }, 1000);
+ }, 250);
+ });
+
+ it('should call the function when file changes: w/ options', function(done) {
+ var tempFile = path.join(outpath, 'watch-func-options.txt');
+
+ createTempFile(tempFile);
+
+ var watcher = gulp.watch('watch-func-options.txt', { cwd: outpath }, function(cb) {
+ watcher.close();
+ cb();
+ done();
+ });
+
+ updateTempFile(tempFile);
+ });
+
+ it('should call the function when file changes at a path with japanese characters', function(done) {
+ var japaneseDir = path.join(outpath, 'フォルダ');
+
+ fs.mkdirSync(japaneseDir);
+
+ var tempFile = path.join(japaneseDir, 'foobar.txt');
+
+ createTempFile(tempFile);
+
+ var watcher = gulp.watch('フォルダ/*', { cwd: outpath }, function(cb) {
+ watcher.close();
+ cb();
+ done();
});
- it('should run many tasks: no options', function(done) {
- // Arrange
- var tempFile = path.join(outpath, 'watch-many-tasks-no-options.txt');
- var task1 = 'task1';
- var task2 = 'task2';
- var task3 = 'task3';
- var a = 0;
- var timeout = writeTimeout * 2.5;
-
- fs.writeFile(tempFile, tempFileContent, function() {
-
- gulp.task(task1, function() {
- a++;
- });
- gulp.task(task2, function() {
- a += 10;
- });
- gulp.task(task3, function() {
- throw new Error('task3 called!');
- });
-
- // It works if it calls the task
- var watcher = gulp.watch(tempFile, [task1, task2]);
-
- // Assert
- setTimeout(function() {
- a.should.equal(11); // Task1 and task2
-
- gulp.reset();
- watcher.end();
- done();
- }, timeout);
-
- // Act: change file
- writeFileWait(tempFile, tempFileContent + ' changed');
+ updateTempFile(tempFile);
+ });
+
+ it('should not call the function when ignored file changes', function(done) {
+ var tempFile = path.join(outpath, 'ignored.txt');
+
+ createTempFile(tempFile);
+
+ var watcher = gulp.watch(['*', '!ignored.txt'], { cwd: outpath }, function() {
+ done(new Error('should not each here!'));
+ });
+
+ removeTempFile(tempFile);
+ setTimeout(function () {
+ watcher.close();
+ done();
+ }, 1000);
+ });
+
+ it('should not drop options when no callback specified', function(done) {
+ var tempFile = path.join(outpath, 'watch-func-nodrop-options.txt');
+ // By passing a cwd option, ensure options are not lost to gaze
+ var relFile = '../watch-func-nodrop-options.txt';
+ var cwd = path.join(outpath, '/subdir');
+
+ createTempFile(tempFile);
+
+ var watcher = gulp.watch(relFile, { cwd: cwd })
+ .on('change', function(filepath) {
+ expect(filepath).toBeDefined();
+ expect(path.resolve(cwd, filepath)).toEqual(path.resolve(tempFile));
+ watcher.close();
+ done();
});
+
+ updateTempFile(tempFile);
+ });
+
+ it('should work without options or callback', function(done) {
+ var watcher = gulp.watch('x');
+ watcher.close();
+ done();
+ });
+
+ it('should run many tasks: w/ options', function(done) {
+ var tempFile = path.join(outpath, 'watch-task-options.txt');
+ var a = 0;
+
+ createTempFile(tempFile);
+
+ gulp.task('task1', function(cb) {
+ a++;
+ cb();
+ });
+ gulp.task('task2', function(cb) {
+ a += 10;
+ expect(a).toEqual(11);
+ watcher.close();
+ cb();
+ done();
});
+ var watcher = gulp.watch('watch-task-options.txt', { cwd: outpath }, gulp.series('task1', 'task2'));
+
+ updateTempFile(tempFile);
});
+
+ it('should run many tasks: no options', function(done) {
+ var tempFile = path.join(outpath, 'watch-many-tasks-no-options.txt');
+ var a = 0;
+
+ createTempFile(tempFile);
+
+ gulp.task('task1', function(cb) {
+ a++;
+ cb();
+ });
+ gulp.task('task2', function(cb) {
+ a += 10;
+ expect(a).toEqual(11);
+ watcher.close();
+ cb();
+ done();
+ });
+
+ var watcher = gulp.watch('./test/out-fixtures/watch-many-tasks-no-options.txt', gulp.series('task1', 'task2'));
+
+ updateTempFile(tempFile);
+ });
+
+ it('should throw an error: passed parameter (string) is not a function', function(done) {
+ var filename = 'empty.txt';
+ var tempFile = path.join(outpath, filename);
+
+ createTempFile(tempFile);
+ try {
+ gulp.watch(filename, { cwd: outpath }, 'task1');
+ } catch (err) {
+ expect(err.message).toEqual('watching ' + filename + ': watch task has to be a function (optionally generated by using gulp.parallel or gulp.series)');
+ done();
+ }
+ });
+
+ it('should throw an error: passed parameter (array) is not a function', function(done) {
+ var filename = 'empty.txt';
+ var tempFile = path.join(outpath, filename);
+
+ createTempFile(tempFile);
+ try {
+ gulp.watch(filename, { cwd: outpath }, ['task1']);
+ } catch (err) {
+ expect(err.message).toEqual('watching ' + filename + ': watch task has to be a function (optionally generated by using gulp.parallel or gulp.series)');
+ done();
+ }
+ });
+
});