Skip to content

Commit bd20c7a

Browse files
Rich-HarrisRich Harris
and
Rich Harris
authored
queue all updates (sveltejs#272)
* queue all updates * remove some unused stuff --------- Co-authored-by: Rich Harris <[email protected]>
1 parent 49c9e66 commit bd20c7a

File tree

3 files changed

+95
-97
lines changed

3 files changed

+95
-97
lines changed

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,8 @@
5757
"port-authority": "^2.0.1",
5858
"prism-svelte": "^0.5.0",
5959
"prismjs": "^1.29.0",
60-
"ws": "^8.12.1"
60+
"ws": "^8.12.1",
61+
"yootils": "^0.3.1"
6162
},
6263
"packageManager": "[email protected]"
6364
}

pnpm-lock.yaml

Lines changed: 6 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/lib/client/adapters/webcontainer/index.js

Lines changed: 87 additions & 96 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { WebContainer } from '@webcontainer/api';
22
import base64 from 'base64-js';
33
import AnsiToHtml from 'ansi-to-html';
4+
import * as yootils from 'yootils';
45
import { escape_html, get_depth } from '../../../utils.js';
56
import { ready } from '../common/index.js';
67

@@ -25,12 +26,7 @@ export async function create(base, error, progress, logs) {
2526

2627
progress.set({ value: 0, text: 'loading files' });
2728

28-
/**
29-
* Keeps track of the latest create/reset to ensure things are not processed in parallel.
30-
* (if this turns out to be insufficient, we can use a queue)
31-
* @type {Promise<any> | undefined}
32-
*/
33-
let running;
29+
const q = yootils.queue(1);
3430

3531
/** Paths and contents of the currently loaded file stubs */
3632
let current_stubs = stubs_to_map([]);
@@ -118,116 +114,111 @@ export async function create(base, error, progress, logs) {
118114
}
119115

120116
return {
121-
reset: async (stubs) => {
122-
await running;
123-
/** @type {Function} */
124-
let resolve = () => {};
125-
running = new Promise((fulfil) => (resolve = fulfil));
126-
127-
/** @type {import('$lib/types').Stub[]} */
128-
const to_write = [];
129-
130-
for (const stub of stubs) {
131-
if (stub.type === 'file') {
132-
const current = /** @type {import('$lib/types').FileStub} */ (
133-
current_stubs.get(stub.name)
134-
);
135-
136-
if (current?.contents !== stub.contents) {
117+
reset: (stubs) => {
118+
return q.add(async () => {
119+
/** @type {import('$lib/types').Stub[]} */
120+
const to_write = [];
121+
122+
for (const stub of stubs) {
123+
if (stub.type === 'file') {
124+
const current = /** @type {import('$lib/types').FileStub} */ (
125+
current_stubs.get(stub.name)
126+
);
127+
128+
if (current?.contents !== stub.contents) {
129+
to_write.push(stub);
130+
}
131+
} else {
132+
// always add directories, otherwise convert_stubs_to_tree will fail
137133
to_write.push(stub);
138134
}
139-
} else {
140-
// always add directories, otherwise convert_stubs_to_tree will fail
141-
to_write.push(stub);
142-
}
143-
144-
current_stubs.delete(stub.name);
145-
}
146135

147-
// Don't delete the node_modules folder when switching from one exercise to another
148-
// where, as this crashes the dev server.
149-
['/node_modules', '/node_modules/.bin'].forEach((name) => current_stubs.delete(name));
150-
151-
const to_delete = Array.from(current_stubs.keys());
152-
current_stubs = stubs_to_map(stubs);
153-
154-
// For some reason, server-ready is fired again when the vite dev server is restarted.
155-
// We need to wait for it to finish before we can continue, else we might
156-
// request files from Vite before it's ready, leading to a timeout.
157-
const will_restart = launched && to_write.some(will_restart_vite_dev_server);
158-
const promise = will_restart
159-
? new Promise((fulfil, reject) => {
160-
const error_unsub = vm.on('error', (error) => {
161-
error_unsub();
162-
resolve();
163-
reject(new Error(error.message));
164-
});
165-
166-
const ready_unsub = vm.on('server-ready', (port, base) => {
167-
ready_unsub();
168-
console.log(`server ready on port ${port} at ${performance.now()}: ${base}`);
169-
resolve();
170-
fulfil(undefined);
171-
});
172-
173-
setTimeout(() => {
174-
resolve();
175-
reject(new Error('Timed out resetting WebContainer'));
176-
}, 10000);
177-
})
178-
: Promise.resolve();
179-
180-
for (const file of to_delete) {
181-
await vm.fs.rm(file, { force: true, recursive: true });
182-
}
136+
current_stubs.delete(stub.name);
137+
}
183138

184-
await vm.mount(convert_stubs_to_tree(to_write));
185-
await promise;
186-
await new Promise((f) => setTimeout(f, 200)); // wait for chokidar
139+
// Don't delete the node_modules folder when switching from one exercise to another
140+
// where, as this crashes the dev server.
141+
['/node_modules', '/node_modules/.bin'].forEach((name) => current_stubs.delete(name));
142+
143+
const to_delete = Array.from(current_stubs.keys());
144+
current_stubs = stubs_to_map(stubs);
145+
146+
// For some reason, server-ready is fired again when the vite dev server is restarted.
147+
// We need to wait for it to finish before we can continue, else we might
148+
// request files from Vite before it's ready, leading to a timeout.
149+
const will_restart = launched && to_write.some(will_restart_vite_dev_server);
150+
const promise = will_restart
151+
? new Promise((fulfil, reject) => {
152+
const error_unsub = vm.on('error', (error) => {
153+
error_unsub();
154+
reject(new Error(error.message));
155+
});
156+
157+
const ready_unsub = vm.on('server-ready', (port, base) => {
158+
ready_unsub();
159+
console.log(`server ready on port ${port} at ${performance.now()}: ${base}`);
160+
fulfil(undefined);
161+
});
162+
163+
setTimeout(() => {
164+
reject(new Error('Timed out resetting WebContainer'));
165+
}, 10000);
166+
})
167+
: Promise.resolve();
168+
169+
for (const file of to_delete) {
170+
await vm.fs.rm(file, { force: true, recursive: true });
171+
}
187172

188-
resolve();
173+
await vm.mount(convert_stubs_to_tree(to_write));
174+
await promise;
175+
await new Promise((f) => setTimeout(f, 200)); // wait for chokidar
189176

190-
// Also trigger a reload of the iframe in case new files were added / old ones deleted,
191-
// because that can result in a broken UI state
192-
const should_reload = !launched || will_restart || to_delete.length > 0;
177+
// Also trigger a reload of the iframe in case new files were added / old ones deleted,
178+
// because that can result in a broken UI state
179+
const should_reload = !launched || will_restart || to_delete.length > 0;
193180

194-
await launch();
181+
await launch();
195182

196-
return should_reload;
183+
return should_reload;
184+
});
197185
},
198-
update: async (file) => {
199-
await running;
186+
update: (file) => {
187+
return q.add(async () => {
188+
/** @type {import('@webcontainer/api').FileSystemTree} */
189+
const root = {};
200190

201-
/** @type {import('@webcontainer/api').FileSystemTree} */
202-
const root = {};
191+
let tree = root;
203192

204-
let tree = root;
193+
const path = file.name.split('/').slice(1);
194+
const basename = /** @type {string} */ (path.pop());
205195

206-
const path = file.name.split('/').slice(1);
207-
const basename = /** @type {string} */ (path.pop());
196+
for (const part of path) {
197+
if (!tree[part]) {
198+
/** @type {import('@webcontainer/api').FileSystemTree} */
199+
const directory = {};
208200

209-
for (const part of path) {
210-
if (!tree[part]) {
211-
/** @type {import('@webcontainer/api').FileSystemTree} */
212-
const directory = {};
201+
tree[part] = {
202+
directory
203+
};
204+
}
213205

214-
tree[part] = {
215-
directory
216-
};
206+
tree = /** @type {import('@webcontainer/api').DirectoryNode} */ (tree[part]).directory;
217207
}
218208

219-
tree = /** @type {import('@webcontainer/api').DirectoryNode} */ (tree[part]).directory;
220-
}
221-
222-
tree[basename] = to_file(file);
209+
tree[basename] = to_file(file);
223210

224-
await vm.mount(root);
211+
await vm.mount(root);
225212

226-
current_stubs.set(file.name, file);
213+
current_stubs.set(file.name, file);
227214

228-
await new Promise((f) => setTimeout(f, 200)); // wait for chokidar
215+
// we need to stagger sequential updates, just enough that the HMR
216+
// wires don't get crossed. 50ms seems to be enough of a delay
217+
// to avoid glitches without noticeably affecting update speed
218+
await new Promise((f) => setTimeout(f, 50));
229219

230-
return will_restart_vite_dev_server(file);
220+
return will_restart_vite_dev_server(file);
221+
});
231222
}
232223
};
233224
}

0 commit comments

Comments
 (0)