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

[SSR] DOM Shim Specification and / or Polyfill #55

Open
thescientist13 opened this issue Feb 28, 2024 · 12 comments
Open

[SSR] DOM Shim Specification and / or Polyfill #55

thescientist13 opened this issue Feb 28, 2024 · 12 comments

Comments

@thescientist13
Copy link

Overview

Breaking the SSR part off from #35 since there were a couple thoughts going on there and thought it might be better to keep them separate

As "Web Components" itself is an umbrella label for a subset of web standards and APIs native to browsers, it is an exercise left up to developers who want to server-render web components to shim the DOM themselves on the server, typically in a JavaScript (e.g. NodeJS) runtime. It would be nice as a community if we could define what a common set of reasonable Web Components and Web Components adjacent APIs for the server-side would like.

This could just be a documented spec / reference, or even a package that can be distributed out on npm for libraries and frameworks and to leverage.

Specification

I think first would be establishing what we would consider a reasonable set of shims to be for a server environment.

At the most basic, that would seem to include:

  • window / document
  • customElements.[define|get]
  • HTMLElement
  • addEventListener (no-op?)
  • HTMLTemplateElement
  • attachShadow
  • .[get|set|has]Attribute
  • <template> / DocumentFragment

Shared Polyfill

Much like the @webcomponentsjs family of polyfills, it would be nice for this CG to maintain / contribute this as a library that could be published to npm.

It would be nice if this is something that could be extended from so if libraries and frameworks want to add additional support on top of the baseline, they can offer that.

Prior Art

(please comment below and share others!)


(who knows, maybe we even "upstream" this into the WinterCG spec! 🤩 )

@Westbrook
Copy link
Collaborator

Westbrook commented Mar 3, 2024

May be something useful to learn from https://github.com/enhance-dev/enhance-ssr-wasm. It's a little disappointing that the default tooling there seems to make up its own interpretation of DOM, custom elements, and slots, but hopefully that's an implementation detail and not a fixed requirement as the breadth of impact a tool like that could have is quite exciting.

@Westbrook
Copy link
Collaborator

Related, I don't think their DOM implementation is complete yet, but I wonder if a more modular browser architecture, like https://servo.org/, might position the ability to use a full DOM implementation in more interesting locations...

@brianleroux
Copy link

missing prior art: https://github.com/enhance-dev/enhance-ssr

@brianleroux
Copy link

brianleroux commented Mar 11, 2024

May be something useful to learn from https://github.com/enhance-dev/enhance-ssr-wasm. It's a little disappointing that the default tooling there seems to make up its own interpretation of DOM, custom elements, and slots, but hopefully that's an implementation detail and not a fixed requirement as the breadth of impact a tool like that could have is quite exciting.

sorry how does this have a 'made up interpretation of DOM???' if so we'd consider that a bug. otherwise I'd consider that statement FUD. fwiw Enhance is based on Parse5 which tmk is a spec compliant dom parser.

@nolanlawson
Copy link
Contributor

We have an SSR implementation in @lwc/engine-server as well.

Interestingly, our current list of shims differs from yours a bit; we don't support window or document or <template>, but we do support classList.

@trusktr
Copy link

trusktr commented Mar 12, 2024

sorry how does this have a 'made up interpretation of DOM???' if so we'd consider that a bug. otherwise I'd consider that statement FUD. fwiw Enhance is based on Parse5 which tmk is a spec compliant dom parser.

@brianleroux I think what was meant by this, if I'm understanding correctly, and of I didn't miss anything, is that looking here:

https://github.com/enhance-dev/enhance-ssr/blob/main/index.mjs

there does not appear to be any DOM APIs shimmed. So basically the library works specifically for Enhance elements, but not generically for elements made with any Custom Element framework.

The idea is that a basic set of shimmed DOM APIs (not something much fuller like jsdom or undom) would end up being useful for a wide range of elements from different libs.

Enhance SSR has the mechanics, but maybe it just needs a layer of DOM shim to achieve the goals in this thread.

@brianleroux
Copy link

