diff --git a/.prettierignore b/.prettierignore index 72cd10aca8e7..5e1d9b1aa741 100644 --- a/.prettierignore +++ b/.prettierignore @@ -28,15 +28,6 @@ packages/svelte/types packages/svelte/compiler/index.js playgrounds/sandbox/src/* -# sites/svelte.dev -sites/svelte.dev/static/svelte-app.json -sites/svelte.dev/scripts/svelte-app/ -sites/svelte.dev/src/routes/_components/Supporters/contributors.jpg -sites/svelte.dev/src/routes/_components/Supporters/contributors.js -sites/svelte.dev/src/routes/_components/Supporters/donors.jpg -sites/svelte.dev/src/routes/_components/Supporters/donors.js -sites/svelte.dev/src/lib/generated - **/node_modules **/.svelte-kit **/.vercel diff --git a/.prettierrc b/.prettierrc index c4fd5d9f2f73..c2d09a428924 100644 --- a/.prettierrc +++ b/.prettierrc @@ -17,12 +17,6 @@ "useTabs": false, "tabWidth": 2 } - }, - { - "files": ["sites/svelte-5-preview/src/routes/docs/content/**/*.md"], - "options": { - "printWidth": 60 - } } ] } diff --git a/.vscode/settings.json b/.vscode/settings.json index 21a2a11c84e3..4d360cbc8a10 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,6 +1,3 @@ { - "search.exclude": { - "sites/svelte-5-preview/static/*": true - }, "typescript.tsdk": "node_modules/typescript/lib" } diff --git a/documentation/docs/02-runes/02-$state.md b/documentation/docs/02-runes/02-$state.md index aea427a8ec52..741e24fde01e 100644 --- a/documentation/docs/02-runes/02-$state.md +++ b/documentation/docs/02-runes/02-$state.md @@ -119,7 +119,9 @@ class Todo { } ``` -> [NOTE!] Svelte provides reactive implementations of built-in classes like `Set` and `Map` that can be imported from [`svelte/reactivity`](svelte-reactivity). +### Built-in classes + +Svelte provides reactive implementations of built-in classes like `Set`, `Map`, `Date` and `URL` that can be imported from [`svelte/reactivity`](svelte-reactivity). ## `$state.raw` diff --git a/documentation/docs/02-runes/03-$derived.md b/documentation/docs/02-runes/03-$derived.md index 5f253cf6d130..0123868c4e5e 100644 --- a/documentation/docs/02-runes/03-$derived.md +++ b/documentation/docs/02-runes/03-$derived.md @@ -99,12 +99,16 @@ let selected = $derived(items[index]); If you use destructuring with a `$derived` declaration, the resulting variables will all be reactive — this... ```js +function stuff() { return { a: 1, b: 2, c: 3 } } +// ---cut--- let { a, b, c } = $derived(stuff()); ``` ...is roughly equivalent to this: ```js +function stuff() { return { a: 1, b: 2, c: 3 } } +// ---cut--- let _stuff = $derived(stuff()); let a = $derived(_stuff.a); let b = $derived(_stuff.b); diff --git a/documentation/docs/07-misc/03-typescript.md b/documentation/docs/07-misc/03-typescript.md index ff33885fb8d9..49ecd8adb5bf 100644 --- a/documentation/docs/07-misc/03-typescript.md +++ b/documentation/docs/07-misc/03-typescript.md @@ -254,39 +254,24 @@ To declare that a variable expects the constructor or instance type of a compone Svelte provides a best effort of all the HTML DOM types that exist. Sometimes you may want to use experimental attributes or custom events coming from an action. In these cases, TypeScript will throw a type error, saying that it does not know these types. If it's a non-experimental standard attribute/event, this may very well be a missing typing from our [HTML typings](https://github.com/sveltejs/svelte/blob/main/packages/svelte/elements.d.ts). In that case, you are welcome to open an issue and/or a PR fixing it. -In case this is a custom or experimental attribute/event, you can enhance the typings like this: - -```ts -/// file: additional-svelte-typings.d.ts -declare namespace svelteHTML { - // enhance elements - interface IntrinsicElements { - 'my-custom-element': { someattribute: string; 'on:event': (e: CustomEvent) => void }; - } - // enhance attributes - interface HTMLAttributes { - // If you want to use the beforeinstallprompt event - onbeforeinstallprompt?: (event: any) => any; - // If you want to use myCustomAttribute={..} (note: all lowercase) - mycustomattribute?: any; // You can replace any with something more specific if you like - } -} -``` - -Then make sure that `d.ts` file is referenced in your `tsconfig.json`. If it reads something like `"include": ["src/**/*"]` and your `d.ts` file is inside `src`, it should work. You may need to reload for the changes to take effect. - -You can also declare the typings by augmenting the `svelte/elements` module like this: +In case this is a custom or experimental attribute/event, you can enhance the typings by augmenting the `svelte/elements` module like this: ```ts /// file: additional-svelte-typings.d.ts import { HTMLButtonAttributes } from 'svelte/elements'; declare module 'svelte/elements' { + // add a new element export interface SvelteHTMLElements { 'custom-button': HTMLButtonAttributes; } - // allows for more granular control over what element to add the typings to + // add a new global attribute that is available on all html elements + export interface HTMLAttributes { + globalattribute?: string; + } + + // add a new attribute for button elements export interface HTMLButtonAttributes { veryexperimentalattribute?: string; } @@ -294,3 +279,5 @@ declare module 'svelte/elements' { export {}; // ensure this is not an ambient module, else types will be overridden instead of augmented ``` + +Then make sure that the `d.ts` file is referenced in your `tsconfig.json`. If it reads something like `"include": ["src/**/*"]` and your `d.ts` file is inside `src`, it should work. You may need to reload for the changes to take effect. diff --git a/documentation/docs/98-reference/.generated/client-errors.md b/documentation/docs/98-reference/.generated/client-errors.md index 3b17ef9f9b4e..8fdb7770aa9b 100644 --- a/documentation/docs/98-reference/.generated/client-errors.md +++ b/documentation/docs/98-reference/.generated/client-errors.md @@ -109,10 +109,10 @@ $effect(() => { The same applies to array mutations, since these both read and write to the array: ```js -let array = $state([]); +let array = $state(['hello']); $effect(() => { - array.push('hello'); + array.push('goodbye'); }); ``` @@ -238,3 +238,23 @@ let odd = $derived(!even); ``` If side-effects are unavoidable, use [`$effect`]($effect) instead. + +### svelte_boundary_reset_onerror + +``` +A `` `reset` function cannot be called while an error is still being handled +``` + +If a [``](https://svelte.dev/docs/svelte/svelte-boundary) has an `onerror` function, it must not call the provided `reset` function synchronously since the boundary is still in a broken state. Typically, `reset()` is called later, once the error has been resolved. + +If it's possible to resolve the error inside the `onerror` callback, you must at least wait for the boundary to settle before calling `reset()`, for example using [`tick`](https://svelte.dev/docs/svelte/lifecycle-hooks#tick): + +```svelte + { + fixTheError(); + +++await tick();+++ + reset(); +}}> + + +``` diff --git a/documentation/docs/98-reference/.generated/client-warnings.md b/documentation/docs/98-reference/.generated/client-warnings.md index 7548428e9784..6f1d677fe951 100644 --- a/documentation/docs/98-reference/.generated/client-warnings.md +++ b/documentation/docs/98-reference/.generated/client-warnings.md @@ -312,6 +312,32 @@ Reactive `$state(...)` proxies and the values they proxy have different identiti To resolve this, ensure you're comparing values where both values were created with `$state(...)`, or neither were. Note that `$state.raw(...)` will _not_ create a state proxy. +### svelte_boundary_reset_noop + +``` +A `` `reset` function only resets the boundary the first time it is called +``` + +When an error occurs while rendering the contents of a [``](https://svelte.dev/docs/svelte/svelte-boundary), the `onerror` handler is called with the error plus a `reset` function that attempts to re-render the contents. + +This `reset` function should only be called once. After that, it has no effect — in a case like this, where a reference to `reset` is stored outside the boundary, clicking the button while `` is rendered will _not_ cause the contents to be rendered again. + +```svelte + + + + + (reset = r)}> + + + {#snippet failed(e)} +

