Skip to content

Commit

Permalink
Call onMount when connected & clean up when disconnected for custom e…
Browse files Browse the repository at this point in the history
…lement (#4522)

* call onDestroy when disconnected

* lifecycle hooks and custom elements
- Call onMount in connectedCallback for customElements
- register onMount return values as on_disconnect-callbacks for customElements
- run on_disconnect callbacks in disconnectedCallback

* do not reset on_mount so that it can fire again if reinserted

* simpler isCustomElement & skip extra function call
- pass options.customElement down to mount_component
- remove expensive isCustomElement check
- only call add_render_callback if not customElement

Co-authored-by: Pontus Lundin <pontus.lundin@ica.se>
  • Loading branch information
hontas and Pontus Lundin authored Feb 15, 2021
1 parent d3f3ea3 commit d4f98fb
Show file tree
Hide file tree
Showing 8 changed files with 74 additions and 23 deletions.
2 changes: 1 addition & 1 deletion src/compiler/compile/render_dom/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -485,7 +485,7 @@ export default function dom(
${css.code && b`this.shadowRoot.innerHTML = \`<style>${css.code.replace(/\\/g, '\\\\')}${options.dev ? `\n/*# sourceMappingURL=${css.map.toUrl()} */` : ''}</style>\`;`}
@init(this, { target: this.shadowRoot, props: ${init_props} }, ${definition}, ${has_create_fragment ? 'create_fragment' : 'null'}, ${not_equal}, ${prop_indexes}, ${dirty});
@init(this, { target: this.shadowRoot, props: ${init_props}, customElement: true }, ${definition}, ${has_create_fragment ? 'create_fragment' : 'null'}, ${not_equal}, ${prop_indexes}, ${dirty});
${dev_props_check}
Expand Down
40 changes: 26 additions & 14 deletions src/runtime/internal/Component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ interface T$$ {
on_mount: any[];
on_destroy: any[];
skip_bound: boolean;
on_disconnect: any[];
}

export function bind(component, name, callback) {
Expand All @@ -52,23 +53,26 @@ export function claim_component(block, parent_nodes) {
block && block.l(parent_nodes);
}

export function mount_component(component, target, anchor) {
export function mount_component(component, target, anchor, customElement) {
const { fragment, on_mount, on_destroy, after_update } = component.$$;

fragment && fragment.m(target, anchor);

// onMount happens before the initial afterUpdate
add_render_callback(() => {
const new_on_destroy = on_mount.map(run).filter(is_function);
if (on_destroy) {
on_destroy.push(...new_on_destroy);
} else {
// Edge case - component was destroyed immediately,
// most likely as a result of a binding initialising
run_all(new_on_destroy);
}
component.$$.on_mount = [];
});
if (!customElement) {
// onMount happens before the initial afterUpdate
add_render_callback(() => {

const new_on_destroy = on_mount.map(run).filter(is_function);
if (on_destroy) {
on_destroy.push(...new_on_destroy);
} else {
// Edge case - component was destroyed immediately,
// most likely as a result of a binding initialising
run_all(new_on_destroy);
}
component.$$.on_mount = [];
});
}

after_update.forEach(add_render_callback);
}
Expand Down Expand Up @@ -113,6 +117,7 @@ export function init(component, options, instance, create_fragment, not_equal, p
// lifecycle
on_mount: [],
on_destroy: [],
on_disconnect: [],
before_update: [],
after_update: [],
context: new Map(parent_component ? parent_component.$$.context : []),
Expand Down Expand Up @@ -155,7 +160,7 @@ export function init(component, options, instance, create_fragment, not_equal, p
}

if (options.intro) transition_in(component.$$.fragment);
mount_component(component, options.target, options.anchor);
mount_component(component, options.target, options.anchor, options.customElement);
flush();
}

Expand All @@ -173,6 +178,9 @@ if (typeof HTMLElement === 'function') {
}

connectedCallback() {
const { on_mount } = this.$$;
this.$$.on_disconnect = on_mount.map(run).filter(is_function);

// @ts-ignore todo: improve typings
for (const key in this.$$.slotted) {
// @ts-ignore todo: improve typings
Expand All @@ -184,6 +192,10 @@ if (typeof HTMLElement === 'function') {
this[attr] = newValue;
}

disconnectedCallback() {
run_all(this.$$.on_disconnect);
}

$destroy() {
destroy_component(this, 1);
this.$destroy = noop;
Expand Down
4 changes: 2 additions & 2 deletions test/custom-elements/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -110,8 +110,8 @@ describe('custom-elements', function() {

const page = await browser.newPage();

page.on('console', (type, ...args) => {
console[type](...args);
page.on('console', (type) => {
console[type._type](type._text);
});

page.on('error', error => {
Expand Down
11 changes: 7 additions & 4 deletions test/custom-elements/samples/oncreate/main.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,12 @@
<script>
import { onMount } from 'svelte';
export let wasCreated;
export let prop = false;
export let propsInitialized;
export let wasCreated;
onMount(() => {
wasCreated = true;
});
onMount(() => {
propsInitialized = prop !== false;
wasCreated = true;
});
</script>
4 changes: 3 additions & 1 deletion test/custom-elements/samples/oncreate/test.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@ import * as assert from 'assert';
import './main.svelte';

export default function (target) {
target.innerHTML = '<my-app/>';
target.innerHTML = '<my-app prop/>';
const el = target.querySelector('my-app');

assert.ok(el.wasCreated);
assert.ok(el.propsInitialized);
}
22 changes: 22 additions & 0 deletions test/custom-elements/samples/ondestroy/main.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<svelte:options tag="my-app"/>

<script>
import { onMount, onDestroy } from 'svelte';
let el;
let parentEl;
onMount(() => {
parentEl = el.parentNode.host.parentElement;
return () => {
parentEl.dataset.onMountDestroyed = true;
}
});
onDestroy(() => {
parentEl.dataset.destroyed = true;
})
</script>

<div bind:this={el}></div>
11 changes: 11 additions & 0 deletions test/custom-elements/samples/ondestroy/test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import * as assert from 'assert';
import './main.svelte';

export default function (target) {
target.innerHTML = '<my-app/>';
const el = target.querySelector('my-app');
target.removeChild(el);

assert.ok(target.dataset.onMountDestroyed);
assert.equal(target.dataset.destroyed, undefined);
}
3 changes: 2 additions & 1 deletion test/js/samples/css-shadow-dom-keyframes/expected.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,8 @@ class Component extends SvelteElement {
this,
{
target: this.shadowRoot,
props: attribute_to_object(this.attributes)
props: attribute_to_object(this.attributes),
customElement: true
},
null,
create_fragment,
Expand Down

0 comments on commit d4f98fb

Please sign in to comment.