Guide to JavaScript Decorators (last proposal)
Tags: webdev, javascript, programming, tutorial
Published: 2022-12-14 15:47:00



JavaScript Decorators is a powerful feature that finally reached stage 3 and is already supported by Babel. 
Here you can read the official proposal https://github.com/tc39/proposal-decorators.

I want to guide you on seamlessly activating the latest decorator feature support for your application. I made a straightforward plan to dive into the decorator feature.

## Predefine environment
In order to try the latest feature of decorators, you can create _**.babelrc**_ file 

```json
{
  "presets": [
    ["@babel/env", {
      "targets": {
        "node": "current"
      }
    }]
  ],
  "plugins": [
    ["@babel/plugin-proposal-class-static-block"],
    ["@babel/plugin-proposal-decorators", { "version": "2022-03" }],
    ["@babel/plugin-proposal-class-properties", { "loose": false }]
  ]
}
```
And here is the package.json, what you can take as an example:

```json
{
  "scripts": {
    "build": "babel index.js -d dist",
    "start": "npm run build && node dist/index.js"
  },
  "devDependencies": {
    "@babel/cli": "^7.19.3",
    "@babel/core": "^7.20.5",
    "@babel/plugin-proposal-decorators": "^7.20.5",
    "@babel/preset-env": "^7.20.2",
    "@babel/preset-stage-3": "^7.8.3"
  }
}

```

## Introduction
Probably you are already familiar with Typescript decorators, and you may see it somewhere, it looks like "@myDecorator". Declared decorators start from "@" and can be applied for classes, methods, accessors, and properties.
Here are some examples you may see before:

```javascript
function myDecorator(value: string) {
  // this is the decorator factory, it sets up
  // the returned decorator function
  return function (target) {
    // this is the decorator
    // do something with 'target'
    return function(...args) {
       return target.apply(this, args)
    } 
  };
}

class User {

   @myDecorator('value')
   anyMethod() { }
}
```

Javascript supports natively decorators, which haven’t been part of standard ECMAScript yet but are experimental features. Experimental means it may be changed in future releases. That is what happened. Actually, the latest proposal of the decorators introduced a new syntax and more accurate and purposeful implementation.


## Decorators in Typescript

Javascript has been introducing decorators since 2018. And TypeScript support decorators as well, with "experimentalDecorators" enabled option

```json
{
  "compilerOptions": {
   ...
    "experimentalDecorators": true
  }
}
```

> **Notice**
> TypeScript doesn’t support the last proposal of decorators
> https://github.com/microsoft/TypeScript/pull/50820

Nevertheless Typescript provides enough powerful and extended functionality around decorators:
- **Decorator Composition** (we can wrap few times an underlying function)

```javascript
class ExampleClass {
  @first()
  @second()
  method() {}
}
```
It is equal to: 

```javascript
first(second(method()))

// or the same as
const enhancedMethod = first(method());
const enhancedTwiceMethod = second(enhancedMethod())
```
- **Parameter Decorators** (Unlike JS, TS decorators support decorating params)
```javascript
class User {

    getRole(@inject RBACService) {
        return RBACService.getRole(this.id)
    }
}
```
The feature is included in the plans for **Typescript version 5.0**.



## How do decorators work?
The decorator basically high order functions. It's a kind of wrapper around another function and enhances its functionality without modifying the underlying function.
Here example of the legacy version:

```javascript
function log(target, name, descriptor) {
    const original = descriptor.value;
    if (typeof original === 'function') {
        descriptor.value = function(...args) {
            console.log(`Arguments: ${args}`);
            try {
                const result = original.apply(this, args);
                console.log(`Result: ${result}`);
                return result;
            } catch (e) {
                console.log(`Error: ${e}`);
                throw e;
            }
        }
    }
    return descriptor;
}
```
And the new implementation (that became stage-3 of the proposal):

```javascript
function log(target, { kind, name }) {
    if (kind === 'method') {
        return function(...args) {
            console.log(`Arguments: ${args}`);
            try {
                const result = target.apply(this, args);
                console.log(`Result: ${result}`);
                return result;
            } catch (e) {
                console.log(`Error: ${e}`);
                throw e;
            }
        }
    }
}

class User {

    @log
    getName(firstName, lastName) {
        return `${firstName} ${lastName}`
    }
}
```
As you can see there are new options, we don't use descriptors anymore to change an object, but we got closer to **metaprogramming** approach. I really like how Axel Rauschmayer describes what metaprogramming is:

