Skip to content

Commit d4f98fb

Browse files
hontasPontus Lundin
and
Pontus Lundin
authored
Call onMount when connected & clean up when disconnected for custom element (#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 <[email protected]>
1 parent d3f3ea3 commit d4f98fb

File tree

8 files changed

+74
-23
lines changed

8 files changed

+74
-23
lines changed

src/compiler/compile/render_dom/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -485,7 +485,7 @@ export default function dom(
485485
486486
${css.code && b`this.shadowRoot.innerHTML = \`<style>${css.code.replace(/\\/g, '\\\\')}${options.dev ? `\n/*# sourceMappingURL=${css.map.toUrl()} */` : ''}</style>\`;`}
487487
488-
@init(this, { target: this.shadowRoot, props: ${init_props} }, ${definition}, ${has_create_fragment ? 'create_fragment' : 'null'}, ${not_equal}, ${prop_indexes}, ${dirty});
488+
@init(this, { target: this.shadowRoot, props: ${init_props}, customElement: true }, ${definition}, ${has_create_fragment ? 'create_fragment' : 'null'}, ${not_equal}, ${prop_indexes}, ${dirty});
489489
490490
${dev_props_check}
491491

src/runtime/internal/Component.ts

Lines changed: 26 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ interface T$$ {
3434
on_mount: any[];
3535
on_destroy: any[];
3636
skip_bound: boolean;
37+
on_disconnect: any[];
3738
}
3839

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

55-
export function mount_component(component, target, anchor) {
56+
export function mount_component(component, target, anchor, customElement) {
5657
const { fragment, on_mount, on_destroy, after_update } = component.$$;
5758

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

60-
// onMount happens before the initial afterUpdate
61-
add_render_callback(() => {
62-
const new_on_destroy = on_mount.map(run).filter(is_function);
63-
if (on_destroy) {
64-
on_destroy.push(...new_on_destroy);
65-
} else {
66-
// Edge case - component was destroyed immediately,
67-
// most likely as a result of a binding initialising
68-
run_all(new_on_destroy);
69-
}
70-
component.$$.on_mount = [];
71-
});
61+
if (!customElement) {
62+
// onMount happens before the initial afterUpdate
63+
add_render_callback(() => {
64+
65+
const new_on_destroy = on_mount.map(run).filter(is_function);
66+
if (on_destroy) {
67+
on_destroy.push(...new_on_destroy);
68+
} else {
69+
// Edge case - component was destroyed immediately,
70+
// most likely as a result of a binding initialising
71+
run_all(new_on_destroy);
72+
}
73+
component.$$.on_mount = [];
74+
});
75+
}
7276

7377
after_update.forEach(add_render_callback);
7478
}
@@ -113,6 +117,7 @@ export function init(component, options, instance, create_fragment, not_equal, p
113117
// lifecycle
114118
on_mount: [],
115119
on_destroy: [],
120+
on_disconnect: [],
116121
before_update: [],
117122
after_update: [],
118123
context: new Map(parent_component ? parent_component.$$.context : []),
@@ -155,7 +160,7 @@ export function init(component, options, instance, create_fragment, not_equal, p
155160
}
156161

157162
if (options.intro) transition_in(component.$$.fragment);
158-
mount_component(component, options.target, options.anchor);
163+
mount_component(component, options.target, options.anchor, options.customElement);
159164
flush();
160165
}
161166

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

175180
connectedCallback() {
181+
const { on_mount } = this.$$;
182+
this.$$.on_disconnect = on_mount.map(run).filter(is_function);
183+
176184
// @ts-ignore todo: improve typings
177185
for (const key in this.$$.slotted) {
178186
// @ts-ignore todo: improve typings
@@ -184,6 +192,10 @@ if (typeof HTMLElement === 'function') {
184192
this[attr] = newValue;
185193
}
186194

195+
disconnectedCallback() {
196+
run_all(this.$$.on_disconnect);
197+
}
198+
187199
$destroy() {
188200
destroy_component(this, 1);
189201
this.$destroy = noop;

test/custom-elements/index.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -110,8 +110,8 @@ describe('custom-elements', function() {
110110

111111
const page = await browser.newPage();
112112

113-
page.on('console', (type, ...args) => {
114-
console[type](...args);
113+
page.on('console', (type) => {
114+
console[type._type](type._text);
115115
});
116116

117117
page.on('error', error => {

test/custom-elements/samples/oncreate/main.svelte

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,12 @@
33
<script>
44
import { onMount } from 'svelte';
55
6-
export let wasCreated;
6+
export let prop = false;
7+
export let propsInitialized;
8+
export let wasCreated;
79
8-
onMount(() => {
9-
wasCreated = true;
10-
});
10+
onMount(() => {
11+
propsInitialized = prop !== false;
12+
wasCreated = true;
13+
});
1114
</script>

test/custom-elements/samples/oncreate/test.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@ import * as assert from 'assert';
22
import './main.svelte';
33

44
export default function (target) {
5-
target.innerHTML = '<my-app/>';
5+
target.innerHTML = '<my-app prop/>';
66
const el = target.querySelector('my-app');
7+
78
assert.ok(el.wasCreated);
9+
assert.ok(el.propsInitialized);
810
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
<svelte:options tag="my-app"/>
2+
3+
<script>
4+
import { onMount, onDestroy } from 'svelte';
5+
6+
let el;
7+
let parentEl;
8+
9+
onMount(() => {
10+
parentEl = el.parentNode.host.parentElement;
11+
12+
return () => {
13+
parentEl.dataset.onMountDestroyed = true;
14+
}
15+
});
16+
17+
onDestroy(() => {
18+
parentEl.dataset.destroyed = true;
19+
})
20+
</script>
21+
22+
<div bind:this={el}></div>
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import * as assert from 'assert';
2+
import './main.svelte';
3+
4+
export default function (target) {
5+
target.innerHTML = '<my-app/>';
6+
const el = target.querySelector('my-app');
7+
target.removeChild(el);
8+
9+
assert.ok(target.dataset.onMountDestroyed);
10+
assert.equal(target.dataset.destroyed, undefined);
11+
}

test/js/samples/css-shadow-dom-keyframes/expected.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,8 @@ class Component extends SvelteElement {
4040
this,
4141
{
4242
target: this.shadowRoot,
43-
props: attribute_to_object(this.attributes)
43+
props: attribute_to_object(this.attributes),
44+
customElement: true
4445
},
4546
null,
4647
create_fragment,

0 commit comments

Comments
 (0)