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

[labs/ssr] LitElement renders content inside a Declarative Shadow DOM when using createRenderRoot server side #3080

Open
1 of 7 tasks
thescientist13 opened this issue Jun 24, 2022 · 13 comments · May be fixed by #3083
Open
1 of 7 tasks

Comments

@thescientist13
Copy link
Contributor

Description

When using Lit SSR and enabling createRenderRoot for a custom element, Lit is rendering the content inside declarative shadow DOM <template> tag.

Steps to Reproduce

  1. Given the sample Greeting component that uses createRenderRoot
    import { html, css, LitElement } from 'lit';
    
    export class SimpleGreeting extends LitElement {
      static styles = css`p { color: blue }`;
    
      static properties = {
        name: {type: String},
      };
    
      constructor() {
        super();
        this.name = 'Somebody';
      }
    
      createRenderRoot(){
        return this;
      }
    
      render() {
        return html`<p>Hello, ${this.name}!</p>`;
      }
    }
    
    customElements.define('simple-greeting', SimpleGreeting);
  2. Server Render the component
    import { render } from '@lit-labs/ssr/lib/render-with-global-dom-shim.js';
    import { html } from 'lit';
    import './greeting.js';
    
    import Koa from 'koa';
    import koaNodeResolve from 'koa-node-resolve';
    import { Readable } from 'stream';
     
    const { nodeResolve } = koaNodeResolve;
    const app = new Koa();
    const port = 8080;
    
    app.use(async (ctx) => {
      ctx.type = 'text/html';
      ctx.body = Readable.from(render(html`
        <simple-greeting></simple-greeting>
        <simple-greeting name="SSR Works!"></simple-greeting>
      `));
    });
    app.use(nodeResolve({}));
    
    app.listen(port, () => {
      console.log(`Server listening on port ${port}`);
    });

Live Reproduction Link

https://stackblitz.com/github/thescientist13/lit-ssr-create-render-root

Expected Results

No Declarative Shadow DOM would be present, just like in the Lit Playground

<simple-greeting>
    <style>p { color: blue }</style>
    <!--lit-part EvGichL14uw=--><p>Hello, <!--lit-part-->Somebody<!--/lit-part-->!</p><!--/lit-part-->
</simple-greeting>

Screen Shot 2022-06-23 at 9 27 51 PM

Actual Results

Declarative Shadow DOM is still rendered

<simple-greeting>
  <template shadowroot="open">
    <style>p { color: blue }</style>
    <!--lit-part EvGichL14uw=--><p>Hello, <!--lit-part-->Somebody<!--/lit-part-->!</p><!--/lit-part-->
  </template>
</simple-greeting>

Screen Shot 2022-06-23 at 9 42 18 PM

Browsers Affected

  • Chrome
  • Firefox
  • Edge
  • Safari 11
  • Safari 10
  • IE 11
  • NodeJS
@thescientist13 thescientist13 changed the title [ssr] LitElement renders a Declarative Shadow DOM when using createRenderRoot server side [ssr] LitElement renders content inside a Declarative Shadow DOM when using createRenderRoot server side Jun 24, 2022
@justinfagnani justinfagnani changed the title [ssr] LitElement renders content inside a Declarative Shadow DOM when using createRenderRoot server side [labs/ssr] LitElement renders content inside a Declarative Shadow DOM when using createRenderRoot server side Jun 24, 2022
@justinfagnani
Copy link
Collaborator

I'm not sure we're going to be able to support customized render roots like this, so we'll have to add a check and throw an error.

@justinfagnani justinfagnani linked a pull request Jun 24, 2022 that will close this issue
@thescientist13
Copy link
Contributor Author

So it would just fail entirely? So effectively there would be no way to SSR a LitElement without having to use Declarative Shadow DOM?

@justinfagnani
Copy link
Collaborator

For now, it's likely.

The problem here is that lit-html's hydration relies on shadow roots for nested hydration - that is lit-html's hydrate() function assumes that it owns the whole DOM subtree being hydrate (very similar to render() on the first render). Without shadow DOM, each LitElement having its own render() call would be a problem because of mixed ownership of the DOM tree, but shadow DOM scoping makes it so that each render() call does have it's own subtree.

