指导

这个指导实际上不是必须的,但是我们强烈建议每一个人来遵守。因为没有人会喜欢用一个不好的插件。这个指导能在某种意义上确保你的插件能很好的适应 gulp,以此来让你的生活变得更将轻松。

编写插件 > 指导

  1. 你的插件不应该去做一些现有 node 模块已经能很容易做到的事情
  2. 比如:删除一个文件夹并不需要做成一个 gulp 插件,在 task 里使用一个类似 del 这样的插件即可。
  3. 只是为了封装而封装一些的东西进去,这只会增加很多低质量的插件到生态中,这不符合 gulp 的期望。
  4. gulp 插件都是以文件为基础操作的,如果你发现你正在把一些很复杂的操作塞进 stream 中去,那么,请直接写一个 node 模块就好。
  5. 一个好的 gulp 插件例子像是 gulp-coffee,coffee-script 模块并不能直接和 vinyl 做很好的适配,因此,才去封装它来使用相应的功能,并且将一些比较痛苦的操作抽象出来,做成更简单的 gulp 插件来使用。
  6. 你的插件应该只做一件事,并且做好。
  7. 避免使用配置选项,使得你的插件能胜任不同场合的任务。
  8. 比如:一个 JS 压缩插件不应该有一个加头部的选项
  9. 你的插件不能去做一些其他插件做的事:
  10. 不应该去拼接,用 gulp-concat 去做
  11. 不应该去增加头部,用 gulp-header 去做
  12. 不应该去增加尾部,用 gulp-footer 去做
  13. 如果是一个常用的可选的操作,那么,请在文档中注明你的插件通常和其他某个插件一起使用
  14. 在你的插件中使用其他的插件,这能大大减少你的代码量,并保证生态系统的稳定。
  15. 你的插件必须被测试过
  16. 测试一个插件很简单,你甚至不需要 gulp 就能测试
  17. 参考其他的插件是怎么做的
  18. package.json 中增加一个名为 gulpplugin 的关键字,这可以让它能在我们的搜索中出现
  19. 不要在 stream 里面抛出错误
  20. 你应该以触发 error 事件来代替
  21. 如果你在 stream 外面遇到错误,比如在创建 stream 时候发现错误的配置选项等,那么你应该抛出它。
  22. 错误需要加上以你插件名字作为前缀
  23. 比如: gulp-replace: Cannot do regexp replace on a stream
  24. 使用 gulp-util 的 PluginError 类来完成它
  25. file.contents 的类型需要总是在输入输出中保持一致
  26. 如果 file.contents 为空 (不可读) 请将他忽略,并传过去
  27. 如果 file.contents 是一个 stream,但是你不支持,那么请触发一个错误
    • 不要把 stream 硬转成 buffer 来使你的插件支持 stream,这会引发很严重的问题。
  28. 在你处理完成之前,不要将 file 传到下游去
  29. 使用 file.clone() 来复制一个文件或者创建另一个以此为基础的文件
  30. 使用我们 模块推荐页 上列举的模块来让你的开发更加轻松
  31. 不要把 gulp 作为一个依赖
  32. 使用 gulp 来测试你的插件的工作流这的确很酷,但请务必确保你将它放到 devDependency 中
  33. 在你的插件中依赖 gulp,这意味着安装你的插件的用户将会重新安装一遍 gulp 以及所有它所依赖的东西。
  34. 没有任何理由说明你需要将 gulp 写到你的插件代码中去,如果你发现你必须这么做,那么请开一个 issue,我们会帮你解决。

为什么这些指导这么严格?

gulp 的目标是为了让用户觉得简单,通过提供一些严格的指导,我们就能提供一致并且高质量的生态系统给大家。不过,这确实给插件作者增加了一些需要考虑的东西,但是也确保了后面的问题会更少。

如果我不遵守这些,会发生什么?

npm 对每个人来说是免费的,你可以开发任何你想要开发的东西出来,并且不需要遵守这个规定。我们承诺测试机制将会很快建立起来,并且加入我们的插件搜索中。如果你坚持不遵守插件导览,那么这会反应在我们的打分/排名系统上,人们都会更加喜欢去使用一个 "更加 gulp" 的插件。

一个插件大概会是怎么样的?

// through2 是一个对 node 的 transform streams 简单封装
var through = require('through2');
var gutil = require('gulp-util');
var PluginError = gutil.PluginError;

// 常量
const PLUGIN_NAME = 'gulp-prefixer';

function prefixStream(prefixText) {
  var stream = through();
  stream.write(prefixText);
  return stream;
}

// 插件级别函数 (处理文件)
function gulpPrefixer(prefixText) {

  if (!prefixText) {
    throw new PluginError(PLUGIN_NAME, 'Missing prefix text!');
  }
  prefixText = new Buffer(prefixText); // 预先分配

  // 创建一个让每个文件通过的 stream 通道
  return through.obj(function(file, enc, cb) {
    if (file.isNull()) {
      // 返回空文件
      cb(null, file);
    }
    if (file.isBuffer()) {
      file.contents = Buffer.concat([prefixText, file.contents]);
    }
    if (file.isStream()) {
      file.contents = file.contents.pipe(prefixStream(prefixText));
    }

    cb(null, file);

  });

};

// 暴露(export)插件主函数
module.exports = gulpPrefixer;