11/** @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' ;
43import { component_context , set_component_context } from '../../context.js' ;
54import { invoke_error_boundary } from '../../error-handling.js' ;
65import { block , branch , destroy_effect , pause_effect } from '../../reactivity/effects.js' ;
@@ -21,116 +20,170 @@ import {
2120import { queue_micro_task } from '../task.js' ;
2221
2322/**
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
2627 */
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 ;
4430
4531/**
4632 * @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
5235 * @returns {void }
5336 */
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;
5653
5754 /** @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;
7556
76- var reset = ( ) => {
77- pause_effect ( boundary_effect ) ;
57+ /** @type { Effect | null } */
58+ #main_effect = null ;
7859
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 ;
8462
85- var previous_reaction = active_reaction ;
63+ #is_creating_fallback = false ;
8664
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 ( ) ;
9282 }
9383
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 ) ;
10088 }
89+ } , flags ) ;
10190
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 ;
122126 } ) ;
123127 }
128+
129+ this . #main_effect = this . #run( ( ) => {
130+ this . #is_creating_fallback = false ;
131+ return branch ( ( ) => this . #children( this . #anchor) ) ;
132+ } ) ;
124133 } ;
125134
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 ) ;
128148 }
129149
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+ }
132154
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+ }
135188 }
136189}
0 commit comments