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

Allow customElementRegistry definitions to persist across page loads #1033

Open
andy-blum opened this issue Oct 2, 2023 · 7 comments
Open

Comments

@andy-blum
Copy link

One of the biggest drawbacks of using web components is the flash of unstyled content (FOUC) following the page load but prior to web components being defined. Additionally, when loading multiple web components as modules pages go through a considerable amount of cumulative layout shift as each component is defined, styles are reevaluated and layout is recalculated.

While server-side rendering is an option, it has several drawbacks:

  • It requires server configuration and resources. This can be a considerable barrier to use on what is fundamentally a client-side platform feature, especially when a single component library design system is used across multiple projects under a single domain
  • Some approaches move component internals outside of shadowRoot, breaking style encapsulation
  • Component stylesheets have to be passed through style nodes instead of constructable/adoptable stylesheets
  • It requires a hydration step on the front-end, meaning there's still a delay to interactivity for components

It seems to me all these problems could be mitigated by allowing component definitions to persist across page loads within the same domain, similar to how data can be stored client-side in sessionStorage and localStorage. In this way, a first visit to a site using web components bears the weight of LCP & CLS metrics, but each successive page load is faster as the components are reused.

@andy-blum andy-blum changed the title All customElementRegistry definitions to persist across page loads Allow customElementRegistry definitions to persist across page loads Oct 2, 2023
@andy-blum
Copy link
Author

I wonder if this is something that could be added into the scoped customElementRegistry proposal, @justinfagnani?

@smaug----
Copy link

In which JS realm would the component definitions live?

I think we need declarative custom elements for this. Implementations could cache at least the source code for definitions (possibly even in pre-parsed/binary format, very similarly what Firefox used to do with XBL).

@EisenbergEffect
Copy link

I think we should start with a more general-purpose mechanism to cache the pre-parsed/binary format of a given library. That seems like a pre-requisite to doing something Web Components specific.

@trusktr
Copy link
Contributor

trusktr commented Oct 3, 2023

I was initially thinking this would allow storing element instances and re-using them across page loads (having their adoptedCallbacks be called once placed into the document across the other side of a page load).

This would require some semantics of Service worker, with considerations for how to bust the cache on app updates. Maybe a ServiceWorker would even be responsible for this, like maybe it would manage a pool of elements. Maybe it would only be position if an element does not reference anything from its previous Realm, and only grabs things from the new document at adoptedCallback time, otherwise the element would simply be GC'd and would not reach the SW's pool. Upon new page load, when a new element is instantiated on the main thread, the engine can return an element of matching class provided by the SW. A SW could also persist some elements while the app isn't even open, ready for later.

When an element would be garbage collected (or when the page is being destroyed) this would be an opportunity for some API in the SW to be called with the element that is no longer needed in the main thread.

Totally just imagining here, not really sure how feasible this is, but the idea seems interesting.

@sashafirsov
Copy link

sashafirsov commented Oct 3, 2023

Piggybacking on idea...

Looks like we are on the edge of breaking the DOM passing across threads prohibition rule. The WC Library ( and in general any ) could have a use for bundle globals initialization and keep as registry for this lib as the implementations. Lib URL would serve the artificial context(hidden page/thread). The consumer page would refer the lib by URL on one of layers enabling its custom elements there.

The life cycle would start with 1st use and close upon session termination with ability to discard any time on browser discression.

Of cource there would be no domain nor document in the scope of WCL. Those would become available only during WC instantiation. Such restrictions can be lifted if lib domain and consumer page match.

WCL would encourange CDN kind deployments and reuse. With security ^^ in place,

preloading

as usual via meta tag

embedding into scope of WC

  • declarative - same meta resource?
  • imerative - import as JS module. Perhaps with own assert-ion.

dedicated content-type=wcl

would allow to omit the special syntax and tags. Instead, resource with this content type would be treated accordingly.

WCL content

The actual payload of customElementRegistry plus (internal?) tag to url+implementation. The dual interface for declarative(HTML/XML) and imperative lingo. Just to be short, just a JS version:

{
   'my-element': { 'module':'rel-path/my-el.js', implementation : class MyElement{...} }
}

import maps/ relative path

Of cource module is a subject for import mapswhen loaded by page. Lib could have own import maps for internal sub-modules as independent deployment unit on CDN.

WCL can load another WCL.

tag maps

Those are needed to make a map of mapping between internal tags and used by caller context.

@sashafirsov
Copy link

It requires server configuration and resources.

Not necessary. If we deine imperative registry API as self-registering in current context, there would be no need for any server side special handling. Plain import JS module with payload like following suffice:

// WCL
export defalut  function register( contextComponent, tagmap={} ){
    contextComponent.define(tagMap["my-custom-element"] || "my-custom-element", class MyCustomElement{ ... } )
    contextComponent.define(tagMap["my-another-element"] || "my-another-element", class MyLuckyElement{ ... } )
}
// caller
import someLib from 'path/wcl.js'
// within component ...
someLib.register(this)

disclaimer: syntax is not exact, just an idea

@sorvell
Copy link

sorvell commented Jan 9, 2024

I'm not really understanding how FOUC is related to web components. FOUC is a general problem which can occur if the parser yields and paints before any expected state, either before some script that performs rendering has run or even before the HTML/CSS has all streamed in.

And, typically upon page-refresh the browser's cache will pull in these resources which should typically result in no FOUC.

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
@sorvell @EisenbergEffect @trusktr @sashafirsov @smaug---- @andy-blum and others