Skip to content
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

esbuild mistakes typescript generic type for JSX #2363

Closed
fijiwebdesign opened this issue Jun 30, 2022 · 8 comments
Closed

esbuild mistakes typescript generic type for JSX #2363

fijiwebdesign opened this issue Jun 30, 2022 · 8 comments

Comments

@fijiwebdesign
Copy link

fijiwebdesign commented Jun 30, 2022

When parsing .ts as tsx esbuild will mistake generic types for JSX.

✘ [ERROR] The character ">" is not valid inside a JSX element

Here is the file being transpiled

export class RequestState {
  @action resetRequest = <T>(): IPromiseBasedObservable<T> => {
    Object.assign(this.request, RequestState.initialReqState)
    return this.request as IPromiseBasedObservable<T>
  }
}

It thinks <T>(): IPromiseBasedObservable<T> is JSX.

It will eventually error with:

✘ [ERROR] Expected "}" but found "return"

    Request.ts:72:4:
      72 │     return this.request as IPromiseBasedObservable<T>
         │     ~~~~~~
         ╵     }

Here is the esbuild serve.js file:

import esbuild from "esbuild"
import inlineImage from "esbuild-plugin-inline-image"
import sassPlugin from 'esbuild-plugin-sass'
//import babel from 'esbuild-plugin-babel';

esbuild
  .serve(
    {
      servedir: "public",
      port: 8000,
    },
    {
      entryPoints: ["./src/index.js"],
      outfile: "./public/assets/app.js",
      bundle: true,
      loader: {
        ".ts": "tsx",
        ".js": "tsx",
      },
      plugins: [
        inlineImage(),
        sassPlugin(),
      ],
    }
  )
@fijiwebdesign
Copy link
Author

fijiwebdesign commented Jun 30, 2022

Note if the method is not defined with an arrow function it transpiles ok:

  @action resetRequest = <T>(): IPromiseBasedObservable<T> => {
    Object.assign(this.request, RequestState.initialReqState)
    return this.request as IPromiseBasedObservable<T>
  }

to

 @action resetRequest<T>(): IPromiseBasedObservable<T> {
    Object.assign(this.request, RequestState.initialReqState)
    return this.request as IPromiseBasedObservable<T>
  }

@evanw
Copy link
Owner

evanw commented Jun 30, 2022

I get a similar error if I compile your code with the official TypeScript compiler:

Unexpected token. Did you mean `{'>'}` or `&gt;`?

You can see it for yourself here: link. So your code is not valid TypeScript.

@fijiwebdesign
Copy link
Author

Thanks for pointing me in the right direction.

Solution is to add a , to avoid ambiguity between tsx and ts generic type on arrow function.

@action resetRequest = <T,>(): IPromiseBasedObservable<T> => {
    Object.assign(this.request, RequestState.initialReqState)
    return this.request as IPromiseBasedObservable<T>
  }

more: https://stackoverflow.com/questions/32308370/what-is-the-syntax-for-typescript-arrow-functions-with-generics

@dryzhkov
Copy link

dryzhkov commented Feb 10, 2023

@evanw - we ran into this same issue trying to upgrade to ESBuild loader v3.0.0.
TypeScript (we are currently on 4.7.4) will compile this just fine as long as it's not a JSX/TSX file, you can see that in the playground by changing JSX dropdown in TS config options from "React" to "None".
Another potential workaround is to change to regular named instead arrow functions but
this seems like a valid bug in ESBuild.

@evanw
Copy link
Owner

evanw commented Feb 10, 2023

That code does not appear to cause esbuild's output to differ from TypeScript's output. So esbuild appears to be functioning as expected.

As a .ts file, both esbuild and TypeScript correctly accept that code and produce the same output:

$ cat example.ts
// @ts-nocheck
const promisesAllSettled = <T>(promises: Promise<T>[]): Promise<RunAllAsyncResult<Promise<T>, T>> =>
  runAllAsync(promises, p => p);
$ tsc example.ts -target ESNext && cat example.js 
// @ts-nocheck
const promisesAllSettled = (promises) => runAllAsync(promises, p => p);
$ esbuild example.ts
const promisesAllSettled = (promises) => runAllAsync(promises, (p) => p);

