@@ -4,6 +4,7 @@ import AnsiToHtml from 'ansi-to-html';
4
4
import * as yootils from 'yootils' ;
5
5
import { escape_html , get_depth } from '../../../utils.js' ;
6
6
import { ready } from '../common/index.js' ;
7
+ import { isWebContainerSupported } from './utils.js' ;
7
8
8
9
/**
9
10
* @typedef {import("../../../../routes/tutorial/[slug]/state.js").CompilerWarning } CompilerWarning
@@ -25,13 +26,15 @@ let vm;
25
26
* @returns {Promise<import('$lib/types').Adapter> }
26
27
*/
27
28
export async function create ( base , error , progress , logs , warnings ) {
28
- if ( / s a f a r i / i . test ( navigator . userAgent ) && ! / c h r o m e / i . test ( navigator . userAgent ) ) {
29
- throw new Error ( 'WebContainers are not supported by Safari' ) ;
29
+ if ( ! isWebContainerSupported ( ) ) {
30
+ throw new Error ( 'WebContainers are not supported by Safari 16.3 or earlier ' ) ;
30
31
}
31
32
32
33
progress . set ( { value : 0 , text : 'loading files' } ) ;
33
34
34
35
const q = yootils . queue ( 1 ) ;
36
+ /** @type {Map<string, Array<import('$lib/types').FileStub>> } */
37
+ const q_per_file = new Map ( ) ;
35
38
36
39
/** Paths and contents of the currently loaded file stubs */
37
40
let current_stubs = stubs_to_map ( [ ] ) ;
@@ -203,25 +206,9 @@ export async function create(base, error, progress, logs, warnings) {
203
206
// For some reason, server-ready is fired again when the vite dev server is restarted.
204
207
// We need to wait for it to finish before we can continue, else we might
205
208
// request files from Vite before it's ready, leading to a timeout.
206
- const will_restart = launched && to_write . some ( will_restart_vite_dev_server ) ;
207
- const promise = will_restart
208
- ? new Promise ( ( fulfil , reject ) => {
209
- const error_unsub = vm . on ( 'error' , ( error ) => {
210
- error_unsub ( ) ;
211
- reject ( new Error ( error . message ) ) ;
212
- } ) ;
213
-
214
- const ready_unsub = vm . on ( 'server-ready' , ( port , base ) => {
215
- ready_unsub ( ) ;
216
- console . log ( `server ready on port ${ port } at ${ performance . now ( ) } : ${ base } ` ) ;
217
- fulfil ( undefined ) ;
218
- } ) ;
219
-
220
- setTimeout ( ( ) => {
221
- reject ( new Error ( 'Timed out resetting WebContainer' ) ) ;
222
- } , 10000 ) ;
223
- } )
224
- : Promise . resolve ( ) ;
209
+ const will_restart = launched &&
210
+ ( to_write . some ( is_config ) || to_delete . some ( is_config_path ) ) ;
211
+ const promise = will_restart ? wait_for_restart_vite ( ) : Promise . resolve ( ) ;
225
212
226
213
for ( const file of to_delete ) {
227
214
await vm . fs . rm ( file , { force : true , recursive : true } ) ;
@@ -241,6 +228,15 @@ export async function create(base, error, progress, logs, warnings) {
241
228
} ) ;
242
229
} ,
243
230
update : ( file ) => {
231
+
232
+ let queue = q_per_file . get ( file . name ) ;
233
+ if ( queue ) {
234
+ queue . push ( file ) ;
235
+ return Promise . resolve ( false ) ;
236
+ }
237
+
238
+ q_per_file . set ( file . name , queue = [ file ] ) ;
239
+
244
240
return q . add ( async ( ) => {
245
241
/** @type {import('@webcontainer/api').FileSystemTree } */
246
242
const root = { } ;
@@ -263,22 +259,35 @@ export async function create(base, error, progress, logs, warnings) {
263
259
tree = /** @type {import('@webcontainer/api').DirectoryNode } */ ( tree [ part ] ) . directory ;
264
260
}
265
261
266
- tree [ basename ] = to_file ( file ) ;
262
+ const will_restart = is_config ( file ) ;
263
+
264
+ while ( queue && queue . length > 0 ) {
267
265
268
- // initialize warnings of this file
269
- $warnings [ file . name ] = [ ] ;
270
- schedule_to_update_warning ( 100 ) ;
266
+ // if the file is updated many times rapidly, get the most recently updated one
267
+ const file = /** @type { import('$lib/types').FileStub } */ ( queue . pop ( ) ) ;
268
+ queue . length = 0
271
269
272
- await vm . mount ( root ) ;
270
+ tree [ basename ] = to_file ( file ) ;
273
271
274
- current_stubs . set ( file . name , file ) ;
272
+ // initialize warnings of this file
273
+ $warnings [ file . name ] = [ ] ;
274
+ schedule_to_update_warning ( 100 ) ;
275
+
276
+ await vm . mount ( root ) ;
277
+
278
+ if ( will_restart ) await wait_for_restart_vite ( ) ;
279
+
280
+ current_stubs . set ( file . name , file ) ;
281
+
282
+ // we need to stagger sequential updates, just enough that the HMR
283
+ // wires don't get crossed. 50ms seems to be enough of a delay
284
+ // to avoid glitches without noticeably affecting update speed
285
+ await new Promise ( ( f ) => setTimeout ( f , 50 ) ) ;
286
+ }
275
287
276
- // we need to stagger sequential updates, just enough that the HMR
277
- // wires don't get crossed. 50ms seems to be enough of a delay
278
- // to avoid glitches without noticeably affecting update speed
279
- await new Promise ( ( f ) => setTimeout ( f , 50 ) ) ;
288
+ q_per_file . delete ( file . name )
280
289
281
- return will_restart_vite_dev_server ( file ) ;
290
+ return will_restart ;
282
291
} ) ;
283
292
}
284
293
} ;
@@ -287,11 +296,34 @@ export async function create(base, error, progress, logs, warnings) {
287
296
/**
288
297
* @param {import('$lib/types').Stub } file
289
298
*/
290
- function will_restart_vite_dev_server ( file ) {
291
- return (
292
- file . type === 'file' &&
293
- ( file . name === '/vite.config.js' || file . name === '/svelte.config.js' || file . name === '/.env' )
294
- ) ;
299
+ function is_config ( file ) {
300
+ return file . type === 'file' && is_config_path ( file . name ) ;
301
+ }
302
+
303
+ /**
304
+ * @param {string } path
305
+ */
306
+ function is_config_path ( path ) {
307
+ return [ '/vite.config.js' , '/svelte.config.js' , '/.env' ] . includes ( path ) ;
308
+ }
309
+
310
+ function wait_for_restart_vite ( ) {
311
+ return new Promise ( ( fulfil , reject ) => {
312
+ const error_unsub = vm . on ( 'error' , ( error ) => {
313
+ error_unsub ( ) ;
314
+ reject ( new Error ( error . message ) ) ;
315
+ } ) ;
316
+
317
+ const ready_unsub = vm . on ( 'server-ready' , ( port , base ) => {
318
+ ready_unsub ( ) ;
319
+ console . log ( `server ready on port ${ port } at ${ performance . now ( ) } : ${ base } ` ) ;
320
+ fulfil ( undefined ) ;
321
+ } ) ;
322
+
323
+ setTimeout ( ( ) => {
324
+ reject ( new Error ( 'Timed out resetting WebContainer' ) ) ;
325
+ } , 10000 ) ;
326
+ } ) ;
295
327
}
296
328
297
329
/**
0 commit comments