Skip to content

Commit

Permalink
feat: allow version to be a pr-[prnumber] or commit-[commithash] (#…
Browse files Browse the repository at this point in the history
…745)

* feat: allow version to be a `pkg.pr.new` url

Co-authored-by: Oscar Dominguez <[email protected]>

* fix: move from full url to `ref:[pr|hash]`

* fix: typescript

* chore: mode tarparser.d.ts

* chore: swap `ref:` for `pr-`

* feat: allow version to be `commit-[commithash]

* feat: show error if fetching the `tar` fails

* fix: lint

* bump tarparser

* DRY out

* fix: only use fetched package json if it's available

* move logic into component

* simplify with regex

* trim down a bit

* fix

* more robust error handling

* separate onstatus from onerror

* simplify with regex

* no need to thunkify

* DRY out

* avoid overloading svelteUrl, just use the verison string directly

* use Promise.withResolvers, simplify error handling

* turns out this code doesn't actually do anything - very old versions are already broken and this doesn't fix them. we can deal with that another time if we want to

* use existing FETCH_CACHE

* tidy up

* remove some unused code

* lint

* ugh

---------

Co-authored-by: Oscar Dominguez <[email protected]>
Co-authored-by: Rich Harris <[email protected]>
  • Loading branch information
3 people authored Nov 5, 2024
1 parent e830fe9 commit 926b033
Show file tree
Hide file tree
Showing 13 changed files with 181 additions and 152 deletions.
1 change: 0 additions & 1 deletion apps/svelte.dev/src/config.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
// REPL props

export const svelteUrl = `https://unpkg.com/svelte@4`;
export const mapbox_setup = `window.MAPBOX_ACCESS_TOKEN = '${
import.meta.env.VITE_MAPBOX_ACCESS_TOKEN
}';`;
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,7 @@ export async function create(): Promise<Adapter> {

bundler = new Bundler({
packages_url: 'https://unpkg.com',
svelte_url: `https://unpkg.com/svelte`,
// svelte_url: `${browser ? location.origin : ''}/svelte`, // TODO think about bringing back main-build for Playground?
svelte_version: 'latest',
onstatus(val) {
if (!done && val === null) {
done = true;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { error } from '@sveltejs/kit';
import type { Examples } from '../api/examples/all.json/+server.js';

export async function load({ fetch, params, url }) {
export async function load({ fetch, params }) {
const examples_res = fetch('/playground/api/examples/all.json').then((r) => r.json());
const res = await fetch(`/playground/api/${params.id}.json`);

Expand All @@ -21,7 +21,6 @@ export async function load({ fetch, params, url }) {
title: example.title,
slug: example.slug
}))
})),
version: url.searchParams.get('version') || 'latest'
}))
};
}
21 changes: 9 additions & 12 deletions apps/svelte.dev/src/routes/(authed)/playground/[id]/+page.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
import type { Gist } from '$lib/db/types';
import { Repl } from '@sveltejs/repl';
import { theme } from '@sveltejs/site-kit/stores';
import { onMount } from 'svelte';
import { mapbox_setup } from '../../../../config.js';
import AppControls from './AppControls.svelte';
import { compress_and_encode_text, decode_and_decompress_text } from './gzip.js';
Expand All @@ -18,15 +17,18 @@
let repl = $state() as ReturnType<typeof Repl>;
let name = $state(data.gist.name);
let modified = $state(false);
let version = data.version;
let setting_hash: any = null;
// svelte-ignore non_reactive_update
let version = $page.url.searchParams.get('version') || 'latest';
let is_pr_or_commit_version = version.startsWith('pr-') || version.startsWith('commit-');
// Hashed URLs are less safe (we can't delete malicious REPLs), therefore
// don't allow links to escape the sandbox restrictions
const can_escape = browser && !$page.url.hash;
onMount(() => {
if (version !== 'local') {
if (version !== 'local' && !is_pr_or_commit_version) {
$effect(() => {
fetch(`https://unpkg.com/svelte@${version}/package.json`)
.then((r) => r.json())
.then((pkg) => {
Expand All @@ -40,8 +42,8 @@
replaceState(url, {});
}
});
}
});
});
}
afterNavigate(() => {
name = data.gist.name;
Expand Down Expand Up @@ -148,11 +150,6 @@
}
}
const svelteUrl =
browser && version === 'local'
? `${location.origin}/playground/local`
: `https://unpkg.com/svelte@${version}`;
const relaxed = $derived(data.gist.relaxed || (data.user && data.user.id === data.gist.owner));
</script>

