-
Notifications
You must be signed in to change notification settings - Fork 1.2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Support emitting typescript decorator metadata #257
Comments
The |
What do you think about adding some way to short circuit the type checking process, such as an EDIT: a little explanation on how an import { Column, Entity, PrimaryGeneratedColumn } from "typeorm";
@Entity()
export class TestModel {
@PrimaryGeneratedColumn()
id!: number;
@Column()
data!: string;
@Column()
complicated!: SomethingComplicated<string, number, Record<string, unknown>>
@Column()
a!: boolean;
@Column()
a2!: Function;
}
interface SomethingComplicated<T, U, V> {
a: T;
u: SomethingComplicated<T, U, V>[];
v: V[]
} will be transpiled to the following JS // ...
const typeorm_1 = require("typeorm");
let TestModel = class TestModel {
};
__decorate([
typeorm_1.PrimaryGeneratedColumn(),
__metadata("design:type", Number)
], TestModel.prototype, "id", void 0);
__decorate([
typeorm_1.Column(),
__metadata("design:type", String)
], TestModel.prototype, "data", void 0);
__decorate([
typeorm_1.Column(),
__metadata("design:type", Object)
], TestModel.prototype, "complicated", void 0);
__decorate([
typeorm_1.Column(),
__metadata("design:type", Boolean)
], TestModel.prototype, "a", void 0);
__decorate([
typeorm_1.Column(),
__metadata("design:type", Function)
], TestModel.prototype, "a2", void 0);
// ... thus, implementing specifically those 5 types would solve it for only reflect-metadata stuff, but I'm unaware of any other scenarios where it'd be beneficial to everyone at large. |
That annotation seems too much of a special-case to put into esbuild core to me. Besides, if you want to be able to do this: class Foo {
@decorator
// @esbuild-type number
prop: number
} couldn't you just do something like this instead? class Foo {
@decorator
@esbuildType(Number)
prop: number
} where let esbuildType = t => __metadata("design:type", t) I think that would do what you're trying to accomplish without needing to extend esbuild at all. In fact it's even shorter than the comment annotation form. I know implementing just those types would be part of the feature, but I don't think it's a good idea to claim to support it and then work differently in subtle ways that silently break things. Even those primitive types can be propagated through arbitrary aliases and type expressions in the type system and can probably be imported from other files. Getting all of that correct means re-implementing the TypeScript type checker. |
Alright, that's completely understandable. Thanks for taking the time to run through the idea 👍 |
I saw swc actually support Thanks. |
This is in scope for swc because they are trying to replicate the whole TypeScript type system in swc. Putting a type system into esbuild is firmly out of scope for esbuild. If you need this feature you should use swc/spack instead (or maybe use swc as a plugin for esbuild?). |
@evanw I am pretty sure that the SWC project to replicate |
Ah weird. Ok well I guess never mind. I missed that part. Looks like this is the relevant comment: swc-project/swc#571 (comment)
|
@evanw Do you think it may still be possible considering how the parts of SWC which act just like Babel are able to do it? |
I'm currently trying to write an esbuild plugin which will try to build with esbuild and compile only the files containing docorators with tsc again. This should result in faster build times as usually not all files use decorators. See #915 (comment) |
I have released the following plugin: esbuild-plugin-tsc which allows you to now use |
@evanw Is there any workaround to this issue? |
One workaround is in the comment immediately preceding yours: you can write an esbuild plugin that runs the TypeScript transpiler on source code files containing decorators before passing the files to esbuild. |
|
…#257 将 lodash 替换为 lodash-es 减小最终打包大小
Just as an experiment I wanted to test how sophisticated the type inference is when creating this metadata. TLDR it doesn't actually do much resolution of complex types at all, and doing something good enough for most use cases could have ok performance. Input file (an example TypeORM table) // snipped imports
@Entity("some_table")
class SomeTable extends BaseEntity {
@PrimaryGeneratedColumn("uuid")
id: string;
@Column()
num: number;
@Column()
something_hard: Pick<{ a: string; b: number }, "a">["a"]; // string
@OneToOne(() => DBDeviceUUID)
lazy_relation: Promise<DBDeviceUUID>;
@OneToOne(() => DBDeviceUUID)
eager_relation: DBDeviceUUID;
@Column()
a_set: Set<string>;
@Column()
a_map: Map<string, string>;
@Column()
a_bigint: BigInteger;
}
const x: SomeTable["something_hard"] =
"just proving that this is just a string"; output file // snipped defintions of metadata and decorate, and snipped requires
let SomeTable = class SomeTable extends typeorm_1.BaseEntity {
};
__decorate([
(0, typeorm_1.PrimaryGeneratedColumn)("uuid"),
__metadata("design:type", String)
], SomeTable.prototype, "id", void 0);
__decorate([
(0, typeorm_1.Column)(),
__metadata("design:type", Number)
], SomeTable.prototype, "num", void 0);
__decorate([
(0, typeorm_1.Column)(),
__metadata("design:type", Object)
], SomeTable.prototype, "something_hard", void 0);
__decorate([
(0, typeorm_1.OneToOne)(() => DeviceUUID_1.DBDeviceUUID),
__metadata("design:type", Promise)
], SomeTable.prototype, "lazy_relation", void 0);
__decorate([
(0, typeorm_1.OneToOne)(() => DeviceUUID_1.DBDeviceUUID),
__metadata("design:type", DeviceUUID_1.DBDeviceUUID)
], SomeTable.prototype, "eager_relation", void 0);
__decorate([
(0, typeorm_1.Column)(),
__metadata("design:type", Set)
], SomeTable.prototype, "a_set", void 0);
__decorate([
(0, typeorm_1.Column)(),
__metadata("design:type", Map)
], SomeTable.prototype, "a_map", void 0);
__decorate([
(0, typeorm_1.Column)(),
__metadata("design:type", Object)
], SomeTable.prototype, "a_bigint", void 0);
SomeTable = __decorate([
(0, typeorm_1.Entity)("some_table")
], SomeTable);
const x = "just proving that this is just a string"; Just to put this in a neater format
I was actually surprised that it didn't get |
@evanw swc supports this, and I doubt they run the full TypeScript compiler, it is likely the actual transform is simpler then you think and doesn't really require complex type resolution. tsup runs swc as a workaround for this not being supported in esbuild, but it would obviously be nicer if this was supported natively. |
I tried looking into the current rules. It looks like they are here. These rules imply that:
Here is an example: // foo.ts
declare let dec: any
import type { ns } from './bar'
class Foo {
@dec fn(e: ns.E): ns.Fn { throw 0 }
} // bar.ts
export namespace ns {
export declare enum E {}
export interface Fn { x(): void }
export interface Fn extends Base {}
type Base = [1] | [false] extends Array<number> ? Y : Call
interface Y { y(): void }
interface Call {
(): void // This makes the return type "Function"
}
} You're supposed to get this: class Foo {
fn(e) { throw 0; }
}
__decorate([
dec,
__metadata("design:type", Function),
__metadata("design:paramtypes", [Number]),
__metadata("design:returntype", Function)
], Foo.prototype, "fn", null); The Obviously it's possible write a type checker for all of this ( Obviously it's also possible to write a simpler type checker that ignores the complexity of TypeScript and checks a different type language instead. That appears to be the approach swc has taken. However, I don't want esbuild to take that approach. That involves signing up for a constant stream of issues (as people would rightly expect esbuild's type system to behave like TypeScript instead of esbuildScript) and it wouldn't be fair to users who would have to deal with subtle and unexpected bugs. |
Thanks for the detailed write up! Guess we can hope stuff will move away from using due to the newer stable decorators... |
For now, guess I'll just use |
For projects such as TypeORM, it is necessary for decorator metadata to get emitted. For example, let's take TypeORM's
typescript-example
project. Running the project normally yields the following output.Attempting to use ESBuild successfully compiles it with warnings, but fails with a runtime error.
The text was updated successfully, but these errors were encountered: