From e36c0b000c0005c214c42ed9e6213e6ff781964e Mon Sep 17 00:00:00 2001 From: Tan Li Hau Date: Thu, 26 Mar 2020 13:59:11 +0800 Subject: [PATCH 1/3] feat provide $$slots --- src/compiler/compile/render_dom/Renderer.ts | 2 +- src/compiler/compile/render_dom/index.ts | 16 ++++++++-- .../compile/render_dom/wrappers/Slot.ts | 2 +- .../compile/render_ssr/handlers/Slot.ts | 4 +-- src/compiler/compile/render_ssr/index.ts | 6 +++- .../compile/utils/reserved_keywords.ts | 2 +- src/runtime/internal/utils.ts | 8 +++++ test/runtime/samples/$$slot/A.svelte | 31 +++++++++++++++++++ test/runtime/samples/$$slot/_config.js | 18 +++++++++++ test/runtime/samples/$$slot/main.svelte | 23 ++++++++++++++ 10 files changed, 103 insertions(+), 9 deletions(-) create mode 100644 test/runtime/samples/$$slot/A.svelte create mode 100644 test/runtime/samples/$$slot/_config.js create mode 100644 test/runtime/samples/$$slot/main.svelte diff --git a/src/compiler/compile/render_dom/Renderer.ts b/src/compiler/compile/render_dom/Renderer.ts index c02d646ebf74..5092626d25f5 100644 --- a/src/compiler/compile/render_dom/Renderer.ts +++ b/src/compiler/compile/render_dom/Renderer.ts @@ -60,7 +60,7 @@ export default class Renderer { if (component.slots.size > 0) { this.add_to_context('$$scope'); - this.add_to_context('$$slots'); + this.add_to_context('#slots'); } if (this.binding_groups.size > 0) { diff --git a/src/compiler/compile/render_dom/index.ts b/src/compiler/compile/render_dom/index.ts index 7d0dce831536..4ee9abae49b3 100644 --- a/src/compiler/compile/render_dom/index.ts +++ b/src/compiler/compile/render_dom/index.ts @@ -70,6 +70,15 @@ export default function dom( ); } + const uses_slots = component.var_lookup.has('$$slots'); + let compute_slots; + if (uses_slots) { + compute_slots = b` + const $$slots = @compute_slots(#slots); + `; + } + + const uses_props = component.var_lookup.has('$$props'); const uses_rest = component.var_lookup.has('$$restProps'); const $$props = uses_props || uses_rest ? `$$new_props` : `$$props`; @@ -409,13 +418,14 @@ export default function dom( ${resubscribable_reactive_store_unsubscribers} + ${component.slots.size || component.compile_options.dev || uses_slots ? b`let { $$slots: #slots = {}, $$scope } = $$props;` : null} + ${component.compile_options.dev && b`@validate_slots('${component.tag}', #slots, [${[...component.slots.keys()].map(key => `'${key}'`).join(',')}]);`} + ${compute_slots} + ${instance_javascript} ${unknown_props_check} - ${component.slots.size || component.compile_options.dev ? b`let { $$slots = {}, $$scope } = $$props;` : null} - ${component.compile_options.dev && b`@validate_slots('${component.tag}', $$slots, [${[...component.slots.keys()].map(key => `'${key}'`).join(',')}]);`} - ${renderer.binding_groups.size > 0 && b`const $$binding_groups = [${[...renderer.binding_groups.keys()].map(_ => x`[]`)}];`} ${component.partly_hoisted} diff --git a/src/compiler/compile/render_dom/wrappers/Slot.ts b/src/compiler/compile/render_dom/wrappers/Slot.ts index bf808196c0b1..5c02e78158c5 100644 --- a/src/compiler/compile/render_dom/wrappers/Slot.ts +++ b/src/compiler/compile/render_dom/wrappers/Slot.ts @@ -125,7 +125,7 @@ export default class SlotWrapper extends Wrapper { 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_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} `); diff --git a/src/compiler/compile/render_ssr/handlers/Slot.ts b/src/compiler/compile/render_ssr/handlers/Slot.ts index 3b1e199c75ed..32b6246e6445 100644 --- a/src/compiler/compile/render_ssr/handlers/Slot.ts +++ b/src/compiler/compile/render_ssr/handlers/Slot.ts @@ -11,8 +11,8 @@ export default function(node: Slot, renderer: Renderer, options: RenderOptions) const result = renderer.pop(); renderer.add_expression(x` - $$slots.${node.slot_name} - ? $$slots.${node.slot_name}(${slot_data}) + #slots.${node.slot_name} + ? #slots.${node.slot_name}(${slot_data}) : ${result} `); } diff --git a/src/compiler/compile/render_ssr/index.ts b/src/compiler/compile/render_ssr/index.ts index a72b8b35aec1..ff45abd78b05 100644 --- a/src/compiler/compile/render_ssr/index.ts +++ b/src/compiler/compile/render_ssr/index.ts @@ -34,6 +34,9 @@ export default function ssr( const props = component.vars.filter(variable => !variable.module && variable.export_name); const rest = uses_rest ? b`let $$restProps = @compute_rest_props($$props, [${props.map(prop => `"${prop.export_name}"`).join(',')}]);` : null; + const uses_slots = component.var_lookup.has('$$slots'); + const slots = uses_slots ? b`let $$slots = @compute_slots(#slots);` : null; + const reactive_stores = component.vars.filter(variable => variable.name[0] === '$' && variable.name[1] !== '$'); const reactive_store_values = reactive_stores .map(({ name }) => { @@ -118,6 +121,7 @@ export default function ssr( const blocks = [ rest, + slots, ...reactive_stores.map(({ name }) => { const store_name = name.slice(1); const store = component.var_lookup.get(store_name); @@ -144,7 +148,7 @@ export default function ssr( ${component.fully_hoisted} - const ${name} = @create_ssr_component(($$result, $$props, $$bindings, $$slots) => { + const ${name} = @create_ssr_component(($$result, $$props, $$bindings, #slots) => { ${blocks} }); `; diff --git a/src/compiler/compile/utils/reserved_keywords.ts b/src/compiler/compile/utils/reserved_keywords.ts index 75825c17193b..d6eb8b96739d 100644 --- a/src/compiler/compile/utils/reserved_keywords.ts +++ b/src/compiler/compile/utils/reserved_keywords.ts @@ -1,4 +1,4 @@ -export const reserved_keywords = new Set(["$$props", "$$restProps"]); +export const reserved_keywords = new Set(["$$props", "$$restProps", "$$slots"]); export function is_reserved_keyword(name) { return reserved_keywords.has(name); diff --git a/src/runtime/internal/utils.ts b/src/runtime/internal/utils.ts index 3fd0a2b70166..633b6f054cdf 100644 --- a/src/runtime/internal/utils.ts +++ b/src/runtime/internal/utils.ts @@ -128,6 +128,14 @@ export function compute_rest_props(props, keys) { return rest; } +export function compute_slots(slots) { + const result = {}; + for (const key in slots) { + result[key] = true; + } + return result; +} + export function once(fn) { let ran = false; return function(this: any, ...args) { diff --git a/test/runtime/samples/$$slot/A.svelte b/test/runtime/samples/$$slot/A.svelte new file mode 100644 index 000000000000..ffa166166ca9 --- /dev/null +++ b/test/runtime/samples/$$slot/A.svelte @@ -0,0 +1,31 @@ + + + + + +$$slots: {toString($$slots)} + +{#if $$slots.b} +
+ +
+{:else} + Slot b is not available +{/if} \ No newline at end of file diff --git a/test/runtime/samples/$$slot/_config.js b/test/runtime/samples/$$slot/_config.js new file mode 100644 index 000000000000..13b2137cdbc5 --- /dev/null +++ b/test/runtime/samples/$$slot/_config.js @@ -0,0 +1,18 @@ +export default { + html: ` + byeworld + hello world + $$slots: {"a":true,"default":true} + Slot b is not available + + bye world + hello world + $$slots: {"a":true,"b":true,"default":true} +
hello world
+ `, + + async test({ assert, target, component }) { + assert.equal(component.getA(), ''); + assert.equal(component.getB(), 'foo'); + } +}; diff --git a/test/runtime/samples/$$slot/main.svelte b/test/runtime/samples/$$slot/main.svelte new file mode 100644 index 000000000000..8b7efae5735a --- /dev/null +++ b/test/runtime/samples/$$slot/main.svelte @@ -0,0 +1,23 @@ + + + + hello world + bye + world + + + + hello world + hello world + bye world + From 517acc5df63a44f14cc8425e3f4ea154935c5f86 Mon Sep 17 00:00:00 2001 From: Tan Li Hau Date: Thu, 26 Mar 2020 14:09:49 +0800 Subject: [PATCH 2/3] fix snapshot tests --- test/js/samples/capture-inject-state/expected.js | 5 ++--- test/js/samples/debug-empty/expected.js | 5 ++--- test/js/samples/debug-foo-bar-baz-things/expected.js | 5 ++--- test/js/samples/debug-foo/expected.js | 5 ++--- test/js/samples/debug-hoisted/expected.js | 4 ++-- test/js/samples/debug-no-dependencies/expected.js | 4 ++-- test/js/samples/debug-ssr-foo/expected.js | 2 +- .../js/samples/dev-warning-missing-data-computed/expected.js | 5 ++--- test/js/samples/loop-protect/expected.js | 5 ++--- test/js/samples/ssr-no-oncreate-etc/expected.js | 2 +- test/js/samples/ssr-preserve-comments/expected.js | 2 +- 11 files changed, 19 insertions(+), 25 deletions(-) diff --git a/test/js/samples/capture-inject-state/expected.js b/test/js/samples/capture-inject-state/expected.js index 6aa93b9c5aef..6a0351ae449e 100644 --- a/test/js/samples/capture-inject-state/expected.js +++ b/test/js/samples/capture-inject-state/expected.js @@ -103,6 +103,8 @@ function instance($$self, $$props, $$invalidate) { $$subscribe_prop = () => ($$unsubscribe_prop(), $$unsubscribe_prop = subscribe(prop, $$value => $$invalidate(2, $prop = $$value)), prop); $$self.$$.on_destroy.push(() => $$unsubscribe_prop()); + let { $$slots: slots = {}, $$scope } = $$props; + validate_slots("Component", slots, []); let { prop } = $$props; validate_store(prop, "prop"); $$subscribe_prop(); @@ -115,9 +117,6 @@ function instance($$self, $$props, $$invalidate) { if (!~writable_props.indexOf(key) && key.slice(0, 2) !== "$$") console.warn(` was created with unknown prop '${key}'`); }); - let { $$slots = {}, $$scope } = $$props; - validate_slots("Component", $$slots, []); - $$self.$$set = $$props => { if ("prop" in $$props) $$subscribe_prop($$invalidate(0, prop = $$props.prop)); if ("alias" in $$props) $$invalidate(1, realName = $$props.alias); diff --git a/test/js/samples/debug-empty/expected.js b/test/js/samples/debug-empty/expected.js index 6781e5333cd0..f427f1bf4581 100644 --- a/test/js/samples/debug-empty/expected.js +++ b/test/js/samples/debug-empty/expected.js @@ -69,6 +69,8 @@ function create_fragment(ctx) { } function instance($$self, $$props, $$invalidate) { + let { $$slots: slots = {}, $$scope } = $$props; + validate_slots("Component", slots, []); let { name } = $$props; const writable_props = ["name"]; @@ -76,9 +78,6 @@ function instance($$self, $$props, $$invalidate) { if (!~writable_props.indexOf(key) && key.slice(0, 2) !== "$$") console.warn(` was created with unknown prop '${key}'`); }); - let { $$slots = {}, $$scope } = $$props; - validate_slots("Component", $$slots, []); - $$self.$$set = $$props => { if ("name" in $$props) $$invalidate(0, name = $$props.name); }; diff --git a/test/js/samples/debug-foo-bar-baz-things/expected.js b/test/js/samples/debug-foo-bar-baz-things/expected.js index 087d2e399d3f..7439b3310b3a 100644 --- a/test/js/samples/debug-foo-bar-baz-things/expected.js +++ b/test/js/samples/debug-foo-bar-baz-things/expected.js @@ -170,6 +170,8 @@ function create_fragment(ctx) { } function instance($$self, $$props, $$invalidate) { + let { $$slots: slots = {}, $$scope } = $$props; + validate_slots("Component", slots, []); let { things } = $$props; let { foo } = $$props; let { bar } = $$props; @@ -180,9 +182,6 @@ function instance($$self, $$props, $$invalidate) { if (!~writable_props.indexOf(key) && key.slice(0, 2) !== "$$") console.warn(` was created with unknown prop '${key}'`); }); - let { $$slots = {}, $$scope } = $$props; - validate_slots("Component", $$slots, []); - $$self.$$set = $$props => { if ("things" in $$props) $$invalidate(0, things = $$props.things); if ("foo" in $$props) $$invalidate(1, foo = $$props.foo); diff --git a/test/js/samples/debug-foo/expected.js b/test/js/samples/debug-foo/expected.js index 9f12bfb807a7..d869a5cf9e84 100644 --- a/test/js/samples/debug-foo/expected.js +++ b/test/js/samples/debug-foo/expected.js @@ -164,6 +164,8 @@ function create_fragment(ctx) { } function instance($$self, $$props, $$invalidate) { + let { $$slots: slots = {}, $$scope } = $$props; + validate_slots("Component", slots, []); let { things } = $$props; let { foo } = $$props; const writable_props = ["things", "foo"]; @@ -172,9 +174,6 @@ function instance($$self, $$props, $$invalidate) { if (!~writable_props.indexOf(key) && key.slice(0, 2) !== "$$") console.warn(` was created with unknown prop '${key}'`); }); - let { $$slots = {}, $$scope } = $$props; - validate_slots("Component", $$slots, []); - $$self.$$set = $$props => { if ("things" in $$props) $$invalidate(0, things = $$props.things); if ("foo" in $$props) $$invalidate(1, foo = $$props.foo); diff --git a/test/js/samples/debug-hoisted/expected.js b/test/js/samples/debug-hoisted/expected.js index 0e634297f08f..c6257ac90d5a 100644 --- a/test/js/samples/debug-hoisted/expected.js +++ b/test/js/samples/debug-hoisted/expected.js @@ -49,6 +49,8 @@ function create_fragment(ctx) { } function instance($$self, $$props, $$invalidate) { + let { $$slots: slots = {}, $$scope } = $$props; + validate_slots("Component", slots, []); let obj = { x: 5 }; let kobzol = 5; const writable_props = []; @@ -57,8 +59,6 @@ function instance($$self, $$props, $$invalidate) { if (!~writable_props.indexOf(key) && key.slice(0, 2) !== "$$") console.warn(` was created with unknown prop '${key}'`); }); - let { $$slots = {}, $$scope } = $$props; - validate_slots("Component", $$slots, []); $$self.$capture_state = () => ({ obj, kobzol }); $$self.$inject_state = $$props => { diff --git a/test/js/samples/debug-no-dependencies/expected.js b/test/js/samples/debug-no-dependencies/expected.js index 76068e8cf464..4d8d05d3aa6e 100644 --- a/test/js/samples/debug-no-dependencies/expected.js +++ b/test/js/samples/debug-no-dependencies/expected.js @@ -136,14 +136,14 @@ function create_fragment(ctx) { } function instance($$self, $$props) { + let { $$slots: slots = {}, $$scope } = $$props; + validate_slots("Component", slots, []); const writable_props = []; Object.keys($$props).forEach(key => { if (!~writable_props.indexOf(key) && key.slice(0, 2) !== "$$") console.warn(` was created with unknown prop '${key}'`); }); - let { $$slots = {}, $$scope } = $$props; - validate_slots("Component", $$slots, []); return []; } diff --git a/test/js/samples/debug-ssr-foo/expected.js b/test/js/samples/debug-ssr-foo/expected.js index fe34a6a4fd42..69da37b2d921 100644 --- a/test/js/samples/debug-ssr-foo/expected.js +++ b/test/js/samples/debug-ssr-foo/expected.js @@ -1,7 +1,7 @@ /* generated by Svelte vX.Y.Z */ import { create_ssr_component, debug, each, escape } from "svelte/internal"; -const Component = create_ssr_component(($$result, $$props, $$bindings, $$slots) => { +const Component = create_ssr_component(($$result, $$props, $$bindings, slots) => { let { things } = $$props; let { foo } = $$props; if ($$props.things === void 0 && $$bindings.things && things !== void 0) $$bindings.things(things); diff --git a/test/js/samples/dev-warning-missing-data-computed/expected.js b/test/js/samples/dev-warning-missing-data-computed/expected.js index fd34778f8d25..8c7f8bb1cff8 100644 --- a/test/js/samples/dev-warning-missing-data-computed/expected.js +++ b/test/js/samples/dev-warning-missing-data-computed/expected.js @@ -65,6 +65,8 @@ function create_fragment(ctx) { } function instance($$self, $$props, $$invalidate) { + let { $$slots: slots = {}, $$scope } = $$props; + validate_slots("Component", slots, []); let { foo } = $$props; let bar; const writable_props = ["foo"]; @@ -73,9 +75,6 @@ function instance($$self, $$props, $$invalidate) { if (!~writable_props.indexOf(key) && key.slice(0, 2) !== "$$") console.warn(` was created with unknown prop '${key}'`); }); - let { $$slots = {}, $$scope } = $$props; - validate_slots("Component", $$slots, []); - $$self.$$set = $$props => { if ("foo" in $$props) $$invalidate(0, foo = $$props.foo); }; diff --git a/test/js/samples/loop-protect/expected.js b/test/js/samples/loop-protect/expected.js index 2ee7d90f17d1..1042b20823b8 100644 --- a/test/js/samples/loop-protect/expected.js +++ b/test/js/samples/loop-protect/expected.js @@ -67,6 +67,8 @@ function foo() { } function instance($$self, $$props, $$invalidate) { + let { $$slots: slots = {}, $$scope } = $$props; + validate_slots("Component", slots, []); let node; { @@ -111,9 +113,6 @@ function instance($$self, $$props, $$invalidate) { if (!~writable_props.indexOf(key) && key.slice(0, 2) !== "$$") console_1.warn(` was created with unknown prop '${key}'`); }); - let { $$slots = {}, $$scope } = $$props; - validate_slots("Component", $$slots, []); - function div_binding($$value) { binding_callbacks[$$value ? "unshift" : "push"](() => { node = $$value; diff --git a/test/js/samples/ssr-no-oncreate-etc/expected.js b/test/js/samples/ssr-no-oncreate-etc/expected.js index 803f06a8829d..b91deb3e9732 100644 --- a/test/js/samples/ssr-no-oncreate-etc/expected.js +++ b/test/js/samples/ssr-no-oncreate-etc/expected.js @@ -15,7 +15,7 @@ function swipe(node, callback) { } // TODO implement -const Component = create_ssr_component(($$result, $$props, $$bindings, $$slots) => { +const Component = create_ssr_component(($$result, $$props, $$bindings, slots) => { onMount(() => { console.log("onMount"); }); diff --git a/test/js/samples/ssr-preserve-comments/expected.js b/test/js/samples/ssr-preserve-comments/expected.js index 1dc12710c0bb..de9fa7582ed9 100644 --- a/test/js/samples/ssr-preserve-comments/expected.js +++ b/test/js/samples/ssr-preserve-comments/expected.js @@ -1,7 +1,7 @@ /* generated by Svelte vX.Y.Z */ import { create_ssr_component } from "svelte/internal"; -const Component = create_ssr_component(($$result, $$props, $$bindings, $$slots) => { +const Component = create_ssr_component(($$result, $$props, $$bindings, slots) => { return `
content
more content
`; From b990010c2c5c27f28baed00f79cbdb1c1284e8d3 Mon Sep 17 00:00:00 2001 From: Conduitry Date: Thu, 13 Aug 2020 10:14:18 -0400 Subject: [PATCH 3/3] update changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index bface6c86d3f..ceff78b10730 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ ## Unreleased +* Expose object of which slots have received content in `$$slots` ([#2106](https://github.com/sveltejs/svelte/issues/2106)) * Add types to `createEventDispatcher` ([#5211](https://github.com/sveltejs/svelte/issues/5211)) * In SSR mode, do not automatically declare variables for reactive assignments to member expressions ([#5247](https://github.com/sveltejs/svelte/issues/5247)) * Include selector in message of `unused-css-selector` warning ([#5252](https://github.com/sveltejs/svelte/issues/5252))