Indeed there are some shimmed apis running noop functions in this implementation. You can still run full WC definitions per https://enhance.dev/docs/conventions/components

At any rate, the majority of elements frankly don't require much more. Backend code isn't going to get click events etc.

@Westbrook
Copy link
Collaborator

For clarity, when I say that Enhance SSR "seems to make up its own interpretation of DOM, custom elements, and slots" it is because of the way it chooses, to convert this code to this code when deploying the page:

<my-header enhanced="✨">
  <header>
      <h1>Header</h1>
      <nav>
          <a href="/">home</a>
          <a href="/about">about</a>
      </nav>
  </header>
</my-header>

Therein you see a holely non-DOM interpretation of how to manage the use of a <slot> element. It's neither wrong nor right to deliver content in this way, but it is contrary to the functionality of a <slot> in the DOM spec.

Full disclosure, I do not know if this is a requirement of the underlying SSR library or of the Enhance framework, as the documentation around this feature is still under development. I'd be very happy to hear that this sort of conversion was specific to the way Enhance the meta-framework leverages Enhance SSR and not of the SSR library itself. However, a DOM Shim, while certainly deserving of no-ops and workarounds for performance and delivery environment, should likely not change the DOM spec if it intends to be interoperable between various library and meta-framework approaches.

@brianleroux
Copy link

@Westbrook this is really great, appreciate you trying Enhance! I think you are misunderstanding how things are running. Code you linked in the elements folder runs server-side (which is perfect for your use case of a header). We implement the slotted algorithm to spec (but if you found a bug there please let us know). We fully expect and want code that runs server-side to come down to the client fully expanded. Thats the point! I ack this is probably the opposite of how the mental model for WC works.

If you want to have preserved slots on the client-side too check out the documentation for Components which also can expand server-side and then pick up where you left off with a template client-side for additional DOM updates).

I hope that clears things up but if not please let me know. We really are hoping to get more people on board with Web Components and runtime SSR that expands elements is a crucial capability for making that happen. Appreciate your help in clarifications to make this stuff better for the next person.

@macdonst
Copy link

Here is an example of an Enhance component that is both SSR and CSR capable.

import CustomElement from '@enhance/custom-element'

export default class MyCard extends CustomElement {
  connectedCallback() {
    this.heading = this.querySelector('h5')
  }

  render({ html, state }) {
    const { attrs={} } = state
    const { title='default' } = attrs
    return html`
      <style>
        // omitted for brevity. See: https://enhance.dev/docs/conventions/components#%40enhance%2Fcustom-element
      </style>
      <slot name="image"></slot>
      <div class="card-body font-sans">
        <h5 class="card-title">${title}</h5>
        <slot></slot>
      </div>
    `
  }

  static get observedAttributes() {
    return [ 'title' ]
  }

  titleChanged(value) {
    this.heading.textContent = value
  }
}

customElements.define('my-card', MyCard)

CustomElement is part of an inheritance chain that starts with HTMLElement.

On the server, we need to parse the entire file, but we only ever call the render method on the server. This helps us keep our shim small. You can see in this file that there are only 8 LOC.

So far we've only required:

  • HTMLElement
  • customElements.[define|get]
  • Worker

@jaredcwhite
Copy link

Really cool to see how the custom element APIs could be leveraged to author components in both SSR and CSR in Enhance.

I echo @Westbrook's comments though about actionable prototypes/proposals mirroring in-browser DOM behavior…aka <slot></slot> is specific to the composition features between light and shadow DOM (which I personally use frequently), CSS selectors like :host are shipped as such because the browser interprets it in shadow DOM, etc. I would assume any general-purpose SSR pipeline won't attempt transpilation of HTML/CSS other than something that's specially configured and opt-in for a particular framework. (FWIW, I've run into similar related problems in both WebC and Astro, and I greatly appreciate them otherwise!)

@brianleroux
Copy link

Sorry @jaredcwhite not following what you saying here. Can you provide a concrete example ? Fairly certain we support whatever you've done normally with client render. Transpiling is absolutely something we can do but it's all optin.

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

7 participants