-
Notifications
You must be signed in to change notification settings - Fork 47.5k
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
TypeScript support for JSX #759
Comments
JSXTransformer uses Unless you are willing to put in that effort, it's unlikely that this is going to happen any time soon or at all in the foreseeable future. After all, JSX is only sugar for very similar looking and functionally equivalent JS code. |
Why does JSXTransformer need a JS parser at all? Why can't it just replace its XML-like constructs with JS code wherever it finds them (except string and comments)? |
Contrived example:
The value shouldn't end at the first |
Technically you don't need a parser but you need a tokenizer. Sweet.js macros operate on the token stream before it hits the parser. JSX almost works with Sweet.js except there are special rules for regular expressions. Closing tags doesn't work with the / token depending on context. The regexp resolution rules are hardcoded into the tokenizer. Sweet.js probably can't support our whitespace rules neither. However, if we treat JSX as strictly part of a custom tokenization stream, we can do the transformation purely on the token level, just like Sweet.js. That should leave it compatible with any other language extension that doesn't need special tokenization rules. The resulting code would even be compatible with Sweet.js macros. What do you think @jeffmo ? |
@sebmarkbage As long as we assume that all opening brackets must have a corresponding closing bracket it should work technically. It will also have to assume that expressions, object notation, array notation, etc is compatible or the output will have to be configurable. But we will lose the ability to detect and report syntax errors before we inject {expressions} into the output stream, so eventual errors in the output stream will likely be a lot less comprehensible as a result, I'm unsure about how bad they actually can become in the worst case. There's no denying that there are advantages to operating on the token stream like this, I just fear that it will be at the expense of making JSX less user friendly and thus less appealing, JSX is just sugar so I would never sacrifice the convenience of really good error message with the convenience of something that looks like HTML. Hopefully I'm wrong and it's not bad at all, but I think it's a very fine line between JSX being a very nice optional feature and a nice toy. JSX is really compelling to me, primarily because it's cleaner than the JavaScript equivalent without any immediate drawback ( |
Even if it was true that we can't get good enough error messages using a token-transform alone you can still have the parser provide context to the tokenizer. If your parser can provide a compatible context , that's great, you get better error messages for free. If it can't and you have to pipe the raw transformed strings, you still have the option to do that. If you want to use TypeScript or HipsterScript you can. We can't add a convenient extension to the language and expect that nobody else will add other extensions. People will want them both. If you have to choose between TypeScript or JSX then JSX will certainly be seen as the toy. Unless there is an ambiguity problem ofc. TypeScript may not be compatible for other reasons. That's why I like macros because they can be contextual extensions. An identifier can be treated differently depending on if it's a known React/JSX component or a Type/Class/Interface. |
TypeScript is similar to Sweet.js macros in that it's a superset of JS, just another convenient extension. However, yes, there might be incompatibilities as the type cast operator looks like this in TypeScript: this.span = <HTMLSpanElement>document.createElement('span'); Oops... |
@sebmarkbage You're more experienced in this area, but it seems like you would have always to explicitly extend the parser of the target language or use the standalone token-transform version, the likelihood of their and our parsers and tokenizers being compatible seems remote (unless esprima is the parser in the JavaScript world). Or am I missing something? If that's the case, I haven't looked into the details, but it seems like a simple fix to just implement an optional I could probably even put together a proof-of-concept if it's worth pursuing that path. |
Apologies for just chiming in without reading the discussion. I just thought I'd let you know the result of a previous discussion I had with someone who started adding JSX support into TypeScript. We found it was difficult to distinguish type parameters |
@thorn0 @jordwalke It seems like It seems to me that your suggestion |
I was worried about maintaining two versions. In theory you could make a tokenizer that can be a drop in replacement in an esprima based typescript parser. But that's certainly more difficult. This could be a nice quick fix. You can always add special look ahead/behind rules. It doesn't have to be pure. On Jan 2, 2014, at 7:14 AM, Andreas Svensson [email protected] wrote:
|
@sebmarkbage I'm playing with a proof-of-concept and it was a simple as I had hoped, the major issue that I hadn't foreseen is that it's basically not possible unambiguously detect the initial tag, without the parser to provide context or without any additional starting token/constraint. If a language supports a feature like So I'm unsure how flexible/verbose we should make it, the major issue being that if we require an additional token for the starting tag, it has to be repeated for every branch of conditional expressions with tags. |
This is already a problem in JavaScript with regular expressions. You can still implement JS highlighting without a proper parser because you can use look behind to disambiguate. It's not easy to enumerate all the potential cases you'll have to look for though. Hopefully they're bounded. Sweet.js has a pretty good write up on this for regular expressions. Since those are allowed in similar places as JSX I figure the solution would be similar. https://github.com/mozilla/sweet.js/wiki/design |
Of course the extended language could add features that you can't account for. So it may not always work. That's why it would be great to have at least a little bit of feedback from the parser. E.g. if a regular expression is allowed, then can we also assume that < is the start of a tag. |
Yeah, so I took a step back and analyzed the problem properly. I see three ways of doing this:
Unless I'm missing something, TypeScript can only be solved (with or without parser backup) by either modifying the syntax for the empty root tag (like @sebmarkbage Your take? Do we need an alternate syntax for special-cases? |
Note: typescript also uses for generics
|
@vjeux I can't think of any case where generics are ambiguous with JSX. Because they're always preceded by an identifier which is not valid JSX. They're ambiguous with JavaScript though! Type casting is a big issue though. The parenthesis doesn't help. Most cases are actually not ambiguous:
The parsing code and error messages becomes really weird when you have an opening tag without attributes. You have to optimistically parse ahead a long way to find the matching closing tag which breaks the ambiguity.
Even then it's ambiguous with regexps.
So, yea. The type cast syntax screws us up. I really want to make this work though. |
Note that |
@spicyj It's not valid TypeScript though. Need to make it into a RegExp to make it ambiguous I think. Maybe you can think of another case? |
Sorry, I thought you were implying that you could stop parsing at the close paren. You may be right that regexes are the only tricky part; perhaps we can solve that by requiring people to wrap regex literals in parens? I think JSLint might already warn about that. Still, it does sound like we may need arbitrary lookahead to disambiguate which sounds like a recipe for confusion. (One other idea I mentioned to @syranide in IRC was requiring people to wrap each JSX expression in backticks, sort of like how we recommend JSX in CoffeeScript now. Obviously that's a bit of a pain but it easily removes any ambiguity.) |
Ideally we would use
To make it fully executable ES6. I think that JSX is always close to being more of a pain than it's worth. Compared to just invoking functions. Back ticks, as small as they seem, might be the final straw. |
I guess back ticks would actually remove an ES6 feature unless we prefix with something. So it doesn't work without a prefix anyway. |
@sebmarkbage We cannot reliably disambiguate I agree with backticks, while it would be a solution, it would remove an ES6 feature and it would be quite error-prone when dealing with nested conditionals. Although realistically one could likely just assume that any However, |
Yes. I agree that infinite lookahead is not an ok solution. It's more of a thought experiment to see where it leads us. I think that you're right that an alternative syntax for cast is the right way to go here. Particularly since this syntax is not really intuitive or common for the current use in TypeScript anyway. It would seem that making that change is possible. Some ideas without really thinking it through... The simplest most intuitive to me is that you type the expression:
Another alternative would be to add a contextual keyword somehow:
|
@sebmarkbage The only thing I worry about effectively overriding TypeScript syntax is that this would be a solution only for TypeScript, and it would effectively mean that either you end up with two different type-cast syntaxes depending on whether the current file is run through the parser or not, or you risk breaking existing code if all files are run through it. Also, depending on the replacement syntax for type-casts, you potentially have to extend the actual TypeScript parser as opposed to just having a language agnostic transform which a special-case for TypeScript-style type-casts. What are the current thoughts on extending the language parsers vs a language agnostic transform? It seems like having an agnostic transform would be preferable, and if people really are invested in TypeScript (or whatever) then nothing prevents them/us from implementing a "language native" solution down the road. One potential idea, if my JSX namespaces PR is accepted and merged, would be to simply have the syntax be something like
|
@syranide There can always be conflicts and we have to solve them on a case by case basis. Namespaces could easily conflict with another language too. You can also just use an external function if we kill the type assertion operator.
|
@sebmarkbage Ah, interesting solution. |
There is other problems with typescript + React than just the JSX syntax. |
@fdecampredon It seems like React intends to move to ES6 classes soonish, I'm assuming that would improve the situation? |
@fdecampredon I meant this: https://github.com/fdecampredon/jsx-typescript |
I finally decided myself to finish this work : https://github.com/fdecampredon/jsx-typescript/ is an alpha version, it is just working and need extra works. it's based on master branch of typescript. |
@fdecampredon Very cool. I will test it when I have some available time. 👍 |
Cool!! |
Will definitely take it for a spin ❤️ |
Congratulations! |
@fdecampredon Thanks for your continued contributions in this space! Wish you had released before React.js Conf, definitely would have mentioned it in my talk :( I just updated ts-loader for webpack to support jsx-typescript, so if you're using TypeScript+JSX+webpack you may be interested. |
Thanks @jbrantly unfortunately I had too much work to do before react conf and since my company don't use typescript nor react I can only work on that topic when I have free time. |
I'm using browserify + gulp for incremental TypeScript compilation and can write about that if it would be helpful. Need to check out ts-loader |
Something to flag up: I've written a proposal for another use of the It's over at microsoft/TypeScript#1985 |
@nathggns I don't think there will be any problem, place where a generic can be defined was really not prone to conflict with JSX, the hardest part was about type assertion, which has been resolved with lookahead and close-tags map like @CyrusNajmabadi described, the only compromise I had to accept is to forbid the usage of the char
After they might be still case I have not think about if anyone find one please report an issue. |
@pspeter3 have any config to share ? |
@scboffspring For gulp and browserify? |
@pspeter3 Yes please |
The discussion so far mostly talks about writing JSX syntax in .ts files so you can get the benefits of TypeScript such as static analysis. This would be nice, but this is not the only way to get the benefits of TypeScript. The other way is to write a translator from .jsx to .ts. (This could be a simple change to the existing .jsx to .js translator.) There should also be syntax extensions to specify the state and props fields and their types in an interface. Once translated to .ts, the TypeScript compiler can then verify the usage of props and state fields. |
I have tried this way, but it is a lot more complex than having a fork with jsx support. For all this reasons I think jsx-typescript is a lot more safer and easy to manage than having a build step jsx -> ts. |
@petilon That is exactly what this suggests. I demoed this concept here along with showing some simplistic type checking on state and props. If you're using webpack, super easy to integrate it using ts-jsx-loader. All of that said, everything @fdecampredon says is true. I get around the parser understanding both TypeScript and JSX because I just regular expressions instead of a parser which has its own set of problems. I also require explicitly marking the JSX which I can certainly understand many people not being a fan of. My approach definitely lacks any sort of language service integration (intellisense, etc). And lastly, the type-checking for props using |
Just adding a bit of my personal experience to that discussion. Also, because one needs to import another file (the compiled template), this sometimes end up with circular dependencies which are impossible to solve with the rigid 'import at the top' "feature" of Typescript. A typical exemple is a recursive display of a Menu object. OTHA, inline solutions like ts-jsx-loader in IntelliJ/WS work great when you use backticks (templates strings). Syntax coloring and auto-completion is available for HTML inside the template string. |
@1two the problem is that intellij don't use the LanguageService, which make it very hard to adapt to different ts version. |
@fdecampredon Not sure I follow you; do you mean variables/context discovery (in addition to syntax auto-completion) ? Using this syntax in IJ 14.1/WS 10EAP
I get syntax coloring and auto-completion on the JSX but nothing on |
No typescript comes with a bundled languageService utilities for editor development. |
Couldn't JSX just simply have escaping possibility:
Or in case of type-assertions:
Either way, I think JSX transformer should have escaping. |
Another possibility is to generate TypeScript interfaces from .jsx files. Here's a tool to do this: https://github.com/fuselabs/jsxtyper |
Do you think there will be a solution that will allow TypeScript to be used with reflux? |
@quantuminformation Probably a better question for the reflux folks. |
TypeScript may start supporting JSX per microsoft/TypeScript#3203. We don't have plans to develop JSX support separately from that, so I'm going to close out this issue – but feel free to continue discussing here or on https://discuss.reactjs.org/ if helpful. |
See http://www.jbrantly.com/typescript-and-jsx/ for an update from @jbrantly on the current state of affairs. |
Would be great if JSX recognized TypeScript constructs.
The text was updated successfully, but these errors were encountered: