Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

breaking: require paths pass to preloadCode to be prefixed with basepath #11259

Merged
merged 8 commits into from
Dec 12, 2023
5 changes: 5 additions & 0 deletions .changeset/nervous-bananas-search.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@sveltejs/kit': major
---

breaking: require paths pass to preloadCode to be prefixed with basepath
24 changes: 10 additions & 14 deletions documentation/docs/60-appendix/30-migrating-to-sveltekit-2.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ export function load({ cookies }) {
}
```

`svelte-migrate` will add comments highlighting the locations that need to be adjusted.

## Top-level promises are no longer awaited

In SvelteKit version 1, if the top-level properties of the object returned from a `load` function were promises, they were automatically awaited. With the introduction of [streaming](https://svelte.dev/blog/streaming-snapshots-sveltekit) this behavior became a bit awkward as it forces you to nest your streamed data one level deep.
Expand Down Expand Up @@ -64,20 +66,6 @@ export function load({ fetch }) {
}
```

## `path` is now a required option for cookies

`cookies.set`, `cookies.delete` and `cookies.serialize` all have an options argument through which certain cookie serialization options are configurable. One of the is the `path` setting, which tells browser under which URLs a cookie is applicable. In SvelteKit 1.x, the `path` is optional and defaults to what the browser does, which is removing everything up to and including the last slash in the pathname of the URL. This means that if you're on `/foo/bar`, then the `path` is `/foo`, but if you're on `/foo/bar/`, the `path` is `/foo/bar`. This behavior is somewhat confusing, and most of the time you probably want to have cookies available more broadly (many people set `path` to `/` for that reason) instead of scratching their heads why a cookie they have set doesn't apply elsewhere. For this reason, `path` is a required option in SvelteKit 2.

```diff
// file: foo/bar/+page.svelte
export function load ({ cookies }) {
- cookies.set('key', 'value');
+ cookies.set('key', 'value', { path: '/foo' });
}
```

`svelte-migrate` will add comments highlighting the locations that need to be adjusted.

## goto(...) no longer accepts external URLs

To navigate to an external URL, use `window.location = url`.
Expand All @@ -92,6 +80,14 @@ This inconsistency is fixed in version 2. Paths are either always relative or al

Previously it was possible to track URLs from `fetch`es on the server in order to rerun load functions. This poses a possible security risk (private URLs leaking), and as such it was behind the `dangerZone.trackServerFetches` setting, which is now removed.

## `preloadCode` arguments must be prefixed with `base`

