do not initialise slot fallback fragment unless necessary (#4514)

pull/4564/head
Tan Li Hau 5 years ago committed by GitHub
parent 0cde17a4ad
commit 82dce0c8fc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -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))

@ -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']);
@ -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);`
);
}
}

@ -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;

@ -0,0 +1,7 @@
<script>
import { model } from "./store.svelte";
export let value = '';
</script>
<input bind:value={$model} />
{value}

@ -0,0 +1,7 @@
<script>
import Inner from "./Inner.svelte";
export let defaultValue = '';
export let slotProps = '';
</script>
<slot {slotProps}><Inner value={defaultValue} /></slot>

@ -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
`
);
}
};

@ -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>

@ -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>
Loading…
Cancel
Save