As a .tsx file, both esbuild and TypeScript correctly reject that code as invalid:

$ cat example.tsx
// @ts-nocheck
const promisesAllSettled = <T>(promises: Promise<T>[]): Promise<RunAllAsyncResult<Promise<T>, T>> =>
  runAllAsync(promises, p => p);
$ tsc example.tsx -target ESNext 
example.tsx:2:29 - error TS17008: JSX element 'T' has no corresponding closing tag.

2 const promisesAllSettled = <T>(promises: Promise<T>[]): Promise<RunAllAsyncResult<Promise<T>, T>> =>
                              ~

example.tsx:2:50 - error TS17008: JSX element 'T' has no corresponding closing tag.

2 const promisesAllSettled = <T>(promises: Promise<T>[]): Promise<RunAllAsyncResult<Promise<T>, T>> =>
                                                   ~

example.tsx:2:65 - error TS17008: JSX element 'RunAllAsyncResult' has no corresponding closing tag.

2 const promisesAllSettled = <T>(promises: Promise<T>[]): Promise<RunAllAsyncResult<Promise<T>, T>> =>
                                                                  ~~~~~~~~~~~~~~~~~

example.tsx:2:100 - error TS1382: Unexpected token. Did you mean `{'>'}` or `&gt;`?

2 const promisesAllSettled = <T>(promises: Promise<T>[]): Promise<RunAllAsyncResult<Promise<T>, T>> =>
                                                                                                     ~

example.tsx:3:28 - error TS1382: Unexpected token. Did you mean `{'>'}` or `&gt;`?

3   runAllAsync(promises, p => p);
                             ~

example.tsx:4:1 - error TS1005: '</' expected.

4 
  


Found 6 errors in the same file, starting at: example.tsx:2
$ esbuild example.tsx
✘ [ERROR] The character ">" is not valid inside a JSX element

    example.tsx:2:99:
      2 │ const promisesAllSettled = <T>(promises: Promise<T>[]): Promise<RunAllAsyncResult<Promise<T>, T>> =>
        ╵                                                                                                    ^

  TypeScript's TSX syntax interprets arrow functions with a single generic type parameter as an
  opening JSX element. If you want it to be interpreted as an arrow function instead, you need to
  add a trailing comma after the type parameter to disambiguate:

    example.tsx:2:27:
      2 │ const promisesAllSettled = <T>(promises: Promise<T>[]): Promise<RunAllAsyncResult<Promise<T>, T>> =>
        │                            ~~~
        ╵                            <T,>

✘ [ERROR] The character ">" is not valid inside a JSX element

    example.tsx:3:27:
      3 │   runAllAsync(promises, p => p);
        ╵                            ^

  TypeScript's TSX syntax interprets arrow functions with a single generic type parameter as an
  opening JSX element. If you want it to be interpreted as an arrow function instead, you need to
  add a trailing comma after the type parameter to disambiguate:

    example.tsx:2:27:
      2 │ const promisesAllSettled = <T>(promises: Promise<T>[]): Promise<RunAllAsyncResult<Promise<T>, T>> =>
        │                            ~~~
        ╵                            <T,>

✘ [ERROR] Unexpected end of file before a closing "RunAllAsyncResult" tag

    example.tsx:4:0:
      4 │ 
        │ ^
        ╵ </RunAllAsyncResult>

  The opening "RunAllAsyncResult" tag is here:

    example.tsx:2:64:
      2 │ const promisesAllSettled = <T>(promises: Promise<T>[]): Promise<RunAllAsyncResult<Promise<T>, T>> =>
        ╵                                                                 ~~~~~~~~~~~~~~~~~

3 errors

@dryzhkov
Copy link

We only started seeing this after trying to upgrade esbuild-loader to V3 which brings in esbuild 0.17.6. The previous version 2.21.0 and 0.16.17 worked just fine with the same version of TS compiler.

@evanw
Copy link
Owner

evanw commented Feb 10, 2023

If you believe you have found a bug with esbuild, then please file a new issue. Make sure you include a self-contained way to reproduce the problem and in this case also a description of why you believe it to be a bug with esbuild.

@charliematters
Copy link

charliematters commented Apr 11, 2023

I believe I found this too, and have raised #3049

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants