Skip to content

Commit

Permalink
merge master
Browse files Browse the repository at this point in the history
  • Loading branch information
Rich-Harris committed Dec 13, 2023
2 parents 20c7f32 + 7710f6c commit 2543938
Show file tree
Hide file tree
Showing 25 changed files with 108 additions and 66 deletions.
5 changes: 5 additions & 0 deletions .changeset/bright-buttons-flash.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@sveltejs/kit': patch
---

fix: allow `"false"` value for preload link options
5 changes: 0 additions & 5 deletions .changeset/honest-terms-help.md

This file was deleted.

5 changes: 5 additions & 0 deletions .changeset/swift-deers-draw.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@sveltejs/kit': patch
---

fix: correctly analyse exported server API methods during build
5 changes: 5 additions & 0 deletions .changeset/thin-shrimps-cheer.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@sveltejs/kit': patch
---

fix: avoid error when back navigating before page is initialized
5 changes: 0 additions & 5 deletions .changeset/warm-impalas-thank.md

This file was deleted.

5 changes: 5 additions & 0 deletions .changeset/warm-otters-compete.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@sveltejs/kit': patch
---

fix: distinguish better between not-found and internal-error
14 changes: 14 additions & 0 deletions documentation/docs/20-core-concepts/20-load.md
Original file line number Diff line number Diff line change
Expand Up @@ -625,6 +625,20 @@ To summarize, a `load` function will rerun in the following situations:

Note that rerunning a `load` function will update the `data` prop inside the corresponding `+layout.svelte` or `+page.svelte`; it does _not_ cause the component to be recreated. As a result, internal state is preserved. If this isn't what you want, you can reset whatever you need to reset inside an [`afterNavigate`](modules#$app-navigation-afternavigate) callback, and/or wrap your component in a [`{#key ...}`](https://svelte.dev/docs#template-syntax-key) block.

## Implications for authentication