Setting the render root to this has a lot of negative side-effects, including that we've now mixed DOM ownership of the parent scope. Some DOM comes from the parent, some comes from the child. Client-side this works sometimes because lit-html won't modify nodes it doesn't create. (It only works sometimes because if the light-DOM-rendering child had children of it's own or expressions in it put there by the parent, they would be cleared.) With server rendering all the DOM would be in one tree and hydrate() would get confused when trying to associate DOM with template expressions. It wouldn't know which came from the parent or child.

We could probably add support for this in time, but until then it seems wiser to error than to incorrectly put what should have been light-DOM into the shadow root.

@thescientist13
Copy link
Contributor Author

Alrighty. Good to know and thanks for the explanation.

@ChadKillingsworth
Copy link

Some password managers (LastPass being the largest) still do not discover input elements in ShadowDOM. Since my main application requires authentication, SSR for the login form is the largest need making the requirement for ShadowDOM only a blocker for me. It's more important that password manager integration function than implementing SSR.

Is there a path forward here for this special case? If that means I have to write custom hydration logic for these specific cases that is completely acceptable. Said another way - I don't expect this to be a common requirement for most developers and the solution can be technically difficult for me to implement.

@justinfagnani
Copy link
Collaborator

justinfagnani commented Mar 8, 2023

@ChadKillingsworth talking with more SSR users recently makes me think we'll need to support this - it's just a question of when we can get to it.

Technically what we need to do is leave an indicator on the root child part that tells any outer hydration pass to just skip it completely - it doesn't even count as a part as far the outer template is concerned. This seems like good practice anyway for all render roots. It'll allow mixing of imperatively created render roots.

Then we'll need to make sure that LitElement's hydration can pick up the right child part when the render root is customized.

cc @kevinpschaaf and @augustjk

@ChadKillingsworth
Copy link

That makes sense and I agree on the priority as well. Thanks!

@traceypooh
Copy link

traceypooh commented Mar 21, 2023

This would greatly help our team out, too, as we're considering lit SSR + light DOM only for our new architecture.

Is there any way to use any of the shadyDOM JS related code to convert a render to shady DOM?
That's actually our current ideal for this new architecture R&D -- then we get (logical) CSS isolation automatically but the markup and CSS rules are all in the light DOM which is seeming to us the best of both worlds.

So if we could convert something like this:

collectResultSync(render(html`<item-tile mdapi="..."><item-stat>...</item-stat></item-tile>`)))

to shady DOM on the server and send that out to the client, we'd get the setup

  • working on all 2%+ browsers
  • no browser JS needed for initial render, no FOUC, no layout shift
  • still able to client hydrate the rest and SPA-like repaint the page on content link clicks/changes, etc. (for browsers w/ JS enabled)

(Seems worth mentioning we've got SPA and rendertron pages as well -- but are interested in moving back towards isomorphic code SSR for SEO reasons, rendertron deprecated, and more)

@ruud
Copy link

ruud commented Mar 22, 2023

Support for this will be very useful for us too 👍

@hvdmeulen
Copy link

We would also be very happy with this issue being fixed. The biggest, if not only, issue keeping us from using Lit SSR at the moment. Keep up the amazing work.

@fetishcoder
Copy link

Hi there! May I ask if there is any update on this?
We would ❤️ for this to be supported too! :)

@thescientist13
Copy link
Contributor Author

thescientist13 commented Jan 3, 2024

Just wanted to circle back on this again, now in light of support for server only templates ( #2441 ), that being able to have server only components (without DSD) would be a great companion feature!

A similar thread around doing async work on the server, by which we could leveraging the component model for the concepts of pages and layouts would bring a nice continuity to using Lit on the client, the server, and everything in between! 💯

@Flrande
Copy link

Flrande commented Apr 12, 2024

Any updates?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
Status: 📋 Triaged
Development

Successfully merging a pull request may close this issue.

8 participants