Skip to content

Commit

Permalink
do not initialise slot fallback fragment unless necessary
Browse files Browse the repository at this point in the history
  • Loading branch information
tanhauhau committed Mar 6, 2020
1 parent fd378f2 commit 5f52146
Show file tree
Hide file tree
Showing 6 changed files with 155 additions and 41 deletions.
97 changes: 56 additions & 41 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;

var: Identifier = { type: 'Identifier', name: 'slot' };
dependencies: Set<string> = new Set(['$$scope']);
Expand All @@ -30,9 +32,15 @@ export default class SlotWrapper extends Wrapper {
this.cannot_use_innerhtml();
this.not_static_content();

this.fallback = block.child({
comment: create_debugging_comment(this.node, 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,66 +111,45 @@ export default class SlotWrapper extends Wrapper {
get_slot_context_fn = 'null';
}

this.fragment.render(this.fallback, parent_node, parent_nodes);

const has_fallback = this.fallback.has_content();
if (has_fallback) {
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 = has_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});
${has_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 => {
Expand All @@ -172,17 +159,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 = 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 = has_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);`
);
}
}
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;
let 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 5f52146

Please sign in to comment.