A couple features of loading data have important implications for auth checks:
- Layout `load` functions do not run on every request, such as during client side navigation between child routes. [(When do load functions rerun?)](load#rerunning-load-functions-when-do-load-functions-rerun)
- Layout and page `load` functions run concurrently unless `await parent()` is called. If a layout `load` throws, the page `load` function runs, but the client will not receive the returned data.

There are a few possible strategies to ensure an auth check occurs before protected code.

To prevent data waterfalls and preserve layout `load` caches:
- Use [hooks](hooks) to protect multiple routes before any `load` functions run
- Use auth guards directly in `+page.server.js` `load` functions for route specific protection

Putting an auth guard in `+layout.server.js` requires all child pages to call `await parent()` before protected code. Unless every child page depends on returned data from `await parent()`, the other options will be more performant.

## Further reading

- [Tutorial: Loading data](https://learn.svelte.dev/tutorial/page-data)
Expand Down
2 changes: 1 addition & 1 deletion documentation/docs/20-core-concepts/40-page-options.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ While prerendering, the value of `building` imported from [`$app/environment`](m

### Prerendering server routes

Unlike the other page options, `prerender` also applies to `+server.js` files. These files are _not_ affected from layouts, but will inherit default values from the pages that fetch data from them, if any. For example if a `+page.js` contains this `load` function...
Unlike the other page options, `prerender` also applies to `+server.js` files. These files are _not_ affected by layouts, but will inherit default values from the pages that fetch data from them, if any. For example if a `+page.js` contains this `load` function...

```js
/// file: +page.js
Expand Down
8 changes: 5 additions & 3 deletions documentation/docs/20-core-concepts/50-state-management.md
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ Updating the context-based store value in deeper-level pages or components will

If you're not using SSR (and can guarantee that you won't need to use SSR in future) then you can safely keep state in a shared module, without using the context API.

## Component state is preserved
## Component and page state is preserved

When you navigate around your application, SvelteKit reuses existing layout and page components. For example, if you have a route like this...

Expand All @@ -140,7 +140,7 @@ When you navigate around your application, SvelteKit reuses existing layout and
<div>{@html data.content}</div>
```

...then navigating from `/blog/my-short-post` to `/blog/my-long-post` won't cause the component to be destroyed and recreated. The `data` prop (and by extension `data.title` and `data.content`) will change, but because the code isn't rerunning, `estimatedReadingTime` won't be recalculated.
...then navigating from `/blog/my-short-post` to `/blog/my-long-post` won't cause the layout, page and any other components within to be destroyed and recreated. Instead the `data` prop (and by extension `data.title` and `data.content`) will update (as it would with any other Svelte component) and, because the code isn't rerunning, lifecycle methods like `onMount` and `onDestroy` won't rerun and `estimatedReadingTime` won't be recalculated.

Instead, we need to make the value [_reactive_](https://learn.svelte.dev/tutorial/reactive-assignments):

Expand All @@ -155,7 +155,9 @@ Instead, we need to make the value [_reactive_](https://learn.svelte.dev/tutoria
</script>
```

Reusing components like this means that things like sidebar scroll state are preserved, and you can easily animate between changing values. However, if you do need to completely destroy and remount a component on navigation, you can use this pattern:
> If your code in `onMount` and `onDestroy` has to run again after navigation you can use [afterNavigate](modules#$app-navigation-afternavigate) and [beforeNavigate](modules#$app-navigation-beforenavigate) respectively.
Reusing components like this means that things like sidebar scroll state are preserved, and you can easily animate between changing values. In the case that you do need to completely destroy and remount a component on navigation, you can use this pattern:

```svelte
{#key $page.url.pathname}
Expand Down
6 changes: 3 additions & 3 deletions documentation/docs/30-advanced/30-link-options.md
Original file line number Diff line number Diff line change
Expand Up @@ -122,8 +122,8 @@ To disable any of these options inside an element where they have been enabled,
</div>
```

To apply an attribute to an element conditionally, do this (`"true"` and `"false"` are both accepted values):
To apply an attribute to an element conditionally, do this:

```html
<div data-sveltekit-reload={shouldReload}>
```svelte
<div data-sveltekit-preload-data={condition ? 'hover' : false}>
```
2 changes: 1 addition & 1 deletion documentation/docs/60-appendix/10-faq.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ If you are still encountering issues we recommend searching both [the Vite issue
## How do I use the view transitions API with SvelteKit?
While SvelteKit does not have any specific integration with [view transitions](https://developer.chrome.com/docs/web-platform/view-transitions/), you can call `document.startViewTransition` in [`onNavigate`](/docs/modules#$app-navigation-onnavigate) to trigger a view transition on every navigation.
While SvelteKit does not have any specific integration with [view transitions](https://developer.chrome.com/docs/web-platform/view-transitions/), you can call `document.startViewTransition` in [`onNavigate`](/docs/modules#$app-navigation-onnavigate) to trigger a view transition on every client-side navigation.
```js
// @errors: 2339 2810
Expand Down
8 changes: 8 additions & 0 deletions packages/kit/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
# @sveltejs/kit

## 1.29.1

### Patch Changes

- fix: correctly return 415 when unexpected content types are submitted to actions ([#11255](https://github.com/sveltejs/kit/pull/11255))

- chore: deprecate `preloadCode` calls with multiple arguments ([#11266](https://github.com/sveltejs/kit/pull/11266))

## 1.29.0

### Minor Changes
Expand Down
2 changes: 1 addition & 1 deletion packages/kit/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@sveltejs/kit",
"version": "1.29.0",
"version": "1.29.1",
"description": "The fastest way to build Svelte apps",
"repository": {
"type": "git",
Expand Down
12 changes: 2 additions & 10 deletions packages/kit/src/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,6 @@ export const SVELTE_KIT_ASSETS = '/_svelte_kit_assets';

export const GENERATED_COMMENT = '// this file is generated — do not edit it\n';

export const ENDPOINT_METHODS = new Set([
'GET',
'POST',
'PUT',
'PATCH',
'DELETE',
'OPTIONS',
'HEAD'
]);
export const ENDPOINT_METHODS = ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS', 'HEAD'];

export const PAGE_METHODS = new Set(['GET', 'POST', 'HEAD']);
export const PAGE_METHODS = ['GET', 'POST', 'HEAD'];
14 changes: 7 additions & 7 deletions packages/kit/src/core/postbuild/analyse.js
Original file line number Diff line number Diff line change
Expand Up @@ -124,13 +124,13 @@ function analyse_endpoint(route, mod) {
/** @type {Array<import('types').HttpMethod | '*'>} */
const methods = [];

Object.values(mod).forEach((/** @type {import('types').HttpMethod} */ method) => {
if (mod[method] && ENDPOINT_METHODS.has(method)) {
methods.push(method);
} else if (mod.fallback) {
methods.push('*');
}
});
for (const method of /** @type {import('types').HttpMethod[]} */ (ENDPOINT_METHODS)) {
if (mod[method]) methods.push(method);
}

if (mod.fallback) {
methods.push('*');
}

return {
config: mod.config,
Expand Down
2 changes: 1 addition & 1 deletion packages/kit/src/runtime/app/navigation.js
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ export const preloadCode = /* @__PURE__ */ client_method('preload_code');
export const beforeNavigate = /* @__PURE__ */ client_method('before_navigate');

/**
* A lifecycle function that runs the supplied `callback` immediately before we navigate to a new URL.
* A lifecycle function that runs the supplied `callback` immediately before we navigate to a new URL except during full-page navigations.
*
* If you return a `Promise`, SvelteKit will wait for it to resolve before completing the navigation. This allows you to — for example — use `document.startViewTransition`. Avoid promises that are slow to resolve, since navigation will appear stalled to the user.
*
Expand Down
19 changes: 11 additions & 8 deletions packages/kit/src/runtime/client/client.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ import { base } from '__sveltekit/paths';
import * as devalue from 'devalue';
import { compact } from '../../utils/array.js';
import { validate_page_exports } from '../../utils/exports.js';
import { HttpError, Redirect } from '../control.js';
import { HttpError, Redirect, NotFound } from '../control.js';
import { INVALIDATED_PARAM, TRAILING_SLASH_PARAM, validate_depends } from '../shared.js';
import { INDEX_KEY, PRELOAD_PRIORITIES, SCROLL_KEY, SNAPSHOT_KEY } from './constants.js';
import { stores } from './singletons.js';
Expand Down Expand Up @@ -545,10 +545,10 @@ export function create_client(app, target) {
typeof data !== 'object'
? `a ${typeof data}`
: data instanceof Response
? 'a Response object'
: Array.isArray(data)
? 'an array'
: 'a non-plain object'
? 'a Response object'
: Array.isArray(data)
? 'an array'
: 'a non-plain object'
}, but must return a plain object at the top level (i.e. \`return {...}\`)`
);
}
Expand Down Expand Up @@ -1374,7 +1374,10 @@ export function create_client(app, target) {

return (
app.hooks.handleError({ error, event }) ??
/** @type {any} */ ({ message: event.route.id != null ? 'Internal Error' : 'Not Found' })
/** @type {any} */ ({
message:
event.route.id === null && error instanceof NotFound ? 'Not Found' : 'Internal Error'
})
);
}

Expand Down Expand Up @@ -1741,8 +1744,8 @@ export function create_client(app, target) {
const scroll = scroll_positions[event.state[INDEX_KEY]];
const url = new URL(location.href);

// if the only change is the hash, we don't need to do anything...
if (current.url.href.split('#')[0] === location.href.split('#')[0]) {
// if the only change is the hash, we don't need to do anything (see https://github.com/sveltejs/kit/pull/10636 for why we need to do `url?.`)...
if (current.url?.href.split('#')[0] === location.href.split('#')[0]) {
// ...except update our internal URL tracking and handle scroll
update_url(url);
scroll_positions[current_history_index] = scroll_state();
Expand Down
3 changes: 2 additions & 1 deletion packages/kit/src/runtime/client/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,6 @@ export const PRELOAD_PRIORITIES = /** @type {const} */ ({
hover: 2,
viewport: 3,
eager: 4,
off: -1
off: -1,
false: -1
});
4 changes: 2 additions & 2 deletions packages/kit/src/runtime/client/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,8 @@ const warned = new WeakSet();
/** @typedef {keyof typeof valid_link_options} LinkOptionName */

const valid_link_options = /** @type {const} */ ({
'preload-code': ['', 'off', 'tap', 'hover', 'viewport', 'eager'],
'preload-data': ['', 'off', 'tap', 'hover'],
'preload-code': ['', 'off', 'false', 'tap', 'hover', 'viewport', 'eager'],
'preload-data': ['', 'off', 'false', 'tap', 'hover'],
keepfocus: ['', 'true', 'off', 'false'],
noscroll: ['', 'true', 'off', 'false'],
reload: ['', 'true', 'off', 'false'],
Expand Down
12 changes: 12 additions & 0 deletions packages/kit/src/runtime/control.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,18 @@ export class Redirect {
}
}

export class NotFound extends Error {
/**
* @param {string} pathname
*/
constructor(pathname) {
super();

this.status = 404;
this.message = `Not found: ${pathname}`;
}
}

/**
* @template {Record<string, unknown> | undefined} [T=undefined]
*/
Expand Down
2 changes: 1 addition & 1 deletion packages/kit/src/runtime/server/endpoint.js
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ export function is_endpoint_request(event) {
const { method, headers } = event.request;

// These methods exist exclusively for endpoints
if (ENDPOINT_METHODS.has(method) && !PAGE_METHODS.has(method)) {
if (ENDPOINT_METHODS.includes(method) && !PAGE_METHODS.includes(method)) {
return true;
}

Expand Down
8 changes: 4 additions & 4 deletions packages/kit/src/runtime/server/respond.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import { exec } from '../../utils/routing.js';
import { redirect_json_response, render_data } from './data/index.js';
import { add_cookies_to_headers, get_cookies } from './cookie.js';
import { create_fetch } from './fetch.js';
import { HttpError, Redirect } from '../control.js';
import { HttpError, Redirect, NotFound } from '../control.js';
import {
validate_layout_exports,
validate_layout_server_exports,
Expand Down Expand Up @@ -344,8 +344,8 @@ export async function respond(request, options, manifest, state) {
const response = is_data_request
? redirect_json_response(e)
: route?.page && is_action_json_request(event)
? action_json_redirect(e)
: redirect_response(e.status, e.location);
? action_json_redirect(e)
: redirect_response(e.status, e.location);
add_cookies_to_headers(response.headers, Object.values(cookies_to_add));
return response;
}
Expand Down Expand Up @@ -483,7 +483,7 @@ export async function respond(request, options, manifest, state) {
manifest,
state,
status: 404,
error: new Error(`Not found: ${event.url.pathname}`),
error: new NotFound(event.url.pathname),
resolve_opts
});
}
Expand Down
22 changes: 11 additions & 11 deletions packages/kit/src/runtime/server/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { DEV } from 'esm-env';
import { json, text } from '../../exports/index.js';
import { coalesce_to_error } from '../../utils/error.js';
import { negotiate } from '../../utils/http.js';
import { HttpError } from '../control.js';
import { HttpError, NotFound } from '../control.js';
import { fix_stack_trace } from '../shared-server.js';
import { ENDPOINT_METHODS } from '../../constants.js';

Expand Down Expand Up @@ -35,7 +35,7 @@ export function method_not_allowed(mod, method) {

/** @param {Partial<Record<import('types').HttpMethod, any>>} mod */
export function allowed_methods(mod) {
const allowed = Array.from(ENDPOINT_METHODS).filter((method) => method in mod);
const allowed = ENDPOINT_METHODS.filter((method) => method in mod);

if ('GET' in mod || 'HEAD' in mod) allowed.push('HEAD');

Expand Down Expand Up @@ -97,17 +97,17 @@ export async function handle_fatal_error(event, options, error) {
export async function handle_error_and_jsonify(event, options, error) {
if (error instanceof HttpError) {
return error.body;
} else {
if (__SVELTEKIT_DEV__ && typeof error == 'object') {
fix_stack_trace(error);
}
}

return (
(await options.hooks.handleError({ error, event })) ?? {
message: event.route.id != null ? 'Internal Error' : 'Not Found'
}
);
if (__SVELTEKIT_DEV__ && typeof error == 'object') {
fix_stack_trace(error);
}

return (
(await options.hooks.handleError({ error, event })) ?? {
message: event.route.id === null && error instanceof NotFound ? 'Not Found' : 'Internal Error'
}
);
}

/**
Expand Down
2 changes: 1 addition & 1 deletion packages/kit/src/version.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// generated during release, do not modify

/** @type {string} */
export const VERSION = '1.29.0';
export const VERSION = '1.29.1';
2 changes: 1 addition & 1 deletion packages/kit/types/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1994,7 +1994,7 @@ declare module '$app/navigation' {
* */
export const beforeNavigate: (callback: (navigation: import('@sveltejs/kit').BeforeNavigate) => void) => void;
/**
* A lifecycle function that runs the supplied `callback` immediately before we navigate to a new URL.
* A lifecycle function that runs the supplied `callback` immediately before we navigate to a new URL except during full-page navigations.
*
* If you return a `Promise`, SvelteKit will wait for it to resolve before completing the navigation. This allows you to — for example — use `document.startViewTransition`. Avoid promises that are slow to resolve, since navigation will appear stalled to the user.
*
Expand Down

0 comments on commit 2543938

Please sign in to comment.