Skip to content
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

feat: add error boundaries #14211

Merged
merged 46 commits into from
Dec 1, 2024
Merged

feat: add error boundaries #14211

merged 46 commits into from
Dec 1, 2024

Conversation

trueadm
Copy link
Contributor

@trueadm trueadm commented Nov 7, 2024

This PR adds support for error boundaries to Svelte. Specifically, it adds <svelte:boundary>, which is a special element that can capture errors that occur from within its subtree during client rendering (error boundaries are no-ops during SSR).

The error boundary will capture all errors that occur in any effects (such as $effect and $effect.pre) within its subtree, as long as the code is run synchronously (code in an async or setTimeout will not be captured). <svelte:boundary> can report errors with using onerror, this can be a place where the error can be re-thrown to the next boundary:

Note: Errors in event handlers are not captured.

<script>
  function throw_error() {
    throw new Error('test')
  }
</script>

<svelte:boundary onerror={(e) => console.log('error caught')}>
  {throw_error()}
</svelte:boundary>

In addition, some fallback content can be rendered when an error occurs in a boundary using the failed snippet prop:

<script>
  function throw_error() {
    throw new Error('test')
  }
</script>

<svelte:boundary>
  {throw_error()}

  {#snippet failed(error)}
    <div>An error occurred! {e}</div>
  {/snippet}
</svelte:boundary>

Additionally, a reset function is passed as the second argument to both onerror and the failed prop:

<script>
  function throw_error() {
    throw new Error('test')
  }
</script>

<svelte:boundary>
  {throw_error()}

  {#snippet failed(error, reset)}
    <div>An error occurred! {e}</div>
    <button onclick={reset}>Try again</button>
  {/snippet}
</svelte:boundary>

Feel free to play around with them on the playground.

Closes #3733, closes #14054

Copy link

changeset-bot bot commented Nov 7, 2024

🦋 Changeset detected

Latest commit: 4509d3b

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
svelte Minor

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@Rich-Harris
Copy link
Member

preview: https://svelte-dev-git-preview-svelte-14211-svelte.vercel.app/

this is an automated message

@trueadm trueadm changed the title feat: adds error boundaries feat: add error boundaries Nov 7, 2024
Copy link
Contributor

github-actions bot commented Nov 7, 2024

Playground

pnpm add https://pkg.pr.new/svelte@14211

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

more fixes

tweak

tweak

more fixes

changeset
@JReinhold
Copy link

JReinhold commented Nov 7, 2024

Can this be feature detected?
Say I'm building a library that will support Svelte 5.0.0 and up, and I want to progressively enhance the experience, with an error boundary if available, or just no handling if the project has Svelte v5.0.0.

Maybe there's a general way to feature detect <svelte:X> components that I just don't know about?

@levibassey
Copy link

levibassey commented Nov 7, 2024

Was kinda expecting the api to look like this

{#boundary}
    ...
{:else e}
    <div>An error occurred! {e}</div>
{/boundary}

Maybe even

{#try}
    ...
{:catch e}
    <div>An error occurred! {e}</div>
{/try}

Aren't error boundaries more like, control flow?

@trueadm
Copy link
Contributor Author

trueadm commented Nov 7, 2024

Was kinda expecting the api to look like this

{#boundary}
    ...
{:else}
    ...
{/boundary}

Maybe even

{#try}
    ...
{:catch}
    ...
{/try}

Aren't error boundaries more like control flow?

What if you want to re-throw an error, or log an error to sentry? What if you want to render the error message somewhere else? We explored this API and it has too many drawbacks. Not to mention, <svelte:boundary> can support other usages in the future other than just capturing errors.

@paoloricciuti
Copy link
Member

Can this be feature detected? Say I'm building a library that will support Svelte 5.0.0 and up, and I want to progressively enhance the experience, with an error boundary if available, or just no handling if the project has Svelte v5.0.0.

Maybe there's a general way to feature detect <svelte:X> components that I just don't know about?

What are you thinking about? Considering this should basically be for unexpected errors how would know about it help a library?

@dominikg
Copy link
Member

dominikg commented Nov 7, 2024

Can this be feature detected? Say I'm building a library that will support Svelte 5.0.0 and up, and I want to progressively enhance the experience, with an error boundary if available, or just no handling if the project has Svelte v5.0.0.
Maybe there's a general way to feature detect <svelte:X> components that I just don't know about?

What are you thinking about? Considering this should basically be for unexpected errors how would know about it help a library?

lets say storybook used it, and a user using storybook is on svelte 5.1, if we add this with 5.2, storybook either has to release a breaking change to add a boundary to their stories or feature detect it so that <svelte:boundary> does not trip the svelte 5.1 compiler to not break their users.

@JReinhold you should be able to use the exported version from svelte/compiler and compare that its equal or larger to the first release of this feature.

@VityaSchel

This comment was marked as spam.

@VityaSchel

This comment was marked as spam.

@dominikg

This comment was marked as off-topic.

@Leonidaz
Copy link

Leonidaz commented Nov 7, 2024

What if you don't want to show anything from inside the boundary if an error occurs, and instead use a fallback?

Right now it seems that whatever has a chance to get rendered / added to the dom, stays in, which could result in a broken state. I added an {#if} with a reactive error var but it doesn't seem to matter.

Demo from a commit in this pr

@trueadm
Copy link
Contributor Author

trueadm commented Nov 7, 2024

What if you don't want to show anything from inside the boundary if an error occurs, and instead use a fallback?

Right now it seems that whatever has a chance to get rendered / added to the dom, stays in, which could result in a broken state. I added an {#if} with a reactive error var but it doesn't seem to matter.

Demo from a commit in this pr

Looks like a bug, looking into that now.

Update:fixed!

@Ocean-OS
Copy link
Contributor

Ocean-OS commented Nov 7, 2024

I like this, however, like other people, I am not sure about the execution of the idea; maybe it could be a logic block, as the svelte: elements usually don't have children, and are often meant more for data or compiler options instead of actual DOM-related functions. Additionally, it doesn't feel as "magical" as other Svelte features/elements to have to use a snippet for the fallback, instead of the fallback being declared in a separate part of the block (eg an "else" statement).
Also, if merged, shouldn't this close #3733?

@trueadm
Copy link
Contributor Author

trueadm commented Nov 7, 2024

I like this, however, like other people, I am not sure about the execution of the idea; maybe it could be a logic block, as the svelte: elements usually don't have children, and are often meant more for data or compiler options instead of actual DOM-related functions. Additionally, it doesn't feel as "magical" as other Svelte features/elements to have to use a snippet for the fallback, instead of the fallback being declared in a separate part of the block (eg an "else" statement). Also, if merged, shouldn't this close #3733?

They compose far better than logic blocks. For example, you can create custom error boundaries for your app using the same snippet pattern:

<MyAppErrorBoundary>
  <Content />
  
  {#snippet failed(error)}
    <ErrorMessage {error} />
  {/snippet}
</MyAppErrorBoundary>

@JReinhold
Copy link

you should be able to use the exported version from svelte/compiler and compare that its equal or larger to the first release of this feature.

@dominikg Unless I'm misunderstanding you, I'm not sure this is true, since it happens at the compiler level. So even if you only have the boundary on a condition based on the version, the compiler will still try to compile it and fail. This will still fail in v5.1:

{#if false}
	<svelte:boundary>
		whoops
	</svelte:boundary>
{/if}

See https://www.sveltelab.dev/5doeu6jrx5dqxj1?files=.%2Fsrc%2Froutes%2F%2Bpage.svelte

@paoloricciuti
Copy link
Member

you should be able to use the exported version from svelte/compiler and compare that its equal or larger to the first release of this feature.

@dominikg Unless I'm misunderstanding you, I'm not sure this is true, since it happens at the compiler level. So even if you only have the boundary on a condition based on the version, the compiler will still try to compile it and fail. This will still fail in v5.1:

{#if false}
	<svelte:boundary>
		whoops
	</svelte:boundary>
{/if}

See https://www.sveltelab.dev/5doeu6jrx5dqxj1?files=.%2Fsrc%2Froutes%2F%2Bpage.svelte

I guess you could have a component that just renders the children snippet and one that wrap in boundary and dynamically import based on the condition

@Ocean-OS

This comment has been minimized.

Copy link
Member

@Rich-Harris Rich-Harris left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

couple of small questions, inline, but LGTM

@Rich-Harris Rich-Harris marked this pull request as ready for review December 1, 2024 13:47
@Rich-Harris Rich-Harris merged commit ed7ebcd into main Dec 1, 2024
11 checks passed
@Rich-Harris Rich-Harris deleted the error-boundaries branch December 1, 2024 13:53
@github-actions github-actions bot mentioned this pull request Dec 1, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Add guidance for handling render errors, try block, like error boundary