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

feat: Produce Declarative Shadow DOM with Hydrate #4010

Closed
3 tasks done
mayerraphael opened this issue Jan 29, 2023 · 26 comments · Fixed by ionic-team/ionic-framework#29666
Closed
3 tasks done

feat: Produce Declarative Shadow DOM with Hydrate #4010

mayerraphael opened this issue Jan 29, 2023 · 26 comments · Fixed by ionic-team/ionic-framework#29666
Labels
Feature: Want this? Upvote it! This PR or Issue may be a great consideration for a future idea. Resolution: Refine This PR is marked for Jira refinement. We're not working on it - we're talking it through.

Comments

@mayerraphael
Copy link

mayerraphael commented Jan 29, 2023

Prerequisites

Describe the Feature Request

Add a feature to the hydrate package so the renderToString function produces static html using Declarative Shadow DOMs' tag instead of the current custom solution.

Terminology used:
Declarative Shadow DOM = DSD.

Resources:
https://caniuse.com/declarative-shadow-dom
https:/mfreed7/declarative-shadow-dom

Describe the Use Case

The current hydrate pacakge is good, thanks for that as we all know how hard WebComponents and SSR are, but with the implementation of DSD in Webkit and easy Polyfils, the hydrate script should have an option which produces Declarative Shadow DOM.

Please keep in mind that my internal knowledge of Stencil is limited, I take some assumptions with the benefits this feature could have.

What are the benefits:

  • The browser automatically creates a shadow-root fragment from <template shadowroot="open">. For Firefox, a simple polyfill can be used.
  • This also means that the WebComponents can be 100% compatible with React/SSR, as React only sees the same tree as the one specified in the template. No hydration warnings anymore.
  • No extra css-scoping and de-scoping required. Just place the css as is inside a <style> tag inside the <template>.
  • renderToString may not require the full html anymore, as no mutations to <head> are needed. It would only need the code of the component(s).
  • Hydration does need not a full re-rendering. The contents of the DSD are still present in the #shadow-root fragment.
  • Currently this means that Stencil must hydrate components before any other framework does, otherwise it would cause for example React Hydration Errors as the DOM does not match.

Describe Preferred Solution

  • "Stencil Hydrate" should allow to render components using Declarative Shadow DOM
  • "Stencil Hydrate"s' hydration logic should work with existing ShadowRoot and Nodes upon initialization to avoid unecessary re-rendering.
  • Styles should be usable without the current custom scoping solution.

Additional Information

I dont have any information on how Firefox plans to support DSD, but for cross-framework-compatible SSR i see no way around DSD.

Edit: Firefox DSD implementation is finished: https://bugzilla.mozilla.org/show_bug.cgi?id=1712140

@ionitron-bot ionitron-bot bot added the triage label Jan 29, 2023
@mayerraphael mayerraphael changed the title feat: Hydrate should produce Declarative Shadow DOM feat: Produce Declarative Shadow DOM with Hydrate Jan 29, 2023
@rwaskiewicz rwaskiewicz self-assigned this Jan 30, 2023
@rwaskiewicz
Copy link
Member

Thanks for the detailed request @mayerraphael! Feature requests like these are very much appreciated. This is something we've been keeping an eye on, and don't have an existing issue/feature request for. I'm going to label this to try to gauge interest in something like this, which helps from a prioritization standpoint. Thanks again!

@rwaskiewicz rwaskiewicz added the Feature: Want this? Upvote it! This PR or Issue may be a great consideration for a future idea. label Jan 30, 2023
@ionitron-bot ionitron-bot bot removed the triage label Jan 30, 2023
@rwaskiewicz rwaskiewicz removed their assignment Jan 30, 2023
@mayerraphael
Copy link
Author

mayerraphael commented Mar 29, 2023

@rwaskiewicz Any update on when or if this is planned? With currently 16 upvotes this seems to spark some interest.
I guess this would also simplify the current custom hydration logic massivly (maybe some problems that needed to be fixed with PR 2938)