oops! {e.message}

+ {/snippet} +
+``` + ### transition_slide_display ``` diff --git a/eslint.config.js b/eslint.config.js index 41d98fa428ff..5241cb43a66f 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -80,7 +80,8 @@ export default [ files: ['packages/svelte/src/**/*'], ignores: ['packages/svelte/src/compiler/**/*'], rules: { - 'custom/no_compiler_imports': 'error' + 'custom/no_compiler_imports': 'error', + 'svelte/no-svelte-internal': 'off' } }, { @@ -102,11 +103,7 @@ export default [ '*.config.js', // documentation can contain invalid examples 'documentation', - // contains a fork of the REPL which doesn't adhere to eslint rules - 'sites/svelte-5-preview/**', - 'tmp/**', - // wasn't checked previously, reenable at some point - 'sites/svelte.dev/**' + 'tmp/**' ] } ]; diff --git a/package.json b/package.json index 458bf340841b..971bd020d14e 100644 --- a/package.json +++ b/package.json @@ -27,12 +27,13 @@ }, "devDependencies": { "@changesets/cli": "^2.27.8", - "@sveltejs/eslint-config": "^8.1.0", + "@sveltejs/eslint-config": "^8.3.3", "@svitejs/changesets-changelog-github-compact": "^1.1.0", "@types/node": "^20.11.5", "@vitest/coverage-v8": "^2.1.9", "eslint": "^9.9.1", "eslint-plugin-lube": "^0.4.3", + "eslint-plugin-svelte": "^3.11.0", "jsdom": "25.0.1", "playwright": "^1.46.1", "prettier": "^3.2.4", diff --git a/packages/svelte/CHANGELOG.md b/packages/svelte/CHANGELOG.md index 56f91c395f79..f78255e7fec6 100644 --- a/packages/svelte/CHANGELOG.md +++ b/packages/svelte/CHANGELOG.md @@ -1,5 +1,15 @@ # svelte +## 5.36.6 + +### Patch Changes + +- fix: delegate functions with shadowed variables if declared locally ([#16417](https://github.com/sveltejs/svelte/pull/16417)) + +- fix: handle error in correct boundary after reset ([#16171](https://github.com/sveltejs/svelte/pull/16171)) + +- fix: make `` reset function a noop after the first call ([#16171](https://github.com/sveltejs/svelte/pull/16171)) + ## 5.36.5 ### Patch Changes diff --git a/packages/svelte/messages/client-errors/errors.md b/packages/svelte/messages/client-errors/errors.md index d6af8598812b..57ecca048977 100644 --- a/packages/svelte/messages/client-errors/errors.md +++ b/packages/svelte/messages/client-errors/errors.md @@ -79,10 +79,10 @@ $effect(() => { The same applies to array mutations, since these both read and write to the array: ```js -let array = $state([]); +let array = $state(['hello']); $effect(() => { - array.push('hello'); + array.push('goodbye'); }); ``` @@ -184,3 +184,21 @@ let odd = $derived(!even); ``` If side-effects are unavoidable, use [`$effect`]($effect) instead. + +## svelte_boundary_reset_onerror + +> A `` `reset` function cannot be called while an error is still being handled + +If a [``](https://svelte.dev/docs/svelte/svelte-boundary) has an `onerror` function, it must not call the provided `reset` function synchronously since the boundary is still in a broken state. Typically, `reset()` is called later, once the error has been resolved. + +If it's possible to resolve the error inside the `onerror` callback, you must at least wait for the boundary to settle before calling `reset()`, for example using [`tick`](https://svelte.dev/docs/svelte/lifecycle-hooks#tick): + +```svelte + { + fixTheError(); + +++await tick();+++ + reset(); +}}> + + +``` diff --git a/packages/svelte/messages/client-warnings/warnings.md b/packages/svelte/messages/client-warnings/warnings.md index 13d9bfcd3bcd..123c6833e688 100644 --- a/packages/svelte/messages/client-warnings/warnings.md +++ b/packages/svelte/messages/client-warnings/warnings.md @@ -272,6 +272,30 @@ To silence the warning, ensure that `value`: To resolve this, ensure you're comparing values where both values were created with `$state(...)`, or neither were. Note that `$state.raw(...)` will _not_ create a state proxy. +## svelte_boundary_reset_noop + +> A `` `reset` function only resets the boundary the first time it is called + +When an error occurs while rendering the contents of a [``](https://svelte.dev/docs/svelte/svelte-boundary), the `onerror` handler is called with the error plus a `reset` function that attempts to re-render the contents. + +This `reset` function should only be called once. After that, it has no effect — in a case like this, where a reference to `reset` is stored outside the boundary, clicking the button while `` is rendered will _not_ cause the contents to be rendered again. + +```svelte + + + + + (reset = r)}> + + + {#snippet failed(e)} +

oops! {e.message}

+ {/snippet} +
+``` + ## transition_slide_display > The `slide` transition does not work correctly for elements with `display: %value%` diff --git a/packages/svelte/package.json b/packages/svelte/package.json index 2a1b3cf9e516..aec8a3447839 100644 --- a/packages/svelte/package.json +++ b/packages/svelte/package.json @@ -2,7 +2,7 @@ "name": "svelte", "description": "Cybernetically enhanced web apps", "license": "MIT", - "version": "5.36.5", + "version": "5.36.6", "type": "module", "types": "./types/index.d.ts", "engines": { diff --git a/packages/svelte/src/ambient.d.ts b/packages/svelte/src/ambient.d.ts index 7c3b941ed1fb..ad32eaa56f5e 100644 --- a/packages/svelte/src/ambient.d.ts +++ b/packages/svelte/src/ambient.d.ts @@ -295,13 +295,13 @@ declare namespace $effect { * let count = $state(0); * * const cleanup = $effect.root(() => { - * $effect(() => { - * console.log(count); - * }) + * $effect(() => { + * console.log(count); + * }) * - * return () => { - * console.log('effect root cleanup'); - * } + * return () => { + * console.log('effect root cleanup'); + * } * }); * * diff --git a/packages/svelte/src/compiler/phases/2-analyze/visitors/Attribute.js b/packages/svelte/src/compiler/phases/2-analyze/visitors/Attribute.js index 773aa597444b..b13f3f89b624 100644 --- a/packages/svelte/src/compiler/phases/2-analyze/visitors/Attribute.js +++ b/packages/svelte/src/compiler/phases/2-analyze/visitors/Attribute.js @@ -192,8 +192,13 @@ function get_delegated_event(event_name, handler, context) { return unhoisted; } - // If we are referencing a binding that is shadowed in another scope then bail out. - if (local_binding !== null && binding !== null && local_binding.node !== binding.node) { + // If we are referencing a binding that is shadowed in another scope then bail out (unless it's declared within the function). + if ( + local_binding !== null && + binding !== null && + local_binding.node !== binding.node && + scope.declarations.get(reference) !== binding + ) { return unhoisted; } diff --git a/packages/svelte/src/internal/client/dom/blocks/boundary.js b/packages/svelte/src/internal/client/dom/blocks/boundary.js index 5e678ab113ce..4ea137bfa8b8 100644 --- a/packages/svelte/src/internal/client/dom/blocks/boundary.js +++ b/packages/svelte/src/internal/client/dom/blocks/boundary.js @@ -1,7 +1,12 @@ /** @import { Effect, Source, TemplateNode, } from '#client' */ -import { BOUNDARY_EFFECT, EFFECT_PRESERVED, EFFECT_TRANSPARENT } from '#client/constants'; +import { + BOUNDARY_EFFECT, + EFFECT_PRESERVED, + EFFECT_RAN, + EFFECT_TRANSPARENT +} from '#client/constants'; import { component_context, set_component_context } from '../../context.js'; -import { invoke_error_boundary } from '../../error-handling.js'; +import { handle_error, invoke_error_boundary } from '../../error-handling.js'; import { block, branch, destroy_effect, pause_effect } from '../../reactivity/effects.js'; import { active_effect, @@ -21,6 +26,7 @@ import { import { get_next_sibling } from '../operations.js'; import { queue_micro_task } from '../task.js'; import * as e from '../../errors.js'; +import * as w from '../../warnings.js'; import { DEV } from 'esm-env'; import { Batch, effect_pending_updates } from '../../reactivity/batch.js'; import { internal_set, source } from '../../reactivity/sources.js'; @@ -196,6 +202,9 @@ export class Boundary { try { return fn(); + } catch (e) { + handle_error(e); + return null; } finally { set_active_effect(previous_effect); set_active_reaction(previous_reaction); @@ -257,7 +266,42 @@ export class Boundary { var onerror = this.#props.onerror; let failed = this.#props.failed; + if (this.#main_effect) { + destroy_effect(this.#main_effect); + this.#main_effect = null; + } + + if (this.#pending_effect) { + destroy_effect(this.#pending_effect); + this.#pending_effect = null; + } + + if (this.#failed_effect) { + destroy_effect(this.#failed_effect); + this.#failed_effect = null; + } + + if (hydrating) { + set_hydrate_node(this.#hydrate_open); + next(); + set_hydrate_node(remove_nodes()); + } + + var did_reset = false; + var calling_on_error = false; + const reset = () => { + if (did_reset) { + w.svelte_boundary_reset_noop(); + return; + } + + did_reset = true; + + if (calling_on_error) { + e.svelte_boundary_reset_onerror(); + } + this.#pending_count = 0; if (this.#failed_effect !== null) { @@ -290,32 +334,15 @@ export class Boundary { try { set_active_reaction(null); + calling_on_error = true; onerror?.(error, reset); + calling_on_error = false; + } catch (error) { + invoke_error_boundary(error, this.#effect && this.#effect.parent); } finally { set_active_reaction(previous_reaction); } - if (this.#main_effect) { - destroy_effect(this.#main_effect); - this.#main_effect = null; - } - - if (this.#pending_effect) { - destroy_effect(this.#pending_effect); - this.#pending_effect = null; - } - - if (this.#failed_effect) { - destroy_effect(this.#failed_effect); - this.#failed_effect = null; - } - - if (hydrating) { - set_hydrate_node(this.#hydrate_open); - next(); - set_hydrate_node(remove_nodes()); - } - if (failed) { queue_micro_task(() => { this.#failed_effect = this.#run(() => { diff --git a/packages/svelte/src/internal/client/error-handling.js b/packages/svelte/src/internal/client/error-handling.js index a512839181d0..6c83a453d540 100644 --- a/packages/svelte/src/internal/client/error-handling.js +++ b/packages/svelte/src/internal/client/error-handling.js @@ -53,7 +53,9 @@ export function invoke_error_boundary(error, effect) { try { /** @type {Boundary} */ (effect.b).error(error); return; - } catch {} + } catch (e) { + error = e; + } } effect = effect.parent; diff --git a/packages/svelte/src/internal/client/errors.js b/packages/svelte/src/internal/client/errors.js index edd66a7400d7..937971da5e0b 100644 --- a/packages/svelte/src/internal/client/errors.js +++ b/packages/svelte/src/internal/client/errors.js @@ -423,4 +423,20 @@ export function state_unsafe_mutation() { } else { throw new Error(`https://svelte.dev/e/state_unsafe_mutation`); } +} + +/** + * A `` `reset` function cannot be called while an error is still being handled + * @returns {never} + */ +export function svelte_boundary_reset_onerror() { + if (DEV) { + const error = new Error(`svelte_boundary_reset_onerror\nA \`\` \`reset\` function cannot be called while an error is still being handled\nhttps://svelte.dev/e/svelte_boundary_reset_onerror`); + + error.name = 'Svelte error'; + + throw error; + } else { + throw new Error(`https://svelte.dev/e/svelte_boundary_reset_onerror`); + } } \ No newline at end of file diff --git a/packages/svelte/src/internal/client/warnings.js b/packages/svelte/src/internal/client/warnings.js index dfd50a8722c9..dfa2a3752e52 100644 --- a/packages/svelte/src/internal/client/warnings.js +++ b/packages/svelte/src/internal/client/warnings.js @@ -224,6 +224,17 @@ export function state_proxy_equality_mismatch(operator) { } } +/** + * A `` `reset` function only resets the boundary the first time it is called + */ +export function svelte_boundary_reset_noop() { + if (DEV) { + console.warn(`%c[svelte] svelte_boundary_reset_noop\n%cA \`\` \`reset\` function only resets the boundary the first time it is called\nhttps://svelte.dev/e/svelte_boundary_reset_noop`, bold, normal); + } else { + console.warn(`https://svelte.dev/e/svelte_boundary_reset_noop`); + } +} + /** * The `slide` transition does not work correctly for elements with `display: %value%` * @param {string} value diff --git a/packages/svelte/src/version.js b/packages/svelte/src/version.js index 4d6811c409df..b0e4ebbab8ca 100644 --- a/packages/svelte/src/version.js +++ b/packages/svelte/src/version.js @@ -4,5 +4,5 @@ * The current version, as set in package.json. * @type {string} */ -export const VERSION = '5.36.5'; +export const VERSION = '5.36.6'; export const PUBLIC_VERSION = '5'; diff --git a/packages/svelte/svelte-html.d.ts b/packages/svelte/svelte-html.d.ts index 5042eaa4b849..476b24e275aa 100644 --- a/packages/svelte/svelte-html.d.ts +++ b/packages/svelte/svelte-html.d.ts @@ -50,6 +50,7 @@ declare global { ? SVGElementTagNameMap[Key] : any; + // TODO remove HTMLAttributes/SVGAttributes/IntrinsicElements in Svelte 6 // For backwards-compatibility and ease-of-use, in case someone enhanced the typings from import('svelte/elements').HTMLAttributes/SVGAttributes // eslint-disable-next-line @typescript-eslint/no-unused-vars interface HTMLAttributes {} diff --git a/packages/svelte/tests/runtime-legacy/shared.ts b/packages/svelte/tests/runtime-legacy/shared.ts index 8d7b3544bc6e..05c1a982ec6f 100644 --- a/packages/svelte/tests/runtime-legacy/shared.ts +++ b/packages/svelte/tests/runtime-legacy/shared.ts @@ -489,10 +489,11 @@ async function run_test_variant( 'Expected component to unmount and leave nothing behind after it was destroyed' ); - // TODO: This seems useless, unhandledRejection is only triggered on the next task - // by which time the test has already finished and the next test resets it to null above + // uncaught errors like during template effects flush if (unhandled_rejection) { - throw unhandled_rejection; // eslint-disable-line no-unsafe-finally + if (!config.expect_unhandled_rejections) { + throw unhandled_rejection; // eslint-disable-line no-unsafe-finally + } } } } diff --git a/packages/svelte/tests/runtime-runes/samples/error-boundary-reset-onerror/_config.js b/packages/svelte/tests/runtime-runes/samples/error-boundary-reset-onerror/_config.js new file mode 100644 index 000000000000..092d7ad37d57 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/error-boundary-reset-onerror/_config.js @@ -0,0 +1,15 @@ +import { flushSync } from 'svelte'; +import { test } from '../../test'; + +export default test({ + test({ assert, target }) { + const btn = target.querySelector('button'); + + btn?.click(); + + assert.throws(flushSync, 'svelte_boundary_reset_onerror'); + + // boundary content empty; only button remains + assert.htmlEqual(target.innerHTML, ``); + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/error-boundary-reset-onerror/main.svelte b/packages/svelte/tests/runtime-runes/samples/error-boundary-reset-onerror/main.svelte new file mode 100644 index 000000000000..f91048a9e778 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/error-boundary-reset-onerror/main.svelte @@ -0,0 +1,17 @@ + + + reset()}> + {must_throw ? throw_error() : 'normal content'} + + {#snippet failed()} +
err
+ {/snippet} +
+ + diff --git a/packages/svelte/tests/runtime-runes/samples/error-boundary-reset-premature/_config.js b/packages/svelte/tests/runtime-runes/samples/error-boundary-reset-premature/_config.js new file mode 100644 index 000000000000..687961e721d0 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/error-boundary-reset-premature/_config.js @@ -0,0 +1,28 @@ +import { flushSync } from 'svelte'; +import { test } from '../../test'; + +export default test({ + html: ` + normal content + + `, + + async test({ assert, target, warnings }) { + const [btn] = target.querySelectorAll('button'); + + flushSync(() => btn.click()); + assert.htmlEqual(target.innerHTML, `
err
`); + assert.deepEqual(warnings, []); + + flushSync(() => btn.click()); + assert.htmlEqual(target.innerHTML, `normal content `); + assert.deepEqual(warnings, []); + + flushSync(() => btn.click()); + assert.htmlEqual(target.innerHTML, `
err
`); + + assert.deepEqual(warnings, [ + 'A `` `reset` function only resets the boundary the first time it is called' + ]); + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/error-boundary-reset-premature/main.svelte b/packages/svelte/tests/runtime-runes/samples/error-boundary-reset-premature/main.svelte new file mode 100644 index 000000000000..c1462eaf09c2 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/error-boundary-reset-premature/main.svelte @@ -0,0 +1,26 @@ + + + + (reset = fn)}> + {must_throw ? throw_error() : 'normal content'} + + {#snippet failed()} +
err
+ {/snippet} +
+
+ + diff --git a/packages/svelte/tests/runtime-runes/samples/error-boundary-reset-with-error/_config.js b/packages/svelte/tests/runtime-runes/samples/error-boundary-reset-with-error/_config.js new file mode 100644 index 000000000000..844e6981bd44 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/error-boundary-reset-with-error/_config.js @@ -0,0 +1,27 @@ +import { flushSync } from 'svelte'; +import { test } from '../../test'; + +export default test({ + test({ assert, target, warnings }) { + const [toggle] = target.querySelectorAll('button'); + + flushSync(() => toggle.click()); + assert.htmlEqual( + target.innerHTML, + `

yikes!

` + ); + + const [, reset] = target.querySelectorAll('button'); + flushSync(() => reset.click()); + assert.htmlEqual( + target.innerHTML, + `

yikes!

` + ); + + flushSync(() => toggle.click()); + + const [, reset2] = target.querySelectorAll('button'); + flushSync(() => reset2.click()); + assert.htmlEqual(target.innerHTML, `

hello!

`); + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/error-boundary-reset-with-error/main.svelte b/packages/svelte/tests/runtime-runes/samples/error-boundary-reset-with-error/main.svelte new file mode 100644 index 000000000000..91479a631ade --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/error-boundary-reset-with-error/main.svelte @@ -0,0 +1,20 @@ + + + + + +

{must_throw ? throw_error() : 'hello!'}

+ + {#snippet failed(error, reset)} +

{error.message}

+ + {/snippet} +
+ + diff --git a/packages/svelte/tests/snapshot/samples/delegated-locally-declared-shadowed/_expected/client/index.svelte.js b/packages/svelte/tests/snapshot/samples/delegated-locally-declared-shadowed/_expected/client/index.svelte.js new file mode 100644 index 000000000000..0d95d8d33551 --- /dev/null +++ b/packages/svelte/tests/snapshot/samples/delegated-locally-declared-shadowed/_expected/client/index.svelte.js @@ -0,0 +1,28 @@ +import 'svelte/internal/disclose-version'; +import 'svelte/internal/flags/legacy'; +import * as $ from 'svelte/internal/client'; + +var on_click = (e) => { + const index = Number(e.currentTarget.dataset.index); + + console.log(index); +}; + +var root_1 = $.from_html(``); + +export default function Delegated_locally_declared_shadowed($$anchor) { + var fragment = $.comment(); + var node = $.first_child(fragment); + + $.each(node, 0, () => ({ length: 1 }), $.index, ($$anchor, $$item, index) => { + var button = root_1(); + + $.set_attribute(button, 'data-index', index); + button.__click = [on_click]; + $.append($$anchor, button); + }); + + $.append($$anchor, fragment); +} + +$.delegate(['click']); \ No newline at end of file diff --git a/packages/svelte/tests/snapshot/samples/delegated-locally-declared-shadowed/_expected/server/index.svelte.js b/packages/svelte/tests/snapshot/samples/delegated-locally-declared-shadowed/_expected/server/index.svelte.js new file mode 100644 index 000000000000..e465af6f8b25 --- /dev/null +++ b/packages/svelte/tests/snapshot/samples/delegated-locally-declared-shadowed/_expected/server/index.svelte.js @@ -0,0 +1,13 @@ +import * as $ from 'svelte/internal/server'; + +export default function Delegated_locally_declared_shadowed($$payload) { + const each_array = $.ensure_array_like({ length: 1 }); + + $$payload.out += ``; + + for (let index = 0, $$length = each_array.length; index < $$length; index++) { + $$payload.out += ``; + } + + $$payload.out += ``; +} \ No newline at end of file diff --git a/packages/svelte/tests/snapshot/samples/delegated-locally-declared-shadowed/index.svelte b/packages/svelte/tests/snapshot/samples/delegated-locally-declared-shadowed/index.svelte new file mode 100644 index 000000000000..d870a6b27025 --- /dev/null +++ b/packages/svelte/tests/snapshot/samples/delegated-locally-declared-shadowed/index.svelte @@ -0,0 +1,12 @@ + + +{#each { length: 1 }, index} + +{/each} diff --git a/packages/svelte/types/index.d.ts b/packages/svelte/types/index.d.ts index a8b769d6d4c3..9ea45af7e6b7 100644 --- a/packages/svelte/types/index.d.ts +++ b/packages/svelte/types/index.d.ts @@ -3369,13 +3369,13 @@ declare namespace $effect { * let count = $state(0); * * const cleanup = $effect.root(() => { - * $effect(() => { - * console.log(count); - * }) + * $effect(() => { + * console.log(count); + * }) * - * return () => { - * console.log('effect root cleanup'); - * } + * return () => { + * console.log('effect root cleanup'); + * } * }); * * diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 5f902186ef11..315d699e25ac 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -12,8 +12,8 @@ importers: specifier: ^2.27.8 version: 2.27.8 '@sveltejs/eslint-config': - specifier: ^8.1.0 - version: 8.1.0(@stylistic/eslint-plugin-js@1.8.0(eslint@9.9.1))(eslint-config-prettier@9.1.0(eslint@9.9.1))(eslint-plugin-n@17.16.1(eslint@9.9.1)(typescript@5.5.4))(eslint-plugin-svelte@2.38.0(eslint@9.9.1)(svelte@packages+svelte))(eslint@9.9.1)(typescript-eslint@8.26.0(eslint@9.9.1)(typescript@5.5.4))(typescript@5.5.4) + specifier: ^8.3.3 + version: 8.3.3(@stylistic/eslint-plugin-js@1.8.0(eslint@9.9.1))(eslint-config-prettier@9.1.0(eslint@9.9.1))(eslint-plugin-n@17.16.1(eslint@9.9.1)(typescript@5.5.4))(eslint-plugin-svelte@3.11.0(eslint@9.9.1)(svelte@packages+svelte))(eslint@9.9.1)(typescript-eslint@8.26.0(eslint@9.9.1)(typescript@5.5.4))(typescript@5.5.4) '@svitejs/changesets-changelog-github-compact': specifier: ^1.1.0 version: 1.1.0 @@ -29,6 +29,9 @@ importers: eslint-plugin-lube: specifier: ^0.4.3 version: 0.4.3 + eslint-plugin-svelte: + specifier: ^3.11.0 + version: 3.11.0(eslint@9.9.1)(svelte@packages+svelte) jsdom: specifier: 25.0.1 version: 25.0.1 @@ -737,16 +740,16 @@ packages: peerDependencies: acorn: ^8.9.0 - '@sveltejs/eslint-config@8.1.0': - resolution: {integrity: sha512-cfgp4lPREYBjNd4ZzaP/jA85ufm7vfXiaV7h9vILXNogne80IbZRNhRCQ8XoOqTAOY/pChIzWTBuR8aDNMbAEA==} + '@sveltejs/eslint-config@8.3.3': + resolution: {integrity: sha512-vkrQgEmhokFEOpuTo7NlVXJJMJJGNzxjmkQCTkHSwIOdzQSUukDIJ4038IjdcnIERSIlo4OpLAydWLx52BVyQA==} peerDependencies: '@stylistic/eslint-plugin-js': '>= 1' eslint: '>= 9' eslint-config-prettier: '>= 9' eslint-plugin-n: '>= 17' - eslint-plugin-svelte: '>= 2.36' + eslint-plugin-svelte: '>= 3' typescript: '>= 5' - typescript-eslint: '>= 7.5' + typescript-eslint: '>= 8' '@sveltejs/vite-plugin-svelte-inspector@3.0.0-next.2': resolution: {integrity: sha512-Yl9BWvEj+1j+8mICIAA04/Sx0wEHNL0n9pSIZFM8n4NWgLFmR3v41qX2k54J/r4LWE2YHTeNNH2WJqEUb3geEA==} @@ -1209,24 +1212,24 @@ packages: peerDependencies: eslint: '>=8.23.0' - eslint-plugin-svelte@2.38.0: - resolution: {integrity: sha512-IwwxhHzitx3dr0/xo0z4jjDlb2AAHBPKt+juMyKKGTLlKi1rZfA4qixMwnveU20/JTHyipM6keX4Vr7LZFYc9g==} - engines: {node: ^14.17.0 || >=16.0.0} + eslint-plugin-svelte@3.11.0: + resolution: {integrity: sha512-KliWlkieHyEa65aQIkRwUFfHzT5Cn4u3BQQsu3KlkJOs7c1u7ryn84EWaOjEzilbKgttT4OfBURA8Uc4JBSQIw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: - eslint: ^7.0.0 || ^8.0.0-0 || ^9.0.0-0 - svelte: ^3.37.0 || ^4.0.0 || ^5.0.0-next.112 + eslint: ^8.57.1 || ^9.0.0 + svelte: ^3.37.0 || ^4.0.0 || ^5.0.0 peerDependenciesMeta: svelte: optional: true - eslint-scope@7.2.2: - resolution: {integrity: sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - eslint-scope@8.0.2: resolution: {integrity: sha512-6E4xmrTw5wtxnLA5wYL3WDfhZ/1bUBGOXV0zQvVRDOtrR8D0p6W7fs3JweNYhwRYeGvd/1CKX2se0/2s7Q/nJA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + eslint-scope@8.4.0: + resolution: {integrity: sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + eslint-visitor-keys@3.4.3: resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -1400,14 +1403,14 @@ packages: resolution: {integrity: sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==} engines: {node: '>=18'} - globals@15.14.0: - resolution: {integrity: sha512-OkToC372DtlQeje9/zHIo5CT8lRP/FUgEOKBEhU4e0abL7J7CD24fD9ohiLN5hagG/kWCYj4K5oaxxtj2Z0Dig==} - engines: {node: '>=18'} - globals@15.15.0: resolution: {integrity: sha512-7ACyT3wmyp3I61S4fG682L0VA2RGD9otkqGJIwNUMF1SWUombIIk+af1unuDYgMm082aHYwD+mzJvv9Iu8dsgg==} engines: {node: '>=18'} + globals@16.3.0: + resolution: {integrity: sha512-bqWEnJ1Nt3neqx2q5SFfGS8r/ahumIakg3HcwtNlrVlwXIeNumWn/c7Pn/wKzGhf6SaW6H6uWXLqC30STCMchQ==} + engines: {node: '>=18'} + globby@11.1.0: resolution: {integrity: sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==} engines: {node: '>=10'} @@ -1589,8 +1592,8 @@ packages: resolution: {integrity: sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==} engines: {node: '>=6'} - known-css-properties@0.30.0: - resolution: {integrity: sha512-VSWXYUnsPu9+WYKkfmJyLKtIvaRJi1kXUqVmBACORXZQxT5oZDsoZ2vQP+bQFDnWtpI/4eq3MLoRMjI2fnLzTQ==} + known-css-properties@0.37.0: + resolution: {integrity: sha512-JCDrsP4Z1Sb9JwG0aJ8Eo2r7k4Ou5MwmThS/6lcIe1ICyb7UBJKGRIUUdqc2ASdE/42lgz6zFUnzAIhtXnBVrQ==} levn@0.4.1: resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} @@ -1886,11 +1889,11 @@ packages: ts-node: optional: true - postcss-safe-parser@6.0.0: - resolution: {integrity: sha512-FARHN8pwH+WiS2OPCxJI8FuRJpTVnn6ZNFiqAM2aeW2LwTHWWmWgIyKC6cUo0L8aeKiF/14MNvnpls6R2PBeMQ==} - engines: {node: '>=12.0'} + postcss-safe-parser@7.0.1: + resolution: {integrity: sha512-0AioNCJZ2DPYz5ABT6bddIqlhgwhpHZ/l65YAYo0BCIn0xiDpsnTHz0gnoTGk0OXZW0JRs+cDwL8u/teRdz+8A==} + engines: {node: '>=18.0'} peerDependencies: - postcss: ^8.3.3 + postcss: ^8.4.31 postcss-scss@4.0.9: resolution: {integrity: sha512-AjKOeiwAitL/MXxQW2DliT28EKukvvbEWx3LBmJIRN8KfBGZbRTxNYW0kSqi1COiTZ57nZ9NW06S6ux//N1c9A==} @@ -1898,8 +1901,8 @@ packages: peerDependencies: postcss: ^8.4.29 - postcss-selector-parser@6.1.2: - resolution: {integrity: sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==} + postcss-selector-parser@7.1.0: + resolution: {integrity: sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA==} engines: {node: '>=4'} postcss@8.5.3: @@ -2121,9 +2124,9 @@ packages: resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} engines: {node: '>= 0.4'} - svelte-eslint-parser@0.43.0: - resolution: {integrity: sha512-GpU52uPKKcVnh8tKN5P4UZpJ/fUDndmq7wfsvoVXsyP+aY0anol7Yqo01fyrlaWGMFfm4av5DyrjlaXdLRJvGA==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + svelte-eslint-parser@1.3.0: + resolution: {integrity: sha512-VCgMHKV7UtOGcGLGNFSbmdm6kEKjtzo5nnpGU/mnx4OsFY6bZ7QwRF5DUx+Hokw5Lvdyo8dpk8B1m8mliomrNg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: svelte: ^3.37.0 || ^4.0.0 || ^5.0.0 peerDependenciesMeta: @@ -2998,14 +3001,14 @@ snapshots: dependencies: acorn: 8.14.0 - '@sveltejs/eslint-config@8.1.0(@stylistic/eslint-plugin-js@1.8.0(eslint@9.9.1))(eslint-config-prettier@9.1.0(eslint@9.9.1))(eslint-plugin-n@17.16.1(eslint@9.9.1)(typescript@5.5.4))(eslint-plugin-svelte@2.38.0(eslint@9.9.1)(svelte@packages+svelte))(eslint@9.9.1)(typescript-eslint@8.26.0(eslint@9.9.1)(typescript@5.5.4))(typescript@5.5.4)': + '@sveltejs/eslint-config@8.3.3(@stylistic/eslint-plugin-js@1.8.0(eslint@9.9.1))(eslint-config-prettier@9.1.0(eslint@9.9.1))(eslint-plugin-n@17.16.1(eslint@9.9.1)(typescript@5.5.4))(eslint-plugin-svelte@3.11.0(eslint@9.9.1)(svelte@packages+svelte))(eslint@9.9.1)(typescript-eslint@8.26.0(eslint@9.9.1)(typescript@5.5.4))(typescript@5.5.4)': dependencies: '@stylistic/eslint-plugin-js': 1.8.0(eslint@9.9.1) eslint: 9.9.1 eslint-config-prettier: 9.1.0(eslint@9.9.1) eslint-plugin-n: 17.16.1(eslint@9.9.1)(typescript@5.5.4) - eslint-plugin-svelte: 2.38.0(eslint@9.9.1)(svelte@packages+svelte) - globals: 15.14.0 + eslint-plugin-svelte: 3.11.0(eslint@9.9.1)(svelte@packages+svelte) + globals: 15.15.0 typescript: 5.5.4 typescript-eslint: 8.26.0(eslint@9.9.1)(typescript@5.5.4) @@ -3125,7 +3128,7 @@ snapshots: fast-glob: 3.3.2 is-glob: 4.0.3 minimatch: 9.0.5 - semver: 7.7.1 + semver: 7.7.2 ts-api-utils: 2.0.1(typescript@5.5.4) typescript: 5.5.4 transitivePeerDependencies: @@ -3532,33 +3535,30 @@ snapshots: - supports-color - typescript - eslint-plugin-svelte@2.38.0(eslint@9.9.1)(svelte@packages+svelte): + eslint-plugin-svelte@3.11.0(eslint@9.9.1)(svelte@packages+svelte): dependencies: '@eslint-community/eslint-utils': 4.7.0(eslint@9.9.1) '@jridgewell/sourcemap-codec': 1.5.0 - debug: 4.4.0 eslint: 9.9.1 - eslint-compat-utils: 0.5.1(eslint@9.9.1) esutils: 2.0.3 - known-css-properties: 0.30.0 + globals: 16.3.0 + known-css-properties: 0.37.0 postcss: 8.5.3 postcss-load-config: 3.1.4(postcss@8.5.3) - postcss-safe-parser: 6.0.0(postcss@8.5.3) - postcss-selector-parser: 6.1.2 + postcss-safe-parser: 7.0.1(postcss@8.5.3) semver: 7.7.2 - svelte-eslint-parser: 0.43.0(svelte@packages+svelte) + svelte-eslint-parser: 1.3.0(svelte@packages+svelte) optionalDependencies: svelte: link:packages/svelte transitivePeerDependencies: - - supports-color - ts-node - eslint-scope@7.2.2: + eslint-scope@8.0.2: dependencies: esrecurse: 4.3.0 estraverse: 5.3.0 - eslint-scope@8.0.2: + eslint-scope@8.4.0: dependencies: esrecurse: 4.3.0 estraverse: 5.3.0 @@ -3769,10 +3769,10 @@ snapshots: globals@14.0.0: {} - globals@15.14.0: {} - globals@15.15.0: {} + globals@16.3.0: {} + globby@11.1.0: dependencies: array-union: 2.1.0 @@ -3971,7 +3971,7 @@ snapshots: kleur@4.1.5: {} - known-css-properties@0.30.0: {} + known-css-properties@0.37.0: {} levn@0.4.1: dependencies: @@ -4209,7 +4209,7 @@ snapshots: optionalDependencies: postcss: 8.5.3 - postcss-safe-parser@6.0.0(postcss@8.5.3): + postcss-safe-parser@7.0.1(postcss@8.5.3): dependencies: postcss: 8.5.3 @@ -4217,7 +4217,7 @@ snapshots: dependencies: postcss: 8.5.3 - postcss-selector-parser@6.1.2: + postcss-selector-parser@7.1.0: dependencies: cssesc: 3.0.0 util-deprecate: 1.0.2 @@ -4442,13 +4442,14 @@ snapshots: supports-preserve-symlinks-flag@1.0.0: {} - svelte-eslint-parser@0.43.0(svelte@packages+svelte): + svelte-eslint-parser@1.3.0(svelte@packages+svelte): dependencies: - eslint-scope: 7.2.2 - eslint-visitor-keys: 3.4.3 - espree: 9.6.1 + eslint-scope: 8.4.0 + eslint-visitor-keys: 4.2.0 + espree: 10.1.0 postcss: 8.5.3 postcss-scss: 4.0.9(postcss@8.5.3) + postcss-selector-parser: 7.1.0 optionalDependencies: svelte: link:packages/svelte diff --git a/vitest.config.js b/vitest.config.js index caeda27e3065..ba1edb355b8f 100644 --- a/vitest.config.js +++ b/vitest.config.js @@ -39,7 +39,7 @@ export default defineConfig({ provider: 'v8', reporter: ['lcov', 'html'], include: ['packages/svelte/src/**'], - reportsDirectory: 'sites/svelte-5-preview/static/coverage', + reportsDirectory: 'coverage', reportOnFailure: true } } pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy