|
36 | 36 | );
|
37 | 37 |
|
38 | 38 | /** @type {Map<string, string>} */
|
39 |
| - const expected = new Map(); |
| 39 | + let expected; |
40 | 40 |
|
41 | 41 | /** @type {Map<import('$lib/types').FileStub, import('monaco-editor').editor.ITextModel>} */
|
42 | 42 | const models = new Map();
|
|
136 | 136 | loading = true;
|
137 | 137 |
|
138 | 138 | if (adapter) {
|
139 |
| - expected.clear(); |
140 |
| -
|
141 | 139 | await adapter.reset(Object.values(b));
|
142 |
| - await get_transformed_modules(adapter.base, section.scope.prefix, Object.values(b), expected); |
143 | 140 | } else {
|
144 | 141 | const module = await import('$lib/client/adapters/webcontainer/index.js');
|
145 |
| -
|
146 | 142 | adapter = await module.create(Object.values(b));
|
147 |
| - await get_transformed_modules(adapter.base, section.scope.prefix, Object.values(b), expected); |
148 | 143 | }
|
149 | 144 |
|
150 |
| - const actual = new Map(); |
| 145 | + set_iframe_src(adapter.base); |
| 146 | +
|
| 147 | + try { |
| 148 | + await new Promise((fulfil, reject) => { |
| 149 | + window.addEventListener('message', function handler(e) { |
| 150 | + if (e.origin !== adapter.base) return; |
| 151 | + if (e.data.type === 'ping') { |
| 152 | + window.removeEventListener('message', handler); |
| 153 | + fulfil(undefined); |
| 154 | + } |
151 | 155 |
|
152 |
| - await adapter.reset(stubs); |
153 |
| - await get_transformed_modules(adapter.base, section.scope.prefix, stubs, actual); |
| 156 | + setTimeout(() => { |
| 157 | + reject(new Error('Timed out')); |
| 158 | + }, 5000); |
| 159 | + }); |
| 160 | + }); |
154 | 161 |
|
155 |
| - for (const [name, transformed] of expected.entries()) { |
156 |
| - complete_states[name] = transformed === actual.get(name); |
157 |
| - } |
| 162 | + expected = await get_transformed_modules(section.scope.prefix, Object.values(b)); |
158 | 163 |
|
159 |
| - set_iframe_src(adapter.base); |
160 |
| - initial = false; |
| 164 | + await adapter.reset(stubs); |
| 165 | + const actual = await get_transformed_modules(section.scope.prefix, stubs); |
| 166 | +
|
| 167 | + for (const [name, transformed] of expected.entries()) { |
| 168 | + complete_states[name] = transformed === actual.get(name); |
| 169 | + } |
| 170 | +
|
| 171 | + set_iframe_src(adapter.base); |
| 172 | +
|
| 173 | + loading = false; |
| 174 | + initial = false; |
| 175 | + } catch (e) { |
| 176 | + console.error(e); |
| 177 | + } |
161 | 178 | });
|
162 | 179 |
|
163 | 180 | /** @type {NodeJS.Timeout} */
|
164 | 181 | let timeout;
|
165 | 182 |
|
166 | 183 | /** @param {MessageEvent} e */
|
167 |
| - function handle_message(e) { |
| 184 | + async function handle_message(e) { |
168 | 185 | if (!adapter) return;
|
169 | 186 | if (e.origin !== adapter.base) return;
|
170 | 187 |
|
|
178 | 195 | // we lost contact, refresh the page
|
179 | 196 | loading = true;
|
180 | 197 | set_iframe_src(adapter.base + path);
|
| 198 | + loading = false; |
181 | 199 | }, 500);
|
182 | 200 | } else if (e.data.type === 'hmr') {
|
183 |
| - e.data.data.forEach((update) => handle_hmr_update(update.path)); |
184 |
| - } |
185 |
| - } |
| 201 | + const transformed = await fetch_from_vite(e.data.data.map(({ path }) => path)); |
186 | 202 |
|
187 |
| - /** @param {string} name */ |
188 |
| - async function handle_hmr_update(name) { |
189 |
| - if (Object.keys(section.b).length === 0) return; |
190 |
| -
|
191 |
| - const res = await fetch(adapter.base + name); |
192 |
| - const transformed = normalise(await res.text()); |
193 |
| - complete_states[name] = transformed === expected.get(name); |
194 |
| -
|
195 |
| - if (dev) compare(name, transformed, expected.get(name)); |
196 |
| -
|
197 |
| - if (name.endsWith('.svelte') && transformed.includes('svelte&type=style&lang.css')) { |
198 |
| - name += '?svelte&type=style&lang.css'; |
199 |
| -
|
200 |
| - const res = await fetch(adapter.base + name); |
201 |
| - const transformed = normalise(await res.text()); |
202 |
| - complete_states[name] = transformed === expected.get(name); |
| 203 | + for (const { name, code } of transformed) { |
| 204 | + const normalised = normalise(code); |
| 205 | + complete_states[name] = normalised === expected.get(name); |
| 206 | + if (dev) compare(name, normalised, expected.get(name)); |
| 207 | + } |
203 | 208 |
|
204 |
| - if (dev) compare(name, transformed, expected.get(name)); |
| 209 | + completed = Object.values(complete_states).every((value) => value); |
205 | 210 | }
|
| 211 | + } |
206 | 212 |
|
207 |
| - completed = Object.values(complete_states).every((value) => value); |
| 213 | + /** |
| 214 | + * @param {string[]} names |
| 215 | + * @return {Promise<Array<{ name: string, code: string }>>} |
| 216 | + */ |
| 217 | + async function fetch_from_vite(names) { |
| 218 | + /** @type {Window} */ (iframe.contentWindow).postMessage({ type: 'fetch', names }, '*'); |
| 219 | +
|
| 220 | + return new Promise((fulfil, reject) => { |
| 221 | + window.addEventListener('message', function handler(e) { |
| 222 | + if (e.data.type === 'fetch-result') { |
| 223 | + fulfil(e.data.data); |
| 224 | + window.removeEventListener('message', handler); |
| 225 | + } |
| 226 | + }); |
| 227 | +
|
| 228 | + setTimeout(() => { |
| 229 | + reject(new Error('Timed out')); |
| 230 | + }, 5000); |
| 231 | + }); |
208 | 232 | }
|
209 | 233 |
|
210 | 234 | /**
|
|
215 | 239 | async function compare(name, actual, expected) {
|
216 | 240 | if (actual === expected) return;
|
217 | 241 |
|
218 |
| - console.log(actual); |
219 |
| -
|
220 | 242 | const Diff = await import('diff');
|
221 |
| - console.group(name); |
| 243 | + console.groupCollapsed(`diff: ${name}`); |
| 244 | + console.log(actual); |
222 | 245 | console.log(Diff.diffLines(actual, expected));
|
223 | 246 | console.groupEnd();
|
224 | 247 | }
|
225 | 248 |
|
226 | 249 | /**
|
227 |
| - * @param {string} base |
228 | 250 | * @param {string} prefix
|
229 | 251 | * @param {import('$lib/types').Stub[]} stubs
|
230 |
| - * @param {Map<string, string>} map |
| 252 | + * @returns {Promise<Map<string, string>>} |
231 | 253 | */
|
232 |
| - async function get_transformed_modules(base, prefix, stubs, map) { |
233 |
| - // TODO commented out because we run into CORS issues |
234 |
| - // for (const stub of stubs) { |
235 |
| - // if (stub.name === '/src/__client.js') continue; |
236 |
| - // if (stub.type !== 'file') continue; |
237 |
| - // if (!/\.(js|ts|svelte)$/.test(stub.name)) continue; |
238 |
| - // if (stub.name.startsWith(prefix)) { |
239 |
| - // const res = await fetch(base + stub.name); |
240 |
| - // const transformed = normalise(await res.text()); |
241 |
| - // map.set(stub.name, transformed); |
242 |
| - // if (stub.name.endsWith('.svelte') && transformed.includes('svelte&type=style&lang.css')) { |
243 |
| - // const name = stub.name + '?svelte&type=style&lang.css'; |
244 |
| - // const res = await fetch(base + name); |
245 |
| - // map.set(name, normalise(await res.text())); |
246 |
| - // } |
247 |
| - // } |
248 |
| - // } |
| 254 | + async function get_transformed_modules(prefix, stubs) { |
| 255 | + const names = stubs |
| 256 | + .filter((stub) => { |
| 257 | + if (stub.name === '/src/__client.js') return; |
| 258 | + if (stub.type !== 'file') return; |
| 259 | + if (!/\.(js|ts|svelte)$/.test(stub.name)) return; |
| 260 | +
|
| 261 | + return stub.name.startsWith(prefix); |
| 262 | + }) |
| 263 | + .map((stub) => stub.name); |
| 264 | +
|
| 265 | + const transformed = await fetch_from_vite(names); |
| 266 | +
|
| 267 | + const map = new Map(); |
| 268 | + transformed.forEach(({ name, code }) => { |
| 269 | + map.set(name, normalise(code)); |
| 270 | + }); |
| 271 | +
|
| 272 | + return map; |
249 | 273 | }
|
250 | 274 |
|
251 | 275 | /** @param {string} code */
|
252 | 276 | function normalise(code) {
|
253 | 277 | return code
|
254 | 278 | .replace(/add_location\([^)]+\)/g, 'add_location(...)')
|
255 |
| - .replace(/\?t=\d+/g, '') |
| 279 | + .replace(/\?[tv]=[a-zA-Z0-9]+/g, '') |
256 | 280 | .replace(/[&?]svelte&type=style&lang\.css/, '')
|
257 | 281 | .replace(/\/\/# sourceMappingURL=.+/, '');
|
258 | 282 | }
|
|
266 | 290 | parentNode?.removeChild(iframe);
|
267 | 291 | iframe.src = src;
|
268 | 292 | parentNode?.appendChild(iframe);
|
269 |
| -
|
270 |
| - loading = false; |
271 | 293 | }
|
272 | 294 |
|
273 | 295 | const hidden = new Set(['__client.js', 'node_modules']);
|
|
367 | 389 | {path}
|
368 | 390 | {loading}
|
369 | 391 | on:refresh={() => {
|
370 |
| - loading = true; |
371 | 392 | set_iframe_src(adapter.base + path);
|
372 | 393 | }}
|
373 | 394 | on:change={(e) => {
|
|
0 commit comments