@rwaskiewicz
Copy link
Member

@mayerraphael It's moved up on our backlog to do some spike work around this, but I don't have a concrete timeline to share at this time

@mayerraphael
Copy link
Author

@rwaskiewicz

Just wanted to say that Porsche is also using Stencil, but they wrote their own SSR solution using Declarative ShadowDOM:

https:/porsche-design-system/porsche-design-system/blob/aa8177ff61c916c8c6872844650fd2b0bfaf1d23/packages/components/scripts/generateDSRComponents.ts#L121

@benelan
Copy link

benelan commented Sep 23, 2023

Not sure if this is possible, but it would be awesome if generating declarative shadow DOMs happened via a build flag rather than as an output of renderToString.

Using renderToString with SSR frameworks requires swapping in a custom server in a lot of cases, which removes some of the frameworks' benefits. For example, Next.js says:

Before deciding to use a custom server, please keep in mind that it should only be used when the integrated router of Next.js can't meet your app requirements. A custom server will remove important performance optimizations, like serverless functions and Automatic Static Optimization.

A custom server cannot be deployed on Vercel.

Compiling directly to web components with declarative shadow DOMs should theoretically remove the need for middleware like renderToString.

@mayerraphael
Copy link
Author

mayerraphael commented Sep 23, 2023

@benelan This would be the job of the React (Wrapper) component then.

I've created a repo which shows how DSD works with a handwritten WebComponent and NextJS: https:/mayerraphael/nextjs-webcomponent-hydration

It works WITHOUT a custom server.js (updated just now with a working StencilJS component).

The DSD cannot be part of the WebComponent because if you use the custom element directly in NextJS, like <my-component />, it does not call a render function on that component, but renders the tag as is.

Now a React-based wrapper components will be called by NextJS and can therefor inject the DSD as seen in my example.
Porsche does the same: their custom SSR code generates React components from the Stencil source files.

I will extend my example soon using StencilJS and try to create a generic wrapper around Stencil components so they can be used with SSR/DSD.

@benelan
Copy link

benelan commented Sep 25, 2023

Thanks for the info and sample @mayerraphael! I haven't worked with DSDs yet, but my understanding is the templates will be rendered on the server, and then the custom elements will hydrate on the client. This Chrome article talks about that a bit.

The DSD cannot be part of the WebComponent because if you use the custom element directly in NextJS, like <my-component />, it does not call a render function on that component, but renders the tag as is.

Do you mean it literally renders the component tag as text or only renders the template?

If it's the latter, that may be a React-specific issue with their VDOM implementation not re-rendering once the custom element hydrates. Stencil provides a React wrapper output target that should be able to help wire up the DSDs and force a rerender after hydration, if that's the issue.

I just used NextJS as an example but there are plenty of other SSR/SSG frameworks like Astro/SveleKit/NuxtJS that won't run into React's web component weirdness.

Note: I was able to prerender Stencil components in NextJS using getStaticProps instead of a custom server at one point, but it was pretty fragile. Here is the sample if anyone is curious.

@mayerraphael
Copy link
Author

mayerraphael commented Sep 26, 2023

Thanks for the info and sample @mayerraphael! I haven't worked with DSDs yet, but my understanding is the templates will be rendered on the server, and then the custom elements will hydrate on the client. This Chrome article talks about that a bit.

Exactly, the goal is to render the <template shadowrootmode> Tag, which the Browser converts on parsing the HTML to a ShadowRoot Fragment. This means we have a working Shadow Root even before the component hydrates on the client.

The DSD cannot be part of the WebComponent because if you use the custom element directly in NextJS, like <my-component />, it does not call a render function on that component, but renders the tag as is.

Do you mean it literally renders the component tag as text or only renders the template?

I mean the first. If you have a basic HtmlElement WebComponent, you specifiy it in your code by tag, like <my-component>. There is no import or class related for NextJS to instantiate that class and call render() on. So it cannot now the "internals" of the component. It only places the tag as is in the HTML. It is like you write a <div> tag.

