privatenumber / tsx

⚡️ TypeScript Execute | The easiest way to run TypeScript in Node.js
https://tsx.is
MIT License
9.24k stars 142 forks source link

Support `emitDecoratorMetadata` #347

Open pincman opened 2 years ago

pincman commented 2 years ago

some typescript and node framework (like nestjs) use emitDecoratorMetadata , but esbuild not support it, and @anatine/esbuild-decorators plugin can solve it . but esbuild's transform api not support plugin,some can change the api to use build?

like the follow code

async function transpile(code, filename, options = {}) {
    const ext = path.extname(filename);

    const loaders = getLoaders(options);

    const builded = await build({
        ...commonOptions,
        platform: 'node',
        ...options,
        ...{
            loader: loaders,
            bundle: true,
            stdin: {
                sourcefile: filename,
                contents: code,
                resolveDir: path.dirname(filename),
                loader: loaders[ext],
            },
            external: [...externals, ...(options.external ?? [])],
            write: false,
            plugins: [
                ...(options.plugins ?? []),
                esbuildDecorators({ tsconfig: options.tsconfig ?? undefined }),
            ],
        },
    });
    return builded.outputFiles.map((f) => f.text).join('\n');
}
privatenumber commented 2 years ago

We can't use the Build API because it would bypass Node.js' resolver, and add overhead writing/reading disk.

To do what that plugin does, we don't technically need to use the Build API. We just need to re-implement the TypeScript transformation on the file when emitDecoratorMetadata is enabled.

However, this will require TypeScript to be a dependency, and add a bottleneck to transformation, which defeats the point of using esbuild (and this entire module).

Alternatively, we can consider leveraging SWC for this part but this should be investigated first. esbuild is not planning to support it.

wenerme commented 1 year ago

Is it possible to some how enable the tsc for this like https://github.com/thomaschaaf/esbuild-plugin-tsc ?

tianyingchun commented 8 months ago

any progress of this?

tianyingchun commented 7 months ago

any progress of this?

wenerme commented 7 months ago

If you really need this, you can switch to ts-node for now. the setup be like

.swcrc

{
  "$schema": "https://json.schemastore.org/swcrc",
  "sourceMaps": true,
  "module": {
    "type": "es6"
  },
  "jsc": {
    "target": "es2022",
    "parser": {
      "syntax": "typescript",
      "decorators": true,
      "dynamicImport": true
    },
    "transform": {
      "legacyDecorator": true,
      "decoratorMetadata": true
    },
    "keepClassNames": true
  },
  "minify": false,
  "exclude": ["\\.test\\.ts$"]
}

tsconfig.json

{
  "ts-node": {
    "transpileOnly": true,
    "swc": true,
    "esm": true,
    "files": true,
    "experimentalSpecifierResolution": "node"
  }
}

then invoke

pnpm node --loader ts-node/esm --watch src/main.ts

If you encouter some ts-node+swc problem, check this patch

ts-node@11.0.0-beta.1.patch

diff --git a/dist/transpilers/swc.js b/dist/transpilers/swc.js
index c334bd3b18b292c3f865639dca4080ff3dfe53e8..26c37e4b45177554f4a79e311775dab3e9085a59 100644
--- a/dist/transpilers/swc.js
+++ b/dist/transpilers/swc.js
@@ -167,7 +167,7 @@ function createSwcOptions(compilerOptions, nodeModuleEmitKind, swcInstance, swcD
                         : {}),
                 }
                 : undefined,