- _We don’t write code that processes user data (programming)_.
- _We write code that processes code that processes user data (metaprogramming)_.

Let's take a look closer at the new signature of decorators, here is the new type well described in TS (but hasn’t merged to master yet), we will use it just as an example
```typescript
type Decorator = (
  value: DecoratedValue,
  context: {
    kind: string;
    name: string | symbol;
    addInitializer(initializer: () => void): void;

    // Don’t always exist:
    static: boolean;
    private: boolean;
    access: {get: () => unknown, set: (value: unknown) => void};
  }
) => void | ReplacementValue;
```
- **Kind** parameter can be:
'class'
'method'
'getter'
'setter'
'accessor'
'field'
Kind property tells the decorator which kind of JavaScript construct it is applied to. 

- **Name** is the name of a method or field in a class.

- **addInitializer** allows you to execute code after the class itself or a class element is fully defined.


## Auto-accessors
The decorators' proposal introduces a new language feature: auto-accessors. 
To understand what the auto-accessors feature is, let's take a look at the below example:

```javascript

// New "accessor" keyword 
class User {
    accessor name = 'user';
    constructor(name) {
        this.name = name
    }  
}

// it's the same as
class User {
    #name = 'user';

    constructor(name) {
        this.name = name
    }

    get name() {
        return this.#name;
    }
    set name(value) {
        this.#name = value;
    }
}
```
Auto-accessor is a shorthand syntax for standard getters and setters that we get used to implementing in classes.
We can apply decorators for accessors easily with the new proposal:

```javascript
function accessorDecorator({get,set}, {name, kind}) {
    if (kind === 'accessor') {
        return {
            init() {
                return 'initial value';
            },
            get() {
                const value = get.call(this);
                ...
                return value;
            },
            set(newValue) {
                const oldValue = get.call(this);
                ...
                set.call(this, newValue);
            },
        };
    }
}

class User {

    @accessorDecorator
    accessor name = 'user';

    constructor(name) {
        this.name = name
    }  
}
```

## Fields Decorator
How can we decorate fields by legacy approach?
Here code example:

```javascript
function fieldDecorator(target, name, descriptor) {
    return {
        ...descriptor,
        writable: false
        initializer: () => 'EU'
    }
}

class Customer {
    @fieldDecorator
    country = 'USA';


    getCountry() {
        return this.country
    }
}

const customer = new Customer('john')
customer.getCountry(); // 'EU' instead of USA, because of initializer
customer.country = 'DE' // TypeError: Cannot assign to read only property 'country' of object '#<Customer>'
```
**Limitation legacy decorators:**
- We can't decorate private fields
- it’s always hacky to decorate efficiently on accessors of fields

The new proposal is more flexible:

```javascript

function readOnly(value, {kind, name}) {
    if (kind === 'field') {
        return function () {
            if (!this.readOnlyFields) {
                this.readOnlyFields = []
            }
            this.readOnlyFields.push(name)
        }
    }
    if (kind === 'class') {
        return function (...args) {
            const object = new value(...args);
            for (const readOnlyKey of object.readOnlyFields) {
                Object.defineProperty(object, readOnlyKey, { writable: false });
            }
            return object;
        }
    }
}

@readOnly
class Customer {
    @readOnly
    country;
    constructor(country) {
        this.country = country
    }

    getCountry() {
        return this.country
    }
}

const customer = new Customer();

customer.getCountry(); // 'USA'

customer.country = 'EU' // // TypeError: Cannot assign to read only property 'country' of object '#<Customer>'
```
As you can see, we don't have access to the property descriptor. Still, we can implement it differently, collect all the not writable fields, and set "_writable: false_" through the class decorator.


## Conclusion
I think this is a whole new level, as developers can dive even more into the world of metaprogramming and look forward to the release of Typescript 5.0 and when the new proposal becomes part of the EcmaScript standard.

Follow me on 🐦 [Twitter](https://twitter.com/DevMoroh) if you want to see more content.