TypeScript's useDefineForClassFields is Slow

When benchmarking Gooey, I noticed a surprising bottleneck: the amount of time it took to construct class instances.

tl;dr: If you write TypeScript and have an application that constructs many classes, consider using the declare keyword before member type declarations, or set useDefineForClassFields to false.

Gooey uses a "Field" class to represent every piece of mutable data. This means it constructs a very large number of these classes. When benchmarking, I noticed that a surprising bottleneck was a call to a mysterious __defNormalProp function, which was taking a surprising amount of time.

This function was injected due to my use of esbuild which was picking up the configuration value "useDefineForClassFields": true in my tsconfig.json.

This is documented in esbuild issue 185. Essentially, when you write something like this:

class MyClass {
    value: number;
    constructor(value: number) {
        this.value = value;
    }
}

Both TypeScript and esbuild output something like this:

class MyClass {
    constructor(value) {
        Object.defineProperty(this, "value", {
            enumerable: true,
            configurable: true,
            writable: true,
            value: void 0
        });
        this.value = value;
    }
}

As this jsbench.me benchmark shows, this is surprisingly slow! Like 90% slower!

There are two ways of getting around this: On a per-project basis, you can set useDefineForClassFields to false. On a per-field basis you can use the declare keyword on the prop in the class:

class MyClass {
    declare value: number;
    constructor(value: number) {
        this.value = value;
    }
}

Both of these approaches will avoid that call to Object.defineProperty.

Note: if you take this approach and your code does not assign to the member in the constructor, that member will not appear as a member of the object.

More about what this does and why here: https://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-7.html#the-usedefineforclassfields-flag-and-the-declare-property-modifier