Skip to content

Commit b903f00

Browse files
authored
request transformed modules via __client.js (sveltejs#82)
1 parent 9375afd commit b903f00

File tree

3 files changed

+132
-65
lines changed

3 files changed

+132
-65
lines changed

content/tutorial/common/src/__client.js

Lines changed: 48 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,51 @@
1+
window.addEventListener('message', async (e) => {
2+
if (e.data.type === 'fetch') {
3+
const names = e.data.names;
4+
5+
const transformed = await Promise.all(
6+
names.map(async (name) => {
7+
const res = await fetch(name);
8+
return {
9+
name,
10+
code: await res.text()
11+
};
12+
})
13+
);
14+
15+
const css_files = [];
16+
17+
for (const { name, code } of transformed) {
18+
if (name.endsWith('.svelte') && code.includes('svelte&type=style&lang.css')) {
19+
css_files.push(name + '?svelte&type=style&lang.css');
20+
}
21+
}
22+
23+
if (css_files.length > 0) {
24+
const css_transformed = await Promise.all(
25+
css_files.map(async (name) => {
26+
const res = await fetch(name);
27+
return {
28+
name,
29+
code: await res.text()
30+
};
31+
})
32+
);
33+
34+
transformed.push(...css_transformed);
35+
}
36+
37+
parent.postMessage(
38+
{
39+
type: 'fetch-result',
40+
data: transformed
41+
},
42+
'*'
43+
);
44+
}
45+
});
46+
147
function ping() {
2-
top.postMessage(
48+
parent.postMessage(
349
{
450
type: 'ping',
551
data: {
@@ -15,7 +61,7 @@ ping();
1561

1662
if (import.meta.hot) {
1763
import.meta.hot.on('vite:beforeUpdate', (event) => {
18-
top.postMessage(
64+
parent.postMessage(
1965
{
2066
type: 'hmr',
2167
data: event.updates

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ export async function create(stubs) {
4040

4141
const base = await new Promise(async (fulfil, reject) => {
4242
vm.on('server-ready', (port, base) => {
43-
console.log(`server ready at ${performance.now()}`);
43+
console.log(`server ready on port ${port} at ${performance.now()}: ${base}`);
4444
fulfil(base);
4545
});
4646

src/routes/tutorial/[slug]/index.svelte

Lines changed: 83 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@
3636
);
3737
3838
/** @type {Map<string, string>} */
39-
const expected = new Map();
39+
let expected;
4040
4141
/** @type {Map<import('$lib/types').FileStub, import('monaco-editor').editor.ITextModel>} */
4242
const models = new Map();
@@ -136,35 +136,52 @@
136136
loading = true;
137137
138138
if (adapter) {
139-
expected.clear();
140-
141139
await adapter.reset(Object.values(b));
142-
await get_transformed_modules(adapter.base, section.scope.prefix, Object.values(b), expected);
143140
} else {
144141
const module = await import('$lib/client/adapters/webcontainer/index.js');
145-
146142
adapter = await module.create(Object.values(b));
147-
await get_transformed_modules(adapter.base, section.scope.prefix, Object.values(b), expected);
148143
}
149144
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+
}
151155
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+
});
154161
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));
158163
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+
}
161178
});
162179
163180
/** @type {NodeJS.Timeout} */
164181
let timeout;
165182
166183
/** @param {MessageEvent} e */
167-
function handle_message(e) {
184+
async function handle_message(e) {
168185
if (!adapter) return;
169186
if (e.origin !== adapter.base) return;
170187
@@ -178,33 +195,40 @@
178195
// we lost contact, refresh the page
179196
loading = true;
180197
set_iframe_src(adapter.base + path);
198+
loading = false;
181199
}, 500);
182200
} 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));
186202
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+
}
203208
204-
if (dev) compare(name, transformed, expected.get(name));
209+
completed = Object.values(complete_states).every((value) => value);
205210
}
211+
}
206212
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+
});
208232
}
209233
210234
/**
@@ -215,44 +239,44 @@
215239
async function compare(name, actual, expected) {
216240
if (actual === expected) return;
217241
218-
console.log(actual);
219-
220242
const Diff = await import('diff');
221-
console.group(name);
243+
console.groupCollapsed(`diff: ${name}`);
244+
console.log(actual);
222245
console.log(Diff.diffLines(actual, expected));
223246
console.groupEnd();
224247
}
225248
226249
/**
227-
* @param {string} base
228250
* @param {string} prefix
229251
* @param {import('$lib/types').Stub[]} stubs
230-
* @param {Map<string, string>} map
252+
* @returns {Promise<Map<string, string>>}
231253
*/
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;
249273
}
250274
251275
/** @param {string} code */
252276
function normalise(code) {
253277
return code
254278
.replace(/add_location\([^)]+\)/g, 'add_location(...)')
255-
.replace(/\?t=\d+/g, '')
279+
.replace(/\?[tv]=[a-zA-Z0-9]+/g, '')
256280
.replace(/[&?]svelte&type=style&lang\.css/, '')
257281
.replace(/\/\/# sourceMappingURL=.+/, '');
258282
}
@@ -266,8 +290,6 @@
266290
parentNode?.removeChild(iframe);
267291
iframe.src = src;
268292
parentNode?.appendChild(iframe);
269-
270-
loading = false;
271293
}
272294
273295
const hidden = new Set(['__client.js', 'node_modules']);
@@ -367,7 +389,6 @@
367389
{path}
368390
{loading}
369391
on:refresh={() => {
370-
loading = true;
371392
set_iframe_src(adapter.base + path);
372393
}}
373394
on:change={(e) => {

0 commit comments

Comments
 (0)