diff --git a/.changeset/rude-cameras-compare.md b/.changeset/rude-cameras-compare.md new file mode 100644 index 000000000000..a08068ff3be0 --- /dev/null +++ b/.changeset/rude-cameras-compare.md @@ -0,0 +1,5 @@ +--- +'@sveltejs/kit': minor +--- + +feat: use relative `base` path in client diff --git a/packages/adapter-static/test/test.js b/packages/adapter-static/test/test.js index 0fe826aceefc..c4144c1bd532 100644 --- a/packages/adapter-static/test/test.js +++ b/packages/adapter-static/test/test.js @@ -1,4 +1,4 @@ -import fs from 'fs'; +import fs from 'node:fs'; import * as assert from 'uvu/assert'; import { run } from './utils.js'; diff --git a/packages/kit/src/exports/vite/index.js b/packages/kit/src/exports/vite/index.js index 5517deb6ea44..1dcb06e9f981 100644 --- a/packages/kit/src/exports/vite/index.js +++ b/packages/kit/src/exports/vite/index.js @@ -374,9 +374,12 @@ function kit({ svelte_config }) { case '\0__sveltekit/paths': const { assets, base } = svelte_config.kit.paths; + // use the values defined in `global`, but fall back to hard-coded values + // for the sake of things like Vitest which may import this module + // outside the context of a page if (browser) { - return `export const base = ${s(base)}; -export const assets = ${global}.assets;`; + return `export const base = ${global}?.base ?? ${s(base)}; +export const assets = ${global}?.assets ?? ${assets ? s(assets) : 'base'};`; } return `export const base = ${s(base)}; diff --git a/packages/kit/src/internal.d.ts b/packages/kit/src/internal.d.ts index cfe9bf548544..a29fe366a20d 100644 --- a/packages/kit/src/internal.d.ts +++ b/packages/kit/src/internal.d.ts @@ -7,7 +7,7 @@ declare module '__sveltekit/environment' { /** Internal version of $app/paths */ declare module '__sveltekit/paths' { - export const base: `/${string}`; + export const base: string; export let assets: `https://${string}` | `http://${string}`; export function set_assets(path: string): void; } diff --git a/packages/kit/src/runtime/server/page/render.js b/packages/kit/src/runtime/server/page/render.js index 92da25ad1916..29edd2a5c745 100644 --- a/packages/kit/src/runtime/server/page/render.js +++ b/packages/kit/src/runtime/server/page/render.js @@ -1,7 +1,7 @@ import * as devalue from 'devalue'; import { readable, writable } from 'svelte/store'; import { DEV } from 'esm-env'; -import { assets, base } from '__sveltekit/paths'; +import * as paths from '__sveltekit/paths'; import { hash } from '../../hash.js'; import { serialize_data } from './serialize_data.js'; import { s } from '../../../utils/misc.js'; @@ -156,34 +156,31 @@ export async function render_response({ rendered = { head: '', html: '', css: { code: '', map: null } }; } + const segments = event.url.pathname.slice(paths.base.length).split('/').slice(2); + const base = segments.map(() => '..').join('/') || '.'; + /** - * The prefix to use for static assets. Replaces `%sveltekit.assets%` in the template - * @type {string} + * An expression that will evaluate in the client to determine the resolved base path. + * We use a relative path when possible to support IPFS, the internet archive, etc. */ - let resolved_assets; + const base_expression = + state.prerendering?.fallback || paths.base !== '' + ? s(paths.base) + : `new URL(${s(base)}, location.href).pathname.slice(0,-1)`; /** - * An expression that will evaluate in the client to determine the resolved asset path + * An expression that will evaluate in the client to determine the resolved asset path. + * If `undefined`, falls back to `base` */ - let asset_expression; - - if (assets) { - // if an asset path is specified, use it - resolved_assets = assets; - asset_expression = s(assets); - } else if (state.prerendering?.fallback) { - // if we're creating a fallback page, asset paths need to be root-relative - resolved_assets = base; - asset_expression = s(base); - } else { - // otherwise we want asset paths to be relative to the page, so that they - // will work in odd contexts like IPFS, the internet archive, and so on - const segments = event.url.pathname.slice(base.length).split('/').slice(2); - resolved_assets = segments.length > 0 ? segments.map(() => '..').join('/') : '.'; - asset_expression = `new URL(${s( - resolved_assets - )}, location.href).pathname.replace(/^\\\/$/, '')`; - } + const asset_expression = paths.assets ? s(paths.assets) : undefined; + + /** + * The prefix to use for static assets. Replaces `%sveltekit.assets%` in the template. + * If an asset path is specified, use it. If we're creating a fallback page, asset paths + * need to be root-relative. Otherwise, use the base path relative to the current page. + * @type {string} + */ + const assets = paths.assets || (state.prerendering?.fallback ? paths.base : base); let head = ''; let body = rendered.html; @@ -198,9 +195,9 @@ export async function render_response({ // Vite makes the start script available through the base path and without it. // We load it via the base path in order to support remote IDE environments which proxy // all URLs under the base path during development. - return base + path; + return paths.base + path; } - return `${resolved_assets}/${path}`; + return `${assets}/${path}`; }; if (inline_styles.size > 0) { @@ -285,9 +282,10 @@ export async function render_response({ const properties = [ `env: ${s(public_env)}`, - `assets: ${asset_expression}`, + asset_expression && `assets: ${asset_expression}`, + `base: ${base_expression}`, `element: document.currentScript.parentElement` - ]; + ].filter(Boolean); if (chunks) { blocks.push(`const deferred = new Map();`); @@ -418,7 +416,7 @@ export async function render_response({ const html = options.templates.app({ head, body, - assets: resolved_assets, + assets, nonce: /** @type {string} */ (csp.nonce), env: public_env });