You can try it yourself. Just normally place your custom element by tag name () inside a NextJS page and disable JavaScript in the Browser or check the generated html. The components content will not be visible (only the children, as they can be rendered by NextJS depending if they are webcomponents too or not) until the component hydrates.

If it's the latter, that may be a React-specific issue with their VDOM implementation not re-rendering once the custom element hydrates. Stencil provides a React wrapper output target that should be able to help wire up the DSDs and force a rerender after hydration, if that's the issue.

We are not on the client yet, that is another topic with problems regarding Reconciliation React Issue. This can be worked around by providing different VDOM on client and on server, as seen here. This is basically a custom React Wrapper (like the React Stencil Output Target creates, but more incomplete) which handles the DSD on the server and no-dsd on the client so React does not create hydration errors because of VDOM mismatches.

You are right that most likely all the SSR/DSD wiring has to happen in the React/Vue/... Output Targets .

I just used NextJS as an example but there are plenty of other SSR/SSG frameworks like Astro/SveleKit/NuxtJS that won't run into React's web component weirdness.

They also do, at least with SSR/DSD. The problem is always the same. With a native WebComponent, you need to integrated it into the Framework so it can render the contents of the WebComponent on the server, as WebComponents are a client side only construct by nature (with the CustomElementRegistry and so on) and may use other libraries (as Stencil does with copying/adjusting Preact) to render. So you need to "tell" the framework on how to run your component and what to do with the "render()" result.

Stencil uses a custom VNode format here, derived from Preact. Because of that we need to convert the result of the render() call to another VNode format, whatever you use for rendering.

This is what i'm doing here. Instantiate the Component class (pass an empty hostref WeakMap), apply props and call the render method. Then i convert the StencilJS VNodes to Preact VNodes, so i can render them on the server. Would also be possible to convert them directly to React VNodes, but i found using preact-render-to-string easier.

Note: I was able to prerender Stencil components in NextJS using getStaticProps instead of a custom server at one point, but it was pretty fragile. Here is the sample if anyone is curious.

I see you use the hydrate renderToString. This is what we want to get away from. The current hydrate renderToString is bad for all the reasons listed in the initial issue comment.

@benelan
Copy link

benelan commented Sep 28, 2023

That all makes sense, thanks for taking the time to write up the response! I'm looking forward to playing with DSDs more.

I think my initial suggestion of generating web components with DSDs during Stencil's component compilation is still valid though. It sounds like we are in agreement that Stencil's framework wrapper output targets should hold the responsibility of wiring up the SSR/DSDs. So renderToString shouldn't be required at all if the web components already have DSDs.

This is all theoretical, I have no idea if it is possible with Stencil's component architecture or compilation process. And if it is possible, I'm sure it would be a much larger undertaking than switching renderToString to generate DSDs.

However, there would be many benefits to removing the need for the component library consumers to use renderToString. It would reduce server render time, which can be pretty significant for very large applications. It would also reduce/eliminate the extra steps required for users to get the component library set up with SSR. The steps for using renderToString will differ depending on the SSR framework, and may not even be possible/stable in some frameworks.

@alicewriteswrongs
Copy link
Contributor

@benelan I'm curious what you're proposing with generating components with DSD during Stencil compilation. My understanding is that DSD is an "in-html" construct, so at a minimum Stencil would have to start distributing some sort of artifact that's a bit different than what we distribute now for the hydrate app output target (or any of the others for that matter).

There would be some advantages to having Stencil produce some sort of DSD 'intermediate representation' itself (whatever that might look like), namely that then the framework wrappers would be able to all consume a unified format instead of having to DIY it, and additionally Stencil developers who don't use a framework would also likely be able to find a way to take advantage of DSD in their applications.

Where it gets a bit interesting is what that DSD 'IR' would look like, as DSD is an in-HTML construct. Since the rendered output of a Stencil component is generally going to be dependent on the props it receives, and prop values wouldn't be known at compile-time, we wouldn't be able to just produce a little html fragment. So maybe a per-component function called something like renderDeclarativeShadowDOM which takes the component's props as an argument and returns an HTML string like

