From f0834b99b4034af01f025327255fdb9f40f87498 Mon Sep 17 00:00:00 2001 From: wighawag Date: Tue, 21 Feb 2023 14:00:21 +0000 Subject: [PATCH 01/23] dynamic base --- packages/kit/src/exports/vite/index.js | 2 +- packages/kit/src/internal.d.ts | 2 +- packages/kit/src/runtime/server/page/render.js | 14 ++++++++++++-- 3 files changed, 14 insertions(+), 4 deletions(-) diff --git a/packages/kit/src/exports/vite/index.js b/packages/kit/src/exports/vite/index.js index 5517deb6ea44..eb2d39afdf5a 100644 --- a/packages/kit/src/exports/vite/index.js +++ b/packages/kit/src/exports/vite/index.js @@ -375,7 +375,7 @@ function kit({ svelte_config }) { const { assets, base } = svelte_config.kit.paths; if (browser) { - return `export const base = ${s(base)}; + return `export const base = ${global}.base; export const assets = ${global}.assets;`; } 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..681622ceb5f7 100644 --- a/packages/kit/src/runtime/server/page/render.js +++ b/packages/kit/src/runtime/server/page/render.js @@ -156,6 +156,16 @@ export async function render_response({ rendered = { head: '', html: '', css: { code: '', map: null } }; } + const segments = event.url.pathname.slice(base.length).split('/').slice(2); + const resolved_base = segments.length > 0 ? segments.map(() => '..').join('/') : '.'; + + let base_expression = s(base); + if (base === '') { + // if base is relative we use resolved_base so that they + // will work in odd contexts like IPFS, the internet archive, and so on + base_expression = `new URL(${s(resolved_base)}, location.href).pathname.slice(0,-1)`; + } + /** * The prefix to use for static assets. Replaces `%sveltekit.assets%` in the template * @type {string} @@ -178,8 +188,7 @@ export async function render_response({ } 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('/') : '.'; + resolved_assets = resolved_base; asset_expression = `new URL(${s( resolved_assets )}, location.href).pathname.replace(/^\\\/$/, '')`; @@ -286,6 +295,7 @@ export async function render_response({ const properties = [ `env: ${s(public_env)}`, `assets: ${asset_expression}`, + `base: ${base_expression}`, `element: document.currentScript.parentElement` ]; From 160084a718cf5797c9f2a7aee4867985c5dc9c1c Mon Sep 17 00:00:00 2001 From: Ben McCann <322311+benmccann@users.noreply.github.com> Date: Wed, 22 Feb 2023 09:32:24 -0800 Subject: [PATCH 02/23] fix failing test --- packages/adapter-static/test/test.js | 2 +- packages/kit/src/runtime/server/page/render.js | 16 +++++++--------- 2 files changed, 8 insertions(+), 10 deletions(-) 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/runtime/server/page/render.js b/packages/kit/src/runtime/server/page/render.js index 681622ceb5f7..31d2208bfbb1 100644 --- a/packages/kit/src/runtime/server/page/render.js +++ b/packages/kit/src/runtime/server/page/render.js @@ -157,14 +157,13 @@ export async function render_response({ } const segments = event.url.pathname.slice(base.length).split('/').slice(2); - const resolved_base = segments.length > 0 ? segments.map(() => '..').join('/') : '.'; + const resolved_base = segments.map(() => '..').join('/') || '.'; - let base_expression = s(base); - if (base === '') { - // if base is relative we use resolved_base so that they - // will work in odd contexts like IPFS, the internet archive, and so on - base_expression = `new URL(${s(resolved_base)}, location.href).pathname.slice(0,-1)`; - } + // we use a relative path when possible to support IPFS, the internet archive, etc. + const base_expression = + state.prerendering?.fallback || base !== '' + ? s(base) + : `new URL(${s(resolved_base)}, location.href).pathname.slice(0,-1)`; /** * The prefix to use for static assets. Replaces `%sveltekit.assets%` in the template @@ -186,9 +185,8 @@ export async function render_response({ 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 resolved_assets = resolved_base; + // we use a relative path when possible to support IPFS, the internet archive, etc. asset_expression = `new URL(${s( resolved_assets )}, location.href).pathname.replace(/^\\\/$/, '')`; From 12c2787b5abce2e0426ff0f1b114b9be8507bd2b Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Sun, 26 Feb 2023 00:14:57 -0500 Subject: [PATCH 03/23] reuse base expression if possible --- packages/kit/src/exports/vite/index.js | 2 +- .../kit/src/runtime/server/page/render.js | 26 +++++++++---------- 2 files changed, 13 insertions(+), 15 deletions(-) diff --git a/packages/kit/src/exports/vite/index.js b/packages/kit/src/exports/vite/index.js index eb2d39afdf5a..47cb3fdf87a5 100644 --- a/packages/kit/src/exports/vite/index.js +++ b/packages/kit/src/exports/vite/index.js @@ -376,7 +376,7 @@ function kit({ svelte_config }) { if (browser) { return `export const base = ${global}.base; -export const assets = ${global}.assets;`; +export const assets = ${global}.assets ?? base;`; } return `export const base = ${s(base)}; diff --git a/packages/kit/src/runtime/server/page/render.js b/packages/kit/src/runtime/server/page/render.js index 31d2208bfbb1..e4d3ed56bac3 100644 --- a/packages/kit/src/runtime/server/page/render.js +++ b/packages/kit/src/runtime/server/page/render.js @@ -159,37 +159,35 @@ export async function render_response({ const segments = event.url.pathname.slice(base.length).split('/').slice(2); const resolved_base = segments.map(() => '..').join('/') || '.'; - // we use a relative path when possible to support IPFS, the internet archive, etc. + /** + * 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. + */ const base_expression = state.prerendering?.fallback || base !== '' ? s(base) : `new URL(${s(resolved_base)}, location.href).pathname.slice(0,-1)`; /** - * 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 asset path. + * If `undefined`, falls back to `base` */ - let resolved_assets; + const asset_expression = assets ? s(assets) : undefined; /** - * An expression that will evaluate in the client to determine the resolved asset path + * The prefix to use for static assets. Replaces `%sveltekit.assets%` in the template + * @type {string} */ - let asset_expression; + let resolved_assets; 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 { resolved_assets = resolved_base; - // we use a relative path when possible to support IPFS, the internet archive, etc. - asset_expression = `new URL(${s( - resolved_assets - )}, location.href).pathname.replace(/^\\\/$/, '')`; } let head = ''; @@ -292,10 +290,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();`); From 26f919acaa5d36fa3472a17a76692cbc0c5a1801 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Sun, 26 Feb 2023 00:20:22 -0500 Subject: [PATCH 04/23] minor tweaks --- .../kit/src/runtime/server/page/render.js | 36 ++++++++----------- 1 file changed, 14 insertions(+), 22 deletions(-) diff --git a/packages/kit/src/runtime/server/page/render.js b/packages/kit/src/runtime/server/page/render.js index e4d3ed56bac3..a72b6458f893 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,39 +156,31 @@ export async function render_response({ rendered = { head: '', html: '', css: { code: '', map: null } }; } - const segments = event.url.pathname.slice(base.length).split('/').slice(2); - const resolved_base = segments.map(() => '..').join('/') || '.'; + const segments = event.url.pathname.slice(paths.base.length).split('/').slice(2); + const base = segments.map(() => '..').join('/') || '.'; /** * 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. */ const base_expression = - state.prerendering?.fallback || base !== '' - ? s(base) - : `new URL(${s(resolved_base)}, location.href).pathname.slice(0,-1)`; + 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. * If `undefined`, falls back to `base` */ - const asset_expression = assets ? s(assets) : undefined; + const asset_expression = paths.assets ? s(paths.assets) : undefined; /** - * The prefix to use for static assets. Replaces `%sveltekit.assets%` in the template + * 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} */ - let resolved_assets; - - if (assets) { - // if an asset path is specified, use it - resolved_assets = assets; - } else if (state.prerendering?.fallback) { - // if we're creating a fallback page, asset paths need to be root-relative - resolved_assets = base; - } else { - resolved_assets = resolved_base; - } + const assets = paths.assets || state.prerendering?.fallback ? paths.base : base; let head = ''; let body = rendered.html; @@ -203,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) { @@ -424,7 +416,7 @@ export async function render_response({ const html = options.templates.app({ head, body, - assets: resolved_assets, + assets: assets, nonce: /** @type {string} */ (csp.nonce), env: public_env }); From be5a74f5b8dd4323187203bee6a238422c267887 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Sun, 26 Feb 2023 00:36:03 -0500 Subject: [PATCH 05/23] shorthand --- packages/kit/src/runtime/server/page/render.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/kit/src/runtime/server/page/render.js b/packages/kit/src/runtime/server/page/render.js index a72b6458f893..761701458791 100644 --- a/packages/kit/src/runtime/server/page/render.js +++ b/packages/kit/src/runtime/server/page/render.js @@ -416,7 +416,7 @@ export async function render_response({ const html = options.templates.app({ head, body, - assets: assets, + assets, nonce: /** @type {string} */ (csp.nonce), env: public_env }); From dff5768af3e545472fee1d00c045000914d28bd0 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Sun, 26 Feb 2023 00:39:19 -0500 Subject: [PATCH 06/23] fix --- packages/kit/src/runtime/server/page/render.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/kit/src/runtime/server/page/render.js b/packages/kit/src/runtime/server/page/render.js index 761701458791..29edd2a5c745 100644 --- a/packages/kit/src/runtime/server/page/render.js +++ b/packages/kit/src/runtime/server/page/render.js @@ -180,7 +180,7 @@ export async function render_response({ * 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; + const assets = paths.assets || (state.prerendering?.fallback ? paths.base : base); let head = ''; let body = rendered.html; From 6250f2b684d6337d04ff089aea61e3ec32853e15 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Sun, 26 Feb 2023 00:45:03 -0500 Subject: [PATCH 07/23] hardcode fallbacks --- packages/kit/src/exports/vite/index.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/packages/kit/src/exports/vite/index.js b/packages/kit/src/exports/vite/index.js index 47cb3fdf87a5..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 = ${global}.base; -export const assets = ${global}.assets ?? base;`; + return `export const base = ${global}?.base ?? ${s(base)}; +export const assets = ${global}?.assets ?? ${assets ? s(assets) : 'base'};`; } return `export const base = ${s(base)}; From 97d57281deaa874aa0d6808f50d9015f5067c105 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Sun, 26 Feb 2023 00:48:27 -0500 Subject: [PATCH 08/23] changeset --- .changeset/rude-cameras-compare.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/rude-cameras-compare.md 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 From 6aaf6e6d267533a7b3688f0f072faecc347b7d77 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Sun, 26 Feb 2023 01:04:39 -0500 Subject: [PATCH 09/23] use dynamic base path when server rendering --- .changeset/few-lions-drive.md | 5 +++++ packages/kit/src/exports/vite/index.js | 7 ++++++- packages/kit/src/internal.d.ts | 1 + packages/kit/src/runtime/server/page/render.js | 16 ++++++++++++---- 4 files changed, 24 insertions(+), 5 deletions(-) create mode 100644 .changeset/few-lions-drive.md diff --git a/.changeset/few-lions-drive.md b/.changeset/few-lions-drive.md new file mode 100644 index 000000000000..ca30f97d3426 --- /dev/null +++ b/.changeset/few-lions-drive.md @@ -0,0 +1,5 @@ +--- +'@sveltejs/kit': minor +--- + +feat: use relative `base` path during server-rendering diff --git a/packages/kit/src/exports/vite/index.js b/packages/kit/src/exports/vite/index.js index 1dcb06e9f981..517106522401 100644 --- a/packages/kit/src/exports/vite/index.js +++ b/packages/kit/src/exports/vite/index.js @@ -382,9 +382,14 @@ function kit({ svelte_config }) { export const assets = ${global}?.assets ?? ${assets ? s(assets) : 'base'};`; } - return `export const base = ${s(base)}; + return `export let base = ${s(base)}; export let assets = ${assets ? s(assets) : 'base'}; +/** @param {string} path */ +export function set_base(path) { + base = path; +} + /** @param {string} path */ export function set_assets(path) { assets = path; diff --git a/packages/kit/src/internal.d.ts b/packages/kit/src/internal.d.ts index a29fe366a20d..87d4bbb33e74 100644 --- a/packages/kit/src/internal.d.ts +++ b/packages/kit/src/internal.d.ts @@ -9,5 +9,6 @@ declare module '__sveltekit/environment' { declare module '__sveltekit/paths' { export const base: string; export let assets: `https://${string}` | `http://${string}`; + export function set_base(path: string): void; 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 29edd2a5c745..6c1ad4fe2b04 100644 --- a/packages/kit/src/runtime/server/page/render.js +++ b/packages/kit/src/runtime/server/page/render.js @@ -80,6 +80,9 @@ export async function render_response({ ? action_result.data ?? null : null; + const segments = event.url.pathname.slice(paths.base.length).split('/').slice(2); + const base = segments.map(() => '..').join('/') || '.'; + if (page_config.ssr) { if (__SVELTEKIT_DEV__ && !branch.at(-1)?.node.component) { // Can only be the leaf, layouts have a fallback component generated @@ -116,6 +119,9 @@ export async function render_response({ form: form_value }; + const original_base = paths.base; + paths.set_base(base); + if (__SVELTEKIT_DEV__) { const fetch = globalThis.fetch; let warned = false; @@ -138,9 +144,14 @@ export async function render_response({ rendered = options.root.render(props); } finally { globalThis.fetch = fetch; + paths.set_base(original_base); } } else { - rendered = options.root.render(props); + try { + rendered = options.root.render(props); + } finally { + paths.set_base(original_base); + } } for (const { node } of branch) { @@ -156,9 +167,6 @@ 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('/') || '.'; - /** * 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. From e53011ed40605138ad73a7b6647f52c14f1f2ee9 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Sun, 26 Feb 2023 19:48:07 -0500 Subject: [PATCH 10/23] use relative assets --- .../kit/src/runtime/server/page/render.js | 22 ++++++++++++------- .../routes/paths/deeply/nested/+page.svelte | 5 +++++ packages/kit/test/apps/basics/test/test.js | 15 ++++++++++--- .../options/source/pages/slash/+page.svelte | 6 +++-- .../source/pages/slash/child/+page.svelte | 2 +- packages/kit/test/apps/options/test/test.js | 6 ++--- 6 files changed, 39 insertions(+), 17 deletions(-) create mode 100644 packages/kit/test/apps/basics/src/routes/paths/deeply/nested/+page.svelte diff --git a/packages/kit/src/runtime/server/page/render.js b/packages/kit/src/runtime/server/page/render.js index 6c1ad4fe2b04..d909016423bd 100644 --- a/packages/kit/src/runtime/server/page/render.js +++ b/packages/kit/src/runtime/server/page/render.js @@ -83,6 +83,14 @@ export async function render_response({ 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. + * 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); + if (page_config.ssr) { if (__SVELTEKIT_DEV__ && !branch.at(-1)?.node.component) { // Can only be the leaf, layouts have a fallback component generated @@ -119,8 +127,12 @@ export async function render_response({ form: form_value }; + // use relative paths during rendering, so that the resulting HTML is as + // portable as possible, but reset afterwards const original_base = paths.base; + const original_assets = paths.assets; paths.set_base(base); + paths.set_assets(assets); if (__SVELTEKIT_DEV__) { const fetch = globalThis.fetch; @@ -145,12 +157,14 @@ export async function render_response({ } finally { globalThis.fetch = fetch; paths.set_base(original_base); + paths.set_assets(original_assets); } } else { try { rendered = options.root.render(props); } finally { paths.set_base(original_base); + paths.set_assets(original_assets); } } @@ -182,14 +196,6 @@ export async function render_response({ */ 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; diff --git a/packages/kit/test/apps/basics/src/routes/paths/deeply/nested/+page.svelte b/packages/kit/test/apps/basics/src/routes/paths/deeply/nested/+page.svelte new file mode 100644 index 000000000000..73d1cb7eb4ef --- /dev/null +++ b/packages/kit/test/apps/basics/src/routes/paths/deeply/nested/+page.svelte @@ -0,0 +1,5 @@ + + +
{JSON.stringify({ base, assets })}
diff --git a/packages/kit/test/apps/basics/test/test.js b/packages/kit/test/apps/basics/test/test.js index 05bc19a9f641..e636bd86fddf 100644 --- a/packages/kit/test/apps/basics/test/test.js +++ b/packages/kit/test/apps/basics/test/test.js @@ -609,13 +609,22 @@ test.describe('$app/environment', () => { }); test.describe('$app/paths', () => { - test('includes paths', async ({ page }) => { + test('includes paths', async ({ page, javaScriptEnabled }) => { await page.goto('/paths'); expect(await page.innerHTML('pre')).toBe( JSON.stringify({ - base: '', - assets: '' + base: javaScriptEnabled ? '' : '.', + assets: javaScriptEnabled ? '' : '.' + }) + ); + + await page.goto('/paths/deeply/nested'); + + expect(await page.innerHTML('pre')).toBe( + JSON.stringify({ + base: javaScriptEnabled ? '' : '../..', + assets: javaScriptEnabled ? '' : '../..' }) ); }); diff --git a/packages/kit/test/apps/options/source/pages/slash/+page.svelte b/packages/kit/test/apps/options/source/pages/slash/+page.svelte index 93a915f3661f..21b88a503057 100644 --- a/packages/kit/test/apps/options/source/pages/slash/+page.svelte +++ b/packages/kit/test/apps/options/source/pages/slash/+page.svelte @@ -1,8 +1,10 @@ -

{$page.url.pathname.replace(base, '')}

+

{$page.url.pathname}

-/slash/child +/slash/child diff --git a/packages/kit/test/apps/options/source/pages/slash/child/+page.svelte b/packages/kit/test/apps/options/source/pages/slash/child/+page.svelte index ab4505e94b0e..ba5e7fa0b0b0 100644 --- a/packages/kit/test/apps/options/source/pages/slash/child/+page.svelte +++ b/packages/kit/test/apps/options/source/pages/slash/child/+page.svelte @@ -3,4 +3,4 @@ import { page } from '$app/stores'; -

{$page.url.pathname.replace(base, '')}

+

{$page.url.pathname}

diff --git a/packages/kit/test/apps/options/test/test.js b/packages/kit/test/apps/options/test/test.js index 3746deaea278..d50125ae8c7b 100644 --- a/packages/kit/test/apps/options/test/test.js +++ b/packages/kit/test/apps/options/test/test.js @@ -176,11 +176,11 @@ test.describe('trailingSlash', () => { await page.goto('/path-base/slash'); expect(page.url()).toBe(`${baseURL}/path-base/slash/`); - expect(await page.textContent('h2')).toBe('/slash/'); + expect(await page.textContent('h2')).toBe('/path-base/slash/'); - await clicknav('[href="/path-base/slash/child"]'); + await clicknav('[data-testid="child"]'); expect(page.url()).toBe(`${baseURL}/path-base/slash/child/`); - expect(await page.textContent('h2')).toBe('/slash/child/'); + expect(await page.textContent('h2')).toBe('/path-base/slash/child/'); }); test('removes trailing slash on endpoint', async ({ baseURL, request }) => { From 9511492d76f54877e0b5c1538f9be1b23665a8f1 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Sun, 26 Feb 2023 19:48:53 -0500 Subject: [PATCH 11/23] tidy up --- packages/kit/test/apps/options/source/pages/slash/+page.svelte | 2 -- .../kit/test/apps/options/source/pages/slash/child/+page.svelte | 1 - 2 files changed, 3 deletions(-) diff --git a/packages/kit/test/apps/options/source/pages/slash/+page.svelte b/packages/kit/test/apps/options/source/pages/slash/+page.svelte index 21b88a503057..7c66a2ad551b 100644 --- a/packages/kit/test/apps/options/source/pages/slash/+page.svelte +++ b/packages/kit/test/apps/options/source/pages/slash/+page.svelte @@ -1,8 +1,6 @@

{$page.url.pathname}

diff --git a/packages/kit/test/apps/options/source/pages/slash/child/+page.svelte b/packages/kit/test/apps/options/source/pages/slash/child/+page.svelte index ba5e7fa0b0b0..fe890859233d 100644 --- a/packages/kit/test/apps/options/source/pages/slash/child/+page.svelte +++ b/packages/kit/test/apps/options/source/pages/slash/child/+page.svelte @@ -1,5 +1,4 @@ From ebb0a68dedc0307d3204d8bd5b787a7326e90646 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Mon, 27 Feb 2023 12:50:00 -0500 Subject: [PATCH 12/23] fix --- packages/kit/src/runtime/server/page/render.js | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/packages/kit/src/runtime/server/page/render.js b/packages/kit/src/runtime/server/page/render.js index d909016423bd..dc87224d220c 100644 --- a/packages/kit/src/runtime/server/page/render.js +++ b/packages/kit/src/runtime/server/page/render.js @@ -80,8 +80,14 @@ export async function render_response({ ? action_result.data ?? null : null; - const segments = event.url.pathname.slice(paths.base.length).split('/').slice(2); - const base = segments.map(() => '..').join('/') || '.'; + const segments = event.url.pathname.slice(paths.base.length).split('/'); + const base = + segments.length === 1 && paths.base !== '' + ? `./${paths.base.split('/').at(-1)}` // if we're on `/my-base-path`, relative links need to start `./my-base-path` rather than `.` + : segments + .slice(2) + .map(() => '..') + .join('/') || '.'; /** * The prefix to use for static assets. Replaces `%sveltekit.assets%` in the template. From 92197dd7eb00b235af35018ddb41a1799fcfa917 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Mon, 27 Feb 2023 12:58:03 -0500 Subject: [PATCH 13/23] reduce indirection --- packages/kit/src/core/sync/write_server.js | 3 ++- packages/kit/src/exports/vite/dev/index.js | 11 +++++------ packages/kit/src/runtime/shared-server.js | 2 -- 3 files changed, 7 insertions(+), 9 deletions(-) diff --git a/packages/kit/src/core/sync/write_server.js b/packages/kit/src/core/sync/write_server.js index 0ccd98c42bd5..a7140dce749e 100644 --- a/packages/kit/src/core/sync/write_server.js +++ b/packages/kit/src/core/sync/write_server.js @@ -27,7 +27,8 @@ const server_template = ({ }) => ` import root from '../root.svelte'; import { set_building } from '__sveltekit/environment'; -import { set_assets, set_private_env, set_public_env } from '${runtime_directory}/shared-server.js'; +import { set_assets } from '__sveltekit/paths'; +import { set_private_env, set_public_env } from '${runtime_directory}/shared-server.js'; export const options = { app_template_contains_nonce: ${template.includes('%sveltekit.nonce%')}, diff --git a/packages/kit/src/exports/vite/dev/index.js b/packages/kit/src/exports/vite/dev/index.js index 05adc2776fb1..27b3a1b5dace 100644 --- a/packages/kit/src/exports/vite/dev/index.js +++ b/packages/kit/src/exports/vite/dev/index.js @@ -451,15 +451,14 @@ export async function dev(vite, vite_config, svelte_config) { await vite.ssrLoadModule(`${runtime_base}/server/index.js`) ); - const { set_assets, set_fix_stack_trace } = - /** @type {import('types').ServerInternalModule} */ ( - await vite.ssrLoadModule(`${runtime_base}/shared-server.js`) - ); + const { set_fix_stack_trace } = await vite.ssrLoadModule( + `${runtime_base}/shared-server.js` + ); + set_fix_stack_trace(fix_stack_trace); + const { set_assets } = await vite.ssrLoadModule('__sveltekit/paths'); set_assets(assets); - set_fix_stack_trace(fix_stack_trace); - const server = new Server(manifest); await server.init({ env }); diff --git a/packages/kit/src/runtime/shared-server.js b/packages/kit/src/runtime/shared-server.js index faaaca4cec79..ed1c5efcb472 100644 --- a/packages/kit/src/runtime/shared-server.js +++ b/packages/kit/src/runtime/shared-server.js @@ -1,5 +1,3 @@ -export { set_assets } from '__sveltekit/paths'; - /** @type {Record} */ export let private_env = {}; From 81fe6c6006061200aea8700a718ca91105c20fdb Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Mon, 27 Feb 2023 14:08:52 -0500 Subject: [PATCH 14/23] add paths.reset() function --- packages/kit/src/exports/vite/index.js | 7 +++++++ packages/kit/src/internal.d.ts | 1 + packages/kit/src/runtime/server/page/render.js | 8 ++------ 3 files changed, 10 insertions(+), 6 deletions(-) diff --git a/packages/kit/src/exports/vite/index.js b/packages/kit/src/exports/vite/index.js index 517106522401..5be84b441369 100644 --- a/packages/kit/src/exports/vite/index.js +++ b/packages/kit/src/exports/vite/index.js @@ -385,6 +385,13 @@ export const assets = ${global}?.assets ?? ${assets ? s(assets) : 'base'};`; return `export let base = ${s(base)}; export let assets = ${assets ? s(assets) : 'base'}; +const initial = { base, assets }; + +export function reset() { + base = initial.base; + assets = initial.assets; +} + /** @param {string} path */ export function set_base(path) { base = path; diff --git a/packages/kit/src/internal.d.ts b/packages/kit/src/internal.d.ts index 87d4bbb33e74..76d2ed710e82 100644 --- a/packages/kit/src/internal.d.ts +++ b/packages/kit/src/internal.d.ts @@ -9,6 +9,7 @@ declare module '__sveltekit/environment' { declare module '__sveltekit/paths' { export const base: string; export let assets: `https://${string}` | `http://${string}`; + export function reset(): void; export function set_base(path: string): void; 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 dc87224d220c..f64325ba9761 100644 --- a/packages/kit/src/runtime/server/page/render.js +++ b/packages/kit/src/runtime/server/page/render.js @@ -135,8 +135,6 @@ export async function render_response({ // use relative paths during rendering, so that the resulting HTML is as // portable as possible, but reset afterwards - const original_base = paths.base; - const original_assets = paths.assets; paths.set_base(base); paths.set_assets(assets); @@ -162,15 +160,13 @@ export async function render_response({ rendered = options.root.render(props); } finally { globalThis.fetch = fetch; - paths.set_base(original_base); - paths.set_assets(original_assets); + paths.reset(); } } else { try { rendered = options.root.render(props); } finally { - paths.set_base(original_base); - paths.set_assets(original_assets); + paths.reset(); } } From 039b681d9acdfcc8f87b11fde7a65d7f81b95190 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Mon, 27 Feb 2023 15:49:07 -0500 Subject: [PATCH 15/23] add paths.relative --- packages/kit/src/core/config/index.spec.js | 16 +++-- packages/kit/src/core/config/options.js | 7 +++ packages/kit/src/exports/vite/index.js | 14 +++-- packages/kit/src/internal.d.ts | 7 ++- .../kit/src/runtime/server/page/render.js | 63 ++++++++++--------- packages/kit/test/apps/basics/test/test.js | 10 +-- packages/kit/types/index.d.ts | 12 +++- 7 files changed, 77 insertions(+), 52 deletions(-) diff --git a/packages/kit/src/core/config/index.spec.js b/packages/kit/src/core/config/index.spec.js index b58ee40beb81..86c30dba1f05 100644 --- a/packages/kit/src/core/config/index.spec.js +++ b/packages/kit/src/core/config/index.spec.js @@ -97,7 +97,8 @@ const get_defaults = (prefix = '') => ({ typescript: {}, paths: { base: '', - assets: '' + assets: '', + relative: undefined }, prerender: { concurrency: 1, @@ -293,8 +294,8 @@ test('fails if prerender.entries are invalid', () => { /** * @param {string} name - * @param {{ base?: string, assets?: string }} input - * @param {{ base?: string, assets?: string }} output + * @param {import('types').KitConfig['paths']} input + * @param {import('types').KitConfig['paths']} output */ function validate_paths(name, input, output) { test(name, () => { @@ -316,7 +317,8 @@ validate_paths( }, { base: '/path/to/base', - assets: '' + assets: '', + relative: undefined } ); @@ -327,7 +329,8 @@ validate_paths( }, { base: '', - assets: 'https://cdn.example.com' + assets: 'https://cdn.example.com', + relative: undefined } ); @@ -339,7 +342,8 @@ validate_paths( }, { base: '/path/to/base', - assets: 'https://cdn.example.com' + assets: 'https://cdn.example.com', + relative: undefined } ); diff --git a/packages/kit/src/core/config/options.js b/packages/kit/src/core/config/options.js index ecf69c81815d..cae5fed1d9ac 100644 --- a/packages/kit/src/core/config/options.js +++ b/packages/kit/src/core/config/options.js @@ -167,6 +167,13 @@ const options = object( } } + return input; + }), + relative: validate(undefined, (input, keypath) => { + if (typeof input !== 'boolean') { + throw new Error(`${keypath} option must be a boolean , if specified`); + } + return input; }) }), diff --git a/packages/kit/src/exports/vite/index.js b/packages/kit/src/exports/vite/index.js index 5be84b441369..4e5fd03d1c43 100644 --- a/packages/kit/src/exports/vite/index.js +++ b/packages/kit/src/exports/vite/index.js @@ -385,21 +385,23 @@ export const assets = ${global}?.assets ?? ${assets ? s(assets) : 'base'};`; return `export let base = ${s(base)}; export let assets = ${assets ? s(assets) : 'base'}; +export const relative = ${svelte_config.kit.paths.relative}; + const initial = { base, assets }; +export function override(paths) { + base = paths.base; + assets = paths.assets; +} + export function reset() { base = initial.base; assets = initial.assets; } -/** @param {string} path */ -export function set_base(path) { - base = path; -} - /** @param {string} path */ export function set_assets(path) { - assets = path; + assets = initial.assets = path; }`; case '\0__sveltekit/environment': diff --git a/packages/kit/src/internal.d.ts b/packages/kit/src/internal.d.ts index 76d2ed710e82..c58a42c682ec 100644 --- a/packages/kit/src/internal.d.ts +++ b/packages/kit/src/internal.d.ts @@ -7,9 +7,10 @@ declare module '__sveltekit/environment' { /** Internal version of $app/paths */ declare module '__sveltekit/paths' { - export const base: string; - export let assets: `https://${string}` | `http://${string}`; + export let base: '' | `/${string}`; + export let assets: '' | `https://${string}` | `http://${string}`; + export let relative: boolean | undefined; // TODO in 2.0, make this a `boolean` that defaults to `true` export function reset(): void; - export function set_base(path: string): void; + export function override(paths: { base: string; assets: string }): void; 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 f64325ba9761..d5f38a31d94b 100644 --- a/packages/kit/src/runtime/server/page/render.js +++ b/packages/kit/src/runtime/server/page/render.js @@ -80,22 +80,41 @@ export async function render_response({ ? action_result.data ?? null : null; - const segments = event.url.pathname.slice(paths.base.length).split('/'); - const base = - segments.length === 1 && paths.base !== '' - ? `./${paths.base.split('/').at(-1)}` // if we're on `/my-base-path`, relative links need to start `./my-base-path` rather than `.` - : segments - .slice(2) - .map(() => '..') - .join('/') || '.'; + /** @type {string} */ + let base = paths.base; + + /** @type {string} */ + let assets = paths.assets; + + /** + * 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 base_expression = s(paths.base); /** - * 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} + * An expression that will evaluate in the client to determine the resolved asset path. + * If `undefined`, falls back to `base` */ - const assets = paths.assets || (state.prerendering?.fallback ? paths.base : base); + const asset_expression = paths.assets ? s(paths.assets) : undefined; + + // if appropriate, use relative paths for greater portability + if (paths.relative !== false && !state.prerendering?.fallback) { + const segments = event.url.pathname.slice(paths.base.length).split('/'); + base = + segments.length === 1 && paths.base !== '' + ? `./${paths.base.split('/').at(-1)}` // if we're on `/my-base-path`, relative links need to start `./my-base-path` rather than `.` + : segments + .slice(2) + .map(() => '..') + .join('/') || '.'; + + base_expression = `new URL(${s(base)}, location.href).pathname.slice(0,-1)`; + + if (!paths.assets) { + assets = base; + } + } if (page_config.ssr) { if (__SVELTEKIT_DEV__ && !branch.at(-1)?.node.component) { @@ -135,8 +154,7 @@ export async function render_response({ // use relative paths during rendering, so that the resulting HTML is as // portable as possible, but reset afterwards - paths.set_base(base); - paths.set_assets(assets); + if (paths.relative) paths.override({ base, assets }); if (__SVELTEKIT_DEV__) { const fetch = globalThis.fetch; @@ -183,21 +201,6 @@ export async function render_response({ rendered = { head: '', html: '', css: { code: '', map: null } }; } - /** - * 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. - */ - 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. - * If `undefined`, falls back to `base` - */ - const asset_expression = paths.assets ? s(paths.assets) : undefined; - let head = ''; let body = rendered.html; diff --git a/packages/kit/test/apps/basics/test/test.js b/packages/kit/test/apps/basics/test/test.js index e636bd86fddf..6548d634c4d7 100644 --- a/packages/kit/test/apps/basics/test/test.js +++ b/packages/kit/test/apps/basics/test/test.js @@ -609,13 +609,13 @@ test.describe('$app/environment', () => { }); test.describe('$app/paths', () => { - test('includes paths', async ({ page, javaScriptEnabled }) => { + test('includes paths', async ({ page }) => { await page.goto('/paths'); expect(await page.innerHTML('pre')).toBe( JSON.stringify({ - base: javaScriptEnabled ? '' : '.', - assets: javaScriptEnabled ? '' : '.' + base: '', + assets: '' }) ); @@ -623,8 +623,8 @@ test.describe('$app/paths', () => { expect(await page.innerHTML('pre')).toBe( JSON.stringify({ - base: javaScriptEnabled ? '' : '../..', - assets: javaScriptEnabled ? '' : '../..' + base: '', + assets: '' }) ); }); diff --git a/packages/kit/types/index.d.ts b/packages/kit/types/index.d.ts index 73c5683ca8fe..e9ee9b25a939 100644 --- a/packages/kit/types/index.d.ts +++ b/packages/kit/types/index.d.ts @@ -427,12 +427,20 @@ export interface KitConfig { * An absolute path that your app's files are served from. This is useful if your files are served from a storage bucket of some kind. * @default "" */ - assets?: string; + assets?: '' | `http://${string}` | `https://${string}`; /** * A root-relative path that must start, but not end with `/` (e.g. `/base-path`), unless it is the empty string. This specifies where your app is served from and allows the app to live on a non-root path. Note that you need to prepend all your root-relative links with the base value or they will point to the root of your domain, not your `base` (this is how the browser works). You can use [`base` from `$app/paths`](/docs/modules#$app-paths-base) for that: `Link`. If you find yourself writing this often, it may make sense to extract this into a reusable component. * @default "" */ - base?: string; + base?: '' | `/${string}`; + /** + * Whether to use relative asset paths. By default, if `paths.assets` is not external, SvelteKit will replace `%sveltekit.assets%` with a relative path and use relative paths to reference build artifacts, but `base` and `assets` imported from `$app/paths` will be as specified in your config. + * + * If `true`, `base` and `assets` imported from `$app/paths` will be replaced with relative asset paths during server-side rendering, resulting in portable HTML. + * If `false`, `%sveltekit.assets%` and references to build artifacts will always be root-relative paths, unless `paths.assets` is an external URL + * @default undefined + */ + relative?: boolean | undefined; }; /** * See [Prerendering](https://kit.svelte.dev/docs/page-options#prerender). From 7e853eed0e46978145dc4e52ad79c3e1b2fc1d7b Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Mon, 27 Feb 2023 16:01:29 -0500 Subject: [PATCH 16/23] fix tests --- packages/kit/src/core/config/index.spec.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/kit/src/core/config/index.spec.js b/packages/kit/src/core/config/index.spec.js index 86c30dba1f05..614edd3bf5b3 100644 --- a/packages/kit/src/core/config/index.spec.js +++ b/packages/kit/src/core/config/index.spec.js @@ -236,6 +236,7 @@ test('fails if paths.base is not root-relative', () => { validate_config({ kit: { paths: { + // @ts-expect-error base: 'https://example.com/somewhere/else' } } @@ -260,6 +261,7 @@ test('fails if paths.assets is relative', () => { validate_config({ kit: { paths: { + // @ts-expect-error assets: 'foo' } } From 23ac8e910037e81b403500c2b336e6338a6c2be6 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Mon, 27 Feb 2023 16:03:23 -0500 Subject: [PATCH 17/23] simplify --- packages/kit/src/runtime/server/page/render.js | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/packages/kit/src/runtime/server/page/render.js b/packages/kit/src/runtime/server/page/render.js index d5f38a31d94b..ea79fcb27f47 100644 --- a/packages/kit/src/runtime/server/page/render.js +++ b/packages/kit/src/runtime/server/page/render.js @@ -92,12 +92,6 @@ export async function render_response({ */ let base_expression = s(paths.base); - /** - * An expression that will evaluate in the client to determine the resolved asset path. - * If `undefined`, falls back to `base` - */ - const asset_expression = paths.assets ? s(paths.assets) : undefined; - // if appropriate, use relative paths for greater portability if (paths.relative !== false && !state.prerendering?.fallback) { const segments = event.url.pathname.slice(paths.base.length).split('/'); @@ -301,7 +295,7 @@ export async function render_response({ const properties = [ `env: ${s(public_env)}`, - asset_expression && `assets: ${asset_expression}`, + paths.assets && `assets: ${s(paths.assets)}`, `base: ${base_expression}`, `element: document.currentScript.parentElement` ].filter(Boolean); From 8ceade05f277a5c930614d6615484c3b9e80c170 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Mon, 27 Feb 2023 16:13:03 -0500 Subject: [PATCH 18/23] small tweak --- packages/kit/src/runtime/server/page/render.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/kit/src/runtime/server/page/render.js b/packages/kit/src/runtime/server/page/render.js index ea79fcb27f47..e802212d7fd7 100644 --- a/packages/kit/src/runtime/server/page/render.js +++ b/packages/kit/src/runtime/server/page/render.js @@ -103,7 +103,7 @@ export async function render_response({ .map(() => '..') .join('/') || '.'; - base_expression = `new URL(${s(base)}, location.href).pathname.slice(0,-1)`; + base_expression = `new URL(${s(base)}, location).pathname.slice(0, -1)`; if (!paths.assets) { assets = base; From 2dd7150f723e8a1c25b8925e63e3440b6fb639a8 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Mon, 27 Feb 2023 16:19:00 -0500 Subject: [PATCH 19/23] update changesets --- .changeset/few-lions-drive.md | 2 +- .changeset/rude-cameras-compare.md | 5 ----- 2 files changed, 1 insertion(+), 6 deletions(-) delete mode 100644 .changeset/rude-cameras-compare.md diff --git a/.changeset/few-lions-drive.md b/.changeset/few-lions-drive.md index ca30f97d3426..89f10014bc2c 100644 --- a/.changeset/few-lions-drive.md +++ b/.changeset/few-lions-drive.md @@ -2,4 +2,4 @@ '@sveltejs/kit': minor --- -feat: use relative `base` path during server-rendering +feat: add `paths.relative` option to control interpretation of `paths.assets` and `paths.base` diff --git a/.changeset/rude-cameras-compare.md b/.changeset/rude-cameras-compare.md deleted file mode 100644 index a08068ff3be0..000000000000 --- a/.changeset/rude-cameras-compare.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@sveltejs/kit': minor ---- - -feat: use relative `base` path in client From 8aedd0d00e810e6a2a553dd804d2407863635161 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Mon, 27 Feb 2023 16:47:59 -0500 Subject: [PATCH 20/23] add tests, and fix the bug revealed by the tests --- .../kit/src/runtime/server/page/render.js | 24 ++++++++++++------- .../apps/options-2/src/routes/+page.svelte | 7 ++++++ .../routes/deeply/nested/page/+page.svelte | 8 +++++++ .../kit/test/apps/options-2/svelte.config.js | 3 ++- packages/kit/test/apps/options-2/test/test.js | 16 ++++++++++++- 5 files changed, 47 insertions(+), 11 deletions(-) create mode 100644 packages/kit/test/apps/options-2/src/routes/deeply/nested/page/+page.svelte diff --git a/packages/kit/src/runtime/server/page/render.js b/packages/kit/src/runtime/server/page/render.js index e802212d7fd7..61d4b32938a2 100644 --- a/packages/kit/src/runtime/server/page/render.js +++ b/packages/kit/src/runtime/server/page/render.js @@ -95,17 +95,23 @@ export async function render_response({ // if appropriate, use relative paths for greater portability if (paths.relative !== false && !state.prerendering?.fallback) { const segments = event.url.pathname.slice(paths.base.length).split('/'); - base = - segments.length === 1 && paths.base !== '' - ? `./${paths.base.split('/').at(-1)}` // if we're on `/my-base-path`, relative links need to start `./my-base-path` rather than `.` - : segments - .slice(2) - .map(() => '..') - .join('/') || '.'; - base_expression = `new URL(${s(base)}, location).pathname.slice(0, -1)`; + if (segments.length === 1 && paths.base !== '') { + // if we're on `/my-base-path`, relative links need to start `./my-base-path` rather than `.` + base = `./${paths.base.split('/').at(-1)}`; - if (!paths.assets) { + base_expression = `new URL(${s(base)}, location).pathname`; + } else { + base = + segments + .slice(2) + .map(() => '..') + .join('/') || '.'; + + base_expression = `new URL(${s(base)}, location).pathname.slice(0, -1)`; + } + + if (!paths.assets || paths.assets[0] === '/') { assets = base; } } diff --git a/packages/kit/test/apps/options-2/src/routes/+page.svelte b/packages/kit/test/apps/options-2/src/routes/+page.svelte index 986a4a1a25bc..d6232afc0cc9 100644 --- a/packages/kit/test/apps/options-2/src/routes/+page.svelte +++ b/packages/kit/test/apps/options-2/src/routes/+page.svelte @@ -1 +1,8 @@ + +

Hello

+ +

base: {base}

+

assets: {assets}

diff --git a/packages/kit/test/apps/options-2/src/routes/deeply/nested/page/+page.svelte b/packages/kit/test/apps/options-2/src/routes/deeply/nested/page/+page.svelte new file mode 100644 index 000000000000..d6232afc0cc9 --- /dev/null +++ b/packages/kit/test/apps/options-2/src/routes/deeply/nested/page/+page.svelte @@ -0,0 +1,8 @@ + + +

Hello

+ +

base: {base}

+

assets: {assets}

diff --git a/packages/kit/test/apps/options-2/svelte.config.js b/packages/kit/test/apps/options-2/svelte.config.js index ade762d9b8a1..ca9cb32020f8 100644 --- a/packages/kit/test/apps/options-2/svelte.config.js +++ b/packages/kit/test/apps/options-2/svelte.config.js @@ -2,7 +2,8 @@ const config = { kit: { paths: { - base: '/basepath' + base: '/basepath', + relative: true }, serviceWorker: { register: false diff --git a/packages/kit/test/apps/options-2/test/test.js b/packages/kit/test/apps/options-2/test/test.js index f9b7cff16d3a..61f0b510720b 100644 --- a/packages/kit/test/apps/options-2/test/test.js +++ b/packages/kit/test/apps/options-2/test/test.js @@ -13,7 +13,7 @@ test.describe('env', () => { }); }); -test.describe('paths.base', () => { +test.describe('paths', () => { test('serves /basepath', async ({ page }) => { await page.goto('/basepath'); expect(await page.textContent('h1')).toBe('Hello'); @@ -23,6 +23,20 @@ test.describe('paths.base', () => { const response = await request.get('/basepath/answer.txt'); expect(await response.text()).toBe('42'); }); + + test('uses relative paths during SSR', async ({ page, javaScriptEnabled }) => { + await page.goto('/basepath'); + + let base = javaScriptEnabled ? '/basepath' : './basepath'; + expect(await page.textContent('[data-testid="base"]')).toBe(`base: ${base}`); + expect(await page.textContent('[data-testid="assets"]')).toBe(`assets: ${base}`); + + await page.goto('/basepath/deeply/nested/page'); + + base = javaScriptEnabled ? '/basepath' : '../..'; + expect(await page.textContent('[data-testid="base"]')).toBe(`base: ${base}`); + expect(await page.textContent('[data-testid="assets"]')).toBe(`assets: ${base}`); + }); }); test.describe('Service worker', () => { From 0779dd15fb854a92edaee41a7a5a6d5ae3c22b48 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Mon, 27 Feb 2023 17:34:20 -0500 Subject: [PATCH 21/23] fix --- packages/kit/src/internal.d.ts | 2 +- packages/kit/src/runtime/server/page/render.js | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/kit/src/internal.d.ts b/packages/kit/src/internal.d.ts index c58a42c682ec..c248d9d528f6 100644 --- a/packages/kit/src/internal.d.ts +++ b/packages/kit/src/internal.d.ts @@ -8,7 +8,7 @@ declare module '__sveltekit/environment' { /** Internal version of $app/paths */ declare module '__sveltekit/paths' { export let base: '' | `/${string}`; - export let assets: '' | `https://${string}` | `http://${string}`; + export let assets: '' | `https://${string}` | `http://${string}` | '/_svelte_kit_assets'; export let relative: boolean | undefined; // TODO in 2.0, make this a `boolean` that defaults to `true` export function reset(): void; export function override(paths: { base: string; assets: string }): void; diff --git a/packages/kit/src/runtime/server/page/render.js b/packages/kit/src/runtime/server/page/render.js index 61d4b32938a2..585186dbc058 100644 --- a/packages/kit/src/runtime/server/page/render.js +++ b/packages/kit/src/runtime/server/page/render.js @@ -11,6 +11,7 @@ import { clarify_devalue_error, stringify_uses, handle_error_and_jsonify } from import { public_env } from '../../shared-server.js'; import { text } from '../../../exports/index.js'; import { create_async_iterator } from '../../../utils/streaming.js'; +import { SVELTE_KIT_ASSETS } from '../../../constants.js'; // TODO rename this function/module @@ -111,7 +112,7 @@ export async function render_response({ base_expression = `new URL(${s(base)}, location).pathname.slice(0, -1)`; } - if (!paths.assets || paths.assets[0] === '/') { + if (!paths.assets || (paths.assets[0] === '/' && paths.assets !== SVELTE_KIT_ASSETS)) { assets = base; } } From 59856571795ce0633442e1419ddab36eff4ec1ef Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Tue, 28 Feb 2023 12:07:20 -0500 Subject: [PATCH 22/23] Update packages/kit/src/core/config/options.js Co-authored-by: Simon H <5968653+dummdidumm@users.noreply.github.com> --- packages/kit/src/core/config/options.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/kit/src/core/config/options.js b/packages/kit/src/core/config/options.js index cae5fed1d9ac..7501967f8ee5 100644 --- a/packages/kit/src/core/config/options.js +++ b/packages/kit/src/core/config/options.js @@ -171,7 +171,7 @@ const options = object( }), relative: validate(undefined, (input, keypath) => { if (typeof input !== 'boolean') { - throw new Error(`${keypath} option must be a boolean , if specified`); + throw new Error(`${keypath} option must be a boolean or undefined`); } return input; From 7892386d45ad41c2d946a93354a4bd54fd157002 Mon Sep 17 00:00:00 2001 From: Simon H <5968653+dummdidumm@users.noreply.github.com> Date: Tue, 28 Feb 2023 18:42:58 +0100 Subject: [PATCH 23/23] Update packages/kit/src/runtime/server/page/render.js Co-authored-by: Rich Harris --- packages/kit/src/runtime/server/page/render.js | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/kit/src/runtime/server/page/render.js b/packages/kit/src/runtime/server/page/render.js index 585186dbc058..2280cdfd2b4d 100644 --- a/packages/kit/src/runtime/server/page/render.js +++ b/packages/kit/src/runtime/server/page/render.js @@ -109,6 +109,7 @@ export async function render_response({ .map(() => '..') .join('/') || '.'; + // resolve e.g. '../..' against current location, then remove trailing slash base_expression = `new URL(${s(base)}, location).pathname.slice(0, -1)`; }