From f83f33403fb1b994ed4abe9170e905afed63f786 Mon Sep 17 00:00:00 2001 From: "S. Elliott Johnson" Date: Tue, 15 Apr 2025 00:24:10 -0600 Subject: [PATCH 1/7] feat: Throw on unrendered snippets in dev --- .../98-reference/.generated/shared-errors.md | 37 ++++++++++++++++ .../svelte/messages/shared-errors/errors.md | 35 +++++++++++++++ .../server/visitors/SnippetBlock.js | 17 ++++--- .../server/visitors/shared/component.js | 10 ++++- .../src/internal/client/dom/blocks/snippet.js | 9 +++- packages/svelte/src/internal/client/index.js | 3 +- packages/svelte/src/internal/server/index.js | 3 +- packages/svelte/src/internal/shared/errors.js | 15 +++++++ .../svelte/src/internal/shared/validate.js | 12 +++++ .../_config.js | 8 ++++ .../main.svelte | 5 +++ .../_config.js | 4 ++ .../main.svelte | 5 +++ .../_config.js | 3 ++ .../main.svelte | 5 +++ .../unrendered-children.svelte | 5 +++ .../_config.js | 8 ++++ .../main.svelte | 5 +++ .../unrendered-children.svelte | 5 +++ .../_expected/server/index.svelte.js | 4 +- .../_expected/server/index.svelte.js | 4 +- packages/svelte/types/index.d.ts | 44 ++++++++++++++++++- 22 files changed, 230 insertions(+), 16 deletions(-) create mode 100644 packages/svelte/tests/runtime-runes/samples/snippet-block-without-render-tag-dev/_config.js create mode 100644 packages/svelte/tests/runtime-runes/samples/snippet-block-without-render-tag-dev/main.svelte create mode 100644 packages/svelte/tests/runtime-runes/samples/snippet-block-without-render-tag-prod/_config.js create mode 100644 packages/svelte/tests/runtime-runes/samples/snippet-block-without-render-tag-prod/main.svelte create mode 100644 packages/svelte/tests/runtime-runes/samples/snippet-children-without-render-tag-dev-prod/_config.js create mode 100644 packages/svelte/tests/runtime-runes/samples/snippet-children-without-render-tag-dev-prod/main.svelte create mode 100644 packages/svelte/tests/runtime-runes/samples/snippet-children-without-render-tag-dev-prod/unrendered-children.svelte create mode 100644 packages/svelte/tests/runtime-runes/samples/snippet-children-without-render-tag-dev/_config.js create mode 100644 packages/svelte/tests/runtime-runes/samples/snippet-children-without-render-tag-dev/main.svelte create mode 100644 packages/svelte/tests/runtime-runes/samples/snippet-children-without-render-tag-dev/unrendered-children.svelte diff --git a/documentation/docs/98-reference/.generated/shared-errors.md b/documentation/docs/98-reference/.generated/shared-errors.md index 0102aafcbca1..92e1ad840cfc 100644 --- a/documentation/docs/98-reference/.generated/shared-errors.md +++ b/documentation/docs/98-reference/.generated/shared-errors.md @@ -54,6 +54,43 @@ Certain lifecycle methods can only be used during component initialisation. To f ``` +### snippet_without_render_tag + +``` +Attempted to render a snippet without a `{@render}` block. This would cause the snippet to be rendered directly to the DOM. To fix this, change `{snippet}` to `{@render snippet()}`. +``` + +A component throwing this error will look something like this (`children` is not being rendered): + +```svelte + + +{children} +``` + +...or like this (a parent component is passing a snippet where a non-snippet value is expected): + +```svelte + + + {#slot label()} + Hi! + {/slot} + +``` + +```svelte + + + + +

{label}

+``` + ### store_invalid_shape ``` diff --git a/packages/svelte/messages/shared-errors/errors.md b/packages/svelte/messages/shared-errors/errors.md index 8b4c61303a07..4284de2899dc 100644 --- a/packages/svelte/messages/shared-errors/errors.md +++ b/packages/svelte/messages/shared-errors/errors.md @@ -48,6 +48,41 @@ Certain lifecycle methods can only be used during component initialisation. To f ``` +## snippet_without_render_tag + +> Attempted to render a snippet without a `{@render}` block. This would cause the snippet to be rendered directly to the DOM. To fix this, change `{snippet}` to `{@render snippet()}`. + +A component throwing this error will look something like this (`children` is not being rendered): + +```svelte + + +{children} +``` + +...or like this (a parent component is passing a snippet where a non-snippet value is expected): + +```svelte + + + {#slot label()} + Hi! + {/slot} + +``` + +```svelte + + + + +

{label}

+``` + ## store_invalid_shape > `%name%` is not a store with a `subscribe` method diff --git a/packages/svelte/src/compiler/phases/3-transform/server/visitors/SnippetBlock.js b/packages/svelte/src/compiler/phases/3-transform/server/visitors/SnippetBlock.js index eb839179271a..713d43064d5d 100644 --- a/packages/svelte/src/compiler/phases/3-transform/server/visitors/SnippetBlock.js +++ b/packages/svelte/src/compiler/phases/3-transform/server/visitors/SnippetBlock.js @@ -1,6 +1,7 @@ -/** @import { BlockStatement } from 'estree' */ +/** @import { ArrowFunctionExpression, BlockStatement, CallExpression, ModuleDeclaration, Statement } from 'estree' */ /** @import { AST } from '#compiler' */ /** @import { ComponentContext } from '../types.js' */ +import { DEV } from 'esm-env'; import * as b from '../../../../utils/builders.js'; /** @@ -8,18 +9,24 @@ import * as b from '../../../../utils/builders.js'; * @param {ComponentContext} context */ export function SnippetBlock(node, context) { - const fn = b.function_declaration( - node.expression, + /** @type {ArrowFunctionExpression | CallExpression} */ + let fn = b.arrow( [b.id('$$payload'), ...node.parameters], /** @type {BlockStatement} */ (context.visit(node.body)) ); + if (DEV) { + fn = b.call('$.prevent_snippet_stringification', fn); + } + + const declaration = b.declaration('const', [b.declarator(node.expression, fn)]); + // @ts-expect-error - TODO remove this hack once $$render_inner for legacy bindings is gone fn.___snippet = true; if (node.metadata.can_hoist) { - context.state.hoisted.push(fn); + context.state.hoisted.push(declaration); } else { - context.state.init.push(fn); + context.state.init.push(declaration); } } diff --git a/packages/svelte/src/compiler/phases/3-transform/server/visitors/shared/component.js b/packages/svelte/src/compiler/phases/3-transform/server/visitors/shared/component.js index 695161ff9b60..3e73674eff05 100644 --- a/packages/svelte/src/compiler/phases/3-transform/server/visitors/shared/component.js +++ b/packages/svelte/src/compiler/phases/3-transform/server/visitors/shared/component.js @@ -1,9 +1,10 @@ -/** @import { BlockStatement, Expression, Pattern, Property, SequenceExpression, Statement } from 'estree' */ +/** @import { CallExpression, ArrowFunctionExpression, BlockStatement, Expression, Pattern, Property, SequenceExpression, Statement } from 'estree' */ /** @import { AST } from '#compiler' */ /** @import { ComponentContext } from '../../types.js' */ import { empty_comment, build_attribute_value } from './utils.js'; import * as b from '../../../../../utils/builders.js'; import { is_element_node } from '../../../../nodes.js'; +import { DEV } from 'esm-env'; /** * @param {AST.Component | AST.SvelteComponent | AST.SvelteSelf} node @@ -226,7 +227,8 @@ export function build_inline_component(node, expression, context) { params.push(pattern); } - const slot_fn = b.arrow(params, b.block(block.body)); + /** @type {ArrowFunctionExpression | CallExpression} */ + let slot_fn = b.arrow(params, b.block(block.body)); if (slot_name === 'default' && !has_children_prop) { if ( @@ -237,6 +239,10 @@ export function build_inline_component(node, expression, context) { !node.attributes.some((attr) => attr.type === 'LetDirective') ) ) { + if (DEV) { + slot_fn = b.call('$.prevent_snippet_stringification', slot_fn); + } + // create `children` prop... push_prop(b.prop('init', b.id('children'), slot_fn)); diff --git a/packages/svelte/src/internal/client/dom/blocks/snippet.js b/packages/svelte/src/internal/client/dom/blocks/snippet.js index b916a02ce55f..421910c946d6 100644 --- a/packages/svelte/src/internal/client/dom/blocks/snippet.js +++ b/packages/svelte/src/internal/client/dom/blocks/snippet.js @@ -15,6 +15,7 @@ import * as e from '../../errors.js'; import { DEV } from 'esm-env'; import { get_first_child, get_next_sibling } from '../operations.js'; import { noop } from '../../../shared/utils.js'; +import { prevent_snippet_stringification } from '../../../shared/validate.js'; /** * @template {(node: TemplateNode, ...args: any[]) => void} SnippetFn @@ -60,7 +61,7 @@ export function snippet(node, get_snippet, ...args) { * @param {(node: TemplateNode, ...args: any[]) => void} fn */ export function wrap_snippet(component, fn) { - return (/** @type {TemplateNode} */ node, /** @type {any[]} */ ...args) => { + const snippet = (/** @type {TemplateNode} */ node, /** @type {any[]} */ ...args) => { var previous_component_function = dev_current_component_function; set_dev_current_component_function(component); @@ -70,6 +71,12 @@ export function wrap_snippet(component, fn) { set_dev_current_component_function(previous_component_function); } }; + + if (DEV) { + prevent_snippet_stringification(snippet); + } + + return snippet; } /** diff --git a/packages/svelte/src/internal/client/index.js b/packages/svelte/src/internal/client/index.js index a5f93e8b171b..18f75f23b0aa 100644 --- a/packages/svelte/src/internal/client/index.js +++ b/packages/svelte/src/internal/client/index.js @@ -164,7 +164,8 @@ export { invalid_default_snippet, validate_dynamic_element_tag, validate_store, - validate_void_dynamic_element + validate_void_dynamic_element, + prevent_snippet_stringification } from '../shared/validate.js'; export { strict_equals, equals } from './dev/equality.js'; export { log_if_contains_state } from './dev/console-log.js'; diff --git a/packages/svelte/src/internal/server/index.js b/packages/svelte/src/internal/server/index.js index 6098b496c5ac..d6e4e4c69415 100644 --- a/packages/svelte/src/internal/server/index.js +++ b/packages/svelte/src/internal/server/index.js @@ -550,7 +550,8 @@ export { fallback } from '../shared/utils.js'; export { invalid_default_snippet, validate_dynamic_element_tag, - validate_void_dynamic_element + validate_void_dynamic_element, + prevent_snippet_stringification } from '../shared/validate.js'; export { escape_html as escape }; diff --git a/packages/svelte/src/internal/shared/errors.js b/packages/svelte/src/internal/shared/errors.js index 26d6822cdb29..09bf3283d768 100644 --- a/packages/svelte/src/internal/shared/errors.js +++ b/packages/svelte/src/internal/shared/errors.js @@ -33,6 +33,21 @@ export function lifecycle_outside_component(name) { } } +/** + * Attempted to render a snippet without a `{@render}` block. This would cause the snippet to be rendered directly to the DOM. To fix this, change `{snippet}` to `{@render snippet()}`. + * @returns {never} + */ +export function snippet_without_render_tag() { + if (DEV) { + const error = new Error(`snippet_without_render_tag\nAttempted to render a snippet without a \`{@render}\` block. This would cause the snippet to be rendered directly to the DOM. To fix this, change \`{snippet}\` to \`{@render snippet()}\`.\nhttps://svelte.dev/e/snippet_without_render_tag`); + + error.name = 'Svelte error'; + throw error; + } else { + throw new Error(`https://svelte.dev/e/snippet_without_render_tag`); + } +} + /** * `%name%` is not a store with a `subscribe` method * @param {string} name diff --git a/packages/svelte/src/internal/shared/validate.js b/packages/svelte/src/internal/shared/validate.js index 852c0e83bfb1..bbb237594bec 100644 --- a/packages/svelte/src/internal/shared/validate.js +++ b/packages/svelte/src/internal/shared/validate.js @@ -35,3 +35,15 @@ export function validate_store(store, name) { e.store_invalid_shape(name); } } + +/** + * @template {() => unknown} T + * @param {T} fn + */ +export function prevent_snippet_stringification(fn) { + fn.toString = () => { + e.snippet_without_render_tag(); + return ''; + }; + return fn; +} diff --git a/packages/svelte/tests/runtime-runes/samples/snippet-block-without-render-tag-dev/_config.js b/packages/svelte/tests/runtime-runes/samples/snippet-block-without-render-tag-dev/_config.js new file mode 100644 index 000000000000..94c5de10af60 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/snippet-block-without-render-tag-dev/_config.js @@ -0,0 +1,8 @@ +import { test } from '../../test'; + +export default test({ + compileOptions: { + dev: true + }, + runtime_error: 'snippet_without_render_tag' +}); diff --git a/packages/svelte/tests/runtime-runes/samples/snippet-block-without-render-tag-dev/main.svelte b/packages/svelte/tests/runtime-runes/samples/snippet-block-without-render-tag-dev/main.svelte new file mode 100644 index 000000000000..3f8edfe4fafc --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/snippet-block-without-render-tag-dev/main.svelte @@ -0,0 +1,5 @@ +{testSnippet} + +{#snippet testSnippet()} +

hi again

+{/snippet} diff --git a/packages/svelte/tests/runtime-runes/samples/snippet-block-without-render-tag-prod/_config.js b/packages/svelte/tests/runtime-runes/samples/snippet-block-without-render-tag-prod/_config.js new file mode 100644 index 000000000000..d616676dd69e --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/snippet-block-without-render-tag-prod/_config.js @@ -0,0 +1,4 @@ +import { test } from '../../test'; + +export default test({ +}); diff --git a/packages/svelte/tests/runtime-runes/samples/snippet-block-without-render-tag-prod/main.svelte b/packages/svelte/tests/runtime-runes/samples/snippet-block-without-render-tag-prod/main.svelte new file mode 100644 index 000000000000..3f8edfe4fafc --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/snippet-block-without-render-tag-prod/main.svelte @@ -0,0 +1,5 @@ +{testSnippet} + +{#snippet testSnippet()} +

hi again

+{/snippet} diff --git a/packages/svelte/tests/runtime-runes/samples/snippet-children-without-render-tag-dev-prod/_config.js b/packages/svelte/tests/runtime-runes/samples/snippet-children-without-render-tag-dev-prod/_config.js new file mode 100644 index 000000000000..f47bee71df87 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/snippet-children-without-render-tag-dev-prod/_config.js @@ -0,0 +1,3 @@ +import { test } from '../../test'; + +export default test({}); diff --git a/packages/svelte/tests/runtime-runes/samples/snippet-children-without-render-tag-dev-prod/main.svelte b/packages/svelte/tests/runtime-runes/samples/snippet-children-without-render-tag-dev-prod/main.svelte new file mode 100644 index 000000000000..4a4ed3176f24 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/snippet-children-without-render-tag-dev-prod/main.svelte @@ -0,0 +1,5 @@ + + +Hi diff --git a/packages/svelte/tests/runtime-runes/samples/snippet-children-without-render-tag-dev-prod/unrendered-children.svelte b/packages/svelte/tests/runtime-runes/samples/snippet-children-without-render-tag-dev-prod/unrendered-children.svelte new file mode 100644 index 000000000000..6b7154a5a406 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/snippet-children-without-render-tag-dev-prod/unrendered-children.svelte @@ -0,0 +1,5 @@ + + +{children} diff --git a/packages/svelte/tests/runtime-runes/samples/snippet-children-without-render-tag-dev/_config.js b/packages/svelte/tests/runtime-runes/samples/snippet-children-without-render-tag-dev/_config.js new file mode 100644 index 000000000000..94c5de10af60 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/snippet-children-without-render-tag-dev/_config.js @@ -0,0 +1,8 @@ +import { test } from '../../test'; + +export default test({ + compileOptions: { + dev: true + }, + runtime_error: 'snippet_without_render_tag' +}); diff --git a/packages/svelte/tests/runtime-runes/samples/snippet-children-without-render-tag-dev/main.svelte b/packages/svelte/tests/runtime-runes/samples/snippet-children-without-render-tag-dev/main.svelte new file mode 100644 index 000000000000..4a4ed3176f24 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/snippet-children-without-render-tag-dev/main.svelte @@ -0,0 +1,5 @@ + + +Hi diff --git a/packages/svelte/tests/runtime-runes/samples/snippet-children-without-render-tag-dev/unrendered-children.svelte b/packages/svelte/tests/runtime-runes/samples/snippet-children-without-render-tag-dev/unrendered-children.svelte new file mode 100644 index 000000000000..6b7154a5a406 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/snippet-children-without-render-tag-dev/unrendered-children.svelte @@ -0,0 +1,5 @@ + + +{children} diff --git a/packages/svelte/tests/snapshot/samples/bind-component-snippet/_expected/server/index.svelte.js b/packages/svelte/tests/snapshot/samples/bind-component-snippet/_expected/server/index.svelte.js index c091179c41ae..635ebe403a2d 100644 --- a/packages/svelte/tests/snapshot/samples/bind-component-snippet/_expected/server/index.svelte.js +++ b/packages/svelte/tests/snapshot/samples/bind-component-snippet/_expected/server/index.svelte.js @@ -1,9 +1,9 @@ import * as $ from 'svelte/internal/server'; import TextInput from './Child.svelte'; -function snippet($$payload) { +const snippet = $.prevent_snippet_stringification(($$payload) => { $$payload.out += `Something`; -} +}); export default function Bind_component_snippet($$payload) { let value = ''; diff --git a/packages/svelte/tests/snapshot/samples/function-prop-no-getter/_expected/server/index.svelte.js b/packages/svelte/tests/snapshot/samples/function-prop-no-getter/_expected/server/index.svelte.js index 88f6f55ee74a..47dffcad12b9 100644 --- a/packages/svelte/tests/snapshot/samples/function-prop-no-getter/_expected/server/index.svelte.js +++ b/packages/svelte/tests/snapshot/samples/function-prop-no-getter/_expected/server/index.svelte.js @@ -13,9 +13,9 @@ export default function Function_prop_no_getter($$payload) { onmousedown: () => count += 1, onmouseup, onmouseenter: () => count = plusOne(count), - children: ($$payload) => { + children: $.prevent_snippet_stringification(($$payload) => { $$payload.out += `clicks: ${$.escape(count)}`; - }, + }), $$slots: { default: true } }); } \ No newline at end of file diff --git a/packages/svelte/types/index.d.ts b/packages/svelte/types/index.d.ts index c6000fc4b67f..b19a832fc212 100644 --- a/packages/svelte/types/index.d.ts +++ b/packages/svelte/types/index.d.ts @@ -1994,12 +1994,51 @@ declare module 'svelte/reactivity' { * @since 5.7.0 */ export function createSubscriber(start: (update: () => void) => (() => void) | void): () => void; + /** + * Used to synchronize external state with Svelte's reactivity system. + * + * In order to synchronize external state, you need to know two things: + * - The value of the external state at a given time + * - When you should update that value + * + * These correspond to the two arguments to this function: + * + * - {@link getSnapshot} is a function that returns the current value of the external state + * - {@link onSubscribe} is a callback that sets up reactivity: + * - It is called the first time the function returned by {@link createReactiveFunction} becomes tracked by a dependent + * - It receives a function, `update`, as its first argument. Calling `update` tells Svelte that the value in the external + * store has changed, and it needs to push that change to all of its listeners + * - If it returns a cleanup function, that cleanup function will be called when the number of listeners to the reactive function + * returns to zero + * + * Combined with `$derived.by`, this enables seamless integration with external data sources, including destructuring support: + * + * @example + * ```js + * import { observer } from 'external-datafetching-library'; + * + * const { data, loading, refetch } = $derived.by( + * createReactiveFunction( + * observer.getCurrentResult, + * observer.subscribe + * ) + * ); + * ``` + * + * (For our React friends, this is a similar model to `useSyncExternalStore`.) + * + * @since 5.26.0 + * + * */ + export function createReactiveFunction(getSnapshot: () => T, onSubscribe: (update: VoidFn_1) => void | VoidFn_1): () => T; + type VoidFn_1 = () => void; class ReactiveValue { - constructor(fn: () => T, onsubscribe: (update: () => void) => void); + constructor(fn: () => T, onsubscribe: (update: VoidFn) => void | VoidFn); get current(): T; #private; } + type VoidFn = () => void; export {}; } @@ -2061,10 +2100,11 @@ declare module 'svelte/reactivity/window' { }; class ReactiveValue { - constructor(fn: () => T, onsubscribe: (update: () => void) => void); + constructor(fn: () => T, onsubscribe: (update: VoidFn) => void | VoidFn); get current(): T; #private; } + type VoidFn = () => void; export {}; } From 8393c94ac1e55bb383c48f30bf7b72560b7913f0 Mon Sep 17 00:00:00 2001 From: "S. Elliott Johnson" Date: Tue, 15 Apr 2025 01:02:27 -0600 Subject: [PATCH 2/7] changeset --- .changeset/strong-pianos-promise.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/strong-pianos-promise.md diff --git a/.changeset/strong-pianos-promise.md b/.changeset/strong-pianos-promise.md new file mode 100644 index 000000000000..f5214c7dcb9d --- /dev/null +++ b/.changeset/strong-pianos-promise.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: Throw on unrendered snippets in `dev` From 3234c77040bd70dd194fa201a82866c12040b11f Mon Sep 17 00:00:00 2001 From: "S. Elliott Johnson" Date: Tue, 15 Apr 2025 01:05:04 -0600 Subject: [PATCH 3/7] fix --- .../compiler/phases/3-transform/client/visitors/SnippetBlock.js | 1 - .../phases/3-transform/server/visitors/shared/component.js | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/svelte/src/compiler/phases/3-transform/client/visitors/SnippetBlock.js b/packages/svelte/src/compiler/phases/3-transform/client/visitors/SnippetBlock.js index f28f8c8a596c..728d5a89343f 100644 --- a/packages/svelte/src/compiler/phases/3-transform/client/visitors/SnippetBlock.js +++ b/packages/svelte/src/compiler/phases/3-transform/client/visitors/SnippetBlock.js @@ -71,7 +71,6 @@ export function SnippetBlock(node, context) { .../** @type {BlockStatement} */ (context.visit(node.body, child_state)).body ]); - // in dev we use a FunctionExpression (not arrow function) so we can use `arguments` let snippet = dev ? b.call('$.wrap_snippet', b.id(context.state.analysis.name), b.function(null, args, body)) : b.arrow(args, body); diff --git a/packages/svelte/src/compiler/phases/3-transform/server/visitors/shared/component.js b/packages/svelte/src/compiler/phases/3-transform/server/visitors/shared/component.js index 760e840c1d64..f4b3dd1b09aa 100644 --- a/packages/svelte/src/compiler/phases/3-transform/server/visitors/shared/component.js +++ b/packages/svelte/src/compiler/phases/3-transform/server/visitors/shared/component.js @@ -1,4 +1,4 @@ -/** @import { CallExpression, ArrowFunctionExpression, BlockStatement, Expression, Pattern, Property, SequenceExpression, Statement } from 'estree' */ +/** @import { BlockStatement, Expression, Pattern, Property, SequenceExpression, Statement } from 'estree' */ /** @import { AST } from '#compiler' */ /** @import { ComponentContext } from '../../types.js' */ import { empty_comment, build_attribute_value } from './utils.js'; From 5233c8e4dd108dc6a6881504472cb04660ca9f7e Mon Sep 17 00:00:00 2001 From: "S. Elliott Johnson" Date: Tue, 15 Apr 2025 01:06:08 -0600 Subject: [PATCH 4/7] fix --- .../compiler/phases/3-transform/client/visitors/SnippetBlock.js | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/svelte/src/compiler/phases/3-transform/client/visitors/SnippetBlock.js b/packages/svelte/src/compiler/phases/3-transform/client/visitors/SnippetBlock.js index 728d5a89343f..f28f8c8a596c 100644 --- a/packages/svelte/src/compiler/phases/3-transform/client/visitors/SnippetBlock.js +++ b/packages/svelte/src/compiler/phases/3-transform/client/visitors/SnippetBlock.js @@ -71,6 +71,7 @@ export function SnippetBlock(node, context) { .../** @type {BlockStatement} */ (context.visit(node.body, child_state)).body ]); + // in dev we use a FunctionExpression (not arrow function) so we can use `arguments` let snippet = dev ? b.call('$.wrap_snippet', b.id(context.state.analysis.name), b.function(null, args, body)) : b.arrow(args, body); From aa50bd79c476440102c14e6a9bb63298bff9edb7 Mon Sep 17 00:00:00 2001 From: Elliott Johnson Date: Tue, 15 Apr 2025 16:40:34 -0600 Subject: [PATCH 5/7] Update errors.md Co-authored-by: Simon H <5968653+dummdidumm@users.noreply.github.com> --- packages/svelte/messages/shared-errors/errors.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/svelte/messages/shared-errors/errors.md b/packages/svelte/messages/shared-errors/errors.md index f1d386c81ba0..0c9d78d8ac41 100644 --- a/packages/svelte/messages/shared-errors/errors.md +++ b/packages/svelte/messages/shared-errors/errors.md @@ -54,7 +54,7 @@ Certain lifecycle methods can only be used during component initialisation. To f ## snippet_without_render_tag -> Attempted to render a snippet without a `{@render}` block. This would cause the snippet to be rendered directly to the DOM. To fix this, change `{snippet}` to `{@render snippet()}`. +> Attempted to render a snippet without a `{@render}` block. This would cause the snippet code to be stringified instead of its content being rendered to the DOM. To fix this, change `{snippet}` to `{@render snippet()}`. A component throwing this error will look something like this (`children` is not being rendered): From a24f93f49c90bf76eaff78886be9d405852e19d5 Mon Sep 17 00:00:00 2001 From: Elliott Johnson Date: Tue, 15 Apr 2025 16:40:46 -0600 Subject: [PATCH 6/7] Update errors.md Co-authored-by: Simon H <5968653+dummdidumm@users.noreply.github.com> --- packages/svelte/messages/shared-errors/errors.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/svelte/messages/shared-errors/errors.md b/packages/svelte/messages/shared-errors/errors.md index 0c9d78d8ac41..4b4d3322028d 100644 --- a/packages/svelte/messages/shared-errors/errors.md +++ b/packages/svelte/messages/shared-errors/errors.md @@ -71,9 +71,9 @@ A component throwing this error will look something like this (`children` is not ```svelte - {#slot label()} + {#snippet label()} Hi! - {/slot} + {/snippet} ``` From b7b69916645aa1e8e0d86fc888bf2132535d3c4a Mon Sep 17 00:00:00 2001 From: "S. Elliott Johnson" Date: Tue, 15 Apr 2025 17:28:09 -0600 Subject: [PATCH 7/7] regenerate --- documentation/docs/98-reference/.generated/shared-errors.md | 6 +++--- packages/svelte/src/internal/shared/errors.js | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/documentation/docs/98-reference/.generated/shared-errors.md b/documentation/docs/98-reference/.generated/shared-errors.md index 81714b596f7f..6c31aaafd0df 100644 --- a/documentation/docs/98-reference/.generated/shared-errors.md +++ b/documentation/docs/98-reference/.generated/shared-errors.md @@ -63,7 +63,7 @@ Certain lifecycle methods can only be used during component initialisation. To f ### snippet_without_render_tag ``` -Attempted to render a snippet without a `{@render}` block. This would cause the snippet to be rendered directly to the DOM. To fix this, change `{snippet}` to `{@render snippet()}`. +Attempted to render a snippet without a `{@render}` block. This would cause the snippet code to be stringified instead of its content being rendered to the DOM. To fix this, change `{snippet}` to `{@render snippet()}`. ``` A component throwing this error will look something like this (`children` is not being rendered): @@ -81,9 +81,9 @@ A component throwing this error will look something like this (`children` is not ```svelte - {#slot label()} + {#snippet label()} Hi! - {/slot} + {/snippet} ``` diff --git a/packages/svelte/src/internal/shared/errors.js b/packages/svelte/src/internal/shared/errors.js index 44571691d967..b8606fbf6f7d 100644 --- a/packages/svelte/src/internal/shared/errors.js +++ b/packages/svelte/src/internal/shared/errors.js @@ -49,12 +49,12 @@ export function lifecycle_outside_component(name) { } /** - * Attempted to render a snippet without a `{@render}` block. This would cause the snippet to be rendered directly to the DOM. To fix this, change `{snippet}` to `{@render snippet()}`. + * Attempted to render a snippet without a `{@render}` block. This would cause the snippet code to be stringified instead of its content being rendered to the DOM. To fix this, change `{snippet}` to `{@render snippet()}`. * @returns {never} */ export function snippet_without_render_tag() { if (DEV) { - const error = new Error(`snippet_without_render_tag\nAttempted to render a snippet without a \`{@render}\` block. This would cause the snippet to be rendered directly to the DOM. To fix this, change \`{snippet}\` to \`{@render snippet()}\`.\nhttps://svelte.dev/e/snippet_without_render_tag`); + const error = new Error(`snippet_without_render_tag\nAttempted to render a snippet without a \`{@render}\` block. This would cause the snippet code to be stringified instead of its content being rendered to the DOM. To fix this, change \`{snippet}\` to \`{@render snippet()}\`.\nhttps://svelte.dev/e/snippet_without_render_tag`); error.name = 'Svelte error'; throw error;