From eb4015804865371a9b94a517902c1455aaf48bd5 Mon Sep 17 00:00:00 2001 From: Tan Li Hau Date: Fri, 6 Mar 2020 09:13:27 +0800 Subject: [PATCH 1/3] do not initialise slot fallback fragment unless necessary --- .../compile/render_dom/wrappers/Slot.ts | 98 +++++++++++-------- .../shared/create_debugging_comment.ts | 12 ++- .../component-slot-fallback-2/Inner.svelte | 7 ++ .../component-slot-fallback-2/Outer.svelte | 7 ++ .../component-slot-fallback-2/_config.js | 39 ++++++++ .../component-slot-fallback-2/main.svelte | 23 +++++ .../component-slot-fallback-2/store.svelte | 23 +++++ 7 files changed, 166 insertions(+), 43 deletions(-) create mode 100644 test/runtime/samples/component-slot-fallback-2/Inner.svelte create mode 100644 test/runtime/samples/component-slot-fallback-2/Outer.svelte create mode 100644 test/runtime/samples/component-slot-fallback-2/_config.js create mode 100644 test/runtime/samples/component-slot-fallback-2/main.svelte create mode 100644 test/runtime/samples/component-slot-fallback-2/store.svelte diff --git a/src/compiler/compile/render_dom/wrappers/Slot.ts b/src/compiler/compile/render_dom/wrappers/Slot.ts index 5f2d3318397e..10f12716a7df 100644 --- a/src/compiler/compile/render_dom/wrappers/Slot.ts +++ b/src/compiler/compile/render_dom/wrappers/Slot.ts @@ -10,10 +10,12 @@ import get_slot_data from '../../utils/get_slot_data'; import Expression from '../../nodes/shared/Expression'; import is_dynamic from './shared/is_dynamic'; import { Identifier, ObjectExpression } from 'estree'; +import create_debugging_comment from './shared/create_debugging_comment'; export default class SlotWrapper extends Wrapper { node: Slot; fragment: FragmentWrapper; + fallback: Block | null; var: Identifier = { type: 'Identifier', name: 'slot' }; dependencies: Set = new Set(['$$scope']); @@ -30,9 +32,18 @@ export default class SlotWrapper extends Wrapper { this.cannot_use_innerhtml(); this.not_static_content(); + this.fallback = null; + if (this.node.children.length) { + this.fallback = block.child({ + comment: create_debugging_comment(this.node.children[0], this.renderer.component), + name: this.renderer.component.get_unique_name(`fallback_block`), + type: 'fallback' + }); + } + this.fragment = new FragmentWrapper( renderer, - block, + this.fallback, node.children, parent, strip_whitespace, @@ -103,66 +114,43 @@ export default class SlotWrapper extends Wrapper { get_slot_context_fn = 'null'; } + if (this.fallback) { + this.fragment.render(this.fallback, null, x`#nodes` as Identifier); + renderer.blocks.push(this.fallback); + } + const slot = block.get_unique_name(`${sanitize(slot_name)}_slot`); const slot_definition = block.get_unique_name(`${sanitize(slot_name)}_slot_template`); + const slot_or_fallback = this.fallback ? block.get_unique_name(`${sanitize(slot_name)}_slot_or_fallback`) : slot; block.chunks.init.push(b` const ${slot_definition} = ${renderer.reference('$$slots')}.${slot_name}; const ${slot} = @create_slot(${slot_definition}, #ctx, ${renderer.reference('$$scope')}, ${get_slot_context_fn}); + ${this.fallback ? b`const ${slot_or_fallback} = ${slot} || ${this.fallback.name}(#ctx);` : null} `); - // TODO this is a dreadful hack! Should probably make this nicer - const { create, claim, hydrate, mount, update, destroy } = block.chunks; - - block.chunks.create = []; - block.chunks.claim = []; - block.chunks.hydrate = []; - block.chunks.mount = []; - block.chunks.update = []; - block.chunks.destroy = []; - - const listeners = block.event_listeners; - block.event_listeners = []; - this.fragment.render(block, parent_node, parent_nodes); - block.render_listeners(`_${slot.name}`); - block.event_listeners = listeners; - - if (block.chunks.create.length) create.push(b`if (!${slot}) { ${block.chunks.create} }`); - if (block.chunks.claim.length) claim.push(b`if (!${slot}) { ${block.chunks.claim} }`); - if (block.chunks.hydrate.length) hydrate.push(b`if (!${slot}) { ${block.chunks.hydrate} }`); - if (block.chunks.mount.length) mount.push(b`if (!${slot}) { ${block.chunks.mount} }`); - if (block.chunks.update.length) update.push(b`if (!${slot}) { ${block.chunks.update} }`); - if (block.chunks.destroy.length) destroy.push(b`if (!${slot}) { ${block.chunks.destroy} }`); - - block.chunks.create = create; - block.chunks.claim = claim; - block.chunks.hydrate = hydrate; - block.chunks.mount = mount; - block.chunks.update = update; - block.chunks.destroy = destroy; - block.chunks.create.push( - b`if (${slot}) ${slot}.c();` + b`if (${slot_or_fallback}) ${slot_or_fallback}.c();` ); if (renderer.options.hydratable) { block.chunks.claim.push( - b`if (${slot}) ${slot}.l(${parent_nodes});` + b`if (${slot_or_fallback}) ${slot_or_fallback}.l(${parent_nodes});` ); } block.chunks.mount.push(b` - if (${slot}) { - ${slot}.m(${parent_node || '#target'}, ${parent_node ? 'null' : 'anchor'}); + if (${slot_or_fallback}) { + ${slot_or_fallback}.m(${parent_node || '#target'}, ${parent_node ? 'null' : 'anchor'}); } `); block.chunks.intro.push( - b`@transition_in(${slot}, #local);` + b`@transition_in(${slot_or_fallback}, #local);` ); block.chunks.outro.push( - b`@transition_out(${slot}, #local);` + b`@transition_out(${slot_or_fallback}, #local);` ); const dynamic_dependencies = Array.from(this.dependencies).filter(name => { @@ -172,17 +160,45 @@ export default class SlotWrapper extends Wrapper { return is_dynamic(variable); }); - block.chunks.update.push(b` - if (${slot} && ${slot}.p && ${renderer.dirty(dynamic_dependencies)}) { + const fallback_dynamic_dependencies = this.fallback ? Array.from(this.fallback.dependencies).filter(name => { + if (name === '$$scope') return true; + if (this.node.scope.is_let(name)) return true; + const variable = renderer.component.var_lookup.get(name); + return is_dynamic(variable); + }) : []; + + const slot_update = b` + if (${slot}.p && ${renderer.dirty(dynamic_dependencies)}) { ${slot}.p( @get_slot_context(${slot_definition}, #ctx, ${renderer.reference('$$scope')}, ${get_slot_context_fn}), @get_slot_changes(${slot_definition}, ${renderer.reference('$$scope')}, #dirty, ${get_slot_changes_fn}) ); } - `); + `; + const fallback_update = this.fallback && fallback_dynamic_dependencies.length > 0 && b` + if (${slot_or_fallback} && ${slot_or_fallback}.p && ${renderer.dirty(fallback_dynamic_dependencies)}) { + ${slot_or_fallback}.p(#ctx, #dirty); + } + `; + + if (fallback_update) { + block.chunks.update.push(b` + if (${slot}) { + ${slot_update} + } else { + ${fallback_update} + } + `); + } else { + block.chunks.update.push(b` + if (${slot}) { + ${slot_update} + } + `); + } block.chunks.destroy.push( - b`if (${slot}) ${slot}.d(detaching);` + b`if (${slot_or_fallback}) ${slot_or_fallback}.d(detaching);` ); } } diff --git a/src/compiler/compile/render_dom/wrappers/shared/create_debugging_comment.ts b/src/compiler/compile/render_dom/wrappers/shared/create_debugging_comment.ts index e2aa0c0b41d2..11f1feb841b9 100644 --- a/src/compiler/compile/render_dom/wrappers/shared/create_debugging_comment.ts +++ b/src/compiler/compile/render_dom/wrappers/shared/create_debugging_comment.ts @@ -16,8 +16,16 @@ export default function create_debugging_comment( let d; if (node.type === 'InlineComponent' || node.type === 'Element') { - d = node.children.length ? node.children[0].start : node.start; - while (source[d - 1] !== '>') d -= 1; + if (node.children.length) { + d = node.children[0].start; + while (source[d - 1] !== '>') d -= 1; + } else { + d = node.start; + while (source[d] !== '>') d += 1; + d += 1; + } + } else if (node.type === 'Text') { + d = node.end; } else { // @ts-ignore d = node.expression ? node.expression.node.end : c; diff --git a/test/runtime/samples/component-slot-fallback-2/Inner.svelte b/test/runtime/samples/component-slot-fallback-2/Inner.svelte new file mode 100644 index 000000000000..4c421969b607 --- /dev/null +++ b/test/runtime/samples/component-slot-fallback-2/Inner.svelte @@ -0,0 +1,7 @@ + + + +{value} \ No newline at end of file diff --git a/test/runtime/samples/component-slot-fallback-2/Outer.svelte b/test/runtime/samples/component-slot-fallback-2/Outer.svelte new file mode 100644 index 000000000000..206a4295d94e --- /dev/null +++ b/test/runtime/samples/component-slot-fallback-2/Outer.svelte @@ -0,0 +1,7 @@ + + + \ No newline at end of file diff --git a/test/runtime/samples/component-slot-fallback-2/_config.js b/test/runtime/samples/component-slot-fallback-2/_config.js new file mode 100644 index 000000000000..585e3b4c9e99 --- /dev/null +++ b/test/runtime/samples/component-slot-fallback-2/_config.js @@ -0,0 +1,39 @@ +export default { + html: ` `, + ssrHtml: ` `, + + async test({ assert, target, component, window }) { + const [input1, input2, inputFallback] = target.querySelectorAll("input"); + + assert.equal(component.getSubscriberCount(), 3); + + input1.value = "a"; + await input1.dispatchEvent(new window.Event("input")); + input1.value = "ab"; + await input1.dispatchEvent(new window.Event("input")); + assert.equal(input1.value, "ab"); + assert.equal(input2.value, "ab"); + assert.equal(inputFallback.value, "ab"); + + component.props = "hello"; + + assert.htmlEqual( + target.innerHTML, + ` + hello + hello + + ` + ); + + component.fallback = "world"; + assert.htmlEqual( + target.innerHTML, + ` + hello + hello + world + ` + ); + } +}; diff --git a/test/runtime/samples/component-slot-fallback-2/main.svelte b/test/runtime/samples/component-slot-fallback-2/main.svelte new file mode 100644 index 000000000000..458366701534 --- /dev/null +++ b/test/runtime/samples/component-slot-fallback-2/main.svelte @@ -0,0 +1,23 @@ + + + + + + + + + + + + diff --git a/test/runtime/samples/component-slot-fallback-2/store.svelte b/test/runtime/samples/component-slot-fallback-2/store.svelte new file mode 100644 index 000000000000..86e1d6bdfd6b --- /dev/null +++ b/test/runtime/samples/component-slot-fallback-2/store.svelte @@ -0,0 +1,23 @@ + \ No newline at end of file From a699323b20252ff1d59fb9b7456eefa0e4faa8c9 Mon Sep 17 00:00:00 2001 From: Conduitry Date: Sun, 15 Mar 2020 05:36:46 -0400 Subject: [PATCH 2/3] tidy --- .../compile/render_dom/wrappers/Slot.ts | 18 ++++++++---------- .../component-slot-fallback-2/Inner.svelte | 4 ++-- .../component-slot-fallback-2/Outer.svelte | 6 +++--- .../component-slot-fallback-2/store.svelte | 8 ++++---- 4 files changed, 17 insertions(+), 19 deletions(-) diff --git a/src/compiler/compile/render_dom/wrappers/Slot.ts b/src/compiler/compile/render_dom/wrappers/Slot.ts index 10f12716a7df..37b10c2ea38a 100644 --- a/src/compiler/compile/render_dom/wrappers/Slot.ts +++ b/src/compiler/compile/render_dom/wrappers/Slot.ts @@ -15,7 +15,7 @@ import create_debugging_comment from './shared/create_debugging_comment'; export default class SlotWrapper extends Wrapper { node: Slot; fragment: FragmentWrapper; - fallback: Block | null; + fallback: Block | null = null; var: Identifier = { type: 'Identifier', name: 'slot' }; dependencies: Set = new Set(['$$scope']); @@ -32,7 +32,6 @@ export default class SlotWrapper extends Wrapper { this.cannot_use_innerhtml(); this.not_static_content(); - this.fallback = null; if (this.node.children.length) { this.fallback = block.child({ comment: create_debugging_comment(this.node.children[0], this.renderer.component), @@ -153,19 +152,18 @@ export default class SlotWrapper extends Wrapper { b`@transition_out(${slot_or_fallback}, #local);` ); - const dynamic_dependencies = Array.from(this.dependencies).filter(name => { + const is_dependency_dynamic = name => { if (name === '$$scope') return true; if (this.node.scope.is_let(name)) return true; const variable = renderer.component.var_lookup.get(name); return is_dynamic(variable); - }); + }; - const fallback_dynamic_dependencies = this.fallback ? Array.from(this.fallback.dependencies).filter(name => { - if (name === '$$scope') return true; - if (this.node.scope.is_let(name)) return true; - const variable = renderer.component.var_lookup.get(name); - return is_dynamic(variable); - }) : []; + const dynamic_dependencies = Array.from(this.dependencies).filter(is_dependency_dynamic); + + const fallback_dynamic_dependencies = this.fallback + ? Array.from(this.fallback.dependencies).filter(is_dependency_dynamic) + : []; const slot_update = b` if (${slot}.p && ${renderer.dirty(dynamic_dependencies)}) { diff --git a/test/runtime/samples/component-slot-fallback-2/Inner.svelte b/test/runtime/samples/component-slot-fallback-2/Inner.svelte index 4c421969b607..d1c247ad35b0 100644 --- a/test/runtime/samples/component-slot-fallback-2/Inner.svelte +++ b/test/runtime/samples/component-slot-fallback-2/Inner.svelte @@ -1,6 +1,6 @@ diff --git a/test/runtime/samples/component-slot-fallback-2/Outer.svelte b/test/runtime/samples/component-slot-fallback-2/Outer.svelte index 206a4295d94e..1e44ba4db722 100644 --- a/test/runtime/samples/component-slot-fallback-2/Outer.svelte +++ b/test/runtime/samples/component-slot-fallback-2/Outer.svelte @@ -1,7 +1,7 @@ \ No newline at end of file diff --git a/test/runtime/samples/component-slot-fallback-2/store.svelte b/test/runtime/samples/component-slot-fallback-2/store.svelte index 86e1d6bdfd6b..e377aaf3149c 100644 --- a/test/runtime/samples/component-slot-fallback-2/store.svelte +++ b/test/runtime/samples/component-slot-fallback-2/store.svelte @@ -1,16 +1,16 @@ \ No newline at end of file From e61a907b0117ba3f3a360d46e463e38d2f71a355 Mon Sep 17 00:00:00 2001 From: Conduitry Date: Sun, 15 Mar 2020 05:38:05 -0400 Subject: [PATCH 3/3] update changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0fe0fa338118..11648dbcd801 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ * Allow `` to be used in a slot ([#2798](https://github.com/sveltejs/svelte/issues/2798)) * Expose object of unknown props in `$$restProps` ([#2930](https://github.com/sveltejs/svelte/issues/2930)) * Allow transitions and animations to work within iframes ([#3624](https://github.com/sveltejs/svelte/issues/3624)) +* Fix initialising slot fallbacks when unnecessary ([#3763](https://github.com/sveltejs/svelte/issues/3763)) * Disallow binding directly to `const` variables ([#4479](https://github.com/sveltejs/svelte/issues/4479)) * Fix updating keyed `{#each}` blocks with `{:else}` ([#4536](https://github.com/sveltejs/svelte/issues/4536), [#4549](https://github.com/sveltejs/svelte/issues/4549)) * Fix hydration of top-level content ([#4542](https://github.com/sveltejs/svelte/issues/4542))