tsx-dom
tsx-dom copied to clipboard
JSX Fragment support
Typescript has JSX fragment support (microsoft/typescript#38720), but tsx-dom hasn't implemented it yet ~~Edit: I've found a temporary workaround:~~
"jsx": "react",
"jsxFactory": "h",
"jsxFragmentFactory": "\"span\"",
Edit 2: The temporary workaround does not work
I'm aware that TypeScript has support. Not sure how you would like this to be implemented though.
tsx-dom is currently always returning an HTMLElement. A Fragment is no HTMLElement. It would be an array of HTMLElements at best. If I change the return type to HTMLElement | HTMLElement[], you would always have to check the type of the returned value, which is a bit ugly.
Possible alternative
I guess it would be an option to return an HTMLElement, which is flagged somehow.
For example <>hello <b>sportshead</b><>
would become <tsx-dom-fragment>hello <b>sportshead</b></tsx-dom-fragment>
and whenever I encounter this tagName inside JSX, I would just extract its children and just trash the tsx-dom-fragment
element itself. But this would only work inside of the JSX syntax. In all other places, where you work with the raw result, you'd have to check yourself.
I.e. If you have the following code:
const Greeting = () => <>hello <b>sportshead</b><>;
const element = <main><Greeting /></main>;
document.body.appendChild(element);
You would see <main>hello <b>sportshead</b></main>
in the browser.
However if you where to use it like this:
const element = <>hello <b>sportshead</b><>;
document.body.appendChild(element);
You would see <tsx-dom-fragment>hello <b>sportshead</b></tsx-dom-fragment>
in the browser.
This would not be what users expected though, so this would have to be made very clear in the documentation.
Of course, you could write a function to help with this issue:
import { h, appendChild} from "tsx-dom";
const element = <>hello <b>sportshead</b><>;
appendChild(document.body, element);
and tsxAppendChild could take care of this for you. But that also adds some complexity to the usage and people might forget.
How about document fragments? https://developer.mozilla.org/en-US/docs/Web/API/DocumentFragment
The issue remains. The jsx syntax can only return one specified type. That is currently HTMLElement. DocumentFragment does not inherit from HTMLElement. Both inherit from Node, but returning Node for all jsx snippets would mean, that for the most common case of HTMLElement, you'd have to cast it.
This is a limitation of the jsx syntax. When using functions, typescript could infer the return type by looking at the parameter types. But the jsx syntax currently does not support this.
That is the reason why this returns HTMLElement and not HTMLInputElement:
const in = <input />;
So if you want the input specific values, you need to cast:
const in = <input /> as HTMLInputElement;
Imagine having to do that for every plain HTMLElement.
See: https://github.com/microsoft/TypeScript/issues/14729
How do the other react libraries do it? It seems like the typings for their Fragments work
After looking through some code, it looks like all the other libs use return props.children
, which won't work with tsx-dom as document.appendChild
doesn't accept multiple args. Perhaps edit Node.prototype.appendChild()
to accept arrays? Although that isn't very clean...
It seems though that Node.prototype.appendChildren()
is kind of becoming a thing, but we still have the problem of users forgetting
This looks interesting: https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver
Perhaps start a observer on the first time that a fragment is created? Then the callback for the observer can remove <tsx-fragment>
elements on dom mutation
The other libs have their own render methods (i.e. you don't do appendChild, but their method does it for you.
prototype polution is an anti-pattern and I won't use it.
MutationObserver might break some of the use-cases, where no real browser is used.
Then what do you suggest?
Well, either wait for typescript to support this scenario or create a hacky solution.
tsx-dom is not a virtual-dom library like react, preact, etc. So it does not manage those kinds of rendering issues for you. It's simply here to make it easy to create elements.
If you're only looking to create a <span>
element and do so by writing it in a short style like <>
, that should be doable by simply specifying a function to use.
"jsxFragmentFactory": "Fragment"
With:
import { h, BaseProps } from "tsx-dom";
// untested
export function Fragment({ children }: BaseProps ) {
return <span>{children}</span>;
}
Then everywhere you use fragments, import that function into scope.
It's not the same though, as your dom will look different and styling may be affected. That's mainly why I'm not gonna build that into tsx-dom by default.
If you find another solution, which does not break existing code, I'm open to take a good look. But right now, I'm not aware of anything.
What about using https://developer.mozilla.org/en-US/docs/Web/API/Document/createDocumentFragment to create a very simple implementation?