diff --git a/packages/svelte/messages/shared-errors/errors.md b/packages/svelte/messages/shared-errors/errors.md index d693b35e05..1d17b0f6d4 100644 --- a/packages/svelte/messages/shared-errors/errors.md +++ b/packages/svelte/messages/shared-errors/errors.md @@ -2,14 +2,6 @@ > `%name%(...)` can only be used during component initialisation -## render_tag_invalid_argument - -> The argument to `{@render ...}` must be a snippet function, not a component or a slot with a `let:` directive or some other kind of function. If you want to dynamically render one snippet or another, use `$derived` and pass its result to `{@render ...}` - -## snippet_used_as_component - -> A snippet must be rendered with `{@render ...}` - ## store_invalid_shape > `%name%` is not a store with a `subscribe` method diff --git a/packages/svelte/src/compiler/phases/3-transform/server/transform-server.js b/packages/svelte/src/compiler/phases/3-transform/server/transform-server.js index 04115a850e..0f5a1f2522 100644 --- a/packages/svelte/src/compiler/phases/3-transform/server/transform-server.js +++ b/packages/svelte/src/compiler/phases/3-transform/server/transform-server.js @@ -969,13 +969,7 @@ function serialize_inline_component(node, expression, context) { lets.length === 0 && children.default.every((node) => node.type !== 'SvelteFragment') ) { - push_prop( - b.prop( - 'init', - b.id('children'), - context.state.options.dev ? b.call('$.add_snippet_symbol', slot_fn) : slot_fn - ) - ); + push_prop(b.prop('init', b.id('children'), slot_fn)); // We additionally add the default slot as a boolean, so that the slot render function on the other // side knows it should get the content to render from $$props.children serialized_slots.push(b.init('default', b.true)); @@ -1501,10 +1495,6 @@ const template_visitors = { fn.___snippet = true; // TODO hoist where possible context.state.init.push(fn); - - if (context.state.options.dev) { - context.state.init.push(b.stmt(b.call('$.add_snippet_symbol', node.expression))); - } }, Component(node, context) { serialize_inline_component(node, b.id(node.name), context); diff --git a/packages/svelte/src/index.d.ts b/packages/svelte/src/index.d.ts index 511bb6c02e..f56b4c66e0 100644 --- a/packages/svelte/src/index.d.ts +++ b/packages/svelte/src/index.d.ts @@ -1,5 +1,6 @@ // This should contain all the public interfaces (not all of them are actually importable, check current Svelte for which ones are). +import type { Getters } from '#shared'; import './ambient.js'; /** @@ -104,6 +105,10 @@ export class SvelteComponent< $set(props: Partial): void; } +declare const brand: unique symbol; +type Brand = { [brand]: B }; +type Branded = T & Brand; + /** * Can be used to create strongly typed Svelte components. * @@ -136,7 +141,8 @@ export interface Component< * @param props The props passed to the component. */ ( - internal: unknown, + this: void, + internal: Branded<{}, 'ComponentInternals'>, props: Props ): { /** @@ -271,13 +277,12 @@ declare const SnippetReturn: unique symbol; export interface Snippet { ( this: void, + internal: Branded<{}, 'SnippetInternals'>, // this conditional allows tuples but not arrays. Arrays would indicate a // rest parameter type, which is not supported. If rest parameters are added // in the future, the condition can be removed. - ...args: number extends Parameters['length'] ? never : Parameters - ): typeof SnippetReturn & { - _: 'functions passed to {@render ...} tags must use the `Snippet` type imported from "svelte"'; - }; + ...args: number extends Parameters['length'] ? never : Getters + ): void; } interface DispatchOptions { diff --git a/packages/svelte/src/internal/client/dom/blocks/snippet.js b/packages/svelte/src/internal/client/dom/blocks/snippet.js index a920f6db3e..2df4487536 100644 --- a/packages/svelte/src/internal/client/dom/blocks/snippet.js +++ b/packages/svelte/src/internal/client/dom/blocks/snippet.js @@ -1,7 +1,6 @@ /** @import { Snippet } from 'svelte' */ /** @import { Effect, TemplateNode } from '#client' */ /** @import { Getters } from '#shared' */ -import { add_snippet_symbol } from '../../../shared/validate.js'; import { EFFECT_TRANSPARENT } from '../../constants.js'; import { branch, block, destroy_effect, teardown } from '../../reactivity/effects.js'; import { @@ -50,10 +49,11 @@ export function snippet(node, get_snippet, ...args) { * In development, wrap the snippet function so that it passes validation, and so that the * correct component context is set for ownership checks * @param {any} component - * @param {(node: TemplateNode, ...args: any[]) => void} fn + * @param {Snippet} fn + * @returns {Snippet} */ export function wrap_snippet(component, fn) { - return add_snippet_symbol((/** @type {TemplateNode} */ node, /** @type {any[]} */ ...args) => { + return (node, ...args) => { var previous_component_function = dev_current_component_function; set_dev_current_component_function(component); @@ -62,7 +62,7 @@ export function wrap_snippet(component, fn) { } finally { set_dev_current_component_function(previous_component_function); } - }); + }; } /** @@ -75,29 +75,27 @@ export function wrap_snippet(component, fn) { * @returns {Snippet} */ export function createRawSnippet(fn) { - return add_snippet_symbol( - (/** @type {TemplateNode} */ anchor, /** @type {Getters} */ ...params) => { - var snippet = fn(...params); + return (anchor, ...params) => { + var snippet = fn(...params); - /** @type {Element} */ - var element; + /** @type {Element} */ + var element; - if (hydrating) { - element = /** @type {Element} */ (hydrate_node); - hydrate_next(); - } else { - var html = snippet.render().trim(); - var fragment = create_fragment_from_html(html); - element = /** @type {Element} */ (fragment.firstChild); - anchor.before(element); - } + if (hydrating) { + element = /** @type {Element} */ (hydrate_node); + hydrate_next(); + } else { + var html = snippet.render().trim(); + var fragment = create_fragment_from_html(html); + element = /** @type {Element} */ (fragment.firstChild); + /** @type {TemplateNode} */ (/** @type {unknown} */ (anchor)).before(element); + } - const result = snippet.setup?.(element); - assign_nodes(element, element); + const result = snippet.setup?.(element); + assign_nodes(element, element); - if (typeof result === 'function') { - teardown(result); - } + if (typeof result === 'function') { + teardown(result); } - ); + }; } diff --git a/packages/svelte/src/internal/server/blocks/snippet.js b/packages/svelte/src/internal/server/blocks/snippet.js index b9f72063f4..884f6baa82 100644 --- a/packages/svelte/src/internal/server/blocks/snippet.js +++ b/packages/svelte/src/internal/server/blocks/snippet.js @@ -1,7 +1,6 @@ /** @import { Snippet } from 'svelte' */ /** @import { Payload } from '#server' */ /** @import { Getters } from '#shared' */ -import { add_snippet_symbol } from '../../shared/validate.js'; /** * Create a snippet programmatically @@ -13,10 +12,10 @@ import { add_snippet_symbol } from '../../shared/validate.js'; * @returns {Snippet} */ export function createRawSnippet(fn) { - return add_snippet_symbol((/** @type {Payload} */ payload, /** @type {Params} */ ...args) => { + return (payload, ...args) => { var getters = /** @type {Getters} */ (args.map((value) => () => value)); - payload.out += fn(...getters) + /** @type {Payload} */ (/** @type {unknown} */ (payload)).out += fn(...getters) .render() .trim(); - }); + }; } diff --git a/packages/svelte/src/internal/server/index.js b/packages/svelte/src/internal/server/index.js index a1a2d3febc..74c66631df 100644 --- a/packages/svelte/src/internal/server/index.js +++ b/packages/svelte/src/internal/server/index.js @@ -555,7 +555,6 @@ export { push_element, pop_element } from './dev.js'; export { snapshot } from '../shared/clone.js'; export { - add_snippet_symbol, validate_component, validate_dynamic_element_tag, validate_snippet, diff --git a/packages/svelte/src/internal/shared/validate.js b/packages/svelte/src/internal/shared/validate.js index e08f3ddab1..817c11751f 100644 --- a/packages/svelte/src/internal/shared/validate.js +++ b/packages/svelte/src/internal/shared/validate.js @@ -4,27 +4,13 @@ import { is_void } from '../../constants.js'; import * as w from './warnings.js'; import * as e from './errors.js'; -const snippet_symbol = Symbol.for('svelte.snippet'); - -/** - * @param {any} fn - * @returns {import('svelte').Snippet} - */ -export function add_snippet_symbol(fn) { - fn[snippet_symbol] = true; - return fn; -} - /** * Validate that the function handed to `{@render ...}` is a snippet function, and not some other kind of function. * @param {any} snippet_fn * @param {Record | undefined} $$props Only passed if render tag receives arguments and is for the children prop */ export function validate_snippet(snippet_fn, $$props) { - if ( - ($$props?.$$slots?.default && typeof $$props.$$slots.default !== 'boolean') || - (snippet_fn && snippet_fn[snippet_symbol] !== true) - ) { + if ($$props?.$$slots?.default && typeof $$props.$$slots.default !== 'boolean') { e.render_tag_invalid_argument(); } @@ -36,11 +22,7 @@ export function validate_snippet(snippet_fn, $$props) { * @param {any} component_fn */ export function validate_component(component_fn) { - if (component_fn?.[snippet_symbol] === true) { - e.snippet_used_as_component(); - } - - return component_fn; + return component_fn; // TODO get rid } /** diff --git a/packages/svelte/tests/runtime-runes/samples/mount-snippet-error/_config.js b/packages/svelte/tests/runtime-runes/samples/mount-snippet-error/_config.js deleted file mode 100644 index 3ba2b358ea..0000000000 --- a/packages/svelte/tests/runtime-runes/samples/mount-snippet-error/_config.js +++ /dev/null @@ -1,12 +0,0 @@ -import { test } from '../../test'; - -export default test({ - compileOptions: { - dev: true - }, - async test({ assert, target }) { - const div = target.querySelector('div'); - assert.htmlEqual(div?.innerHTML || '', ''); - }, - runtime_error: 'snippet_used_as_component\nA snippet must be rendered with `{@render ...}`' -}); diff --git a/packages/svelte/tests/runtime-runes/samples/mount-snippet-error/main.svelte b/packages/svelte/tests/runtime-runes/samples/mount-snippet-error/main.svelte deleted file mode 100644 index 075fbba24e..0000000000 --- a/packages/svelte/tests/runtime-runes/samples/mount-snippet-error/main.svelte +++ /dev/null @@ -1,14 +0,0 @@ - - -
-{#snippet foo()} - shouldnt be rendered -{/snippet} \ No newline at end of file diff --git a/packages/svelte/tests/runtime-runes/samples/snippet-validation-error-1/_config.js b/packages/svelte/tests/runtime-runes/samples/snippet-validation-error-1/_config.js deleted file mode 100644 index 288edc26a6..0000000000 --- a/packages/svelte/tests/runtime-runes/samples/snippet-validation-error-1/_config.js +++ /dev/null @@ -1,8 +0,0 @@ -import { test } from '../../test'; - -export default test({ - compileOptions: { - dev: true - }, - error: 'render_tag_invalid_argument' -}); diff --git a/packages/svelte/tests/runtime-runes/samples/snippet-validation-error-1/main.svelte b/packages/svelte/tests/runtime-runes/samples/snippet-validation-error-1/main.svelte deleted file mode 100644 index d07df452da..0000000000 --- a/packages/svelte/tests/runtime-runes/samples/snippet-validation-error-1/main.svelte +++ /dev/null @@ -1,7 +0,0 @@ - - -{@render not_a_snippet()} diff --git a/packages/svelte/tests/runtime-runes/samples/snippet-validation-error-2/_config.js b/packages/svelte/tests/runtime-runes/samples/snippet-validation-error-2/_config.js deleted file mode 100644 index b549144bf5..0000000000 --- a/packages/svelte/tests/runtime-runes/samples/snippet-validation-error-2/_config.js +++ /dev/null @@ -1,8 +0,0 @@ -import { test } from '../../test'; - -export default test({ - compileOptions: { - dev: true - }, - error: 'snippet_used_as_component\nA snippet must be rendered with `{@render ...}`' -}); diff --git a/packages/svelte/tests/runtime-runes/samples/snippet-validation-error-2/main.svelte b/packages/svelte/tests/runtime-runes/samples/snippet-validation-error-2/main.svelte deleted file mode 100644 index e24e9549e3..0000000000 --- a/packages/svelte/tests/runtime-runes/samples/snippet-validation-error-2/main.svelte +++ /dev/null @@ -1,5 +0,0 @@ -{#snippet Foo()} -

hello

-{/snippet} - -