<my-component>
  <template shadowrootmode="open">
    <style>{{ component styles }}</style>
    {{ rendered component html }}
  </template>
</my-component>

I would think that a function like that could be called in the framework wrappers or directly for both SSR and SSG use-cases to cover both dynamic components which need per-page-view props and static components which only need to be rendered once per page, but the details of how exactly that would happen would be a bit of a downstream concern from Stencil itself.

That feels like something that would give a lot of flexibility for generating a DSD for a given Stencil component, while also not bringing the responsibility for all of this into Stencil itself.

Is that along the lines of what you're thinking @benelan or something else?

@benelan
Copy link

benelan commented Oct 5, 2023

@alicewriteswrongs I hadn't considered prop values when I was thinking about this, but your potential solution sounds great.

My main goal is for our component library consumers to be able to use the framework wrappers in SSR and SSG apps without needing to use renderToString on their end. There would likely need to be some kind of browser/server check added to the framework wrappers so they still work in SPA apps. Although there might be a way to generate and render DSDs that works on both the client and server for some frameworks, but I'm not sure.

It sounds like the solution you're envisioning would allow for that, which would be awesome! Being able to manually wire up the generated DSDs in frameworks without wrapper components (like Astro) is a big plus too.

@benelan
Copy link

benelan commented Oct 5, 2023

In case you haven't seen yet, Lit has an experimental SSR solution that takes advantage of DSDs. It could be helpful to see how they did it while you plan Stencil's implementation.

@alicewriteswrongs
Copy link
Contributor

Definitely something to keep considering and noodling on here! I suspect you're right that for some frameworks it would probably be relatively doable to generate one component that will work for both a server-side and client-side case if Stencil exported a first-class way to generate a DSD (i.e. a <template> tag) for the elements but I will also add a big caveat that at present we haven't yet tried to do any of this so when any implementation actually starts things could always end up being a good deal more complicated than we think.

When I think in my head about how this could work in react + nextjs, for instance, I can kind of visualize a path through where Stencil exports some way to generate a DSD from props, a React component wrapper either calls that function or static JSX is generated based on that (or something along those lines) and then Stencil's component runtime is modified to support gracefully taking over components initialized with DSD, allowing a React component wrapping a Stencil component to be server-rendered or client-rendered, with the runtime gracefully "doing the right thing" in either case, but that's actually a lot of pieces to get working together so a lot of opportunities for things to be more difficult than would be ideal 😅

As a sort of meta-comment on this issue, the Stencil team is looking to do more exploration and research about this soon and while I can't promise a definite timeline it is definitely something we're looking at

@denyo
Copy link

denyo commented Oct 31, 2023

@rwaskiewicz

Just wanted to say that Porsche is also using Stencil, but they wrote their own SSR solution using Declarative ShadowDOM:

https:/porsche-design-system/porsche-design-system/blob/aa8177ff61c916c8c6872844650fd2b0bfaf1d23/packages/components/scripts/generateDSRComponents.ts#L121

Maybe to give some more details regarding our solution:

  • we do framework wrapper generation ourselves for Angular, React and Vue
  • the SSR support for Next.js and Remix is an extension of the React wrappers, so it currently only works with React SSR
  • it's quite messy, especially the countless replacements and transformations done in generateDSRComponents.ts that transform the raw render functions of the Stencil components into something usable by React
  • the switch between what is rendered on server side / client side is based on process.browser which needs to be injected/replaced with true | false during build: https:/porsche-design-system/porsche-design-system/blob/41a6f853db436ca8f0d286be584b96ca0ec2b4d0/packages/components/scripts/wrapper-generator/NextJsReactWrapperGenerator.ts#L69-L79
  • then we rely on dead code elimination to not ship the DSR wrappers to the client
  • React SSR wrappers are shipped via sub package ssr of https://www.npmjs.com/package/@porsche-design-system/components-react
  • we also had to patch stencil core to make bootstrapping (you may also call it hydration) work since it does not have to create a shadow root, when there is already a DSR
  • upon client side initialization the web component just rerenders everything within which leads to flickering when there are nested web components since it does not do real hydration which would just add event listeners and not rerender

