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

DOM Diffing fails when slot elements are used in the element definition #8

Open
kristoferjoseph opened this issue May 16, 2024 · 0 comments

Comments

@kristoferjoseph
Copy link
Contributor

kristoferjoseph commented May 16, 2024

The problem

Given this element that benefits from DOM Diffing since it would require multiple DOM manipulations to update the element when the heading attribute changes:

export default function MyHeading({ html, state }) {
  const { attrs = {} } = state
  const { heading = "default" } = attrs
  return html`
    <style>
      :host > h1{
        font-size: 2rem;
        text-transform: uppercase;
      }
    </style>
    <h1>${heading}</h1>
    <p>${heading}</p>
    <a href="/foo/${heading}"></a>
    <slot></slot>
  `
}

The unnamed slot allows any content to be set as children used as such:

<my-heading heading="works">
  <button>Enter</button>
</my-heading>

Initial SSR does the correct thing and expands the custom element correctly giving you:

<my-heading heading="works">
   <h1>works</h1>
  <p>works</p>
  <a href="/foo/works"></a>
  <button>Enter</button>
<my-heading>

The issue arrises when you update the heading attribute and the DOM diffing happens.

The template written into the page looks like this:

<template id="my-heading-template">
    <h1>default</h1>
    <p>default</p>
    <a href="/foo/default"></a>
    <slot></slot>
</template>

One thing to note is that this assumes that the template will only ever be used with a Custom Element and not the Shadow DOM. A user needs to define a second Custom element in order to use the Shadow DOM with a different instance as the style tag needs to remain in the template to apply to the Shadow root.

The way the DOM diffing works is that we call the Element function when attributes or store keys are updated and then compare the returned HTML string to the currently rendered Element in the page.

In the case where the heading attribute is updated to "nope" the element function containing slots from above will be rendered as:

 <h1>nope</h1>
<p>nope</p>
<a href="/foo/nope"></a>
<slot></slot>

and the existing markup will be

<h1>works</h1>
<p>works</p>
<a href="/foo/works"></a>
<button>Enter</button>

So the diff will fail on the slot to button comparison leaving you with nothing rendered in the place of the slot.
There is no good way currently to determine what the content of the slot should be since the original markup has been mixed with the template contents and slot replacement.

Expected result

<h1>nope</h1>
<p>nope</p>
<a href="/foo/nope"></a>
<button>Enter</button>

Actual result

<h1>nope</h1>
<p>nope</p>
<a href="/foo/nope"></a>

Some ideas:

  1. We could add a property of initialMarkup that in this case would contain the <button>Enter</button> markup and then use that to expand the slots in the browser during the render.
  2. We move the code that removes the style and script tags into their own functions in order to reuse them as this.removeStylesTags() during rerender and before DOM diffing.
  3. Open to other suggestions.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant