-
-
Notifications
You must be signed in to change notification settings - Fork 4.3k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* 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 <[email protected]> * Update .changeset/polite-peaches-do.md Co-authored-by: Simon H <[email protected]> * 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 <[email protected]> * Update packages/svelte/src/internal/client/dom/blocks/boundary.js Co-authored-by: Rich Harris <[email protected]> * Update packages/svelte/src/internal/client/dom/blocks/boundary.js Co-authored-by: Rich Harris <[email protected]> * fix type * fix link * explain what happens if onerror throws --------- Co-authored-by: Simon H <[email protected]> Co-authored-by: Simon Holthausen <[email protected]> Co-authored-by: Rich Harris <[email protected]>
- Loading branch information
1 parent
f2eed15
commit ed7ebcd
Showing
78 changed files
with
1,199 additions
and
49 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
--- | ||
'svelte': minor | ||
--- | ||
|
||
feat: add error boundaries with `<svelte:boundary>` |
79 changes: 79 additions & 0 deletions
79
documentation/docs/05-special-elements/01-svelte-boundary.md
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,79 @@ | ||
--- | ||
title: <svelte:boundary> | ||
--- | ||
|
||
```svelte | ||
<svelte:boundary onerror={handler}>...</svelte:boundary> | ||
``` | ||
|
||
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 `<svelte:boundary>`, 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 | ||
<svelte:boundary> | ||
<FlakyComponent /> | ||
{#snippet failed(error, reset)} | ||
<button onclick={reset}>oops! try again</button> | ||
{/snippet} | ||
</svelte:boundary> | ||
``` | ||
|
||
> [!NOTE] | ||
> As with [snippets passed to components](snippet#Passing-snippets-to-components), the `failed` snippet can be passed explicitly as a property... | ||
> | ||
> ```svelte | ||
> <svelte:boundary {failed}>...</svelte:boundary> | ||
> ``` | ||
> | ||
> ...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 | ||
<svelte:boundary onerror={(e) => report(e)}> | ||
... | ||
</svelte:boundary> | ||
``` | ||
...or using `error` and `reset` outside the boundary itself: | ||
|
||
```svelte | ||
<script> | ||
let error = $state(null); | ||
let reset = $state(() => {}); | ||
function onerror(e, r) { | ||
error = e; | ||
reset = r; | ||
} | ||
</script> | ||
<svelte:boundary {onerror}> | ||
<FlakyComponent /> | ||
</svelte:boundary> | ||
{#if error} | ||
<button onclick={() => { | ||
error = null; | ||
reset(); | ||
}}> | ||
oops! try again | ||
</button> | ||
{/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. |
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
27 changes: 27 additions & 0 deletions
27
packages/svelte/src/compiler/phases/2-analyze/visitors/SvelteBoundary.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
/** @import { AST } from '#compiler' */ | ||
/** @import { Context } from '../types' */ | ||
import * as e from '../../../errors.js'; | ||
|
||
const valid = ['onerror', 'failed']; | ||
|
||
/** | ||
* @param {AST.SvelteBoundary} node | ||
* @param {Context} context | ||
*/ | ||
export function SvelteBoundary(node, context) { | ||
for (const attribute of node.attributes) { | ||
if (attribute.type !== 'Attribute' || !valid.includes(attribute.name)) { | ||
e.svelte_boundary_invalid_attribute(attribute); | ||
} | ||
|
||
if ( | ||
attribute.value === true || | ||
(Array.isArray(attribute.value) && | ||
(attribute.value.length !== 1 || attribute.value[0].type !== 'ExpressionTag')) | ||
) { | ||
e.svelte_boundary_invalid_attribute_value(attribute); | ||
} | ||
} | ||
|
||
context.next(); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
61 changes: 61 additions & 0 deletions
61
packages/svelte/src/compiler/phases/3-transform/client/visitors/SvelteBoundary.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,61 @@ | ||
/** @import { BlockStatement, Statement, Expression } from 'estree' */ | ||
/** @import { AST } from '#compiler' */ | ||
/** @import { ComponentContext } from '../types' */ | ||
import * as b from '../../../../utils/builders.js'; | ||
|
||
/** | ||
* @param {AST.SvelteBoundary} node | ||
* @param {ComponentContext} context | ||
*/ | ||
export function SvelteBoundary(node, context) { | ||
const props = b.object([]); | ||
|
||
for (const attribute of node.attributes) { | ||
if (attribute.type !== 'Attribute' || attribute.value === true) { | ||
// these can't exist, because they would have caused validation | ||
// to fail, but typescript doesn't know that | ||
continue; | ||
} | ||
|
||
const chunk = Array.isArray(attribute.value) | ||
? /** @type {AST.ExpressionTag} */ (attribute.value[0]) | ||
: attribute.value; | ||
|
||
const expression = /** @type {Expression} */ (context.visit(chunk.expression, context.state)); | ||
|
||
if (attribute.metadata.expression.has_state) { | ||
props.properties.push(b.get(attribute.name, [b.return(expression)])); | ||
} else { | ||
props.properties.push(b.init(attribute.name, expression)); | ||
} | ||
} | ||
|
||
const nodes = []; | ||
|
||
/** @type {Statement[]} */ | ||
const snippet_statements = []; | ||
|
||
// Capture the `failed` implicit snippet prop | ||
for (const child of node.fragment.nodes) { | ||
if (child.type === 'SnippetBlock' && child.expression.name === 'failed') { | ||
/** @type {Statement[]} */ | ||
const init = []; | ||
context.visit(child, { ...context.state, init }); | ||
props.properties.push(b.prop('init', child.expression, child.expression)); | ||
snippet_statements.push(...init); | ||
} else { | ||
nodes.push(child); | ||
} | ||
} | ||
|
||
const block = /** @type {BlockStatement} */ (context.visit({ ...node.fragment, nodes })); | ||
|
||
const boundary = b.stmt( | ||
b.call('$.boundary', context.state.node, props, b.arrow([b.id('$$anchor')], block)) | ||
); | ||
|
||
context.state.template.push('<!>'); | ||
context.state.init.push( | ||
snippet_statements.length > 0 ? b.block([...snippet_statements, boundary]) : boundary | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
17 changes: 17 additions & 0 deletions
17
packages/svelte/src/compiler/phases/3-transform/server/visitors/SvelteBoundary.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
/** @import { BlockStatement } from 'estree' */ | ||
/** @import { AST } from '#compiler' */ | ||
/** @import { ComponentContext } from '../types' */ | ||
import { BLOCK_CLOSE, BLOCK_OPEN } from '../../../../../internal/server/hydration.js'; | ||
import * as b from '../../../../utils/builders.js'; | ||
|
||
/** | ||
* @param {AST.SvelteBoundary} node | ||
* @param {ComponentContext} context | ||
*/ | ||
export function SvelteBoundary(node, context) { | ||
context.state.template.push( | ||
b.literal(BLOCK_OPEN), | ||
/** @type {BlockStatement} */ (context.visit(node.fragment)), | ||
b.literal(BLOCK_CLOSE) | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.