From 82dce0c8fcc2d23cc0911ff9d9152ff11a2136a6 Mon Sep 17 00:00:00 2001 From: Tan Li Hau Date: Sun, 15 Mar 2020 17:44:05 +0800 Subject: [PATCH] do not initialise slot fallback fragment unless necessary (#4514) --- CHANGELOG.md | 1 + .../compile/render_dom/wrappers/Slot.ts | 100 ++++++++++-------- .../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 ++++ 8 files changed, 167 insertions(+), 45 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/CHANGELOG.md b/CHANGELOG.md index 0fe0fa3381..11648dbcd8 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)) diff --git a/src/compiler/compile/render_dom/wrappers/Slot.ts b/src/compiler/compile/render_dom/wrappers/Slot.ts index 5f2d331839..37b10c2ea3 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 = null; var: Identifier = { type: 'Identifier', name: 'slot' }; dependencies: Set = new Set(['$$scope']); @@ -30,9 +32,17 @@ export default class SlotWrapper extends Wrapper { this.cannot_use_innerhtml(); this.not_static_content(); + 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,86 +113,90 @@ 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 => { + 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 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) + : []; - block.chunks.update.push(b` - if (${slot} && ${slot}.p && ${renderer.dirty(dynamic_dependencies)}) { + 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 e2aa0c0b41..11f1feb841 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 0000000000..d1c247ad35 --- /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 0000000000..1e44ba4db7 --- /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 0000000000..585e3b4c9e --- /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 0000000000..4583667015 --- /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 0000000000..e377aaf314 --- /dev/null +++ b/test/runtime/samples/component-slot-fallback-2/store.svelte @@ -0,0 +1,23 @@ + \ No newline at end of file