1
1
/** @import { Effect, TemplateNode, } from '#client' */
2
2
3
3
import { BOUNDARY_EFFECT , EFFECT_TRANSPARENT } from '../../constants.js' ;
4
- import { block , branch , destroy_effect , pause_effect } from '../../reactivity/effects.js' ;
4
+ import {
5
+ block ,
6
+ branch ,
7
+ destroy_effect ,
8
+ pause_effect ,
9
+ resume_effect
10
+ } from '../../reactivity/effects.js' ;
5
11
import {
6
12
active_effect ,
7
13
active_reaction ,
@@ -20,7 +26,11 @@ import {
20
26
remove_nodes ,
21
27
set_hydrate_node
22
28
} from '../hydration.js' ;
23
- import { queue_micro_task } from '../task.js' ;
29
+ import { get_next_sibling } from '../operations.js' ;
30
+ import { queue_boundary_micro_task } from '../task.js' ;
31
+
32
+ const ASYNC_INCREMENT = Symbol ( ) ;
33
+ const ASYNC_DECREMENT = Symbol ( ) ;
24
34
25
35
/**
26
36
* @param {Effect } boundary
@@ -49,6 +59,7 @@ function with_boundary(boundary, fn) {
49
59
* @param {{
50
60
* onerror?: (error: unknown, reset: () => void) => void,
51
61
* failed?: (anchor: Node, error: () => unknown, reset: () => () => void) => void
62
+ * pending?: (anchor: Node) => void
52
63
* }} props
53
64
* @param {((anchor: Node) => void) } boundary_fn
54
65
* @returns {void }
@@ -58,14 +69,106 @@ export function boundary(node, props, boundary_fn) {
58
69
59
70
/** @type {Effect } */
60
71
var boundary_effect ;
72
+ /** @type {Effect | null } */
73
+ var async_effect = null ;
74
+ /** @type {DocumentFragment | null } */
75
+ var async_fragment = null ;
76
+ var async_count = 0 ;
61
77
62
78
block ( ( ) => {
63
79
var boundary = /** @type {Effect } */ ( active_effect ) ;
64
80
var hydrate_open = hydrate_node ;
65
81
var is_creating_fallback = false ;
66
82
67
- // We re-use the effect's fn property to avoid allocation of an additional field
68
- boundary . fn = ( /** @type {unknown }} */ error ) => {
83
+ const render_snippet = ( /** @type { () => void } */ snippet_fn ) => {
84
+ with_boundary ( boundary , ( ) => {
85
+ is_creating_fallback = true ;
86
+
87
+ try {
88
+ boundary_effect = branch ( ( ) => {
89
+ snippet_fn ( ) ;
90
+ } ) ;
91
+ } catch ( error ) {
92
+ handle_error ( error , boundary , null , boundary . ctx ) ;
93
+ }
94
+
95
+ reset_is_throwing_error ( ) ;
96
+ is_creating_fallback = false ;
97
+ } ) ;
98
+ } ;
99
+
100
+ // @ts -ignore We re-use the effect's fn property to avoid allocation of an additional field
101
+ boundary . fn = ( /** @type {unknown } */ input ) => {
102
+ let pending = props . pending ;
103
+
104
+ if ( input === ASYNC_INCREMENT ) {
105
+ if ( ! pending ) {
106
+ return false ;
107
+ }
108
+
109
+ if ( async_count ++ === 0 ) {
110
+ queue_boundary_micro_task ( ( ) => {
111
+ if ( async_effect || ! boundary_effect ) {
112
+ return ;
113
+ }
114
+
115
+ var effect = boundary_effect ;
116
+ async_effect = boundary_effect ;
117
+
118
+ pause_effect (
119
+ async_effect ,
120
+ ( ) => {
121
+ /** @type {TemplateNode | null } */
122
+ var node = effect . nodes_start ;
123
+ var end = effect . nodes_end ;
124
+ async_fragment = document . createDocumentFragment ( ) ;
125
+
126
+ while ( node !== null ) {
127
+ /** @type {TemplateNode | null } */
128
+ var sibling =
129
+ node === end ? null : /** @type {TemplateNode } */ ( get_next_sibling ( node ) ) ;
130
+
131
+ node . remove ( ) ;
132
+ async_fragment . append ( node ) ;
133
+ node = sibling ;
134
+ }
135
+ } ,
136
+ false
137
+ ) ;
138
+
139
+ render_snippet ( ( ) => {
140
+ pending ( anchor ) ;
141
+ } ) ;
142
+ } ) ;
143
+ }
144
+
145
+ return true ;
146
+ }
147
+
148
+ if ( input === ASYNC_DECREMENT ) {
149
+ if ( ! pending ) {
150
+ return false ;
151
+ }
152
+
153
+ if ( -- async_count === 0 ) {
154
+ queue_boundary_micro_task ( ( ) => {
155
+ if ( ! async_effect ) {
156
+ return ;
157
+ }
158
+ if ( boundary_effect ) {
159
+ destroy_effect ( boundary_effect ) ;
160
+ }
161
+ boundary_effect = async_effect ;
162
+ async_effect = null ;
163
+ anchor . before ( /** @type {DocumentFragment } */ ( async_fragment ) ) ;
164
+ resume_effect ( boundary_effect ) ;
165
+ } ) ;
166
+ }
167
+
168
+ return true ;
169
+ }
170
+
171
+ var error = input ;
69
172
var onerror = props . onerror ;
70
173
let failed = props . failed ;
71
174
@@ -96,25 +199,13 @@ export function boundary(node, props, boundary_fn) {
96
199
}
97
200
98
201
if ( failed ) {
99
- // Render the `failed` snippet in a microtask
100
- queue_micro_task ( ( ) => {
101
- with_boundary ( boundary , ( ) => {
102
- is_creating_fallback = true ;
103
-
104
- try {
105
- boundary_effect = branch ( ( ) => {
106
- failed (
107
- anchor ,
108
- ( ) => error ,
109
- ( ) => reset
110
- ) ;
111
- } ) ;
112
- } catch ( error ) {
113
- handle_error ( error , boundary , null , boundary . ctx ) ;
114
- }
115
-
116
- reset_is_throwing_error ( ) ;
117
- is_creating_fallback = false ;
202
+ queue_boundary_micro_task ( ( ) => {
203
+ render_snippet ( ( ) => {
204
+ failed (
205
+ anchor ,
206
+ ( ) => error ,
207
+ ( ) => reset
208
+ ) ;
118
209
} ) ;
119
210
} ) ;
120
211
}
@@ -132,3 +223,21 @@ export function boundary(node, props, boundary_fn) {
132
223
anchor = hydrate_node ;
133
224
}
134
225
}
226
+
227
+ /**
228
+ * @param {Effect | null } effect
229
+ * @param {typeof ASYNC_INCREMENT | typeof ASYNC_DECREMENT } trigger
230
+ */
231
+ export function trigger_async_boundary ( effect , trigger ) {
232
+ var current = effect ;
233
+
234
+ while ( current !== null ) {
235
+ if ( ( current . f & BOUNDARY_EFFECT ) !== 0 ) {
236
+ // @ts -ignore
237
+ if ( current . fn ( trigger ) ) {
238
+ return ;
239
+ }
240
+ }
241
+ current = current . parent ;
242
+ }
243
+ }
0 commit comments