Skip to content

Commit 82dce0c

Browse files
authoredMar 15, 2020
do not initialise slot fallback fragment unless necessary (#4514)
1 parent 0cde17a commit 82dce0c

File tree

8 files changed

+167
-45
lines changed

8 files changed

+167
-45
lines changed
 

‎CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
* Allow `<svelte:self>` to be used in a slot ([#2798](https://github.com/sveltejs/svelte/issues/2798))
66
* Expose object of unknown props in `$$restProps` ([#2930](https://github.com/sveltejs/svelte/issues/2930))
77
* Allow transitions and animations to work within iframes ([#3624](https://github.com/sveltejs/svelte/issues/3624))
8+
* Fix initialising slot fallbacks when unnecessary ([#3763](https://github.com/sveltejs/svelte/issues/3763))
89
* Disallow binding directly to `const` variables ([#4479](https://github.com/sveltejs/svelte/issues/4479))
910
* Fix updating keyed `{#each}` blocks with `{:else}` ([#4536](https://github.com/sveltejs/svelte/issues/4536), [#4549](https://github.com/sveltejs/svelte/issues/4549))
1011
* Fix hydration of top-level content ([#4542](https://github.com/sveltejs/svelte/issues/4542))

‎src/compiler/compile/render_dom/wrappers/Slot.ts

+57-43
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,12 @@ import get_slot_data from '../../utils/get_slot_data';
1010
import Expression from '../../nodes/shared/Expression';
1111
import is_dynamic from './shared/is_dynamic';
1212
import { Identifier, ObjectExpression } from 'estree';
13+
import create_debugging_comment from './shared/create_debugging_comment';
1314

1415
export default class SlotWrapper extends Wrapper {
1516
node: Slot;
1617
fragment: FragmentWrapper;
18+
fallback: Block | null = null;
1719

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

35+
if (this.node.children.length) {
36+
this.fallback = block.child({
37+
comment: create_debugging_comment(this.node.children[0], this.renderer.component),
38+
name: this.renderer.component.get_unique_name(`fallback_block`),
39+
type: 'fallback'
40+
});
41+
}
42+
3343
this.fragment = new FragmentWrapper(
3444
renderer,
35-
block,
45+
this.fallback,
3646
node.children,
3747
parent,
3848
strip_whitespace,
@@ -103,86 +113,90 @@ export default class SlotWrapper extends Wrapper {
103113
get_slot_context_fn = 'null';
104114
}
105115

116+
if (this.fallback) {
117+
this.fragment.render(this.fallback, null, x`#nodes` as Identifier);
118+
renderer.blocks.push(this.fallback);
119+
}
120+
106121
const slot = block.get_unique_name(`${sanitize(slot_name)}_slot`);
107122
const slot_definition = block.get_unique_name(`${sanitize(slot_name)}_slot_template`);
123+
const slot_or_fallback = this.fallback ? block.get_unique_name(`${sanitize(slot_name)}_slot_or_fallback`) : slot;
108124

109125
block.chunks.init.push(b`
110126
const ${slot_definition} = ${renderer.reference('$$slots')}.${slot_name};
111127
const ${slot} = @create_slot(${slot_definition}, #ctx, ${renderer.reference('$$scope')}, ${get_slot_context_fn});
128+
${this.fallback ? b`const ${slot_or_fallback} = ${slot} || ${this.fallback.name}(#ctx);` : null}
112129
`);
113130

114-
// TODO this is a dreadful hack! Should probably make this nicer
115-
const { create, claim, hydrate, mount, update, destroy } = block.chunks;
116-
117-
block.chunks.create = [];
118-
block.chunks.claim = [];
119-
block.chunks.hydrate = [];
120-
block.chunks.mount = [];
121-
block.chunks.update = [];
122-
block.chunks.destroy = [];
123-
124-
const listeners = block.event_listeners;
125-
block.event_listeners = [];
126-
this.fragment.render(block, parent_node, parent_nodes);
127-
block.render_listeners(`_${slot.name}`);
128-
block.event_listeners = listeners;
129-
130-
if (block.chunks.create.length) create.push(b`if (!${slot}) { ${block.chunks.create} }`);
131-
if (block.chunks.claim.length) claim.push(b`if (!${slot}) { ${block.chunks.claim} }`);
132-
if (block.chunks.hydrate.length) hydrate.push(b`if (!${slot}) { ${block.chunks.hydrate} }`);
133-
if (block.chunks.mount.length) mount.push(b`if (!${slot}) { ${block.chunks.mount} }`);
134-
if (block.chunks.update.length) update.push(b`if (!${slot}) { ${block.chunks.update} }`);
135-
if (block.chunks.destroy.length) destroy.push(b`if (!${slot}) { ${block.chunks.destroy} }`);
136-
137-
block.chunks.create = create;
138-
block.chunks.claim = claim;
139-
block.chunks.hydrate = hydrate;
140-
block.chunks.mount = mount;
141-
block.chunks.update = update;
142-
block.chunks.destroy = destroy;
143-
144131
block.chunks.create.push(
145-
b`if (${slot}) ${slot}.c();`
132+
b`if (${slot_or_fallback}) ${slot_or_fallback}.c();`
146133
);
147134

148135
if (renderer.options.hydratable) {
149136
block.chunks.claim.push(
150-
b`if (${slot}) ${slot}.l(${parent_nodes});`
137+
b`if (${slot_or_fallback}) ${slot_or_fallback}.l(${parent_nodes});`
151138
);
152139
}
153140

154141
block.chunks.mount.push(b`
155-
if (${slot}) {
156-
${slot}.m(${parent_node || '#target'}, ${parent_node ? 'null' : 'anchor'});
142+
if (${slot_or_fallback}) {
143+
${slot_or_fallback}.m(${parent_node || '#target'}, ${parent_node ? 'null' : 'anchor'});
157144
}
158145
`);
159146

160147
block.chunks.intro.push(
161-
b`@transition_in(${slot}, #local);`
148+
b`@transition_in(${slot_or_fallback}, #local);`
162149
);
163150

164151
block.chunks.outro.push(
165-
b`@transition_out(${slot}, #local);`
152+
b`@transition_out(${slot_or_fallback}, #local);`
166153
);
167154

168-
const dynamic_dependencies = Array.from(this.dependencies).filter(name => {
155+
const is_dependency_dynamic = name => {
169156
if (name === '$$scope') return true;
170157
if (this.node.scope.is_let(name)) return true;
171158
const variable = renderer.component.var_lookup.get(name);
172159
return is_dynamic(variable);
173-
});
160+
};
161+
162+
const dynamic_dependencies = Array.from(this.dependencies).filter(is_dependency_dynamic);
163+
164+
const fallback_dynamic_dependencies = this.fallback
165+
? Array.from(this.fallback.dependencies).filter(is_dependency_dynamic)
166+
: [];
174167

175-
block.chunks.update.push(b`
176-
if (${slot} && ${slot}.p && ${renderer.dirty(dynamic_dependencies)}) {
168+
const slot_update = b`
169+
if (${slot}.p && ${renderer.dirty(dynamic_dependencies)}) {
177170
${slot}.p(
178171
@get_slot_context(${slot_definition}, #ctx, ${renderer.reference('$$scope')}, ${get_slot_context_fn}),
179172
@get_slot_changes(${slot_definition}, ${renderer.reference('$$scope')}, #dirty, ${get_slot_changes_fn})
180173
);
181174
}
182-
`);
175+
`;
176+
const fallback_update = this.fallback && fallback_dynamic_dependencies.length > 0 && b`
177+
if (${slot_or_fallback} && ${slot_or_fallback}.p && ${renderer.dirty(fallback_dynamic_dependencies)}) {
178+
${slot_or_fallback}.p(#ctx, #dirty);
179+
}
180+
`;
181+
182+
if (fallback_update) {
183+
block.chunks.update.push(b`
184+
if (${slot}) {
185+
${slot_update}
186+
} else {
187+
${fallback_update}
188+
}
189+
`);
190+
} else {
191+
block.chunks.update.push(b`
192+
if (${slot}) {
193+
${slot_update}
194+
}
195+
`);
196+
}
183197

184198
block.chunks.destroy.push(
185-
b`if (${slot}) ${slot}.d(detaching);`
199+
b`if (${slot_or_fallback}) ${slot_or_fallback}.d(detaching);`
186200
);
187201
}
188202
}

‎src/compiler/compile/render_dom/wrappers/shared/create_debugging_comment.ts

+10-2
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,16 @@ export default function create_debugging_comment(
1616
let d;
1717

1818
if (node.type === 'InlineComponent' || node.type === 'Element') {
19-
d = node.children.length ? node.children[0].start : node.start;
20-
while (source[d - 1] !== '>') d -= 1;
19+
if (node.children.length) {
20+
d = node.children[0].start;
21+
while (source[d - 1] !== '>') d -= 1;
22+
} else {
23+
d = node.start;
24+
while (source[d] !== '>') d += 1;
25+
d += 1;
26+
}
27+
} else if (node.type === 'Text') {
28+
d = node.end;
2129
} else {
2230
// @ts-ignore
2331
d = node.expression ? node.expression.node.end : c;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
<script>
2+
import { model } from "./store.svelte";
3+
export let value = '';
4+
</script>
5+
6+
<input bind:value={$model} />
7+
{value}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
<script>
2+
import Inner from "./Inner.svelte";
3+
export let defaultValue = '';
4+
export let slotProps = '';
5+
</script>
6+
7+
<slot {slotProps}><Inner value={defaultValue} /></slot>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
export default {
2+
html: `<input> <input> <input>`,
3+
ssrHtml: `<input value="Blub"> <input value="Blub"> <input value="Blub">`,
4+
5+
async test({ assert, target, component, window }) {
6+
const [input1, input2, inputFallback] = target.querySelectorAll("input");
7+
8+
assert.equal(component.getSubscriberCount(), 3);
9+
10+
input1.value = "a";
11+
await input1.dispatchEvent(new window.Event("input"));
12+
input1.value = "ab";
13+
await input1.dispatchEvent(new window.Event("input"));
14+
assert.equal(input1.value, "ab");
15+
assert.equal(input2.value, "ab");
16+
assert.equal(inputFallback.value, "ab");
17+
18+
component.props = "hello";
19+
20+
assert.htmlEqual(
21+
target.innerHTML,
22+
`
23+
<input> hello
24+
<input> hello
25+
<input>
26+
`
27+
);
28+
29+
component.fallback = "world";
30+
assert.htmlEqual(
31+
target.innerHTML,
32+
`
33+
<input> hello
34+
<input> hello
35+
<input> world
36+
`
37+
);
38+
}
39+
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
<script>
2+
import Outer from "./Outer.svelte";
3+
import Inner from "./Inner.svelte";
4+
import {model} from "./store.svelte";
5+
6+
export let props = '';
7+
export let fallback = '';
8+
9+
export function getSubscriberCount() {
10+
return model.getCount();
11+
}
12+
</script>
13+
14+
<Outer slotProps={props} defaultValue={fallback} let:slotProps>
15+
<Inner value={slotProps} />
16+
</Outer>
17+
18+
<Outer slotProps={props} defaultValue={fallback} let:slotProps>
19+
<Inner value={slotProps} />
20+
</Outer>
21+
22+
<Outer slotProps={props} defaultValue={fallback}>
23+
</Outer>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
<script context="module">
2+
let value = 'Blub';
3+
let count = 0;
4+
const subscribers = new Set();
5+
export const model = {
6+
subscribe(fn) {
7+
subscribers.add(fn);
8+
count ++;
9+
fn(value);
10+
return () => {
11+
count--;
12+
subscribers.delete(fn);
13+
};
14+
},
15+
set(v) {
16+
value = v;
17+
subscribers.forEach(fn => fn(v));
18+
},
19+
getCount() {
20+
return count;
21+
}
22+
};
23+
</script>

0 commit comments

Comments
 (0)