Skip to content

Commit 9ca228f

Browse files
authored
fix: more playground fixes (sveltejs#500)
closes sveltejs#433 - handle Svelte 3/4 in the bundler worker - handle Svelte 3/4 in the compiler worker - fix race condition
1 parent dfd4c35 commit 9ca228f

File tree

6 files changed

+131
-45
lines changed

6 files changed

+131
-45
lines changed

packages/editor/src/lib/Workspace.svelte.ts

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -32,10 +32,10 @@ export type Item = File | Directory;
3232

3333
export interface Compiled {
3434
error: CompileError | null;
35-
result: CompileResult;
35+
result: CompileResult | null;
3636
migration: {
3737
code: string;
38-
};
38+
} | null;
3939
}
4040

4141
function is_file(item: Item): item is File {
@@ -85,6 +85,7 @@ export class Workspace {
8585
});
8686
compiled = $state<Record<string, Compiled>>({});
8787

88+
#svelte_version: string;
8889
#readonly = false; // TODO do we need workspaces for readonly stuff?
8990
#files = $state.raw<Item[]>([]);
9091
#current = $state.raw() as File;
@@ -99,17 +100,20 @@ export class Workspace {
99100
constructor(
100101
files: Item[],
101102
{
103+
svelte_version = 'latest',
102104
initial,
103105
readonly = false,
104106
onupdate,
105107
onreset
106108
}: {
109+
svelte_version?: string;
107110
initial?: string;
108111
readonly?: boolean;
109112
onupdate?: (file: File) => void;
110113
onreset?: (items: Item[]) => void;
111114
} = {}
112115
) {
116+
this.#svelte_version = svelte_version;
113117
this.#readonly = readonly;
114118

115119
this.set(files, initial);
@@ -315,7 +319,7 @@ export class Workspace {
315319
this.modified[file.name] = true;
316320

317321
if (BROWSER && is_svelte_file(file)) {
318-
compile_file(file, this.compiler_options).then((compiled) => {
322+
compile_file(file, this.#svelte_version, this.compiler_options).then((compiled) => {
319323
this.compiled[file.name] = compiled;
320324
});
321325
}
@@ -430,7 +434,7 @@ export class Workspace {
430434

431435
seen.push(file.name);
432436

433-
compile_file(file, this.compiler_options).then((compiled) => {
437+
compile_file(file, this.#svelte_version, this.compiler_options).then((compiled) => {
434438
this.compiled[file.name] = compiled;
435439
});
436440
}
Lines changed: 31 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
11
import { BROWSER } from 'esm-env';
22
import CompileWorker from './worker?worker';
33
import type { Compiled, File } from '../Workspace.svelte';
4-
import type { CompileOptions } from 'svelte/compiler';
54

6-
const callbacks = new Map<number, (compiled: Compiled) => void>();
5+
const callbacks = new Map<string, Map<number, (compiled: Compiled) => void>>();
76

87
let worker: Worker;
98

@@ -13,27 +12,50 @@ if (BROWSER) {
1312
worker = new CompileWorker();
1413

1514
worker.addEventListener('message', (event) => {
16-
const callback = callbacks.get(event.data.id);
17-
18-
if (callback) {
19-
callback(event.data.payload);
20-
callbacks.delete(event.data.id);
15+
const { filename, id, payload } = event.data;
16+
const file_callbacks = callbacks.get(filename);
17+
18+
if (file_callbacks) {
19+
const callback = file_callbacks.get(id);
20+
if (callback) {
21+
callback(payload);
22+
file_callbacks.delete(id);
23+
24+
for (const [other_id, callback] of file_callbacks) {
25+
if (id > other_id) {
26+
callback(payload);
27+
file_callbacks.delete(other_id);
28+
}
29+
}
30+
31+
if (file_callbacks.size === 0) {
32+
callbacks.delete(filename);
33+
}
34+
}
2135
}
2236
});
2337
}
2438

2539
export function compile_file(
2640
file: File,
41+
version: string,
2742
options: { generate: 'client' | 'server'; dev: boolean }
2843
): Promise<Compiled> {
2944
// @ts-ignore
3045
if (!BROWSER) return;
3146

3247
let id = uid++;
48+
const filename = file.name;
49+
50+
if (!callbacks.has(filename)) {
51+
callbacks.set(filename, new Map());
52+
}
53+
54+
const file_callbacks = callbacks.get(filename)!;
3355

34-
worker.postMessage({ id, file, options });
56+
worker.postMessage({ id, file, version, options });
3557

3658
return new Promise((fulfil) => {
37-
callbacks.set(id, fulfil);
59+
file_callbacks.set(id, fulfil);
3860
});
3961
}

packages/editor/src/lib/compile-worker/worker.ts

Lines changed: 66 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,43 +1,99 @@
1-
import { compile, compileModule, migrate } from 'svelte/compiler';
21
import type { File } from '../Workspace.svelte';
32

4-
// TODO need to handle Svelte 3/4 for playground
3+
// hack for magic-string and Svelte 4 compiler
4+
// do not put this into a separate module and import it, would be treeshaken in prod
5+
self.window = self;
6+
7+
declare var self: Window & typeof globalThis & { svelte: typeof import('svelte/compiler') };
8+
9+
let inited = false;
10+
let fulfil_ready: (arg?: never) => void;
11+
const ready = new Promise((f) => {
12+
fulfil_ready = f;
13+
});
14+
15+
addEventListener('message', async (event) => {
16+
if (!inited) {
17+
inited = true;
18+
const svelte_url = `https://unpkg.com/svelte@${event.data.version}`;
19+
const { version } = await fetch(`${svelte_url}/package.json`).then((r) => r.json());
20+
21+
if (version.startsWith('4.')) {
22+
// unpkg doesn't set the correct MIME type for .cjs files
23+
// https://github.com/mjackson/unpkg/issues/355
24+
const compiler = await fetch(`${svelte_url}/compiler.cjs`).then((r) => r.text());
25+
(0, eval)(compiler + '\n//# sourceURL=compiler.cjs@' + version);
26+
} else if (version.startsWith('3.')) {
27+
const compiler = await fetch(`${svelte_url}/compiler.js`).then((r) => r.text());
28+
(0, eval)(compiler + '\n//# sourceURL=compiler.js@' + version);
29+
} else {
30+
const compiler = await fetch(`${svelte_url}/compiler/index.js`).then((r) => r.text());
31+
(0, eval)(compiler + '\n//# sourceURL=compiler/index.js@' + version);
32+
}
33+
34+
fulfil_ready();
35+
}
36+
37+
await ready;
538

6-
addEventListener('message', (event) => {
739
const { id, file, options } = event.data as {
840
id: number;
941
file: File;
1042
options: { generate: 'client' | 'server'; dev: boolean };
1143
};
1244

13-
const fn = file.name.endsWith('.svelte') ? compile : compileModule;
45+
const fn = file.name.endsWith('.svelte') ? self.svelte.compile : self.svelte.compileModule;
46+
47+
if (!fn) {
48+
// .svelte.js file compiled with Svelte 3/4 compiler
49+
postMessage({
50+
id,
51+
filename: file.name,
52+
payload: {
53+
error: null,
54+
result: null,
55+
migration: null
56+
}
57+
});
58+
return;
59+
}
1460

1561
let migration = null;
1662

17-
try {
18-
migration = migrate(file.contents, { filename: file.name });
19-
} catch (e) {
20-
// can this happen?
63+
if (self.svelte.migrate) {
64+
try {
65+
migration = self.svelte.migrate(file.contents, { filename: file.name });
66+
} catch (e) {
67+
// can this happen?
68+
}
2169
}
2270

2371
try {
2472
const result = fn(file.contents, { ...options, filename: file.name });
2573

2674
postMessage({
2775
id,
76+
filename: file.name,
2877
payload: {
2978
error: null,
3079
result: {
80+
// @ts-expect-error Svelte 3/4 doesn't contain this field
81+
metadata: { runes: false },
3182
...result,
32-
// @ts-expect-error https://github.com/sveltejs/svelte/issues/13628
33-
warnings: result.warnings.map((w) => ({ message: w.message, ...w }))
83+
warnings: result.warnings.map((w) => {
84+
// @ts-expect-error This exists on Svelte 3/4 and is required to be deleted, otherwise postMessage won't work
85+
delete w.toString;
86+
// @ts-expect-error https://github.com/sveltejs/svelte/issues/13628 (fixed in 5.0, but was like that for most of the preview phase)
87+
return { message: w.message, ...w };
88+
})
3489
},
3590
migration
3691
}
3792
});
3893
} catch (e) {
3994
postMessage({
4095
id,
96+
filename: file.name,
4197
payload: {
4298
// @ts-expect-error
4399
error: { message: e.message, ...e },

packages/repl/src/lib/Output/Output.svelte

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -65,14 +65,12 @@
6565
6666
// TODO this effect is a bit of a code smell
6767
$effect(() => {
68-
if (current) {
69-
if (current.error) {
70-
js.contents = css.contents = `/* ${current.error.message} */`;
71-
} else {
72-
js.contents = current.result.js.code;
73-
css.contents =
74-
current.result.css?.code ?? `/* Add a <st` + `yle> tag to see the CSS output */`;
75-
}
68+
if (current?.error) {
69+
js.contents = css.contents = `/* ${current.error.message} */`;
70+
} else if (current?.result) {
71+
js.contents = current.result.js.code;
72+
css.contents =
73+
current.result.css?.code ?? `/* Add a <st` + `yle> tag to see the CSS output */`;
7674
} else {
7775
js.contents = css.contents = `/* Select a component to see its compiled code */`;
7876
}

packages/repl/src/lib/Repl.svelte

Lines changed: 12 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -49,18 +49,17 @@
4949
text: true
5050
};
5151
52-
const workspace = $state(
53-
new Workspace([dummy], {
54-
initial: 'App.svelte',
55-
onupdate() {
56-
rebundle();
57-
onchange?.();
58-
},
59-
onreset() {
60-
rebundle();
61-
}
62-
})
63-
);
52+
const workspace = new Workspace([dummy], {
53+
initial: 'App.svelte',
54+
svelte_version: svelteUrl.split('@')[1],
55+
onupdate() {
56+
rebundle();
57+
onchange?.();
58+
},
59+
onreset() {
60+
rebundle();
61+
}
62+
});
6463
6564
// TODO get rid
6665
export function toJSON() {
@@ -105,7 +104,7 @@
105104
106105
workspace.update_file({
107106
...workspace.current!,
108-
contents: migration.code
107+
contents: migration!.code
109108
});
110109
111110
rebundle();

packages/repl/src/lib/workers/bundler/index.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,10 @@ import type { Warning } from '../../types';
1717
import type { CompileError, CompileOptions, CompileResult } from 'svelte/compiler';
1818
import type { File } from 'editor';
1919

20+
// hack for magic-string and rollup inline sourcemaps
21+
// do not put this into a separate module and import it, would be treeshaken in prod
22+
self.window = self;
23+
2024
let packages_url: string;
2125
let svelte_url: string;
2226
let version: string;
@@ -40,6 +44,9 @@ self.addEventListener('message', async (event: MessageEvent<BundleMessageData>)
4044
// https://github.com/mjackson/unpkg/issues/355
4145
const compiler = await fetch(`${svelte_url}/compiler.cjs`).then((r) => r.text());
4246
(0, eval)(compiler + '\n//# sourceURL=compiler.cjs@' + version);
47+
} else if (version.startsWith('3.')) {
48+
const compiler = await fetch(`${svelte_url}/compiler.js`).then((r) => r.text());
49+
(0, eval)(compiler + '\n//# sourceURL=compiler.js@' + version);
4350
} else {
4451
const compiler = await fetch(`${svelte_url}/compiler/index.js`).then((r) => r.text());
4552
(0, eval)(compiler + '\n//# sourceURL=compiler/index.js@' + version);
@@ -395,7 +402,7 @@ async function get_bundle(
395402
`.replace(/\t/g, '');
396403
}
397404
} else if (id.endsWith('.svelte.js')) {
398-
result = svelte.compileModule(code, {
405+
result = svelte.compileModule?.(code, {
399406
filename: name + '.js',
400407
generate: 'client',
401408
dev: true

0 commit comments

Comments
 (0)