Skip to content

Proposed documentation clarification for migrating a Typescript project #1240

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

Closed
nkprasad12 opened this issue Feb 16, 2025 · 9 comments
Closed

Comments

@nkprasad12
Copy link

nkprasad12 commented Feb 16, 2025

Hi Preact team -

Suggestion

Please consider adding a note to the Typescript preact/compat configuration section indicating that if the path aliases for compat are set up, there is no need to specify jsxImportSource: preact in the tsconfig. This may seem obvious but for someone not as used to messing around with the toolchain, such a message could save a lot of time.

Thanks for your hard work on the project!

Background

I was following along https://preactjs.com/guide/v10/typescript

I first followed the Typescript configuration section and added "jsxImportSource": "preact" to my config.

I then moved on to the Typescript preact/compat configuration section and added the following:

    "paths": {
      "react": ["./node_modules/preact/compat/"],
      "react/jsx-runtime": ["./node_modules/preact/jsx-runtime"],
      "react-dom": ["./node_modules/preact/compat/"],
      "react-dom/*": ["./node_modules/preact/compat/*"]
    }

The result was that every single file in my project with JSX had a bunch of type errors for reasons I couldn't understand.

Solution

Eventually (after a few hours of debugging) I realized that once the path aliases are set up, "jsxImportSource": "preact" is not needed.
After that, Typescript is happy, my site works as expected, and all my unit and E2E tests pass.

@rschristian rschristian transferred this issue from preactjs/preact Feb 16, 2025
@rschristian
Copy link
Member

I don't think that's the case, you should still keep setting jsxImportSource. The paths option of a tsconfig.json is a type-only alias, not a runtime aliasing method. It will not supply the correct jsx-runtime to your modules at build.

The type errors might be from not restarting the TS lang server? It can get a little confused when you change config options.

@nkprasad12
Copy link
Author

Thanks for taking a look! Comments below:

Build-time resolution

You are of course correct.
The build-time resolution is resolved by setting the analogous path aliases to my bundler:

  alias: {
    react: "preact/compat",
    "react-dom/test-utils": "preact/test-utils",
    "react-dom": "preact/compat",
    "react/jsx-runtime": "preact/jsx-runtime",
  },

Type checking

If I add that line for jsxImportSource back, I can confirm that the type errors were not just from the TS lang server as they showed up from npx tsc.

Found 340 errors in 53 files.

The error (which shows up in any line containing JSX) is:

Argument of type 'Element' is not assignable to parameter of type 'ReactElement<any, string | JSXElementConstructor<any>>'

@rschristian
Copy link
Member

That's bizarre -- do you have a repo I can take a look at by chance? Setting the JSX transform shouldn't be creating type errors to my knowledge.

@nkprasad12
Copy link
Author

Of course!

Project is here: https://github.com/nkprasad12/morcus-net

  • The main branch is using React.
  • The preact branch has a single commit with my migration to preact.
    • The checked in code does not have jsxImportSource set to preact, and npx tsc has empty output.
    • If you update the tsconfig.json (in the root directory) to add that, I get lots of Typescript errors.

@rschristian
Copy link
Member

rschristian commented Feb 16, 2025

Thanks, that's helpful!

It looks like the type errors are "correct", or at least meant to be occurring. I don't know why this is, it makes no sense to me, but when you don't set jsxImportSource TS is still using @types/react & @types/react-dom (which you probably want to get rid of, though that might require some dependency finagling). If you delete those packages out of node_modules/ you'll see the type errors (even with jsxImportSource still unset).

So this isn't so much of a config issue as it is a types issue, TS just behaves rather oddly here and keeps supplying your app with React's types (which therefore avoid the types issues) when you haven't set jsxImportSource -- that might be a good bug to file with them, I don't understand why that'd have any effect.

@rschristian
Copy link
Member

rschristian commented Feb 16, 2025

Some issues I've come across:

  1. You're using JSX as a global type, which our types intentionally don't support as it causes all sorts of clashes. Even React dropped this in v19. You must import JSX explicitly, i.e., import { JSX } from 'preact' (or from react, either works due to aliases)
  2. Your paths are wrong in your tsconfig.json. You set the baseURL to ./src, but use ./node_modules/... in your aliases. It should be ../node_modules/... (this is probably the issue with the jsxImportSource, really could do with better warnings from TS here though)
  3. You're using a few types we don't offer (will work on this, feel free to open issues with us if you find any we're missing of course)
  4. You're using e.target.value in your event handlers -- you likely want to replace those with e.currentTarget.value. Outside of React and their custom event system, .target is not what you want.

@rschristian
Copy link
Member

Going to close this on out as we got this corrected in your repo

@rschristian rschristian closed this as not planned Won't fix, can't repro, duplicate, stale Feb 16, 2025
@nkprasad12
Copy link
Author

First of all: thank you for such a detailed look! I definitely was not expecting anything like this.

After your heroic effort, I brought in your fixes to my project and patched in your changes to my node_modules/preact (just until they get released) and then removed @types/react and @types/react-dom and was able to fix all the remaining issues.

Two issues I fixed that I wanted to quickly flag:


One issue was in the following snippet:

onBlur={(e) => {
...
e.relatedTarget.ariaLabel
...
}}

This worked with @types/react because they have it as:

    interface FocusEvent<Target = Element, RelatedTarget = Element> extends SyntheticEvent<Target, NativeFocusEvent> {
        relatedTarget: (EventTarget & RelatedTarget) | null;
        target: EventTarget & Target;
    }

while preact just tells me it's an EventTarget.

I think preact's behavior is the correct one (looking at relatedTarget docs) but just flagging in case you think there reason relatedTarget can be narrowed incertain circumstances.


The other issue I was related to type widening, which I am not exactly sure why React's CSSProperties didn't cause the same issue, but there were a few places where I had to change e.g.

const foo: CssProperties = {...};

to

const foo = {...} as const satisfies CssProperties;

to get things to work correctly. I am no Typescript expert so I don't know if this is the @types/react-dom typing being too lax or preact being too specific, or perhaps neither. But just mentioned to close the loop here.


Thanks again for your help on this! I really appreciate it.

@rschristian
Copy link
Member

After your heroic effort, I brought in your fixes to my project and patched in your changes to my node_modules/preact (just until they get released)

Actually, they were released earlier today as 10.26.0, should be able to bump your Preact version & get access to them.


I think preact's behavior is the correct one (looking at relatedTarget docs) but just flagging in case you think there reason relatedTarget can be narrowed incertain circumstances.

Not sure to be quite honest. It's possible it needs to be added here but I'm not familiar enough myself with our TS event handling.


For CSSProperties, React's types depend on csstype which I think we didn't want to get into. CSSProperties in our types extend an interface AllCSSProperties which has a [key: string] index property which I'm guessing is the reason why as const satisfies CSSProperties is necessary, but I'm not 100%.

Adding pkg dependencies just for types is a bit weird, especially as we ship our types with the module (unlike React).

But no problem! Glad I could be of some help.

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

2 participants