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

Light DOM children / slots (aka HTML Web Components) #154

Closed
thescientist13 opened this issue Apr 10, 2024 · 5 comments · Fixed by #155
Closed

Light DOM children / slots (aka HTML Web Components) #154

thescientist13 opened this issue Apr 10, 2024 · 5 comments · Fixed by #155
Assignees
Labels
0.15.0 documentation Improvements or additions to documentation enhancement Improvement to existing functionality
Milestone

Comments

@thescientist13
Copy link
Member

thescientist13 commented Apr 10, 2024

Summary

Although primarily a client side / runtime dependent technique, the idea of HTML Web Components, seems like a really nice technique to be able to do in situations where it would be easer to "slot" children in via HTML instead of attributes, in particular for very content heavy scenarios and that could still be done at SSR time.

For comparison

<!-- Attributes -->
<app-card
  heading="My Heading"
  image="/path/to/some/image.webp"
/></app-card>

<!-- Light DOM Children -->
<app-card>
  <h1>My Heading</h1>
  <img src="/path/to/some/image.webp""/>
</app-card>

Details

Now this basically relies on doing an innerHTML in the connectedCallback function, e.g.

export class Card extends HTMLElement {
  connectedCallback() {
    this.innerHTML = `
      <div class="card">
        ${this.innerHTML}
      </div>
    `
  }
}

but if done on the server, could basically allow for any sort of option for handling light DOM children that is still standard compliant, especially in conjunction with something like ProjectEvergreen/greenwood#1233


I've tested this in the browser and so as long as this doesn't break any test cases, this seems like a legit feature to support?

@thescientist13 thescientist13 added enhancement Improvement to existing functionality 0.13.0 labels Apr 10, 2024
@thescientist13 thescientist13 self-assigned this Apr 10, 2024
@thescientist13 thescientist13 added this to the 1.0 milestone Apr 10, 2024
@Flrande
Copy link

Flrande commented Apr 12, 2024

We use Lit to build our editor, and due to the feature of rich text editors, we do not use shadow DOM. Lit is simply used as a way to encapsulate HTML (this seems to be referred to as light DOM, please correct me if I'm wrong). Recently, we are researching SSR for light DOM in order to render a read-only editing page in the browser (similar to Notion's public page). For speed and SEO purposes, we need to render light DOM on the server. Is this issue attempting to address this problem? Do you have any possible suggestions for our scenario?🙏

That's out project:
https://github.com/toeverything/blocksuite

You can click this link to know how we use lit:
https://try-blocksuite.vercel.app/starter?init

image

@thescientist13
Copy link
Member Author

thescientist13 commented Apr 12, 2024

Hey @Flrande 👋

So just making sure I'm looking at the right thing, but if this is an example the what you are you referring to then yes, the createRenderRoot override will be bypass rendering into a Shadow DOM with Lit, and instead into the Light DOM. Unfortunately Lit does not support createRenderRoot for SSR use cases, and thus has no way to get just Light DOM out of a LitElement.

For WCC, it supports both because it is a bit more low-level than Lit and so the way you author your WebComponent will determine what WCC server renders out for you when running SSR.

So for this usage

<app-card
  title="Some Title"
  thumbnail="image.png"
/>
</app-card>

Light DOM Example

export default class Card extends HTMLElement {
  connectedCallback() {
    this.innerHTML = `
      <style>
        /* ideally you would use a global styling solution instead of an inline style tag */
      </style>
      <div>
        <h3>${title}</h3>
        <img src="${thumbnail}" alt="${title}" loading="lazy" width="100%">
      </div>
    `;
  }
}

customElements.define('app-card', Card);

Will output

<app-card>
  <style>
    /* ideally you would use a global styling solution instead of an inline style tag */
  </style>
  <div>
    <h3>Some Title</h3>
    <img src="image.png" alt="Some Title" loading="lazy" width="100%">
  </div>
</app-card>

(Declarative) Shadow DOM Example

export default class Card extends HTMLElement {

  connectedCallback() {
    if (!this.shadowRoot) {
      const thumbnail = this.getAttribute('thumbnail');
      const title = this.getAttribute('title');
      const template = document.createElement('template');

      template.innerHTML = `
        <style>
          /* ... */
        </style>
        <div>
          <h3>${title}</h3>
          <img src="${thumbnail}" alt="${title}" loading="lazy" width="100%">
        </div>
      `;
      this.attachShadow({ mode: 'open' });
      this.shadowRoot.appendChild(template.content.cloneNode(true));
    }
  }
}

customElements.define('app-card', Card);

Will output

<app-card>
  <template shadowrootmode="open">
    <style>
      /* ideally you would use a global styling solution instead of an inline style tag */
    </style>
    <div>
      <h3>Some Title</h3>
      <img src="image.png" alt="Some Title" loading="lazy" width="100%">
    </div>
  </template>
</app-card>

This particular feature is more around exploring a use case where providing content to a WC may be more ergonomic to do through HTML tags instead of attributes (in old AngularJS I think they called this transclusion, in React it would be props.children), while still having a JS "hook" to be able to wrap that nested content in HTML at SSR time so that wrapping HTML can include some sort of Tailwind or CSS Modules classes, and thus you can still have a component based development experience for templating and styling, even for more a content heavy website (blog post layouts, code snippet components, etc)

You can see my prototype here if you're interested - https://github.com/ProjectEvergreen/www.greenwoodjs.dev/pull/22/files

This is just for a solution aimed at Light DOM scenarios though, as if you were using Shadow DOM, then <slot> is the API for this, but it only works with Shadow DOM. 😢


Hope that helps and neat project! 🌟

@Flrande
Copy link

Flrande commented Apr 13, 2024

Thank you for your response! It looks like we may need to try implementing our own SSR mechanism for Lit that supports light dom, as our components actually rely on many features of Lit that are not available in native Web Components, such as property-expressions.

https://github.com/toeverything/blocksuite/blob/66eac41b59a9f299be31a682754de753ffdf625f/packages/blocks/src/paragraph-block/paragraph-block.ts#L157-L169

@thescientist13
Copy link
Member Author

thescientist13 commented Apr 18, 2024

@Flrande
Yeah, unfortunately that might be the case.

I think it would definitely be worth sharing your experience in that Discussion thread and / or joining their Discord and reaching out in their #ssr channel.

@vera-js
Copy link

vera-js commented Oct 17, 2024

Hi @Firande, Just came across this thread and wondering if you figured anything out here? I'm on the same journey where I'm just using lit-html (not LitElement) and am trying to figure out an SSR solution that works for both light-dom and shadow-dom web components while allowing for property-expressions. Thank you in advance!

@thescientist13 thescientist13 added 0.15.0 documentation Improvements or additions to documentation labels Oct 19, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
0.15.0 documentation Improvements or additions to documentation enhancement Improvement to existing functionality
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants