WICG / webcomponents

Web Components specifications
Other
4.34k stars 371 forks source link

Allow customElementRegistry definitions to persist across page loads #1033

Open andy-blum opened 9 months ago

andy-blum commented 9 months ago

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 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 commented 9 months ago

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

smaug---- commented 9 months ago

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 commented 9 months ago

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 commented 9 months ago

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 commented 9 months ago

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

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 commented 9 months ago

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 commented 5 months ago

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.