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

Custom element callback/promise for "connected and parsed" #662

Open
jakearchibald opened this issue Jun 29, 2018 · 11 comments
Open

Custom element callback/promise for "connected and parsed" #662

jakearchibald opened this issue Jun 29, 2018 · 11 comments
Labels
interop Implementations are not interoperable with each other topic: custom elements Relates to custom elements (as defined in DOM and HTML)

Comments

@jakearchibald
Copy link
Collaborator

jakearchibald commented Jun 29, 2018

Developers seem to assume that connectedCallback means the element's contents will be present. This isn't helped by browser differences (demo). Safari & Firefox get it wrong and show some element contents in connectedCallback depending on loading speed. Chrome appears to get it right.

However, consider an element like <video>. Because <video>'s contents influence what should be loaded, the content shouldn't be considered until the parser has stopped adding elements into the <video>, else you can end up with pointless double fetching.

Do we need something similar for custom elements? This could be an additional callback, or perhaps even htmlElement.parsed, which is a promise that resolves once the element has fully parsed. This would be useful in other situations too, as document.documentElement.parsed would be similar to DOM-ready.

@domenic
Copy link
Member

domenic commented Jun 29, 2018

Previously: WICG/webcomponents#619, WICG/webcomponents#550.

Video/source does not appear to work in the way you describe:

If a source element is inserted as a child of a media element that has no src attribute and whose networkState has the value NETWORK_EMPTY, the user agent must invoke the media element's resource selection algorithm.

There are a couple elements which do work this way. You can find them by searching for things that trigger when the element is popped off the stack of open elements. It appears that list consists of:

  • object
  • video/audio but only with respect to text tracks
  • textarea
  • style
  • script elements, but via a different mechanism (more hard-coded into the parser)

I'm not sure it's worth spreading this pattern further, as opposed to just saying it's a legacy pattern. The most compelling examples you might want to emulate are style/script (i.e., don't apply styles/execute script until all of the text content is present). But do we see a lot of people trying to do the style/script case? My impression is it's mostly been people who are unwilling to put in the work of making their element properly reactive to descendant changes, i.e. they want to use children as configuration for initial setup, and not as part of a dynamic tree.

@jakearchibald
Copy link
Collaborator Author

Video/source does not appear to work in the way you describe

Yeah, that feels like a bug as it makes the fetching nondeterministic.

But do we see a lot of people trying to do the style/script case?

I guess you would if your web component was interpreting the input. Eg a babel/jsx component. A responsive image web component would likely want the same pattern, assuming we didn't already have responsive images.

I don't feel strongly about this, so I'll leave it for others to add use cases.

@jakearchibald
Copy link
Collaborator Author

Although, I guess <video> and <picture> do work sequentially. Ignore me in terms of that. I guess it's only the interpreting case that's valid.

@caridy
Copy link

caridy commented Jun 29, 2018

I agree with @domenic here. It will open the gates for a lot of wrong-doing. As for browser differences, do we have tickets for safari and firefox to correct that behavior?

@jakearchibald
Copy link
Collaborator Author

jakearchibald commented Jun 29, 2018 via email

@Jamesernator
Copy link

Jamesernator commented Jul 11, 2018

I've definitely wanted to have behaviour similar to this issue for simple inline code. e.g. For this sort've use case:

<math-plot>
  <module-script>
    export default function(x) {
      return x**2
    }
  </module-script>
</math-plot>

In this case it's the children of the module-script I would like to ensure only execute once (which can be important for use cases that fetch expensive resources).

I tried to create a customized builtin <script> element in Chrome Canary to simulate the behaviour but still the connectedCallback is executed too early.

<script>
  class XModuleTest extends HTMLScriptElement {
    connectedCallback() {
      console.log(this.text)
    }
  }

customElements.define('x-module', XModuleTest, { extends: 'script' })
</script>

<script is="x-module" type="_"> console.log("Hello!") </script>


On the other hand this particular problem could be solved by ensuring that the customElements.define call always happens after parsing has completed.

e.g. Just defer the customElements.define call until DOMContentLoaded:

class ScriptLike extends HTMLElement {
  // ...
}

if (document.readyState === 'interactive'
|| document.readyState === 'complete') {
  customElements.define('script-like', ScriptLike)
} else {
  document.addEventListener('DOMContentLoaded', _ => {
    customElements.define('script-like', ScriptLike)
  })
}

@annevk annevk added the interop Implementations are not interoperable with each other label Jul 21, 2018
@franktopel
Copy link

@annevk annevk added the topic: custom elements Relates to custom elements (as defined in DOM and HTML) label Oct 26, 2018
@hax
Copy link

hax commented Nov 9, 2018

@Jamesernator

Just defer the customElements.define call until DOMContentLoaded:

Normally developers just add customElements.define() after the class declaration of the custom element. The code of deferring it is a little bit tricky. Are you suggesting customElements.deferDefine? 😝

@franktopel
Copy link

franktopel commented Nov 9, 2018

@Jamesernator

Just defer the customElements.define call until DOMContentLoaded

Imo that's a bad idea for at least two reasons:

a) it won't solve problems related to dynamic creation/updating of elements via e.g. morphdom,
and, even more important,
b) it will disallow for people using your components to register their own listeners/ do whatever stuff with your components which they'd usually do on DOMContentLoaded.

That would introduce a mess of customElements.whenDefined() callbacks (where all the consumer of the components usually wants is a webcomponentsReady() callback mechanism).

@rniwa
Copy link
Collaborator

rniwa commented Apr 30, 2019

Filed WICG/webcomponents#809 to track this in web components repository.

@patricknelson
Copy link

patricknelson commented Dec 9, 2023

I don't feel strongly about this, so I'll leave it for others to add use cases.

I can help with that. 😄 One good use case might be in my Svelte custom element polyfill, svelte-retag. Essentially, it was built to support slots in the light DOM, among other things (one of its core differentiating features from Svelte's existing custom elements).

Doing this well is of course a bit tricky if you're balancing performance concerns. Funny enough, my workaround ended up looking a bit like, uh... a light "shadow" DOM of sorts... in order to efficiently get out of the way of the parser to facilitate rendering ASAP (at least on the next rAF). Demo here. I also wrote in some detail about it if you're at all interested.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
interop Implementations are not interoperable with each other topic: custom elements Relates to custom elements (as defined in DOM and HTML)
Development

No branches or pull requests

9 participants