esbuild
esbuild copied to clipboard
Experimental class decorator will degrade private fields.
experimentalDecorators false:
const decorate = (() => {}) as any;
@decorate
export class Class {
#field = "field";
}
/**
* output
const decorate = () => {
};
@decorate
export class Class {
#field = "field";
}
*/
experimentalDecorators true:
const decorate = (() => {}) as any;
@decorate
export class Class {
#field = "field";
}
/**
* output
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __decorateClass = (decorators, target, key, kind) => {
var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc(target, key) : target;
for (var i = decorators.length - 1, decorator; i >= 0; i--)
if (decorator = decorators[i])
result = (kind ? decorator(target, key, result) : decorator(result)) || result;
if (kind && result)
__defProp(target, key, result);
return result;
};
var __privateAdd = (obj, member, value) => {
if (member.has(obj))
throw TypeError("Cannot add the same private member more than once");
member instanceof WeakSet ? member.add(obj) : member.set(obj, value);
};
var _field;
const decorate = () => {
};
export let Class = class {
constructor() {
__privateAdd(this, _field, "field");
}
};
_field = new WeakMap();
Class = __decorateClass([
decorate
], Class);
*/
How can I keep a private field under an experimental decorator?
That was because experimentalDecorators
implies lowering all static fields to avoid the issue when referencing the class name in static fields, which implies lowering all instance fields to avoid the issue when a static field gets private instance field outside of the class.
https://github.com/evanw/esbuild/blob/40711afe0baa545012b813c5c7788225be9ef74c/internal/js_parser/js_parser_lower_class.go#L393-L410
https://github.com/evanw/esbuild/blob/40711afe0baa545012b813c5c7788225be9ef74c/internal/js_parser/js_parser.go#L11343-L11373
Due to the "scan twice" design of esbuild, I'm not sure if it can take further optimizations on this thing. Given these facts:
- There's no static fields, so the first condition is not necessary true
- There's no static field referencing the class name or
this
TypeScript Compiler would give this output:
// input
const decorate = () => void 0
@decorate
class A { #a = A.name }
// output
var A_1;
const decorate = () => void 0;
let A = A_1 = class A {
#a = A_1.name;
};
A = A_1 = __decorate([
decorate
], A);
@evanw As @hyrious mentioned above, in this scenario, there is a difference in the implementation of esbuild and tsc. Private fields still exist, no degradation has occurred. Is this a feature or a bug?
Reference tsc: https://www.typescriptlang.org/play?experimentalDecorators=true&target=9#code/MYewdgzgLgBAJgU1AJwIZQTAvDAFLgSmwD4YBvAXyNQhlTAE8BuAKBYAFEV0EWEAPAA4hksYABsatAMKSItMixgwAxADMAlgnFxsMAESbtcfawpA
const decorate = (() => {}) as any;
@decorate
export class Class {
#field = "field";
}
/**
* output
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
const decorate = (() => { });
let Class = class Class {
#field = "field";
};
Class = __decorate([
decorate
], Class);
export { Class };
*/