@@ -2,12 +2,20 @@ import { load } from '@webcontainer/api';
2
2
import base64 from 'base64-js' ;
3
3
import { ready } from '../common/index.js' ;
4
4
5
- /** @type {import('@webcontainer/api').WebContainer } */
5
+ /** @type {import('@webcontainer/api').WebContainer } Web container singleton */
6
6
let vm ;
7
7
/** Keep track of startup progress, so we don't repeat previous steps in case of a timeout */
8
8
let step = 0 ;
9
- /** @type {string } */
9
+ /** @type {string } path to the web container server instance */
10
10
let base ;
11
+ /**
12
+ * Keeps track of the latest create/reset to ensure things are not processed in parallel.
13
+ * (if this turns out to be insufficient, we can use a queue)
14
+ * @type {Promise<any> | undefined }
15
+ */
16
+ let running ;
17
+ /** @type {Set<string> } Paths of the currently loaded file stubs */
18
+ let current = new Set ( ) ;
11
19
12
20
/**
13
21
* @param {import('$lib/types').Stub[] } stubs
@@ -28,7 +36,9 @@ export async function create(stubs) {
28
36
throw new Error ( 'WebContainers are not supported by Safari' ) ;
29
37
}
30
38
31
- base = await new Promise ( async ( fulfil , reject ) => {
39
+ await running ; // wait for any previous create to finish
40
+
41
+ const init = new Promise ( async ( fulfil , reject ) => {
32
42
if ( base ) {
33
43
// startup was successful in the meantime
34
44
fulfil ( base ) ;
@@ -75,6 +85,8 @@ export async function create(stubs) {
75
85
console . log ( 'loading files' ) ;
76
86
77
87
await vm . loadFiles ( tree ) ;
88
+
89
+ current = new Set ( stub_filenames ( stubs ) ) ;
78
90
}
79
91
80
92
if ( step < 2 ) {
@@ -120,131 +132,131 @@ export async function create(stubs) {
120
132
}
121
133
} ) ;
122
134
123
- /**
124
- * Paths of the currently loaded file stubs
125
- */
126
- let current = new Set ( stubs . filter ( ( stub ) => stub . type === 'file' ) . map ( ( stub ) => stub . name ) ) ;
127
- /**
128
- * Keeps track of the latest create/reset to ensure things are not processed in parallel.
129
- * (if this turns out to be insufficient, we can use a queue)
130
- */
131
- let running = Promise . resolve ( ) ;
135
+ running = init . catch ( ( ) => { } ) ;
136
+ base = await init ;
132
137
133
- return {
134
- base,
138
+ if ( stub_filenames ( stubs ) . some ( ( name ) => ! current . has ( name ) ) ) {
139
+ await reset ( stubs ) ;
140
+ }
135
141
136
- /**
137
- * Deletes old files and adds new ones
138
- * @param {import('$lib/types').Stub[] } stubs
139
- */
140
- async reset ( stubs ) {
141
- await running ;
142
- /** @type {Function } */
143
- let resolve = ( ) => { } ;
144
- running = new Promise ( ( fulfil ) => ( resolve = fulfil ) ) ;
145
-
146
- const old = current ;
147
- current = new Set ( stubs . filter ( ( stub ) => stub . type === 'file' ) . map ( ( stub ) => stub . name ) ) ;
148
-
149
- for ( const stub of stubs ) {
150
- if ( stub . type === 'file' ) {
151
- old . delete ( stub . name ) ;
152
- }
142
+ /**
143
+ * Deletes old files and adds new ones
144
+ * @param {import('$lib/types').Stub[] } stubs
145
+ */
146
+ async function reset ( stubs ) {
147
+ await running ;
148
+ /** @type {Function } */
149
+ let resolve = ( ) => { } ;
150
+ running = new Promise ( ( fulfil ) => ( resolve = fulfil ) ) ;
151
+
152
+ const old = current ;
153
+ current = new Set ( stub_filenames ( stubs ) ) ;
154
+
155
+ for ( const stub of stubs ) {
156
+ if ( stub . type === 'file' ) {
157
+ old . delete ( stub . name ) ;
153
158
}
159
+ }
154
160
155
- // For some reason, server-ready is fired again on resetting the files here.
156
- // We need to wait for it to finish before we can continue, else we might
157
- // request files from Vite before it's ready, leading to a timeout.
158
- const promise = new Promise ( ( fulfil , reject ) => {
159
- const error_unsub = vm . on ( 'error' , ( error ) => {
160
- error_unsub ( ) ;
161
- resolve ( ) ;
162
- reject ( new Error ( error . message ) ) ;
163
- } ) ;
164
-
165
- const ready_unsub = vm . on ( 'server-ready' , ( port , base ) => {
166
- ready_unsub ( ) ;
167
- console . log ( `server ready on port ${ port } at ${ performance . now ( ) } : ${ base } ` ) ;
168
- resolve ( ) ;
169
- fulfil ( undefined ) ;
170
- } ) ;
161
+ // For some reason, server-ready is fired again on resetting the files here.
162
+ // We need to wait for it to finish before we can continue, else we might
163
+ // request files from Vite before it's ready, leading to a timeout.
164
+ const promise = new Promise ( ( fulfil , reject ) => {
165
+ const error_unsub = vm . on ( 'error' , ( error ) => {
166
+ error_unsub ( ) ;
167
+ resolve ( ) ;
168
+ reject ( new Error ( error . message ) ) ;
169
+ } ) ;
171
170
172
- setTimeout ( ( ) => {
173
- resolve ( ) ;
174
- reject ( new Error ( 'Timed out resetting WebContainer' ) ) ;
175
- } , 10000 ) ;
171
+ const ready_unsub = vm . on ( 'server-ready' , ( port , base ) => {
172
+ ready_unsub ( ) ;
173
+ console . log ( `server ready on port ${ port } at ${ performance . now ( ) } : ${ base } ` ) ;
174
+ resolve ( ) ;
175
+ fulfil ( undefined ) ;
176
176
} ) ;
177
177
178
- for ( const file of old ) {
179
- // TODO this fails with a cryptic error
180
- // index.svelte:155 Uncaught (in promise) TypeError: Cannot read properties of undefined (reading 'rmSync')
181
- // at Object.rm (webcontainer.e2e246a845f9e80283581d6b944116e399af6950.js:6:121171)
182
- // at MessagePort._0x4ec3f4 (webcontainer.e2e246a845f9e80283581d6b944116e399af6950.js:6:110957)
183
- // at MessagePort.nrWrapper (headless:5:29785)
184
- // await vm.fs.rm(file);
185
-
186
- // temporary workaround
187
- try {
188
- await vm . run ( {
189
- command : 'node' ,
190
- args : [ '-e' , `fs.rmSync('${ file . slice ( 1 ) } ')` ]
191
- } ) ;
192
- } catch ( e ) {
193
- console . error ( e ) ;
194
- }
195
- }
178
+ setTimeout ( ( ) => {
179
+ resolve ( ) ;
180
+ reject ( new Error ( 'Timed out resetting WebContainer' ) ) ;
181
+ } , 10000 ) ;
182
+ } ) ;
196
183
197
- await vm . loadFiles ( convert_stubs_to_tree ( stubs ) ) ;
184
+ for ( const file of old ) {
185
+ // TODO this fails with a cryptic error
186
+ // index.svelte:155 Uncaught (in promise) TypeError: Cannot read properties of undefined (reading 'rmSync')
187
+ // at Object.rm (webcontainer.e2e246a845f9e80283581d6b944116e399af6950.js:6:121171)
188
+ // at MessagePort._0x4ec3f4 (webcontainer.e2e246a845f9e80283581d6b944116e399af6950.js:6:110957)
189
+ // at MessagePort.nrWrapper (headless:5:29785)
190
+ // await vm.fs.rm(file);
191
+
192
+ // temporary workaround
193
+ try {
194
+ await vm . run ( {
195
+ command : 'node' ,
196
+ args : [ '-e' , `fs.rmSync('${ file . slice ( 1 ) } ')` ]
197
+ } ) ;
198
+ } catch ( e ) {
199
+ console . error ( e ) ;
200
+ }
201
+ }
198
202
199
- await promise ;
203
+ await vm . loadFiles ( convert_stubs_to_tree ( stubs ) ) ;
200
204
201
- await new Promise ( ( f ) => setTimeout ( f , 200 ) ) ; // wait for chokidar
205
+ await promise ;
202
206
203
- resolve ( ) ;
204
- } ,
207
+ await new Promise ( ( f ) => setTimeout ( f , 200 ) ) ; // wait for chokidar
205
208
206
- /**
207
- * Loads new files but keeps the old ones
208
- * @param {import('$lib/types').FileStub[] } stubs
209
- */
210
- async update ( stubs ) {
211
- await running ;
209
+ resolve ( ) ;
210
+ }
212
211
213
- /** @type {import('@webcontainer/api').FileSystemTree } */
214
- const root = { } ;
212
+ /**
213
+ * Loads new files but keeps the old ones
214
+ * @param {import('$lib/types').FileStub[] } stubs
215
+ */
216
+ async function update ( stubs ) {
217
+ await running ;
215
218
216
- for ( const stub of stubs ) {
217
- let tree = root ;
219
+ /** @type { import('@webcontainer/api').FileSystemTree } */
220
+ const root = { } ;
218
221
219
- const path = stub . name . split ( '/' ) . slice ( 1 ) ;
220
- const basename = /** @type { string } */ ( path . pop ( ) ) ;
222
+ for ( const stub of stubs ) {
223
+ let tree = root ;
221
224
222
- for ( const part of path ) {
223
- if ( ! tree [ part ] ) {
224
- /** @type {import('@webcontainer/api').FileSystemTree } */
225
- const directory = { } ;
225
+ const path = stub . name . split ( '/' ) . slice ( 1 ) ;
226
+ const basename = /** @type {string } */ ( path . pop ( ) ) ;
226
227
227
- tree [ part ] = {
228
- directory
229
- } ;
230
- }
228
+ for ( const part of path ) {
229
+ if ( ! tree [ part ] ) {
230
+ /** @type { import('@webcontainer/api').FileSystemTree } */
231
+ const directory = { } ;
231
232
232
- tree = /** @type {import('@webcontainer/api').DirectoryEntry } */ ( tree [ part ] ) . directory ;
233
+ tree [ part ] = {
234
+ directory
235
+ } ;
233
236
}
234
237
235
- tree [ basename ] = to_file ( stub ) ;
238
+ tree = /** @type { import('@webcontainer/api').DirectoryEntry } */ ( tree [ part ] ) . directory ;
236
239
}
237
240
238
- await vm . loadFiles ( root ) ;
241
+ tree [ basename ] = to_file ( stub ) ;
242
+ }
239
243
240
- await new Promise ( ( f ) => setTimeout ( f , 200 ) ) ; // wait for chokidar
241
- } ,
244
+ await vm . loadFiles ( root ) ;
242
245
243
- async destroy ( ) {
244
- vm . teardown ( ) ;
245
- // @ts -ignore
246
- vm = null ;
247
- }
246
+ await new Promise ( ( f ) => setTimeout ( f , 200 ) ) ; // wait for chokidar
247
+ }
248
+
249
+ async function destroy ( ) {
250
+ vm . teardown ( ) ;
251
+ // @ts -ignore
252
+ vm = null ;
253
+ }
254
+
255
+ return {
256
+ base,
257
+ reset,
258
+ update,
259
+ destroy
248
260
} ;
249
261
}
250
262
@@ -293,3 +305,11 @@ function to_file(stub) {
293
305
file : { contents }
294
306
} ;
295
307
}
308
+
309
+ /**
310
+ *
311
+ * @param {import('$lib/types').Stub[] } stubs
312
+ */
313
+ function stub_filenames ( stubs ) {
314
+ return stubs . filter ( ( stub ) => stub . type === 'file' ) . map ( ( stub ) => stub . name ) ;
315
+ }
0 commit comments