How to decorate a JavaScript class constructor when using non-legacy decorators?

Viewed 408

I have the following class:

@log
class Example {
  constructor(name, age) {
    console.log("Example constructor", name, age);
    this.name = name;
    this.age = age;
  }
}

And this @log decorator when using legacy decorators:

function log(Class) {
  return class extends Class {
    constructor(...args) {
      console.log("log", args);
      super(...args);
    }
  };
}
// .babelrc
// ...
      "plugins": [
        ["@babel/plugin-proposal-decorators", { "legacy": true }],
        // ...
      ]
// ...

The above setup works well, and the following code:

const example = new Example("Graham", 34);

Outputs:

log (2) ["Graham", 34]
Example constructor Graham 34

Now, how can I achieve the same result when using non-legacy decorators, i.e.:

// .babelrc
// ...
      "plugins": [
        [
          "@babel/plugin-proposal-decorators",
          { "decoratorsBeforeExport": false }
        ],
        // ...
      ]
// ...

How can I implement @log so that it works in the same way as with legacy decorators?

function log(...args) {
  const [descriptor] = args;
  const { kind, elements } = descriptor;

  const newElements = elements.concat({
    kind: "method",
    placement: "prototype",
    key: "constructor",
    descriptor: {
      value: (...args) => {
        // I have tried this but it doesn't work...
        console.log("log", args);
      },
      configurable: true,
      writable: true,
      enumerable: true,
    },
  });

  return {
    kind,
    elements: newElements,
  };
}

I have tried the code above but it didn't work. The problem is that I do not have a reference to the target when using non-legacy decorators. Do you know if there is a way to achieve the same behaviour as with the legacy ones?

Thank you.

1 Answers

I have found the answer, thanks to loganfsmyth on the BabelJS Slack channel.

You have to return an object with a property called finisher wrapping the class, e.g.:

function log(...args) {
  const [descriptor] = args;
  const { kind, elements } = descriptor;

  return {
    kind,
    elements,
    finisher: (Class) => {
      return class extends Class {
        constructor(...args) {
          console.log("log", args);
          super(...args);
        }
      };
    }
  };
}

Then this code when using non-legacy decorators:

@log
class Example {
  constructor(name, age) {
    console.log("Example constructor", name, age);
    this.name = name;
    this.age = age;
  }
}

const example = new Example("Graham", 34);

Outputs:

log (2) ["Graham", 34]
Example constructor Graham 34

As with legacy decorators.

It was difficult to find the answer because this feature of non-legacy decorators is not documented on @babel/plugin-proposal-decorators.

Related