-
-
Notifications
You must be signed in to change notification settings - Fork 4.6k
feat: add error boundaries #14211
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
feat: add error boundaries #14211
Changes from all commits
Commits
Show all changes
46 commits
Select commit
Hold shift + click to select a range
007d5f3
feat: add error boundary support
trueadm f683ce2
tweak
trueadm 51e49be
Update packages/svelte/elements.d.ts
trueadm 11b2ecd
Update .changeset/polite-peaches-do.md
trueadm 10897ac
fix issue with rethrowing
trueadm e6b6a68
handle fallback error
trueadm dc95bd3
handle fallback error
trueadm b9bc80d
add more test coverage
trueadm cf11f58
more tests
trueadm 4d8ad24
more bug fixes
trueadm 2c8dea6
guard against non-errors
trueadm 5be2334
add component_stack to error
trueadm c468c6d
alternative approach
trueadm 3ae0c80
Merge branch 'main' into error-boundaries
trueadm 3441004
remove spread support
trueadm 75a01a5
lint
trueadm 371f118
add to legacy ast
dummdidumm 36dcb7d
Merge branch 'main' into error-boundaries
dummdidumm 3afd1bb
add to svelte-html
dummdidumm 1564640
disallow anything but attributes on the boundary element
dummdidumm acb3cd0
Merge branch 'main' into error-boundaries
Rich-Harris bc72ed2
fix error
Rich-Harris a77bf50
more validation
Rich-Harris f82a59b
only create block when necessary
Rich-Harris 6aa714e
swap argument order - results in nicer-looking code in many cases
Rich-Harris b53cfc8
Update .changeset/polite-peaches-do.md
Rich-Harris 1ec18a3
simplify a bit
Rich-Harris a7ee520
simplify
Rich-Harris 3070f67
move declaration closer to usage
Rich-Harris fbbb7d9
push once
Rich-Harris 3322856
unused
Rich-Harris 08b82f9
tweaks
Rich-Harris 2d27f50
consistent naming
Rich-Harris 702adf9
simplify
Rich-Harris 69877ae
add a couple newlines
Rich-Harris 8e74719
tweak comments
Rich-Harris b4a30a4
simplify
Rich-Harris a81dc34
newlines
Rich-Harris 0fe5274
placeholder documentation
Rich-Harris 62e1af8
add some docs
Rich-Harris dfdcf02
Update packages/svelte/src/internal/client/dom/blocks/boundary.js
trueadm b3d1d92
Update packages/svelte/src/internal/client/dom/blocks/boundary.js
trueadm dc36557
Update packages/svelte/src/internal/client/dom/blocks/boundary.js
trueadm 2b0778e
fix type
trueadm 93b16b1
fix link
Rich-Harris 4509d3b
explain what happens if onerror throws
Rich-Harris File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or 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 hidden or 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 hidden or 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 hidden or 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 hidden or 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 hidden or 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 hidden or 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 hidden or 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 hidden or 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 hidden or 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 hidden or 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 hidden or 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 hidden or 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 hidden or 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 hidden or 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 hidden or 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.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.