Skip to content

Commit

Permalink
do not initialise slot fallback fragment unless necessary (#4514)
Browse files Browse the repository at this point in the history
  • Loading branch information
tanhauhau authored Mar 15, 2020
1 parent 0cde17a commit 82dce0c
Show file tree
Hide file tree
Showing 8 changed files with 167 additions and 45 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
* Allow `<svelte:self>` 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))
Expand Down
100 changes: 57 additions & 43 deletions src/compiler/compile/render_dom/wrappers/Slot.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<string> = new Set(['$$scope']);
Expand All @@ -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,
Expand Down Expand Up @@ -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);`
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
7 changes: 7 additions & 0 deletions test/runtime/samples/component-slot-fallback-2/Inner.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<script>
import { model } from "./store.svelte";
export let value = '';
</script>

<input bind:value={$model} />
{value}
7 changes: 7 additions & 0 deletions test/runtime/samples/component-slot-fallback-2/Outer.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<script>
import Inner from "./Inner.svelte";
export let defaultValue = '';
export let slotProps = '';
</script>

<slot {slotProps}><Inner value={defaultValue} /></slot>
39 changes: 39 additions & 0 deletions test/runtime/samples/component-slot-fallback-2/_config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
export default {
html: `<input> <input> <input>`,
ssrHtml: `<input value="Blub"> <input value="Blub"> <input value="Blub">`,

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,
`
<input> hello
<input> hello
<input>
`
);

component.fallback = "world";
assert.htmlEqual(
target.innerHTML,
`
<input> hello
<input> hello
<input> world
`
);
}
};
23 changes: 23 additions & 0 deletions test/runtime/samples/component-slot-fallback-2/main.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<script>
import Outer from "./Outer.svelte";
import Inner from "./Inner.svelte";
import {model} from "./store.svelte";
export let props = '';
export let fallback = '';
export function getSubscriberCount() {
return model.getCount();
}
</script>

<Outer slotProps={props} defaultValue={fallback} let:slotProps>
<Inner value={slotProps} />
</Outer>

<Outer slotProps={props} defaultValue={fallback} let:slotProps>
<Inner value={slotProps} />
</Outer>

<Outer slotProps={props} defaultValue={fallback}>
</Outer>
23 changes: 23 additions & 0 deletions test/runtime/samples/component-slot-fallback-2/store.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<script context="module">
let value = 'Blub';
let count = 0;
const subscribers = new Set();
export const model = {
subscribe(fn) {
subscribers.add(fn);
count ++;
fn(value);
return () => {
count--;
subscribers.delete(fn);
};
},
set(v) {
value = v;
subscribers.forEach(fn => fn(v));
},
getCount() {
return count;
}
};
</script>

0 comments on commit 82dce0c

Please sign in to comment.