Closed thorn0 closed 9 years ago
"it will be impossible to incorporate that syntax into TypeScript because of type assertions."
This is not the case. As i mentioned in the original thread:
Hey Guys,
TS developer here (and guy who wrote the current 1.0 parser). I haven't read through the entire thread. However, from looking at a few examples of what you're asking for, this seems like it would be very easy to add in a fork of the typescript parser, if one were so willing.
The algorithm would be pretty darn simple. First, you'd prescan the file, looking for XML close tag candidates. i.e. you'd have a regexp that would match strings like (allowing for spaces between things). With that, you'd have a table mapping potential end tags to locations in the file.
You'd then need to augment the scanner and parser. The parser would be updated such that if it hit a potential opentag, it would need to speculatively parse ahead to see if this was actually XML or just a cast. The end-tag table would be helpful here as it would allow you to even decide if it was worthwhile to do that speculative parse. i.e. if you saw and had never seen in your doc, then there would be no point speculatively parsing. On the other hand, if you had and did have a tag later on, then it would be worth going forward.
If you never find the end tag, then you rewind parsing, and go ahead parsing the entity as a type assertion expression.
This approach is very similar to the one we already need to take today to deal with the ambiguity between type assertion expressions and generic arrow function expressions. Speculative lookahead is necessary (and already done here), so it's no stretch to augment it to support XML. The only little trick here is to get the end tags to ensure you don't speculatively parse every time, most of the time when it's not going to be fruitful.
Cyrus covered some of the issues here. Changing the syntax for type assertions would be a pretty serious breaking change. If we have to do so for some reason in the future (like a future EcmaScript version) then I think we cross that bridge when we get there.
I didn't propose to change the syntax. I asked for adding an alternative one. It's not a breaking change.
@thorn0 I think the subtle issue here is that while you are proposing an alternative syntax for type-assertions, you seem to be implicitly stating that the existing type-assertion syntax would then be used for the xml-like syntax extensions. That part is what could be a significant breaking change.
That said, i still think this is very possible to do. However, i don't think it currently has enough interest to warrant the work right now. If you can show that ther'es enough interest in the community for this, that could change. Thanks!
If you can show that ther'es enough interest in the community for this, that could change.
— CyrusNajmabadi
There is about 100k monthly downloads of Rect.js library from the npm registry. Even if only 10% of this audience would willing to use TypeScript with React+JSX that looks big enough, IMHO. But, as of now, all this audience is steadily switching from TypeScript to Flow.
For the record, you can easily subsume this functionality with template strings/tagged template strings, which will be available in 1.4.
Now that TypeScript is a first-class citizen in Angular (2.0), it would be nice if React got some love too. There are ways to integrate them today but they are clunky, not very IDE-friendly, etc. I'd personally love if I could combine the two technologies seamlessly.
:+1: definetly a must have! I would love to have type safety in my whole application, even in the views
@CyrusNajmabadi
I think the subtle issue here is that while you are proposing an alternative syntax for type-assertions, you seem to be implicitly stating that the existing type-assertion syntax would then be used for the xml-like syntax extensions. That part is what could be a significant breaking change
It's not the case. Right, the existing syntax wouldn't be used in the files containing JSX expressions. However, the new syntax is all that is needed from TypeScript here. It's not a breaking change at all. The JSX expressions would be processed by the JSX processor, not by TypeScript. The TS compiler would get normal TypeScript code as its input, without any extensions. How is this a breaking change?
The React team would've been glad to add the support for the TS constructions to JSX, but the current syntax for type assumptions just makes it too complicated.
This is a noob question but how comes that Flow doesn't have problem with angle brackets + generics + JSX and TypeScript does?
There is no problems with generics, only with type assertions. Flow doesn't use angle brackets for them.
Ah ok.
Is the problem here with JSX where there is angle brackets? Couldn't JSX just simply have escaping possibility:
var a =
<SomeComponent>
{
if (someFunc\<generic\>(param)) {
<NotGeneric>
}
}
</SomeComponent>
Edit or in case of type-assertions:
var a =
<SomeComponent>
{
if ((\<SomeType\> a).test) {
<NotGeneric>
}
}
</SomeComponent>
Either way, I think JSX transformer should have escaping.
It's definitely worth mentioning on this issue that @fdecampredon has a fork jsx-typescript
Forking TypeScript :-1: is like expecting this one guy to use all his remaining free time to merge.
This thread does not intend to add JSX support to TypeScript but instead have possiblity to write TypeScript inside JSX syntax. And I think this problem should be taken to JSX transformer, and perhaps not discussed in TypeScript issue list at all.
@Ciantic for what it worth I intend to maintain my jsx fork since I need it for my own projects.
Also note that with the technique that @CyrusNajmabadi described I was able to support type assertion and jsx syntax, so I don't think there is a need for a new type assertion syntax.
I will always have much harder time convincing my bosses to use a TypeScript clone rather than TypeScript itself. And with React Native, it's even more possible that React / JSX will be an important technology to support in the near future.
@borekb while I understand your point what do you expect ? I mean actually jsx syntax is a syntactic sugar created for React and completely dependent of React, for example:
<div />
Is compiled down to :
React.createElement('div', null);
It's not the purpose of typescript to support every dsl language created by every js framework out there, and I would be seriously surprised that the typescript team decide to support something like that directly.
If you don't want to use a fork you still have few options :
var h = React.DOM;
h.div(null, h.span({className: 'something'}))// instead of <div><span class="somehing" /></div>
React.createClass
directly by renaming itvar h = React.createElement;
h('div', null, h('span', {className: 'something'},)); // instead of <div><span class="somehing" /></div>
@fdecampredon
It's not the purpose of typescript to support every dsl language created by every js framework out there
Agree, but I'd argue that React is not just "another JS framework" and I believe it (incl. JSX) should get some consideration from the TypeScript team. All your alternatives make it possible to combine TS + React which is good and I know about them but the convenience and/or toolability is simply not there. There is a reason why FB introduced JSX and there is a reason why it's not in string templates, comments etc.
but I'd argue that React is not just "another JS framework"
It looks and smells rather like a JS framework. For it not to be a framework, it should be indispensable. I believe React deals with the specific problem of binding a JavaScript model to the DOM. The alternatives are Knockout JS and Angular JS.
Agree, but I'd argue that React is not just "another JS framework" and I believe it (incl. JSX) should get some consideration from the TypeScript team. All your alternatives make it possible to combine TS + React which is good and I know about them but the convenience and/or toolability is simply not there. There is a reason why FB introduced JSX and there is a reason why it's not in string templates, comments etc.
That's why I made jsx-typescript :D
Don't get me wrong, I'm glad your fork exists. However, for real-world projects, there are other important questions to be answered like does it have support in Visual Studio? In WebStorm / IntelliJ IDEA? How quickly is the fork updated for new releases of TypeScript? Etc. All these questions wouldn't exist if TS supported JSX out of the box which is why I think it would be valuable.
Yup I understand @borek, in the meantimes I can answer your question:
Also note that jsx is not the only painful point of React/TS integration, see https://github.com/Microsoft/TypeScript/issues/1926 and https://github.com/Microsoft/TypeScript/issues/1947, jsx-typescript resolve that by creating special cases with type inference and jsx-element, I doubt the official ts compiler could make this kind of choice.
Thanks for shedding some more light on it, @fdecampredon.
It's not the purpose of typescript to support every dsl language created by every js framework out there
What if TS and its language services provided a generic extension point for plug-in code preprocessors? Preprocessors would generate TS source files (or AST) and something like source maps. The language services would consume those maps and this way enable the support for any kind of DSL in VS and the other IDEs that use the language services. Sounds too utopian, right?
I mean actually jsx syntax is a syntactic sugar created for React and completely dependent of React
Does it have to be?
That's actually not what I thought was being proposed at all.
What I was hoping for was something along the lines of e.g.:
var who = 'World';
var doc =
<foo>
<bar baz="bam" hello=who/>
</foo>;
Being interpreted as something along the lines of:
var who = 'World';
var doc = foo({
children: [bar({
attrs: {
baz: "bam",
hello: who
}
})]}
);
With functions looking something like this:
class Bar {}
function bar(params: { attrs: { baz: string; hello: string } }) {
return new Bar();
}
function foo(params: { children:Bar[] }) {
// ...
}
The names in params
would need to be standardized as e.g. attrs
, children
, etc. and aren't really important due to structural sub-typing, which would enable type-safety even on attribute values and children.
I don't know that it would work like this exactly, that's just a quick sketch, but I was expecting basically tags to be interpreted as function-names, attributes allowing both for string literals and expressions and getting passed as a hash - and either parents receiving their children as arguments, or children receiving the parent as argument, I'm not sure which is better or more flexible..
But so, basically, general-purpose syntactic sugar for function calls where parent/child relationships are important.
You could build any object/data structure you want in this way - doesn't have to be specific to any framework or even to XML or HTML, you could use it for anything.
Also note, no plug-ins required - this is not a framework, it's just syntactic sugar, and IDE support via the language service would be possible out-of-the-box.
But so, basically, general-purpose syntactic sugar for function calls where parent/child relationships are important.
Wouldn't that require following some sort of convention? (I mean to the extent the attributes and element names are translated into functions.) And if we are to follow non-Ecmascript conventions then that hints at TypeScript supporting one framework or the other.
You could build any object/data structure you want in this way - doesn't have to be specific to any framework or even to XML or HTML, you could use it for anything.
Isn't that what JSON is for?
@paulvanbrenk I'm sorry to ask that, but I've seen that you added a branch called 'jsxSupport' with basic jsx tests, is it planned to support jsx ? if yes I could perhaps do a pr ?
Hi @fdecampredon. We’re in early experiments to see what it would take to add JSX support, and if we could get a solid implementation without impacting performance or compiler complexity too greatly. Nothing committed yet, but we are aware of the value for TypeScript users this would have. Your existing work in this space is awesome, and we’d be more than happy to have you help us in any way.
Wouldn't that require following some sort of convention? (I mean to the extent the attributes and element names are translated into functions.)
You would need a convention only for the names used for the object that gets passed as the argument to the functions, e.g. children
and attrs
- element names are 1:1 with function names, so no convention there, and attribute-names are 1:1 with property-names in attrs
, so no convention needed there either.
And if we are to follow non-Ecmascript conventions then that hints at TypeScript supporting one framework or the other.
This is just alternative syntax (sugar) making complex function call structures easier to read. Anyone can implement such functions for any framework, or for that matter, simple functions that don't require any framework. For example, a simple set of functions could be written to return DOM elements and apply attribute-values as HTML attributes.
You could build any object/data structure you want in this way
Isn't that what JSON is for?
JSON is a data structure, it doesn't build data structures - it doesn't actively "do" anything, it's a data format.
What I'm proposing here is not a template syntax - if you implement functions that perform template engine work, you could of course implement templates, but the point is, it's really just syntax for nested function-calls, so you could implement just about anything. What comes out of an XML-like expression literal isn't a document object model of some sort (as in JSX) which needs further processing by being passed through a function, which then has to parse that document model using code and probably a state-machine.
Really, all you get with JSX (as far as my understanding) is an XML parser, which means you have a very weak coupling to the programming language itself - you still then have to write an actual document parser, which has to do run-time work to make sense of element names and attributes etc... so you get no static coupling to any real language features, most importantly type-checking, and deciding which functions to invoke or what to do with the document becomes entirely run-time.
I think what I'm proposing is much simpler, thinner, and more directly coupled to the language - the idea is not simply to build data structures (which we can already do with object literals) but to make complex function-call trees more legible.
A simple example to get you thinking about this:
function a(params: { attrs: { href: string }; text: string }) {
return `<a href="${params.attrs.href}">${params.text}</a>`;
}
var a = <a href="http://github.com">GitHub</a>;
The interpretation of the XML-like initialization is literally:
var a = a({attrs:{href:"http://github.com"}, text:"GitHub"});
So the compiler (and language service) can type-check at design-time, e.g. checking the presence and types (and structural sub-types) of the arguments. It can also infer the return-type of the top-level call to a()
, which in this case is string
, but the syntax works for building document models too:
function a(params: { attrs: { href: string; }; text: string }) {
var e = new HTMLAnchorElement();
e.setAttribute('href', params.attrs.href);
e.textContent = params.text;
return e;
}
var a = <a href="http://github.com">GitHub</a>;
The point is, this syntax works for building anything, not just for building documents, and it leverages type-checking, return-types, inference, etc. - rather than just embedding documents and type-checking XML syntax, this type-checks arguments, return-types, child element types, etc. which seems much simpler and more powerful to me. You don't need a run-time document parser, and you're leveraging all the strengths of the language.
One more simple example to get you thinking about this - using optional attributes and closures:
function a(params: { attrs: { href: string; onclick?: { (ev: Event):any } }; text: string }) {
var e = new HTMLAnchorElement();
e.setAttribute('href', params.attrs.href);
e.textContent = params.text;
if (params.attrs.onclick) {
e.addEventListener("click", params.attrs.onclick);
}
return e;
}
var a = <a href="http://github.com" onclick=(ev) => alert('leaving!')>GitHub</a>;
The last initialization is literally interpreted as:
var a = a({ attrs: { href:"http://github.com", onclick: (ev) => alert('leaving!')}, text:"GitHub" });
So here you get type-checking for the optional onclick
callback.
Try to imagine the safe, expressive DSLs you could build with type-checking on both function-calls, attributes and children - as opposed to a simple embedded XML syntax, where all of these type-checks need to happen at run-time in a document parser.
I know this isn't where Facebook is heading with JSX, but is that the right direction? Is it even an interesting feature? We have object and array literals for document-like data structures already, providing the same basic type-safety, though, actually, probably more type-safety. I don't see JSX really integrating with the language, so much as co-existing inside it - to me, that's not very interesting or very powerful.
Perhaps in plain JS this is more relevant, since you don't have any type-checking or inference to leverage in the first place - but in TS, I think we can do better?
@mindplay-dk I'm not sure what you're proposing.
Do you propose a library which can generate Dom Elements, using functions like the 'a' function you define in your post? As you show in your post, this is something you can create right now, no changes needed... Or do you propose a transpiler, which takes the 'jsx' syntax and converts it a method call using your 'a' function.
Our current thinking is that there will be (and are) many transpilers from jsx -> js, so the biggest gain for TypeScript is to support 'jsx' constructs in a 'tsx' file. Thus adding a step to the existing workflow, tsx -> jsx -> js, while giving users the freedom to chose their jsx transpiler.
Make sense?
@mindplay-dk
I know this isn't where Facebook is heading with JSX
I think you're wrong. I can't see any difference between JSX and what you're describing. Except that you're using different properties of the only parameter for attrs and children whereas Facebook just uses different parameters for them. But that doesn't count. Have a look at the JSX spec: https://facebook.github.io/jsx/
@paulvanbrek the compiler will not transpile jsx constructs, but if so how will jsx element be typed ?
@fdecampredon that's still an open discussion, Flow types them as 'any'.
@paulvanbrenk While the type of an element itself is not so important, having types for attributes is invaluable, and flow has special case for react element if I remember correctly. Also in that scenario we would loose any kind of completion, and I don't think that a lot of jsx compiler support sourcemap double sourcemap. For all this reason I'm a little afraid of the added value of the proposition.
@fdecampredon can you share an example where you would use attribute types.
As for typing the expression, I believe we have 3 options:
any
pros:
cons:
{}
pros:
cons:
React.ReactElement
/React.DOMElement
pros:
cons:
A variation might be to have a way to tag XML literals, similar to template string tagging or decorators which would pass the resulting object to a function that interprets the attributes and types the result.
eg.
import jsx = require('jsx');
return @jsx <div foo=bar>;
In the "jsx" module:
interface JSXLiteral {
[index: string]: <union of types that an attribute value can have>;
children: (JSXLiteral | string)[];
}
function jsx(obj: JSXLiteral): React.DOMElement { ... }
I'm wary of complicating the language for this though, and the knock-on complexity it could add to all the tooling related to TypeScript, eg. syntax highlighting, linting, etc.
@thorn0
I know this isn't where Facebook is heading with JSX
I think you're wrong. I can't see any difference between JSX and what you're describing.
For one, everything passes through a global function React.createElement()
, which couples it to a framework, and would mean no static type-checking for elements/attributes (?)
It also seems like the result is always an object model, at least as intermediary? Of course you could pass the resulting object model to something that generates strings, but that would mean a full object model always need to be constructed first, then processed separately. No reason this shouldn't work for strings or any other return type, as far as I can figure.
@mhegazy In the previous versions of React, JSX files contained so called JSX pragma: /** @jsx React.DOM */
. Then the React team decided to get rid of it. Something like that pragma can be introduced in TypeScript to define the semantics for JSX syntax constructs and to leave a possibility to change it without breaking existing code.
@thorn0 I think what @mindplay-dk meant is that don't bake this support with thinking about React too much.
Consider this <Something prop={5} />
is transpiled to React.createElement(Something, {prop: 5}, ...
obviously this is not a good idea when using TypeScript, since it can't type check the properties. To get it fully typed it should be something like Something({prop : 5}, ...
@Ciantic @mindplay-dk Ah, okay. Last time I looked at React's JSX transformation it transformed <div/>
to React.DOM.div()
. It's not longer the case.
@thorn0 yeah, it's not longer the case. And I can't understand why? Facebook has this parellel Flow typing effort going on, how are they going to make property checks typed now? I get the feeling it's going to change more as the Flow becomes more mature.
One can try the latest JSX compiler in here: https://facebook.github.io/react/jsx-compiler.html
@Ciantic Flow is very tightly integrated with React and JSX. For instance, Flow looks at calls to React.createClass along with propTypes to do type checking of props in JSX.
@mhegazy With React JSX :
<MyComp prop={true} />;
<div id="hello" />;
is transformed to :
React.createElement(MyComp, {prop : true });
React.createElement('div', {id : 'hello' });
With React.createElement
signature being:
createElement<P>(type: { new(p: P): React.Component<P> }, props: P): ReactElement<P>
createElement(type: string, props: HTMLAttribute | SVGAttribute): ReactDOMElement
Having type-check on P
properties is really useful.
With jsx-typescript I had to add special rule in call resolution for JSXElement so type was inferred only from the first argument (and P was typed correctly).
By the way, I'm working on a cleaned version of jsx-typescript based on the master and I'll submit a PR just to feed the discussion.
@mhegazy I think a good solution for typing could be to let the user set the factory function for jsx element with directive/options, for example :
/// <jsx-factory function="React.createElement" />
class MyComponent {
....
render() {
<div /> // typed as React.createElement('div', null); not transpiled
}
...
}
...
Or :
tsc --jsxFacotry=React.createElement
This way anybody could use JSX for his own framework and it would be typed correctly
Note: I think it's really important to have to have type inferred only from the tag in case of JSX Element, without that the typing is near to be useless for React (and I think for any other framework that would like to use JSX)
@fdecampredon Is this valid TypeScript definition?
createElement<P>(type: { new(p: P): React.Component<P> }, props: P): ReactElement<P>
If that is all it takes to make properties type checked it should be commited to here: https://github.com/borisyankov/DefinitelyTyped/blob/master/react/react.d.ts I tried it, it doesn't throw any weird errors but couldn't get the property type checks to work, at least without jsx-typescript fork.
as a happy user of jsx-typescript, this is an encouraging discussion- so much of javascript is views, and typechecking for them is of course super valuable. I'm excited to see the possibility of react+ts gaining first party support
The current syntax for type assertions prevented the React team from adding TypeScript support to JSX. While E4X was a failure, XML-like syntax extensions is a good idea, which might make it into some next ECMAScript version. If it happens, it will be impossible to incorporate that syntax into TypeScript because of type assertions.
(ported from http://typescript.codeplex.com/workitem/2608)