ryansolid / dom-expressions

A Fine-Grained Runtime for Performant DOM Rendering
MIT License
863 stars 125 forks source link

Feature: `<head>` bubbling #221

Open lxsmnsyc opened 1 year ago

lxsmnsyc commented 1 year ago
// server
function ssrHead(props: HeadProps): void;

// client
function insertHead(props: HeadProps): void;

Extra:

ryansolid commented 1 year ago

I'm super interested in what you are thinking here. If this is viable, this is pretty high priority to me.

lxsmnsyc commented 1 year ago

I think this is 100% viable, we just need to know some edge cases first that may potentially break this. For instance, I'm thinking right now what would happen if a <head> suspends

ryansolid commented 1 year ago

Maybe I can help you think through that. Can you describe what the mechanism is, and how it differs from what we are doing with @solidjs/meta. We've talked a bit in Discord but I think it would be great to have that here.

lxsmnsyc commented 1 year ago

The behavior I'm thinking right now is that <head> renders much like <Assets> that its children only evaluates after the sync render. All <head> declarations are then merged into one to form a single <head>. The resulting <head> is then appended after the <html>'s opening tag (if it exists) or prefixed in the SSR markup.

@solidjs/meta

Currently, what we do with @solidjs/meta is that we use special meta-related components (i.e. Title), attach a special kind of key (data-sm), push them to a meta array, then in hydration, we match the elements with their respective hydration keys. I think this is okay, although this doesn't scale very well when there's a lot of meta tags to render, specially when each Meta component is accompanied with a data-sm and data-hk.

For <head>, the ideal solution is instead to use hydration key ranges (like what we do for components/fragments) wherein we just look up the matching opening and closing hydration comments. This way, there's only one key for every <head> and it's not part of each element, at least with that thing we don't have to worry much about hydration matching.

Comparison (@solidjs/meta vs new <head>):

<title data-sm="<HYDRATION KEY>" data-hk="<HYDRATION KEY>">Hello World</title>
<meta data-sm="<HYDRATION KEY>" data-hk="<HYDRATION KEY>" name="description" content="This is a description">
<!--<HYDRATION KEY>-->
<title>Hello World</title>
<meta name="description" content="This is a description">
<!--/<HYDRATION KEY>-->

On client-side, we can utilize insert to append <head>'s children after the comment anchors

Differences with <Assets>

My biggest issue with <Assets> is its reliance on existing <head>, on top of that, it's no-op if <head> doesn't exist (related #202)

When <head> is used, it guarantees that the resulting markup will have a <head> element, but maybe the problem here is what if it's unideal add <head>. (I'll mention it in the challenges section below).

The new <head> also has type="pre"|"post" to allow users to decide where to inject the children, something useAssets/<Assets> lacks.

Challenges

Suspense and suspending <head>

This might be tricky for the following code:

<Suspense>
  <head>
    <title{titleData()}</title>
  </head>
</Suspense>

in this case, titleData is a Resource data. The obstacle here is whether or not it is possible to bubble from the deferred children to its original Suspense ancestor.

Is <head> worth replacing?

Initially I was thinking if <head> should be replaced or should we just introduce <Head> or <<rxcore>:head> instead, so that the current <head>'s behavior stays the same while <Head> can do the bubbling behavior

What happens if the SSR markup isn't actually a document

This is another challenge. Suppose to say that the target output is for islands, how would we bubble up outside the markup? With @solidjs/meta it's certainly possible since we have the meta array.

Edit: Based on what I've tested, a <head> inside another <head> or other elements (like <body>) will be removed but its children will be moved to its ancestor. This is a browser correction process so I guess this challenge is a minor issue.

Note 🚧 This message is going to be edited over time 🚧