@igorlino
Copy link

igorlino commented Nov 24, 2023

FYI support for Declarative Shadow DOM (DSD) has been very recently merged and is already part of the HTML spec under whatwg/html#9538

which was mentioned in this article: https://developer.chrome.com/en/articles/declarative-shadow-dom/

It would be a dream for a clean out-of-the-box way to integrate Next.js with Stencil web components without workarounds or performance penalties.

No qualified nor out-of-the-box anwers under:

@rwaskiewicz rwaskiewicz added the Resolution: Refine This PR is marked for Jira refinement. We're not working on it - we're talking it through. label Dec 11, 2023
@christian-bromann
Copy link
Member

We have implemented support for this in #5792. Please give it a try by installing our dev build for this:

npm install @stencil/[email protected]

And provide feedback. Thanks!

@mayerraphael
Copy link
Author

mayerraphael commented Jun 18, 2024

Thanks for your work Christian.

Unfortunately all elements next to the style tag (which are part of the original render method) in the shadow root are duplicated on hydration.

image

We render the following html:

<my-main-navigation-item data-ssr="true">
  <template shadowrootmode="open">
    <style>
      /* Removed for readability. */
    </style>
    <div class="lg:mr-[16px] lg:mt-[1px] lg:mb-[7px] lg:border-0">    <!-- this div is duplicated on hydration -->
      <div class="main-navigation-item block relative font-text-bold ...">
        <slot></slot>
      </div>
    </div>
  </template>
  
  <a href="#mainmenu3" target="_self">Main Menu 3</a>
</my-main-navigation-item>

The style tag is injected by us in our React component on the server. Same like the new renderToString with serializeShadowRoot should do.

This happens with every component we render on the server.

Its like those slot projection bugs that existed (and still exist to some extend).

@christian-bromann
Copy link
Member

Thanks @mayerraphael for taking a look at testing it out, I've received this feedback from my peers as well and will investigate.

@christian-bromann
Copy link
Member

@mayerraphael can you provide a minimal reproducible example by any chance?

@mayerraphael
Copy link
Author

@christian-bromann I created an isolated simple example here: https:/mayerraphael/stencil-dsd-ssr-playground

I faked the SSR part by sending the html with the declarative shadow dom in a fixed way.

Upon hydration, the <div> is rendered twice again.

image

The component:

import { Component, h } from '@stencil/core';

@Component({
  tag: 'my-component',
  styleUrl: 'my-component.css',
  shadow: true,
})
export class MyComponent {

  render() {
    return <div><slot></slot></div>;
  }
}

How the component is send out using a declarative shadow dom (see server.js).

<my-component>
    <template shadowrootmode="open">
        <style>
            :host {
                display: block;
            }
        </style>
        <div><slot></slot></div>
    </template>
    <span>Hello SSR.</span>
</my-component>
<script type="module">
    import {defineCustomElements}  from "./static/loader/index.js";
    defineCustomElements().catch(console.error);
</script>

I hope that helps.

@christian-bromann
Copy link
Member

I faked the SSR part

This is not supported as Stencil relies on HTML comments created during the serialization process. Without them it won't be able to reconcile the elements properly. Applying the following patch made the example work:

diff --git a/server.js b/server.js
index c75334c..606385f 100644
--- a/server.js
+++ b/server.js
@@ -2,29 +2,17 @@ import express from 'express';
 const app = express();
 const port = 3333;

+import { renderToString } from './hydrate/index.mjs';
+
 app.use("/static", express.static("."));