-            swcrc: false,
+            swcrc: true,
             jsc: {
                 externalHelpers: importHelpers,
                 parser: {

The living example https://github.com/wenerme/wode/blob/main/server.mk#L15 When building with esbuild, you can enable the plugin for this like https://github.com/wenerme/wode/blob/main/apps/server/src/app/scripts/bundle.esbuild.ts

tianyingchun commented 7 months ago

can ts-node support node V20?

tianyingchun commented 7 months ago

it can not works, node:internal/process/esm_loader:34 internalBinding('errors').triggerUncaughtException( ^ Error: ERR_PACKAGE_PATH_NOT_EXPORTED /Users/tianyingchun/Documents/coding/dress/dress-srv/node_modules/@semic/common/node_modules/date-fns/ ./_lib/cloneObject/index.js /Users/tianyingchun/Documents/coding/dress/dress-srv/node_modules/@semic/common/node_modules/date-fns-tz/esm/formatInTimeZone/index.js

wenerme commented 7 months ago

can ts-node support node V20?

Yes

ERR_PACKAGE_PATH_NOT_EXPORTED

you should check the import path

privatenumber commented 7 months ago

I'd like to reserve Issues for those interested in contributing, and discussions constructive towards addressing the issue (e.g. implementing fixes or features). Please discuss workarounds elsewhere.

privatenumber commented 7 months ago

@wenerme:

Is it possible to some how enable the tsc for this like thomaschaaf/esbuild-plugin-tsc ?

This is already answered above:

However, this will require TypeScript to be a dependency, and add a bottleneck to transformation, which defeats the point of using esbuild (and this entire module).

cayter commented 4 months ago

FYI, esbuild just shipped decorators support. Can't wait to test it out with tsx soon.

privatenumber commented 4 months ago

Is it that same thing as this issue? I've personally never used decorators so I'm not sure.

As I understand, what shipped in terms of tsconfig.json is support for experimentalDecorators (JS spec), rather than emitDecoratorMetadata (TS spec), which is what this thread is about.


I published the esbuild v0.21 upgrade in tsx 4.10.0-rc.1 through the development repo (sponsors only). Holding official release for a few weeks since it's still seems to require further testing.

cayter commented 4 months ago

Is it that same thing as this issue? I've personally never used decorators so I'm not sure.

As I understand, what shipped in terms of tsconfig.json is support for experimentalDecorators (JS spec), rather than emitDecoratorMetadata (TS spec), which is what this thread is about.

I published the esbuild v0.21 upgrade in tsx 4.10.0-rc.1 through the development repo (sponsors only). Holding official release for a few weeks since it's still seems to require further testing.

Should be the same thing, I tested it with the snippet which is what NestJS uses and it works well.

privatenumber commented 4 months ago

Do you know why there are two different configs if they're the same thing? Seems like they map to different behaviors.

From TypeScript's announcement:

This new decorators proposal is not compatible with --emitDecoratorMetadata, and it does not allow decorating parameters. Future ECMAScript proposals may be able to help bridge that gap.

cayter commented 4 months ago

Do you know why there are two different configs if they're the same thing? Seems like they map to different behaviors.

From TypeScript's announcement:

This new decorators proposal is not compatible with --emitDecoratorMetadata, and it does not allow decorating parameters. Future ECMAScript proposals may be able to help bridge that gap.

This is a problem that Typescript implemented their own decorators first when tc39 was still figuring out the final spec in JS.

And because of that, evanw had to research into multiple decorators implementation which he tests all of them here to ensure his current implementation sticks as close as to the tc39 one. https://github.com/evanw/decorator-tests

privatenumber commented 4 months ago

That, I understand. But what I'm trying to figure out is if this thread could actually be closed soon.

This issue is specifically about emitDecoratorMetadata. And it seems like what was released in esbuild is support for JS decorators (experimentalDecorators), which you claim is the same thing, but TypeScript seems to claim it's not?

cayter commented 4 months ago

That, I understand. But what I'm trying to figure out is if this thread could actually be closed soon.

This issue is specifically about emitDecoratorMetadata. And it seems like what was released in esbuild is support for JS decorators (experimentalDecorators), which you claim is the same thing, but TypeScript seems to claim it's not?

Oh from what I understand:

The previous esbuild version couldn't understand the decorators syntax in TS which it didn't emit the decorator functions in the JS output. Hence, breaks NestJS or TypeORM runtime functionalities.

With this new release, it now parses the decorators (e.g. @log()) and emits ___decorators({ ... }) in the JS output which allows NestJS and TypeORM to work well.

Also, the current NodeJS runtime with the latest V8 still doesn't recognise decorators syntax yet as tc39 spec for decorators hasn't been implemented in V8 yet AFAIK which is why esbuild have to parse @decorator() and emit them as ___decorators() so that it can be evaluated at runtime.

privatenumber commented 4 months ago

I see, thanks for elaborating. So given this issue is specifically about emitDecoratorMetadata, and not about JS decorators, NestJS, or TypeORM support, it sounds this this is a tangental conversation. Going to hide it to keep the conversation easier to follow for those interested in tackling emitDecoratorMetadata support.

cayter commented 4 months ago

Sorry, just got to my laptop and re-read the conversation plus Typescript's experimentalDecoratorMetadata and experimentalDecorators docs.

I think it's related as mentioned in https://github.com/evanw/esbuild/releases/tag/v0.21.0:

With this release, esbuild now contains an implementation of the upcoming JavaScript decorators proposal. This is the same feature that shipped in TypeScript 5.0 and has been highly-requested on esbuild's issue tracker.

The only difference is esbuild's implementation is following the latest tc39 spec whereas typescript 5's emitDecoratorMetadata (this requires reflect-metadata package to work at runtime) is currently outdated.

Think of esbuild's decorators = Typescript 5's emitDecoratorMetadata + reflect-metadata.

So, Typescript 5's latest experimentalDecorators (no need reflect-metadata) is to match the latest tc39 which is what esbuild latest version currently doing.

privatenumber commented 4 months ago

Yes, it's related in terms of the succession of the feature, but they're different specs so it doesn't solve the reported issue.

Note, this issue is for TypeScript's emitDecoratorMetadata—not for decorators in the general sense—which has support for annotating with type data: https://www.typescriptlang.org/tsconfig/#emitDecoratorMetadata

As you can see from TS's compiled output, it relays type information, which JS decorators don't have access to:

__decorate([
    LogMethod,
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [Number]),
    __metadata("design:returntype", void 0)
], Demo.prototype, "foo", null);

Confirmed in esbuild's playground that it doesn't do this.

Anyway, this is not constructive towards closing this issue which is why I hid it. If you want to talk about this further, please open a thread in https://github.com/pvtnbr/tsx/discussions