Skip to content

Commit 854a01c

Browse files
feat: allow version to be a pkg.pr.new url
Co-authored-by: Oscar Dominguez <[email protected]>
1 parent 5728b06 commit 854a01c

File tree

14 files changed

+296
-30
lines changed

14 files changed

+296
-30
lines changed

apps/svelte.dev/src/routes/(authed)/playground/[id]/+page.server.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,15 @@ export async function load({ fetch, params, url }) {
1111

1212
const [gist, examples] = await Promise.all([res.json(), examples_res as Promise<Examples>]);
1313

14+
let version = url.searchParams.get('version') || 'latest';
15+
16+
let is_pkg_pr_new = false;
17+
18+
try {
19+
const url = new URL(version);
20+
is_pkg_pr_new = url.origin === 'https://pkg.pr.new';
21+
} catch {}
22+
1423
return {
1524
gist,
1625
examples: examples
@@ -22,6 +31,7 @@ export async function load({ fetch, params, url }) {
2231
slug: example.slug
2332
}))
2433
})),
25-
version: url.searchParams.get('version') || 'latest'
34+
version: url.searchParams.get('version') || 'latest',
35+
is_pkg_pr_new
2636
};
2737
}

apps/svelte.dev/src/routes/(authed)/playground/[id]/+page.svelte

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@
2626
const can_escape = browser && !$page.url.hash;
2727
2828
onMount(() => {
29-
if (version !== 'local') {
29+
if (version !== 'local' && !data.is_pkg_pr_new) {
3030
fetch(`https://unpkg.com/svelte@${version}/package.json`)
3131
.then((r) => r.json())
3232
.then((pkg) => {
@@ -151,7 +151,9 @@
151151
const svelteUrl =
152152
browser && version === 'local'
153153
? `${location.origin}/playground/local`
154-
: `https://unpkg.com/svelte@${version}`;
154+
: data.is_pkg_pr_new
155+
? version
156+
: `https://unpkg.com/svelte@${version}`;
155157
156158
const relaxed = $derived(data.gist.relaxed || (data.user && data.user.id === data.gist.owner));
157159
</script>
@@ -203,6 +205,7 @@
203205
{can_escape}
204206
injectedJS={mapbox_setup}
205207
{onchange}
208+
isPkgPrNew={data.is_pkg_pr_new}
206209
previewTheme={$theme.current}
207210
/>
208211
</div>

packages/editor/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@
6363
"type": "module",
6464
"dependencies": {
6565
"@lezer/highlight": "^1.2.1",
66-
"esm-env": "^1.0.0"
66+
"esm-env": "^1.0.0",
67+
"tarparser": "^0.0.2"
6768
}
6869
}

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

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,7 @@ export class Workspace {
9696
compiled = $state<Record<string, Compiled>>({});
9797

9898
#svelte_version: string;
99+
#is_pkg_pr_new: boolean;
99100
#readonly = false; // TODO do we need workspaces for readonly stuff?
100101
#files = $state.raw<Item[]>([]);
101102
#current = $state.raw() as File;
@@ -156,19 +157,22 @@ export class Workspace {
156157
files: Item[],
157158
{
158159
svelte_version = 'latest',
160+
is_pkg_pr_new = false,
159161
initial,
160162
readonly = false,
161163
onupdate,
162164
onreset
163165
}: {
164166
svelte_version?: string;
167+
is_pkg_pr_new?: boolean;
165168
initial?: string;
166169
readonly?: boolean;
167170
onupdate?: (file: File) => void;
168171
onreset?: (items: Item[]) => void;
169172
} = {}
170173
) {
171174
this.#svelte_version = svelte_version;
175+
this.#is_pkg_pr_new = is_pkg_pr_new;
172176
this.#readonly = readonly;
173177

174178
this.set(files, initial);
@@ -497,9 +501,11 @@ export class Workspace {
497501

498502
seen.push(file.name);
499503

500-
compile_file(file, this.#svelte_version, this.compiler_options).then((compiled) => {
501-
this.compiled[file.name] = compiled;
502-
});
504+
compile_file(file, this.#svelte_version, this.#is_pkg_pr_new, this.compiler_options).then(
505+
(compiled) => {
506+
this.compiled[file.name] = compiled;
507+
}
508+
);
503509
}
504510

505511
for (const key of keys) {
@@ -529,9 +535,11 @@ export class Workspace {
529535
this.modified[file.name] = true;
530536

531537
if (BROWSER && is_svelte_file(file)) {
532-
compile_file(file, this.#svelte_version, this.compiler_options).then((compiled) => {
533-
this.compiled[file.name] = compiled;
534-
});
538+
compile_file(file, this.#svelte_version, this.#is_pkg_pr_new, this.compiler_options).then(
539+
(compiled) => {
540+
this.compiled[file.name] = compiled;
541+
}
542+
);
535543
}
536544

537545
this.#onupdate(file);

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ if (BROWSER) {
3939
export function compile_file(
4040
file: File,
4141
version: string,
42+
is_pkg_pr_new: boolean,
4243
options: { generate: 'client' | 'server'; dev: boolean }
4344
): Promise<Compiled> {
4445
// @ts-ignore
@@ -53,7 +54,7 @@ export function compile_file(
5354

5455
const file_callbacks = callbacks.get(filename)!;
5556

56-
worker.postMessage({ id, file, version, options });
57+
worker.postMessage({ id, file, version, options, is_pkg_pr_new });
5758

5859
return new Promise((fulfil) => {
5960
file_callbacks.set(id, fulfil);

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

Lines changed: 27 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import type { File } from '../Workspace.svelte';
2+
import { parseTar } from 'tarparser';
23

34
// hack for magic-string and Svelte 4 compiler
45
// do not put this into a separate module and import it, would be treeshaken in prod
@@ -16,18 +17,39 @@ addEventListener('message', async (event) => {
1617
if (!inited) {
1718
inited = true;
1819
const svelte_url = `https://unpkg.com/svelte@${event.data.version}`;
19-
const { version } = await fetch(`${svelte_url}/package.json`).then((r) => r.json());
20-
20+
let local_files;
21+
let package_json;
22+
if (event.data.is_pkg_pr_new) {
23+
const maybe_tar = await fetch(event.data.version);
24+
if (maybe_tar.headers.get('content-type') === 'application/tar+gzip') {
25+
const buffer = await maybe_tar.arrayBuffer();
26+
local_files = await parseTar(buffer);
27+
const package_json_content = local_files.find(
28+
(file) => file.name === 'package/package.json'
29+
)?.text;
30+
if (package_json_content) {
31+
package_json = JSON.parse(package_json_content);
32+
}
33+
}
34+
}
35+
const { version } =
36+
package_json ?? (await fetch(`${svelte_url}/package.json`).then((r) => r.json()));
2137
if (version.startsWith('4.')) {
2238
// unpkg doesn't set the correct MIME type for .cjs files
2339
// https://github.com/mjackson/unpkg/issues/355
24-
const compiler = await fetch(`${svelte_url}/compiler.cjs`).then((r) => r.text());
40+
const compiler =
41+
local_files?.find((file) => file.name === 'package/compiler.cjs')?.text ??
42+
(await fetch(`${svelte_url}/compiler.cjs`).then((r) => r.text()));
2543
(0, eval)(compiler + '\n//# sourceURL=compiler.cjs@' + version);
2644
} else if (version.startsWith('3.')) {
27-
const compiler = await fetch(`${svelte_url}/compiler.js`).then((r) => r.text());
45+
const compiler =
46+
local_files?.find((file) => file.name === 'package/compiler.js')?.text ??
47+
(await fetch(`${svelte_url}/compiler.js`).then((r) => r.text()));
2848
(0, eval)(compiler + '\n//# sourceURL=compiler.js@' + version);
2949
} else {
30-
const compiler = await fetch(`${svelte_url}/compiler/index.js`).then((r) => r.text());
50+
const compiler =
51+
local_files?.find((file) => file.name === 'package/compiler/index.js')?.text ??
52+
(await fetch(`${svelte_url}/compiler/index.js`).then((r) => r.text()));
3153
(0, eval)(compiler + '\n//# sourceURL=compiler/index.js@' + version);
3254
}
3355

packages/editor/src/tarparser.d.ts

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
declare module 'tarparser' {
2+
/**
3+
* @typedef FileDescription
4+
* @property {string} name - The name of the file.
5+
* @property {"file"|"directory"} type - The type of the file, either "file" or "directory".
6+
* @property {number} size - The size of the file in bytes.
7+
* @property {Uint8Array} data - The binary data of the file content.
8+
* @property {string} text - A getter to decode and return the file content as a UTF-8 string.
9+
* @property {FileAttrs} attrs - file attributes
10+
*/
11+
/**
12+
* @typedef FileAttrs
13+
* @property {string} mode - The file permissions in octal format.
14+
* @property {number} uid - User ID of the file owner.
15+
* @property {number} gid - Group ID of the file owner.
16+
* @property {number} mtime - Last modification time in Unix time format.
17+
* @property {string} user - The username of the file owner.
18+
* @property {string} group - The group name of the file owner.
19+
*/
20+
/**
21+
* Parses a tar file from binary data and returns an array of FileDescription objects.
22+
* @param {ArrayBuffer|Uint8Array} data - The binary data of the tar file.
23+
* @returns {Promise<FileDescription[]>} - An array of FileDescription objects representing the parsed files in the tar archive.
24+
*/
25+
export function parseTar(data: ArrayBuffer | Uint8Array): Promise<FileDescription[]>;
26+
export type FileDescription = {
27+
/**
28+
* - The name of the file.
29+
*/
30+
name: string;
31+
/**
32+
* - The type of the file, either "file" or "directory".
33+
*/
34+
type: 'file' | 'directory';
35+
/**
36+
* - The size of the file in bytes.
37+
*/
38+
size: number;
39+
/**
40+
* - The binary data of the file content.
41+
*/
42+
data: Uint8Array;
43+
/**
44+
* - A getter to decode and return the file content as a UTF-8 string.
45+
*/
46+
text: string;
47+
/**
48+
* - file attributes
49+
*/
50+
attrs: FileAttrs;
51+
};
52+
export type FileAttrs = {
53+
/**
54+
* - The file permissions in octal format.
55+
*/
56+
mode: string;
57+
/**
58+
* - User ID of the file owner.
59+
*/
60+
uid: number;
61+
/**
62+
* - Group ID of the file owner.
63+
*/
64+
gid: number;
65+
/**
66+
* - Last modification time in Unix time format.
67+
*/
68+
mtime: number;
69+
/**
70+
* - The username of the file owner.
71+
*/
72+
user: string;
73+
/**
74+
* - The group name of the file owner.
75+
*/
76+
group: string;
77+
};
78+
}

packages/repl/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@
8686
"marked": "^14.1.2",
8787
"resolve.exports": "^2.0.2",
8888
"svelte": "5.0.1",
89+
"tarparser": "^0.0.2",
8990
"zimmerframe": "^1.1.2"
9091
}
9192
}

packages/repl/src/lib/Bundler.ts

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,21 +12,25 @@ export default class Bundler {
1212
hash: string;
1313
worker: Worker;
1414
handlers: Map<number, (data: BundleMessageData) => void>;
15+
#is_pkg_pr_new: boolean;
1516

1617
constructor({
1718
packages_url,
1819
svelte_url,
19-
onstatus
20+
onstatus,
21+
is_pkg_pr_new
2022
}: {
2123
packages_url: string;
2224
svelte_url: string;
2325
onstatus: (val: string | null) => void;
26+
is_pkg_pr_new?: boolean;
2427
}) {
2528
this.hash = `${packages_url}:${svelte_url}`;
29+
this.#is_pkg_pr_new = is_pkg_pr_new;
2630

2731
if (!workers.has(this.hash)) {
2832
const worker = new Worker();
29-
worker.postMessage({ type: 'init', packages_url, svelte_url });
33+
worker.postMessage({ type: 'init', packages_url, svelte_url, is_pkg_pr_new });
3034
workers.set(this.hash, worker);
3135
}
3236

@@ -54,12 +58,12 @@ export default class Bundler {
5458
bundle(files: File[], options: CompileOptions = {}): Promise<BundleResult> {
5559
return new Promise<any>((fulfil) => {
5660
this.handlers.set(uid, fulfil);
57-
5861
this.worker.postMessage({
5962
uid,
6063
type: 'bundle',
6164
files,
62-
options
65+
options,
66+
is_pkg_pr_new: this.#is_pkg_pr_new
6367
});
6468

6569
uid += 1;

packages/repl/src/lib/Repl.svelte

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
injectedJS?: string;
2323
injectedCSS?: string;
2424
previewTheme?: 'light' | 'dark';
25+
isPkgPrNew?: boolean;
2526
onchange?: () => void;
2627
}
2728
@@ -37,7 +38,8 @@
3738
injectedJS = '',
3839
injectedCSS = '',
3940
previewTheme = 'light',
40-
onchange = () => {}
41+
onchange = () => {},
42+
isPkgPrNew
4143
}: Props = $props();
4244
4345
// TODO pass in real data
@@ -51,7 +53,8 @@
5153
5254
const workspace = new Workspace([dummy], {
5355
initial: 'App.svelte',
54-
svelte_version: svelteUrl.split('@')[1],
56+
svelte_version: isPkgPrNew ? svelteUrl : svelteUrl.split('@')[1],
57+
is_pkg_pr_new: isPkgPrNew,
5558
onupdate() {
5659
rebundle();
5760
onchange?.();
@@ -120,6 +123,7 @@
120123
? new Bundler({
121124
packages_url: packagesUrl,
122125
svelte_url: svelteUrl,
126+
is_pkg_pr_new: isPkgPrNew,
123127
onstatus: (message) => {
124128
if (message) {
125129
// show bundler status, but only after time has elapsed, to

0 commit comments

Comments
 (0)