-app.get('/', (req, resp) => {
-  resp.send(`
-        <my-component>
-           <template shadowrootmode="open">
-                <style>
-                    :host {
-                        display: block;
-                    }
-                </style>
-                <div>
-                    <slot></slot>
-                </div>
-           </template>
-           <span>Hello SSR.</span>
-        </my-component>
-        <script type="module">
-            import {defineCustomElements}  from "./static/loader/index.js";
-            defineCustomElements().catch(console.error);
-        </script>
-    `);
+app.get('/', async (req, resp) => {
+  const { html } = await renderToString(`<my-component>Hello SSR.</my-component>`, {
+    serializeShadowRoot: true,
+    fullDocument: false
+  });
+  resp.send(html);
 });


diff --git a/stencil.config.ts b/stencil.config.ts
index c58df8b..0e98e8e 100644
--- a/stencil.config.ts
+++ b/stencil.config.ts
@@ -19,6 +19,9 @@ export const config: Config = {
       type: 'www',
       serviceWorker: null, // disable service workers
     },
+    {
+      type: 'dist-hydrate-script',
+    }
   ],
   testing: {
     browserHeadless: "new",

@mayerraphael
Copy link
Author

mayerraphael commented Jun 21, 2024

@christian-bromann Thanks for your reply.

I would've not expected that those things would still be required with the new solution. The whole reason for Declarative Shadow DOM is to be able to let the HTML/CSS inside the component as-is so no reconstruction is required, just "real hydration" (attaching handlers).

I think Stencil should be able to hydrate without any weird CSS scoping (which was part of the feature request) and HTML comments (and mutation like c-id) like most frameworks out there.

Edit:
I have a really hard time to imagine how to integrate renderToString(" ... ") into a real world application without having most of the drawbacks the old solution already had.

The current solution would require, again, to first serialize the whole React/NextJS page and then pass everything to renderToString.

This does not allow:

  • To exclude some components from SSR.
  • Requires a custom server, which brings problems on their own (like Custom server renderToHTML does not work with next 13.4 vercel/next.js#54977).
    • This prevents smooth integration with NextJS/React or any other SSR framework (like Nuxt) and their streaming/partial rendering.
  • Has to happen at runtime. This is in my opinion the worst part. s-id and c-id are fine, if they would work at compile (beeing the same for a component, not increment for each usage of a component).

Really curious how #5831 will be solved without those drawbacks. Especially how the increment s-id/c-id stuff will work if individual components are rendered.

But really, thanks! This is a step in the right direction, even though still hard to apply in a real-world use case.

@christian-bromann
Copy link
Member

@mayerraphael thanks a lot for your feedback!

The whole reason for Declarative Shadow DOM is to be able to let the HTML/CSS inside the component as-is so no reconstruction is required, just "real hydration" (attaching handlers).

This makes sense. I would love to track this in a separate issue as I believe it requires a new approach on how we hydrate components in general. We already had conversations within them team of getting rid of the HTML comments but this may be a breaking change that we have to carefully evaluate. Hence I suggest to track this as a next step.

The current solution would require, again, to first serialize the whole React/NextJS page and then pass everything to renderToString.

How so? You can serialize/hydrate individual components by setting fullDocument: false as an option. In fact, my next step is to enhance the StencIl React Output target to support Next.js application using this approach.

Keep an 👁️ on #5831 , I would love to get your feedback on this. I hope to have something to test for you within the next 1/2 weeks.

@mayerraphael
Copy link
Author

How so? You can serialize/hydrate individual components by setting fullDocument: false as an option. In fact, my next step is to enhance the StencIl React Output target to support Next.js application using this approach.

Because the current solution can only work with strings? Or maybe i am missing something crucial here.

Lets take a normal page in NextJS:

import { MyStencilComponent }  from "stencil-react-output-target".

export default function MyPage() {
  return (
    <div>
      <MyStencilComponent prop="hello">
         <span>World</span> 
      <MyStencilComponent>
    </div>
  )
}

How would serialization work in this case? The only way i currently see is a custom server which serializes the NextJS page (with next/apps' renderToHtml) and then running that result through hydrates renderToString(). With the new Shadow DOM serialization we at least dont have to run Stencil first on the client because the browser already creates the Shadow Root and React is not complaining about not being isomorphic with what was declared on the server.

Your solution, extending the React output target: Does this mean inside the wrapper the contents are serialized to string and some kind of dangerouslySetInnerHTML element is rendered? Or do you plan on returning React compatible VNodes (with the HTML attributes/comments added) so the serialization can be done by native React/NextJS?

The ideal case would be if Stencils React ouput target would provide React compatible JSX/VNodes (or calling a method which serializes not to string, but to VDOM) so nothing has to be done by Stencil on the server (maybe the VNodes have the required HTML attributes/comments included). But as s-id is incremental and unique for each rendering and the c-ids are dependent on s-id ($host$ part) and conditional parts inside the component, this can never be done at compile time but only at runtime? So each rendering would require quite the amount of preprocessing on Stencils part. At least according to my limited understanding.

This makes sense. I would love to track this in a separate issue as I believe it requires a new approach on how we hydrate components in general. We already had conversations within them team of getting rid of the HTML comments but this may be a breaking change that we have to carefully evaluate. Hence I suggest to track this as a next step.

I understand. One step after the other 👍🏼

@christian-bromann
Copy link
Member

How would serialization work in this case?

We are working on an enhancement on the React output target which would provide better support for Next.js. The approach is to wrap the Stencil component to use the renderToString method on server side and the react output target on client side. We have a POC that verified this approach to be working fairly well. This would indeed mean we will have logic that transforms the Stencil component and its props into a string internally.

Your solution, extending the React output target: Does this mean inside the wrapper the contents are serialized to string and some kind of dangerouslySetInnerHTML element is rendered?

Correct, it may look something like this:

import { renderToString } from 'component-library/hydrate';

export const MyButton = typeof globalThis.window !== 'undefined'
    /**
     * export React output target
     */
    ? createComponent<MyButtonElement, MyButtonEvents>({
        tagName: 'my-button',
        elementClass: MyButtonElement,
        react: React,
        events: {
            onMyFocus: 'myFocus',
            onMyBlur: 'myBlur'
        } as MyButtonEvents,
        defineCustomElement: defineMyButton
    })
    :
    /**
     * export serialized version of component for server side rendering
     */
    async (props: React.PropsWithChildren<{}>) => {
        const { html } = await renderToString(
            `<my-button ${reactPropsToStencilAttrs(props)}></my-button>`,
            {
                serializeShadowRoot: true,
                fullDocument: false
            }
        );
        return (
            <my-button suppressHydrationErrors>
                <template shadowrootmode="open" dangerouslySetInnerHTML={{
                    __html: html
                }} />
				{ props.children }
            </my-button>
        )
    }

But as s-id is incremental and unique for each rendering and the c-ids are dependent on s-id (
part) and conditional parts inside the component, this can never be done at compile time but only at runtime?

Yes, these are required at runtime for Stencil to reconcile the VDOM correctly. They are indeed random which is why the suppressHydrationErrors flag has to be set. As mentioned before we are looking for better solutions, e.g. not being dependent on any custom attributes or html comments for reconciliation but we will approach this one step at a time.

Thanks for all your great feedback so far.

@tanner-reits
Copy link
Member

The initial work for this was included in today's v4.19.0 release! Please let us know of any bugs or complications in a new issue.

github-merge-queue bot pushed a commit to ionic-team/ionic-framework that referenced this issue Jun 26, 2024
### Release Notes

<details>
<summary>ionic-team/stencil (@&#8203;stencil/core)</summary>

###
[`v4.19.0`](https://togithub.com/ionic-team/stencil/blob/HEAD/CHANGELOG.md#-4190-2024-06-26)

[Compare
Source](https://togithub.com/ionic-team/stencil/compare/v4.18.3...v4.19.0)

### Bug Fixes

* **compiler:** support rollup's external input option
([#3227](ionic-team/stencil#3227))
([2c68849](ionic-team/stencil@2c68849)),
fixes [#3226](ionic-team/stencil#3226)
* **emit:** don't emit test files
([#5789](ionic-team/stencil#5789))
([50892f1](ionic-team/stencil@50892f1)),
fixes [#5788](ionic-team/stencil#5788)
* **hyrdate:** support vdom annotation in nested dsd structures
([#5856](ionic-team/stencil#5856))
([61bb5e3](ionic-team/stencil@61bb5e3))
* label attribute not toggling input
([#3474](ionic-team/stencil#3474))
([13db920](ionic-team/stencil@13db920)),
fixes [#3473](ionic-team/stencil#3473)
* **mock-doc:** expose ShadowRoot and DocumentFragment globals
([#5827](ionic-team/stencil#5827))
([98bbd7c](ionic-team/stencil@98bbd7c)),
fixes [#3260](ionic-team/stencil#3260)
* **runtime:** allow watchers to fire w/ no Stencil members
([#5855](ionic-team/stencil#5855))
([850ad4f](ionic-team/stencil@850ad4f)),
fixes [#5854](ionic-team/stencil#5854)
* **runtime:** catch errors in async lifecycle methods
([#5826](ionic-team/stencil#5826))
([87e5b33](ionic-team/stencil@87e5b33)),
fixes [#5824](ionic-team/stencil#5824)
* **runtime:** don't register listener before connected to DOM
([#5844](ionic-team/stencil#5844))
([9d7021f](ionic-team/stencil@9d7021f)),
fixes [#4067](ionic-team/stencil#4067)
* **runtime:** properly assign style declarations
([#5838](ionic-team/stencil#5838))
([5c10ebf](ionic-team/stencil@5c10ebf))
* **testing:** allow to re-use pages across it blocks
([#5830](ionic-team/stencil#5830))
([561eab4](ionic-team/stencil@561eab4)),
fixes [#3720](ionic-team/stencil#3720)
* **typescript:** remove unsupported label property
([#5840](ionic-team/stencil#5840))
([d26ea2b](ionic-team/stencil@d26ea2b)),
fixes [#3473](ionic-team/stencil#3473)


### Features

* **cli:** support generation of sass and less files
([#5857](ionic-team/stencil#5857))
([1883812](ionic-team/stencil@1883812)),
closes [#2155](ionic-team/stencil#2155)
* **compiler:** generate export maps on build
([#5809](ionic-team/stencil#5809))
([b6d2404](ionic-team/stencil@b6d2404))
* **complier:** support type import aliasing
([#5836](ionic-team/stencil#5836))
([7ffb25d](ionic-team/stencil@7ffb25d)),
closes [#2335](ionic-team/stencil#2335)
* **runtime:** support declarative shadow DOM
([#5792](ionic-team/stencil#5792))
([c837063](ionic-team/stencil@c837063)),
closes [#4010](ionic-team/stencil#4010)
* **testing:** add `toHaveLastReceivedEventDetail` event spy matcher
([#5829](ionic-team/stencil#5829))
([63491de](ionic-team/stencil@63491de)),
closes [#2488](ionic-team/stencil#2488)
* **testing:** allow to disable network error logging via
'logFailingNetworkRequests' option
([#5839](ionic-team/stencil#5839))
([dac3e33](ionic-team/stencil@dac3e33)),
closes [#2572](ionic-team/stencil#2572)
* **testing:** expose captureBeyondViewport in pageCompareScreenshot
([#5828](ionic-team/stencil#5828))
([cf6a450](ionic-team/stencil@cf6a450)),
closes [#3188](ionic-team/stencil#3188)

</details>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Feature: Want this? Upvote it! This PR or Issue may be a great consideration for a future idea. Resolution: Refine This PR is marked for Jira refinement. We're not working on it - we're talking it through.
Projects
None yet
Development

Successfully merging a pull request may close this issue.

8 participants