SvelteKit exposes two functions, [`preloadCode`](/docs/modules#$app-navigation-preloadcode) and [`preloadData`](/docs/modules#$app-navigation-preloaddata), for programmatically loading the code and data associated with a particular path. In version 1, there was a subtle inconsistency — the path passed to `preloadCode` did not need to be prefixed with the `base` path (if set), while the path passed to `preloadData` did.

This is fixed in SvelteKit 2 — in both cases, the path should be prefixed with `base` if it is set.

Additionally, `preloadCode` now takes a single argument rather than _n_ arguments.

## `resolvePath` has been removed

SvelteKit 1 included a function called `resolvePath` which allows you to resolve a route ID (like `/blog/[slug]`) and a set of parameters (like `{ slug: 'hello' }`) to a pathname. Unfortunately the return value didn't include the `base` path, limiting its usefulness in cases where `base` was set.
Expand Down
4 changes: 2 additions & 2 deletions packages/kit/src/runtime/app/navigation.js
Original file line number Diff line number Diff line change
Expand Up @@ -81,8 +81,8 @@ export const preloadData = /* @__PURE__ */ client_method('preload_data');
* Unlike `preloadData`, this won't call `load` functions.
* Returns a Promise that resolves when the modules have been imported.
*
* @type {(...urls: string[]) => Promise<void>}
* @param {...string[]} urls
* @type {(url: string) => Promise<void>}
* @param {string} url
* @returns {Promise<void>}
*/
export const preloadCode = /* @__PURE__ */ client_method('preload_code');
Expand Down
50 changes: 28 additions & 22 deletions packages/kit/src/runtime/client/client.js
Original file line number Diff line number Diff line change
Expand Up @@ -273,19 +273,13 @@ export function create_client(app, target) {
return load_cache.promise;
}

/** @param {...string} pathnames */
async function preload_code(...pathnames) {
if (DEV && pathnames.length > 1) {
console.warn('Calling `preloadCode` with multiple arguments is deprecated');
}

const matching = routes.filter((route) => pathnames.some((pathname) => route.exec(pathname)));

const promises = matching.map((r) => {
return Promise.all([...r.layouts, r.leaf].map((load) => load?.[1]()));
});
/** @param {string} pathname */
async function preload_code(pathname) {
const route = routes.find((route) => route.exec(get_url_path(pathname)));

await Promise.all(promises);
if (route) {
await Promise.all([...route.layouts, route.leaf].map((load) => load?.[1]()));
}
}

/** @param {import('./types.js').NavigationFinished} result */
Expand Down Expand Up @@ -951,7 +945,7 @@ export function create_client(app, target) {
function get_navigation_intent(url, invalidating) {
if (is_external_url(url, base)) return;

const path = get_url_path(url);
const path = get_url_path(url.pathname);

for (const route of routes) {
const params = route.exec(path);
Expand All @@ -965,9 +959,9 @@ export function create_client(app, target) {
}
}

/** @param {URL} url */
function get_url_path(url) {
return decode_pathname(url.pathname.slice(base.length) || '/');
/** @param {string} pathname */
function get_url_path(pathname) {
return decode_pathname(pathname.slice(base.length) || '/');
}

/**
Expand Down Expand Up @@ -1293,9 +1287,7 @@ export function create_client(app, target) {
(entries) => {
for (const entry of entries) {
if (entry.isIntersecting) {
preload_code(
get_url_path(new URL(/** @type {HTMLAnchorElement} */ (entry.target).href))
);
preload_code(/** @type {HTMLAnchorElement} */ (entry.target).href);
observer.unobserve(entry.target);
}
}
Expand Down Expand Up @@ -1336,7 +1328,7 @@ export function create_client(app, target) {
}
}
} else if (priority <= options.preload_code) {
preload_code(get_url_path(/** @type {URL} */ (url)));
preload_code(/** @type {URL} */ (url).pathname);
}
}
}
Expand All @@ -1356,7 +1348,7 @@ export function create_client(app, target) {
}

if (options.preload_code === PRELOAD_PRIORITIES.eager) {
preload_code(get_url_path(/** @type {URL} */ (url)));
preload_code(/** @type {URL} */ (url).pathname);
}
}
}
Expand Down Expand Up @@ -1473,7 +1465,21 @@ export function create_client(app, target) {
await preload_data(intent);
},

preload_code,
preload_code: (pathname) => {
if (DEV) {
if (!pathname.startsWith(base)) {
throw new Error(
`pathnames passed to preloadCode must start with \`paths.base\` (i.e. "${base}${pathname}" rather than "${pathname}")`
);
}

if (!routes.find((route) => route.exec(get_url_path(pathname)))) {
throw new Error(`'${pathname}' did not match any routes`);
}
}

return preload_code(pathname);
},

apply_action: async (result) => {
if (result.type === 'error') {
Expand Down
5 changes: 4 additions & 1 deletion packages/kit/test/apps/options/test/test.js
Original file line number Diff line number Diff line change
Expand Up @@ -239,7 +239,7 @@ test.describe('trailingSlash', () => {

// also wait for network processing to complete, see
// https://playwright.dev/docs/network#network-events
await app.preloadData('/path-base/preloading/preloaded');
await app.preloadCode('/path-base/preloading/preloaded');

// svelte request made is environment dependent
if (process.env.DEV) {
Expand All @@ -248,6 +248,9 @@ test.describe('trailingSlash', () => {
expect(requests.filter((req) => req.endsWith('.mjs')).length).toBeGreaterThan(0);
}

requests = [];
await app.preloadData('/path-base/preloading/preloaded');

expect(requests.includes('/path-base/preloading/preloaded/__data.json')).toBe(true);

requests = [];
Expand Down
2 changes: 1 addition & 1 deletion packages/kit/types/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1982,7 +1982,7 @@ declare module '$app/navigation' {
* Returns a Promise that resolves when the modules have been imported.
*
* */
export const preloadCode: (...urls: string[]) => Promise<void>;
export const preloadCode: (url: string) => Promise<void>;
/**
* A navigation interceptor that triggers before we navigate to a new URL, whether by clicking a link, calling `goto(...)`, or using the browser back/forward controls.
* Calling `cancel()` will prevent the navigation from completing. If the navigation would have directly unloaded the current page, calling `cancel` will trigger the native
Expand Down
Loading