From 854a01cea999a876d5afc4a7febb16faa5d7a2ec Mon Sep 17 00:00:00 2001 From: paoloricciuti Date: Fri, 1 Nov 2024 00:05:15 +0100 Subject: [PATCH 01/28] feat: allow version to be a `pkg.pr.new` url Co-authored-by: Oscar Dominguez --- .../(authed)/playground/[id]/+page.server.ts | 12 ++- .../(authed)/playground/[id]/+page.svelte | 7 +- packages/editor/package.json | 3 +- packages/editor/src/lib/Workspace.svelte.ts | 20 +++-- .../editor/src/lib/compile-worker/index.ts | 3 +- .../editor/src/lib/compile-worker/worker.ts | 32 ++++++-- packages/editor/src/tarparser.d.ts | 78 +++++++++++++++++++ packages/repl/package.json | 1 + packages/repl/src/lib/Bundler.ts | 12 ++- packages/repl/src/lib/Repl.svelte | 8 +- .../repl/src/lib/workers/bundler/index.ts | 60 ++++++++++++-- packages/repl/src/lib/workers/workers.d.ts | 1 + packages/repl/src/tarparser.d.ts | 78 +++++++++++++++++++ pnpm-lock.yaml | 11 +++ 14 files changed, 296 insertions(+), 30 deletions(-) create mode 100644 packages/editor/src/tarparser.d.ts create mode 100644 packages/repl/src/tarparser.d.ts diff --git a/apps/svelte.dev/src/routes/(authed)/playground/[id]/+page.server.ts b/apps/svelte.dev/src/routes/(authed)/playground/[id]/+page.server.ts index 6bb2dfbdc..c099b2cc8 100644 --- a/apps/svelte.dev/src/routes/(authed)/playground/[id]/+page.server.ts +++ b/apps/svelte.dev/src/routes/(authed)/playground/[id]/+page.server.ts @@ -11,6 +11,15 @@ export async function load({ fetch, params, url }) { const [gist, examples] = await Promise.all([res.json(), examples_res as Promise]); + let version = url.searchParams.get('version') || 'latest'; + + let is_pkg_pr_new = false; + + try { + const url = new URL(version); + is_pkg_pr_new = url.origin === 'https://pkg.pr.new'; + } catch {} + return { gist, examples: examples @@ -22,6 +31,7 @@ export async function load({ fetch, params, url }) { slug: example.slug })) })), - version: url.searchParams.get('version') || 'latest' + version: url.searchParams.get('version') || 'latest', + is_pkg_pr_new }; } diff --git a/apps/svelte.dev/src/routes/(authed)/playground/[id]/+page.svelte b/apps/svelte.dev/src/routes/(authed)/playground/[id]/+page.svelte index a16ef352a..b06cdc0c3 100644 --- a/apps/svelte.dev/src/routes/(authed)/playground/[id]/+page.svelte +++ b/apps/svelte.dev/src/routes/(authed)/playground/[id]/+page.svelte @@ -26,7 +26,7 @@ const can_escape = browser && !$page.url.hash; onMount(() => { - if (version !== 'local') { + if (version !== 'local' && !data.is_pkg_pr_new) { fetch(`https://unpkg.com/svelte@${version}/package.json`) .then((r) => r.json()) .then((pkg) => { @@ -151,7 +151,9 @@ const svelteUrl = browser && version === 'local' ? `${location.origin}/playground/local` - : `https://unpkg.com/svelte@${version}`; + : data.is_pkg_pr_new + ? version + : `https://unpkg.com/svelte@${version}`; const relaxed = $derived(data.gist.relaxed || (data.user && data.user.id === data.gist.owner)); @@ -203,6 +205,7 @@ {can_escape} injectedJS={mapbox_setup} {onchange} + isPkgPrNew={data.is_pkg_pr_new} previewTheme={$theme.current} /> diff --git a/packages/editor/package.json b/packages/editor/package.json index 33bc48510..d194b87c1 100644 --- a/packages/editor/package.json +++ b/packages/editor/package.json @@ -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.2" } } diff --git a/packages/editor/src/lib/Workspace.svelte.ts b/packages/editor/src/lib/Workspace.svelte.ts index 3c8cbe1a8..86a649883 100644 --- a/packages/editor/src/lib/Workspace.svelte.ts +++ b/packages/editor/src/lib/Workspace.svelte.ts @@ -96,6 +96,7 @@ export class Workspace { compiled = $state>({}); #svelte_version: string; + #is_pkg_pr_new: boolean; #readonly = false; // TODO do we need workspaces for readonly stuff? #files = $state.raw([]); #current = $state.raw() as File; @@ -156,12 +157,14 @@ export class Workspace { files: Item[], { svelte_version = 'latest', + is_pkg_pr_new = false, initial, readonly = false, onupdate, onreset }: { svelte_version?: string; + is_pkg_pr_new?: boolean; initial?: string; readonly?: boolean; onupdate?: (file: File) => void; @@ -169,6 +172,7 @@ export class Workspace { } = {} ) { this.#svelte_version = svelte_version; + this.#is_pkg_pr_new = is_pkg_pr_new; this.#readonly = readonly; this.set(files, initial); @@ -497,9 +501,11 @@ export class Workspace { seen.push(file.name); - compile_file(file, this.#svelte_version, this.compiler_options).then((compiled) => { - this.compiled[file.name] = compiled; - }); + compile_file(file, this.#svelte_version, this.#is_pkg_pr_new, this.compiler_options).then( + (compiled) => { + this.compiled[file.name] = compiled; + } + ); } for (const key of keys) { @@ -529,9 +535,11 @@ export class Workspace { this.modified[file.name] = true; if (BROWSER && is_svelte_file(file)) { - compile_file(file, this.#svelte_version, this.compiler_options).then((compiled) => { - this.compiled[file.name] = compiled; - }); + compile_file(file, this.#svelte_version, this.#is_pkg_pr_new, this.compiler_options).then( + (compiled) => { + this.compiled[file.name] = compiled; + } + ); } this.#onupdate(file); diff --git a/packages/editor/src/lib/compile-worker/index.ts b/packages/editor/src/lib/compile-worker/index.ts index a6b1eada7..5950c4b19 100644 --- a/packages/editor/src/lib/compile-worker/index.ts +++ b/packages/editor/src/lib/compile-worker/index.ts @@ -39,6 +39,7 @@ if (BROWSER) { export function compile_file( file: File, version: string, + is_pkg_pr_new: boolean, options: { generate: 'client' | 'server'; dev: boolean } ): Promise { // @ts-ignore @@ -53,7 +54,7 @@ export function compile_file( const file_callbacks = callbacks.get(filename)!; - worker.postMessage({ id, file, version, options }); + worker.postMessage({ id, file, version, options, is_pkg_pr_new }); return new Promise((fulfil) => { file_callbacks.set(id, fulfil); diff --git a/packages/editor/src/lib/compile-worker/worker.ts b/packages/editor/src/lib/compile-worker/worker.ts index 57eb4fbad..1872c1c41 100644 --- a/packages/editor/src/lib/compile-worker/worker.ts +++ b/packages/editor/src/lib/compile-worker/worker.ts @@ -1,4 +1,5 @@ import type { File } from '../Workspace.svelte'; +import { parseTar } 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 @@ -16,18 +17,39 @@ 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()); - + let local_files; + let package_json; + if (event.data.is_pkg_pr_new) { + const maybe_tar = await fetch(event.data.version); + if (maybe_tar.headers.get('content-type') === 'application/tar+gzip') { + const buffer = await maybe_tar.arrayBuffer(); + local_files = await parseTar(buffer); + const package_json_content = local_files.find( + (file) => file.name === 'package/package.json' + )?.text; + if (package_json_content) { + package_json = JSON.parse(package_json_content); + } + } + } + const { version } = + package_json ?? (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()); + const compiler = + local_files?.find((file) => file.name === 'package/compiler.cjs')?.text ?? + (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()); + const compiler = + local_files?.find((file) => file.name === 'package/compiler.js')?.text ?? + (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()); + const compiler = + local_files?.find((file) => file.name === 'package/compiler/index.js')?.text ?? + (await fetch(`${svelte_url}/compiler/index.js`).then((r) => r.text())); (0, eval)(compiler + '\n//# sourceURL=compiler/index.js@' + version); } diff --git a/packages/editor/src/tarparser.d.ts b/packages/editor/src/tarparser.d.ts new file mode 100644 index 000000000..e09863c3d --- /dev/null +++ b/packages/editor/src/tarparser.d.ts @@ -0,0 +1,78 @@ +declare module 'tarparser' { + /** + * @typedef FileDescription + * @property {string} name - The name of the file. + * @property {"file"|"directory"} type - The type of the file, either "file" or "directory". + * @property {number} size - The size of the file in bytes. + * @property {Uint8Array} data - The binary data of the file content. + * @property {string} text - A getter to decode and return the file content as a UTF-8 string. + * @property {FileAttrs} attrs - file attributes + */ + /** + * @typedef FileAttrs + * @property {string} mode - The file permissions in octal format. + * @property {number} uid - User ID of the file owner. + * @property {number} gid - Group ID of the file owner. + * @property {number} mtime - Last modification time in Unix time format. + * @property {string} user - The username of the file owner. + * @property {string} group - The group name of the file owner. + */ + /** + * Parses a tar file from binary data and returns an array of FileDescription objects. + * @param {ArrayBuffer|Uint8Array} data - The binary data of the tar file. + * @returns {Promise} - An array of FileDescription objects representing the parsed files in the tar archive. + */ + export function parseTar(data: ArrayBuffer | Uint8Array): Promise; + export type FileDescription = { + /** + * - The name of the file. + */ + name: string; + /** + * - The type of the file, either "file" or "directory". + */ + type: 'file' | 'directory'; + /** + * - The size of the file in bytes. + */ + size: number; + /** + * - The binary data of the file content. + */ + data: Uint8Array; + /** + * - A getter to decode and return the file content as a UTF-8 string. + */ + text: string; + /** + * - file attributes + */ + attrs: FileAttrs; + }; + export type FileAttrs = { + /** + * - The file permissions in octal format. + */ + mode: string; + /** + * - User ID of the file owner. + */ + uid: number; + /** + * - Group ID of the file owner. + */ + gid: number; + /** + * - Last modification time in Unix time format. + */ + mtime: number; + /** + * - The username of the file owner. + */ + user: string; + /** + * - The group name of the file owner. + */ + group: string; + }; +} diff --git a/packages/repl/package.json b/packages/repl/package.json index 6d5683f2d..a3873e4cf 100644 --- a/packages/repl/package.json +++ b/packages/repl/package.json @@ -86,6 +86,7 @@ "marked": "^14.1.2", "resolve.exports": "^2.0.2", "svelte": "5.0.1", + "tarparser": "^0.0.2", "zimmerframe": "^1.1.2" } } diff --git a/packages/repl/src/lib/Bundler.ts b/packages/repl/src/lib/Bundler.ts index 6fbef7eff..5f1777954 100644 --- a/packages/repl/src/lib/Bundler.ts +++ b/packages/repl/src/lib/Bundler.ts @@ -12,21 +12,25 @@ export default class Bundler { hash: string; worker: Worker; handlers: Map void>; + #is_pkg_pr_new: boolean; constructor({ packages_url, svelte_url, - onstatus + onstatus, + is_pkg_pr_new }: { packages_url: string; svelte_url: string; onstatus: (val: string | null) => void; + is_pkg_pr_new?: boolean; }) { this.hash = `${packages_url}:${svelte_url}`; + this.#is_pkg_pr_new = is_pkg_pr_new; if (!workers.has(this.hash)) { const worker = new Worker(); - worker.postMessage({ type: 'init', packages_url, svelte_url }); + worker.postMessage({ type: 'init', packages_url, svelte_url, is_pkg_pr_new }); workers.set(this.hash, worker); } @@ -54,12 +58,12 @@ export default class Bundler { bundle(files: File[], options: CompileOptions = {}): Promise { return new Promise((fulfil) => { this.handlers.set(uid, fulfil); - this.worker.postMessage({ uid, type: 'bundle', files, - options + options, + is_pkg_pr_new: this.#is_pkg_pr_new }); uid += 1; diff --git a/packages/repl/src/lib/Repl.svelte b/packages/repl/src/lib/Repl.svelte index 2c20688ae..eb2ef25d6 100644 --- a/packages/repl/src/lib/Repl.svelte +++ b/packages/repl/src/lib/Repl.svelte @@ -22,6 +22,7 @@ injectedJS?: string; injectedCSS?: string; previewTheme?: 'light' | 'dark'; + isPkgPrNew?: boolean; onchange?: () => void; } @@ -37,7 +38,8 @@ injectedJS = '', injectedCSS = '', previewTheme = 'light', - onchange = () => {} + onchange = () => {}, + isPkgPrNew }: Props = $props(); // TODO pass in real data @@ -51,7 +53,8 @@ const workspace = new Workspace([dummy], { initial: 'App.svelte', - svelte_version: svelteUrl.split('@')[1], + svelte_version: isPkgPrNew ? svelteUrl : svelteUrl.split('@')[1], + is_pkg_pr_new: isPkgPrNew, onupdate() { rebundle(); onchange?.(); @@ -120,6 +123,7 @@ ? new Bundler({ packages_url: packagesUrl, svelte_url: svelteUrl, + is_pkg_pr_new: isPkgPrNew, onstatus: (message) => { if (message) { // show bundler status, but only after time has elapsed, to diff --git a/packages/repl/src/lib/workers/bundler/index.ts b/packages/repl/src/lib/workers/bundler/index.ts index 6ca5f928f..e8f1d7f19 100644 --- a/packages/repl/src/lib/workers/bundler/index.ts +++ b/packages/repl/src/lib/workers/bundler/index.ts @@ -16,6 +16,7 @@ import type { BundleMessageData } from '../workers'; import type { Warning } from '../../types'; import type { CompileError, CompileOptions, CompileResult } from 'svelte/compiler'; import type { File } from 'editor'; +import { parseTar } from 'tarparser'; // hack for magic-string and rollup inline sourcemaps // do not put this into a separate module and import it, would be treeshaken in prod @@ -31,24 +32,50 @@ const ready = new Promise((f) => { fulfil_ready = f; }); +let files: Map string>; +let package_json; + self.addEventListener('message', async (event: MessageEvent) => { switch (event.data.type) { case 'init': { ({ packages_url, svelte_url } = event.data); - ({ version } = await fetch(`${svelte_url}/package.json`).then((r) => r.json())); + if (event.data.is_pkg_pr_new) { + let local_files; + + const maybe_tar = await fetch(svelte_url); + if (maybe_tar.headers.get('content-type') === 'application/tar+gzip') { + const buffer = await maybe_tar.arrayBuffer(); + local_files = await parseTar(buffer); + files = new Map( + local_files.map((file) => [file.name.substring('package'.length), () => file.text]) + ); + const package_json_content = files.get('/package.json')(); + if (package_json_content) { + package_json = JSON.parse(package_json_content); + } + } + } + ({ version } = + package_json ?? (await fetch(`${svelte_url}/package.json`).then((r) => r.json()))); console.log(`Using Svelte compiler version ${version}`); 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()); + const compiler = + files.get('/compiler.cjs')() ?? + (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()); + const compiler = + files.get('/compiler.js')() ?? + (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()); + const compiler = + files.get('/compiler/index.js')() ?? + (await fetch(`${svelte_url}/compiler/index.js`).then((r) => r.text())); (0, eval)(compiler + '\n//# sourceURL=compiler/index.js@' + version); } @@ -278,10 +305,13 @@ async function get_bundle( return; } else { // relative import in an external file - const url = new URL(importee, importer).href; - self.postMessage({ type: 'status', uid, message: `resolving ${url}` }); + const url = new URL(importee, importer); + if (url.protocol === 'file:') { + return url.toString(); + } - return await follow_redirects(url, uid); + self.postMessage({ type: 'status', uid, message: `resolving ${url}` }); + return await follow_redirects(url.href, uid); } } else { // fetch from unpkg @@ -324,7 +354,13 @@ async function get_bundle( } }; - const { pkg, pkg_url_base } = await fetch_package_info(); + let pkg; + let pkg_url_base = 'file:'; + if (importee.startsWith(`svelte`)) { + pkg = package_json; + } else { + ({ pkg, pkg_url_base } = await fetch_package_info()); + } try { const resolved_id = await resolve_from_pkg(pkg, subpath, uid, pkg_url_base); @@ -341,6 +377,14 @@ async function get_bundle( return `export const BROWSER = true; export const DEV = true`; } + try { + const resolved_url = new URL(resolved); + + if (resolved_url.protocol === 'file:') { + return files.get(resolved_url.pathname)(); + } + } catch {} + const cached_file = local_files_lookup.get(resolved); if (cached_file) { return cached_file.contents; diff --git a/packages/repl/src/lib/workers/workers.d.ts b/packages/repl/src/lib/workers/workers.d.ts index ab0b5d838..8afd73830 100644 --- a/packages/repl/src/lib/workers/workers.d.ts +++ b/packages/repl/src/lib/workers/workers.d.ts @@ -54,6 +54,7 @@ export type BundleMessageData = { message: string; packages_url: string; svelte_url: string; + is_pkg_pr_new: boolean; files: File[]; options: CompileOptions; }; diff --git a/packages/repl/src/tarparser.d.ts b/packages/repl/src/tarparser.d.ts new file mode 100644 index 000000000..e09863c3d --- /dev/null +++ b/packages/repl/src/tarparser.d.ts @@ -0,0 +1,78 @@ +declare module 'tarparser' { + /** + * @typedef FileDescription + * @property {string} name - The name of the file. + * @property {"file"|"directory"} type - The type of the file, either "file" or "directory". + * @property {number} size - The size of the file in bytes. + * @property {Uint8Array} data - The binary data of the file content. + * @property {string} text - A getter to decode and return the file content as a UTF-8 string. + * @property {FileAttrs} attrs - file attributes + */ + /** + * @typedef FileAttrs + * @property {string} mode - The file permissions in octal format. + * @property {number} uid - User ID of the file owner. + * @property {number} gid - Group ID of the file owner. + * @property {number} mtime - Last modification time in Unix time format. + * @property {string} user - The username of the file owner. + * @property {string} group - The group name of the file owner. + */ + /** + * Parses a tar file from binary data and returns an array of FileDescription objects. + * @param {ArrayBuffer|Uint8Array} data - The binary data of the tar file. + * @returns {Promise} - An array of FileDescription objects representing the parsed files in the tar archive. + */ + export function parseTar(data: ArrayBuffer | Uint8Array): Promise; + export type FileDescription = { + /** + * - The name of the file. + */ + name: string; + /** + * - The type of the file, either "file" or "directory". + */ + type: 'file' | 'directory'; + /** + * - The size of the file in bytes. + */ + size: number; + /** + * - The binary data of the file content. + */ + data: Uint8Array; + /** + * - A getter to decode and return the file content as a UTF-8 string. + */ + text: string; + /** + * - file attributes + */ + attrs: FileAttrs; + }; + export type FileAttrs = { + /** + * - The file permissions in octal format. + */ + mode: string; + /** + * - User ID of the file owner. + */ + uid: number; + /** + * - Group ID of the file owner. + */ + gid: number; + /** + * - Last modification time in Unix time format. + */ + mtime: number; + /** + * - The username of the file owner. + */ + user: string; + /** + * - The group name of the file owner. + */ + group: string; + }; +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 4c128a252..399896669 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -204,6 +204,9 @@ importers: esm-env: specifier: ^1.0.0 version: 1.0.0 + tarparser: + specifier: ^0.0.2 + version: 0.0.2 devDependencies: '@codemirror/autocomplete': specifier: ^6.9.0 @@ -358,6 +361,9 @@ importers: svelte: specifier: 5.0.1 version: 5.0.1 + tarparser: + specifier: ^0.0.2 + version: 0.0.2 zimmerframe: specifier: ^1.1.2 version: 1.1.2 @@ -2966,6 +2972,9 @@ packages: resolution: {integrity: sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==} engines: {node: '>=10'} + tarparser@0.0.2: + resolution: {integrity: sha512-4BdvHPDTQp1Tze9Jlt46djrtUIQ0ZgaIb+R69F79ahi8spOCT/a9EcJgQWVGwaWwJe9fioTkY0a+G1nYpgJBYg==} + term-size@2.2.1: resolution: {integrity: sha512-wK0Ri4fOGjv/XPy8SBHZChl8CM7uMc5VML7SqiQ0zG7+J5Vr+RMQDoHa2CNT6KHUnTGIXH34UDMkPzAUyapBZg==} engines: {node: '>=8'} @@ -5941,6 +5950,8 @@ snapshots: mkdirp: 1.0.4 yallist: 4.0.0 + tarparser@0.0.2: {} + term-size@2.2.1: {} tiny-glob@0.2.9: From da1126fa8c08c64548bea7eafd7462727020783c Mon Sep 17 00:00:00 2001 From: paoloricciuti Date: Fri, 1 Nov 2024 01:06:04 +0100 Subject: [PATCH 02/28] fix: move from full url to `ref:[pr|hash]` --- .../(authed)/playground/[id]/+page.server.ts | 9 ++------ .../(authed)/playground/[id]/+page.svelte | 5 ++--- packages/editor/src/lib/Workspace.svelte.ts | 20 +++++------------ .../editor/src/lib/compile-worker/index.ts | 3 +-- .../editor/src/lib/compile-worker/worker.ts | 8 ++++--- packages/repl/src/lib/Bundler.ts | 11 +++------- packages/repl/src/lib/Repl.svelte | 8 ++----- .../repl/src/lib/workers/bundler/index.ts | 22 ++++++++++--------- packages/repl/src/lib/workers/workers.d.ts | 1 - 9 files changed, 33 insertions(+), 54 deletions(-) diff --git a/apps/svelte.dev/src/routes/(authed)/playground/[id]/+page.server.ts b/apps/svelte.dev/src/routes/(authed)/playground/[id]/+page.server.ts index c099b2cc8..09ec39801 100644 --- a/apps/svelte.dev/src/routes/(authed)/playground/[id]/+page.server.ts +++ b/apps/svelte.dev/src/routes/(authed)/playground/[id]/+page.server.ts @@ -13,12 +13,7 @@ export async function load({ fetch, params, url }) { let version = url.searchParams.get('version') || 'latest'; - let is_pkg_pr_new = false; - - try { - const url = new URL(version); - is_pkg_pr_new = url.origin === 'https://pkg.pr.new'; - } catch {} + let is_ref_version = version.startsWith('ref:'); return { gist, @@ -32,6 +27,6 @@ export async function load({ fetch, params, url }) { })) })), version: url.searchParams.get('version') || 'latest', - is_pkg_pr_new + is_ref_version }; } diff --git a/apps/svelte.dev/src/routes/(authed)/playground/[id]/+page.svelte b/apps/svelte.dev/src/routes/(authed)/playground/[id]/+page.svelte index b06cdc0c3..c08c44865 100644 --- a/apps/svelte.dev/src/routes/(authed)/playground/[id]/+page.svelte +++ b/apps/svelte.dev/src/routes/(authed)/playground/[id]/+page.svelte @@ -26,7 +26,7 @@ const can_escape = browser && !$page.url.hash; onMount(() => { - if (version !== 'local' && !data.is_pkg_pr_new) { + if (version !== 'local' && !data.is_ref_version) { fetch(`https://unpkg.com/svelte@${version}/package.json`) .then((r) => r.json()) .then((pkg) => { @@ -151,7 +151,7 @@ const svelteUrl = browser && version === 'local' ? `${location.origin}/playground/local` - : data.is_pkg_pr_new + : data.is_ref_version ? version : `https://unpkg.com/svelte@${version}`; @@ -205,7 +205,6 @@ {can_escape} injectedJS={mapbox_setup} {onchange} - isPkgPrNew={data.is_pkg_pr_new} previewTheme={$theme.current} /> diff --git a/packages/editor/src/lib/Workspace.svelte.ts b/packages/editor/src/lib/Workspace.svelte.ts index 86a649883..3c8cbe1a8 100644 --- a/packages/editor/src/lib/Workspace.svelte.ts +++ b/packages/editor/src/lib/Workspace.svelte.ts @@ -96,7 +96,6 @@ export class Workspace { compiled = $state>({}); #svelte_version: string; - #is_pkg_pr_new: boolean; #readonly = false; // TODO do we need workspaces for readonly stuff? #files = $state.raw([]); #current = $state.raw() as File; @@ -157,14 +156,12 @@ export class Workspace { files: Item[], { svelte_version = 'latest', - is_pkg_pr_new = false, initial, readonly = false, onupdate, onreset }: { svelte_version?: string; - is_pkg_pr_new?: boolean; initial?: string; readonly?: boolean; onupdate?: (file: File) => void; @@ -172,7 +169,6 @@ export class Workspace { } = {} ) { this.#svelte_version = svelte_version; - this.#is_pkg_pr_new = is_pkg_pr_new; this.#readonly = readonly; this.set(files, initial); @@ -501,11 +497,9 @@ export class Workspace { seen.push(file.name); - compile_file(file, this.#svelte_version, this.#is_pkg_pr_new, this.compiler_options).then( - (compiled) => { - this.compiled[file.name] = compiled; - } - ); + compile_file(file, this.#svelte_version, this.compiler_options).then((compiled) => { + this.compiled[file.name] = compiled; + }); } for (const key of keys) { @@ -535,11 +529,9 @@ export class Workspace { this.modified[file.name] = true; if (BROWSER && is_svelte_file(file)) { - compile_file(file, this.#svelte_version, this.#is_pkg_pr_new, this.compiler_options).then( - (compiled) => { - this.compiled[file.name] = compiled; - } - ); + compile_file(file, this.#svelte_version, this.compiler_options).then((compiled) => { + this.compiled[file.name] = compiled; + }); } this.#onupdate(file); diff --git a/packages/editor/src/lib/compile-worker/index.ts b/packages/editor/src/lib/compile-worker/index.ts index 5950c4b19..a6b1eada7 100644 --- a/packages/editor/src/lib/compile-worker/index.ts +++ b/packages/editor/src/lib/compile-worker/index.ts @@ -39,7 +39,6 @@ if (BROWSER) { export function compile_file( file: File, version: string, - is_pkg_pr_new: boolean, options: { generate: 'client' | 'server'; dev: boolean } ): Promise { // @ts-ignore @@ -54,7 +53,7 @@ export function compile_file( const file_callbacks = callbacks.get(filename)!; - worker.postMessage({ id, file, version, options, is_pkg_pr_new }); + worker.postMessage({ id, file, version, options }); return new Promise((fulfil) => { file_callbacks.set(id, fulfil); diff --git a/packages/editor/src/lib/compile-worker/worker.ts b/packages/editor/src/lib/compile-worker/worker.ts index 1872c1c41..77bc29cd2 100644 --- a/packages/editor/src/lib/compile-worker/worker.ts +++ b/packages/editor/src/lib/compile-worker/worker.ts @@ -17,10 +17,12 @@ addEventListener('message', async (event) => { if (!inited) { inited = true; const svelte_url = `https://unpkg.com/svelte@${event.data.version}`; - let local_files; + let local_files: Awaited>; let package_json; - if (event.data.is_pkg_pr_new) { - const maybe_tar = await fetch(event.data.version); + if (event.data.version.startsWith('ref:')) { + const ref = event.data.version.substring('ref:'.length); + + const maybe_tar = await fetch(`https://pkg.pr.new/svelte@${ref}`); if (maybe_tar.headers.get('content-type') === 'application/tar+gzip') { const buffer = await maybe_tar.arrayBuffer(); local_files = await parseTar(buffer); diff --git a/packages/repl/src/lib/Bundler.ts b/packages/repl/src/lib/Bundler.ts index 5f1777954..2298e72c7 100644 --- a/packages/repl/src/lib/Bundler.ts +++ b/packages/repl/src/lib/Bundler.ts @@ -12,25 +12,21 @@ export default class Bundler { hash: string; worker: Worker; handlers: Map void>; - #is_pkg_pr_new: boolean; constructor({ packages_url, svelte_url, - onstatus, - is_pkg_pr_new + onstatus }: { packages_url: string; svelte_url: string; onstatus: (val: string | null) => void; - is_pkg_pr_new?: boolean; }) { this.hash = `${packages_url}:${svelte_url}`; - this.#is_pkg_pr_new = is_pkg_pr_new; if (!workers.has(this.hash)) { const worker = new Worker(); - worker.postMessage({ type: 'init', packages_url, svelte_url, is_pkg_pr_new }); + worker.postMessage({ type: 'init', packages_url, svelte_url }); workers.set(this.hash, worker); } @@ -62,8 +58,7 @@ export default class Bundler { uid, type: 'bundle', files, - options, - is_pkg_pr_new: this.#is_pkg_pr_new + options }); uid += 1; diff --git a/packages/repl/src/lib/Repl.svelte b/packages/repl/src/lib/Repl.svelte index eb2ef25d6..d599ebc71 100644 --- a/packages/repl/src/lib/Repl.svelte +++ b/packages/repl/src/lib/Repl.svelte @@ -22,7 +22,6 @@ injectedJS?: string; injectedCSS?: string; previewTheme?: 'light' | 'dark'; - isPkgPrNew?: boolean; onchange?: () => void; } @@ -38,8 +37,7 @@ injectedJS = '', injectedCSS = '', previewTheme = 'light', - onchange = () => {}, - isPkgPrNew + onchange = () => {} }: Props = $props(); // TODO pass in real data @@ -53,8 +51,7 @@ const workspace = new Workspace([dummy], { initial: 'App.svelte', - svelte_version: isPkgPrNew ? svelteUrl : svelteUrl.split('@')[1], - is_pkg_pr_new: isPkgPrNew, + svelte_version: svelteUrl.startsWith('ref:') ? svelteUrl : svelteUrl.split('@')[1], onupdate() { rebundle(); onchange?.(); @@ -123,7 +120,6 @@ ? new Bundler({ packages_url: packagesUrl, svelte_url: svelteUrl, - is_pkg_pr_new: isPkgPrNew, onstatus: (message) => { if (message) { // show bundler status, but only after time has elapsed, to diff --git a/packages/repl/src/lib/workers/bundler/index.ts b/packages/repl/src/lib/workers/bundler/index.ts index e8f1d7f19..ac0a99383 100644 --- a/packages/repl/src/lib/workers/bundler/index.ts +++ b/packages/repl/src/lib/workers/bundler/index.ts @@ -33,24 +33,26 @@ const ready = new Promise((f) => { }); let files: Map string>; -let package_json; +let package_json: any; self.addEventListener('message', async (event: MessageEvent) => { switch (event.data.type) { case 'init': { ({ packages_url, svelte_url } = event.data); - if (event.data.is_pkg_pr_new) { - let local_files; + if (svelte_url.startsWith('ref:')) { + let local_files: Awaited>; - const maybe_tar = await fetch(svelte_url); + const ref = svelte_url.substring('ref:'.length); + + const maybe_tar = await fetch(`https://pkg.pr.new/svelte@${ref}`); if (maybe_tar.headers.get('content-type') === 'application/tar+gzip') { const buffer = await maybe_tar.arrayBuffer(); local_files = await parseTar(buffer); files = new Map( local_files.map((file) => [file.name.substring('package'.length), () => file.text]) ); - const package_json_content = files.get('/package.json')(); + const package_json_content = files.get('/package.json')?.(); if (package_json_content) { package_json = JSON.parse(package_json_content); } @@ -64,17 +66,17 @@ self.addEventListener('message', async (event: MessageEvent) // unpkg doesn't set the correct MIME type for .cjs files // https://github.com/mjackson/unpkg/issues/355 const compiler = - files.get('/compiler.cjs')() ?? + files.get('/compiler.cjs')?.() ?? (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 = - files.get('/compiler.js')() ?? + files.get('/compiler.js')?.() ?? (await fetch(`${svelte_url}/compiler.js`).then((r) => r.text())); (0, eval)(compiler + '\n//# sourceURL=compiler.js@' + version); } else { const compiler = - files.get('/compiler/index.js')() ?? + files.get('/compiler/index.js')?.() ?? (await fetch(`${svelte_url}/compiler/index.js`).then((r) => r.text())); (0, eval)(compiler + '\n//# sourceURL=compiler/index.js@' + version); } @@ -354,7 +356,7 @@ async function get_bundle( } }; - let pkg; + let pkg: any; let pkg_url_base = 'file:'; if (importee.startsWith(`svelte`)) { pkg = package_json; @@ -381,7 +383,7 @@ async function get_bundle( const resolved_url = new URL(resolved); if (resolved_url.protocol === 'file:') { - return files.get(resolved_url.pathname)(); + return files.get(resolved_url.pathname)?.(); } } catch {} diff --git a/packages/repl/src/lib/workers/workers.d.ts b/packages/repl/src/lib/workers/workers.d.ts index 8afd73830..ab0b5d838 100644 --- a/packages/repl/src/lib/workers/workers.d.ts +++ b/packages/repl/src/lib/workers/workers.d.ts @@ -54,7 +54,6 @@ export type BundleMessageData = { message: string; packages_url: string; svelte_url: string; - is_pkg_pr_new: boolean; files: File[]; options: CompileOptions; }; From 7cd5a2865f334a144934e6865715527e0cdd2aba Mon Sep 17 00:00:00 2001 From: paoloricciuti Date: Fri, 1 Nov 2024 01:09:00 +0100 Subject: [PATCH 03/28] fix: typescript --- packages/editor/src/lib/compile-worker/worker.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/editor/src/lib/compile-worker/worker.ts b/packages/editor/src/lib/compile-worker/worker.ts index 77bc29cd2..27faa12ee 100644 --- a/packages/editor/src/lib/compile-worker/worker.ts +++ b/packages/editor/src/lib/compile-worker/worker.ts @@ -17,7 +17,7 @@ addEventListener('message', async (event) => { if (!inited) { inited = true; const svelte_url = `https://unpkg.com/svelte@${event.data.version}`; - let local_files: Awaited>; + let local_files: Awaited> | undefined; let package_json; if (event.data.version.startsWith('ref:')) { const ref = event.data.version.substring('ref:'.length); From fdc692c2bbb936256a911d0c7dcf3ef93e275509 Mon Sep 17 00:00:00 2001 From: paoloricciuti Date: Fri, 1 Nov 2024 01:16:25 +0100 Subject: [PATCH 04/28] chore: mode tarparser.d.ts --- apps/svelte.dev/src/tarparser.d.ts | 78 ++++++++++++++++++++++++++++++ 1 file changed, 78 insertions(+) create mode 100644 apps/svelte.dev/src/tarparser.d.ts diff --git a/apps/svelte.dev/src/tarparser.d.ts b/apps/svelte.dev/src/tarparser.d.ts new file mode 100644 index 000000000..e09863c3d --- /dev/null +++ b/apps/svelte.dev/src/tarparser.d.ts @@ -0,0 +1,78 @@ +declare module 'tarparser' { + /** + * @typedef FileDescription + * @property {string} name - The name of the file. + * @property {"file"|"directory"} type - The type of the file, either "file" or "directory". + * @property {number} size - The size of the file in bytes. + * @property {Uint8Array} data - The binary data of the file content. + * @property {string} text - A getter to decode and return the file content as a UTF-8 string. + * @property {FileAttrs} attrs - file attributes + */ + /** + * @typedef FileAttrs + * @property {string} mode - The file permissions in octal format. + * @property {number} uid - User ID of the file owner. + * @property {number} gid - Group ID of the file owner. + * @property {number} mtime - Last modification time in Unix time format. + * @property {string} user - The username of the file owner. + * @property {string} group - The group name of the file owner. + */ + /** + * Parses a tar file from binary data and returns an array of FileDescription objects. + * @param {ArrayBuffer|Uint8Array} data - The binary data of the tar file. + * @returns {Promise} - An array of FileDescription objects representing the parsed files in the tar archive. + */ + export function parseTar(data: ArrayBuffer | Uint8Array): Promise; + export type FileDescription = { + /** + * - The name of the file. + */ + name: string; + /** + * - The type of the file, either "file" or "directory". + */ + type: 'file' | 'directory'; + /** + * - The size of the file in bytes. + */ + size: number; + /** + * - The binary data of the file content. + */ + data: Uint8Array; + /** + * - A getter to decode and return the file content as a UTF-8 string. + */ + text: string; + /** + * - file attributes + */ + attrs: FileAttrs; + }; + export type FileAttrs = { + /** + * - The file permissions in octal format. + */ + mode: string; + /** + * - User ID of the file owner. + */ + uid: number; + /** + * - Group ID of the file owner. + */ + gid: number; + /** + * - Last modification time in Unix time format. + */ + mtime: number; + /** + * - The username of the file owner. + */ + user: string; + /** + * - The group name of the file owner. + */ + group: string; + }; +} From f3ae9818bc1ea6808daecf0ab5068db3da0ef9f3 Mon Sep 17 00:00:00 2001 From: paoloricciuti Date: Fri, 1 Nov 2024 02:02:51 +0100 Subject: [PATCH 05/28] chore: swap `ref:` for `pr-` --- .../src/routes/(authed)/playground/[id]/+page.server.ts | 4 ++-- .../src/routes/(authed)/playground/[id]/+page.svelte | 4 ++-- packages/editor/src/lib/compile-worker/worker.ts | 4 ++-- packages/repl/src/lib/Repl.svelte | 2 +- packages/repl/src/lib/workers/bundler/index.ts | 4 ++-- 5 files changed, 9 insertions(+), 9 deletions(-) diff --git a/apps/svelte.dev/src/routes/(authed)/playground/[id]/+page.server.ts b/apps/svelte.dev/src/routes/(authed)/playground/[id]/+page.server.ts index 09ec39801..638032795 100644 --- a/apps/svelte.dev/src/routes/(authed)/playground/[id]/+page.server.ts +++ b/apps/svelte.dev/src/routes/(authed)/playground/[id]/+page.server.ts @@ -13,7 +13,7 @@ export async function load({ fetch, params, url }) { let version = url.searchParams.get('version') || 'latest'; - let is_ref_version = version.startsWith('ref:'); + let is_pr_version = version.startsWith('pr-'); return { gist, @@ -27,6 +27,6 @@ export async function load({ fetch, params, url }) { })) })), version: url.searchParams.get('version') || 'latest', - is_ref_version + is_pr_version }; } diff --git a/apps/svelte.dev/src/routes/(authed)/playground/[id]/+page.svelte b/apps/svelte.dev/src/routes/(authed)/playground/[id]/+page.svelte index c08c44865..d2f767c03 100644 --- a/apps/svelte.dev/src/routes/(authed)/playground/[id]/+page.svelte +++ b/apps/svelte.dev/src/routes/(authed)/playground/[id]/+page.svelte @@ -26,7 +26,7 @@ const can_escape = browser && !$page.url.hash; onMount(() => { - if (version !== 'local' && !data.is_ref_version) { + if (version !== 'local' && !data.is_pr_version) { fetch(`https://unpkg.com/svelte@${version}/package.json`) .then((r) => r.json()) .then((pkg) => { @@ -151,7 +151,7 @@ const svelteUrl = browser && version === 'local' ? `${location.origin}/playground/local` - : data.is_ref_version + : data.is_pr_version ? version : `https://unpkg.com/svelte@${version}`; diff --git a/packages/editor/src/lib/compile-worker/worker.ts b/packages/editor/src/lib/compile-worker/worker.ts index 27faa12ee..9b4bb118c 100644 --- a/packages/editor/src/lib/compile-worker/worker.ts +++ b/packages/editor/src/lib/compile-worker/worker.ts @@ -19,8 +19,8 @@ addEventListener('message', async (event) => { const svelte_url = `https://unpkg.com/svelte@${event.data.version}`; let local_files: Awaited> | undefined; let package_json; - if (event.data.version.startsWith('ref:')) { - const ref = event.data.version.substring('ref:'.length); + if (event.data.version.startsWith('pr-')) { + const ref = event.data.version.substring('pr-'.length); const maybe_tar = await fetch(`https://pkg.pr.new/svelte@${ref}`); if (maybe_tar.headers.get('content-type') === 'application/tar+gzip') { diff --git a/packages/repl/src/lib/Repl.svelte b/packages/repl/src/lib/Repl.svelte index d599ebc71..f2ffa56e3 100644 --- a/packages/repl/src/lib/Repl.svelte +++ b/packages/repl/src/lib/Repl.svelte @@ -51,7 +51,7 @@ const workspace = new Workspace([dummy], { initial: 'App.svelte', - svelte_version: svelteUrl.startsWith('ref:') ? svelteUrl : svelteUrl.split('@')[1], + svelte_version: svelteUrl.startsWith('pr-') ? svelteUrl : svelteUrl.split('@')[1], onupdate() { rebundle(); onchange?.(); diff --git a/packages/repl/src/lib/workers/bundler/index.ts b/packages/repl/src/lib/workers/bundler/index.ts index ac0a99383..76ccc83e0 100644 --- a/packages/repl/src/lib/workers/bundler/index.ts +++ b/packages/repl/src/lib/workers/bundler/index.ts @@ -40,10 +40,10 @@ self.addEventListener('message', async (event: MessageEvent) case 'init': { ({ packages_url, svelte_url } = event.data); - if (svelte_url.startsWith('ref:')) { + if (svelte_url.startsWith('pr-')) { let local_files: Awaited>; - const ref = svelte_url.substring('ref:'.length); + const ref = svelte_url.substring('pr-'.length); const maybe_tar = await fetch(`https://pkg.pr.new/svelte@${ref}`); if (maybe_tar.headers.get('content-type') === 'application/tar+gzip') { From 88751940aab5ffe7428d354eb0191572c86829d2 Mon Sep 17 00:00:00 2001 From: paoloricciuti Date: Fri, 1 Nov 2024 02:09:57 +0100 Subject: [PATCH 06/28] feat: allow version to be `commit-[commithash] --- .../src/routes/(authed)/playground/[id]/+page.server.ts | 4 ++-- .../src/routes/(authed)/playground/[id]/+page.svelte | 4 ++-- packages/editor/src/lib/compile-worker/worker.ts | 9 +++++++-- packages/repl/src/lib/Repl.svelte | 5 ++++- packages/repl/src/lib/workers/bundler/index.ts | 9 +++++++-- 5 files changed, 22 insertions(+), 9 deletions(-) diff --git a/apps/svelte.dev/src/routes/(authed)/playground/[id]/+page.server.ts b/apps/svelte.dev/src/routes/(authed)/playground/[id]/+page.server.ts index 638032795..fc1f2f086 100644 --- a/apps/svelte.dev/src/routes/(authed)/playground/[id]/+page.server.ts +++ b/apps/svelte.dev/src/routes/(authed)/playground/[id]/+page.server.ts @@ -13,7 +13,7 @@ export async function load({ fetch, params, url }) { let version = url.searchParams.get('version') || 'latest'; - let is_pr_version = version.startsWith('pr-'); + let is_pr_or_commit_version = version.startsWith('pr-') || version.startsWith('commit-'); return { gist, @@ -27,6 +27,6 @@ export async function load({ fetch, params, url }) { })) })), version: url.searchParams.get('version') || 'latest', - is_pr_version + is_pr_or_commit_version }; } diff --git a/apps/svelte.dev/src/routes/(authed)/playground/[id]/+page.svelte b/apps/svelte.dev/src/routes/(authed)/playground/[id]/+page.svelte index d2f767c03..3044fd0b5 100644 --- a/apps/svelte.dev/src/routes/(authed)/playground/[id]/+page.svelte +++ b/apps/svelte.dev/src/routes/(authed)/playground/[id]/+page.svelte @@ -26,7 +26,7 @@ const can_escape = browser && !$page.url.hash; onMount(() => { - if (version !== 'local' && !data.is_pr_version) { + if (version !== 'local' && !data.is_pr_or_commit_version) { fetch(`https://unpkg.com/svelte@${version}/package.json`) .then((r) => r.json()) .then((pkg) => { @@ -151,7 +151,7 @@ const svelteUrl = browser && version === 'local' ? `${location.origin}/playground/local` - : data.is_pr_version + : data.is_pr_or_commit_version ? version : `https://unpkg.com/svelte@${version}`; diff --git a/packages/editor/src/lib/compile-worker/worker.ts b/packages/editor/src/lib/compile-worker/worker.ts index 9b4bb118c..6cf6533eb 100644 --- a/packages/editor/src/lib/compile-worker/worker.ts +++ b/packages/editor/src/lib/compile-worker/worker.ts @@ -19,8 +19,13 @@ addEventListener('message', async (event) => { const svelte_url = `https://unpkg.com/svelte@${event.data.version}`; let local_files: Awaited> | undefined; let package_json; - if (event.data.version.startsWith('pr-')) { - const ref = event.data.version.substring('pr-'.length); + let local_version = event.data.version; + const starts_with_pr = local_version.startsWith('pr-'); + const starts_with_commit = local_version.startsWith('commit-'); + if (starts_with_pr || starts_with_commit) { + const ref = starts_with_pr + ? local_version.substring('pr-'.length) + : local_version.substring('commit-'.length); const maybe_tar = await fetch(`https://pkg.pr.new/svelte@${ref}`); if (maybe_tar.headers.get('content-type') === 'application/tar+gzip') { diff --git a/packages/repl/src/lib/Repl.svelte b/packages/repl/src/lib/Repl.svelte index f2ffa56e3..92e8006f7 100644 --- a/packages/repl/src/lib/Repl.svelte +++ b/packages/repl/src/lib/Repl.svelte @@ -51,7 +51,10 @@ const workspace = new Workspace([dummy], { initial: 'App.svelte', - svelte_version: svelteUrl.startsWith('pr-') ? svelteUrl : svelteUrl.split('@')[1], + svelte_version: + svelteUrl.startsWith('pr-') || svelteUrl.startsWith('commit-') + ? svelteUrl + : svelteUrl.split('@')[1], onupdate() { rebundle(); onchange?.(); diff --git a/packages/repl/src/lib/workers/bundler/index.ts b/packages/repl/src/lib/workers/bundler/index.ts index 76ccc83e0..df37128fe 100644 --- a/packages/repl/src/lib/workers/bundler/index.ts +++ b/packages/repl/src/lib/workers/bundler/index.ts @@ -40,10 +40,15 @@ self.addEventListener('message', async (event: MessageEvent) case 'init': { ({ packages_url, svelte_url } = event.data); - if (svelte_url.startsWith('pr-')) { + const starts_with_pr = svelte_url.startsWith('pr-'); + const starts_with_commit = svelte_url.startsWith('commit-'); + + if (starts_with_pr || starts_with_commit) { let local_files: Awaited>; - const ref = svelte_url.substring('pr-'.length); + const ref = starts_with_pr + ? svelte_url.substring('pr-'.length) + : svelte_url.substring('commit-'.length); const maybe_tar = await fetch(`https://pkg.pr.new/svelte@${ref}`); if (maybe_tar.headers.get('content-type') === 'application/tar+gzip') { From 6f84c82d508e67608cff3faaa0f61ccf91c3c9ee Mon Sep 17 00:00:00 2001 From: paoloricciuti Date: Sat, 2 Nov 2024 23:53:33 +0100 Subject: [PATCH 07/28] feat: show error if fetching the `tar` fails --- packages/repl/src/lib/Bundler.ts | 6 +-- packages/repl/src/lib/Repl.svelte | 11 +++-- .../repl/src/lib/workers/bundler/index.ts | 44 +++++++++++++------ packages/repl/src/lib/workers/workers.d.ts | 2 +- 4 files changed, 43 insertions(+), 20 deletions(-) diff --git a/packages/repl/src/lib/Bundler.ts b/packages/repl/src/lib/Bundler.ts index 2298e72c7..eba096a8a 100644 --- a/packages/repl/src/lib/Bundler.ts +++ b/packages/repl/src/lib/Bundler.ts @@ -20,7 +20,7 @@ export default class Bundler { }: { packages_url: string; svelte_url: string; - onstatus: (val: string | null) => void; + onstatus: (val: string | null, kind?: 'status' | 'error') => void; }) { this.hash = `${packages_url}:${svelte_url}`; @@ -39,8 +39,8 @@ export default class Bundler { if (handler) { // if no handler, was meant for a different REPL - if (event.data.type === 'status') { - onstatus(event.data.message); + if (event.data.type === 'status' || event.data.type === 'error') { + onstatus(event.data.message, event.data.type); return; } diff --git a/packages/repl/src/lib/Repl.svelte b/packages/repl/src/lib/Repl.svelte index 625c59fed..d0cd0e907 100644 --- a/packages/repl/src/lib/Repl.svelte +++ b/packages/repl/src/lib/Repl.svelte @@ -116,6 +116,7 @@ let width = $state(0); let show_output = $state(false); let status: string | null = $state(null); + let runtime_error: Error | null = $state(null); let status_visible = $state(false); let status_timeout: NodeJS.Timeout | undefined = undefined; @@ -123,7 +124,7 @@ ? new Bundler({ packages_url: packagesUrl, svelte_url: svelteUrl, - onstatus: (message) => { + onstatus: (message, kind) => { if (message) { // show bundler status, but only after time has elapsed, to // prevent the banner flickering @@ -137,8 +138,11 @@ status_visible = false; status_timeout = undefined; } - - status = message; + if (kind === 'status') { + status = message; + } else { + runtime_error = new Error(message); + } } }) : null; @@ -192,6 +196,7 @@ {injectedCSS} {previewTheme} {workspace} + runtimeError={status_visible ? runtime_error : null} /> diff --git a/packages/repl/src/lib/workers/bundler/index.ts b/packages/repl/src/lib/workers/bundler/index.ts index b64854e12..6672348d4 100644 --- a/packages/repl/src/lib/workers/bundler/index.ts +++ b/packages/repl/src/lib/workers/bundler/index.ts @@ -28,8 +28,10 @@ let version: string; let current_id: number; let fulfil_ready: (arg?: never) => void; -const ready = new Promise((f) => { +let reject_ready: (arg?: Error) => void; +const ready = new Promise((f, r) => { fulfil_ready = f; + reject_ready = r; }); let files: Map string>; @@ -49,18 +51,26 @@ self.addEventListener('message', async (event: MessageEvent) const ref = starts_with_pr ? svelte_url.substring('pr-'.length) : svelte_url.substring('commit-'.length); - - const maybe_tar = await fetch(`https://pkg.pr.new/svelte@${ref}`); - if (maybe_tar.headers.get('content-type') === 'application/tar+gzip') { - const buffer = await maybe_tar.arrayBuffer(); - local_files = await parseTar(buffer); - files = new Map( - local_files.map((file) => [file.name.substring('package'.length), () => file.text]) - ); - const package_json_content = files.get('/package.json')?.(); - if (package_json_content) { - package_json = JSON.parse(package_json_content); + try { + const maybe_tar = await fetch(`https://pkg.pr.new/svelte@${ref}`); + if (!maybe_tar.ok) + throw new Error( + `impossible to fetch the compiler from this ${starts_with_pr ? 'PR' : 'commit'}` + ); + if (maybe_tar.headers.get('content-type') === 'application/tar+gzip') { + const buffer = await maybe_tar.arrayBuffer(); + local_files = await parseTar(buffer); + files = new Map( + local_files.map((file) => [file.name.substring('package'.length), () => file.text]) + ); + const package_json_content = files.get('/package.json')?.(); + if (package_json_content) { + package_json = JSON.parse(package_json_content); + } } + } catch (e) { + reject_ready(e); + return; } } ({ version } = @@ -91,7 +101,15 @@ self.addEventListener('message', async (event: MessageEvent) } case 'bundle': { - await ready; + try { + await ready; + } catch (e) { + self.postMessage({ + type: 'error', + uid: event.data.uid, + message: `Error loading the compiler: ${e.message}` + }); + } const { uid, files, options } = event.data; if (files.length === 0) return; diff --git a/packages/repl/src/lib/workers/workers.d.ts b/packages/repl/src/lib/workers/workers.d.ts index ab0b5d838..8fd301feb 100644 --- a/packages/repl/src/lib/workers/workers.d.ts +++ b/packages/repl/src/lib/workers/workers.d.ts @@ -50,7 +50,7 @@ export interface MigrateOutput { export type BundleMessageData = { uid: number; - type: 'init' | 'bundle' | 'status'; + type: 'init' | 'bundle' | 'status' | 'error'; message: string; packages_url: string; svelte_url: string; From 9f77f6317d2c80165d130ff18ebc3dc6975a1a83 Mon Sep 17 00:00:00 2001 From: paoloricciuti Date: Sun, 3 Nov 2024 01:17:59 +0100 Subject: [PATCH 08/28] fix: lint --- packages/repl/src/lib/Repl.svelte | 2 +- packages/repl/src/lib/workers/bundler/index.ts | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/repl/src/lib/Repl.svelte b/packages/repl/src/lib/Repl.svelte index d0cd0e907..7b65cf254 100644 --- a/packages/repl/src/lib/Repl.svelte +++ b/packages/repl/src/lib/Repl.svelte @@ -140,7 +140,7 @@ } if (kind === 'status') { status = message; - } else { + } else if (message) { runtime_error = new Error(message); } } diff --git a/packages/repl/src/lib/workers/bundler/index.ts b/packages/repl/src/lib/workers/bundler/index.ts index 6672348d4..df196d7bf 100644 --- a/packages/repl/src/lib/workers/bundler/index.ts +++ b/packages/repl/src/lib/workers/bundler/index.ts @@ -69,7 +69,7 @@ self.addEventListener('message', async (event: MessageEvent) } } } catch (e) { - reject_ready(e); + reject_ready(e as Error); return; } } @@ -107,7 +107,7 @@ self.addEventListener('message', async (event: MessageEvent) self.postMessage({ type: 'error', uid: event.data.uid, - message: `Error loading the compiler: ${e.message}` + message: `Error loading the compiler: ${(e as Error).message}` }); } const { uid, files, options } = event.data; From 4c33e6777ecc25d7e94a7672cc6d8606bd32b9a4 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Mon, 4 Nov 2024 11:35:35 -0500 Subject: [PATCH 09/28] bump tarparser --- apps/svelte.dev/src/tarparser.d.ts | 78 ------------------------------ packages/editor/package.json | 2 +- packages/editor/src/tarparser.d.ts | 78 ------------------------------ packages/repl/package.json | 2 +- packages/repl/src/tarparser.d.ts | 78 ------------------------------ pnpm-lock.yaml | 14 +++--- 6 files changed, 9 insertions(+), 243 deletions(-) delete mode 100644 apps/svelte.dev/src/tarparser.d.ts delete mode 100644 packages/editor/src/tarparser.d.ts delete mode 100644 packages/repl/src/tarparser.d.ts diff --git a/apps/svelte.dev/src/tarparser.d.ts b/apps/svelte.dev/src/tarparser.d.ts deleted file mode 100644 index e09863c3d..000000000 --- a/apps/svelte.dev/src/tarparser.d.ts +++ /dev/null @@ -1,78 +0,0 @@ -declare module 'tarparser' { - /** - * @typedef FileDescription - * @property {string} name - The name of the file. - * @property {"file"|"directory"} type - The type of the file, either "file" or "directory". - * @property {number} size - The size of the file in bytes. - * @property {Uint8Array} data - The binary data of the file content. - * @property {string} text - A getter to decode and return the file content as a UTF-8 string. - * @property {FileAttrs} attrs - file attributes - */ - /** - * @typedef FileAttrs - * @property {string} mode - The file permissions in octal format. - * @property {number} uid - User ID of the file owner. - * @property {number} gid - Group ID of the file owner. - * @property {number} mtime - Last modification time in Unix time format. - * @property {string} user - The username of the file owner. - * @property {string} group - The group name of the file owner. - */ - /** - * Parses a tar file from binary data and returns an array of FileDescription objects. - * @param {ArrayBuffer|Uint8Array} data - The binary data of the tar file. - * @returns {Promise} - An array of FileDescription objects representing the parsed files in the tar archive. - */ - export function parseTar(data: ArrayBuffer | Uint8Array): Promise; - export type FileDescription = { - /** - * - The name of the file. - */ - name: string; - /** - * - The type of the file, either "file" or "directory". - */ - type: 'file' | 'directory'; - /** - * - The size of the file in bytes. - */ - size: number; - /** - * - The binary data of the file content. - */ - data: Uint8Array; - /** - * - A getter to decode and return the file content as a UTF-8 string. - */ - text: string; - /** - * - file attributes - */ - attrs: FileAttrs; - }; - export type FileAttrs = { - /** - * - The file permissions in octal format. - */ - mode: string; - /** - * - User ID of the file owner. - */ - uid: number; - /** - * - Group ID of the file owner. - */ - gid: number; - /** - * - Last modification time in Unix time format. - */ - mtime: number; - /** - * - The username of the file owner. - */ - user: string; - /** - * - The group name of the file owner. - */ - group: string; - }; -} diff --git a/packages/editor/package.json b/packages/editor/package.json index d194b87c1..d5fc2e17a 100644 --- a/packages/editor/package.json +++ b/packages/editor/package.json @@ -64,6 +64,6 @@ "dependencies": { "@lezer/highlight": "^1.2.1", "esm-env": "^1.0.0", - "tarparser": "^0.0.2" + "tarparser": "^0.0.4" } } diff --git a/packages/editor/src/tarparser.d.ts b/packages/editor/src/tarparser.d.ts deleted file mode 100644 index e09863c3d..000000000 --- a/packages/editor/src/tarparser.d.ts +++ /dev/null @@ -1,78 +0,0 @@ -declare module 'tarparser' { - /** - * @typedef FileDescription - * @property {string} name - The name of the file. - * @property {"file"|"directory"} type - The type of the file, either "file" or "directory". - * @property {number} size - The size of the file in bytes. - * @property {Uint8Array} data - The binary data of the file content. - * @property {string} text - A getter to decode and return the file content as a UTF-8 string. - * @property {FileAttrs} attrs - file attributes - */ - /** - * @typedef FileAttrs - * @property {string} mode - The file permissions in octal format. - * @property {number} uid - User ID of the file owner. - * @property {number} gid - Group ID of the file owner. - * @property {number} mtime - Last modification time in Unix time format. - * @property {string} user - The username of the file owner. - * @property {string} group - The group name of the file owner. - */ - /** - * Parses a tar file from binary data and returns an array of FileDescription objects. - * @param {ArrayBuffer|Uint8Array} data - The binary data of the tar file. - * @returns {Promise} - An array of FileDescription objects representing the parsed files in the tar archive. - */ - export function parseTar(data: ArrayBuffer | Uint8Array): Promise; - export type FileDescription = { - /** - * - The name of the file. - */ - name: string; - /** - * - The type of the file, either "file" or "directory". - */ - type: 'file' | 'directory'; - /** - * - The size of the file in bytes. - */ - size: number; - /** - * - The binary data of the file content. - */ - data: Uint8Array; - /** - * - A getter to decode and return the file content as a UTF-8 string. - */ - text: string; - /** - * - file attributes - */ - attrs: FileAttrs; - }; - export type FileAttrs = { - /** - * - The file permissions in octal format. - */ - mode: string; - /** - * - User ID of the file owner. - */ - uid: number; - /** - * - Group ID of the file owner. - */ - gid: number; - /** - * - Last modification time in Unix time format. - */ - mtime: number; - /** - * - The username of the file owner. - */ - user: string; - /** - * - The group name of the file owner. - */ - group: string; - }; -} diff --git a/packages/repl/package.json b/packages/repl/package.json index a3873e4cf..4e3598d59 100644 --- a/packages/repl/package.json +++ b/packages/repl/package.json @@ -86,7 +86,7 @@ "marked": "^14.1.2", "resolve.exports": "^2.0.2", "svelte": "5.0.1", - "tarparser": "^0.0.2", + "tarparser": "^0.0.4", "zimmerframe": "^1.1.2" } } diff --git a/packages/repl/src/tarparser.d.ts b/packages/repl/src/tarparser.d.ts deleted file mode 100644 index e09863c3d..000000000 --- a/packages/repl/src/tarparser.d.ts +++ /dev/null @@ -1,78 +0,0 @@ -declare module 'tarparser' { - /** - * @typedef FileDescription - * @property {string} name - The name of the file. - * @property {"file"|"directory"} type - The type of the file, either "file" or "directory". - * @property {number} size - The size of the file in bytes. - * @property {Uint8Array} data - The binary data of the file content. - * @property {string} text - A getter to decode and return the file content as a UTF-8 string. - * @property {FileAttrs} attrs - file attributes - */ - /** - * @typedef FileAttrs - * @property {string} mode - The file permissions in octal format. - * @property {number} uid - User ID of the file owner. - * @property {number} gid - Group ID of the file owner. - * @property {number} mtime - Last modification time in Unix time format. - * @property {string} user - The username of the file owner. - * @property {string} group - The group name of the file owner. - */ - /** - * Parses a tar file from binary data and returns an array of FileDescription objects. - * @param {ArrayBuffer|Uint8Array} data - The binary data of the tar file. - * @returns {Promise} - An array of FileDescription objects representing the parsed files in the tar archive. - */ - export function parseTar(data: ArrayBuffer | Uint8Array): Promise; - export type FileDescription = { - /** - * - The name of the file. - */ - name: string; - /** - * - The type of the file, either "file" or "directory". - */ - type: 'file' | 'directory'; - /** - * - The size of the file in bytes. - */ - size: number; - /** - * - The binary data of the file content. - */ - data: Uint8Array; - /** - * - A getter to decode and return the file content as a UTF-8 string. - */ - text: string; - /** - * - file attributes - */ - attrs: FileAttrs; - }; - export type FileAttrs = { - /** - * - The file permissions in octal format. - */ - mode: string; - /** - * - User ID of the file owner. - */ - uid: number; - /** - * - Group ID of the file owner. - */ - gid: number; - /** - * - Last modification time in Unix time format. - */ - mtime: number; - /** - * - The username of the file owner. - */ - user: string; - /** - * - The group name of the file owner. - */ - group: string; - }; -} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 399896669..a0a8f1af5 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -205,8 +205,8 @@ importers: specifier: ^1.0.0 version: 1.0.0 tarparser: - specifier: ^0.0.2 - version: 0.0.2 + specifier: ^0.0.4 + version: 0.0.4 devDependencies: '@codemirror/autocomplete': specifier: ^6.9.0 @@ -362,8 +362,8 @@ importers: specifier: 5.0.1 version: 5.0.1 tarparser: - specifier: ^0.0.2 - version: 0.0.2 + specifier: ^0.0.4 + version: 0.0.4 zimmerframe: specifier: ^1.1.2 version: 1.1.2 @@ -2972,8 +2972,8 @@ packages: resolution: {integrity: sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==} engines: {node: '>=10'} - tarparser@0.0.2: - resolution: {integrity: sha512-4BdvHPDTQp1Tze9Jlt46djrtUIQ0ZgaIb+R69F79ahi8spOCT/a9EcJgQWVGwaWwJe9fioTkY0a+G1nYpgJBYg==} + tarparser@0.0.4: + resolution: {integrity: sha512-H3+VR6ys/N4eRBYbcyKuy4i0lKAxW/tx+wZANTFPfXjHLkz7UDs7V9g0rdGJDsVWYyqnmvmLM7YqWZ7yrtoReA==} term-size@2.2.1: resolution: {integrity: sha512-wK0Ri4fOGjv/XPy8SBHZChl8CM7uMc5VML7SqiQ0zG7+J5Vr+RMQDoHa2CNT6KHUnTGIXH34UDMkPzAUyapBZg==} @@ -5950,7 +5950,7 @@ snapshots: mkdirp: 1.0.4 yallist: 4.0.0 - tarparser@0.0.2: {} + tarparser@0.0.4: {} term-size@2.2.1: {} From 3d5eda0cf09642099e9294ae69829837790ca7c1 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Mon, 4 Nov 2024 13:03:09 -0500 Subject: [PATCH 10/28] DRY out --- .../editor/src/lib/compile-worker/worker.ts | 31 ++++++++----------- 1 file changed, 13 insertions(+), 18 deletions(-) diff --git a/packages/editor/src/lib/compile-worker/worker.ts b/packages/editor/src/lib/compile-worker/worker.ts index 9153d7f00..61f567163 100644 --- a/packages/editor/src/lib/compile-worker/worker.ts +++ b/packages/editor/src/lib/compile-worker/worker.ts @@ -40,26 +40,21 @@ addEventListener('message', async (event) => { } } } + const { version } = package_json ?? (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 = - local_files?.find((file) => file.name === 'package/compiler.cjs')?.text ?? - (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 = - local_files?.find((file) => file.name === 'package/compiler.js')?.text ?? - (await fetch(`${svelte_url}/compiler.js`).then((r) => r.text())); - (0, eval)(compiler + '\n//# sourceURL=compiler.js@' + version); - } else { - const compiler = - local_files?.find((file) => file.name === 'package/compiler/index.js')?.text ?? - (await fetch(`${svelte_url}/compiler/index.js`).then((r) => r.text())); - (0, eval)(compiler + '\n//# sourceURL=compiler/index.js@' + version); - } + + const compiler_file = version.startsWith('3.') + ? 'compiler.js' + : version.startsWith('4.') + ? 'compiler.cjs' + : 'compiler/index.js'; + + const compiler_text = local_files + ? local_files.find((file) => file.name === `package/${compiler_file}`)!.text + : await fetch(`${svelte_url}/${compiler_file}`).then((r) => r.text()); + + (0, eval)(compiler_text + `\n//# sourceURL=${compiler_file}@` + version); fulfil_ready(); } From 526cbe741ec0af525604997d32249fb9f0b72ed6 Mon Sep 17 00:00:00 2001 From: paoloricciuti Date: Mon, 4 Nov 2024 21:44:48 +0100 Subject: [PATCH 11/28] fix: only use fetched package json if it's available --- packages/repl/src/lib/workers/bundler/index.ts | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/packages/repl/src/lib/workers/bundler/index.ts b/packages/repl/src/lib/workers/bundler/index.ts index df196d7bf..7e726a1f6 100644 --- a/packages/repl/src/lib/workers/bundler/index.ts +++ b/packages/repl/src/lib/workers/bundler/index.ts @@ -81,18 +81,19 @@ self.addEventListener('message', async (event: MessageEvent) // unpkg doesn't set the correct MIME type for .cjs files // https://github.com/mjackson/unpkg/issues/355 const compiler = - files.get('/compiler.cjs')?.() ?? + files?.get('/compiler.cjs')?.() ?? (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 = - files.get('/compiler.js')?.() ?? + files?.get('/compiler.js')?.() ?? (await fetch(`${svelte_url}/compiler.js`).then((r) => r.text())); (0, eval)(compiler + '\n//# sourceURL=compiler.js@' + version); } else { const compiler = - files.get('/compiler/index.js')?.() ?? + files?.get('/compiler/index.js')?.() ?? (await fetch(`${svelte_url}/compiler/index.js`).then((r) => r.text())); + console.log(compiler); (0, eval)(compiler + '\n//# sourceURL=compiler/index.js@' + version); } @@ -381,7 +382,7 @@ async function get_bundle( let pkg: any; let pkg_url_base = 'file:'; - if (importee.startsWith(`svelte`)) { + if (importee.startsWith(`svelte`) && package_json) { pkg = package_json; } else { ({ pkg, pkg_url_base } = await fetch_package_info()); From c145fbf252ee646d8d0ddb23f8370aee58a2f18e Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Mon, 4 Nov 2024 17:27:47 -0500 Subject: [PATCH 12/28] move logic into component --- .../(authed)/playground/[id]/+page.server.ts | 10 ++-------- .../routes/(authed)/playground/[id]/+page.svelte | 15 ++++++++------- packages/repl/src/lib/workers/bundler/index.ts | 1 - 3 files changed, 10 insertions(+), 16 deletions(-) diff --git a/apps/svelte.dev/src/routes/(authed)/playground/[id]/+page.server.ts b/apps/svelte.dev/src/routes/(authed)/playground/[id]/+page.server.ts index fc1f2f086..2abc3dbdb 100644 --- a/apps/svelte.dev/src/routes/(authed)/playground/[id]/+page.server.ts +++ b/apps/svelte.dev/src/routes/(authed)/playground/[id]/+page.server.ts @@ -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`); @@ -11,10 +11,6 @@ export async function load({ fetch, params, url }) { const [gist, examples] = await Promise.all([res.json(), examples_res as Promise]); - let version = url.searchParams.get('version') || 'latest'; - - let is_pr_or_commit_version = version.startsWith('pr-') || version.startsWith('commit-'); - return { gist, examples: examples @@ -25,8 +21,6 @@ export async function load({ fetch, params, url }) { title: example.title, slug: example.slug })) - })), - version: url.searchParams.get('version') || 'latest', - is_pr_or_commit_version + })) }; } diff --git a/apps/svelte.dev/src/routes/(authed)/playground/[id]/+page.svelte b/apps/svelte.dev/src/routes/(authed)/playground/[id]/+page.svelte index 3044fd0b5..82f88761e 100644 --- a/apps/svelte.dev/src/routes/(authed)/playground/[id]/+page.svelte +++ b/apps/svelte.dev/src/routes/(authed)/playground/[id]/+page.svelte @@ -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'; @@ -18,15 +17,17 @@ let repl = $state() as ReturnType; let name = $state(data.gist.name); let modified = $state(false); - let version = data.version; let setting_hash: any = null; + 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' && !data.is_pr_or_commit_version) { + if (version !== 'local' && !is_pr_or_commit_version) { + $effect(() => { fetch(`https://unpkg.com/svelte@${version}/package.json`) .then((r) => r.json()) .then((pkg) => { @@ -40,8 +41,8 @@ replaceState(url, {}); } }); - } - }); + }); + } afterNavigate(() => { name = data.gist.name; @@ -151,7 +152,7 @@ const svelteUrl = browser && version === 'local' ? `${location.origin}/playground/local` - : data.is_pr_or_commit_version + : is_pr_or_commit_version ? version : `https://unpkg.com/svelte@${version}`; diff --git a/packages/repl/src/lib/workers/bundler/index.ts b/packages/repl/src/lib/workers/bundler/index.ts index 7e726a1f6..c3e1af402 100644 --- a/packages/repl/src/lib/workers/bundler/index.ts +++ b/packages/repl/src/lib/workers/bundler/index.ts @@ -93,7 +93,6 @@ self.addEventListener('message', async (event: MessageEvent) const compiler = files?.get('/compiler/index.js')?.() ?? (await fetch(`${svelte_url}/compiler/index.js`).then((r) => r.text())); - console.log(compiler); (0, eval)(compiler + '\n//# sourceURL=compiler/index.js@' + version); } From 5bb596ea4eac2ebfe13c1c5b10e88541d8296752 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Mon, 4 Nov 2024 17:52:06 -0500 Subject: [PATCH 13/28] simplify with regex --- .../editor/src/lib/compile-worker/worker.ts | 23 +++++++++---------- 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/packages/editor/src/lib/compile-worker/worker.ts b/packages/editor/src/lib/compile-worker/worker.ts index 61f567163..74c44ba53 100644 --- a/packages/editor/src/lib/compile-worker/worker.ts +++ b/packages/editor/src/lib/compile-worker/worker.ts @@ -1,6 +1,7 @@ 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 @@ -18,19 +19,17 @@ addEventListener('message', async (event) => { if (!inited) { inited = true; const svelte_url = `https://unpkg.com/svelte@${event.data.version}`; - let local_files: Awaited> | undefined; + + let local_files: FileDescription[] | undefined; let package_json; - let local_version = event.data.version; - const starts_with_pr = local_version.startsWith('pr-'); - const starts_with_commit = local_version.startsWith('commit-'); - if (starts_with_pr || starts_with_commit) { - const ref = starts_with_pr - ? local_version.substring('pr-'.length) - : local_version.substring('commit-'.length); - - const maybe_tar = await fetch(`https://pkg.pr.new/svelte@${ref}`); - if (maybe_tar.headers.get('content-type') === 'application/tar+gzip') { - const buffer = await maybe_tar.arrayBuffer(); + + const match = /^(?:pr|commit)-(.+)/.exec(event.data.version); + + if (match) { + const response = await fetch(`https://pkg.pr.new/svelte@${match[1]}`); + + if (response.ok) { + const buffer = await response.arrayBuffer(); local_files = await parseTar(buffer); const package_json_content = local_files.find( (file) => file.name === 'package/package.json' From b57f4e97ac2be42de92b6e6b23b252a9438a0356 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Mon, 4 Nov 2024 17:59:23 -0500 Subject: [PATCH 14/28] trim down a bit --- .../editor/src/lib/compile-worker/worker.ts | 35 ++++++++----------- 1 file changed, 15 insertions(+), 20 deletions(-) diff --git a/packages/editor/src/lib/compile-worker/worker.ts b/packages/editor/src/lib/compile-worker/worker.ts index 74c44ba53..05978ebd6 100644 --- a/packages/editor/src/lib/compile-worker/worker.ts +++ b/packages/editor/src/lib/compile-worker/worker.ts @@ -18,42 +18,37 @@ const ready = new Promise((f) => { addEventListener('message', async (event) => { if (!inited) { inited = true; - const svelte_url = `https://unpkg.com/svelte@${event.data.version}`; - - let local_files: FileDescription[] | undefined; - let package_json; + const svelte_url = `https://unpkg.com/svelte@${event.data.version}`; const match = /^(?:pr|commit)-(.+)/.exec(event.data.version); + let tarball: FileDescription[] | undefined; + let version; + if (match) { const response = await fetch(`https://pkg.pr.new/svelte@${match[1]}`); if (response.ok) { - const buffer = await response.arrayBuffer(); - local_files = await parseTar(buffer); - const package_json_content = local_files.find( - (file) => file.name === 'package/package.json' - )?.text; - if (package_json_content) { - package_json = JSON.parse(package_json_content); - } + 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 { version } = - package_json ?? (await fetch(`${svelte_url}/package.json`).then((r) => r.json())); - - const compiler_file = version.startsWith('3.') + const file = version.startsWith('3.') ? 'compiler.js' : version.startsWith('4.') ? 'compiler.cjs' : 'compiler/index.js'; - const compiler_text = local_files - ? local_files.find((file) => file.name === `package/${compiler_file}`)!.text - : await fetch(`${svelte_url}/${compiler_file}`).then((r) => r.text()); + const compiler = tarball + ? tarball.find((file) => file.name === `package/${file}`)!.text + : await fetch(`${svelte_url}/${file}`).then((r) => r.text()); - (0, eval)(compiler_text + `\n//# sourceURL=${compiler_file}@` + version); + (0, eval)(compiler + `\n//# sourceURL=${file}@` + version); fulfil_ready(); } From ad935b5af140f9bdaa08e1449fd93cb0f91379ea Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Mon, 4 Nov 2024 18:07:24 -0500 Subject: [PATCH 15/28] fix --- packages/editor/src/lib/compile-worker/worker.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/editor/src/lib/compile-worker/worker.ts b/packages/editor/src/lib/compile-worker/worker.ts index 05978ebd6..447a4f1d5 100644 --- a/packages/editor/src/lib/compile-worker/worker.ts +++ b/packages/editor/src/lib/compile-worker/worker.ts @@ -38,17 +38,17 @@ addEventListener('message', async (event) => { version = (await fetch(`${svelte_url}/package.json`).then((r) => r.json())).version; } - const file = version.startsWith('3.') + const entry = version.startsWith('3.') ? 'compiler.js' : version.startsWith('4.') ? 'compiler.cjs' : 'compiler/index.js'; const compiler = tarball - ? tarball.find((file) => file.name === `package/${file}`)!.text - : await fetch(`${svelte_url}/${file}`).then((r) => r.text()); + ? tarball.find((file) => file.name === `package/${entry}`)!.text + : await fetch(`${svelte_url}/${entry}`).then((r) => r.text()); - (0, eval)(compiler + `\n//# sourceURL=${file}@` + version); + (0, eval)(compiler + `\n//# sourceURL=${entry}@` + version); fulfil_ready(); } From 1f41f8abc8e26517dab8c8a45b4d515246b30d1e Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Mon, 4 Nov 2024 18:19:30 -0500 Subject: [PATCH 16/28] more robust error handling --- .../editor/src/lib/compile-worker/worker.ts | 79 ++++++++++--------- 1 file changed, 40 insertions(+), 39 deletions(-) diff --git a/packages/editor/src/lib/compile-worker/worker.ts b/packages/editor/src/lib/compile-worker/worker.ts index 447a4f1d5..366501a1a 100644 --- a/packages/editor/src/lib/compile-worker/worker.ts +++ b/packages/editor/src/lib/compile-worker/worker.ts @@ -9,51 +9,52 @@ 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; -addEventListener('message', async (event) => { - if (!inited) { - inited = true; +async function init(v: string) { + const svelte_url = `https://unpkg.com/svelte@${v}`; + const match = /^(?:pr|commit)-(.+)/.exec(v); - const svelte_url = `https://unpkg.com/svelte@${event.data.version}`; - const match = /^(?:pr|commit)-(.+)/.exec(event.data.version); + let tarball: FileDescription[] | undefined; + let version; - let tarball: FileDescription[] | undefined; - let version; + if (match) { + const response = await fetch(`https://pkg.pr.new/svelte@${match[1]}`); - if (match) { - const response = await fetch(`https://pkg.pr.new/svelte@${match[1]}`); + if (!response.ok) { + throw new Error('Could not fetch tarball'); + } - if (response.ok) { - tarball = await parseTar(await response.arrayBuffer()); + 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 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 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()); - 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); - (0, eval)(compiler + `\n//# sourceURL=${entry}@` + version); + return self.svelte; +} - fulfil_ready(); +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; @@ -61,7 +62,7 @@ addEventListener('message', async (event) => { 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, @@ -77,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? } @@ -89,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' @@ -102,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 From 91d6e7f993c932571a7f5328b0fb88a8683b8f5f Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Mon, 4 Nov 2024 18:27:38 -0500 Subject: [PATCH 17/28] separate onstatus from onerror --- packages/repl/src/lib/Bundler.ts | 15 +++++++++++---- packages/repl/src/lib/Repl.svelte | 11 +++++------ 2 files changed, 16 insertions(+), 10 deletions(-) diff --git a/packages/repl/src/lib/Bundler.ts b/packages/repl/src/lib/Bundler.ts index eba096a8a..6d6da1595 100644 --- a/packages/repl/src/lib/Bundler.ts +++ b/packages/repl/src/lib/Bundler.ts @@ -16,11 +16,13 @@ export default class Bundler { constructor({ packages_url, svelte_url, - onstatus + onstatus, + onerror }: { packages_url: string; svelte_url: string; - onstatus: (val: string | null, kind?: 'status' | 'error') => void; + onstatus: (val: string | null) => void; + onerror?: (message: string) => void; }) { this.hash = `${packages_url}:${svelte_url}`; @@ -39,8 +41,13 @@ export default class Bundler { if (handler) { // if no handler, was meant for a different REPL - if (event.data.type === 'status' || event.data.type === 'error') { - onstatus(event.data.message, event.data.type); + if (event.data.type === 'status') { + onstatus(event.data.message); + return; + } + + if (event.data.type === 'error') { + onerror?.(event.data.message); return; } diff --git a/packages/repl/src/lib/Repl.svelte b/packages/repl/src/lib/Repl.svelte index 3c0f1c1c9..f2bbece6f 100644 --- a/packages/repl/src/lib/Repl.svelte +++ b/packages/repl/src/lib/Repl.svelte @@ -124,7 +124,7 @@ ? new Bundler({ packages_url: packagesUrl, svelte_url: svelteUrl, - onstatus: (message, kind) => { + onstatus: (message) => { if (message) { // show bundler status, but only after time has elapsed, to // prevent the banner flickering @@ -138,11 +138,10 @@ status_visible = false; status_timeout = undefined; } - if (kind === 'status') { - status = message; - } else if (message) { - runtime_error = new Error(message); - } + status = message; + }, + onerror: (message) => { + runtime_error = new Error(message); } }) : null; From a7887356e967301b6f0d26947773a8d60493c4e0 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Mon, 4 Nov 2024 20:00:14 -0500 Subject: [PATCH 18/28] simplify with regex --- .../repl/src/lib/workers/bundler/index.ts | 40 +++++++++---------- 1 file changed, 18 insertions(+), 22 deletions(-) diff --git a/packages/repl/src/lib/workers/bundler/index.ts b/packages/repl/src/lib/workers/bundler/index.ts index c3e1af402..db794b8b6 100644 --- a/packages/repl/src/lib/workers/bundler/index.ts +++ b/packages/repl/src/lib/workers/bundler/index.ts @@ -16,7 +16,7 @@ import type { BundleMessageData } from '../workers'; import type { Warning } from '../../types'; import type { CompileError, CompileOptions, CompileResult } from 'svelte/compiler'; import type { File } from 'editor'; -import { parseTar } from 'tarparser'; +import { parseTar, type FileDescription } from 'tarparser'; // hack for magic-string and rollup inline sourcemaps // do not put this into a separate module and import it, would be treeshaken in prod @@ -41,38 +41,34 @@ self.addEventListener('message', async (event: MessageEvent) switch (event.data.type) { case 'init': { ({ packages_url, svelte_url } = event.data); + const match = /^(pr|commit)-(.+)/.exec(svelte_url); - const starts_with_pr = svelte_url.startsWith('pr-'); - const starts_with_commit = svelte_url.startsWith('commit-'); + if (match) { + let local_files: FileDescription[]; - if (starts_with_pr || starts_with_commit) { - let local_files: Awaited>; - - const ref = starts_with_pr - ? svelte_url.substring('pr-'.length) - : svelte_url.substring('commit-'.length); try { - const maybe_tar = await fetch(`https://pkg.pr.new/svelte@${ref}`); - if (!maybe_tar.ok) + const response = await fetch(`https://pkg.pr.new/svelte@${match[2]}`); + + if (!response.ok) { throw new Error( - `impossible to fetch the compiler from this ${starts_with_pr ? 'PR' : 'commit'}` + `impossible to fetch the compiler from this ${match[1] === 'pr' ? 'PR' : 'commit'}` ); - if (maybe_tar.headers.get('content-type') === 'application/tar+gzip') { - const buffer = await maybe_tar.arrayBuffer(); - local_files = await parseTar(buffer); - files = new Map( - local_files.map((file) => [file.name.substring('package'.length), () => file.text]) - ); - const package_json_content = files.get('/package.json')?.(); - if (package_json_content) { - package_json = JSON.parse(package_json_content); - } + } + + local_files = await parseTar(await response.arrayBuffer()); + files = new Map( + local_files.map((file) => [file.name.substring('package'.length), () => file.text]) + ); + const package_json_content = files.get('/package.json')?.(); + if (package_json_content) { + package_json = JSON.parse(package_json_content); } } catch (e) { reject_ready(e as Error); return; } } + ({ version } = package_json ?? (await fetch(`${svelte_url}/package.json`).then((r) => r.json()))); console.log(`Using Svelte compiler version ${version}`); From 25655c3019da0234646e92ac8be9b96dc8e432eb Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Mon, 4 Nov 2024 20:05:25 -0500 Subject: [PATCH 19/28] no need to thunkify --- packages/repl/src/lib/workers/bundler/index.ts | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/packages/repl/src/lib/workers/bundler/index.ts b/packages/repl/src/lib/workers/bundler/index.ts index db794b8b6..16553ed0f 100644 --- a/packages/repl/src/lib/workers/bundler/index.ts +++ b/packages/repl/src/lib/workers/bundler/index.ts @@ -34,7 +34,7 @@ const ready = new Promise((f, r) => { reject_ready = r; }); -let files: Map string>; +let files: Map; let package_json: any; self.addEventListener('message', async (event: MessageEvent) => { @@ -57,9 +57,9 @@ self.addEventListener('message', async (event: MessageEvent) local_files = await parseTar(await response.arrayBuffer()); files = new Map( - local_files.map((file) => [file.name.substring('package'.length), () => file.text]) + local_files.map((file) => [file.name.substring('package'.length), file.text]) ); - const package_json_content = files.get('/package.json')?.(); + const package_json_content = files.get('/package.json')!; if (package_json_content) { package_json = JSON.parse(package_json_content); } @@ -77,17 +77,17 @@ self.addEventListener('message', async (event: MessageEvent) // unpkg doesn't set the correct MIME type for .cjs files // https://github.com/mjackson/unpkg/issues/355 const compiler = - files?.get('/compiler.cjs')?.() ?? + files?.get('/compiler.cjs') ?? (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 = - files?.get('/compiler.js')?.() ?? + files?.get('/compiler.js') ?? (await fetch(`${svelte_url}/compiler.js`).then((r) => r.text())); (0, eval)(compiler + '\n//# sourceURL=compiler.js@' + version); } else { const compiler = - files?.get('/compiler/index.js')?.() ?? + files?.get('/compiler/index.js') ?? (await fetch(`${svelte_url}/compiler/index.js`).then((r) => r.text())); (0, eval)(compiler + '\n//# sourceURL=compiler/index.js@' + version); } @@ -402,7 +402,7 @@ async function get_bundle( const resolved_url = new URL(resolved); if (resolved_url.protocol === 'file:') { - return files.get(resolved_url.pathname)?.(); + return files.get(resolved_url.pathname); } } catch {} From 1585ffd73996ebe351007c234654b3270b73da49 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Mon, 4 Nov 2024 20:11:36 -0500 Subject: [PATCH 20/28] DRY out --- .../editor/src/lib/compile-worker/worker.ts | 2 +- .../repl/src/lib/workers/bundler/index.ts | 41 ++++++++----------- 2 files changed, 17 insertions(+), 26 deletions(-) diff --git a/packages/editor/src/lib/compile-worker/worker.ts b/packages/editor/src/lib/compile-worker/worker.ts index 366501a1a..dcd517572 100644 --- a/packages/editor/src/lib/compile-worker/worker.ts +++ b/packages/editor/src/lib/compile-worker/worker.ts @@ -16,7 +16,7 @@ async function init(v: string) { const match = /^(?:pr|commit)-(.+)/.exec(v); let tarball: FileDescription[] | undefined; - let version; + let version: string; if (match) { const response = await fetch(`https://pkg.pr.new/svelte@${match[1]}`); diff --git a/packages/repl/src/lib/workers/bundler/index.ts b/packages/repl/src/lib/workers/bundler/index.ts index 16553ed0f..b4972cd3b 100644 --- a/packages/repl/src/lib/workers/bundler/index.ts +++ b/packages/repl/src/lib/workers/bundler/index.ts @@ -43,9 +43,9 @@ self.addEventListener('message', async (event: MessageEvent) ({ packages_url, svelte_url } = event.data); const match = /^(pr|commit)-(.+)/.exec(svelte_url); - if (match) { - let local_files: FileDescription[]; + let tarball: FileDescription[] | undefined; + if (match) { try { const response = await fetch(`https://pkg.pr.new/svelte@${match[2]}`); @@ -55,14 +55,12 @@ self.addEventListener('message', async (event: MessageEvent) ); } - local_files = await parseTar(await response.arrayBuffer()); + tarball = await parseTar(await response.arrayBuffer()); files = new Map( - local_files.map((file) => [file.name.substring('package'.length), file.text]) + tarball.map((file) => [file.name.substring('package'.length), file.text]) ); const package_json_content = files.get('/package.json')!; - if (package_json_content) { - package_json = JSON.parse(package_json_content); - } + package_json = JSON.parse(package_json_content); } catch (e) { reject_ready(e as Error); return; @@ -73,24 +71,17 @@ self.addEventListener('message', async (event: MessageEvent) package_json ?? (await fetch(`${svelte_url}/package.json`).then((r) => r.json()))); console.log(`Using Svelte compiler version ${version}`); - if (version.startsWith('4.')) { - // unpkg doesn't set the correct MIME type for .cjs files - // https://github.com/mjackson/unpkg/issues/355 - const compiler = - files?.get('/compiler.cjs') ?? - (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 = - files?.get('/compiler.js') ?? - (await fetch(`${svelte_url}/compiler.js`).then((r) => r.text())); - (0, eval)(compiler + '\n//# sourceURL=compiler.js@' + version); - } else { - const compiler = - files?.get('/compiler/index.js') ?? - (await fetch(`${svelte_url}/compiler/index.js`).then((r) => r.text())); - (0, eval)(compiler + '\n//# sourceURL=compiler/index.js@' + 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); fulfil_ready(); break; From f08c88095a10dadf758a69eba1f2c0e7347f1a2a Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Mon, 4 Nov 2024 20:25:49 -0500 Subject: [PATCH 21/28] avoid overloading svelteUrl, just use the verison string directly --- apps/svelte.dev/src/config.js | 1 - .../tutorial/adapters/rollup/index.svelte.ts | 3 +-- .../(authed)/playground/[id]/+page.svelte | 10 ++------ .../playground/[id]/embed/+page.svelte | 23 +++++++++---------- packages/repl/src/lib/Bundler.ts | 8 +++---- packages/repl/src/lib/Repl.svelte | 11 ++++----- .../repl/src/lib/workers/bundler/index.ts | 7 ++++-- packages/repl/src/lib/workers/workers.d.ts | 2 +- 8 files changed, 28 insertions(+), 37 deletions(-) diff --git a/apps/svelte.dev/src/config.js b/apps/svelte.dev/src/config.js index bdf5a5dec..86d210d0c 100644 --- a/apps/svelte.dev/src/config.js +++ b/apps/svelte.dev/src/config.js @@ -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 }';`; diff --git a/apps/svelte.dev/src/lib/tutorial/adapters/rollup/index.svelte.ts b/apps/svelte.dev/src/lib/tutorial/adapters/rollup/index.svelte.ts index f9b868b3f..2907bbe4f 100644 --- a/apps/svelte.dev/src/lib/tutorial/adapters/rollup/index.svelte.ts +++ b/apps/svelte.dev/src/lib/tutorial/adapters/rollup/index.svelte.ts @@ -24,8 +24,7 @@ export async function create(): Promise { 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; diff --git a/apps/svelte.dev/src/routes/(authed)/playground/[id]/+page.svelte b/apps/svelte.dev/src/routes/(authed)/playground/[id]/+page.svelte index 82f88761e..d11f2522b 100644 --- a/apps/svelte.dev/src/routes/(authed)/playground/[id]/+page.svelte +++ b/apps/svelte.dev/src/routes/(authed)/playground/[id]/+page.svelte @@ -19,6 +19,7 @@ let modified = $state(false); 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-'); @@ -149,13 +150,6 @@ } } - const svelteUrl = - browser && version === 'local' - ? `${location.origin}/playground/local` - : is_pr_or_commit_version - ? version - : `https://unpkg.com/svelte@${version}`; - const relaxed = $derived(data.gist.relaxed || (data.user && data.user.id === data.gist.owner)); @@ -201,7 +195,7 @@
; - 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({ @@ -28,11 +32,6 @@ }); }); - 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)); @@ -48,7 +47,7 @@ {#if browser} 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); } diff --git a/packages/repl/src/lib/Repl.svelte b/packages/repl/src/lib/Repl.svelte index f2bbece6f..65619ad31 100644 --- a/packages/repl/src/lib/Repl.svelte +++ b/packages/repl/src/lib/Repl.svelte @@ -12,7 +12,7 @@ interface Props { packagesUrl?: string; - svelteUrl?: any; + svelteVersion?: string; embedded?: boolean; orientation?: 'columns' | 'rows'; relaxed?: boolean; @@ -27,7 +27,7 @@ let { packagesUrl = 'https://unpkg.com', - svelteUrl = `${BROWSER ? location.origin : ''}/svelte`, + svelteVersion = 'latest', embedded = false, orientation = 'columns', relaxed = false, @@ -51,10 +51,7 @@ const workspace = new Workspace([dummy], { initial: 'App.svelte', - svelte_version: - svelteUrl.startsWith('pr-') || svelteUrl.startsWith('commit-') - ? svelteUrl - : svelteUrl.split('@')[1], + svelte_version: svelteVersion, onupdate() { rebundle(); onchange?.(); @@ -123,7 +120,7 @@ const bundler = BROWSER ? new Bundler({ packages_url: packagesUrl, - svelte_url: svelteUrl, + svelte_version: svelteVersion, onstatus: (message) => { if (message) { // show bundler status, but only after time has elapsed, to diff --git a/packages/repl/src/lib/workers/bundler/index.ts b/packages/repl/src/lib/workers/bundler/index.ts index b4972cd3b..36e94cc91 100644 --- a/packages/repl/src/lib/workers/bundler/index.ts +++ b/packages/repl/src/lib/workers/bundler/index.ts @@ -40,8 +40,11 @@ let package_json: any; self.addEventListener('message', async (event: MessageEvent) => { switch (event.data.type) { case 'init': { - ({ packages_url, svelte_url } = event.data); - const match = /^(pr|commit)-(.+)/.exec(svelte_url); + const svelte_version = event.data.svelte_version; + packages_url = event.data.packages_url; + + svelte_url = `${packages_url}/svelte@${svelte_version}`; + const match = /^(pr|commit)-(.+)/.exec(svelte_version); let tarball: FileDescription[] | undefined; diff --git a/packages/repl/src/lib/workers/workers.d.ts b/packages/repl/src/lib/workers/workers.d.ts index 8fd301feb..1515b1097 100644 --- a/packages/repl/src/lib/workers/workers.d.ts +++ b/packages/repl/src/lib/workers/workers.d.ts @@ -53,7 +53,7 @@ export type BundleMessageData = { type: 'init' | 'bundle' | 'status' | 'error'; message: string; packages_url: string; - svelte_url: string; + svelte_version: string; files: File[]; options: CompileOptions; }; From 6f4a129784c4a76fafd90ffa67a2268ec44e6f22 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Mon, 4 Nov 2024 20:42:06 -0500 Subject: [PATCH 22/28] use Promise.withResolvers, simplify error handling --- .../repl/src/lib/workers/bundler/index.ts | 93 +++++++++---------- 1 file changed, 45 insertions(+), 48 deletions(-) diff --git a/packages/repl/src/lib/workers/bundler/index.ts b/packages/repl/src/lib/workers/bundler/index.ts index 36e94cc91..5e52449e8 100644 --- a/packages/repl/src/lib/workers/bundler/index.ts +++ b/packages/repl/src/lib/workers/bundler/index.ts @@ -27,72 +27,69 @@ let svelte_url: string; let version: string; let current_id: number; -let fulfil_ready: (arg?: never) => void; -let reject_ready: (arg?: Error) => void; -const ready = new Promise((f, r) => { - fulfil_ready = f; - reject_ready = r; -}); - let files: Map; let package_json: any; -self.addEventListener('message', async (event: MessageEvent) => { - switch (event.data.type) { - case 'init': { - const svelte_version = event.data.svelte_version; - packages_url = event.data.packages_url; +let inited = Promise.withResolvers(); - svelte_url = `${packages_url}/svelte@${svelte_version}`; - const match = /^(pr|commit)-(.+)/.exec(svelte_version); +async function init(v: string, packages_url: string) { + svelte_url = `${packages_url}/svelte@${v}`; + const match = /^(pr|commit)-(.+)/.exec(v); - let tarball: FileDescription[] | undefined; + let tarball: FileDescription[] | undefined; - if (match) { - try { - const response = await fetch(`https://pkg.pr.new/svelte@${match[2]}`); + if (match) { + const response = await fetch(`https://pkg.pr.new/svelte@${match[2]}`); - if (!response.ok) { - throw new Error( - `impossible to fetch the compiler from this ${match[1] === 'pr' ? 'PR' : 'commit'}` - ); - } + if (!response.ok) { + throw new Error( + `impossible to fetch the compiler from this ${match[1] === 'pr' ? 'PR' : 'commit'}` + ); + } - tarball = await parseTar(await response.arrayBuffer()); - files = new Map( - tarball.map((file) => [file.name.substring('package'.length), file.text]) - ); - const package_json_content = files.get('/package.json')!; - package_json = JSON.parse(package_json_content); - } catch (e) { - reject_ready(e as Error); - return; - } - } + tarball = await parseTar(await response.arrayBuffer()); + files = new Map(tarball.map((file) => [file.name.substring('package'.length), file.text])); + + const json = files.get('/package.json')!; + package_json = JSON.parse(json); + version = package_json.version; + } else { + version = (await fetch(`${svelte_url}/package.json`).then((r) => r.json())).version; + } + + console.log(`Using Svelte compiler version ${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); - ({ version } = - package_json ?? (await fetch(`${svelte_url}/package.json`).then((r) => r.json()))); - console.log(`Using Svelte compiler version ${version}`); + return svelte; +} - const entry = version.startsWith('3.') - ? 'compiler.js' - : version.startsWith('4.') - ? 'compiler.cjs' - : 'compiler/index.js'; +self.addEventListener('message', async (event: MessageEvent) => { + switch (event.data.type) { + case 'init': { + const svelte_version = event.data.svelte_version; - const compiler = tarball - ? tarball.find((file) => file.name === `package/${entry}`)!.text - : await fetch(`${svelte_url}/${entry}`).then((r) => r.text()); + packages_url = event.data.packages_url; + svelte_url = `${packages_url}/svelte@${svelte_version}`; - (0, eval)(compiler + `\n//# sourceURL=${entry}@` + version); + init(svelte_version, packages_url).then(inited.resolve, inited.reject); - fulfil_ready(); break; } case 'bundle': { try { - await ready; + await inited.promise; } catch (e) { self.postMessage({ type: 'error', From 0c509afa1d5bece76ef43b6ef0f6fb6e7f197e19 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Mon, 4 Nov 2024 20:59:02 -0500 Subject: [PATCH 23/28] 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 --- .../repl/src/lib/workers/bundler/index.ts | 20 ------------------- 1 file changed, 20 deletions(-) diff --git a/packages/repl/src/lib/workers/bundler/index.ts b/packages/repl/src/lib/workers/bundler/index.ts index 5e52449e8..9d101bf69 100644 --- a/packages/repl/src/lib/workers/bundler/index.ts +++ b/packages/repl/src/lib/workers/bundler/index.ts @@ -275,26 +275,6 @@ async function get_bundle( if (importee === 'esm-env') return importee; - const v5 = is_v5(); - const v4 = !v5 && is_v4(); - - if (!v5) { - // importing from Svelte - if (importee === `svelte`) - return v4 ? `${svelte_url}/src/runtime/index.js` : `${svelte_url}/index.mjs`; - - if (importee.startsWith(`svelte/`)) { - const sub_path = importee.slice(7); - if (v4) { - return `${svelte_url}/src/runtime/${sub_path}/index.js`; - } - - return is_legacy_package_structure() - ? `${svelte_url}/${sub_path}.mjs` - : `${svelte_url}/${sub_path}/index.mjs`; - } - } - if (importee === shared_file) return importee; // importing from another file in REPL From 00a01f9b04430b1fe8bdc16a3e49eaca790225cb Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Mon, 4 Nov 2024 21:19:30 -0500 Subject: [PATCH 24/28] use existing FETCH_CACHE --- .../repl/src/lib/workers/bundler/index.ts | 54 +++++++------------ 1 file changed, 19 insertions(+), 35 deletions(-) diff --git a/packages/repl/src/lib/workers/bundler/index.ts b/packages/repl/src/lib/workers/bundler/index.ts index 9d101bf69..57ce12602 100644 --- a/packages/repl/src/lib/workers/bundler/index.ts +++ b/packages/repl/src/lib/workers/bundler/index.ts @@ -27,13 +27,9 @@ let svelte_url: string; let version: string; let current_id: number; -let files: Map; -let package_json: any; - let inited = Promise.withResolvers(); async function init(v: string, packages_url: string) { - svelte_url = `${packages_url}/svelte@${v}`; const match = /^(pr|commit)-(.+)/.exec(v); let tarball: FileDescription[] | undefined; @@ -48,13 +44,19 @@ async function init(v: string, packages_url: string) { } tarball = await parseTar(await response.arrayBuffer()); - files = new Map(tarball.map((file) => [file.name.substring('package'.length), file.text])); - const json = files.get('/package.json')!; - package_json = JSON.parse(json); - version = package_json.version; + const json = tarball.find((file) => file.name === 'package/package.json')!.text; + version = JSON.parse(json).version; + + svelte_url = `svelte://svelte@${version}`; + + for (const file of tarball) { + const url = `${svelte_url}/${file.name.slice('package/'.length)}`; + FETCH_CACHE.set(url, Promise.resolve({ url, body: file.text })); + } } else { version = (await fetch(`${svelte_url}/package.json`).then((r) => r.json())).version; + svelte_url = `${packages_url}/svelte@${version}`; } console.log(`Using Svelte compiler version ${version}`); @@ -297,13 +299,9 @@ async function get_bundle( return; } else { // relative import in an external file - const url = new URL(importee, importer); - if (url.protocol === 'file:') { - return url.toString(); - } - + const url = new URL(importee, importer).href; self.postMessage({ type: 'status', uid, message: `resolving ${url}` }); - return await follow_redirects(url.href, uid); + return await follow_redirects(url, uid); } } else { // fetch from unpkg @@ -315,7 +313,10 @@ async function get_bundle( } const pkg_name = match[1]; - const version = pkg_name === 'svelte' ? svelte.VERSION : match[2] ?? 'latest'; + const pkg_url = + pkg_name === 'svelte' + ? `${svelte_url}/package.json` + : `${packages_url}/${pkg_name}/package.json`; const subpath = `.${match[3] ?? ''}`; // if this was imported by one of our files, add it to the `imports` set @@ -323,12 +324,9 @@ async function get_bundle( imports.add(pkg_name); } - const fetch_package_info = async () => { + const fetch_package_info = async (pkg_url) => { try { - const pkg_url = await follow_redirects( - `${packages_url}/${pkg_name}@${version}/package.json`, - uid - ); + pkg_url = await follow_redirects(pkg_url, uid); if (!pkg_url) throw new Error(); @@ -346,13 +344,7 @@ async function get_bundle( } }; - let pkg: any; - let pkg_url_base = 'file:'; - if (importee.startsWith(`svelte`) && package_json) { - pkg = package_json; - } else { - ({ pkg, pkg_url_base } = await fetch_package_info()); - } + const { pkg, pkg_url_base } = await fetch_package_info(pkg_url); try { const resolved_id = await resolve_from_pkg(pkg, subpath, uid, pkg_url_base); @@ -369,14 +361,6 @@ async function get_bundle( return `export const BROWSER = true; export const DEV = true`; } - try { - const resolved_url = new URL(resolved); - - if (resolved_url.protocol === 'file:') { - return files.get(resolved_url.pathname); - } - } catch {} - const cached_file = local_files_lookup.get(resolved); if (cached_file) { return cached_file.contents; From d981974ae57e1ba1bb4e110c6cd994ae51552842 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Mon, 4 Nov 2024 21:33:56 -0500 Subject: [PATCH 25/28] tidy up --- packages/repl/src/lib/workers/bundler/index.ts | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/packages/repl/src/lib/workers/bundler/index.ts b/packages/repl/src/lib/workers/bundler/index.ts index 57ce12602..cdb6da9c2 100644 --- a/packages/repl/src/lib/workers/bundler/index.ts +++ b/packages/repl/src/lib/workers/bundler/index.ts @@ -55,7 +55,9 @@ async function init(v: string, packages_url: string) { FETCH_CACHE.set(url, Promise.resolve({ url, body: file.text })); } } else { - version = (await fetch(`${svelte_url}/package.json`).then((r) => r.json())).version; + const response = await fetch(`${packages_url}/svelte@${v}/package.json`); + const pkg = await response.json(); + version = pkg.version; svelte_url = `${packages_url}/svelte@${version}`; } @@ -79,13 +81,8 @@ async function init(v: string, packages_url: string) { self.addEventListener('message', async (event: MessageEvent) => { switch (event.data.type) { case 'init': { - const svelte_version = event.data.svelte_version; - packages_url = event.data.packages_url; - svelte_url = `${packages_url}/svelte@${svelte_version}`; - - init(svelte_version, packages_url).then(inited.resolve, inited.reject); - + init(event.data.svelte_version, packages_url).then(inited.resolve, inited.reject); break; } From 64937706dc99bcf30d7fd3eeb225198443927ca9 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Mon, 4 Nov 2024 21:35:15 -0500 Subject: [PATCH 26/28] remove some unused code --- .../repl/src/lib/workers/bundler/index.ts | 23 ------------------- 1 file changed, 23 deletions(-) diff --git a/packages/repl/src/lib/workers/bundler/index.ts b/packages/repl/src/lib/workers/bundler/index.ts index cdb6da9c2..d65a87b56 100644 --- a/packages/repl/src/lib/workers/bundler/index.ts +++ b/packages/repl/src/lib/workers/bundler/index.ts @@ -160,29 +160,6 @@ async function follow_redirects(url: string, uid: number) { return res?.url; } -function compare_to_version(major: number, minor: number, patch: number): number { - const v = svelte.VERSION.match(/^(\d+)\.(\d+)\.(\d+)/); - - // @ts-ignore - return +v[1] - major || +v[2] - minor || +v[3] - patch; -} - -function is_v4() { - return compare_to_version(4, 0, 0) >= 0; -} - -function is_v5() { - return compare_to_version(5, 0, 0) >= 0; -} - -function is_legacy_package_structure() { - return compare_to_version(3, 4, 4) <= 0; -} - -function has_loopGuardTimeout_feature() { - return compare_to_version(3, 14, 0) >= 0; -} - async function resolve_from_pkg( pkg: Record, subpath: string, From 01a6a6ecd9b670bc99e7075c909f7068c36f7e67 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Mon, 4 Nov 2024 21:50:56 -0500 Subject: [PATCH 27/28] lint --- packages/repl/src/lib/workers/bundler/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/repl/src/lib/workers/bundler/index.ts b/packages/repl/src/lib/workers/bundler/index.ts index d65a87b56..07607eddc 100644 --- a/packages/repl/src/lib/workers/bundler/index.ts +++ b/packages/repl/src/lib/workers/bundler/index.ts @@ -298,7 +298,7 @@ async function get_bundle( imports.add(pkg_name); } - const fetch_package_info = async (pkg_url) => { + const fetch_package_info = async (pkg_url: string) => { try { pkg_url = await follow_redirects(pkg_url, uid); From 505dc045d0188e958cd37b37228b79533af1d4bc Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Mon, 4 Nov 2024 22:18:21 -0500 Subject: [PATCH 28/28] ugh --- packages/repl/src/lib/workers/bundler/index.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/repl/src/lib/workers/bundler/index.ts b/packages/repl/src/lib/workers/bundler/index.ts index 07607eddc..c241fa922 100644 --- a/packages/repl/src/lib/workers/bundler/index.ts +++ b/packages/repl/src/lib/workers/bundler/index.ts @@ -300,14 +300,14 @@ async function get_bundle( const fetch_package_info = async (pkg_url: string) => { try { - pkg_url = await follow_redirects(pkg_url, uid); + const redirected = await follow_redirects(pkg_url, uid); - if (!pkg_url) throw new Error(); + if (!redirected) throw new Error(); - const pkg_json = (await fetch_if_uncached(pkg_url, uid))?.body; + const pkg_json = (await fetch_if_uncached(redirected, uid))?.body; const pkg = JSON.parse(pkg_json ?? '""'); - const pkg_url_base = pkg_url.replace(/\/package\.json$/, ''); + const pkg_url_base = redirected.replace(/\/package\.json$/, ''); return { pkg,