Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/resolve-query-relative.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@sveltejs/kit": patch
---

fix: allow `resolve()` from `$app/paths` inside remote queries when `paths.relative` is `true`
2 changes: 2 additions & 0 deletions packages/kit/src/runtime/app/paths/client.js
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@ const pathname_prefix = hash_routing ? '#' : base;
* In hash routing mode, the returned URL starts with `#`.
*
* During server rendering, the base path is relative and depends on the page currently being rendered.
* Inside a remote `query`, the path is always absolute (the configured base plus the resolved pathname),
* because query results are cached across pages.
*
* @example
* ```js
Expand Down
5 changes: 4 additions & 1 deletion packages/kit/src/runtime/app/paths/server.js
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,10 @@ export function resolve(id, params) {
if (relative) {
const store = try_get_request_store();

if (store && !store.state.prerendering?.fallback) {
// Relative prefixes depend on the current page URL. Queries cannot read
// `event.url`, and a page-relative path would be wrong anyway because
// query results are cached across pages — fall back to an absolute path.
if (store && !store.state.prerendering?.fallback && !store.state.is_in_remote_query) {
// the relative path depth must reflect the URL the browser is actually at, which
// for a data request includes the `__data.json` suffix that was stripped during routing
const pathname = store.event.isDataRequest
Expand Down
68 changes: 68 additions & 0 deletions packages/kit/src/runtime/app/paths/server.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
/** @import { RequestEvent } from '@sveltejs/kit' */
/** @import { RequestState } from 'types' */
import { expect, test, vi } from 'vitest';
import { getRequestEvent, with_request_store } from '@sveltejs/kit/internal/server';
import { run_remote_function } from '../server/remote/shared.js';

vi.hoisted(() => {
vi.stubGlobal('__SVELTEKIT_PATHS_BASE__', '');
vi.stubGlobal('__SVELTEKIT_PATHS_ASSETS__', '');
vi.stubGlobal('__SVELTEKIT_APP_DIR__', '_app');
vi.stubGlobal('__SVELTEKIT_PATHS_RELATIVE__', true);
vi.stubGlobal('__SVELTEKIT_DEV__', false);
});

const { resolve } = await import('./server.js');

/** @returns {RequestEvent} */
function event() {
return /** @type {any} */ ({
request: new Request('http://localhost/nested/page'),
url: new URL('http://localhost/nested/page'),
params: {},
route: { id: '/nested/page' },
cookies: { set: vi.fn(), delete: vi.fn(), get: () => undefined },
setHeaders: vi.fn(),
isRemoteRequest: false,
isDataRequest: false
});
}

test('resolve() falls back to an absolute path inside a remote query', async () => {
const result = await run_remote_function(
event(),
/** @type {RequestState} */ ({ is_in_remote_query: true }),
false,
() => undefined,
() => resolve('login')
);

expect(result).toBe('/login');
});

test('resolve() does not read event.url inside a remote query', async () => {
await run_remote_function(
event(),
/** @type {RequestState} */ ({ is_in_remote_query: true }),
false,
() => undefined,
() => {
expect(resolve('/blog/[slug]', { slug: 'hello' })).toBe('/blog/hello');
expect(() => getRequestEvent().url).toThrowKitError('remote_request_property', {
contains: ['`event.url`']
});
}
);
});

test('resolve() still uses a relative prefix outside a query', () => {
const result = with_request_store(
{
event: event(),
state: /** @type {RequestState} */ ({ is_in_remote_query: false })
},
() => resolve('login')
);

expect(result).toBe('../login');
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<script>
import { resolved_path } from './data.remote.js';
</script>

<p id="resolved">{await resolved_path()}</p>
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import { resolve } from '$app/paths';
import { query } from '$app/server';

export const resolved_path = query(() => resolve('/remote'));
5 changes: 5 additions & 0 deletions packages/kit/test/apps/async/test/test.js
Original file line number Diff line number Diff line change
Expand Up @@ -1065,6 +1065,11 @@ test.describe('remote functions', () => {
}
});

test('resolve() inside a query returns an absolute path', async ({ page }) => {
await page.goto('/remote/query-resolve');
await expect(page.locator('#resolved')).toHaveText('/remote');
});

test('queries cannot set cookies or headers', async ({ page }) => {
await page.goto('/remote/query-event-guards');

Expand Down
2 changes: 2 additions & 0 deletions packages/kit/types/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2926,6 +2926,8 @@ declare module '$app/paths' {
* In hash routing mode, the returned URL starts with `#`.
*
* During server rendering, the base path is relative and depends on the page currently being rendered.
* Inside a remote `query`, the path is always absolute (the configured base plus the resolved pathname),
* because query results are cached across pages.
*
* @example
* ```js
Expand Down
Loading