initial commit

This commit is contained in:
tmshv committed 2014-02-12 12:36:59 +04:00
commit d3fcd7f10c
3159 files changed
+349702

No files matched your search

+53
View File
@@ -0,0 +1,53 @@
# Writing a plugin
If you plan to create your own Gulp plugin, you will save time by reading the full documentation.
* [Guidelines](guidelines.md) (a MUST read)
* [Using buffers](using-buffers.md)
* [Dealing with streams](dealing-with-streams.md)
* [Testing](testing.md)
* [README conventions](readme-conventions.md)
## What it does
### 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:
1. Takes in [vinyl File objects](http://github.com/wearefractal/vinyl)
2. Outputs [vinyl File objects](http://github.com/wearefractal/vinyl)
These are known as [transform streams](http://nodejs.org/api/stream.html#stream_class_stream_transform_1) (also sometimes called through streams). Transform streams are streams that are readable and writable which manipulate objects as they're being passed through.
### Modifying file content
Vinyl files can have 3 possible forms for the contents attribute:
- [Streams](dealing-with-streams.md)
- [Buffers](using-buffers.md)
- Empty (null) - Useful for things like rimraf, clean, where contents is not neeeded.
## Useful resources
* [File object](https://github.com/wearefractal/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/wearefractal/gulp-util)
## Sample plugins
* [sindresorhus' gulp plugins](https://github.com/search?q=%40sindresorhus+gulp-)
* [Fractal's gulp plugins](https://github.com/search?q=%40wearefractal+gulp-)
* [gulp-replace](https://github.com/lazd/gulp-replace)
## About streams
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
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.
+83
View File
@@ -0,0 +1,83 @@
# Dealing with streams
> It is highly recommended to write plugins supporting streams. Here is some information on creating a gulp plugin that supports streams.
> Make sure to follow the best practice regarging error handling and add the line that make the gulp plugin re-emit the first error catched during the transformation of the content
[Writing a Plugin](README.md) > Writing stream based plugins
## Dealing with streams
Let's implement a plugin prepending some text to files. This plugin supports all possible forms of file.contents.
```js
var through = require('through2');
var gutil = require('gulp-util');
var PluginError = gutil.PluginError;
// Consts
const PLUGIN_NAME = 'gulp-prefixer';
function prefixStream(prefixText) {
var stream = through();
stream.write(prefixText);
return stream;
}
// Plugin level function (dealing with files)
function gulpPrefixer(prefixText) {
if (!prefixText) {
throw PluginError(PLUGIN_NAME, "Missing prefix text!");
}
prefixText = new Buffer(prefixText); // allocate ahead of time
// Creating a stream through which each file will pass
var stream = through.obj(function (file, enc, callback) {
if (file.isNull()) {
this.push(file); // Do nothing if no contents
return callback();
}
if (file.isBuffer()) {
this.emit('error', new PluginError(PLUGIN_NAME, 'Buffers not supported!'));
return callback();
}
if (file.isStream()) {
// define the streamer that will transform the content
var streamer = prefixStream(prefixText);
// catch errors from the streamer and emit a gulp plugin error
streamer.on('error', this.emit.bind(this, 'error'));
// start the transformation
file.contents = file.contents.pipe(streamer);
// make sure the file goes through the next gulp plugin
this.push(file);
// tell the stream engine that we are done with this file
return callback();
}
});
// returning the file stream
return stream;
};
// Exporting the plugin main function
module.exports = gulpPrefixer;
```
The above plugin can be used like this:
```js
var gulp = require('gulp');
var gulpPrefixer = require('gulp-prefixer');
gulp.src('files/**/*.js')
.pipe(gulpPrefixer('prepended string'))
.pipe(gulp.dest('/modified-files/'));
```
## Some plugins using streams
* [gulp-svgicons2svgfont](https://github.com/nfroidure/gulp-svgiconstosvgfont)
* gulp-browserify (Soon)
+97
View File
@@ -0,0 +1,97 @@
# Guidelines
> While these guidelines are totally optional, we **HIGHLY** recommend that everyone follows them. Nobody wants to use a bad plugin. These guidelines will actually help make your life easier by giving you assurance that your plugin fits well within gulp.
[Writing a Plugin](README.md) > Guidelines
1. Your plugin should not do something that can be done easily with an existing node module
- Wrapping every possible thing just for the sake of wrapping it will pollute the ecosystem with low quality plugins that don't make sense
1. Your plugin should only do **one thing**, and do it well.
- Avoid config options that make your plugin do completely different tasks
1. Your plugin shouldn't do things that other plugins are responsible for
- It should not concat, [gulp-concat](https://github.com/wearefractal/gulp-concat) does that
- It should not add headers, [gulp-header](https://github.com/godaddy/gulp-header) does that
- It should not add footers, [gulp-footer](https://github.com/godaddy/gulp-footer) does that
- If it's a common but optional use case, document that your plugin is often used with another plugin
- If it's an internal requirement, make use of existing plugins by piping your plugin's output to them
1. Your plugin **must be tested**
- Testing a gulp plugin is easy, you don't even need gulp to test it
- Look at other plugins for examples
1. Add `gulpplugin` as a keyword in your `package.json` so you show up on our search
1. Do not throw errors inside a stream
- Instead, you should emit it as an **error** event.
- If you encounter an error **outside** the stream, such as invalid configuration, 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
1. The type of `file.contents` should always be the same going out as it was when it came in
- If file.contents is null (non-read) just ignore the file and pass it along
- If file.contents is a Stream and you don't support that just emit an error
- If you choose to support file.contents as a Stream, use [BufferStream](https://github.com/nfroidure/BufferStream) to make this easy
1. Do not pass the `file` object downstream until you are done with it
1. Make use of the [gulp-util](https://github.com/gulpjs/gulp-util) library
- It provides templating, CLI colors, logging, changing file extensions
- Try looking for common things there first and add it if it doesn't exist
1. Do NOT require `gulp` as a dependency or peerDependency
- There is no reason you should have to do this and it will cause problems if you do
## Why are these guidelines so strict?
gulp aims to be simple for users. By providing strict guidelines we are able to provide a consistent and high-quality ecosystem for everyone. While this does add a little more work and thought for plugin authors, it removes a lot of problems later down the road.
### What happens if I don't follow them?
npm is open for everyone, and you are free to make whatever you want but these guidelines were prescribed for a reason. There are acceptances tests coming soon that will be integrated into the plugin search. If you fail to adhere to the plugin guidelines it will be publicly visible/sortable via a scoring system. People will always prefer to use plugins that match "the gulp way".
### What does a good plugin look like?
```js
// through2 is a thin wrapper around node transform streams
var through = require('through2');
var gutil = require('gulp-util');
var PluginError = gutil.PluginError;
// Consts
const PLUGIN_NAME = 'gulp-prefixer';
function prefixStream(prefixText) {
var stream = through();
stream.write(prefixText);
return stream;
}
// Plugin level function (dealing with files)
function gulpPrefixer(prefixText) {
if (!prefixText) {
throw PluginError(PLUGIN_NAME, "Missing prefix text!");
}
prefixText = new Buffer(prefixText); // allocate ahead of time
// Creating a stream through which each file will pass
var stream = through.obj(function (file, enc, callback) {
if (file.isNull()) {
this.push(file); // Do nothing if no contents
return callback();
}
if (file.isBuffer()) {
file.contents = Buffer.concat([prefixText, file.contents]);
this.push(file);
return callback();
}
if (file.isStream()) {
file.contents = file.contents.pipe(prefixStream(prefixText));
this.push(file);
return callback();
}
});
// returning the file stream
return stream;
};
// Exporting the plugin main function
module.exports = gulpPrefixer;
```
+218
View File
@@ -0,0 +1,218 @@
# README conventions
> Follow README conventions to increase your plugin readability and usability.
[Writing a Gulp Plugin](README.md) > README conventions
A plugin README should have the following parts at a minimum:
1. **Heading**: Give a short introduction to your plugin
* Include the version of gulp your plugin was designed for
* Include any badges on the same line as the heading
1. **Usage**: Include a simple usage example
* Show the plugin used on the context of a `gulpfile.js`
* Cover the most common or simplest use case
1. **API**: Document your plugin's API
* For individual parameters, include:
1. Parameter name
1. Type
1. Default value
1. Description
1. Optional example for complex options
* If a parameter accepts an Object, do the above for each property
* If your plugin accepts overloaded arguments, document each style individually
# Example: Options object
The following is an example of a plugin that takes a single options object (from [gulp-handlebars](https://github.com/lazd/gulp-handlebars)).
***
# gulp-handlebars [![NPM version][handlebars-npm-image]][handlebars-npm-url] [![Build status][handlebars-travis-image]][handlebars-travis-url]
> Handlebars plugin for gulp 3
## Usage
First, install `gulp-handlebars` as a development dependency:
```shell
npm install --save-dev gulp-handlebars
```
Then, add it to your `gulpfile.js`:
```js
var handlebars = require('gulp-handlebars');
gulp.task('templates', function(){
gulp.src(['client/templates/*.hbs'])
.pipe(handlebars({
namespace: 'MyApp.templates',
outputType: 'hybrid'
}))
.pipe(concat('templates.js'))
.pipe(gulp.dest('build/js/'));
});
```
## API
### handlebars(options)
#### options.namespace
Type: `String`
Default: `templates`
The namespace in which the precompiled templates will be assigned. Use dot notation (e.g. `App.Templates`) for nested namespaces or false to declare templates in the global namespace.
#### options.outputType
Type: `String`
Default: `browser`
The desired output type. One of the following:
* `browser` - Produce plain JavaScript files for the browser
* `hybrid` - Produce Node modules that can optionally be used on the frontend
* `node` - Produce Node modules
* `amd` - Produce AMD modules
* `commonjs` - Produce CommonJS modules
* `bare` - Return an unmolested function definition
#### options.declareNamespace
Type: `Boolean`
Default: `true`
If true, non-destructively declare all parts of the namespace. This option is only necessary when `options.type` is `browser` or `hybrid`.
For example, if the namespace is `MyApp.Templates` and a template is named `App.Header.hbs`, the following declaration will be present in the output file for that template:
```js
this["MyApp"] = this["MyApp"] || {};
this["MyApp"]["templates"] = this["MyApp"]["templates"] || {};
this["MyApp"]["templates"]["App"] = this["MyApp"]["templates"]["App"] || {};
this["MyApp"]["templates"]["App"]["Header"] = function () {};
```
When processing multiple templates under a given namespace, this will result in duplicate declarations. That is, the non-destructive declaration of the namespace will be repeated for each template compiled.
#### options.processName
Type: `Function`
Default: Strip file extension
This option accepts a function which takes one argument (the template filepath) and returns a string which will be used as the key for the precompiled template object. By default, the filename minus the extension is used.
If this function returns a string containing periods (not including the file extension), they will be represented as a sub-namespace. See `options.declareNamespace` for an example of the effect.
#### options.compilerOptions
Type: `Object`
Compiler options to pass to `Handlebars.precompile()`.
[handlebars-travis-url]: http://travis-ci.org/lazd/gulp-handlebars
[handlebars-travis-image]: https://secure.travis-ci.org/lazd/gulp-handlebars.png?branch=master
[handlebars-npm-url]: https://npmjs.org/package/gulp-handlebars
[handlebars-npm-image]: https://badge.fury.io/js/gulp-handlebars.png
***
# Example: Overloaded API
The following is an example of a plugin that has an overloaded API (from [gulp-csslint](https://github.com/lazd/gulp-csslint)).
***
# gulp-csslint [![NPM version][csslint-npm-image]][csslint-npm-url] [![Build status][csslint-travis-image]][csslint-travis-url]
> CSSLint plugin for gulp 3
## Usage
First, install `gulp-csslint` as a development dependency:
```shell
npm install --save-dev gulp-csslint
```
Then, add it to your `gulpfile.js`:
```javascript
var csslint = require('gulp-csslint');
gulp.task('css', function() {
gulp.src('./client/css/*.css')
.pipe(csslint())
.pipe(csslint.reporter());
});
```
## API
### csslint(ruleConfiguration)
#### ruleConfiguration
Type: `Object`
You can pass rule configuration as an object. See the [list of rules by ID on the CSSLint wiki](https://github.com/stubbornella/csslint/wiki/Rules-by-ID) for valid rule IDs.
```javascript
gulp.src('./client/css/*.css')
.pipe(csslint({
'shorthand': false
}))
.pipe(csslint.reporter());
```
### csslint(csslintrc)
#### csslintrc
Type: `String`
You can also pass the path to your csslintrc file instead of a rule configuration object.
```javascript
gulp.src('./client/css/*.css')
.pipe(csslint('csslintrc.json'))
.pipe(csslint.reporter());
```
## Results
Adds the following properties to the file object:
```javascript
file.csslint.success = true; // or false
file.csslint.errorCount = 0; // number of errors returned by CSSLint
file.csslint.results = []; // CSSLint errors
file.csslint.opt = {}; // The options you passed to CSSLint
```
## Custom Reporters
Custom reporter functions can be passed as `cssline.reporter(reporterFunc)`. The reporter function will be called for each linted file and passed the file object as described above.
```javascript
var csslint = require('gulp-csslint');
var gutil = require('gulp-util');
var customReporter = function(file) {
gutil.log(gutil.colors.cyan(file.csslint.errorCount)+' errors in '+gutil.colors.magenta(file.path));
file.csslint.results.forEach(function(result) {
gutil.log(result.error.message+' on line '+result.error.line);
});
};
gulp.task('lint', function() {
gulp.files('./lib/*.js')
.pipe(csslint())
.pipe(csslint.reporter(customReporter));
});
```
[csslint-travis-url]: http://travis-ci.org/lazd/gulp-csslint
[csslint-travis-image]: https://secure.travis-ci.org/lazd/gulp-csslint.png?branch=master
[csslint-npm-url]: https://npmjs.org/package/gulp-csslint
[csslint-npm-image]: https://badge.fury.io/js/gulp-csslint.png
***
+95
View File
@@ -0,0 +1,95 @@
# Testing
> Testing your plugin is the only way to ensure quality. It brings confidence to your users and makes your life easier.
[Writing a Plugin](README.md) > Testing
## Tooling
Most plugins use [mocha](https://github.com/visionmedia/mocha) and [event-stream](https://github.com/dominictarr/event-stream) to help them test. The following examples will use these tools.
## Testing plugins for streaming mode
```js
var assert = require('assert');
var es = require('event-stream');
var gutil = require('gulp-util');
var prefixer = require('../index');
describe('gulp-prefixer', function () {
describe('in streaming mode', function () {
it('should prepend text', function (done) {
// create the fake file
var fakeFile = new gutil.File({
contents: es.readArray(['stream', 'with', 'those', 'contents'])
});
// Create a prefixer plugin stream
var myPrefixer = prefixer('prependthis');
// write the fake file to it
myPrefixer.write(fakeFile);
// wait for the file to come back out
myPrefixer.once('data', function (file) {
// make sure it came out the same way it went in
assert(file.isStream());
// buffer the contents to make sure it got prepended to
file.contents.pipe(es.wait(function (err, data) {
// check the contents
assert.equal(data, 'prependthistostreamwiththosecontents');
done();
}));
});
});
});
});
```
## Testing plugins for buffer mode
```js
var assert = require('assert');
var es = require('event-stream');
var gutil = require('gulp-util');
var prefixer = require('../index');
describe('gulp-prefixer', function () {
describe('in buffer mode', function () {
it('should prepend text', function (done) {
// create the fake file
var fakeFile = new gutil.File({
contents: new Buffer('abufferwiththiscontent')
});
// Create a prefixer plugin stream
var myPrefixer = prefixer('prependthis');
// write the fake file to it
myPrefixer.write(fakeFile);
// wait for the file to come back out
myPrefixer.once('data', function (file) {
// make sure it came out the same way it went in
assert(file.isBuffer());
// check the contents
assert.equal(file.contents.toString('utf8'), 'prependthisabufferwiththiscontent');
done();
});
});
});
});
```
## Sample tests
* [gulp-cat](https://github.com/ben-eb/gulp-cat/blob/master/test.js)
+72
View File
@@ -0,0 +1,72 @@
# Using buffers
> Here is some information on creating gulp plugin that manipulates buffers.
[Writing a Plugin](README.md) > Using buffers
## Using buffers
If your plugin is relying on a buffer based library, you will probably choose to base your plugin around file.contents as a buffer. Let's implement a plugin prepending some text to files:
```js
var through = require('through2');
var gutil = require('gulp-util');
var PluginError = gutil.PluginError;
// Consts
const PLUGIN_NAME = 'gulp-prefixer';
// Plugin level function (dealing with files)
function gulpPrefixer(prefixText) {
if (!prefixText) {
throw new PluginError(PLUGIN_NAME, "Missing prefix text!");
}
prefixText = new Buffer(prefixText); // allocate ahead of time
// Creating a stream through which each file will pass
var stream = through.obj(function (file, enc, callback) {
if (file.isNull()) {
this.push(file); // Do nothing if no contents
return callback();
}
if (file.isBuffer()) {
file.contents = Buffer.concat([prefixText, file.contents]);
this.push(file);
return callback();
}
if (file.isStream()) {
this.emit('error', new PluginError(PLUGIN_NAME, 'Streams are not supported!'));
return callback();
}
});
// returning the file stream
return stream;
};
// Exporting the plugin main function
module.exports = gulpPrefixer;
```
The above plugin can be used like this:
```js
var gulp = require('gulp');
var gulpPrefixer = require('gulp-prefixer');
gulp.src('files/**/*.js')
.pipe(gulpPrefixer('prepended string'))
.pipe(gulp.dest('/modified-files/'));
```
## Handling streams
Unfortunately, the above plugin will error when using gulp.src in non-buffered (streaming) mode. You should support streams too if possible. See [Dealing with streams](dealing-with-streams.md) for more information.
## Some plugins based on buffers
* [gulp-coffee](https://github.com/wearefractal/gulp-coffee)
* [gulp-svgmin](https://github.com/ben-eb/gulp-svgmin)
* [gulp-marked](https://github.com/lmtm/gulp-marked)
* [gulp-svg2ttf](https://github.com/nfroidure/gulp-svg2ttf)