11/** @import { Effect, TemplateNode, } from '#client' */
22
3- import { BOUNDARY_EFFECT , EFFECT_TRANSPARENT } from '#client/constants' ;
3+ import { BOUNDARY_EFFECT , EFFECT_PRESERVED , EFFECT_TRANSPARENT } from '#client/constants' ;
44import { component_context , set_component_context } from '../../context.js' ;
55import { invoke_error_boundary } from '../../error-handling.js' ;
66import { block , branch , destroy_effect , pause_effect } from '../../reactivity/effects.js' ;
@@ -18,119 +18,197 @@ import {
1818 remove_nodes ,
1919 set_hydrate_node
2020} from '../hydration.js' ;
21+ import { get_next_sibling } from '../operations.js' ;
2122import { queue_micro_task } from '../task.js' ;
2223
2324/**
24- * @param {Effect } boundary
25- * @param {() => void } fn
25+ * @typedef {{
26+ * onerror?: (error: unknown, reset: () => void) => void;
27+ * failed?: (anchor: Node, error: () => unknown, reset: () => () => void) => void;
28+ * }} BoundaryProps
2629 */
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- }
30+
31+ var flags = EFFECT_TRANSPARENT | EFFECT_PRESERVED | BOUNDARY_EFFECT ;
4432
4533/**
4634 * @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
35+ * @param {BoundaryProps } props
36+ * @param {((anchor: Node) => void) } children
5237 * @returns {void }
5338 */
54- export function boundary ( node , props , boundary_fn ) {
55- var anchor = node ;
39+ export function boundary ( node , props , children ) {
40+ new Boundary ( node , props , children ) ;
41+ }
42+
43+ export class Boundary {
44+ /** @type {Boundary | null } */
45+ parent ;
46+
47+ /** @type {TemplateNode } */
48+ #anchor;
49+
50+ /** @type {TemplateNode } */
51+ #hydrate_open;
52+
53+ /** @type {BoundaryProps } */
54+ #props;
55+
56+ /** @type {((anchor: Node) => void) } */
57+ #children;
5658
5759 /** @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- }
60+ #effect;
7561
76- var reset = ( ) => {
77- pause_effect ( boundary_effect ) ;
62+ /** @type { Effect | null } */
63+ #main_effect = null ;
7864
79- with_boundary ( boundary , ( ) => {
80- is_creating_fallback = false ;
81- boundary_effect = branch ( ( ) => boundary_fn ( anchor ) ) ;
82- } ) ;
83- } ;
65+ /** @type {Effect | null } */
66+ #failed_effect = null ;
8467
85- var previous_reaction = active_reaction ;
68+ #is_creating_fallback = false ;
8669
87- try {
88- set_active_reaction ( null ) ;
89- onerror ?. ( error , reset ) ;
90- } finally {
91- set_active_reaction ( previous_reaction ) ;
70+ /**
71+ * @param {TemplateNode } node
72+ * @param {BoundaryProps } props
73+ * @param {((anchor: Node) => void) } children
74+ */
75+ constructor ( node , props , children ) {
76+ this . #anchor = node ;
77+ this . #props = props ;
78+ this . #children = children ;
79+
80+ this . #hydrate_open = hydrate_node ;
81+
82+ this . parent = /** @type {Effect } */ ( active_effect ) . b ;
83+
84+ this . #effect = block ( ( ) => {
85+ /** @type {Effect } */ ( active_effect ) . b = this ;
86+
87+ if ( hydrating ) {
88+ hydrate_next ( ) ;
9289 }
9390
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 ( ) ) ;
91+ try {
92+ this . #main_effect = branch ( ( ) => children ( this . #anchor) ) ;
93+ } catch ( error ) {
94+ this . error ( error ) ;
10095 }
96+ } , flags ) ;
10197
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- } ) ;
98+ if ( hydrating ) {
99+ this . #anchor = hydrate_node ;
100+ }
101+ }
102+
103+ /**
104+ * @param {() => Effect | null } fn
105+ */
106+ #run( fn ) {
107+ var previous_effect = active_effect ;
108+ var previous_reaction = active_reaction ;
109+ var previous_ctx = component_context ;
110+
111+ set_active_effect ( this . #effect) ;
112+ set_active_reaction ( this . #effect) ;
113+ set_component_context ( this . #effect. ctx ) ;
114+
115+ try {
116+ return fn ( ) ;
117+ } finally {
118+ set_active_effect ( previous_effect ) ;
119+ set_active_reaction ( previous_reaction ) ;
120+ set_component_context ( previous_ctx ) ;
121+ }
122+ }
123+
124+ /** @param {unknown } error */
125+ error ( error ) {
126+ var onerror = this . #props. onerror ;
127+ let failed = this . #props. failed ;
128+
129+ const reset = ( ) => {
130+ if ( this . #failed_effect !== null ) {
131+ pause_effect ( this . #failed_effect, ( ) => {
132+ this . #failed_effect = null ;
122133 } ) ;
123134 }
135+
136+ this . #main_effect = this . #run( ( ) => {
137+ this . #is_creating_fallback = false ;
138+ return branch ( ( ) => this . #children( this . #anchor) ) ;
139+ } ) ;
124140 } ;
125141
142+ // If we have nothing to capture the error, or if we hit an error while
143+ // rendering the fallback, re-throw for another boundary to handle
144+ if ( this . #is_creating_fallback || ( ! onerror && ! failed ) ) {
145+ throw error ;
146+ }
147+
148+ var previous_reaction = active_reaction ;
149+
150+ try {
151+ set_active_reaction ( null ) ;
152+ onerror ?. ( error , reset ) ;
153+ } finally {
154+ set_active_reaction ( previous_reaction ) ;
155+ }
156+
157+ if ( this . #main_effect) {
158+ destroy_effect ( this . #main_effect) ;
159+ this . #main_effect = null ;
160+ }
161+
162+ if ( this . #failed_effect) {
163+ destroy_effect ( this . #failed_effect) ;
164+ this . #failed_effect = null ;
165+ }
166+
126167 if ( hydrating ) {
127- hydrate_next ( ) ;
168+ set_hydrate_node ( this . #hydrate_open) ;
169+ next ( ) ;
170+ set_hydrate_node ( remove_nodes ( ) ) ;
171+ }
172+
173+ if ( failed ) {
174+ queue_micro_task ( ( ) => {
175+ this . #failed_effect = this . #run( ( ) => {
176+ this . #is_creating_fallback = true ;
177+
178+ try {
179+ return branch ( ( ) => {
180+ failed (
181+ this . #anchor,
182+ ( ) => error ,
183+ ( ) => reset
184+ ) ;
185+ } ) ;
186+ } catch ( error ) {
187+ invoke_error_boundary ( error , /** @type {Effect } */ ( this . #effect. parent ) ) ;
188+ return null ;
189+ } finally {
190+ this . #is_creating_fallback = false ;
191+ }
192+ } ) ;
193+ } ) ;
128194 }
195+ }
196+ }
197+
198+ /**
199+ *
200+ * @param {Effect } effect
201+ * @param {DocumentFragment } fragment
202+ */
203+ function move_effect ( effect , fragment ) {
204+ var node = effect . nodes_start ;
205+ var end = effect . nodes_end ;
129206
130- boundary_effect = branch ( ( ) => boundary_fn ( anchor ) ) ;
131- } , EFFECT_TRANSPARENT | BOUNDARY_EFFECT ) ;
207+ while ( node !== null ) {
208+ /** @type {TemplateNode | null } */
209+ var next = node === end ? null : /** @type {TemplateNode } */ ( get_next_sibling ( node ) ) ;
132210
133- if ( hydrating ) {
134- anchor = hydrate_node ;
211+ fragment . append ( node ) ;
212+ node = next ;
135213 }
136214}
0 commit comments