From d4f98fb63a4cf77c4b3b4f2b359d5b991390f5c1 Mon Sep 17 00:00:00 2001 From: Pontus Lundin Date: Mon, 15 Feb 2021 23:40:17 +0100 Subject: [PATCH] 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 --- src/compiler/compile/render_dom/index.ts | 2 +- src/runtime/internal/Component.ts | 40 ++++++++++++------- test/custom-elements/index.ts | 4 +- .../samples/oncreate/main.svelte | 11 +++-- test/custom-elements/samples/oncreate/test.js | 4 +- .../samples/ondestroy/main.svelte | 22 ++++++++++ .../custom-elements/samples/ondestroy/test.js | 11 +++++ .../css-shadow-dom-keyframes/expected.js | 3 +- 8 files changed, 74 insertions(+), 23 deletions(-) create mode 100644 test/custom-elements/samples/ondestroy/main.svelte create mode 100644 test/custom-elements/samples/ondestroy/test.js diff --git a/src/compiler/compile/render_dom/index.ts b/src/compiler/compile/render_dom/index.ts index 8e7deb7a78..a56f3fb2b3 100644 --- a/src/compiler/compile/render_dom/index.ts +++ b/src/compiler/compile/render_dom/index.ts @@ -485,7 +485,7 @@ export default function dom( ${css.code && b`this.shadowRoot.innerHTML = \`\`;`} - @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} diff --git a/src/runtime/internal/Component.ts b/src/runtime/internal/Component.ts index 6d211dfedd..5a2a966d0b 100644 --- a/src/runtime/internal/Component.ts +++ b/src/runtime/internal/Component.ts @@ -34,6 +34,7 @@ interface T$$ { on_mount: any[]; on_destroy: any[]; skip_bound: boolean; + on_disconnect: any[]; } export function bind(component, name, callback) { @@ -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); } @@ -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 : []), @@ -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(); } @@ -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 @@ -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; diff --git a/test/custom-elements/index.ts b/test/custom-elements/index.ts index d3644a3142..35df156879 100644 --- a/test/custom-elements/index.ts +++ b/test/custom-elements/index.ts @@ -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 => { diff --git a/test/custom-elements/samples/oncreate/main.svelte b/test/custom-elements/samples/oncreate/main.svelte index ed3980a28e..23819e660f 100644 --- a/test/custom-elements/samples/oncreate/main.svelte +++ b/test/custom-elements/samples/oncreate/main.svelte @@ -3,9 +3,12 @@ diff --git a/test/custom-elements/samples/oncreate/test.js b/test/custom-elements/samples/oncreate/test.js index c33f8a6a10..f451979976 100644 --- a/test/custom-elements/samples/oncreate/test.js +++ b/test/custom-elements/samples/oncreate/test.js @@ -2,7 +2,9 @@ import * as assert from 'assert'; import './main.svelte'; export default function (target) { - target.innerHTML = ''; + target.innerHTML = ''; const el = target.querySelector('my-app'); + assert.ok(el.wasCreated); + assert.ok(el.propsInitialized); } diff --git a/test/custom-elements/samples/ondestroy/main.svelte b/test/custom-elements/samples/ondestroy/main.svelte new file mode 100644 index 0000000000..aa945ca602 --- /dev/null +++ b/test/custom-elements/samples/ondestroy/main.svelte @@ -0,0 +1,22 @@ + + + + +
diff --git a/test/custom-elements/samples/ondestroy/test.js b/test/custom-elements/samples/ondestroy/test.js new file mode 100644 index 0000000000..61375bfa96 --- /dev/null +++ b/test/custom-elements/samples/ondestroy/test.js @@ -0,0 +1,11 @@ +import * as assert from 'assert'; +import './main.svelte'; + +export default function (target) { + target.innerHTML = ''; + const el = target.querySelector('my-app'); + target.removeChild(el); + + assert.ok(target.dataset.onMountDestroyed); + assert.equal(target.dataset.destroyed, undefined); +} diff --git a/test/js/samples/css-shadow-dom-keyframes/expected.js b/test/js/samples/css-shadow-dom-keyframes/expected.js index 82a39e5924..4d188201eb 100644 --- a/test/js/samples/css-shadow-dom-keyframes/expected.js +++ b/test/js/samples/css-shadow-dom-keyframes/expected.js @@ -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,