TypeScript: Unable to `import` JSX types, Error: "no interface JSX.IntrinsicElements exists", although it exists
TypeScript Version: 4.1.2
Search Terms:
no interface JSX.IntrinsicElements exists
Code
This works:
namespace JSX {
export interface IntrinsicElements {
div: {[k: string]: any}
}
}
const d = <div /> // <-- no error
type test = JSX.IntrinsicElements // <-- no error
But this does not work:
import type {JSX} from 'solid-js'
const d = <div /> // Error: JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.
// But JSX is in scope, just that JSX expressions do not see it.
// As an example, the following non-JSX code works fine:
type test = JSX.IntrinsicElements // <-- no error
Expected behavior:
It should see the definition of JSX.IntrinsicElements
which is clearly present due to the import
statement.
Actual behavior:
The JSX definition in the second example (import
ed) appears to be invisible to TS.
As you can see from both examples, only a locally-defined JSX
works, but not one that is import
ed.
About this issue
- Original URL
- State: closed
- Created 4 years ago
- Comments: 24 (7 by maintainers)
@weswigham @RyanCavanaugh Mind re-opening the issue? The OP describes the issue exactly, there is no need for a new one. But I can make a duplicate if you confirm that would be better.
Right, Global though is basically a mess. You can’t have multiple JSX providers in the same project using Global they attack each other and it is completely broken. In those scenarios it is basically a non-starter.
I strongly dislike the assumption JSX has a single factory function. It is very narrow view that isn’t true. From Inferno’s precompiled VDOM, Solid’s custom DOM transformations… It is not a coincidence these also happen to be the most performant JSX libraries. You will see more optimizations of this nature in the future for performance reasons. Guaranteed. I would expect similar from Vue at some point and I wouldn’t count React out in the future.
There are plenty of things that JSX can generate and do. So
jsxImportSource
not being attached to a factory is a huge step forward to remove the last of the global JSX as it gets adopted. I’m fine with that generally but there are some obvious inconsistencies which @trusktr brings up. I’m not sure the best way though since the default would be to apply it across the project without any per file intervention. The challenge is thatjsxImportSource
only means something terms of the new Babel translation so coming from a clean slate perspective it’s not obvious why. The next time Babel changes (like they just did) so will TypeScript.So it could be beneficial to have a native TypeScript solution, but I do understand if the plan is just continue keeping this perspective and follow Babel’s lead. Interestingly Babel itself does a pretty good job following the spec in terms of their syntax support, whereas TypeScript has historically tuned the implementation to match React.
I appreciate your consideration on this issue. At this point I’m fairly used to having most of the ecosystem assume JSX === VDOM factory function because Babel/TypeScript ship with a default JSX transform. But the spec for JSX makes no mention of what the output should be only indicates the syntax. I’m happy to have any solution at all. Even it is simply from the fact that React decided one day to have a new version that doesn’t rely on a pragma import. The pre 4.1.1 solutions were horrendous, so I will happily follow predefined file paths for types if it accomplishes the goal. I understand since you did not invent the syntax for jsxImportSource you can’t really change its function, but importing JSX working seems like it could be reasonable.
I agree. This discludes other forms of JSX that do not have factory functions (like @ryansolid’s Solid.js is quite amazing), and it means they would have to define a fake type for something that doesn’t actually exist.
Disregarding that, I really don’t see why we can’t just have
jsx:preserve
along withimport {JSX} from 'anywhere'
, which would be intuitive and following standards that the ES community already has, and be on our way.If
import
worked, I wouldn’t have spent multiple days fiddling to try and get JSX segregation working.@weswigham Have you used more than one form of JSX in a single project before? It is a real big pain.
I believe
import {JSX} from 'anywhere'
should at least be supported, regardless of the other stuff. This would greatly simplify things.@RyanCavanaugh I think the OP describes the issue fairly well. I don’t think a new issue would be needed.
Note, Babel’s new
jsx-runtime
feature (which also does not follow ES Module standards by virtue of requiring a particular import path) is specifically for libraries with runtime factory functions, largely guided by React, and ignoring non-factory JSX libraries and frameworks.This is unfair to the rest of the JSX ecosystem, and TypeScript is not providing a more fair and desirable playground for other JSX libraries.
React incubated JSX, a language spec, not a runtime spec.
jsxImportSource
requires ajsx-runtime.d.ts
file at the root of a package. This coupling of JSX types to a specific filesystem structure is not ideal at all.We should be able to
"jsx": "preserve"
in tsconfig.json (or specify a jsxFactory for compile output)import type {JSX} from 'anywhere'
in any file (or import it from a file that makes it global, which already works), or specify"jsxImportSource": "anywhere"
(without the impliedjsx-runtime
path) to have it auto-imported in all files.and be done.
I can’t think of a good reason to have this stuff coupled to specific files or coupled to React.
The “runtime” in
jsx-runtime
doesn’t make any sense for libraries that want to usejsx:preserve
, as there isn’t even a runtime for TypeScript to care about.After fixing
jsxImportSource
to not require some particular file path, it will make more sense because:jsx:preserve
is used along withjsxImportSource
, then one can expect that it will import only type definitions (which apparently is not documented anywhere to begin with)jsx:react
orjsxFactory
or similar are used, then the runtime can be expected to also be imported fromjsxImportSource
But it would be best to simply allow
import {JSX}
(orimport {JSX, jsx}
, orimport {JSX, h}
, depending on tsconfig options) in any file, allowing users to useimport
instead ofjsxImportSource
, being more closely aligned with standards and conventions.@trusktr Can you please log a new concrete suggestion issue for supporting that? Piling on more comments to something that was filed as a bug is very confusing for us; if we want to have a real discussion on that point it’s very awkward that we’d have to scroll down n comments to understand what the issue was really about.
Besides when
@jsxImportSource
comments aren’t working (I posted a reproduction there),I realized that we have to import JSX twice in order to use it in both normal code, and with JSX expressions. This is WET instead of DRY.
So in order for the following code to work, we must import JSX in the same file using both syntaxes:
Try it here live in your browser, or clone the repo, and run
npm install && npx tsx --noEmit
and it will pass type checks. But if you remove either of those imports, one part of the code, or the other, will have a type error.I hope by now you may be convinced that
import {JSX}
for both cases is more intuitive, more DRY, and probably less likely to not work (f.e. playground would just work).