Expand Down Expand Up @@ -198,7 +195,7 @@
<div style="display: contents" onfocusout={update_hash}>
<Repl
bind:this={repl}
{svelteUrl}
svelteVersion={version}
{relaxed}
{can_escape}
injectedJS={mapbox_setup}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,35 +4,34 @@
import { theme } from '@sveltejs/site-kit/stores';
import { Repl } from '@sveltejs/repl';
import { mapbox_setup } from '../../../../../config.js';
import { onMount } from 'svelte';
import { page } from '$app/stores';
let { data } = $props();
let repl = $state() as ReturnType<typeof Repl>;
onMount(() => {
if (data.version !== 'local') {
fetch(`https://unpkg.com/svelte@${data.version}/package.json`)
// svelte-ignore non_reactive_update
let version = $page.url.searchParams.get('version') || 'latest';
let is_pr_or_commit_version = version.startsWith('pr-') || version.startsWith('commit-');
if (version !== 'local' && !is_pr_or_commit_version) {
$effect(() => {
fetch(`https://unpkg.com/svelte@${version}/package.json`)
.then((r) => r.json())
.then((pkg) => {
if (pkg.version !== data.version) {
replaceState(`/playground/${data.gist.id}/embed?version=${pkg.version}`, {});
}
});
}
});
});
}
afterNavigate(() => {
repl?.set({
files: data.gist.components
});
});
const svelteUrl =
browser && data.version === 'local'
? `${location.origin}/playground/local`
: `https://unpkg.com/svelte@${data.version}`;
const relaxed = $derived(data.gist.relaxed || (data.user && data.user.id === data.gist.owner));
</script>

Expand All @@ -48,7 +47,7 @@
{#if browser}
<Repl
bind:this={repl}
{svelteUrl}
svelteVersion={version}
{relaxed}
can_escape
injectedJS={mapbox_setup}
Expand Down
3 changes: 2 additions & 1 deletion packages/editor/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@
"type": "module",
"dependencies": {
"@lezer/highlight": "^1.2.1",
"esm-env": "^1.0.0"
"esm-env": "^1.0.0",
"tarparser": "^0.0.4"
}
}
79 changes: 49 additions & 30 deletions packages/editor/src/lib/compile-worker/worker.ts
Original file line number Diff line number Diff line change
@@ -1,49 +1,68 @@
import { parseTar } from 'tarparser';
import type { CompileResult } from 'svelte/compiler';
import type { ExposedCompilerOptions, File } from '../Workspace.svelte';
import type { FileDescription } from 'tarparser';

// hack for magic-string and Svelte 4 compiler
// do not put this into a separate module and import it, would be treeshaken in prod
self.window = self;

declare var self: Window & typeof globalThis & { svelte: typeof import('svelte/compiler') };

let inited = false;
let fulfil_ready: (arg?: never) => void;
const ready = new Promise((f) => {
fulfil_ready = f;
});
let inited: PromiseWithResolvers<typeof self.svelte>;

