diff --git a/CHANGELOG.md b/CHANGELOG.md index b0672d790ea4..f39308e5ffa3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ ## Unreleased +* In `dev` mode, display a runtime warning when a component is passed an unexpected slot ([#1020](https://github.com/sveltejs/svelte/issues/1020), [#1447](https://github.com/sveltejs/svelte/issues/1447)) * In `vars` array, correctly indicate whether `module` variables are `mutated` or `reassigned` ([#3215](https://github.com/sveltejs/svelte/issues/3215)) * Fix spread props not updating in certain situations ([#3521](https://github.com/sveltejs/svelte/issues/3521), [#4480](https://github.com/sveltejs/svelte/issues/4480)) * Use the fallback content for slots if they are passed only whitespace ([#4092](https://github.com/sveltejs/svelte/issues/4092)) diff --git a/src/compiler/compile/render_dom/Block.ts b/src/compiler/compile/render_dom/Block.ts index 62bdc5bdd9af..2da77d3fbf52 100644 --- a/src/compiler/compile/render_dom/Block.ts +++ b/src/compiler/compile/render_dom/Block.ts @@ -412,8 +412,7 @@ export default class Block { } has_content() { - return this.renderer.options.dev || - this.first || + return this.first || this.event_listeners.length > 0 || this.chunks.intro.length > 0 || this.chunks.outro.length > 0 || diff --git a/src/compiler/compile/render_dom/index.ts b/src/compiler/compile/render_dom/index.ts index d6da6142768c..fc3c94a2be19 100644 --- a/src/compiler/compile/render_dom/index.ts +++ b/src/compiler/compile/render_dom/index.ts @@ -264,7 +264,7 @@ export default function dom( args.push(x`$$props`); } - const has_create_fragment = block.has_content(); + const has_create_fragment = component.compile_options.dev || block.has_content(); if (has_create_fragment) { body.push(b` function create_fragment(#ctx) { @@ -412,7 +412,8 @@ export default function dom( ${unknown_props_check} - ${component.slots.size ? b`let { $$slots = {}, $$scope } = $$props;` : null} + ${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.length > 0 && b`const $$binding_groups = [${renderer.binding_groups.map(_ => x`[]`)}];`} diff --git a/src/runtime/internal/dev.ts b/src/runtime/internal/dev.ts index 8aefc88ba7b0..751f1f802bc0 100644 --- a/src/runtime/internal/dev.ts +++ b/src/runtime/internal/dev.ts @@ -89,6 +89,13 @@ export function validate_each_argument(arg) { } } +export function validate_slots(name, slot, keys) { + for (const slot_key of Object.keys(slot)) { + if (!~keys.indexOf(slot_key)) { + console.warn(`<${name}> received an unexpected slot "${slot_key}".`); + } + } +} type Props = Record; export interface SvelteComponentDev { diff --git a/test/js/samples/capture-inject-state/expected.js b/test/js/samples/capture-inject-state/expected.js index 859314556580..cd719ac5d25f 100644 --- a/test/js/samples/capture-inject-state/expected.js +++ b/test/js/samples/capture-inject-state/expected.js @@ -14,6 +14,7 @@ import { space, subscribe, text, + validate_slots, validate_store } from "svelte/internal"; @@ -114,6 +115,9 @@ 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 87d78bd69821..dd142adb26b2 100644 --- a/test/js/samples/debug-empty/expected.js +++ b/test/js/samples/debug-empty/expected.js @@ -12,7 +12,8 @@ import { safe_not_equal, set_data_dev, space, - text + text, + validate_slots } from "svelte/internal"; const file = undefined; @@ -75,6 +76,9 @@ 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 589c4a783288..977702b99f5c 100644 --- a/test/js/samples/debug-foo-bar-baz-things/expected.js +++ b/test/js/samples/debug-foo-bar-baz-things/expected.js @@ -14,7 +14,8 @@ import { set_data_dev, space, text, - validate_each_argument + validate_each_argument, + validate_slots } from "svelte/internal"; const file = undefined; @@ -179,6 +180,9 @@ 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 10129e1b2837..fe62ff77bfb1 100644 --- a/test/js/samples/debug-foo/expected.js +++ b/test/js/samples/debug-foo/expected.js @@ -14,7 +14,8 @@ import { set_data_dev, space, text, - validate_each_argument + validate_each_argument, + validate_slots } from "svelte/internal"; const file = undefined; @@ -171,6 +172,9 @@ 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 190ff12c50ee..0e634297f08f 100644 --- a/test/js/samples/debug-hoisted/expected.js +++ b/test/js/samples/debug-hoisted/expected.js @@ -4,7 +4,8 @@ import { dispatch_dev, init, noop, - safe_not_equal + safe_not_equal, + validate_slots } from "svelte/internal"; const file = undefined; @@ -56,6 +57,8 @@ 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 a097869e747b..76068e8cf464 100644 --- a/test/js/samples/debug-no-dependencies/expected.js +++ b/test/js/samples/debug-no-dependencies/expected.js @@ -11,7 +11,8 @@ import { safe_not_equal, space, text, - validate_each_argument + validate_each_argument, + validate_slots } from "svelte/internal"; const file = undefined; @@ -141,6 +142,8 @@ function instance($$self, $$props) { 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/dev-warning-missing-data-computed/expected.js b/test/js/samples/dev-warning-missing-data-computed/expected.js index 9c28e0406450..0a50e2cd970a 100644 --- a/test/js/samples/dev-warning-missing-data-computed/expected.js +++ b/test/js/samples/dev-warning-missing-data-computed/expected.js @@ -12,7 +12,8 @@ import { safe_not_equal, set_data_dev, space, - text + text, + validate_slots } from "svelte/internal"; const file = undefined; @@ -72,6 +73,9 @@ 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 f433bd61a962..c52d9df437e5 100644 --- a/test/js/samples/loop-protect/expected.js +++ b/test/js/samples/loop-protect/expected.js @@ -11,7 +11,8 @@ import { insert_dev, loop_guard, noop, - safe_not_equal + safe_not_equal, + validate_slots } from "svelte/internal"; const { console: console_1 } = globals; @@ -110,6 +111,9 @@ 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"](() => { $$invalidate(0, node = $$value); @@ -161,4 +165,4 @@ class Component extends SvelteComponentDev { } } -export default Component; +export default Component; \ No newline at end of file diff --git a/test/runtime/samples/component-slot-warning/Nested.svelte b/test/runtime/samples/component-slot-warning/Nested.svelte new file mode 100644 index 000000000000..c6f086d96c19 --- /dev/null +++ b/test/runtime/samples/component-slot-warning/Nested.svelte @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/test/runtime/samples/component-slot-warning/_config.js b/test/runtime/samples/component-slot-warning/_config.js new file mode 100644 index 000000000000..6ffe624782e0 --- /dev/null +++ b/test/runtime/samples/component-slot-warning/_config.js @@ -0,0 +1,9 @@ +export default { + compileOptions: { + dev: true + }, + warnings: [ + ' received an unexpected slot "default".', + ' received an unexpected slot "slot1".' + ] +}; diff --git a/test/runtime/samples/component-slot-warning/main.svelte b/test/runtime/samples/component-slot-warning/main.svelte new file mode 100644 index 000000000000..c29ef3e85be2 --- /dev/null +++ b/test/runtime/samples/component-slot-warning/main.svelte @@ -0,0 +1,7 @@ + + + + +