├── .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 |
--------------------------------------------------------------------------------