addEventListener('message', async (event) => {
if (!inited) {
inited = true;
const svelte_url = `https://unpkg.com/svelte@${event.data.version}`;
const { version } = await fetch(`${svelte_url}/package.json`).then((r) => r.json());

if (version.startsWith('4.')) {
// unpkg doesn't set the correct MIME type for .cjs files
// https://github.com/mjackson/unpkg/issues/355
const compiler = await fetch(`${svelte_url}/compiler.cjs`).then((r) => r.text());
(0, eval)(compiler + '\n//# sourceURL=compiler.cjs@' + version);
} else if (version.startsWith('3.')) {
const compiler = await fetch(`${svelte_url}/compiler.js`).then((r) => r.text());
(0, eval)(compiler + '\n//# sourceURL=compiler.js@' + version);
} else {
const compiler = await fetch(`${svelte_url}/compiler/index.js`).then((r) => r.text());
(0, eval)(compiler + '\n//# sourceURL=compiler/index.js@' + version);
async function init(v: string) {
const svelte_url = `https://unpkg.com/svelte@${v}`;
const match = /^(?:pr|commit)-(.+)/.exec(v);

let tarball: FileDescription[] | undefined;
let version: string;

if (match) {
const response = await fetch(`https://pkg.pr.new/svelte@${match[1]}`);

if (!response.ok) {
throw new Error('Could not fetch tarball');
}

fulfil_ready();
tarball = await parseTar(await response.arrayBuffer());

const json = tarball.find((file) => file.name === 'package/package.json')!.text;
version = JSON.parse(json).version;
} else {
version = (await fetch(`${svelte_url}/package.json`).then((r) => r.json())).version;
}

const entry = version.startsWith('3.')
? 'compiler.js'
: version.startsWith('4.')
? 'compiler.cjs'
: 'compiler/index.js';

const compiler = tarball
? tarball.find((file) => file.name === `package/${entry}`)!.text
: await fetch(`${svelte_url}/${entry}`).then((r) => r.text());

(0, eval)(compiler + `\n//# sourceURL=${entry}@` + version);

return self.svelte;
}

addEventListener('message', async (event) => {
if (!inited) {
inited = Promise.withResolvers();
init(event.data.version).then(inited.resolve, inited.reject);
}

await ready;
const svelte = await inited.promise;

const { id, file, options } = event.data as {
id: number;
file: File;
options: ExposedCompilerOptions;
};

if (!file.name.endsWith('.svelte') && !self.svelte.compileModule) {
if (!file.name.endsWith('.svelte') && !svelte.compileModule) {
// .svelte.js file compiled with Svelte 3/4 compiler
postMessage({
id,
Expand All @@ -59,9 +78,9 @@ addEventListener('message', async (event) => {

let migration = null;

if (self.svelte.migrate) {
if (svelte.migrate) {
try {
migration = self.svelte.migrate(file.contents, { filename: file.name });
migration = svelte.migrate(file.contents, { filename: file.name });
} catch (e) {
// can this happen?
}
Expand All @@ -71,7 +90,7 @@ addEventListener('message', async (event) => {
let result: CompileResult;

if (file.name.endsWith('.svelte')) {
const is_svelte_3_or_4 = !self.svelte.compileModule;
const is_svelte_3_or_4 = !svelte.compileModule;
const compilerOptions: any = {
generate: is_svelte_3_or_4
? options.generate === 'client'
Expand All @@ -84,9 +103,9 @@ addEventListener('message', async (event) => {
if (!is_svelte_3_or_4) {
compilerOptions.modernAst = options.modernAst; // else Svelte 3/4 will throw an "unknown option" error
}
result = self.svelte.compile(file.contents, compilerOptions);
result = svelte.compile(file.contents, compilerOptions);
} else {
result = self.svelte.compileModule(file.contents, {
result = svelte.compileModule(file.contents, {
generate: options.generate,
dev: options.dev,
filename: file.name
Expand Down
1 change: 1 addition & 0 deletions packages/repl/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@
"marked": "^14.1.2",
"resolve.exports": "^2.0.2",
"svelte": "5.0.1",
"tarparser": "^0.0.4",
"zimmerframe": "^1.1.2"
}
}
18 changes: 12 additions & 6 deletions packages/repl/src/lib/Bundler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,18 +15,20 @@ export default class Bundler {

constructor({
packages_url,
svelte_url,
onstatus
svelte_version,
onstatus,
onerror
}: {
packages_url: string;
svelte_url: string;
svelte_version: string;
onstatus: (val: string | null) => void;
onerror?: (message: string) => void;
}) {
this.hash = `${packages_url}:${svelte_url}`;
this.hash = `${packages_url}:${svelte_version}`;

if (!workers.has(this.hash)) {
const worker = new Worker();
worker.postMessage({ type: 'init', packages_url, svelte_url });
worker.postMessage({ type: 'init', packages_url, svelte_version });
workers.set(this.hash, worker);
}

Expand All @@ -44,6 +46,11 @@ export default class Bundler {
return;
}

if (event.data.type === 'error') {
onerror?.(event.data.message);
return;
}

onstatus(null);
handler(event.data);
this.handlers.delete(event.data.uid);
Expand All @@ -54,7 +61,6 @@ export default class Bundler {
bundle(files: File[], options: CompileOptions = {}): Promise<BundleResult> {
return new Promise<any>((fulfil) => {
this.handlers.set(uid, fulfil);

this.worker.postMessage({
uid,
type: 'bundle',
Expand Down
Loading

0 comments on commit 926b033

Please sign in to comment.