Skip to content

Commit

Permalink
breaking: require paths pass to preloadCode to be prefixed with bas…
Browse files Browse the repository at this point in the history
…epath (#11259)

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

---------

Co-authored-by: Rich Harris <rich.harris@vercel.com>
Co-authored-by: Simon H <5968653+dummdidumm@users.noreply.github.com>
  • Loading branch information
3 people authored Dec 12, 2023
1 parent 6f6fa38 commit 408f6c2
Show file tree
Hide file tree
Showing 6 changed files with 50 additions and 40 deletions.
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

0 comments on commit 408f6c2

Please sign in to comment.