@@ -16,16 +16,17 @@ function console_stream(label) {
16
16
}
17
17
18
18
/**
19
- * @param {import('$lib/types').Stub[] } stubs
20
- * @param {(progress: number, status: string) => void } callback
21
- * @returns {Promise<import('$lib/types').AdapterInternal> }
19
+ * @param {import('svelte/store').Writable<string | null> } base
20
+ * @param {import('svelte/store').Writable<Error | null> } error
21
+ * @param {import('svelte/store').Writable<{ value: number, text: string }> } progress
22
+ * @returns {Promise<import('$lib/types').Adapter> }
22
23
*/
23
- export async function create ( stubs , callback ) {
24
+ export async function create ( base , error , progress ) {
24
25
if ( / s a f a r i / i. test ( navigator . userAgent ) && ! / c h r o m e / i. test ( navigator . userAgent ) ) {
25
26
throw new Error ( 'WebContainers are not supported by Safari' ) ;
26
27
}
27
28
28
- callback ( 0 , 'loading files' ) ;
29
+ progress . set ( { value : 0 , text : 'loading files' } ) ;
29
30
30
31
/**
31
32
* Keeps track of the latest create/reset to ensure things are not processed in parallel.
@@ -35,27 +36,26 @@ export async function create(stubs, callback) {
35
36
let running ;
36
37
37
38
/** Paths and contents of the currently loaded file stubs */
38
- let current_stubs = stubs_to_map ( stubs ) ;
39
+ let current_stubs = stubs_to_map ( [ ] ) ;
39
40
40
41
/** @type {boolean } Track whether there was an error from vite dev server */
41
42
let vite_error = false ;
42
43
43
- callback ( 1 / 5 , 'booting webcontainer' ) ;
44
+ progress . set ( { value : 1 / 5 , text : 'booting webcontainer' } ) ;
44
45
vm = await WebContainer . boot ( ) ;
45
46
46
- callback ( 2 / 5 , 'writing virtual files' ) ;
47
+ progress . set ( { value : 2 / 5 , text : 'writing virtual files' } ) ;
47
48
const common = await ready ;
48
49
await vm . mount ( {
49
50
'common.zip' : {
50
51
file : { contents : new Uint8Array ( common . zipped ) }
51
52
} ,
52
53
'unzip.cjs' : {
53
54
file : { contents : common . unzip }
54
- } ,
55
- ...convert_stubs_to_tree ( stubs )
55
+ }
56
56
} ) ;
57
57
58
- callback ( 3 / 5 , 'unzipping files' ) ;
58
+ progress . set ( { value : 3 / 5 , text : 'unzipping files' } ) ;
59
59
const unzip = await vm . spawn ( 'node' , [ 'unzip.cjs' ] ) ;
60
60
unzip . output . pipeTo ( console_stream ( 'unzip' ) ) ;
61
61
const code = await unzip . exit ;
@@ -66,41 +66,51 @@ export async function create(stubs, callback) {
66
66
67
67
await vm . spawn ( 'chmod' , [ 'a+x' , 'node_modules/vite/bin/vite.js' ] ) ;
68
68
69
- callback ( 4 / 5 , 'starting dev server' ) ;
70
- const base = await new Promise ( async ( fulfil , reject ) => {
71
- const error_unsub = vm . on ( 'error' , ( error ) => {
72
- error_unsub ( ) ;
73
- reject ( new Error ( error . message ) ) ;
74
- } ) ;
69
+ vm . on ( 'server-ready' , ( _port , url ) => {
70
+ base . set ( url ) ;
71
+ } ) ;
75
72
76
- const ready_unsub = vm . on ( 'server-ready' , ( _port , base ) => {
77
- ready_unsub ( ) ;
78
- callback ( 5 / 5 , 'ready' ) ;
79
- fulfil ( base ) ; // this will be the last thing that happens if everything goes well
80
- } ) ;
73
+ vm . on ( 'error' , ( { message } ) => {
74
+ error . set ( new Error ( message ) ) ;
75
+ } ) ;
81
76
82
- await run_dev ( ) ;
77
+ let launched = false ;
83
78
84
- async function run_dev ( ) {
85
- const process = await vm . spawn ( 'turbo' , [ 'run' , 'dev' ] ) ;
79
+ async function launch ( ) {
80
+ if ( launched ) return ;
81
+ launched = true ;
86
82
87
- // TODO differentiate between stdout and stderr (sets `vite_error` to `true`)
88
- // https://github.com/stackblitz/webcontainer-core/issues/971
89
- process . output . pipeTo ( console_stream ( 'dev' ) ) ;
83
+ progress . set ( { value : 4 / 5 , text : 'starting dev server' } ) ;
90
84
91
- // keep restarting dev server (can crash in case of illegal +files for example)
92
- process . exit . then ( ( code ) => {
93
- if ( code !== 0 ) {
94
- setTimeout ( ( ) => {
95
- run_dev ( ) ;
96
- } , 2000 ) ;
97
- }
85
+ await new Promise ( async ( fulfil , reject ) => {
86
+ const error_unsub = vm . on ( 'error' , ( error ) => {
87
+ error_unsub ( ) ;
88
+ reject ( new Error ( error . message ) ) ;
98
89
} ) ;
99
- }
100
- } ) ;
90
+
91
+ const ready_unsub = vm . on ( 'server-ready' , ( _port , base ) => {
92
+ ready_unsub ( ) ;
93
+ progress . set ( { value : 5 / 5 , text : 'ready' } ) ;
94
+ fulfil ( base ) ; // this will be the last thing that happens if everything goes well
95
+ } ) ;
96
+
97
+ await run_dev ( ) ;
98
+
99
+ async function run_dev ( ) {
100
+ const process = await vm . spawn ( 'turbo' , [ 'run' , 'dev' ] ) ;
101
+
102
+ // TODO differentiate between stdout and stderr (sets `vite_error` to `true`)
103
+ // https://github.com/stackblitz/webcontainer-core/issues/971
104
+ process . output . pipeTo ( console_stream ( 'dev' ) ) ;
105
+
106
+ // keep restarting dev server (can crash in case of illegal +files for example)
107
+ await process . exit ;
108
+ run_dev ( ) ;
109
+ }
110
+ } ) ;
111
+ }
101
112
102
113
return {
103
- base,
104
114
reset : async ( stubs ) => {
105
115
await running ;
106
116
/** @type {Function } */
@@ -146,7 +156,7 @@ export async function create(stubs, callback) {
146
156
// For some reason, server-ready is fired again when the vite dev server is restarted.
147
157
// We need to wait for it to finish before we can continue, else we might
148
158
// request files from Vite before it's ready, leading to a timeout.
149
- const will_restart = will_restart_vite_dev_server ( to_write ) ;
159
+ const will_restart = launched && to_write . some ( will_restart_vite_dev_server ) ;
150
160
const promise = will_restart
151
161
? new Promise ( ( fulfil , reject ) => {
152
162
const error_unsub = vm . on ( 'error' , ( error ) => {
@@ -190,60 +200,57 @@ export async function create(stubs, callback) {
190
200
191
201
// Also trigger a reload of the iframe in case new files were added / old ones deleted,
192
202
// because that can result in a broken UI state
193
- return will_restart || vite_error || to_delete . length > 0 || added_new_file ;
203
+ const should_reload = ! launched || will_restart || vite_error || to_delete . length > 0 ;
204
+ // `|| added_new_file`, but I don't actually think that's necessary?
205
+
206
+ await launch ( ) ;
207
+
208
+ return should_reload ;
194
209
} ,
195
- update : async ( stubs ) => {
210
+ update : async ( file ) => {
196
211
await running ;
197
212
198
213
/** @type {import('@webcontainer/api').FileSystemTree } */
199
214
const root = { } ;
200
215
201
- for ( const stub of stubs ) {
202
- let tree = root ;
203
-
204
- const path = stub . name . split ( '/' ) . slice ( 1 ) ;
205
- const basename = /** @type {string } */ ( path . pop ( ) ) ;
216
+ let tree = root ;
206
217
207
- for ( const part of path ) {
208
- if ( ! tree [ part ] ) {
209
- /** @type {import('@webcontainer/api').FileSystemTree } */
210
- const directory = { } ;
218
+ const path = file . name . split ( '/' ) . slice ( 1 ) ;
219
+ const basename = /** @type {string } */ ( path . pop ( ) ) ;
211
220
212
- tree [ part ] = {
213
- directory
214
- } ;
215
- }
221
+ for ( const part of path ) {
222
+ if ( ! tree [ part ] ) {
223
+ /** @type { import('@webcontainer/api').FileSystemTree } */
224
+ const directory = { } ;
216
225
217
- tree = /** @type {import('@webcontainer/api').DirectoryNode } */ ( tree [ part ] ) . directory ;
226
+ tree [ part ] = {
227
+ directory
228
+ } ;
218
229
}
219
230
220
- tree [ basename ] = to_file ( stub ) ;
231
+ tree = /** @type { import('@webcontainer/api').DirectoryNode } */ ( tree [ part ] ) . directory ;
221
232
}
222
233
234
+ tree [ basename ] = to_file ( file ) ;
235
+
223
236
await vm . mount ( root ) ;
224
237
225
- stubs_to_map ( stubs , current_stubs ) ;
238
+ current_stubs . set ( file . name , file ) ;
226
239
227
240
await new Promise ( ( f ) => setTimeout ( f , 200 ) ) ; // wait for chokidar
228
241
229
- return will_restart_vite_dev_server ( stubs ) ;
230
- } ,
231
- destroy : async ( ) => {
232
- vm . teardown ( ) ;
242
+ return will_restart_vite_dev_server ( file ) ;
233
243
}
234
244
} ;
235
245
}
236
246
237
247
/**
238
- * @param {import('$lib/types').Stub[] } stubs
248
+ * @param {import('$lib/types').Stub } file
239
249
*/
240
- function will_restart_vite_dev_server ( stubs ) {
241
- return stubs . some (
242
- ( stub ) =>
243
- stub . type === 'file' &&
244
- ( stub . name === '/vite.config.js' ||
245
- stub . name === '/svelte.config.js' ||
246
- stub . name === '/.env' )
250
+ function will_restart_vite_dev_server ( file ) {
251
+ return (
252
+ file . type === 'file' &&
253
+ ( file . name === '/vite.config.js' || file . name === '/svelte.config.js' || file . name === '/.env' )
247
254
) ;
248
255
}
249
256
@@ -272,11 +279,11 @@ function convert_stubs_to_tree(stubs, depth = 1) {
272
279
return tree ;
273
280
}
274
281
275
- /** @param {import('$lib/types').FileStub } stub */
276
- function to_file ( stub ) {
282
+ /** @param {import('$lib/types').FileStub } file */
283
+ function to_file ( file ) {
277
284
// special case
278
- if ( stub . name === '/src/app.html' ) {
279
- const contents = stub . contents . replace (
285
+ if ( file . name === '/src/app.html' ) {
286
+ const contents = file . contents . replace (
280
287
'</head>' ,
281
288
'<script type="module" src="/src/__client.js"></script></head>'
282
289
) ;
@@ -286,20 +293,20 @@ function to_file(stub) {
286
293
} ;
287
294
}
288
295
289
- const contents = stub . text ? stub . contents : base64 . toByteArray ( stub . contents ) ;
296
+ const contents = file . text ? file . contents : base64 . toByteArray ( file . contents ) ;
290
297
291
298
return {
292
299
file : { contents }
293
300
} ;
294
301
}
295
302
296
303
/**
297
- * @param {import('$lib/types').Stub[] } stubs
304
+ * @param {import('$lib/types').Stub[] } files
298
305
* @returns {Map<string, import('$lib/types').Stub> }
299
306
*/
300
- function stubs_to_map ( stubs , map = new Map ( ) ) {
301
- for ( const stub of stubs ) {
302
- map . set ( stub . name , stub ) ;
307
+ function stubs_to_map ( files , map = new Map ( ) ) {
308
+ for ( const file of files ) {
309
+ map . set ( file . name , file ) ;
303
310
}
304
311
return map ;
305
312
}
0 commit comments