prashantpalikhe / nuxt-ssr-lit

SSR support for Lit elements in Nuxt3
Other
42 stars 7 forks source link

Hydration of custom elements that have child custom elements #107

Closed augustjk closed 8 months ago

augustjk commented 8 months ago

Describe the bug When a custom element that also uses a custom element as part of its render template is server rendered, the child might try and hydrate before its parent does. This is problematic as the child might try to hydrate before receiving props from its parent.

A user reported an issue with the Astro Lit integration in the Lit discord(https://discord.com/channels/1012791295170859069/1169254576449396806/1169254576449396806) and I found the same issue in @lit-labs/ssr-react as documented here: https://github.com/lit/lit/issues/4363

To Reproduce One way to surface the problem: In the playground, I made <simple-button> take an array prop that defaults to empty array.

// simple-button.ts
export default class SimpleButton extends LitElement {
  static styles = styles;
  declare disabled;
  declare foo: number[];

  constructor() {
    super();
    this.disabled = false;
    this.foo = [];
  }

  static get properties(): PropertyDeclarations {
    return {
      disabled: { type: Boolean, reflect: true },
      foo: { attribute: false }
    };
  }

  // ...

  render(): TemplateResult {
    return html`
      <button ?disabled=${this.disabled}>
        <slot></slot>
      </button>
      ${this.foo.map((n) => n)}
    `;
  }

I made <my-element> also render <simple-button> as part of its render template, giving it an array as prop.

// my-element.ts
  render() {
    return html` <div class="my-element my-element--${this.theme}">
      <slot name="prepend">Default prepend text</slot>
      <div>
        <button type="button" @click="${this.onButtonClick}"><slot>Lit button with name "${this.name}"</slot></button>
        <simple-button .foo=${[1, 2, 3]}>Simple button</simple-button>
      </div>
      <slot name="append">Default append text</slot>
    </div>`;
  }

This causes <simple-button> to hydrate with the default empty array before it gets the property from parent, causing hydration error with shorter than expected iterable.

Solution These lines here https://github.com/prashantpalikhe/nuxt-ssr-lit/blob/a77f9f8f2f604de178f1b17cb1d4ee1f7b078756/src/runtime/utils/litElementRenderer.ts#L19-L20 should be

      customElementInstanceStack: [renderer], 
      customElementHostStack: [renderer]

Providing the renderer gives Lit SSR context that this is rendering a custom element so any child custom elements should get defer-hydration attribute added to wait for parent to hydrate before hydrating themselves.