Skip to content
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

JSX Fragment support #4

Open
sportshead opened this issue Oct 17, 2020 · 10 comments
Open

JSX Fragment support #4

sportshead opened this issue Oct 17, 2020 · 10 comments
Labels
enhancement New feature or request

Comments

@sportshead
Copy link
Contributor

sportshead commented Oct 17, 2020

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

@Lusito
Copy link
Owner

Lusito commented Oct 17, 2020

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.

@Lusito Lusito added the enhancement New feature or request label Oct 17, 2020
@sportshead
Copy link
Contributor Author

How about document fragments? https://developer.mozilla.org/en-US/docs/Web/API/DocumentFragment

@Lusito
Copy link
Owner

Lusito commented Oct 22, 2020

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.

@Lusito
Copy link
Owner

Lusito commented Oct 22, 2020

See: microsoft/TypeScript#14729

@sportshead
Copy link
Contributor Author

How do the other react libraries do it? It seems like the typings for their Fragments work

@sportshead
Copy link
Contributor Author

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

@sportshead
Copy link
Contributor Author

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

@Lusito
Copy link
Owner

Lusito commented Oct 23, 2020

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.

@sportshead
Copy link
Contributor Author

Then what do you suggest?

@Lusito
Copy link
Owner

Lusito commented Oct 25, 2020

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

No branches or pull requests

2 participants