Almost @Decorate

This is a handy little snippet I’ve been using to emulate ES7′s decorators without needing to depend on a non-standard syntax, nor wire in extra Babel (or whatever) plugins. Or even use more than ES3, once the function itself is transpiled.

const decorate = (...decorators) => component =>
    decorators.reduce((c, d) => d(c), component);

Consider this ES7 decorated component (using the syntax as of 2017-08-25):

@MyDecorator(/* my config */)
@OtherDecorator(/* other config */)
class Fred {}
export default Fred;

Without decorators, it looks like this:

class Fred {}
export default OtherDecorator(/* other config */)(
    MyDecorator(/* my config */)(
        Fred
    )
);

I find this hard to read, even before complicated configuration (functions returning objects of functions, anyone?). Even if you play around with different line breaks, indentation, and intermediate variables, it’s never super clean. Also, the nesting is in the “reverse” order of how decorators apply. You often see a temp variable to fix this latter issue:

class Fred {}
const wrappedFred = MyDecorator(/* my config */)(
    Fred
);
export default OtherDecorator(/* other config */)(
    wrappedFred
);

Still a lot of cruft. Now if we use that decorate function up top, we get this:

class Fred {}
export default decorate(
    MyDecorator(/* my config */),
    OtherDecorator(/* other config */)
)(Fred);

Not as nice as today’s candidate ES7 syntax, but only barely. And it’s also completely transparent, unlike the Babel plugin (found at https://github.com/loganfsmyth/babel-plugin-transform-decorators-legacy/blob/v1.3.4/src/index.js for the masochists among us).

Update: if you don’t have an ES6 transpiler and need ES3, here it is:

function decorate() {
    var l = arguments.length, decorators = Array(l);
    for (var i = 0; i < l; i++) {
        decorators[i] = arguments[i];
    }
    return function(component) {
        return decorators.reduce(function(c, d) {
            return d(c);
        }, component);
    };
};