diff --git a/packages/repl/src/lib/workers/bundler/index.ts b/packages/repl/src/lib/workers/bundler/index.ts index 119725d4f4..38e7c8ac59 100644 --- a/packages/repl/src/lib/workers/bundler/index.ts +++ b/packages/repl/src/lib/workers/bundler/index.ts @@ -18,6 +18,7 @@ import type { Warning } from '../../types'; import type { CompileError, CompileOptions, CompileResult } from 'svelte/compiler'; import type { File } from 'editor'; import { parseTar, type FileDescription } from 'tarparser'; +import { max } from './semver'; // hack for magic-string and rollup inline sourcemaps // do not put this into a separate module and import it, would be treeshaken in prod @@ -230,6 +231,8 @@ async function resolve_from_pkg( return subpath; } +const versions = Object.create(null); + async function get_bundle( uid: number, mode: 'client' | 'server', @@ -288,10 +291,46 @@ async function get_bundle( } const pkg_name = match[1]; + + let default_version = 'latest'; + + if (importer?.startsWith(packages_url)) { + const path = importer.slice(packages_url.length + 1); + const parts = path.split('/').slice(0, 2); + if (!parts[0].startsWith('@')) parts.pop(); + + const importer_name_and_version = parts.join('/'); + const importer_name = importer_name_and_version.slice( + 0, + importer_name_and_version.indexOf('@', 1) + ); + + const default_versions = (versions[importer_name_and_version] ??= Object.create(null)); + + if (!default_versions[pkg_name]) { + const pkg_json_url = `${packages_url}/${importer_name_and_version}/package.json`; + const pkg_json = (await fetch_if_uncached(pkg_json_url, uid))?.body; + const pkg = JSON.parse(pkg_json ?? '""'); + + if (importer_name === pkg_name) { + default_versions[pkg_name] = pkg.version; + } else { + const version = + pkg.devDependencies?.[pkg_name] ?? + pkg.peerDependencies?.[pkg_name] ?? + pkg.dependencies?.[pkg_name]; + + default_versions[pkg_name] = max(version); + } + } + + default_version = default_versions[pkg_name]; + } + const pkg_url = pkg_name === 'svelte' ? `${svelte_url}/package.json` - : `${packages_url}/${pkg_name}@${match[2] ?? 'latest'}/package.json`; + : `${packages_url}/${pkg_name}@${match[2] ?? default_version}/package.json`; const subpath = `.${match[3] ?? ''}`; // if this was imported by one of our files, add it to the `imports` set diff --git a/packages/repl/src/lib/workers/bundler/semver.ts b/packages/repl/src/lib/workers/bundler/semver.ts new file mode 100644 index 0000000000..945b56541e --- /dev/null +++ b/packages/repl/src/lib/workers/bundler/semver.ts @@ -0,0 +1,43 @@ +// https://devhints.io/semver +export function max(version: string) { + if (!version || version === '*' || version === 'x') { + return 'latest'; + } + + // strip any * parts, e.g. 1.2.x becomes 1.2 + version = version.replace(/\.[x*].+/, ''); + + const match = /^([~^])?(\d+|[*x])(?:\.(\d+|[*x])(?:\.(\d+|[*x]))?)?(?:-.+)?$/.exec(version); + + if (!match) { + // bail + console.warn(`Could not resolve version from ${version}`); + return 'latest'; + } + + const [_, qualifier, major, minor] = match; + + // ^ means 'same major', unless 0.x + if (qualifier === '^') { + if (major === '0') { + if (minor === '0') { + return version.slice(1); + } + + return `${major}.${minor}`; + } + + return major; + } + + // ~ means 'same minor' + if (qualifier === '~') { + if (minor !== undefined) { + return `${major}.${minor}`; + } + + return major; + } + + return version; +}