From ed7ebcde1e8f01c49bc3082d00724e092803ac63 Mon Sep 17 00:00:00 2001 From: Dominic Gannaway Date: Sun, 1 Dec 2024 13:53:42 +0000 Subject: [PATCH] feat: add error boundaries (#14211) * feat: add error boundary support tweak tweak again retry -> reset tweaks add tests tweaks tweaks tweaks more tests more tests and tweaks comments tweak tweak tweak tweak tweak * tweak tweak tweak tweak more fixes tweak tweak more fixes changeset * Update packages/svelte/elements.d.ts Co-authored-by: Simon H <5968653+dummdidumm@users.noreply.github.com> * Update .changeset/polite-peaches-do.md Co-authored-by: Simon H <5968653+dummdidumm@users.noreply.github.com> * fix issue with rethrowing * handle fallback error * handle fallback error * add more test coverage * more tests * more bug fixes * guard against non-errors * add component_stack to error * alternative approach * remove spread support * lint * add to legacy ast * add to svelte-html * disallow anything but attributes on the boundary element * fix error * more validation * only create block when necessary * swap argument order - results in nicer-looking code in many cases * Update .changeset/polite-peaches-do.md * simplify a bit * simplify * move declaration closer to usage * push once * unused * tweaks * consistent naming * simplify * add a couple newlines * tweak comments * simplify * newlines * placeholder documentation * add some docs * Update packages/svelte/src/internal/client/dom/blocks/boundary.js Co-authored-by: Rich Harris * Update packages/svelte/src/internal/client/dom/blocks/boundary.js Co-authored-by: Rich Harris * Update packages/svelte/src/internal/client/dom/blocks/boundary.js Co-authored-by: Rich Harris * fix type * fix link * explain what happens if onerror throws --------- Co-authored-by: Simon H <5968653+dummdidumm@users.noreply.github.com> Co-authored-by: Simon Holthausen Co-authored-by: Rich Harris --- .changeset/polite-peaches-do.md | 5 + .../05-special-elements/01-svelte-boundary.md | 79 ++++++++++ ...1-svelte-window.md => 02-svelte-window.md} | 0 ...elte-document.md => 03-svelte-document.md} | 0 .../{03-svelte-body.md => 04-svelte-body.md} | 0 .../{04-svelte-head.md => 05-svelte-head.md} | 0 ...svelte-element.md => 06-svelte-element.md} | 0 ...svelte-options.md => 07-svelte-options.md} | 0 .../98-reference/.generated/compile-errors.md | 12 ++ packages/svelte/elements.d.ts | 4 + .../messages/compile-errors/template.md | 8 + packages/svelte/src/compiler/errors.js | 18 +++ packages/svelte/src/compiler/legacy.js | 14 ++ .../compiler/phases/1-parse/state/element.js | 3 +- .../src/compiler/phases/2-analyze/index.js | 2 + .../2-analyze/visitors/SvelteBoundary.js | 27 ++++ .../3-transform/client/transform-client.js | 2 + .../client/visitors/SvelteBoundary.js | 61 ++++++++ .../3-transform/server/transform-server.js | 4 +- .../server/visitors/SvelteBoundary.js | 17 +++ .../src/compiler/phases/3-transform/utils.js | 1 + .../svelte/src/compiler/types/template.d.ts | 8 +- .../svelte/src/internal/client/constants.js | 27 ++-- .../internal/client/dom/blocks/boundary.js | 134 +++++++++++++++++ .../src/internal/client/dom/template.js | 1 - packages/svelte/src/internal/client/index.js | 1 + .../src/internal/client/reactivity/effects.js | 2 +- .../svelte/src/internal/client/runtime.js | 142 ++++++++++++++---- packages/svelte/svelte-html.d.ts | 4 + .../samples/svelte-selfdestructive/_config.js | 2 +- .../samples/error-boundary-10/_config.js | 14 ++ .../samples/error-boundary-10/main.svelte | 17 +++ .../samples/error-boundary-11/_config.js | 14 ++ .../samples/error-boundary-11/main.svelte | 17 +++ .../samples/error-boundary-12/_config.js | 13 ++ .../samples/error-boundary-12/main.svelte | 22 +++ .../samples/error-boundary-13/Child.svelte | 7 + .../samples/error-boundary-13/_config.js | 13 ++ .../samples/error-boundary-13/main.svelte | 22 +++ .../samples/error-boundary-14/Child.svelte | 9 ++ .../samples/error-boundary-14/_config.js | 9 ++ .../samples/error-boundary-14/main.svelte | 9 ++ .../samples/error-boundary-15/Child.svelte | 9 ++ .../samples/error-boundary-15/_config.js | 9 ++ .../samples/error-boundary-15/main.svelte | 19 +++ .../samples/error-boundary-16/Child.svelte | 13 ++ .../samples/error-boundary-16/_config.js | 24 +++ .../samples/error-boundary-16/main.svelte | 12 ++ .../samples/error-boundary-17/Child.svelte | 15 ++ .../samples/error-boundary-17/_config.js | 15 ++ .../samples/error-boundary-17/main.svelte | 18 +++ .../samples/error-boundary-18/_config.js | 15 ++ .../samples/error-boundary-18/main.svelte | 16 ++ .../samples/error-boundary-19/_config.js | 14 ++ .../samples/error-boundary-19/main.svelte | 22 +++ .../samples/error-boundary-2/_config.js | 13 ++ .../samples/error-boundary-2/main.svelte | 15 ++ .../samples/error-boundary-3/_config.js | 14 ++ .../samples/error-boundary-3/main.svelte | 17 +++ .../samples/error-boundary-4/_config.js | 14 ++ .../samples/error-boundary-4/main.svelte | 17 +++ .../samples/error-boundary-5/Child.svelte | 13 ++ .../samples/error-boundary-5/_config.js | 31 ++++ .../samples/error-boundary-5/main.svelte | 18 +++ .../samples/error-boundary-6/_config.js | 13 ++ .../samples/error-boundary-6/main.svelte | 20 +++ .../samples/error-boundary-7/Child.svelte | 5 + .../samples/error-boundary-7/_config.js | 8 + .../samples/error-boundary-7/main.svelte | 14 ++ .../samples/error-boundary-8/Child.svelte | 5 + .../samples/error-boundary-8/_config.js | 8 + .../samples/error-boundary-8/main.svelte | 17 +++ .../samples/error-boundary-9/Child.svelte | 5 + .../samples/error-boundary-9/_config.js | 8 + .../samples/error-boundary-9/main.svelte | 20 +++ .../samples/error-boundary/_config.js | 9 ++ .../samples/error-boundary/main.svelte | 11 ++ packages/svelte/types/index.d.ts | 8 +- 78 files changed, 1199 insertions(+), 49 deletions(-) create mode 100644 .changeset/polite-peaches-do.md create mode 100644 documentation/docs/05-special-elements/01-svelte-boundary.md rename documentation/docs/05-special-elements/{01-svelte-window.md => 02-svelte-window.md} (100%) rename documentation/docs/05-special-elements/{02-svelte-document.md => 03-svelte-document.md} (100%) rename documentation/docs/05-special-elements/{03-svelte-body.md => 04-svelte-body.md} (100%) rename documentation/docs/05-special-elements/{04-svelte-head.md => 05-svelte-head.md} (100%) rename documentation/docs/05-special-elements/{05-svelte-element.md => 06-svelte-element.md} (100%) rename documentation/docs/05-special-elements/{06-svelte-options.md => 07-svelte-options.md} (100%) create mode 100644 packages/svelte/src/compiler/phases/2-analyze/visitors/SvelteBoundary.js create mode 100644 packages/svelte/src/compiler/phases/3-transform/client/visitors/SvelteBoundary.js create mode 100644 packages/svelte/src/compiler/phases/3-transform/server/visitors/SvelteBoundary.js create mode 100644 packages/svelte/src/internal/client/dom/blocks/boundary.js create mode 100644 packages/svelte/tests/runtime-runes/samples/error-boundary-10/_config.js create mode 100644 packages/svelte/tests/runtime-runes/samples/error-boundary-10/main.svelte create mode 100644 packages/svelte/tests/runtime-runes/samples/error-boundary-11/_config.js create mode 100644 packages/svelte/tests/runtime-runes/samples/error-boundary-11/main.svelte create mode 100644 packages/svelte/tests/runtime-runes/samples/error-boundary-12/_config.js create mode 100644 packages/svelte/tests/runtime-runes/samples/error-boundary-12/main.svelte create mode 100644 packages/svelte/tests/runtime-runes/samples/error-boundary-13/Child.svelte create mode 100644 packages/svelte/tests/runtime-runes/samples/error-boundary-13/_config.js create mode 100644 packages/svelte/tests/runtime-runes/samples/error-boundary-13/main.svelte create mode 100644 packages/svelte/tests/runtime-runes/samples/error-boundary-14/Child.svelte create mode 100644 packages/svelte/tests/runtime-runes/samples/error-boundary-14/_config.js create mode 100644 packages/svelte/tests/runtime-runes/samples/error-boundary-14/main.svelte create mode 100644 packages/svelte/tests/runtime-runes/samples/error-boundary-15/Child.svelte create mode 100644 packages/svelte/tests/runtime-runes/samples/error-boundary-15/_config.js create mode 100644 packages/svelte/tests/runtime-runes/samples/error-boundary-15/main.svelte create mode 100644 packages/svelte/tests/runtime-runes/samples/error-boundary-16/Child.svelte create mode 100644 packages/svelte/tests/runtime-runes/samples/error-boundary-16/_config.js create mode 100644 packages/svelte/tests/runtime-runes/samples/error-boundary-16/main.svelte create mode 100644 packages/svelte/tests/runtime-runes/samples/error-boundary-17/Child.svelte create mode 100644 packages/svelte/tests/runtime-runes/samples/error-boundary-17/_config.js create mode 100644 packages/svelte/tests/runtime-runes/samples/error-boundary-17/main.svelte create mode 100644 packages/svelte/tests/runtime-runes/samples/error-boundary-18/_config.js create mode 100644 packages/svelte/tests/runtime-runes/samples/error-boundary-18/main.svelte create mode 100644 packages/svelte/tests/runtime-runes/samples/error-boundary-19/_config.js create mode 100644 packages/svelte/tests/runtime-runes/samples/error-boundary-19/main.svelte create mode 100644 packages/svelte/tests/runtime-runes/samples/error-boundary-2/_config.js create mode 100644 packages/svelte/tests/runtime-runes/samples/error-boundary-2/main.svelte create mode 100644 packages/svelte/tests/runtime-runes/samples/error-boundary-3/_config.js create mode 100644 packages/svelte/tests/runtime-runes/samples/error-boundary-3/main.svelte create mode 100644 packages/svelte/tests/runtime-runes/samples/error-boundary-4/_config.js create mode 100644 packages/svelte/tests/runtime-runes/samples/error-boundary-4/main.svelte create mode 100644 packages/svelte/tests/runtime-runes/samples/error-boundary-5/Child.svelte create mode 100644 packages/svelte/tests/runtime-runes/samples/error-boundary-5/_config.js create mode 100644 packages/svelte/tests/runtime-runes/samples/error-boundary-5/main.svelte create mode 100644 packages/svelte/tests/runtime-runes/samples/error-boundary-6/_config.js create mode 100644 packages/svelte/tests/runtime-runes/samples/error-boundary-6/main.svelte create mode 100644 packages/svelte/tests/runtime-runes/samples/error-boundary-7/Child.svelte create mode 100644 packages/svelte/tests/runtime-runes/samples/error-boundary-7/_config.js create mode 100644 packages/svelte/tests/runtime-runes/samples/error-boundary-7/main.svelte create mode 100644 packages/svelte/tests/runtime-runes/samples/error-boundary-8/Child.svelte create mode 100644 packages/svelte/tests/runtime-runes/samples/error-boundary-8/_config.js create mode 100644 packages/svelte/tests/runtime-runes/samples/error-boundary-8/main.svelte create mode 100644 packages/svelte/tests/runtime-runes/samples/error-boundary-9/Child.svelte create mode 100644 packages/svelte/tests/runtime-runes/samples/error-boundary-9/_config.js create mode 100644 packages/svelte/tests/runtime-runes/samples/error-boundary-9/main.svelte create mode 100644 packages/svelte/tests/runtime-runes/samples/error-boundary/_config.js create mode 100644 packages/svelte/tests/runtime-runes/samples/error-boundary/main.svelte diff --git a/.changeset/polite-peaches-do.md b/.changeset/polite-peaches-do.md new file mode 100644 index 0000000000..7b59c33ea7 --- /dev/null +++ b/.changeset/polite-peaches-do.md @@ -0,0 +1,5 @@ +--- +'svelte': minor +--- + +feat: add error boundaries with `` diff --git a/documentation/docs/05-special-elements/01-svelte-boundary.md b/documentation/docs/05-special-elements/01-svelte-boundary.md new file mode 100644 index 0000000000..2e9f85c83a --- /dev/null +++ b/documentation/docs/05-special-elements/01-svelte-boundary.md @@ -0,0 +1,79 @@ +--- +title: +--- + +```svelte +... +``` + +Boundaries allow you to guard against errors in part of your app from breaking the app as a whole, and to recover from those errors. + +If an error occurs while rendering or updating the children of a ``, or running any [`$effect`]($effect) functions contained therein, the contents will be removed. + +Errors occurring outside the rendering process (for example, in event handlers) are _not_ caught by error boundaries. + +## Properties + +For the boundary to do anything, one or both of `failed` and `onerror` must be provided. + +### `failed` + +If an `failed` snippet is provided, it will be rendered with the error that was thrown, and a `reset` function that recreates the contents ([demo](/playground/hello-world#H4sIAAAAAAAAE3VRy26DMBD8lS2tFCIh6JkAUlWp39Cq9EBg06CAbdlLArL87zWGKk8ORnhmd3ZnrD1WtOjFXqKO2BDGW96xqpBD5gXerm5QefG39mgQY9EIWHxueRMinLosti0UPsJLzggZKTeilLWgLGc51a3gkuCjKQ7DO7cXZotgJ3kLqzC6hmex1SZnSXTWYHcrj8LJjWTk0PHoZ8VqIdCOKayPykcpuQxAokJaG1dGybYj4gw4K5u6PKTasSbjXKgnIDlA8VvUdo-pzonraBY2bsH7HAl78mKSHZpgIcuHjq9jXSpZSLixRlveKYQUXhQVhL6GPobXAAb7BbNeyvNUs4qfRg3OnELLj5hqH9eQZqCnoBwR9lYcQxuVXeBzc8kMF8yXY4yNJ5oGiUzP_aaf_waTRGJib5_Ad3P_vbCuaYxzeNpbU0eUMPAOKh7Yw1YErgtoXyuYlPLzc10_xo_5A91zkQL_AgAA)): + +```svelte + + + + {#snippet failed(error, reset)} + + {/snippet} + +``` + +> [!NOTE] +> As with [snippets passed to components](snippet#Passing-snippets-to-components), the `failed` snippet can be passed explicitly as a property... +> +> ```svelte +> ... +> ``` +> +> ...or implicitly by declaring it directly inside the boundary, as in the example above. + +### `onerror` + +If an `onerror` function is provided, it will be called with the same two `error` and `reset` arguments. This is useful for tracking the error with an error reporting service... + +```svelte + report(e)}> + ... + +``` + +...or using `error` and `reset` outside the boundary itself: + +```svelte + + + + + + +{#if error} + +{/if} +``` + +If an error occurs inside the `onerror` function (or if you rethrow the error), it will be handled by a parent boundary if such exists. diff --git a/documentation/docs/05-special-elements/01-svelte-window.md b/documentation/docs/05-special-elements/02-svelte-window.md similarity index 100% rename from documentation/docs/05-special-elements/01-svelte-window.md rename to documentation/docs/05-special-elements/02-svelte-window.md diff --git a/documentation/docs/05-special-elements/02-svelte-document.md b/documentation/docs/05-special-elements/03-svelte-document.md similarity index 100% rename from documentation/docs/05-special-elements/02-svelte-document.md rename to documentation/docs/05-special-elements/03-svelte-document.md diff --git a/documentation/docs/05-special-elements/03-svelte-body.md b/documentation/docs/05-special-elements/04-svelte-body.md similarity index 100% rename from documentation/docs/05-special-elements/03-svelte-body.md rename to documentation/docs/05-special-elements/04-svelte-body.md diff --git a/documentation/docs/05-special-elements/04-svelte-head.md b/documentation/docs/05-special-elements/05-svelte-head.md similarity index 100% rename from documentation/docs/05-special-elements/04-svelte-head.md rename to documentation/docs/05-special-elements/05-svelte-head.md diff --git a/documentation/docs/05-special-elements/05-svelte-element.md b/documentation/docs/05-special-elements/06-svelte-element.md similarity index 100% rename from documentation/docs/05-special-elements/05-svelte-element.md rename to documentation/docs/05-special-elements/06-svelte-element.md diff --git a/documentation/docs/05-special-elements/06-svelte-options.md b/documentation/docs/05-special-elements/07-svelte-options.md similarity index 100% rename from documentation/docs/05-special-elements/06-svelte-options.md rename to documentation/docs/05-special-elements/07-svelte-options.md diff --git a/documentation/docs/98-reference/.generated/compile-errors.md b/documentation/docs/98-reference/.generated/compile-errors.md index 16cd361e52..77166097a2 100644 --- a/documentation/docs/98-reference/.generated/compile-errors.md +++ b/documentation/docs/98-reference/.generated/compile-errors.md @@ -762,6 +762,18 @@ A component can have a single top-level `