├── .gitignore ├── LICENSE ├── README.md ├── package.json └── src ├── DefaultBoundary.js ├── DefaultBoundary.svelte ├── createBoundary.js └── index.js /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /dist/ 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Hyperlab AB 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Svelte Error Boundary 2 | 3 | This package provides a simple error boundary component for Svelte that can be 4 | can be used with both DOM and SSR targets. The default error boundary component 5 | provides an optional `onError` callback that can be used to log the error to 6 | e.g. Sentry. 7 | 8 | This package also provides a `createBoundary` function that can be used to 9 | monkey-patch an existing Svelte component in order to create custom error 10 | state UIs. 11 | 12 | Monkey-patching is obviously less than ideal since this might break without 13 | warning in future versions of Svelte. This library should be considered merely 14 | as a stop-gap solution for those using Svelte in production today. 15 | 16 | Relevant Svelte issues: [svelte#1096](https://github.com/sveltejs/svelte/issues/1096) 17 | [svelte#3587](https://github.com/sveltejs/svelte/issues/#3587) 18 | [svelte#3733](https://github.com/sveltejs/svelte/issues/#3733) 19 | 20 | **[REPL Demo](https://svelte.dev/repl/9d44bbcf30444cd08cca6b85f07f2e2a?version=3.29.4)** 21 | 22 | ## Installation 23 | 24 | ```bash 25 | npm i -D @crownframework/svelte-error-boundary 26 | ``` 27 | 28 | ## Use default error boundary 29 | 30 | ```svelte 31 | 35 | 36 | 37 | 38 | {a.b.c} 39 | 40 | ``` 41 | 42 | ## Create custom error boundary 43 | 44 | You can use the `createBoundary` function to monkey-patch any ordinary Svelte 45 | component in to an error boundary. 46 | 47 | The component needs to meet the following criteria: 48 | 49 | 1. Have one unnamed slot (this is what will be "enhanced" with an error boundary) 50 | 2. Accept an error prop which will contain a writable store with the last error 51 | 52 | Feel free to use the [default error boundary component](./src/DefaultBoundary.svelte) 53 | as inspiration. 54 | 55 | ### CustomBoundary.js 56 | 57 | ```js 58 | import { createBoundary } from '@crownframework/svelte-error-boundary'; 59 | import Component from './CustomBoundaryComponent.svelte'; 60 | export default createBoundary(Component); 61 | ``` 62 | 63 | ### Usage 64 | 65 | ```svelte 66 | 71 | 72 | 73 | 74 | {a.b.c} 75 | 76 | ``` 77 | 78 | ## TODO 79 | 80 | - [x] Catch client side errors after initial mount 81 | - [ ] Allow client side recovery if error condition goes away 82 | - [ ] Find a way to reliablty catch errors originating from local state changes in the subtree (#3) 83 | 84 | ## Credits 85 | 86 | The initial version of this package was based on a proof of concept by @halfnelson: 87 | https://svelte.dev/repl/006facb65ece4f808cd733e838783228?version=3.22.2 88 | 89 | ## License 90 | 91 | MIT. 92 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@crown/error-boundary", 3 | "svelte": "src/index.js", 4 | "module": "index/index.js", 5 | "main": "index/index.js", 6 | "license": "MIT", 7 | "scripts": {}, 8 | "devDependencies": { 9 | "svelte": "^3.0.0" 10 | }, 11 | "keywords": [ 12 | "svelte", 13 | "error boundary" 14 | ], 15 | "files": [ 16 | "src", 17 | "README.md", 18 | "LICENSE" 19 | ], 20 | "description": "This package provides a simple error boundary as well as an \"higher order component\" that can be used to create custom error boundaries.", 21 | "version": "1.0.3", 22 | "repository": { 23 | "type": "git", 24 | "url": "git+https://github.com/crownframework/svelte-error-boundary.git" 25 | }, 26 | "author": "Jonatan Svennberg", 27 | "bugs": { 28 | "url": "https://github.com/crownframework/svelte-error-boundary/issues" 29 | }, 30 | "homepage": "https://github.com/crownframework/svelte-error-boundary#readme" 31 | } 32 | -------------------------------------------------------------------------------- /src/DefaultBoundary.js: -------------------------------------------------------------------------------- 1 | import Component from './DefaultBoundary.svelte'; 2 | import { createBoundary } from './createBoundary.js'; 3 | 4 | export default createBoundary(Component); 5 | -------------------------------------------------------------------------------- /src/DefaultBoundary.svelte: -------------------------------------------------------------------------------- 1 | 11 | 19 | 20 | {#if $error} 21 |
22 | {$error.message} 23 |
24 |       {DEV ? $error.stack : ''}
25 |     
26 |
27 | {:else} 28 | 29 | {/if} 30 | -------------------------------------------------------------------------------- /src/createBoundary.js: -------------------------------------------------------------------------------- 1 | import { writable } from 'svelte/store'; 2 | const GUARDED_BLOCK_FNS = ['c', 'l', 'h', 'm', 'p', 'a', 'i', 'o', 'd']; 3 | 4 | export function createBoundary(Component) { 5 | if (Component.$$render) { 6 | let render = Component.$$render; 7 | Component.$$render = (result, props, bindings, slots) => { 8 | const error = writable(undefined); 9 | 10 | try { 11 | return render(result, { error, ...props }, bindings, slots); 12 | } catch (e) { 13 | error.set(e); 14 | return render(result, { error, ...props }, bindings, {}); 15 | } 16 | }; 17 | 18 | return Component; 19 | } 20 | 21 | function guard(fn, onError) { 22 | return function guarded(...args) { 23 | try { 24 | return fn(...args); 25 | } catch (err) { 26 | onError(err); 27 | } 28 | }; 29 | } 30 | 31 | return class ErrorBoundaryComponent extends Component { 32 | constructor(config) { 33 | const error = writable(undefined); 34 | 35 | config.props.$$slots.default = config.props.$$slots.default.map( 36 | (slot) => (...args) => { 37 | let guarded = guard(slot, error.set); 38 | let block = guarded(...args); 39 | 40 | if (block) { 41 | for (let fn of GUARDED_BLOCK_FNS) { 42 | if (block[fn]) block[fn] = guard(block[fn], error.set); 43 | } 44 | } 45 | 46 | return block; 47 | } 48 | ); 49 | 50 | super(config); 51 | 52 | this.$$set({ error }); 53 | } 54 | }; 55 | } 56 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | export { default as Boundary } from './DefaultBoundary.js'; 2 | export { createBoundary } from './createBoundary.js'; 3 | --------------------------------------------------------------------------------