Skip to content

[labs/ssr] Implement SSR custom elements event handling#4755

Merged
sorvell merged 16 commits intolit:mainfrom
kyubisation:feat-ssr-events
Dec 21, 2024
Merged

[labs/ssr] Implement SSR custom elements event handling#4755
sorvell merged 16 commits intolit:mainfrom
kyubisation:feat-ssr-events

Conversation

@kyubisation
Copy link
Contributor

@kyubisation kyubisation commented Sep 2, 2024

This PR implements limited event handling for custom elements, by shimming EventTarget (with methods addEventListener, removeEventListener and dispatchElement) which is now extended by the HTMLElement SSR shim. It only considers and allows event handlers and event dispatching on custom element instances (e.g. event handlers in a html... template will not break, but also not work with SSR). Due to the streaming nature of SSR, it will also not allow mutating the parent from a child for rendering. This primarily enables a use case like @lit/context.

It works by building a minimal DOM tree on server side, which only considers custom element instances and slot elements, to respect the event path including Shadow DOM. The event properties are patched during the dispatchEvent call, to more accurately reflect the state of the event.
This PR also adds a global flag to run connectedCallback (and hostConnected on registered controllers) with SSR, which can be enabled with globalThis.litSsrCallConnectedCallback = true.
Additionally, a global property globalThis.litServerRoot has been implemented, which can be used as an alternative for document.documentElement or document.body to register global event handlers (e.g. for @lit/context ContextProvider).

Closes #3301
Closes #3365

@changeset-bot
Copy link

changeset-bot bot commented Sep 2, 2024

🦋 Changeset detected

Latest commit: 8dc7b3d

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 3 packages
Name Type
@lit-labs/ssr-dom-shim Minor
@lit-labs/ssr Minor
@lit-labs/ssr-react Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@kyubisation
Copy link
Contributor Author

kyubisation commented Oct 28, 2024

Over the weekend I realized that this implementation will not work, if a consumer uses their own polyfill for HTMLElement. If that is a concern (and I feel like it would be), I would need to adapt the implementation.
A possible approach would be to patch the HTMLElement/LitElement prototype for the event related methods and apply the logic currently in the shim there.

Discussed via call; Accepted for now.

@kyubisation
Copy link
Contributor Author

@justinfagnani @sorvell Thanks again for your reviews and time.

As discussed during our last call, I added tests and comments. Please have another look.

Open topics in my mind:

  1. How to provide React support? I did a simple experiment with React Context, which could work. But from my limited understanding of React, I don't think it's possible to resolve slot assignment reliably and the implementation could just default to either unnamed slot or the parent custom element.
  2. Should there be an event target root object for SSR? This would facilitate using ContextProvider with a root object both on client (e.g. document.body) and server.
  3. Nesting slots does not seem to work (in Chrome? https://stackblitz.com/edit/stackblitz-starters-nodfpm?file=script.js ) or did I misunderstand the request?
  4. Is any documentation lacking/missing? Limitations or usage guide for @lit/context in SSR?

@kyubisation
Copy link
Contributor Author

About the slot nesting; I simply forgot about white space with unnamed slots...
Also the event path always travels the full available, slotted path, regardless if it is "rendered" or not: https://stackblitz.com/edit/stackblitz-starters-atybzr?file=index.html

@kyubisation
Copy link
Contributor Author

@justinfagnani @sorvell I have added the discussed tests (and found a bug with the nested slot case) and rebased the branch on main. I have renamed the global property to litServerRoot to keep it simple, but I am open to rename this to something else.
Please have another look.

Copy link
Member

@sorvell sorvell left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just a couple questions, very close!

renderInfo.eventTargetStack
) as HTMLElementWithEventMeta;
const slotName = getLast(renderInfo.slotStack);
(instance.element as HTMLElementWithEventMeta).__eventTargetParent =
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would this mean that in the case the elements are not slotted, a sibling's parent node would be incorrectly set to its previousElementSibling?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, I'm not sure I fully understand what you mean? Could you elaborate?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it will default to eventTarget, which is the result of getLast(renderInfo.eventTargetStack) - can that end up being the previous element sibling?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, I think I understand now what is meant.
No, because the entry is removed with the closing of the custom element or slot element down below.

@kyubisation
Copy link
Contributor Author

@sorvell Thank you for the review. Feel free to contact me via Discord, if you would like to discuss the open topics it in person.

Copy link
Collaborator

@justinfagnani justinfagnani left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks so much for your work an patience here!!

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

Successfully merging this pull request may close these issues.

[labs/ssr] Events [labs/context] SSR compatibility

5 participants