duojs / duo

A next-generation package manager for the front-end
3.42k stars 118 forks source link

Adding support for plugins to transform the final build #450

Closed dominicbarnes closed 9 years ago

dominicbarnes commented 9 years ago

This adds the ability for plugins to be configured to transform the entire build, not just individual files. The use-cases I have in mind are things like minifiers and CSS preprocessors, but other applications exist I'm sure!

Internally, these are called "alternate" plugins. It's a really, really awful name, but it was the best I could think of at the time. Now that the legwork is done, we should discuss what we ultimately want this feature to look like. It's something I think is really important to have in the long-run, and something I have been wanting to do for a long time.

The aim was to have a simple implementation, as you can see it involved very minimal changes under the hood. Basically, if the plugin Function has an alternate property that is truthy, it will be added to Duo#altPlugins instead of Duo#plugins. Then, in Duo#run(), the altPlugins Ware instance will be applied to the results object, where each one can modify the code or map as needed before the final return.

/cc @duojs/owners feedback is very much wanted :)

dominicbarnes commented 9 years ago

I think long-term, we should consider a duo-plugin repo/library that exposes a Plugin constructor. That constructor can be used to configure plugins more granularly. (especially if we implement some sort of hooks infrastructure)

Something like:

var Plugin = require('duo-plugin');

// normal per-file plugin
Plugin('coffeescript')
  .type('js')
  .file(function (file, entry) {
    // plugin logic 
  });

// "alternate" plugin
Plugin('uglify')
  .type('js')
  .build(function (results) {
    // plugin logic
  });

This would enable a more rich way to configure and include plugins. (but it would require more infrastructure in duo itself)

dominicbarnes commented 9 years ago

Btw, this will close #346

matthewmueller commented 9 years ago

Hmm... I think that's cleaner, but it's not as simple as just exporting a function, which is what the other package managers offer.

I was thinking something more along the lines of:

function uglify(file, duo) {
  file.src = uglify(file.src);
  duo.hook('before build', before);
  duo.hook('after build', after);
}

Open to more ideas though. Also, I'd rather change duo drastically than break compatibility with all the plugins.


EDIT: Oops, didn't realize this was a PR, hmmm..

dominicbarnes commented 9 years ago

@MatthewMueller I'm totally open to using hooks, but "before build" and "after build" are really unclear. (at least to me) How about something like:

module.exports = function (duo) {
  duo.hook('file', function (file, entry) {
    file.src = babel(file.src);
  });

  duo.hook('build', function (results) {
    results.code = uglify(results.code);
  });
};

Of course, it depends on what the hook infrastructure is intended to look like. Maybe we can include hooks for various stages of the build. (eg: "after resolve", "after download", "process file", "process build", etc)

matthewmueller commented 9 years ago

Oh yah I'm def down for better names.

FWIW, I built this module to setup the hooks, just never got around to adding it in: https://github.com/matthewmueller/hooke

dominicbarnes commented 9 years ago

@MatthewMueller Awesome! Would you prefer to do that now? (or would you be willing to merge this feature in now, and punt hooks to another minor version?)

matthewmueller commented 9 years ago

Personally, I'd rather just implement hooks. I haven't run up to any blockers with this stuff yet though, so depending on your needs and if it's backwards compatible, I don't mind.

dominicbarnes commented 9 years ago

I've added a new docs page (plugins.md) which gives more detailed information for both plugin authors and consumers.

In addition, I've added a note about these "alternate plugins". (along with a notice about it's temporality) Since this feature is backwards-compatible, and hooks would not be, I'm going to punt adding a hooks infrastructure to a later time.