1
1
/** @import { Effect, TemplateNode, } from '#client' */
2
-
3
- import { BOUNDARY_EFFECT , EFFECT_TRANSPARENT } from '#client/constants' ;
2
+ import { BOUNDARY_EFFECT , EFFECT_PRESERVED , EFFECT_TRANSPARENT } from '#client/constants' ;
4
3
import { component_context , set_component_context } from '../../context.js' ;
5
4
import { invoke_error_boundary } from '../../error-handling.js' ;
6
5
import { block , branch , destroy_effect , pause_effect } from '../../reactivity/effects.js' ;
@@ -21,116 +20,170 @@ import {
21
20
import { queue_micro_task } from '../task.js' ;
22
21
23
22
/**
24
- * @param {Effect } boundary
25
- * @param {() => void } fn
23
+ * @typedef {{
24
+ * onerror?: (error: unknown, reset: () => void) => void;
25
+ * failed?: (anchor: Node, error: () => unknown, reset: () => () => void) => void;
26
+ * }} BoundaryProps
26
27
*/
27
- function with_boundary ( boundary , fn ) {
28
- var previous_effect = active_effect ;
29
- var previous_reaction = active_reaction ;
30
- var previous_ctx = component_context ;
31
-
32
- set_active_effect ( boundary ) ;
33
- set_active_reaction ( boundary ) ;
34
- set_component_context ( boundary . ctx ) ;
35
-
36
- try {
37
- fn ( ) ;
38
- } finally {
39
- set_active_effect ( previous_effect ) ;
40
- set_active_reaction ( previous_reaction ) ;
41
- set_component_context ( previous_ctx ) ;
42
- }
43
- }
28
+
29
+ var flags = EFFECT_TRANSPARENT | EFFECT_PRESERVED | BOUNDARY_EFFECT ;
44
30
45
31
/**
46
32
* @param {TemplateNode } node
47
- * @param {{
48
- * onerror?: (error: unknown, reset: () => void) => void,
49
- * failed?: (anchor: Node, error: () => unknown, reset: () => () => void) => void
50
- * }} props
51
- * @param {((anchor: Node) => void) } boundary_fn
33
+ * @param {BoundaryProps } props
34
+ * @param {((anchor: Node) => void) } children
52
35
* @returns {void }
53
36
*/
54
- export function boundary ( node , props , boundary_fn ) {
55
- var anchor = node ;
37
+ export function boundary ( node , props , children ) {
38
+ new Boundary ( node , props , children ) ;
39
+ }
40
+
41
+ export class Boundary {
42
+ /** @type {TemplateNode } */
43
+ #anchor;
44
+
45
+ /** @type {TemplateNode } */
46
+ #hydrate_open;
47
+
48
+ /** @type {BoundaryProps } */
49
+ #props;
50
+
51
+ /** @type {((anchor: Node) => void) } */
52
+ #children;
56
53
57
54
/** @type {Effect } */
58
- var boundary_effect ;
59
-
60
- block ( ( ) => {
61
- var boundary = /** @type {Effect } */ ( active_effect ) ;
62
- var hydrate_open = hydrate_node ;
63
- var is_creating_fallback = false ;
64
-
65
- // We re-use the effect's fn property to avoid allocation of an additional field
66
- boundary . fn = ( /** @type {unknown }} */ error ) => {
67
- var onerror = props . onerror ;
68
- let failed = props . failed ;
69
-
70
- // If we have nothing to capture the error, or if we hit an error while
71
- // rendering the fallback, re-throw for another boundary to handle
72
- if ( ( ! onerror && ! failed ) || is_creating_fallback ) {
73
- throw error ;
74
- }
55
+ #effect;
75
56
76
- var reset = ( ) => {
77
- pause_effect ( boundary_effect ) ;
57
+ /** @type { Effect | null } */
58
+ #main_effect = null ;
78
59
79
- with_boundary ( boundary , ( ) => {
80
- is_creating_fallback = false ;
81
- boundary_effect = branch ( ( ) => boundary_fn ( anchor ) ) ;
82
- } ) ;
83
- } ;
60
+ /** @type {Effect | null } */
61
+ #failed_effect = null ;
84
62
85
- var previous_reaction = active_reaction ;
63
+ #is_creating_fallback = false ;
86
64
87
- try {
88
- set_active_reaction ( null ) ;
89
- onerror ?. ( error , reset ) ;
90
- } finally {
91
- set_active_reaction ( previous_reaction ) ;
65
+ /**
66
+ * @param {TemplateNode } node
67
+ * @param {BoundaryProps } props
68
+ * @param {((anchor: Node) => void) } children
69
+ */
70
+ constructor ( node , props , children ) {
71
+ this . #anchor = node ;
72
+ this . #props = props ;
73
+ this . #children = children ;
74
+
75
+ this . #hydrate_open = hydrate_node ;
76
+
77
+ this . #effect = block ( ( ) => {
78
+ /** @type {Effect } */ ( active_effect ) . b = this ;
79
+
80
+ if ( hydrating ) {
81
+ hydrate_next ( ) ;
92
82
}
93
83
94
- if ( boundary_effect ) {
95
- destroy_effect ( boundary_effect ) ;
96
- } else if ( hydrating ) {
97
- set_hydrate_node ( hydrate_open ) ;
98
- next ( ) ;
99
- set_hydrate_node ( remove_nodes ( ) ) ;
84
+ try {
85
+ this . #main_effect = branch ( ( ) => children ( this . #anchor) ) ;
86
+ } catch ( error ) {
87
+ this . error ( error ) ;
100
88
}
89
+ } , flags ) ;
101
90
102
- if ( failed ) {
103
- // Render the `failed` snippet in a microtask
104
- queue_micro_task ( ( ) => {
105
- with_boundary ( boundary , ( ) => {
106
- is_creating_fallback = true ;
107
-
108
- try {
109
- boundary_effect = branch ( ( ) => {
110
- failed (
111
- anchor ,
112
- ( ) => error ,
113
- ( ) => reset
114
- ) ;
115
- } ) ;
116
- } catch ( error ) {
117
- invoke_error_boundary ( error , /** @type {Effect } */ ( boundary . parent ) ) ;
118
- }
119
-
120
- is_creating_fallback = false ;
121
- } ) ;
91
+ if ( hydrating ) {
92
+ this . #anchor = hydrate_node ;
93
+ }
94
+ }
95
+
96
+ /**
97
+ * @param {() => Effect | null } fn
98
+ */
99
+ #run( fn ) {
100
+ var previous_effect = active_effect ;
101
+ var previous_reaction = active_reaction ;
102
+ var previous_ctx = component_context ;
103
+
104
+ set_active_effect ( this . #effect) ;
105
+ set_active_reaction ( this . #effect) ;
106
+ set_component_context ( this . #effect. ctx ) ;
107
+
108
+ try {
109
+ return fn ( ) ;
110
+ } finally {
111
+ set_active_effect ( previous_effect ) ;
112
+ set_active_reaction ( previous_reaction ) ;
113
+ set_component_context ( previous_ctx ) ;
114
+ }
115
+ }
116
+
117
+ /** @param {unknown } error */
118
+ error ( error ) {
119
+ var onerror = this . #props. onerror ;
120
+ let failed = this . #props. failed ;
121
+
122
+ const reset = ( ) => {
123
+ if ( this . #failed_effect !== null ) {
124
+ pause_effect ( this . #failed_effect, ( ) => {
125
+ this . #failed_effect = null ;
122
126
} ) ;
123
127
}
128
+
129
+ this . #main_effect = this . #run( ( ) => {
130
+ this . #is_creating_fallback = false ;
131
+ return branch ( ( ) => this . #children( this . #anchor) ) ;
132
+ } ) ;
124
133
} ;
125
134
126
- if ( hydrating ) {
127
- hydrate_next ( ) ;
135
+ // If we have nothing to capture the error, or if we hit an error while
136
+ // rendering the fallback, re-throw for another boundary to handle
137
+ if ( this . #is_creating_fallback || ( ! onerror && ! failed ) ) {
138
+ throw error ;
139
+ }
140
+
141
+ var previous_reaction = active_reaction ;
142
+
143
+ try {
144
+ set_active_reaction ( null ) ;
145
+ onerror ?. ( error , reset ) ;
146
+ } finally {
147
+ set_active_reaction ( previous_reaction ) ;
128
148
}
129
149
130
- boundary_effect = branch ( ( ) => boundary_fn ( anchor ) ) ;
131
- } , EFFECT_TRANSPARENT | BOUNDARY_EFFECT ) ;
150
+ if ( this . #main_effect) {
151
+ destroy_effect ( this . #main_effect) ;
152
+ this . #main_effect = null ;
153
+ }
132
154
133
- if ( hydrating ) {
134
- anchor = hydrate_node ;
155
+ if ( this . #failed_effect) {
156
+ destroy_effect ( this . #failed_effect) ;
157
+ this . #failed_effect = null ;
158
+ }
159
+
160
+ if ( hydrating ) {
161
+ set_hydrate_node ( this . #hydrate_open) ;
162
+ next ( ) ;
163
+ set_hydrate_node ( remove_nodes ( ) ) ;
164
+ }
165
+
166
+ if ( failed ) {
167
+ queue_micro_task ( ( ) => {
168
+ this . #failed_effect = this . #run( ( ) => {
169
+ this . #is_creating_fallback = true ;
170
+
171
+ try {
172
+ return branch ( ( ) => {
173
+ failed (
174
+ this . #anchor,
175
+ ( ) => error ,
176
+ ( ) => reset
177
+ ) ;
178
+ } ) ;
179
+ } catch ( error ) {
180
+ invoke_error_boundary ( error , /** @type {Effect } */ ( this . #effect. parent ) ) ;
181
+ return null ;
182
+ } finally {
183
+ this . #is_creating_fallback = false ;
184
+ }
185
+ } ) ;
186
+ } ) ;
187
+ }
135
188
}
136
189
}
0 commit comments