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

Call onMount when connected & clean up when disconnected for custom element #4522

Merged
merged 21 commits into from
Feb 15, 2021

Conversation

hontas
Copy link
Contributor

@hontas hontas commented Mar 7, 2020

Relates to #1152
Resolves #2227

Solves the issue with function returned from onMount not getting called for customElements.

Introduces three changes:

  1. Run onMount in connectedCallback
  2. Register return values from onMount as on_disconnect and run them in disconnectedCallback
  3. Not resetting on_mount after initial run

Benefits:

  1. Props are initialised when onMount happens (no more tick().then(/**/)). Maps closer to mental model of a custom element.
  2. We can clean up (prevent memory leaks) without changing how a normal Svelte component behaves. Note that onDestroy is still not called.
  3. If component is disconnected and then reinserted, callbacks would run again, generating fresh clean up callbacks.

Example:

<svelte:options tag="x-tag"/>
<script>
  import { onMount } from 'svelte';

  export let disabled = false;

  onMount(() => {
    console.log('will be called by connectedCallback');
    console.log(disabled); // "" (empty string, if set like this: <x-tag disabled/>)

    return () => {
      console.log('will be called by disconnectedCallback');
    }
  });
</script>

* 'master' of https://github.com/sveltejs/svelte: (137 commits)
  -> v3.19.2
  fix lazy code breaks in build
  fit bitmask overflow initial dirty value in 'if' blocks (sveltejs#4507)
  add dev runtime warning for unknown slot names (sveltejs#4501)
  fix render fallback slot content due to whitespace (sveltejs#4500)
  docs: describe falsy and nullish attribute behavior (sveltejs#4498)
  in spread, distinguish never-updating and always-updating props (sveltejs#4487)
  chore: more specific typings, and add README note about Yarn (sveltejs#4483)
  update changelog
  check for unknown props even if component doesn't have writable props (sveltejs#4454)
  Bump codecov from 3.5.0 to 3.6.5 (sveltejs#4433)
  fix bitmask overflow for slot (sveltejs#4485)
  mark module variables as mutated or reassigned (sveltejs#4469)
  docs: referenced_from_script var value (sveltejs#4486)
  docs: clarify default prop behaviour (sveltejs#4460)
  site: turn fancybutton into custombutton (sveltejs#4476)
  update changelog
  exclude global variables from $capture_state (sveltejs#4475)
  -> v3.19.1
  don't treat $$-names as stores during invalidation (sveltejs#4453)
  ...
@Rich-Harris
Copy link
Member

Thank you. What do you think about the discussion in that issue though — about the fact that we actually don't want onDestroy callbacks to run when an element is merely disconnected? What if instead, only the return values from onMount callbacks were run in those situations, and onDestroy callbacks were restricted to when $destroy is explicitly called?

@hontas
Copy link
Contributor Author

hontas commented Jun 3, 2020

@Rich-Harris splendid!

Updated the PR so that on_mount-callbacks are run from connectedCallback, saving its return values as on_disconnect. 👈 This is then run from disconnectedCallback.
That way, if the component is reinserted the disconnect-callbacks are re-generated and can run again when disconnected.
And we get the added benefit that props are initialized at the time of onMount which I think simplifies the mental model when working with custom elements 😀

@hontas hontas requested a review from tanhauhau June 4, 2020 05:58
@jesper-bylund
Copy link

@tanhauhau is this being merged? Looks like it's waiting on you, AND that you've apporved. I'm confused. 😅

* upstream/master: (190 commits)
  invalidate $$props and $$restProps only when there are changes (sveltejs#5123)
  site: use https in link in blog (sveltejs#5148)
  Simplify each block bindings example (sveltejs#5094)
  fix $$props reactive for slots (sveltejs#5125)
  site: add FAQ entry for how to document a svelte component (sveltejs#5131)
  site: remove an obsolete TODO in blog post (sveltejs#5135)
  Increase timeout for unit build
  Increase timeout for unit tests
  -> v3.24.0
  spread condition for input element (sveltejs#5004)
  update changelog
  fix(5018): compare wholeText instead of data (sveltejs#5028)
  html anchor in head (sveltejs#5071)
  error on expression scope store (sveltejs#5079)
  update changelog
  preprocess self-closing script and style tags (sveltejs#5082)
  update changelog
  fix: Parameters with default values are optional (sveltejs#5083)
  make builds time out after a reasonable period (sveltejs#5100)
  site: fix blog typo (sveltejs#5090)
  ...
* 'master' of github.com:hontas/svelte:
* upstream/master:
  add updating guard to binding callback (sveltejs#5126)
  Bump lodash from 4.17.15 to 4.17.19 (sveltejs#5152)
  Bump lodash from 4.17.15 to 4.17.19 in /site (sveltejs#5155)
  Fixes sveltejs#5153 (sveltejs#5154)
@hontas hontas changed the title Call onDestroy callbacks when disconnected Call onMount when connected & clean up when disconnected for custom element Jul 18, 2020
@Truffula
Copy link

Truffula commented Aug 5, 2020

@hontas I've been working on another solution to #2227 (see #5139) and comparing the impact on the various hooks as well as any top-level code. If the props are initialised before onMount is called, but not before the initial render or beforeUpdate, you can end up rendering the template without the props being available - i.e. you end up rendering twice, and if any props are mandatory you need to add extra logic for the case where they're not initialised yet.

* upstream/master: (140 commits)
  support $$props and $$restProps for custom elements (sveltejs#5608)
  Bump eslint-config to 5.5.0 (sveltejs#5599)
  update changelog
  add Node and Element as known globals (sveltejs#5601)
  docs: fix a11y warning in media elements example (sveltejs#5606)
  Curly braces linting fixes (sveltejs#5585)
  refactor sourcemap and preprocessor tests (sveltejs#5583)
  -> v3.29.4
  fix code generation error with nullish coalescing operator and logical operators (sveltejs#5564)
  -> v3.29.3
  -> v3.29.2
  -> v3.29.1
  docs: fix a11y warning in media elements tutorial (sveltejs#5523)
  update changelog
  get context at start of {#if} update block instead of at the end (sveltejs#5531)
  update changelog
  only allow passing functions to lifecycle functions (sveltejs#5529)
  update changelog
  fix compiler hanging on <slot slot="..."> (sveltejs#5536)
  Add svelte@next caveat to bug report template (sveltejs#5561)
  ...
@yannkost
Copy link

yannkost commented Dec 1, 2020

Can I ask if this issu is still being worked on?
The custom solution here, adding the following seems to work just fine for me..

class SubElement extends SvelteElement { // extend the Svelte custom element here
  disconnectedCallback() {
    this.$destroy()
  }
}

Still, I don't like the fact that I'm changing something in the svelte internals. Still, for some cases it is needed to call onDestroy to clean up after removing a custom element.

@hontas
Copy link
Contributor Author

hontas commented Dec 3, 2020

Can I ask if this issu is still being worked on?

Certainly @yannkost ;) It’s still waiting for feedback from the maintainers.
Regarding the custom solution, please see @Rich-Harris comment above and the thoughts in #1152 on how they don’t want destroy to be called from disconnectedCallback.

@antony antony self-assigned this Jan 20, 2021
@antony
Copy link
Member

antony commented Jan 20, 2021

Are the issues discussed above solved by #5139 ?

We should close one of these PRs, and focus on getting the other merged. Perhaps somebody who is closer to the issue (somebody who knows about custom elements) could clarify?

@Truffula
Copy link

@antony I don't think #5139 solves all of the issues raised in this PR, it deals with the creation lifecycle but not disconnectedCallback. IIRC this PR (#4522) doesn't prevent exports from initialising with the wrong value, so ultimately we probably need a bit of both.

@arackaf
Copy link
Contributor

arackaf commented Feb 9, 2021

Don't worry - I'm not here asking for "when it'll be merged" :-)

Just wanna make sure I understand what this PR does, as it pertains to web components, and the comments @Rich-Harris made.

So, apparently, it's not unheard of for a web component to be disconnected, and then re-connected / re-attached to the dom. And so what this PR does is fire only the onUnMount handler the user can optionally return from onMount, when the web component is disconnected, but otherwise not destroy anything. And then if the wc were to be re-attached to the dom, then the user's onMount handler would fire de novo, and the whole process would repeat.

Is that about right?

@hontas
Copy link
Contributor Author

hontas commented Feb 10, 2021

@arackaf yes that’s the idea 💡

@arackaf
Copy link
Contributor

arackaf commented Feb 10, 2021

@antony - per our discussion, I've pulled down this PR, and tested it locally. It appears to be a solid fix for web component connected / disconnected lifecycles, as they pertain to Svelte. Before, onMount would be called the moment the web component was created, and the unmount callback was never, ever called.

Now, with this PR, the onMount handler is called when the wc is connected to the dom, with the unMount handler being called when it's disconnected. Rinse, repeat. I doubt this fixes all outstanding wc issues, but it seems to fix a big one, and well. I'm confident enough to wager @Rich-Harris's life on it ;)

@hontas - your branch appears to be far behind master (I of course merged prior to testing). Would you mind merging and pushing? Heads up - expect one conflict in a test file.

* 'master' of https://github.com/sveltejs/svelte: (129 commits)
  -> v3.32.3
  fix remove of lone :host selectors (sveltejs#5984)
  -> v3.32.2
  update changelog
  fix extra invalidation with component prop binding to object property (sveltejs#5890)
  update css-tree@^1.1.2 (sveltejs#5958)
  fix :host and :global css scoping (sveltejs#5957)
  Tutorial : a better explanation of component events (sveltejs#4639)
  "What's new in Svelte" February newsletter (sveltejs#5925)
  Change color on 404 page (sveltejs#5932)
  -> v3.32.1
  warn module variables are nonreactive and make them truly nonreactive (sveltejs#5847)
  update changelog
  fix: "foreign" namespace elements should still allow binding 'this' (sveltejs#5942)
  inline `prop_values` in `init` helper (sveltejs#5909)
  update changelog
  don't create class update functions when dependencies aren't reactive (sveltejs#5926)
  fix extraneous store subscription in SSR (sveltejs#5929)
  make `SvelteComponentDev` typings more forgiving (sveltejs#5937)
  make animation/transition params optional (sveltejs#5936)
  ...
Pontus Lundin and others added 6 commits February 11, 2021 20:50
- Call onMount in connectedCallback for customElements
- register onMount return values as on_disconnect-callbacks for customElements
- run on_disconnect callbacks in disconnectedCallback
@hontas hontas force-pushed the feature/custom-element-destroy branch from 8fc8c81 to 9cc7dd2 Compare February 11, 2021 19:52
@arackaf
Copy link
Contributor

arackaf commented Feb 11, 2021

@hontas I see you just updated your branch - are also you ok fixing the issue Rich mentioned? I'm happy to if this is a busy / bad time for you.

@hontas
Copy link
Contributor Author

hontas commented Feb 11, 2021

@arackaf I'm on it!
@Rich-Harris thanks for the feedback. Did you mean from the init function? If so where is it obvious to us that it's a CE that's being rendered?

@arackaf
Copy link
Contributor

arackaf commented Feb 11, 2021

@hontas - you're amazing. I can't answer your question, but in the off chance there's no easy way to implement what @Rich-Harris suggested, would

const isCustomElement = component instanceof SvelteElement;

work? I think that would be a lot more performant that the original code. (untested of course - just an idea - apologies if there's something dumb wrong with that)

- pass options.customElement down to mount_component
- remove expensive isCustomElement check
- only call add_render_callback if not customElement
@hontas
Copy link
Contributor Author

hontas commented Feb 11, 2021

@Rich-Harris updated PR with your suggestions 😃 please have a look if that's what you had in mind. I'm super excited!
Thank you @arackaf! I think I found another even more performant option 👍

@yannkost
Copy link

Do I understand this right when I say that the component will execute the callback fonction set up in onMount when disconnected (only if it's a custom élément)? (This is what I seem to understand in the code)

@arackaf
Copy link
Contributor

arackaf commented Feb 11, 2021

@yannkost it’ll run the teardown function, which is returned from onMount, when the custom element disconnects. Likewise it’ll run the onMount function itself when the web component connects.

@antony antony merged commit d4f98fb into sveltejs:master Feb 15, 2021
@arackaf
Copy link
Contributor

arackaf commented Feb 16, 2021

@hontas thanks a ton for making this fix. Huge lifesaver.

I'm not by any means asking you to fix this, but since you seem to have interest in using Svelte for web components, just an fyi that I recently found this issue: #5989

Seems like the fix would be incredibly similar to what you did here, or at the very least, something for you to be aware of, for your future use of Svelte :)

Thanks again!

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.

Prop initialization in web/standalone components
9 participants