diff --git a/.changeset/calm-brooms-burn.md b/.changeset/calm-brooms-burn.md
new file mode 100644
index 000000000000..d9ce3a8c8373
--- /dev/null
+++ b/.changeset/calm-brooms-burn.md
@@ -0,0 +1,5 @@
+---
+'@sveltejs/kit': major
+---
+
+breaking: remove top-level promise awaiting
diff --git a/.changeset/calm-pugs-applaud.md b/.changeset/calm-pugs-applaud.md
new file mode 100644
index 000000000000..556047ed1e63
--- /dev/null
+++ b/.changeset/calm-pugs-applaud.md
@@ -0,0 +1,5 @@
+---
+'@sveltejs/kit': major
+---
+
+breaking: prevent use of dynamic env vars during prerendering, serve env vars dynamically
diff --git a/.changeset/chilled-fireants-learn.md b/.changeset/chilled-fireants-learn.md
new file mode 100644
index 000000000000..3796f57910ba
--- /dev/null
+++ b/.changeset/chilled-fireants-learn.md
@@ -0,0 +1,5 @@
+---
+'@sveltejs/kit': major
+---
+
+breaking: remove deprecated `use:enhance` callback values
diff --git a/.changeset/clean-cars-kiss.md b/.changeset/clean-cars-kiss.md
new file mode 100644
index 000000000000..6ce1b9e8db30
--- /dev/null
+++ b/.changeset/clean-cars-kiss.md
@@ -0,0 +1,5 @@
+---
+'@sveltejs/kit': major
+---
+
+breaking: turn `error` and `redirect` into commands
diff --git a/.changeset/dirty-phones-report.md b/.changeset/dirty-phones-report.md
new file mode 100644
index 000000000000..40b13d4c0e5c
--- /dev/null
+++ b/.changeset/dirty-phones-report.md
@@ -0,0 +1,5 @@
+---
+'@sveltejs/kit': major
+---
+
+breaking: the type for `depends` now requires a `:` as part of the string
diff --git a/.changeset/dull-eyes-check.md b/.changeset/dull-eyes-check.md
new file mode 100644
index 000000000000..16b4a181f123
--- /dev/null
+++ b/.changeset/dull-eyes-check.md
@@ -0,0 +1,5 @@
+---
+"@sveltejs/kit": major
+---
+
+breaking: remove baseUrl fallback from generated tsconfig
diff --git a/.changeset/eight-pens-help.md b/.changeset/eight-pens-help.md
new file mode 100644
index 000000000000..be20211b5210
--- /dev/null
+++ b/.changeset/eight-pens-help.md
@@ -0,0 +1,5 @@
+---
+'@sveltejs/kit': major
+---
+
+breaking: fail if route with +page and +server is marked prerenderable
diff --git a/.changeset/eighty-scissors-kick.md b/.changeset/eighty-scissors-kick.md
new file mode 100644
index 000000000000..4f82777eddd2
--- /dev/null
+++ b/.changeset/eighty-scissors-kick.md
@@ -0,0 +1,5 @@
+---
+'@sveltejs/kit': major
+---
+
+breaking: remove `resolvePath` in favour of `resolveRoute` from `$app/paths`
diff --git a/.changeset/fast-dolls-clean.md b/.changeset/fast-dolls-clean.md
new file mode 100644
index 000000000000..824824b64d61
--- /dev/null
+++ b/.changeset/fast-dolls-clean.md
@@ -0,0 +1,6 @@
+---
+'@sveltejs/adapter-vercel': major
+'@sveltejs/adapter-node': major
+---
+
+breaking: require SvelteKit 2 peer dependency
diff --git a/.changeset/fast-eyes-deny.md b/.changeset/fast-eyes-deny.md
new file mode 100644
index 000000000000..bfc7e2051d0e
--- /dev/null
+++ b/.changeset/fast-eyes-deny.md
@@ -0,0 +1,5 @@
+---
+"@sveltejs/kit": minor
+---
+
+feat: add untrack to load
diff --git a/.changeset/flat-clocks-listen.md b/.changeset/flat-clocks-listen.md
new file mode 100644
index 000000000000..c7fe30360fa7
--- /dev/null
+++ b/.changeset/flat-clocks-listen.md
@@ -0,0 +1,5 @@
+---
+'@sveltejs/adapter-vercel': minor
+---
+
+feat: expose vercel image optimization config in adapter config
diff --git a/.changeset/gentle-guests-clean.md b/.changeset/gentle-guests-clean.md
new file mode 100644
index 000000000000..d103f1d81e2b
--- /dev/null
+++ b/.changeset/gentle-guests-clean.md
@@ -0,0 +1,5 @@
+---
+'@sveltejs/kit': major
+---
+
+breaking: drop support for Svelte 3
diff --git a/.changeset/good-jars-relax.md b/.changeset/good-jars-relax.md
new file mode 100644
index 000000000000..ea6baeab4b7b
--- /dev/null
+++ b/.changeset/good-jars-relax.md
@@ -0,0 +1,5 @@
+---
+'@sveltejs/kit': patch
+---
+
+fix: prerender optional parameters as empty when `entries` contains `'*'`
diff --git a/.changeset/grumpy-sheep-fix.md b/.changeset/grumpy-sheep-fix.md
new file mode 100644
index 000000000000..4264d51be24e
--- /dev/null
+++ b/.changeset/grumpy-sheep-fix.md
@@ -0,0 +1,5 @@
+---
+'@sveltejs/kit': major
+---
+
+breaking: require Vite 5.0.3+
diff --git a/.changeset/happy-beds-run.md b/.changeset/happy-beds-run.md
new file mode 100644
index 000000000000..132a06443a5f
--- /dev/null
+++ b/.changeset/happy-beds-run.md
@@ -0,0 +1,5 @@
+---
+'@sveltejs/kit': major
+---
+
+breaking: generate `__data.json` files as sibling to `.html` files
diff --git a/.changeset/hot-pumas-pump.md b/.changeset/hot-pumas-pump.md
new file mode 100644
index 000000000000..8bd1f22e250d
--- /dev/null
+++ b/.changeset/hot-pumas-pump.md
@@ -0,0 +1,5 @@
+---
+'create-svelte': minor
+---
+
+feat: update vitest to 1.0
diff --git a/.changeset/hungry-kings-collect.md b/.changeset/hungry-kings-collect.md
new file mode 100644
index 000000000000..7289023b7bc5
--- /dev/null
+++ b/.changeset/hungry-kings-collect.md
@@ -0,0 +1,12 @@
+---
+'@sveltejs/adapter-cloudflare-workers': minor
+'@sveltejs/adapter-cloudflare': minor
+'@sveltejs/adapter-netlify': minor
+'@sveltejs/adapter-static': minor
+'@sveltejs/adapter-vercel': minor
+'@sveltejs/adapter-auto': minor
+'@sveltejs/adapter-node': minor
+'@sveltejs/amp': minor
+---
+
+feat: allow SvelteKit 2 as peer dependency
diff --git a/.changeset/hungry-moles-turn.md b/.changeset/hungry-moles-turn.md
new file mode 100644
index 000000000000..7f537b5501d8
--- /dev/null
+++ b/.changeset/hungry-moles-turn.md
@@ -0,0 +1,5 @@
+---
+'@sveltejs/adapter-cloudflare-workers': patch
+---
+
+fix: declare the adapter plugin options as optional
diff --git a/.changeset/light-moons-dress.md b/.changeset/light-moons-dress.md
new file mode 100644
index 000000000000..85afe61bcaaa
--- /dev/null
+++ b/.changeset/light-moons-dress.md
@@ -0,0 +1,5 @@
+---
+"@sveltejs/kit": minor
+---
+
+feat: implement shallow routing
diff --git a/.changeset/loud-parrots-flow.md b/.changeset/loud-parrots-flow.md
new file mode 100644
index 000000000000..fca577998c6e
--- /dev/null
+++ b/.changeset/loud-parrots-flow.md
@@ -0,0 +1,5 @@
+---
+"svelte-migrate": minor
+---
+
+feat: add sveltekit v2 migration
diff --git a/.changeset/loud-plants-move.md b/.changeset/loud-plants-move.md
new file mode 100644
index 000000000000..c148e4086394
--- /dev/null
+++ b/.changeset/loud-plants-move.md
@@ -0,0 +1,5 @@
+---
+'@sveltejs/kit': major
+---
+
+breaking: fail if +page and +server have mismatched config
diff --git a/.changeset/mean-moose-smell.md b/.changeset/mean-moose-smell.md
new file mode 100644
index 000000000000..17d98b2cdbae
--- /dev/null
+++ b/.changeset/mean-moose-smell.md
@@ -0,0 +1,5 @@
+---
+'@sveltejs/kit': major
+---
+
+breaking: error if form without multipart/form-data enctype contains a file input
diff --git a/.changeset/mean-trees-notice.md b/.changeset/mean-trees-notice.md
new file mode 100644
index 000000000000..397ef057eae6
--- /dev/null
+++ b/.changeset/mean-trees-notice.md
@@ -0,0 +1,5 @@
+---
+'@sveltejs/kit': patch
+---
+
+fix: resolve route config correctly
diff --git a/.changeset/neat-hats-shop.md b/.changeset/neat-hats-shop.md
new file mode 100644
index 000000000000..251c39101243
--- /dev/null
+++ b/.changeset/neat-hats-shop.md
@@ -0,0 +1,5 @@
+---
+'@sveltejs/kit': patch
+---
+
+fix: import Svelte types from svelte/compiler
diff --git a/.changeset/nervous-bananas-search.md b/.changeset/nervous-bananas-search.md
new file mode 100644
index 000000000000..badfdec6e142
--- /dev/null
+++ b/.changeset/nervous-bananas-search.md
@@ -0,0 +1,5 @@
+---
+'@sveltejs/kit': major
+---
+
+breaking: require paths pass to preloadCode to be prefixed with basepath
diff --git a/.changeset/nervous-cups-argue.md b/.changeset/nervous-cups-argue.md
new file mode 100644
index 000000000000..8c5d30d77af4
--- /dev/null
+++ b/.changeset/nervous-cups-argue.md
@@ -0,0 +1,5 @@
+---
+'@sveltejs/adapter-node': minor
+---
+
+chore: upgrade rollup
diff --git a/.changeset/nervous-snails-chew.md b/.changeset/nervous-snails-chew.md
new file mode 100644
index 000000000000..8822f934a577
--- /dev/null
+++ b/.changeset/nervous-snails-chew.md
@@ -0,0 +1,5 @@
+---
+'@sveltejs/kit': major
+---
+
+breaking: `@sveltejs/vite-plugin-svelte` is now a peer dependency and will need to be installed in each project using SvelteKit
diff --git a/.changeset/nine-moles-buy.md b/.changeset/nine-moles-buy.md
new file mode 100644
index 000000000000..73cc6d40b821
--- /dev/null
+++ b/.changeset/nine-moles-buy.md
@@ -0,0 +1,5 @@
+---
+'@sveltejs/kit': major
+---
+
+breaking: stop re-exporting vitePreprocess
diff --git a/.changeset/old-rockets-film.md b/.changeset/old-rockets-film.md
new file mode 100644
index 000000000000..7f6d7e4f9a95
--- /dev/null
+++ b/.changeset/old-rockets-film.md
@@ -0,0 +1,5 @@
+---
+'@sveltejs/kit': patch
+---
+
+fix: reset invalid resources after a successful invalidation
diff --git a/.changeset/pink-lobsters-protect.md b/.changeset/pink-lobsters-protect.md
new file mode 100644
index 000000000000..2a92abb5b038
--- /dev/null
+++ b/.changeset/pink-lobsters-protect.md
@@ -0,0 +1,5 @@
+---
+'@sveltejs/adapter-static': major
+---
+
+breaking: update SvelteKit peer dependency to version 2
diff --git a/.changeset/poor-parrots-own.md b/.changeset/poor-parrots-own.md
new file mode 100644
index 000000000000..5e9116e27fd2
--- /dev/null
+++ b/.changeset/poor-parrots-own.md
@@ -0,0 +1,5 @@
+---
+'@sveltejs/kit': major
+---
+
+breaking: require path option when setting/deleting/serializing cookies
diff --git a/.changeset/real-pets-fix.md b/.changeset/real-pets-fix.md
new file mode 100644
index 000000000000..336da95529b1
--- /dev/null
+++ b/.changeset/real-pets-fix.md
@@ -0,0 +1,5 @@
+---
+"@sveltejs/kit": major
+---
+
+breaking: tighten up error handling
diff --git a/.changeset/rotten-penguins-hope.md b/.changeset/rotten-penguins-hope.md
new file mode 100644
index 000000000000..e350d1710d38
--- /dev/null
+++ b/.changeset/rotten-penguins-hope.md
@@ -0,0 +1,5 @@
+---
+'@sveltejs/adapter-node': major
+---
+
+breaking: remove polyfill option. fetch APIs will now always come from the platform being used. File and crypto APIs will be polyfilled if not available
diff --git a/.changeset/serious-months-happen.md b/.changeset/serious-months-happen.md
new file mode 100644
index 000000000000..861d42d2959f
--- /dev/null
+++ b/.changeset/serious-months-happen.md
@@ -0,0 +1,5 @@
+---
+"@sveltejs/kit": major
+---
+
+breaking: remove state option from goto in favor of shallow routing
diff --git a/.changeset/shy-trees-carry.md b/.changeset/shy-trees-carry.md
new file mode 100644
index 000000000000..c00143651e4b
--- /dev/null
+++ b/.changeset/shy-trees-carry.md
@@ -0,0 +1,5 @@
+---
+'create-svelte': major
+---
+
+feat: create projects with SvelteKit 2
diff --git a/.changeset/silent-donuts-push.md b/.changeset/silent-donuts-push.md
new file mode 100644
index 000000000000..a10b047c9bbb
--- /dev/null
+++ b/.changeset/silent-donuts-push.md
@@ -0,0 +1,8 @@
+---
+'@sveltejs/adapter-cloudflare-workers': minor
+'@sveltejs/adapter-cloudflare': minor
+'@sveltejs/adapter-netlify': minor
+'@sveltejs/adapter-vercel': minor
+---
+
+chore: upgrade esbuild
diff --git a/.changeset/silent-games-taste.md b/.changeset/silent-games-taste.md
new file mode 100644
index 000000000000..fcd66101a965
--- /dev/null
+++ b/.changeset/silent-games-taste.md
@@ -0,0 +1,5 @@
+---
+'@sveltejs/kit': major
+---
+
+breaking: disallow external navigation with `goto`
diff --git a/.changeset/silent-mayflies-burn.md b/.changeset/silent-mayflies-burn.md
new file mode 100644
index 000000000000..7bd6f712f5ed
--- /dev/null
+++ b/.changeset/silent-mayflies-burn.md
@@ -0,0 +1,5 @@
+---
+'@sveltejs/kit': major
+---
+
+breaking: upgrade to TypeScript 5. Default `moduleResolution` to `bundler` in user projects to be permissive in consuming and `NodeNext` when running `package` to be strict in distributing
diff --git a/.changeset/smart-buttons-return.md b/.changeset/smart-buttons-return.md
new file mode 100644
index 000000000000..fb4bfc1dd054
--- /dev/null
+++ b/.changeset/smart-buttons-return.md
@@ -0,0 +1,5 @@
+---
+'@sveltejs/kit': minor
+---
+
+feat: provide SvelteKit html typings
diff --git a/.changeset/smart-crabs-lick.md b/.changeset/smart-crabs-lick.md
new file mode 100644
index 000000000000..05f603cfc359
--- /dev/null
+++ b/.changeset/smart-crabs-lick.md
@@ -0,0 +1,5 @@
+---
+'@sveltejs/kit': minor
+---
+
+feat: redact internal stack trace when reporting config errors
diff --git a/.changeset/strange-eyes-sort.md b/.changeset/strange-eyes-sort.md
new file mode 100644
index 000000000000..70de048ab975
--- /dev/null
+++ b/.changeset/strange-eyes-sort.md
@@ -0,0 +1,5 @@
+---
+"@sveltejs/kit": minor
+---
+
+feat: allow for fine grained invalidation of search params
diff --git a/.changeset/swift-clocks-kneel.md b/.changeset/swift-clocks-kneel.md
new file mode 100644
index 000000000000..493107153a90
--- /dev/null
+++ b/.changeset/swift-clocks-kneel.md
@@ -0,0 +1,5 @@
+---
+'@sveltejs/kit': patch
+---
+
+fix: Adjust fail method and ActionFailure type
diff --git a/.changeset/tall-suns-protect.md b/.changeset/tall-suns-protect.md
new file mode 100644
index 000000000000..21f8fa055fa0
--- /dev/null
+++ b/.changeset/tall-suns-protect.md
@@ -0,0 +1,5 @@
+---
+'@sveltejs/kit': major
+---
+
+breaking: undefined is no longer a valid value for paths.relative
diff --git a/.changeset/thick-suits-help.md b/.changeset/thick-suits-help.md
new file mode 100644
index 000000000000..acb67c634a71
--- /dev/null
+++ b/.changeset/thick-suits-help.md
@@ -0,0 +1,6 @@
+---
+'@sveltejs/kit': major
+'create-svelte': major
+---
+
+breaking: require Node 18.13 or newer
diff --git a/.changeset/twelve-paws-remember.md b/.changeset/twelve-paws-remember.md
new file mode 100644
index 000000000000..1d484047af0c
--- /dev/null
+++ b/.changeset/twelve-paws-remember.md
@@ -0,0 +1,5 @@
+---
+'@sveltejs/kit': major
+---
+
+breaking: fix path resolution
diff --git a/.changeset/twenty-birds-eat.md b/.changeset/twenty-birds-eat.md
new file mode 100644
index 000000000000..c999f7c49845
--- /dev/null
+++ b/.changeset/twenty-birds-eat.md
@@ -0,0 +1,5 @@
+---
+'@sveltejs/kit': major
+---
+
+breaking: remove `dangerZone.trackServerFetches`
diff --git a/.changeset/twenty-ducks-reply.md b/.changeset/twenty-ducks-reply.md
new file mode 100644
index 000000000000..9ddd4abb78a0
--- /dev/null
+++ b/.changeset/twenty-ducks-reply.md
@@ -0,0 +1,5 @@
+---
+'@sveltejs/kit': patch
+---
+
+chore(deps): upgrade cookies dependency
diff --git a/.changeset/unlucky-crabs-wonder.md b/.changeset/unlucky-crabs-wonder.md
new file mode 100644
index 000000000000..1ac22c413258
--- /dev/null
+++ b/.changeset/unlucky-crabs-wonder.md
@@ -0,0 +1,5 @@
+---
+'@sveltejs/enhanced-img': patch
+---
+
+chore: upgrade vite-imagetools
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 7cd2ca41db19..c38ce191773a 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -26,11 +26,11 @@ jobs:
- uses: pnpm/action-setup@v2.4.0
- uses: actions/setup-node@v4
with:
- node-version: '16.x'
+ node-version: '18.x'
cache: pnpm
- run: pnpm install --frozen-lockfile
- run: pnpm run lint
- - run: cd packages/kit && pnpm prepublishOnly && { [ "`git status --porcelain=v1`" == "" ] || (echo "Generated types have changed — please run prepublishOnly locally and commit the changes after you have reviewed them" && exit 1); }
+ - run: cd packages/kit && pnpm prepublishOnly && { [ "`git status --porcelain=v1`" == "" ] || (echo "Generated types have changed — please run prepublishOnly locally and commit the changes after you have reviewed them"; git diff; exit 1); }
- run: pnpm run check
Tests:
runs-on: ${{ matrix.os }}
@@ -39,9 +39,6 @@ jobs:
fail-fast: false
matrix:
include:
- - node-version: 16
- os: ubuntu-latest
- e2e-browser: 'chromium'
- node-version: 18
os: ubuntu-latest
e2e-browser: 'chromium'
@@ -79,27 +76,27 @@ jobs:
fail-fast: false
matrix:
include:
- - node-version: 16
+ - node-version: 18
os: windows-2019 # slowness reported on newer versions https://github.com/actions/runner-images/issues/5166
e2e-browser: 'chromium'
mode: 'dev'
- - node-version: 16
+ - node-version: 18
os: ubuntu-latest
e2e-browser: 'firefox'
mode: 'dev'
- - node-version: 16
+ - node-version: 18
os: macOS-latest
e2e-browser: 'webkit'
mode: 'dev'
- - node-version: 16
+ - node-version: 18
os: windows-2019 # slowness reported on newer versions https://github.com/actions/runner-images/issues/5166
e2e-browser: 'chromium'
mode: 'build'
- - node-version: 16
+ - node-version: 18
os: ubuntu-latest
e2e-browser: 'firefox'
mode: 'build'
- - node-version: 16
+ - node-version: 18
os: macOS-latest
e2e-browser: 'webkit'
mode: 'build'
@@ -134,7 +131,7 @@ jobs:
- uses: pnpm/action-setup@v2.4.0
- uses: actions/setup-node@v4
with:
- node-version: 16
+ node-version: 18
cache: pnpm
- run: pnpm install --frozen-lockfile
- run: cd packages/kit && pnpm prepublishOnly
diff --git a/documentation/docs/10-getting-started/10-introduction.md b/documentation/docs/10-getting-started/10-introduction.md
index 189fcb3bcc7d..fb113d7578fb 100644
--- a/documentation/docs/10-getting-started/10-introduction.md
+++ b/documentation/docs/10-getting-started/10-introduction.md
@@ -22,6 +22,6 @@ In short, Svelte is a way of writing user interface components — like a naviga
Svelte renders UI components. You can compose these components and render an entire page with just Svelte, but you need more than just Svelte to write an entire app.
-SvelteKit helps you build web apps while following modern best practices and providing solutions to common development challenges. It offers everything from basic functionalities — like a [router](glossary#routing) that updates your UI when a link is clicked — to more advanced capabilities. Its extensive list of features includes [build optimizations](https://vitejs.dev/guide/features.html#build-optimizations) to load only the minimal required code; [offline support](service-workers); [preloading](link-options#data-sveltekit-preload-data) pages before user navigation; [configurable rendering](page-options) to handle different parts of your app on the server via [SSR](glossary#ssr), in the browser through [client-side rendering](glossary#csr), or at build-time with [prerendering](glossary#prerendering); and much more. Building an app with all the modern best practices is fiendishly complicated, but SvelteKit does all the boring stuff for you so that you can get on with the creative part.
+SvelteKit helps you build web apps while following modern best practices and providing solutions to common development challenges. It offers everything from basic functionalities — like a [router](glossary#routing) that updates your UI when a link is clicked — to more advanced capabilities. Its extensive list of features includes [build optimizations](https://vitejs.dev/guide/features.html#build-optimizations) to load only the minimal required code; [offline support](service-workers); [preloading](link-options#data-sveltekit-preload-data) pages before user navigation; [configurable rendering](page-options) to handle different parts of your app on the server via [SSR](glossary#ssr), in the browser through [client-side rendering](glossary#csr), or at build-time with [prerendering](glossary#prerendering); [image optimization](images); and much more. Building an app with all the modern best practices is fiendishly complicated, but SvelteKit does all the boring stuff for you so that you can get on with the creative part.
It reflects changes to your code in the browser instantly to provide a lightning-fast and feature-rich development experience by leveraging [Vite](https://vitejs.dev/) with a [Svelte plugin](https://github.com/sveltejs/vite-plugin-svelte) to do [Hot Module Replacement (HMR)](https://github.com/sveltejs/vite-plugin-svelte/blob/main/docs/config.md#hot).
diff --git a/documentation/docs/20-core-concepts/10-routing.md b/documentation/docs/20-core-concepts/10-routing.md
index 125b1d5282e2..d21be05291af 100644
--- a/documentation/docs/20-core-concepts/10-routing.md
+++ b/documentation/docs/20-core-concepts/10-routing.md
@@ -61,7 +61,7 @@ export function load({ params }) {
};
}
- throw error(404, 'Not found');
+ error(404, 'Not found');
}
```
@@ -104,7 +104,7 @@ export async function load({ params }) {
return post;
}
- throw error(404, 'Not found');
+ error(404, 'Not found');
}
```
@@ -264,7 +264,7 @@ export function GET({ url }) {
const d = max - min;
if (isNaN(d) || d < 0) {
- throw error(400, 'min and max must be numbers, and min must be less than max');
+ error(400, 'min and max must be numbers, and min must be less than max');
}
const random = min + Math.random() * d;
@@ -277,7 +277,7 @@ The first argument to `Response` can be a [`ReadableStream`](https://developer.m
You can use the [`error`](modules#sveltejs-kit-error), [`redirect`](modules#sveltejs-kit-redirect) and [`json`](modules#sveltejs-kit-json) methods from `@sveltejs/kit` for convenience (but you don't have to).
-If an error is thrown (either `throw error(...)` or an unexpected error), the response will be a JSON representation of the error or a fallback error page — which can be customised via `src/error.html` — depending on the `Accept` header. The [`+error.svelte`](#error) component will _not_ be rendered in this case. You can read more about error handling [here](errors).
+If an error is thrown (either `error(...)` or an unexpected error), the response will be a JSON representation of the error or a fallback error page — which can be customised via `src/error.html` — depending on the `Accept` header. The [`+error.svelte`](#error) component will _not_ be rendered in this case. You can read more about error handling [here](errors).
> When creating an `OPTIONS` handler, note that Vite will inject `Access-Control-Allow-Origin` and `Access-Control-Allow-Methods` headers — these will not be present in production unless you add them.
diff --git a/documentation/docs/20-core-concepts/20-load.md b/documentation/docs/20-core-concepts/20-load.md
index f867fe6d9787..7448cf521c5f 100644
--- a/documentation/docs/20-core-concepts/20-load.md
+++ b/documentation/docs/20-core-concepts/20-load.md
@@ -172,7 +172,7 @@ A `load` function is invoked at runtime, unless you [prerender](page-options#pre
### Input
-Both universal and server `load` functions have access to properties describing the request (`params`, `route` and `url`) and various functions (`fetch`, `setHeaders`, `parent` and `depends`). These are described in the following sections.
+Both universal and server `load` functions have access to properties describing the request (`params`, `route` and `url`) and various functions (`fetch`, `setHeaders`, `parent`, `depends` and `untrack`). These are described in the following sections.
Server `load` functions are called with a `ServerLoadEvent`, which inherits `clientAddress`, `cookies`, `locals`, `platform` and `request` from `RequestEvent`.
@@ -283,8 +283,6 @@ For example, if SvelteKit is serving my.domain.com:
Other cookies will not be passed when `credentials: 'include'` is set, because SvelteKit does not know which domain which cookie belongs to (the browser does not pass this information along), so it's not safe to forward any of them. Use the [handleFetch hook](hooks#server-hooks-handlefetch) to work around it.
-> When setting cookies, be aware of the `path` property. By default, the `path` of a cookie is the current pathname. If you for example set a cookie at page `admin/user`, the cookie will only be available within the `admin` pages by default. In most cases you likely want to set `path` to `'/'` to make the cookie available throughout your app.
-
## Headers
Both server and universal `load` functions have access to a `setHeaders` function that, when running on the server, can set headers for the response. (When running in the browser, `setHeaders` has no effect.) This is useful if you want the page to be cached, for example:
@@ -376,7 +374,7 @@ export async function load({ params, parent }) {
## Errors
-If an error is thrown during `load`, the nearest [`+error.svelte`](routing#error) will be rendered. For _expected_ errors, use the `error` helper from `@sveltejs/kit` to specify the HTTP status code and an optional message:
+If an error is thrown during `load`, the nearest [`+error.svelte`](routing#error) will be rendered. For [_expected_](/docs/errors#expected-errors) errors, use the `error` helper from `@sveltejs/kit` to specify the HTTP status code and an optional message:
```js
/// file: src/routes/admin/+layout.server.js
@@ -397,20 +395,24 @@ import { error } from '@sveltejs/kit';
/** @type {import('./$types').LayoutServerLoad} */
export function load({ locals }) {
if (!locals.user) {
- throw error(401, 'not logged in');
+ error(401, 'not logged in');
}
if (!locals.user.isAdmin) {
- throw error(403, 'not an admin');
+ error(403, 'not an admin');
}
}
```
-If an _unexpected_ error is thrown, SvelteKit will invoke [`handleError`](hooks#shared-hooks-handleerror) and treat it as a 500 Internal Error.
+Calling `error(...)` will throw an exception, making it easy to stop execution from inside helper functions.
+
+If an [_unexpected_](/docs/errors#unexpected-errors) error is thrown, SvelteKit will invoke [`handleError`](hooks#shared-hooks-handleerror) and treat it as a 500 Internal Error.
+
+> [In SvelteKit 1.x](migrating-to-sveltekit-2#redirect-and-error-are-no-longer-thrown-by-you) you had to `throw` the error yourself
## Redirects
-To redirect users, use the `redirect` helper from `@sveltejs/kit` to specify the location to which they should be redirected alongside a `3xx` status code.
+To redirect users, use the `redirect` helper from `@sveltejs/kit` to specify the location to which they should be redirected alongside a `3xx` status code. Like `error(...)`, calling `redirect(...)` will throw an exception, making it easy to stop execution from inside helper functions.
```js
/// file: src/routes/user/+layout.server.js
@@ -430,33 +432,40 @@ import { redirect } from '@sveltejs/kit';
/** @type {import('./$types').LayoutServerLoad} */
export function load({ locals }) {
if (!locals.user) {
- throw redirect(307, '/login');
+ redirect(307, '/login');
}
}
```
-> Don't use `throw redirect()` from within a try-catch block, as the redirect will immediately trigger the catch statement.
+> Don't use `redirect()` inside a `try {...}` block, as the redirect will immediately trigger the catch statement.
In the browser, you can also navigate programmatically outside of a `load` function using [`goto`](modules#$app-navigation-goto) from [`$app.navigation`](modules#$app-navigation).
+> [In SvelteKit 1.x](migrating-to-sveltekit-2#redirect-and-error-are-no-longer-thrown-by-you) you had to `throw` the `redirect` yourself
+
## Streaming with promises
-Promises at the _top level_ of the returned object will be awaited, making it easy to return multiple promises without creating a waterfall. When using a server `load`, _nested_ promises will be streamed to the browser as they resolve. This is useful if you have slow, non-essential data, since you can start rendering the page before all the data is available:
+When using a server `load`, promises will be streamed to the browser as they resolve. This is useful if you have slow, non-essential data, since you can start rendering the page before all the data is available:
```js
-/// file: src/routes/+page.server.js
+/// file: src/routes/blog/[slug]/+page.server.js
+// @filename: ambient.d.ts
+declare global {
+ const loadPost: (slug: string) => Promise<{ title: string, content: string }>;
+ const loadComments: (slug: string) => Promise<{ content: string }>;
+}
+
+export {};
+
+// @filename: index.js
+// ---cut---
/** @type {import('./$types').PageServerLoad} */
-export function load() {
+export async function load({ params }) {
return {
- one: Promise.resolve(1),
- two: Promise.resolve(2),
- streamed: {
- three: new Promise((fulfil) => {
- setTimeout(() => {
- fulfil(3)
- }, 1000);
- })
- }
+ // make sure the `await` happens at the end, otherwise we
+ // can't start loading comments until we've loaded the post
+ comments: loadComments(params.slug),
+ post: await loadPost(params.slug)
};
}
```
@@ -464,28 +473,24 @@ export function load() {
This is useful for creating skeleton loading states, for example:
```svelte
-
+
-
+{/await}
```
When streaming data, be careful to handle promise rejections correctly. More specifically, the server could crash with an "unhandled promise rejection" error if a lazy-loaded promise fails before rendering starts (at which point it's caught) and isn't handling the error in some way. When using SvelteKit's `fetch` directly in the `load` function, SvelteKit will handle this case for you. For other promises, it is enough to attach a noop-`catch` to the promise to mark it as handled.
@@ -498,21 +503,21 @@ export function load({ fetch }) {
ok_manual.catch(() => {});
return {
- streamed: {
- ok_manual,
- ok_fetch: fetch('/fetch/that/could/fail'),
- dangerous_unhandled: Promise.reject()
- }
+ ok_manual,
+ ok_fetch: fetch('/fetch/that/could/fail'),
+ dangerous_unhandled: Promise.reject()
};
}
```
> On platforms that do not support streaming, such as AWS Lambda, responses will be buffered. This means the page will only render once all promises resolve. If you are using a proxy (e.g. NGINX), make sure it does not buffer responses from the proxied server.
-> Streaming data will only work when JavaScript is enabled. You should avoid returning nested promises from a universal `load` function if the page is server rendered, as these are _not_ streamed — instead, the promise is recreated when the function reruns in the browser.
+> Streaming data will only work when JavaScript is enabled. You should avoid returning promises from a universal `load` function if the page is server rendered, as these are _not_ streamed — instead, the promise is recreated when the function reruns in the browser.
> The headers and status code of a response cannot be changed once the response has started streaming, therefore you cannot `setHeaders` or throw redirects inside a streamed promise.
+> [In SvelteKit 1.x](migrating-to-sveltekit-2#top-level-promises-are-no-longer-awaited) top-level promises were automatically awaited, only nested promises were streamed.
+
## Parallel loading
When rendering (or navigating to) a page, SvelteKit runs all `load` functions concurrently, avoiding a waterfall of requests. During client-side navigation, the result of calling multiple server `load` functions are grouped into a single response. Once all `load` functions have returned, the page is rendered.
@@ -567,6 +572,23 @@ A `load` function that calls `await parent()` will also rerun if a parent `load`
Dependency tracking does not apply _after_ the `load` function has returned — for example, accessing `params.x` inside a nested [promise](#streaming-with-promises) will not cause the function to rerun when `params.x` changes. (Don't worry, you'll get a warning in development if you accidentally do this.) Instead, access the parameter in the main body of your `load` function.
+Search parameters are tracked independently from the rest of the url. For example, accessing `event.url.searchParams.get("x")` inside a `load` function will make that `load` function re-run when navigating from `?x=1` to `?x=2`, but not when navigating from `?x=1&y=1` to `?x=1&y=2`.
+
+### Untracking dependencies
+
+In rare cases, you may wish to exclude something from the dependency tracking mechanism. You can do this with the provided `untrack` function:
+
+```js
+/// file: src/routes/+page.js
+/** @type {import('./$types').PageLoad} */
+export async function load({ untrack, url }) {
+ // Untrack url.pathname so that path changes don't trigger a rerun
+ if (untrack(() => url.pathname === '/')) {
+ return { message: 'Welcome!' };
+ }
+}
+```
+
### Manual invalidation
You can also rerun `load` functions that apply to the current page using [`invalidate(url)`](modules#$app-navigation-invalidate), which reruns all `load` functions that depend on `url`, and [`invalidateAll()`](modules#$app-navigation-invalidateall), which reruns every `load` function. Server load functions will never automatically depend on a fetched `url` to avoid leaking secrets to the client.
@@ -616,6 +638,7 @@ To summarize, a `load` function will rerun in the following situations:
- It references a property of `params` whose value has changed
- It references a property of `url` (such as `url.pathname` or `url.search`) whose value has changed. Properties in `request.url` are _not_ tracked
+- It calls `url.searchParams.get(...)`, `url.searchParams.getAll(...)` or `url.searchParams.has(...)` and the parameter in question changes. Accessing other properties of `url.searchParams` will have the same effect as accessing `url.search`.
- It calls `await parent()` and a parent `load` function reran
- It declared a dependency on a specific URL via [`fetch`](#making-fetch-requests) (universal load only) or [`depends`](types#public-types-loadevent), and that URL was marked invalid with [`invalidate(url)`](modules#$app-navigation-invalidate)
- All active `load` functions were forcibly rerun with [`invalidateAll()`](modules#$app-navigation-invalidateall)
diff --git a/documentation/docs/20-core-concepts/30-form-actions.md b/documentation/docs/20-core-concepts/30-form-actions.md
index d25e141eadc7..470d7318412b 100644
--- a/documentation/docs/20-core-concepts/30-form-actions.md
+++ b/documentation/docs/20-core-concepts/30-form-actions.md
@@ -234,7 +234,7 @@ export const actions = {
cookies.set('sessionid', await db.createSession(user));
+ if (url.searchParams.has('redirectTo')) {
-+ throw redirect(303, url.searchParams.get('redirectTo'));
++ redirect(303, url.searchParams.get('redirectTo'));
+ }
return { success: true };
diff --git a/documentation/docs/25-build-and-deploy/40-adapter-node.md b/documentation/docs/25-build-and-deploy/40-adapter-node.md
index f3c04bfa85c3..8736968e1a31 100644
--- a/documentation/docs/25-build-and-deploy/40-adapter-node.md
+++ b/documentation/docs/25-build-and-deploy/40-adapter-node.md
@@ -131,8 +131,7 @@ export default {
// default options are shown
out: 'build',
precompress: false,
- envPrefix: '',
- polyfill: true
+ envPrefix: ''
})
}
};
@@ -161,12 +160,6 @@ MY_CUSTOM_ORIGIN=https://my.site \
node build
```
-### polyfill
-
-Controls whether your build will load polyfills for missing modules. It defaults to `true`, and should only be disabled when using Node 18.11 or greater.
-
-Note: to use Node's built-in `crypto` global with Node 18 you will need to use the `--experimental-global-webcrypto` flag. This flag is not required with Node 20.
-
## Custom server
The adapter creates two files in your build directory — `index.js` and `handler.js`. Running `index.js` — e.g. `node build`, if you use the default build directory — will start a server on the configured port.
diff --git a/documentation/docs/25-build-and-deploy/90-adapter-vercel.md b/documentation/docs/25-build-and-deploy/90-adapter-vercel.md
index 6f72025b9565..2e0f92ff77d7 100644
--- a/documentation/docs/25-build-and-deploy/90-adapter-vercel.md
+++ b/documentation/docs/25-build-and-deploy/90-adapter-vercel.md
@@ -18,7 +18,7 @@ import adapter from '@sveltejs/adapter-vercel';
export default {
kit: {
adapter: adapter({
- // see the 'Deployment configuration' section below
+ // see below for options that can be set here
})
}
};
@@ -64,6 +64,18 @@ And the following option apply to serverless functions:
If your functions need to access data in a specific region, it's recommended that they be deployed in the same region (or close to it) for optimal performance.
+## Image Optimization
+
+You may set the `images` config to control how Vercel builds your images. See the [image configuration reference](https://vercel.com/docs/build-output-api/v3/configuration#images) for full details. As an example, you may set:
+
+```
+{
+ sizes: [640, 828, 1200, 1920, 3840],
+ formats: ['image/avif', 'image/webp'],
+ minimumCacheTTL: 300
+}
+```
+
## Incremental Static Regeneration
Vercel supports [Incremental Static Regeneration](https://vercel.com/docs/concepts/incremental-static-regeneration/overview) (ISR), which provides the performance and cost advantages of prerendered content with the flexibility of dynamically rendered content.
diff --git a/documentation/docs/30-advanced/10-advanced-routing.md b/documentation/docs/30-advanced/10-advanced-routing.md
index ca84a27df22b..524cb698287d 100644
--- a/documentation/docs/30-advanced/10-advanced-routing.md
+++ b/documentation/docs/30-advanced/10-advanced-routing.md
@@ -57,7 +57,7 @@ import { error } from '@sveltejs/kit';
/** @type {import('./$types').PageLoad} */
export function load(event) {
- throw error(404, 'Not Found');
+ error(404, 'Not Found');
}
```
diff --git a/documentation/docs/30-advanced/20-hooks.md b/documentation/docs/30-advanced/20-hooks.md
index 48a9a9388d61..d979ec88b1b1 100644
--- a/documentation/docs/30-advanced/20-hooks.md
+++ b/documentation/docs/30-advanced/20-hooks.md
@@ -139,12 +139,14 @@ The following can be added to `src/hooks.server.js` _and_ `src/hooks.client.js`:
### handleError
-If an unexpected error is thrown during loading or rendering, this function will be called with the `error` and the `event`. This allows for two things:
+If an [unexpected error](/docs/errors#unexpected-errors) is thrown during loading or rendering, this function will be called with the `error`, `event`, `status` code and `message`. This allows for two things:
- you can log the error
-- you can generate a custom representation of the error that is safe to show to users, omitting sensitive details like messages and stack traces. The returned value becomes the value of `$page.error`. It defaults to `{ message: 'Not Found' }` in case of a 404 (you can detect them through `event.route.id` being `null`) and to `{ message: 'Internal Error' }` for everything else. To make this type-safe, you can customize the expected shape by declaring an `App.Error` interface (which must include `message: string`, to guarantee sensible fallback behavior).
+- you can generate a custom representation of the error that is safe to show to users, omitting sensitive details like messages and stack traces. The returned value, which defaults to `{ message }`, becomes the value of `$page.error`.
-The following code shows an example of typing the error shape as `{ message: string; errorId: string }` and returning it accordingly from the `handleError` functions:
+For errors thrown from your code (or library code called by your code) the status will be 500 and the message will be "Internal Error". While `error.message` may contain sensitive information that should not be exposed to users, `message` is safe (albeit meaningless to the average user).
+
+To add more information to the `$page.error` object in a type-safe way, you can customize the expected shape by declaring an `App.Error` interface (which must include `message: string`, to guarantee sensible fallback behavior). This allows you to — for example — append a tracking ID for users to quote in correspondence with your technical support staff:
```ts
/// file: src/app.d.ts
@@ -162,7 +164,7 @@ export {};
```js
/// file: src/hooks.server.js
-// @errors: 2322
+// @errors: 2322 2353
// @filename: ambient.d.ts
declare module '@sentry/sveltekit' {
export const init: (opts: any) => void;
@@ -172,15 +174,17 @@ declare module '@sentry/sveltekit' {
// @filename: index.js
// ---cut---
import * as Sentry from '@sentry/sveltekit';
-import crypto from 'crypto';
Sentry.init({/*...*/})
/** @type {import('@sveltejs/kit').HandleServerError} */
-export async function handleError({ error, event }) {
+export async function handleError({ error, event, status, message }) {
const errorId = crypto.randomUUID();
+
// example integration with https://sentry.io/
- Sentry.captureException(error, { extra: { event, errorId } });
+ Sentry.captureException(error, {
+ extra: { event, errorId, status }
+ });
return {
message: 'Whoops!',
@@ -191,7 +195,7 @@ export async function handleError({ error, event }) {
```js
/// file: src/hooks.client.js
-// @errors: 2322
+// @errors: 2322 2353
// @filename: ambient.d.ts
declare module '@sentry/sveltekit' {
export const init: (opts: any) => void;
@@ -205,10 +209,13 @@ import * as Sentry from '@sentry/sveltekit';
Sentry.init({/*...*/})
/** @type {import('@sveltejs/kit').HandleClientError} */
-export async function handleError({ error, event }) {
+export async function handleError({ error, event, status, message }) {
const errorId = crypto.randomUUID();
+
// example integration with https://sentry.io/
- Sentry.captureException(error, { extra: { event, errorId } });
+ Sentry.captureException(error, {
+ extra: { event, errorId, status }
+ });
return {
message: 'Whoops!',
diff --git a/documentation/docs/30-advanced/25-errors.md b/documentation/docs/30-advanced/25-errors.md
index 4918db08b697..b594c6dd31d8 100644
--- a/documentation/docs/30-advanced/25-errors.md
+++ b/documentation/docs/30-advanced/25-errors.md
@@ -31,7 +31,7 @@ export async function load({ params }) {
const post = await db.getPost(params.slug);
if (!post) {
- throw error(404, {
+ error(404, {
message: 'Not found'
});
}
@@ -40,7 +40,7 @@ export async function load({ params }) {
}
```
-This tells SvelteKit to set the response status code to 404 and render an [`+error.svelte`](routing#error) component, where `$page.error` is the object provided as the second argument to `error(...)`.
+This throws an exception that SvelteKit catches, causing it to set the response status code to 404 and render an [`+error.svelte`](routing#error) component, where `$page.error` is the object provided as the second argument to `error(...)`.
```svelte
@@ -54,7 +54,7 @@ This tells SvelteKit to set the response status code to 404 and render an [`+err
You can add extra properties to the error object if needed...
```diff
-throw error(404, {
+error(404, {
message: 'Not found',
+ code: 'NOT_FOUND'
});
@@ -63,10 +63,12 @@ throw error(404, {
...otherwise, for convenience, you can pass a string as the second argument:
```diff
--throw error(404, { message: 'Not found' });
-+throw error(404, 'Not found');
+-error(404, { message: 'Not found' });
++error(404, 'Not found');
```
+> [In SvelteKit 1.x](migrating-to-sveltekit-2#redirect-and-error-are-no-longer-thrown-by-you) you had to `throw` the `error` yourself
+
## Unexpected errors
An _unexpected_ error is any other exception that occurs while handling a request. Since these can contain sensitive information, unexpected error messages and stack traces are not exposed to users.
@@ -77,36 +79,7 @@ By default, unexpected errors are printed to the console (or, in production, you
{ "message": "Internal Error" }
```
-Unexpected errors will go through the [`handleError`](hooks#shared-hooks-handleerror) hook, where you can add your own error handling — for example, sending errors to a reporting service, or returning a custom error object.
-
-```js
-/// file: src/hooks.server.js
-// @errors: 2322 1360 2571 2339
-// @filename: ambient.d.ts
-declare module '@sentry/sveltekit' {
- export const init: (opts: any) => void;
- export const captureException: (error: any, opts: any) => void;
-}
-
-// @filename: index.js
-// ---cut---
-import * as Sentry from '@sentry/sveltekit';
-
-Sentry.init({/*...*/})
-
-/** @type {import('@sveltejs/kit').HandleServerError} */
-export function handleError({ error, event }) {
- // example integration with https://sentry.io/
- Sentry.captureException(error, { extra: { event } });
-
- return {
- message: 'Whoops!',
- code: error?.code ?? 'UNKNOWN'
- };
-}
-```
-
-> Make sure that `handleError` _never_ throws an error
+Unexpected errors will go through the [`handleError`](hooks#shared-hooks-handleerror) hook, where you can add your own error handling — for example, sending errors to a reporting service, or returning a custom error object which becomes `$page.error`.
## Responses
diff --git a/documentation/docs/30-advanced/67-shallow-routing.md b/documentation/docs/30-advanced/67-shallow-routing.md
new file mode 100644
index 000000000000..466f9ee2049e
--- /dev/null
+++ b/documentation/docs/30-advanced/67-shallow-routing.md
@@ -0,0 +1,97 @@
+---
+title: Shallow routing
+---
+
+As you navigate around a SvelteKit app, you create _history entries_. Clicking the back and forward buttons traverses through this list of entries, re-running any `load` functions and replacing page components as necessary.
+
+Sometimes, it's useful to create history entries _without_ navigating. For example, you might want to show a modal dialog that the user can dismiss by navigating back. This is particularly valuable on mobile devices, where swipe gestures are often more natural than interacting directly with the UI. In these cases, a modal that is _not_ associated with a history entry can be a source of frustration, as a user may swipe backwards in an attempt to dismiss it and find themselves on the wrong page.
+
+SvelteKit makes this possible with the [`pushState`](/docs/modules#$app-navigation-pushstate) and [`replaceState`](/docs/modules#$app-navigation-replacestate) functions, which allow you to associate state with a history entry without navigating. For example, to implement a history-driven modal:
+
+```svelte
+
+
+
+{#if $page.state.showModal}
+ history.back()} />
+{/if}
+```
+
+The modal can be dismissed by navigating back (unsetting `$page.state.showModal`) or by interacting with it in a way that causes the `close` callback to run, which will navigate back programmatically.
+
+## API
+
+The first argument to `pushState` is the URL, relative to the current URL. To stay on the current URL, use `''`.
+
+The second argument is the new page state, which can be accessed via the [page store](/docs/modules#$app-stores-page) as `$page.state`. You can make page state type-safe by declaring an [`App.PageState`](/docs/types#app) interface (usually in `src/app.d.ts`).
+
+To set page state without creating a new history entry, use `replaceState` instead of `pushState`.
+
+## Loading data for a route
+
+When shallow routing, you may want to render another `+page.svelte` inside the current page. For example, clicking on a photo thumbnail could pop up the detail view without navigating to the photo page.
+
+For this to work, you need to load the data that the `+page.svelte` expects. A convenient way to do this is to use [`preloadData`](/docs/modules#$app-navigation-preloaddata) inside the `click` handler of an `` element. If the element (or a parent) uses [`data-sveltekit-preload-data`](/docs/link-options#data-sveltekit-preload-data), the data will have already been requested, and `preloadData` will reuse that request.
+
+```svelte
+
+
+
+{#each data.thumbnails as thumbnail}
+ {
+ // bail if opening a new tab, or we're on too small a screen
+ if (e.metaKey || innerWidth < 640) return;
+
+ // prevent navigation
+ e.preventDefault();
+
+ const { href } = e.currentTarget;
+
+ // run `load` functions (or rather, get the result of the `load` functions
+ // that are already running because of `data-sveltekit-preload-data`)
+ const result = await preloadData(href);
+
+ if (result.type === 'loaded' && result.status === 200) {
+ pushState(href, { selected: result.data });
+ } else {
+ // something bad happened! try navigating
+ goto(href);
+ }
+ }}
+ >
+
+
+{/each}
+
+{#if $page.state.selected}
+ history.goBack()}>
+
+
+
+{/if}
+```
+
+## Caveats
+
+During server-side rendering, `$page.state` is always an empty object. The same is true for the first page the user lands on — if the user reloads the page, state will _not_ be applied until they navigate.
+
+Shallow routing is a feature that requires JavaScript to work. Be mindful when using it and try to think of sensible fallback behavior in case JavaScript isn't available.
diff --git a/documentation/docs/60-appendix/01-faq.md b/documentation/docs/60-appendix/10-faq.md
similarity index 100%
rename from documentation/docs/60-appendix/01-faq.md
rename to documentation/docs/60-appendix/10-faq.md
diff --git a/documentation/docs/60-appendix/05-integrations.md b/documentation/docs/60-appendix/20-integrations.md
similarity index 91%
rename from documentation/docs/60-appendix/05-integrations.md
rename to documentation/docs/60-appendix/20-integrations.md
index 4bf23ff97c51..9ebd5fd330e6 100644
--- a/documentation/docs/60-appendix/05-integrations.md
+++ b/documentation/docs/60-appendix/20-integrations.md
@@ -8,11 +8,11 @@ Preprocessors transform your `.svelte` files before passing them to the compiler
### `vitePreprocess`
-`vite-plugin-svelte` offers a [`vitePreprocess`](https://github.com/sveltejs/vite-plugin-svelte/blob/main/docs/preprocess.md) feature which utilizes Vite for preprocessing. It is capable of handling the language flavors Vite handles: TypeScript, PostCSS, SCSS, Less, Stylus, and SugarSS. For convenience, it is re-exported from the `@sveltejs/kit/vite` package. If you set your project up with TypeScript it will be included by default:
+`vite-plugin-svelte` offers a [`vitePreprocess`](https://github.com/sveltejs/vite-plugin-svelte/blob/main/docs/preprocess.md) feature which utilizes Vite for preprocessing. It is capable of handling the language flavors Vite handles: TypeScript, PostCSS, SCSS, Less, Stylus, and SugarSS. If you set your project up with TypeScript it will be included by default:
```js
// svelte.config.js
-import { vitePreprocess } from '@sveltejs/kit/vite';
+import { vitePreprocess } from '@sveltejs/vite-plugin-svelte';
export default {
preprocess: [vitePreprocess()]
diff --git a/documentation/docs/60-appendix/30-migrating-to-sveltekit-2.md b/documentation/docs/60-appendix/30-migrating-to-sveltekit-2.md
new file mode 100644
index 000000000000..0fc01031ce78
--- /dev/null
+++ b/documentation/docs/60-appendix/30-migrating-to-sveltekit-2.md
@@ -0,0 +1,160 @@
+---
+title: Migrating to SvelteKit v2
+---
+
+Upgrading from SvelteKit version 1 to version 2 should be mostly seamless. There are a few breaking changes to note, which are listed here. You can use `npx svelte-migrate sveltekit-2` to migrate some of these changes automatically.
+
+We highly recommend upgrading to the most recent 1.x version before upgrading to 2.0, so that you can take advantage of targeted deprecation warnings. We also recommend [updating to Svelte 4](https://svelte.dev/docs/v4-migration-guide) first: Later versions of SvelteKit 1.x support it, and SvelteKit 2.0 requires it.
+
+## `redirect` and `error` are no longer thrown by you
+
+Previously, you had to `throw` the values returned from `error(...)` and `redirect(...)` yourself. In SvelteKit 2 this is no longer the case — calling the functions is sufficient.
+
+```diff
+import { error } from '@sveltejs/kit'
+
+...
+- throw error(500, 'something went wrong');
++ error(500, 'something went wrong');
+```
+
+`svelte-migrate` will do these changes automatically for you.
+
+If the error or redirect is thrown inside a `try {...}` block (hint: don't do this!), you can distinguish them from unexpected errors using [`isHttpError`](/docs/modules#sveltejs-kit-ishttperror) and [`isRedirect`](/docs/modules#sveltejs-kit-isredirect) imported from `@sveltejs/kit`.
+
+## path is required when setting cookies
+
+When receiving a `Set-Cookie` header that doesn't specify a `path`, browsers will [set the cookie path](https://www.rfc-editor.org/rfc/rfc6265#section-5.1.4) to the parent of the resource in question. This behaviour isn't particularly helpful or intuitive, and frequently results in bugs because the developer expected the cookie to apply to the domain as a whole.
+
+As of SvelteKit 2.0, you need to set a `path` when calling `cookies.set(...)`, `cookies.delete(...)` or `cookies.serialize(...)` so that there's no ambiguity. Most of the time, you probably want to use `path: '/'`, but you can set it to whatever you like, including relative paths — `''` means 'the current path', `'.'` means 'the current directory'.
+
+```diff
+export function load({ cookies }) {
+- cookies.set(name, value);
++ cookies.set(name, value, { path: '/' });
+ return { response }
+}
+```
+
+`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.
+
+As of version 2, SvelteKit no longer differentiates between top-level and non-top-level promises. To get back the blocking behavior, use `await` (with `Promise.all` to prevent waterfalls, where appropriate):
+
+```diff
+// If you have a single promise
+export function load({ fetch }) {
+- const response = fetch(...).then(r => r.json());
++ const response = await fetch(...).then(r => r.json());
+ return { response }
+}
+```
+
+```diff
+// If you have multiple promises
+export function load({ fetch }) {
+- const a = fetch(...).then(r => r.json());
+- const b = fetch(...).then(r => r.json());
++ const [a, b] = await Promise.all([
++ fetch(...).then(r => r.json()),
++ fetch(...).then(r => r.json()),
++ ]);
+ return { a, b };
+}
+```
+
+## goto(...) changes
+
+`goto(...)` no longer accepts external URLs. To navigate to an external URL, use `window.location = url`. The `state` option was removed in favor of [shallow routing](shallow-routing).
+
+## paths are now relative by default
+
+In SvelteKit 1, `%sveltekit.assets%` in your `app.html` was replaced with a relative path by default (i.e. `.` or `..` or `../..` etc, depending on the path being rendered) during server-side rendering unless the [`paths.relative`](/docs/configuration#paths) config option was explicitly set to `false`. The same was true for `base` and `assets` imported from `$app/paths`, but only if the `paths.relative` option was explicitly set to `true`.
+
+This inconsistency is fixed in version 2. Paths are either always relative or always absolute, depending on the value of [`paths.relative`](/docs/configuration#paths). It defaults to `true` as this results in more portable apps: if the `base` is something other than the app expected (as is the case when viewed on the [Internet Archive](https://archive.org/), for example) or unknown at build time (as is the case when deploying to [IPFS](https://ipfs.tech/) and so on), fewer things are likely to break.
+
+## Server fetches are not trackable anymore
+
+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.
+
+As such, SvelteKit 2 replaces `resolvePath` with a (slightly better named) function called `resolveRoute`, which is imported from `$app/paths` and which takes `base` into account.
+
+```diff
+-import { resolvePath } from '@sveltejs/kit';
+-import { base } from '$app/paths';
++import { resolveRoute } from '$app/paths';
+
+-const path = base + resolvePath('/blog/[slug]', { slug });
++const path = resolveRoute('/blog/[slug]', { slug });
+```
+
+`svelte-migrate` will do the method replacement for you, though if you later prepend the result with `base`, you need to remove that yourself.
+
+## Improved error handling
+
+Errors are handled inconsistently in SvelteKit 1. Some errors trigger the `handleError` hook but there is no good way to discern their status (for example, the only way to tell a 404 from a 500 is by seeing if `event.route.id` is `null`), while others (such as 405 errors for `POST` requests to pages without actions) don't trigger `handleError` at all, but should. In the latter case, the resulting `$page.error` will deviate from the [`App.Error`](/docs/types#app-error) type, if it is specified.
+
+SvelteKit 2 cleans this up by calling `handleError` hooks with two new properties: `status` and `message`. For errors thrown from your code (or library code called by your code) the status will be `500` and the message will be `Internal Error`. While `error.message` may contain sensitive information that should not be exposed to users, `message` is safe.
+
+## Dynamic environment variables cannot be used during prerendering
+
+The `$env/dynamic/public` and `$env/dynamic/private` modules provide access to _run time_ environment variables, as opposed to the _build time_ environment variables exposed by `$env/static/public` and `$env/static/private`.
+
+During prerendering in SvelteKit 1, they are one and the same. As such, prerendered pages that make use of 'dynamic' environment variables are really 'baking in' build time values, which is incorrect. Worse, `$env/dynamic/public` is populated in the browser with these stale values if the user happens to land on a prerendered page before navigating to dynamically-rendered pages.
+
+Because of this, dynamic environment variables can no longer be read during prerendering in SvelteKit 2 — you should use the `static` modules instead. If the user lands on a prerendered page, SvelteKit will request up-to-date values for `$env/dynamic/public` from the server (by default from a module called `_env.js` — this can be configured with `config.kit.env.publicModule`) instead of reading them from the server-rendered HTML.
+
+## `form` and `data` have been removed from `use:enhance` callbacks
+
+If you provide a callback to [`use:enhance`](/docs/form-actions#progressive-enhancement-use-enhance), it will be called with an object containing various useful properties.
+
+In SvelteKit 1, those properties included `form` and `data`. These were deprecated some time ago in favour of `formElement` and `formData`, and have been removed altogether in SvelteKit 2.
+
+## Forms containing file inputs must use `multipart/form-data`
+
+If a form contains an `` but does not have an `enctype="multipart/form-data"` attribute, non-JS submissions will omit the file. SvelteKit 2 will throw an error if it encounters a form like this during a `use:enhance` submission to ensure that your forms work correctly when JavaScript is not present.
+
+## Generated `tsconfig.json` is more strict
+
+Previously, the generated `tsconfig.json` was trying its best to still produce a somewhat valid config when your `tsconfig.json` included `paths` or `baseUrl`. In SvelteKit 2, the validation is more strict and will warn when you use either `paths` or `baseUrl` in your `tsconfig.json`. These settings are used to generate path aliases and you should use [the `alias` config](configuration#alias) option in your `svelte.config.js` instead, to also create a corresponding alias for the bundler.
+
+## `getRequest` no longer throws errors
+
+The `@sveltejs/kit/node` module exports helper functions for use in Node environments, including `getRequest` which turns a Node [`ClientRequest`](https://nodejs.org/api/http.html#class-httpclientrequest) into a standard [`Request`](https://developer.mozilla.org/en-US/docs/Web/API/Request) object.
+
+In SvelteKit 1, `getRequest` could throw if the `Content-Length` header exceeded the specified size limit. In SvelteKit 2, the error will not be thrown until later, when the request body (if any) is being read. This enables better diagnostics and simpler code.
+
+## `vitePreprocess` is no longer exported from `@sveltejs/kit/vite`
+
+Since `@sveltejs/vite-plugin-svelte` is now a peer dependency, SvelteKit 2 no longer re-exports `vitePreprocess`. You should import it directly from `@sveltejs/vite-plugin-svelte`.
+
+## Updated dependency requirements
+
+SvelteKit 2 requires Node `18.13` or higher, and the following minimum dependency versions:
+
+- `svelte@4`
+- `vite@5`
+- `typescript@5`
+- `@sveltejs/adapter-static@3` (if you're using it)
+- `@sveltejs/vite-plugin-svelte@3` (this is now required as a `peerDependency` of SvelteKit — previously it was directly depended upon)
+
+`svelte-migrate` will update your `package.json` for you.
+
+As part of the TypeScript upgrade, the generated `tsconfig.json` (the one your `tsconfig.json` extends from) now uses `"moduleResolution": "bundler"` (which is recommended by the TypeScript team, as it properly resolves types from packages with an `exports` map in package.json) and `verbatimModuleSyntax` (which replaces the existing `importsNotUsedAsValues ` and `preserveValueImports` flags — if you have those in your `tsconfig.json`, remove them. `svelte-migrate` will do this for you).
+
+SvelteKit 2 uses ES2022 features, which are supported in all modern browsers.
diff --git a/documentation/docs/60-appendix/10-migrating.md b/documentation/docs/60-appendix/40-migrating.md
similarity index 100%
rename from documentation/docs/60-appendix/10-migrating.md
rename to documentation/docs/60-appendix/40-migrating.md
diff --git a/documentation/docs/60-appendix/20-additional-resources.md b/documentation/docs/60-appendix/50-additional-resources.md
similarity index 100%
rename from documentation/docs/60-appendix/20-additional-resources.md
rename to documentation/docs/60-appendix/50-additional-resources.md
diff --git a/documentation/docs/60-appendix/30-glossary.md b/documentation/docs/60-appendix/60-glossary.md
similarity index 100%
rename from documentation/docs/60-appendix/30-glossary.md
rename to documentation/docs/60-appendix/60-glossary.md
diff --git a/package.json b/package.json
index 25b2fa0143cd..a64f82d7623f 100644
--- a/package.json
+++ b/package.json
@@ -19,24 +19,21 @@
"start": "cd sites/kit.svelte.dev && npm run dev"
},
"devDependencies": {
- "@changesets/cli": "^2.26.2",
- "@rollup/plugin-commonjs": "^25.0.0",
- "@rollup/plugin-json": "^6.0.0",
- "@rollup/plugin-node-resolve": "^15.0.1",
+ "@changesets/cli": "^2.27.1",
"@sveltejs/eslint-config": "^6.0.4",
"@svitejs/changesets-changelog-github-compact": "^1.1.0",
- "@typescript-eslint/eslint-plugin": "^6.0.0",
- "eslint": "^8.52.0",
- "eslint-config-prettier": "^9.0.0",
- "eslint-plugin-svelte": "^2.31.0",
+ "@typescript-eslint/eslint-plugin": "^6.14.0",
+ "eslint": "^8.55.0",
+ "eslint-config-prettier": "^9.1.0",
+ "eslint-plugin-svelte": "^2.35.1",
"eslint-plugin-unicorn": "^49.0.0",
"playwright": "1.30.0",
"prettier": "^3.1.1",
"rollup": "^3.29.4",
- "svelte": "^4.2.7",
- "typescript": "^4.9.4"
+ "svelte": "^4.2.8",
+ "typescript": "^5.3.3"
},
- "packageManager": "pnpm@8.10.2",
+ "packageManager": "pnpm@8.11.0",
"engines": {
"pnpm": "^8.0.0"
}
diff --git a/packages/adapter-auto/package.json b/packages/adapter-auto/package.json
index 037ba5fd38ab..ba1ff78660e7 100644
--- a/packages/adapter-auto/package.json
+++ b/packages/adapter-auto/package.json
@@ -31,13 +31,14 @@
},
"devDependencies": {
"@sveltejs/kit": "workspace:^",
- "@types/node": "^16.18.6",
- "typescript": "^4.9.4"
+ "@sveltejs/vite-plugin-svelte": "^3.0.1",
+ "@types/node": "^18.19.3",
+ "typescript": "^5.3.3"
},
"dependencies": {
"import-meta-resolve": "^4.0.0"
},
"peerDependencies": {
- "@sveltejs/kit": "^1.0.0"
+ "@sveltejs/kit": "^1.0.0 || ^2.0.0"
}
}
diff --git a/packages/adapter-cloudflare-workers/index.d.ts b/packages/adapter-cloudflare-workers/index.d.ts
index 8b90611b73c3..d5ef5b24e7b5 100644
--- a/packages/adapter-cloudflare-workers/index.d.ts
+++ b/packages/adapter-cloudflare-workers/index.d.ts
@@ -1,4 +1,4 @@
import { Adapter } from '@sveltejs/kit';
import './ambient.js';
-export default function plugin(options: { config?: string }): Adapter;
+export default function plugin(options?: { config?: string }): Adapter;
diff --git a/packages/adapter-cloudflare-workers/package.json b/packages/adapter-cloudflare-workers/package.json
index 8913f8885e73..86a31cd1270a 100644
--- a/packages/adapter-cloudflare-workers/package.json
+++ b/packages/adapter-cloudflare-workers/package.json
@@ -30,16 +30,16 @@
"check": "tsc"
},
"dependencies": {
- "@cloudflare/workers-types": "^4.20230404.0",
+ "@cloudflare/workers-types": "^4.20231121.0",
"@iarna/toml": "^2.2.5",
- "esbuild": "^0.18.11"
+ "esbuild": "^0.19.9"
},
"devDependencies": {
"@cloudflare/kv-asset-handler": "^0.3.0",
- "@types/node": "^16.18.6",
- "typescript": "^4.9.4"
+ "@types/node": "^18.19.3",
+ "typescript": "^5.3.3"
},
"peerDependencies": {
- "@sveltejs/kit": "^1.0.0"
+ "@sveltejs/kit": "^1.0.0 || ^2.0.0"
}
}
diff --git a/packages/adapter-cloudflare/package.json b/packages/adapter-cloudflare/package.json
index 6b2f5d274568..19c6f42cad95 100644
--- a/packages/adapter-cloudflare/package.json
+++ b/packages/adapter-cloudflare/package.json
@@ -32,17 +32,17 @@
"prepublishOnly": "pnpm build"
},
"dependencies": {
- "@cloudflare/workers-types": "^4.20230404.0",
- "esbuild": "^0.18.11",
+ "@cloudflare/workers-types": "^4.20231121.0",
+ "esbuild": "^0.19.9",
"worktop": "0.8.0-next.15"
},
"devDependencies": {
- "@types/node": "^16.18.6",
- "@types/ws": "^8.5.3",
- "typescript": "^4.9.4"
+ "@types/node": "^18.19.3",
+ "@types/ws": "^8.5.10",
+ "typescript": "^5.3.3"
},
"peerDependencies": {
- "@sveltejs/kit": "^1.0.0"
+ "@sveltejs/kit": "^1.0.0 || ^2.0.0"
},
"publishConfig": {
"access": "public"
diff --git a/packages/adapter-netlify/package.json b/packages/adapter-netlify/package.json
index b417461d9b8f..2a921d2529b1 100644
--- a/packages/adapter-netlify/package.json
+++ b/packages/adapter-netlify/package.json
@@ -34,22 +34,23 @@
},
"dependencies": {
"@iarna/toml": "^2.2.5",
- "esbuild": "^0.18.11",
+ "esbuild": "^0.19.9",
"set-cookie-parser": "^2.6.0"
},
"devDependencies": {
- "@netlify/functions": "^2.0.1",
- "@rollup/plugin-commonjs": "^25.0.0",
- "@rollup/plugin-json": "^6.0.0",
- "@rollup/plugin-node-resolve": "^15.0.1",
+ "@netlify/functions": "^2.4.1",
+ "@rollup/plugin-commonjs": "^25.0.7",
+ "@rollup/plugin-json": "^6.1.0",
+ "@rollup/plugin-node-resolve": "^15.2.3",
"@sveltejs/kit": "workspace:^",
- "@types/node": "^16.18.6",
- "@types/set-cookie-parser": "^2.4.2",
- "rollup": "^3.29.4",
- "typescript": "^4.9.4",
- "vitest": "^0.34.5"
+ "@sveltejs/vite-plugin-svelte": "^3.0.1",
+ "@types/node": "^18.19.3",
+ "@types/set-cookie-parser": "^2.4.7",
+ "rollup": "^4.8.0",
+ "typescript": "^5.3.3",
+ "vitest": "^1.0.4"
},
"peerDependencies": {
- "@sveltejs/kit": "^1.5.0"
+ "@sveltejs/kit": "^1.5.0 || ^2.0.0"
}
}
diff --git a/packages/adapter-netlify/rollup.config.js b/packages/adapter-netlify/rollup.config.js
index 7eca711c728c..b69d62886502 100644
--- a/packages/adapter-netlify/rollup.config.js
+++ b/packages/adapter-netlify/rollup.config.js
@@ -12,6 +12,7 @@ const config = {
dir: 'files/esm',
format: 'esm'
},
+ // @ts-ignore https://github.com/rollup/plugins/issues/1329
plugins: [nodeResolve({ preferBuiltins: true }), commonjs(), json()],
external: (id) => id === '0SERVER' || id.startsWith('node:'),
preserveEntrySignatures: 'exports-only'
diff --git a/packages/adapter-netlify/tsconfig.json b/packages/adapter-netlify/tsconfig.json
index 050c14d467ea..1eeee8b0847d 100644
--- a/packages/adapter-netlify/tsconfig.json
+++ b/packages/adapter-netlify/tsconfig.json
@@ -5,10 +5,10 @@
"noEmit": true,
"noImplicitAny": true,
"target": "es2022",
- "module": "es2022",
- // `@netlify/function` > `@netlify/serverless-functions-api` types are not compatible,
- // so using this moduleResolution for now
- "moduleResolution": "node",
+ "module": "node16",
+ "moduleResolution": "node16",
+ // https://github.com/netlify/functions/issues/447
+ "skipLibCheck": true,
"allowSyntheticDefaultImports": true,
"baseUrl": ".",
"paths": {
diff --git a/packages/adapter-node/index.d.ts b/packages/adapter-node/index.d.ts
index 12ea6273dd66..d32510e25186 100644
--- a/packages/adapter-node/index.d.ts
+++ b/packages/adapter-node/index.d.ts
@@ -9,7 +9,6 @@ interface AdapterOptions {
out?: string;
precompress?: boolean;
envPrefix?: string;
- polyfill?: boolean;
}
export default function plugin(options?: AdapterOptions): Adapter;
diff --git a/packages/adapter-node/index.js b/packages/adapter-node/index.js
index 2d34cfb22d29..4cd8d42cbe29 100644
--- a/packages/adapter-node/index.js
+++ b/packages/adapter-node/index.js
@@ -9,7 +9,7 @@ const files = fileURLToPath(new URL('./files', import.meta.url).href);
/** @type {import('./index.js').default} */
export default function (opts = {}) {
- const { out = 'build', precompress, envPrefix = '', polyfill = true } = opts;
+ const { out = 'build', precompress, envPrefix = '' } = opts;
return {
name: '@sveltejs/adapter-node',
@@ -62,7 +62,9 @@ export default function (opts = {}) {
preferBuiltins: true,
exportConditions: ['node']
}),
+ // @ts-ignore https://github.com/rollup/plugins/issues/1329
commonjs({ strictRequires: true }),
+ // @ts-ignore https://github.com/rollup/plugins/issues/1329
json()
]
});
@@ -84,11 +86,6 @@ export default function (opts = {}) {
ENV_PREFIX: JSON.stringify(envPrefix)
}
});
-
- // If polyfills aren't wanted then clear the file
- if (!polyfill) {
- writeFileSync(`${out}/shims.js`, '', 'utf-8');
- }
}
};
}
diff --git a/packages/adapter-node/package.json b/packages/adapter-node/package.json
index d18a3f1c46f1..d3b5c1c7ce58 100644
--- a/packages/adapter-node/package.json
+++ b/packages/adapter-node/package.json
@@ -33,22 +33,23 @@
"prepublishOnly": "pnpm build"
},
"devDependencies": {
- "@polka/url": "^1.0.0-next.23",
+ "@polka/url": "1.0.0-next.24",
"@sveltejs/kit": "workspace:^",
- "@types/node": "^16.18.6",
- "c8": "^8.0.0",
- "polka": "^1.0.0-next.23",
+ "@sveltejs/vite-plugin-svelte": "^3.0.1",
+ "@types/node": "^18.19.3",
+ "c8": "^8.0.1",
+ "polka": "1.0.0-next.24",
"sirv": "^2.0.3",
- "typescript": "^4.9.4",
- "vitest": "^0.34.5"
+ "typescript": "^5.3.3",
+ "vitest": "^1.0.4"
},
"dependencies": {
- "@rollup/plugin-commonjs": "^25.0.0",
- "@rollup/plugin-json": "^6.0.0",
- "@rollup/plugin-node-resolve": "^15.0.1",
- "rollup": "^3.7.0"
+ "@rollup/plugin-commonjs": "^25.0.7",
+ "@rollup/plugin-json": "^6.1.0",
+ "@rollup/plugin-node-resolve": "^15.2.3",
+ "rollup": "^4.8.0"
},
"peerDependencies": {
- "@sveltejs/kit": "^1.0.0"
+ "@sveltejs/kit": "^2.0.0"
}
}
diff --git a/packages/adapter-node/src/handler.js b/packages/adapter-node/src/handler.js
index bae1854bdd7e..09bf98f63a11 100644
--- a/packages/adapter-node/src/handler.js
+++ b/packages/adapter-node/src/handler.js
@@ -76,20 +76,11 @@ function serve_prerendered() {
/** @type {import('polka').Middleware} */
const ssr = async (req, res) => {
- /** @type {Request | undefined} */
- let request;
-
- try {
- request = await getRequest({
- base: origin || get_origin(req.headers),
- request: req,
- bodySizeLimit: body_size_limit
- });
- } catch (err) {
- res.statusCode = err.status || 400;
- res.end('Invalid request body');
- return;
- }
+ const request = await getRequest({
+ base: origin || get_origin(req.headers),
+ request: req,
+ bodySizeLimit: body_size_limit
+ });
setResponse(
res,
diff --git a/packages/adapter-node/tsconfig.json b/packages/adapter-node/tsconfig.json
index 7d320c09f70c..17a612157ece 100644
--- a/packages/adapter-node/tsconfig.json
+++ b/packages/adapter-node/tsconfig.json
@@ -6,11 +6,8 @@
"noImplicitAny": true,
"allowSyntheticDefaultImports": true,
"target": "es2022",
- "module": "es2022",
- // Can't use moduleResolution: node16 because of these issues:
- // https://github.com/rollup/plugins/issues/1329
- // https://github.com/lukeed/polka/issues/199
- "moduleResolution": "node",
+ "module": "node16",
+ "moduleResolution": "node16",
"baseUrl": ".",
"paths": {
"@sveltejs/kit": ["../kit/types/index"]
diff --git a/packages/adapter-static/index.js b/packages/adapter-static/index.js
index 2fe366a5231e..32d7e5319df7 100644
--- a/packages/adapter-static/index.js
+++ b/packages/adapter-static/index.js
@@ -52,6 +52,7 @@ See https://kit.svelte.dev/docs/page-options#prerender for more details`
}
const {
+ // @ts-ignore
pages = 'build',
assets = pages,
fallback,
@@ -61,6 +62,7 @@ See https://kit.svelte.dev/docs/page-options#prerender for more details`
builder.rimraf(assets);
builder.rimraf(pages);
+ builder.generateEnvModule();
builder.writeClient(assets);
builder.writePrerendered(pages);
diff --git a/packages/adapter-static/package.json b/packages/adapter-static/package.json
index 1a1ab9478510..878e652767f9 100644
--- a/packages/adapter-static/package.json
+++ b/packages/adapter-static/package.json
@@ -32,13 +32,14 @@
"devDependencies": {
"@playwright/test": "1.30.0",
"@sveltejs/kit": "workspace:^",
- "@types/node": "^16.18.6",
+ "@sveltejs/vite-plugin-svelte": "^3.0.1",
+ "@types/node": "^18.19.3",
"sirv": "^2.0.3",
- "svelte": "^4.2.7",
- "typescript": "^4.9.4",
- "vite": "^4.4.9"
+ "svelte": "^4.2.8",
+ "typescript": "^5.3.3",
+ "vite": "^5.0.8"
},
"peerDependencies": {
- "@sveltejs/kit": "^1.5.0"
+ "@sveltejs/kit": "^2.0.0"
}
}
diff --git a/packages/adapter-static/test/apps/prerendered/package.json b/packages/adapter-static/test/apps/prerendered/package.json
index 782b53e3c746..98b381193dbe 100644
--- a/packages/adapter-static/test/apps/prerendered/package.json
+++ b/packages/adapter-static/test/apps/prerendered/package.json
@@ -10,9 +10,10 @@
},
"devDependencies": {
"@sveltejs/kit": "workspace:^",
+ "@sveltejs/vite-plugin-svelte": "^3.0.1",
"sirv-cli": "^2.0.2",
- "svelte": "^4.2.7",
- "vite": "^4.4.9"
+ "svelte": "^4.2.8",
+ "vite": "^5.0.8"
},
"type": "module"
}
diff --git a/packages/adapter-static/test/apps/prerendered/src/routes/public-env/+page.svelte b/packages/adapter-static/test/apps/prerendered/src/routes/public-env/+page.svelte
index 1c06b45b43e4..a36f01afaa54 100644
--- a/packages/adapter-static/test/apps/prerendered/src/routes/public-env/+page.svelte
+++ b/packages/adapter-static/test/apps/prerendered/src/routes/public-env/+page.svelte
@@ -1,5 +1,11 @@
-
The answer is {env.PUBLIC_ANSWER}
+
The answer is {PUBLIC_ANSWER}
+
+{#if browser}
+
The dynamic answer is {env.PUBLIC_ANSWER}
+{/if}
diff --git a/packages/adapter-static/test/apps/prerendered/test/test.js b/packages/adapter-static/test/apps/prerendered/test/test.js
index 6a5ea0c2ee8b..2e187f8effd5 100644
--- a/packages/adapter-static/test/apps/prerendered/test/test.js
+++ b/packages/adapter-static/test/apps/prerendered/test/test.js
@@ -24,4 +24,5 @@ test('prerenders a referenced endpoint with implicit `prerender` setting', async
test('exposes public env vars to the client', async ({ page }) => {
await page.goto('/public-env');
expect(await page.textContent('h1')).toEqual('The answer is 42');
+ expect(await page.textContent('h2')).toEqual('The dynamic answer is 42');
});
diff --git a/packages/adapter-static/test/apps/spa/package.json b/packages/adapter-static/test/apps/spa/package.json
index d166f5828292..03971e030a2e 100644
--- a/packages/adapter-static/test/apps/spa/package.json
+++ b/packages/adapter-static/test/apps/spa/package.json
@@ -11,9 +11,10 @@
"devDependencies": {
"@sveltejs/adapter-node": "workspace:^",
"@sveltejs/kit": "workspace:^",
+ "@sveltejs/vite-plugin-svelte": "^3.0.1",
"sirv-cli": "^2.0.2",
- "svelte": "^4.2.7",
- "vite": "^4.4.9"
+ "svelte": "^4.2.8",
+ "vite": "^5.0.8"
},
"type": "module"
}
diff --git a/packages/adapter-static/test/apps/spa/src/routes/fallback/[...rest]/+page.svelte b/packages/adapter-static/test/apps/spa/src/routes/fallback/[...rest]/+page.svelte
index 8950feed162a..55b4798e99c5 100644
--- a/packages/adapter-static/test/apps/spa/src/routes/fallback/[...rest]/+page.svelte
+++ b/packages/adapter-static/test/apps/spa/src/routes/fallback/[...rest]/+page.svelte
@@ -1,7 +1,7 @@
the fallback page was rendered
-{env.PUBLIC_VALUE}
+{PUBLIC_VALUE}
diff --git a/packages/adapter-vercel/files/serverless.js b/packages/adapter-vercel/files/serverless.js
index 5a6c2f64e2fd..d732371568c8 100644
--- a/packages/adapter-vercel/files/serverless.js
+++ b/packages/adapter-vercel/files/serverless.js
@@ -32,15 +32,7 @@ export default async (req, res) => {
}
}
- /** @type {Request} */
- let request;
-
- try {
- request = await getRequest({ base: `https://${req.headers.host}`, request: req });
- } catch (err) {
- res.statusCode = /** @type {any} */ (err).status || 400;
- return res.end('Invalid request body');
- }
+ const request = await getRequest({ base: `https://${req.headers.host}`, request: req });
setResponse(
res,
diff --git a/packages/adapter-vercel/index.d.ts b/packages/adapter-vercel/index.d.ts
index 8e3a96bec132..6d9f8d359891 100644
--- a/packages/adapter-vercel/index.d.ts
+++ b/packages/adapter-vercel/index.d.ts
@@ -28,6 +28,12 @@ export interface ServerlessConfig {
* If `true`, this route will always be deployed as its own separate function
*/
split?: boolean;
+
+ /**
+ * https://vercel.com/docs/build-output-api/v3/configuration#images
+ */
+ images?: ImagesConfig;
+
/**
* [Incremental Static Regeneration](https://vercel.com/docs/concepts/incremental-static-regeneration/overview) configuration.
* Serverless only.
@@ -53,6 +59,26 @@ export interface ServerlessConfig {
| false;
}
+type ImageFormat = 'image/avif' | 'image/webp';
+
+type RemotePattern = {
+ protocol?: 'http' | 'https';
+ hostname: string;
+ port?: string;
+ pathname?: string;
+};
+
+type ImagesConfig = {
+ sizes: number[];
+ domains: string[];
+ remotePatterns?: RemotePattern[];
+ minimumCacheTTL?: number; // seconds
+ formats?: ImageFormat[];
+ dangerouslyAllowSVG?: boolean;
+ contentSecurityPolicy?: string;
+ contentDispositionType?: string;
+};
+
export interface EdgeConfig {
/**
* Whether to use [Edge Functions](https://vercel.com/docs/concepts/functions/edge-functions) (`'edge'`) or [Serverless Functions](https://vercel.com/docs/concepts/functions/serverless-functions) (`'nodejs18.x'`, `'nodejs20.x'` etc).
diff --git a/packages/adapter-vercel/index.js b/packages/adapter-vercel/index.js
index 40fe02c2b0d9..3a6ebec5240f 100644
--- a/packages/adapter-vercel/index.js
+++ b/packages/adapter-vercel/index.js
@@ -54,7 +54,7 @@ const plugin = function (defaults = {}) {
functions: `${dir}/functions`
};
- const static_config = static_vercel_config(builder);
+ const static_config = static_vercel_config(builder, defaults);
builder.log.minor('Generating serverless function...');
@@ -367,14 +367,20 @@ function write(file, data) {
}
// This function is duplicated in adapter-static
-/** @param {import('@sveltejs/kit').Builder} builder */
-function static_vercel_config(builder) {
+/**
+ * @param {import('@sveltejs/kit').Builder} builder
+ * @param {import('.').Config} config
+ */
+function static_vercel_config(builder, config) {
/** @type {any[]} */
const prerendered_redirects = [];
/** @type {Record} */
const overrides = {};
+ /** @type {import('./index').ImagesConfig} */
+ const images = config.images;
+
for (const [src, redirect] of builder.prerendered.redirects) {
prerendered_redirects.push({
src,
@@ -420,7 +426,8 @@ function static_vercel_config(builder) {
handle: 'filesystem'
}
],
- overrides
+ overrides,
+ images
};
}
diff --git a/packages/adapter-vercel/package.json b/packages/adapter-vercel/package.json
index e8b8b139a7f7..7da9c6843e32 100644
--- a/packages/adapter-vercel/package.json
+++ b/packages/adapter-vercel/package.json
@@ -31,16 +31,17 @@
"test": "vitest run"
},
"dependencies": {
- "@vercel/nft": "^0.24.0",
- "esbuild": "^0.18.11"
+ "@vercel/nft": "^0.24.4",
+ "esbuild": "^0.19.9"
},
"devDependencies": {
"@sveltejs/kit": "workspace:^",
- "@types/node": "^16.18.6",
- "typescript": "^4.9.4",
- "vitest": "^0.34.5"
+ "@sveltejs/vite-plugin-svelte": "^3.0.1",
+ "@types/node": "^18.19.3",
+ "typescript": "^5.3.3",
+ "vitest": "^1.0.4"
},
"peerDependencies": {
- "@sveltejs/kit": "^1.5.0"
+ "@sveltejs/kit": "^2.0.0"
}
}
diff --git a/packages/amp/package.json b/packages/amp/package.json
index d8740cc5aeb1..b93383234560 100644
--- a/packages/amp/package.json
+++ b/packages/amp/package.json
@@ -27,6 +27,6 @@
"format": "pnpm lint --write"
},
"peerDependencies": {
- "@sveltejs/kit": "^1.0.0"
+ "@sveltejs/kit": "^1.0.0 || ^2.0.0"
}
}
diff --git a/packages/create-svelte/package.json b/packages/create-svelte/package.json
index 909ada69e12e..26981aa1d375 100644
--- a/packages/create-svelte/package.json
+++ b/packages/create-svelte/package.json
@@ -17,14 +17,14 @@
},
"devDependencies": {
"@playwright/test": "1.30.0",
- "@types/gitignore-parser": "^0.0.2",
+ "@types/gitignore-parser": "^0.0.3",
"gitignore-parser": "^0.0.2",
"prettier": "^3.1.1",
- "prettier-plugin-svelte": "^3.0.0",
- "sucrase": "^3.29.0",
- "svelte": "^4.2.7",
+ "prettier-plugin-svelte": "^3.1.2",
+ "sucrase": "^3.34.0",
+ "svelte": "^4.2.8",
"tiny-glob": "^0.2.9",
- "vitest": "^0.34.5"
+ "vitest": "^1.0.4"
},
"scripts": {
"build": "node scripts/build-templates",
diff --git a/packages/create-svelte/shared/+default+checkjs/svelte.config.js b/packages/create-svelte/shared/+default+checkjs/svelte.config.js
index 1cf26a00dfea..2b35fe1befd3 100644
--- a/packages/create-svelte/shared/+default+checkjs/svelte.config.js
+++ b/packages/create-svelte/shared/+default+checkjs/svelte.config.js
@@ -1,5 +1,5 @@
import adapter from '@sveltejs/adapter-auto';
-import { vitePreprocess } from '@sveltejs/kit/vite';
+import { vitePreprocess } from '@sveltejs/vite-plugin-svelte';
/** @type {import('@sveltejs/kit').Config} */
const config = {
diff --git a/packages/create-svelte/shared/+default+typescript/svelte.config.js b/packages/create-svelte/shared/+default+typescript/svelte.config.js
index 1cf26a00dfea..2b35fe1befd3 100644
--- a/packages/create-svelte/shared/+default+typescript/svelte.config.js
+++ b/packages/create-svelte/shared/+default+typescript/svelte.config.js
@@ -1,5 +1,5 @@
import adapter from '@sveltejs/adapter-auto';
-import { vitePreprocess } from '@sveltejs/kit/vite';
+import { vitePreprocess } from '@sveltejs/vite-plugin-svelte';
/** @type {import('@sveltejs/kit').Config} */
const config = {
diff --git a/packages/create-svelte/shared/+typescript/svelte.config.js b/packages/create-svelte/shared/+typescript/svelte.config.js
index 1cf26a00dfea..2b35fe1befd3 100644
--- a/packages/create-svelte/shared/+typescript/svelte.config.js
+++ b/packages/create-svelte/shared/+typescript/svelte.config.js
@@ -1,5 +1,5 @@
import adapter from '@sveltejs/adapter-auto';
-import { vitePreprocess } from '@sveltejs/kit/vite';
+import { vitePreprocess } from '@sveltejs/vite-plugin-svelte';
/** @type {import('@sveltejs/kit').Config} */
const config = {
diff --git a/packages/create-svelte/shared/+vitest/package.json b/packages/create-svelte/shared/+vitest/package.json
index 58c77018dcf5..94385f64f0df 100644
--- a/packages/create-svelte/shared/+vitest/package.json
+++ b/packages/create-svelte/shared/+vitest/package.json
@@ -1,6 +1,6 @@
{
"devDependencies": {
- "vitest": "^0.34.0"
+ "vitest": "^1.0.0"
},
"scripts": {
"test": "vitest"
diff --git a/packages/create-svelte/templates/default/package.json b/packages/create-svelte/templates/default/package.json
index 48b47d940eb1..6983ef883100 100644
--- a/packages/create-svelte/templates/default/package.json
+++ b/packages/create-svelte/templates/default/package.json
@@ -11,12 +11,13 @@
"@neoconfetti/svelte": "^1.0.0",
"@sveltejs/adapter-auto": "workspace:*",
"@sveltejs/kit": "workspace:*",
- "svelte": "^4.2.7",
- "typescript": "^5.0.0",
- "vite": "^4.4.9"
+ "@sveltejs/vite-plugin-svelte": "^3.0.0",
+ "svelte": "^4.2.8",
+ "typescript": "^5.3.3",
+ "vite": "^5.0.8"
},
"type": "module",
"dependencies": {
- "@fontsource/fira-mono": "^5.0.5"
+ "@fontsource/fira-mono": "^5.0.8"
}
}
diff --git a/packages/create-svelte/templates/default/package.template.json b/packages/create-svelte/templates/default/package.template.json
index 02b891efa8c2..586d0f7e863b 100644
--- a/packages/create-svelte/templates/default/package.template.json
+++ b/packages/create-svelte/templates/default/package.template.json
@@ -10,9 +10,10 @@
"@fontsource/fira-mono": "^4.5.10",
"@neoconfetti/svelte": "^1.0.0",
"@sveltejs/adapter-auto": "^2.0.0",
- "@sveltejs/kit": "^1.27.4",
+ "@sveltejs/kit": "^2.0.0",
+ "@sveltejs/vite-plugin-svelte": "^3.0.0",
"svelte": "^4.2.7",
- "vite": "^4.4.2"
+ "vite": "^5.0.3"
},
"type": "module"
}
diff --git a/packages/create-svelte/templates/default/src/app.d.ts b/packages/create-svelte/templates/default/src/app.d.ts
index f59b884c51ed..743f07b2e50a 100644
--- a/packages/create-svelte/templates/default/src/app.d.ts
+++ b/packages/create-svelte/templates/default/src/app.d.ts
@@ -5,6 +5,7 @@ declare global {
// interface Error {}
// interface Locals {}
// interface PageData {}
+ // interface PageState {}
// interface Platform {}
}
}
diff --git a/packages/create-svelte/templates/default/src/routes/sverdle/+page.server.ts b/packages/create-svelte/templates/default/src/routes/sverdle/+page.server.ts
index 0bd1db1f264f..cc77d8d43913 100644
--- a/packages/create-svelte/templates/default/src/routes/sverdle/+page.server.ts
+++ b/packages/create-svelte/templates/default/src/routes/sverdle/+page.server.ts
@@ -45,7 +45,7 @@ export const actions = {
game.guesses[i] += key;
}
- cookies.set('sverdle', game.toString());
+ cookies.set('sverdle', game.toString(), { path: '' });
},
/**
@@ -62,10 +62,10 @@ export const actions = {
return fail(400, { badGuess: true });
}
- cookies.set('sverdle', game.toString());
+ cookies.set('sverdle', game.toString(), { path: '' });
},
restart: async ({ cookies }) => {
- cookies.delete('sverdle');
+ cookies.delete('sverdle', { path: '' });
}
} satisfies Actions;
diff --git a/packages/create-svelte/templates/default/svelte.config.js b/packages/create-svelte/templates/default/svelte.config.js
index e4597b125653..67ce801eb041 100644
--- a/packages/create-svelte/templates/default/svelte.config.js
+++ b/packages/create-svelte/templates/default/svelte.config.js
@@ -1,5 +1,5 @@
import adapter from '@sveltejs/adapter-auto';
-import { vitePreprocess } from '@sveltejs/kit/vite';
+import { vitePreprocess } from '@sveltejs/vite-plugin-svelte';
// This config is ignored and replaced with one of the configs in the shared folder when a project is created.
diff --git a/packages/create-svelte/templates/skeleton/package.template.json b/packages/create-svelte/templates/skeleton/package.template.json
index e37b4bff6665..fada20157b4a 100644
--- a/packages/create-svelte/templates/skeleton/package.template.json
+++ b/packages/create-svelte/templates/skeleton/package.template.json
@@ -9,9 +9,10 @@
},
"devDependencies": {
"@sveltejs/adapter-auto": "^2.0.0",
- "@sveltejs/kit": "^1.27.4",
+ "@sveltejs/kit": "^2.0.0",
+ "@sveltejs/vite-plugin-svelte": "^3.0.0",
"svelte": "^4.2.7",
- "vite": "^4.4.2"
+ "vite": "^5.0.3"
},
"type": "module"
}
diff --git a/packages/create-svelte/templates/skeleton/src/app.d.ts b/packages/create-svelte/templates/skeleton/src/app.d.ts
index f59b884c51ed..743f07b2e50a 100644
--- a/packages/create-svelte/templates/skeleton/src/app.d.ts
+++ b/packages/create-svelte/templates/skeleton/src/app.d.ts
@@ -5,6 +5,7 @@ declare global {
// interface Error {}
// interface Locals {}
// interface PageData {}
+ // interface PageState {}
// interface Platform {}
}
}
diff --git a/packages/create-svelte/templates/skeletonlib/package.template.json b/packages/create-svelte/templates/skeletonlib/package.template.json
index 0b6cda74d2e1..b5af7d05f186 100644
--- a/packages/create-svelte/templates/skeletonlib/package.template.json
+++ b/packages/create-svelte/templates/skeletonlib/package.template.json
@@ -20,13 +20,14 @@
},
"devDependencies": {
"@sveltejs/adapter-auto": "^2.0.0",
- "@sveltejs/kit": "^1.27.4",
+ "@sveltejs/kit": "^2.0.0",
"@sveltejs/package": "^2.0.0",
+ "@sveltejs/vite-plugin-svelte": "^3.0.0",
"publint": "^0.1.9",
"svelte": "^4.2.7",
"tslib": "^2.4.1",
- "typescript": "^5.0.0",
- "vite": "^4.4.2"
+ "typescript": "^5.3.2",
+ "vite": "^5.0.3"
},
"svelte": "./dist/index.js",
"types": "./dist/index.d.ts",
diff --git a/packages/create-svelte/templates/skeletonlib/src/app.d.ts b/packages/create-svelte/templates/skeletonlib/src/app.d.ts
index f59b884c51ed..743f07b2e50a 100644
--- a/packages/create-svelte/templates/skeletonlib/src/app.d.ts
+++ b/packages/create-svelte/templates/skeletonlib/src/app.d.ts
@@ -5,6 +5,7 @@ declare global {
// interface Error {}
// interface Locals {}
// interface PageData {}
+ // interface PageState {}
// interface Platform {}
}
}
diff --git a/packages/enhanced-img/package.json b/packages/enhanced-img/package.json
index f8cbe3180c32..5cbb1d4f2599 100644
--- a/packages/enhanced-img/package.json
+++ b/packages/enhanced-img/package.json
@@ -26,17 +26,18 @@
},
"types": "types/index.d.ts",
"dependencies": {
- "magic-string": "^0.30.0",
- "svelte-parse-markup": "^0.1.1",
- "vite-imagetools": "^6.2.4"
+ "magic-string": "^0.30.5",
+ "svelte-parse-markup": "^0.1.2",
+ "vite-imagetools": "^6.2.7"
},
"devDependencies": {
- "@types/estree": "^1.0.2",
- "@types/node": "^16.18.6",
+ "@types/estree": "^1.0.5",
+ "@types/node": "^18.19.3",
"estree-walker": "^3.0.3",
- "svelte": "^4.2.7",
- "typescript": "^4.9.4",
- "vite": "^4.4.2",
- "vitest": "^0.34.0"
+ "rollup": "^4.8.0",
+ "svelte": "^4.2.8",
+ "typescript": "^5.3.3",
+ "vite": "^5.0.8",
+ "vitest": "^1.0.4"
}
}
diff --git a/packages/enhanced-img/src/index.js b/packages/enhanced-img/src/index.js
index 438b1ced6260..48347ee6ae97 100644
--- a/packages/enhanced-img/src/index.js
+++ b/packages/enhanced-img/src/index.js
@@ -24,7 +24,7 @@ export async function enhancedImages() {
function image_plugin(imagetools_plugin) {
/**
* @type {{
- * plugin_context: import('rollup').PluginContext
+ * plugin_context: import('vite').Rollup.PluginContext
* imagetools_plugin: import('vite').Plugin
* }}
*/
diff --git a/packages/enhanced-img/src/preprocessor.js b/packages/enhanced-img/src/preprocessor.js
index bf1289a25878..e67adfe8b5bb 100644
--- a/packages/enhanced-img/src/preprocessor.js
+++ b/packages/enhanced-img/src/preprocessor.js
@@ -9,7 +9,7 @@ const OPTIMIZABLE = /^[^?]+\.(avif|heif|gif|jpeg|jpg|png|tiff|webp)(\?.*)?$/;
/**
* @param {{
- * plugin_context: import('rollup').PluginContext
+ * plugin_context: import('vite').Rollup.PluginContext
* imagetools_plugin: import('vite').Plugin
* }} opts
* @returns {import('svelte/types/compiler/preprocess').PreprocessorGroup}
@@ -144,7 +144,7 @@ function is_quote(content, index) {
/**
* @param {{
- * plugin_context: import('rollup').PluginContext
+ * plugin_context: import('vite').Rollup.PluginContext
* imagetools_plugin: import('vite').Plugin
* }} opts
* @param {string} url
diff --git a/packages/enhanced-img/tsconfig.json b/packages/enhanced-img/tsconfig.json
index 638484204f12..0ff94d9e3f38 100644
--- a/packages/enhanced-img/tsconfig.json
+++ b/packages/enhanced-img/tsconfig.json
@@ -5,8 +5,8 @@
"noEmit": true,
"strict": true,
"target": "es2022",
- "module": "es2022",
- "moduleResolution": "node",
+ "module": "node16",
+ "moduleResolution": "node16",
"allowSyntheticDefaultImports": true,
"paths": {
"types": ["./types/index"],
diff --git a/packages/kit/package.json b/packages/kit/package.json
index ba46dd8c0ba2..ff775f1e8364 100644
--- a/packages/kit/package.json
+++ b/packages/kit/package.json
@@ -11,37 +11,37 @@
"homepage": "https://kit.svelte.dev",
"type": "module",
"dependencies": {
- "@sveltejs/vite-plugin-svelte": "^2.5.0",
- "@types/cookie": "^0.5.1",
- "cookie": "^0.5.0",
- "devalue": "^4.3.1",
+ "@types/cookie": "^0.6.0",
+ "cookie": "^0.6.0",
+ "devalue": "^4.3.2",
"esm-env": "^1.0.0",
"kleur": "^4.1.5",
- "magic-string": "^0.30.0",
+ "magic-string": "^0.30.5",
"mrmime": "^1.0.1",
"sade": "^1.8.1",
"set-cookie-parser": "^2.6.0",
- "sirv": "^2.0.2",
- "tiny-glob": "^0.2.9",
- "undici": "~5.26.2"
+ "sirv": "^2.0.3",
+ "tiny-glob": "^0.2.9"
},
"devDependencies": {
"@playwright/test": "1.30.0",
- "@types/connect": "^3.4.35",
- "@types/node": "^16.18.6",
- "@types/sade": "^1.7.4",
- "@types/set-cookie-parser": "^2.4.2",
- "dts-buddy": "^0.2.4",
- "rollup": "^3.29.4",
- "svelte": "^4.2.7",
- "svelte-preprocess": "^5.1.1",
- "typescript": "^4.9.4",
- "vite": "^4.4.9",
- "vitest": "^0.34.5"
+ "@sveltejs/vite-plugin-svelte": "^3.0.1",
+ "@types/connect": "^3.4.38",
+ "@types/node": "^18.19.3",
+ "@types/sade": "^1.7.8",
+ "@types/set-cookie-parser": "^2.4.7",
+ "dts-buddy": "^0.4.3",
+ "rollup": "^4.8.0",
+ "svelte": "^4.2.8",
+ "svelte-preprocess": "^5.1.2",
+ "typescript": "^5.3.3",
+ "vite": "^5.0.8",
+ "vitest": "^1.0.4"
},
"peerDependencies": {
- "svelte": "^3.54.0 || ^4.0.0-next.0 || ^5.0.0-next.0",
- "vite": "^4.0.0"
+ "@sveltejs/vite-plugin-svelte": "^3.0.0",
+ "svelte": "^4.0.0 || ^5.0.0-next.0",
+ "vite": "^5.0.3"
},
"bin": {
"svelte-kit": "svelte-kit.js"
@@ -57,7 +57,7 @@
],
"scripts": {
"lint": "prettier --config ../../.prettierrc --check .",
- "check": "tsc",
+ "check": "tsc && cd ./test/types && tsc",
"check:all": "tsc && pnpm -r --filter=\"./**\" check",
"format": "prettier --config ../../.prettierrc --write .",
"test": "pnpm test:unit && pnpm test:integration",
@@ -95,6 +95,6 @@
},
"types": "types/index.d.ts",
"engines": {
- "node": "^16.14 || >=18"
+ "node": ">=18.13"
}
}
diff --git a/packages/kit/src/core/adapt/builder.js b/packages/kit/src/core/adapt/builder.js
index 11fd9cbc1fd6..2c55f5027485 100644
--- a/packages/kit/src/core/adapt/builder.js
+++ b/packages/kit/src/core/adapt/builder.js
@@ -156,6 +156,13 @@ export function create_builder({
write(dest, fallback);
},
+ generateEnvModule() {
+ const dest = `${config.kit.outDir}/output/prerendered/dependencies/${config.kit.appDir}/env.js`;
+ const env = get_env(config.kit.env, vite_config.mode);
+
+ write(dest, `export const env=${JSON.stringify(env.public)}`);
+ },
+
generateManifest({ relativePath, routes: subset }) {
return generate_manifest({
build_data,
diff --git a/packages/kit/src/core/config/index.js b/packages/kit/src/core/config/index.js
index a6e37d794277..cb2a44670bfd 100644
--- a/packages/kit/src/core/config/index.js
+++ b/packages/kit/src/core/config/index.js
@@ -69,7 +69,15 @@ export async function load_config({ cwd = process.cwd() } = {}) {
const config = await import(`${url.pathToFileURL(config_file).href}?ts=${Date.now()}`);
- return process_config(config.default, { cwd });
+ try {
+ return process_config(config.default, { cwd });
+ } catch (e) {
+ const error = /** @type {Error} */ (e);
+
+ // redact the stack trace — it's not helpful to users
+ error.stack = `Could not load svelte.config.js: ${error.message}\n`;
+ throw error;
+ }
}
/**
diff --git a/packages/kit/src/core/config/index.spec.js b/packages/kit/src/core/config/index.spec.js
index faaf138aebb3..673515c60802 100644
--- a/packages/kit/src/core/config/index.spec.js
+++ b/packages/kit/src/core/config/index.spec.js
@@ -69,9 +69,6 @@ const get_defaults = (prefix = '') => ({
csrf: {
checkOrigin: true
},
- dangerZone: {
- trackServerFetches: false
- },
embedded: false,
env: {
dir: process.cwd(),
@@ -102,7 +99,7 @@ const get_defaults = (prefix = '') => ({
paths: {
base: '',
assets: '',
- relative: undefined
+ relative: true
},
prerender: {
concurrency: 1,
@@ -321,7 +318,7 @@ validate_paths(
{
base: '/path/to/base',
assets: '',
- relative: undefined
+ relative: true
}
);
@@ -333,7 +330,7 @@ validate_paths(
{
base: '',
assets: 'https://cdn.example.com',
- relative: undefined
+ relative: true
}
);
@@ -346,7 +343,7 @@ validate_paths(
{
base: '/path/to/base',
assets: 'https://cdn.example.com',
- relative: undefined
+ relative: true
}
);
diff --git a/packages/kit/src/core/config/options.js b/packages/kit/src/core/config/options.js
index 2d0c928d9290..dbbb19d97cce 100644
--- a/packages/kit/src/core/config/options.js
+++ b/packages/kit/src/core/config/options.js
@@ -111,11 +111,6 @@ const options = object(
checkOrigin: boolean(true)
}),
- dangerZone: object({
- // TODO 2.0: Remove this
- trackServerFetches: boolean(false)
- }),
-
embedded: boolean(false),
env: object({
@@ -179,13 +174,7 @@ const options = object(
return input;
}),
- relative: validate(undefined, (input, keypath) => {
- if (typeof input !== 'boolean') {
- throw new Error(`${keypath} option must be a boolean or undefined`);
- }
-
- return input;
- })
+ relative: boolean(true)
}),
prerender: object({
diff --git a/packages/kit/src/core/postbuild/analyse.js b/packages/kit/src/core/postbuild/analyse.js
index 6cfd975969c7..9dcb0c432c82 100644
--- a/packages/kit/src/core/postbuild/analyse.js
+++ b/packages/kit/src/core/postbuild/analyse.js
@@ -10,11 +10,10 @@ import {
} from '../../utils/exports.js';
import { load_config } from '../config/index.js';
import { forked } from '../../utils/fork.js';
-import { should_polyfill } from '../../utils/platform.js';
import { installPolyfills } from '../../exports/node/polyfills.js';
-import { resolvePath } from '../../exports/index.js';
import { ENDPOINT_METHODS } from '../../constants.js';
import { filter_private_env, filter_public_env } from '../../utils/env.js';
+import { resolve_route } from '../../utils/routing.js';
export default forked(import.meta.url, analyse);
@@ -36,9 +35,7 @@ async function analyse({ manifest_path, env }) {
/** @type {import('types').ServerInternalModule} */
const internal = await import(pathToFileURL(`${server_root}/server/internal.js`).href);
- if (should_polyfill) {
- installPolyfills();
- }
+ installPolyfills();
// configure `import { building } from '$app/environment'` —
// essential we do this before analysing the code
@@ -46,8 +43,11 @@ async function analyse({ manifest_path, env }) {
// set env, in case it's used in initialisation
const { publicPrefix: public_prefix, privatePrefix: private_prefix } = config.env;
- internal.set_private_env(filter_private_env(env, { public_prefix, private_prefix }));
- internal.set_public_env(filter_public_env(env, { public_prefix, private_prefix }));
+ const private_env = filter_private_env(env, { public_prefix, private_prefix });
+ const public_env = filter_public_env(env, { public_prefix, private_prefix });
+ internal.set_private_env(private_env);
+ internal.set_public_env(public_env);
+ internal.set_safe_public_env(public_env);
/** @type {import('types').ServerMetadata} */
const metadata = {
@@ -55,10 +55,10 @@ async function analyse({ manifest_path, env }) {
routes: new Map()
};
- // analyse nodes
- for (const loader of manifest._.nodes) {
- const node = await loader();
+ const nodes = await Promise.all(manifest._.nodes.map((loader) => loader()));
+ // analyse nodes
+ for (const node of nodes) {
metadata.nodes[node.index] = {
has_server_load: node.server?.load !== undefined || node.server?.trailingSlash !== undefined
};
@@ -66,78 +66,35 @@ async function analyse({ manifest_path, env }) {
// analyse routes
for (const route of manifest._.routes) {
- /** @type {Array<'GET' | 'POST'>} */
- const page_methods = [];
-
- /** @type {(import('types').HttpMethod | '*')[]} */
- const api_methods = [];
+ const page =
+ route.page &&
+ analyse_page(
+ route.page.layouts.map((n) => (n === undefined ? n : nodes[n])),
+ nodes[route.page.leaf]
+ );
- /** @type {import('types').PrerenderOption | undefined} */
- let prerender = undefined;
- /** @type {any} */
- let config = undefined;
- /** @type {import('types').PrerenderEntryGenerator | undefined} */
- let entries = undefined;
+ const endpoint = route.endpoint && analyse_endpoint(route, await route.endpoint());
- if (route.endpoint) {
- const mod = await route.endpoint();
- if (mod.prerender !== undefined) {
- validate_server_exports(mod, route.id);
+ if (page?.prerender && endpoint?.prerender) {
+ throw new Error(`Cannot prerender a route with both +page and +server files (${route.id})`);
+ }
- if (mod.prerender && (mod.POST || mod.PATCH || mod.PUT || mod.DELETE)) {
+ if (page?.config && endpoint?.config) {
+ for (const key in { ...page.config, ...endpoint.config }) {
+ if (JSON.stringify(page.config[key]) !== JSON.stringify(endpoint.config[key])) {
throw new Error(
- `Cannot prerender a +server file with POST, PATCH, PUT, or DELETE (${route.id})`
+ `Mismatched route config for ${route.id} — the +page and +server files must export the same config, if any`
);
}
-
- prerender = mod.prerender;
- }
-
- for (const method of /** @type {import('types').HttpMethod[]} */ (ENDPOINT_METHODS)) {
- if (mod[method]) api_methods.push(method);
}
-
- if (mod.fallback) {
- api_methods.push('*');
- }
-
- config = mod.config;
- entries = mod.entries;
}
- if (route.page) {
- const nodes = await Promise.all(
- [...route.page.layouts, route.page.leaf].map((n) => {
- if (n !== undefined) return manifest._.nodes[n]();
- })
- );
-
- const layouts = nodes.slice(0, -1);
- const page = nodes.at(-1);
-
- for (const layout of layouts) {
- if (layout) {
- validate_layout_server_exports(layout.server, layout.server_id);
- validate_layout_exports(layout.universal, layout.universal_id);
- }
- }
-
- if (page) {
- page_methods.push('GET');
- if (page.server?.actions) page_methods.push('POST');
-
- validate_page_server_exports(page.server, page.server_id);
- validate_page_exports(page.universal, page.universal_id);
- }
-
- prerender = get_option(nodes, 'prerender') ?? false;
-
- config = get_config(nodes);
- entries ??= get_option(nodes, 'entries');
- }
+ const page_methods = page?.methods ?? [];
+ const api_methods = endpoint?.methods ?? [];
+ const entries = page?.entries ?? endpoint?.entries;
metadata.routes.set(route.id, {
- config,
+ config: page?.config ?? endpoint?.config,
methods: Array.from(new Set([...page_methods, ...api_methods])),
page: {
methods: page_methods
@@ -145,29 +102,90 @@ async function analyse({ manifest_path, env }) {
api: {
methods: api_methods
},
- prerender,
+ prerender: page?.prerender ?? endpoint?.prerender,
entries:
- entries && (await entries()).map((entry_object) => resolvePath(route.id, entry_object))
+ entries && (await entries()).map((entry_object) => resolve_route(route.id, entry_object))
});
}
return metadata;
}
+/**
+ * @param {import('types').SSRRoute} route
+ * @param {import('types').SSREndpoint} mod
+ */
+function analyse_endpoint(route, mod) {
+ validate_server_exports(mod, route.id);
+
+ if (mod.prerender && (mod.POST || mod.PATCH || mod.PUT || mod.DELETE)) {
+ throw new Error(
+ `Cannot prerender a +server file with POST, PATCH, PUT, or DELETE (${route.id})`
+ );
+ }
+
+ /** @type {Array} */
+ const methods = [];
+
+ 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,
+ entries: mod.entries,
+ methods,
+ prerender: mod.prerender ?? false
+ };
+}
+
+/**
+ * @param {Array} layouts
+ * @param {import('types').SSRNode} leaf
+ */
+function analyse_page(layouts, leaf) {
+ for (const layout of layouts) {
+ if (layout) {
+ validate_layout_server_exports(layout.server, layout.server_id);
+ validate_layout_exports(layout.universal, layout.universal_id);
+ }
+ }
+
+ /** @type {Array<'GET' | 'POST'>} */
+ const methods = ['GET'];
+ if (leaf.server?.actions) methods.push('POST');
+
+ validate_page_server_exports(leaf.server, leaf.server_id);
+ validate_page_exports(leaf.universal, leaf.universal_id);
+
+ return {
+ config: get_config([...layouts, leaf]),
+ entries: leaf.universal?.entries ?? leaf.server?.entries,
+ methods,
+ prerender: get_option([...layouts, leaf], 'prerender') ?? false
+ };
+}
+
/**
* Do a shallow merge (first level) of the config object
* @param {Array} nodes
*/
function get_config(nodes) {
+ /** @type {any} */
let current = {};
+
for (const node of nodes) {
- const config = node?.universal?.config ?? node?.server?.config;
- if (config) {
- current = {
- ...current,
- ...config
- };
- }
+ if (!node?.universal?.config && !node?.server?.config) continue;
+
+ current = {
+ ...current,
+ ...node?.universal?.config,
+ ...node?.server?.config
+ };
}
return Object.keys(current).length ? current : undefined;
diff --git a/packages/kit/src/core/postbuild/fixtures/base/output.json b/packages/kit/src/core/postbuild/fixtures/base/output.json
index af7a9e70a93c..203973f65687 100644
--- a/packages/kit/src/core/postbuild/fixtures/base/output.json
+++ b/packages/kit/src/core/postbuild/fixtures/base/output.json
@@ -2,7 +2,7 @@
"hrefs": [
"/base-path/styles.css",
"/base-path/favicon.png",
- "https://external.com",
+ "https://external.com/",
"/base-path/wheee",
"/base-path/wheee2",
"/base-path/wheee3",
diff --git a/packages/kit/src/core/postbuild/fixtures/basic-href/output.json b/packages/kit/src/core/postbuild/fixtures/basic-href/output.json
index 415f7b38671f..48b9db674814 100644
--- a/packages/kit/src/core/postbuild/fixtures/basic-href/output.json
+++ b/packages/kit/src/core/postbuild/fixtures/basic-href/output.json
@@ -2,7 +2,7 @@
"hrefs": [
"/styles.css",
"/favicon.png",
- "https://external.com",
+ "https://external.com/",
"/wheee",
"/wheee2",
"/wheee3",
diff --git a/packages/kit/src/core/postbuild/fixtures/href-with-character-reference/output.json b/packages/kit/src/core/postbuild/fixtures/href-with-character-reference/output.json
index eb649d122431..0ac291842779 100644
--- a/packages/kit/src/core/postbuild/fixtures/href-with-character-reference/output.json
+++ b/packages/kit/src/core/postbuild/fixtures/href-with-character-reference/output.json
@@ -3,9 +3,9 @@
"/styles.css",
"/favicon.png",
"/test&ersand",
- "/test\"quotation",
- "/test♡decimal",
- "/test♥hex"
+ "/test%22quotation",
+ "/test%E2%99%A1decimal",
+ "/test%E2%99%A5hex"
],
"ids": []
}
diff --git a/packages/kit/src/core/postbuild/fixtures/ids/output.json b/packages/kit/src/core/postbuild/fixtures/ids/output.json
index e749d3128d7e..750406f0dbcc 100644
--- a/packages/kit/src/core/postbuild/fixtures/ids/output.json
+++ b/packages/kit/src/core/postbuild/fixtures/ids/output.json
@@ -1,4 +1,4 @@
{
- "hrefs": ["/#encöded"],
+ "hrefs": ["/#enc%C3%B6ded"],
"ids": ["before", "after", "encöded"]
}
diff --git a/packages/kit/src/core/postbuild/fixtures/include-rel-external/output.json b/packages/kit/src/core/postbuild/fixtures/include-rel-external/output.json
index 1d2f9337af06..b389f1b5a3e7 100644
--- a/packages/kit/src/core/postbuild/fixtures/include-rel-external/output.json
+++ b/packages/kit/src/core/postbuild/fixtures/include-rel-external/output.json
@@ -1,4 +1,4 @@
{
- "hrefs": ["/styles.css", "/favicon.png", "https://external.com"],
+ "hrefs": ["/styles.css", "/favicon.png", "https://external.com/"],
"ids": []
}
diff --git a/packages/kit/src/core/postbuild/fixtures/meta/output.json b/packages/kit/src/core/postbuild/fixtures/meta/output.json
index 76cfe72ada91..2e5881e7521d 100644
--- a/packages/kit/src/core/postbuild/fixtures/meta/output.json
+++ b/packages/kit/src/core/postbuild/fixtures/meta/output.json
@@ -1,4 +1,9 @@
{
- "hrefs": ["https://external.com", "/og-image.jpg", "https://example.com/audio.mp3", "/video.mp4"],
+ "hrefs": [
+ "https://external.com/",
+ "/og-image.jpg",
+ "https://example.com/audio.mp3",
+ "/video.mp4"
+ ],
"ids": []
}
diff --git a/packages/kit/src/core/postbuild/fixtures/unquoted-attributes/output.json b/packages/kit/src/core/postbuild/fixtures/unquoted-attributes/output.json
index 2f95abb62dd4..d2d68c5d41e0 100644
--- a/packages/kit/src/core/postbuild/fixtures/unquoted-attributes/output.json
+++ b/packages/kit/src/core/postbuild/fixtures/unquoted-attributes/output.json
@@ -1,4 +1,4 @@
{
- "hrefs": ["/styles.css", "/favicon.png", "https://external.com", "/wheee"],
+ "hrefs": ["/styles.css", "/favicon.png", "https://external.com/", "/wheee"],
"ids": []
}
diff --git a/packages/kit/src/core/postbuild/prerender.js b/packages/kit/src/core/postbuild/prerender.js
index e64b7f852364..f6e3e2315f37 100644
--- a/packages/kit/src/core/postbuild/prerender.js
+++ b/packages/kit/src/core/postbuild/prerender.js
@@ -3,7 +3,6 @@ import { dirname, join } from 'node:path';
import { pathToFileURL } from 'node:url';
import { installPolyfills } from '../../exports/node/polyfills.js';
import { mkdirp, posixify, walk } from '../../utils/filesystem.js';
-import { should_polyfill } from '../../utils/platform.js';
import { decode_uri, is_root_relative, resolve } from '../../utils/url.js';
import { escape_html_attr } from '../../utils/escape.js';
import { logger } from '../utils.js';
@@ -94,9 +93,7 @@ async function prerender({ out, manifest_path, metadata, verbose, env }) {
/** @type {import('types').Logger} */
const log = logger({ verbose });
- if (should_polyfill) {
- installPolyfills();
- }
+ installPolyfills();
/** @type {Map} */
const saved = new Map();
@@ -153,6 +150,7 @@ async function prerender({ out, manifest_path, metadata, verbose, env }) {
}
const files = new Set(walk(`${out}/client`).map(posixify));
+ files.add(`${config.appDir}/env.js`);
const immutable = `${config.appDir}/immutable`;
if (existsSync(`${out}/server/${immutable}`)) {
@@ -429,8 +427,12 @@ async function prerender({ out, manifest_path, metadata, verbose, env }) {
if (entry === '*') {
for (const [id, prerender] of prerender_map) {
if (prerender) {
- if (id.includes('[')) continue;
- const path = `/${get_route_segments(id).join('/')}`;
+ // remove optional parameters from the route
+ const segments = get_route_segments(id).filter((segment) => !segment.startsWith('[['));
+ const processed_id = '/' + segments.join('/');
+
+ if (processed_id.includes('[')) continue;
+ const path = `/${get_route_segments(processed_id).join('/')}`;
enqueue(null, config.paths.base + path);
}
}
@@ -472,10 +474,10 @@ async function prerender({ out, manifest_path, metadata, verbose, env }) {
}
if (not_prerendered.length > 0) {
+ const list = not_prerendered.map((id) => ` - ${id}`).join('\n');
+
throw new Error(
- `The following routes were marked as prerenderable, but were not prerendered because they were not found while crawling your app:\n${not_prerendered.map(
- (id) => ` - ${id}`
- )}\n\nSee https://kit.svelte.dev/docs/page-options#prerender-troubleshooting for info on how to solve this`
+ `The following routes were marked as prerenderable, but were not prerendered because they were not found while crawling your app:\n${list}\n\nSee https://kit.svelte.dev/docs/page-options#prerender-troubleshooting for info on how to solve this`
);
}
diff --git a/packages/kit/src/core/sync/sync.js b/packages/kit/src/core/sync/sync.js
index 3d1f0f9e57e9..d16466818f40 100644
--- a/packages/kit/src/core/sync/sync.js
+++ b/packages/kit/src/core/sync/sync.js
@@ -5,6 +5,7 @@ import { write_root } from './write_root.js';
import { write_tsconfig } from './write_tsconfig.js';
import { write_types, write_all_types } from './write_types/index.js';
import { write_ambient } from './write_ambient.js';
+import { write_non_ambient } from './write_non_ambient.js';
import { write_server } from './write_server.js';
/**
@@ -15,6 +16,7 @@ import { write_server } from './write_server.js';
export function init(config, mode) {
write_tsconfig(config.kit);
write_ambient(config.kit, mode);
+ write_non_ambient(config.kit);
}
/**
diff --git a/packages/kit/src/core/sync/write_non_ambient.js b/packages/kit/src/core/sync/write_non_ambient.js
new file mode 100644
index 000000000000..a191495ac6f2
--- /dev/null
+++ b/packages/kit/src/core/sync/write_non_ambient.js
@@ -0,0 +1,42 @@
+import path from 'node:path';
+import { GENERATED_COMMENT } from '../../constants.js';
+import { write_if_changed } from './utils.js';
+
+// `declare module "svelte/elements"` needs to happen in a non-ambient module, and dts-buddy generates one big ambient module,
+// so we can't add it there - therefore generate the typings ourselves here.
+// We're not using the `declare namespace svelteHTML` variant because that one doesn't augment the HTMLAttributes interface
+// people could use to type their own components.
+// The T generic is needed or else there's a "all declarations must have identical type parameters" error.
+const template = `
+${GENERATED_COMMENT}
+
+declare module "svelte/elements" {
+ export interface HTMLAttributes {
+ 'data-sveltekit-keepfocus'?: true | '' | 'off' | undefined | null;
+ 'data-sveltekit-noscroll'?: true | '' | 'off' | undefined | null;
+ 'data-sveltekit-preload-code'?:
+ | true
+ | ''
+ | 'eager'
+ | 'viewport'
+ | 'hover'
+ | 'tap'
+ | 'off'
+ | undefined
+ | null;
+ 'data-sveltekit-preload-data'?: true | '' | 'hover' | 'tap' | 'off' | undefined | null;
+ 'data-sveltekit-reload'?: true | '' | 'off' | undefined | null;
+ 'data-sveltekit-replacestate'?: true | '' | 'off' | undefined | null;
+ }
+}
+
+export {};
+`;
+
+/**
+ * Writes non-ambient declarations to the output directory
+ * @param {import('types').ValidatedKitConfig} config
+ */
+export function write_non_ambient(config) {
+ write_if_changed(path.join(config.outDir, 'non-ambient.d.ts'), template);
+}
diff --git a/packages/kit/src/core/sync/write_server.js b/packages/kit/src/core/sync/write_server.js
index f3d29dfbb20b..8971f82aa871 100644
--- a/packages/kit/src/core/sync/write_server.js
+++ b/packages/kit/src/core/sync/write_server.js
@@ -28,13 +28,13 @@ const server_template = ({
import root from '../root.${isSvelte5Plus() ? 'js' : 'svelte'}';
import { set_building } from '__sveltekit/environment';
import { set_assets } from '__sveltekit/paths';
-import { set_private_env, set_public_env } from '${runtime_directory}/shared-server.js';
+import { set_private_env, set_public_env, set_safe_public_env } from '${runtime_directory}/shared-server.js';
export const options = {
+ app_dir: ${s(config.kit.appDir)},
app_template_contains_nonce: ${template.includes('%sveltekit.nonce%')},
csp: ${s(config.kit.csp)},
csrf_check_origin: ${s(config.kit.csrf.checkOrigin)},
- track_server_fetches: ${s(config.kit.dangerZone.trackServerFetches)},
embedded: ${config.kit.embedded},
env_public_prefix: '${config.kit.env.publicPrefix}',
env_private_prefix: '${config.kit.env.privatePrefix}',
@@ -63,7 +63,7 @@ export function get_hooks() {
return ${hooks ? `import(${s(hooks)})` : '{}'};
}
-export { set_assets, set_building, set_private_env, set_public_env };
+export { set_assets, set_building, set_private_env, set_public_env, set_safe_public_env };
`;
// TODO need to re-run this whenever src/app.html or src/error.html are
diff --git a/packages/kit/src/core/sync/write_tsconfig.js b/packages/kit/src/core/sync/write_tsconfig.js
index 92e7daf53cc5..8e2747436f42 100644
--- a/packages/kit/src/core/sync/write_tsconfig.js
+++ b/packages/kit/src/core/sync/write_tsconfig.js
@@ -3,7 +3,6 @@ import path from 'node:path';
import colors from 'kleur';
import { posixify } from '../../utils/filesystem.js';
import { write_if_changed } from './utils.js';
-import { ts } from './ts.js';
/**
* @param {string} cwd
@@ -42,51 +41,22 @@ export function write_tsconfig(kit, cwd = process.cwd()) {
const out = path.join(kit.outDir, 'tsconfig.json');
const user_config = load_user_tsconfig(cwd);
- if (user_config) validate_user_config(kit, cwd, out, user_config);
-
- // only specify baseUrl if a) the user doesn't specify their own baseUrl
- // and b) they have non-relative paths. this causes problems with auto-imports,
- // so we print a suggestion that they use relative paths instead
- // TODO(v2): never include base URL, and skip the check below
- let include_base_url = false;
-
- if (user_config && !user_config.options.compilerOptions?.baseUrl) {
- const non_relative_paths = new Set();
- for (const paths of Object.values(user_config?.options.compilerOptions?.paths || {})) {
- for (const path of paths) {
- if (!path.startsWith('.')) non_relative_paths.add(path);
- }
- }
-
- if (non_relative_paths.size) {
- include_base_url = true;
-
- console.log(colors.bold().yellow('Please replace non-relative compilerOptions.paths:\n'));
+ if (user_config) validate_user_config(cwd, out, user_config);
- for (const path of non_relative_paths) {
- console.log(` - "${path}" -> "./${path}"`);
- }
-
- console.log(
- '\nDoing so allows us to omit "baseUrl" — which causes problems with imports — from the generated tsconfig.json. See https://github.com/sveltejs/kit/pull/8437 for more information.'
- );
- }
- }
-
- write_if_changed(out, JSON.stringify(get_tsconfig(kit, include_base_url), null, '\t'));
+ write_if_changed(out, JSON.stringify(get_tsconfig(kit), null, '\t'));
}
/**
* Generates the tsconfig that the user's tsconfig inherits from.
* @param {import('types').ValidatedKitConfig} kit
- * @param {boolean} include_base_url
*/
-export function get_tsconfig(kit, include_base_url) {
+export function get_tsconfig(kit) {
/** @param {string} file */
const config_relative = (file) => posixify(path.relative(kit.outDir, file));
const include = new Set([
'ambient.d.ts',
+ 'non-ambient.d.ts',
'./types/**/$types.d.ts',
config_relative('vite.config.js'),
config_relative('vite.config.ts')
@@ -110,7 +80,7 @@ export function get_tsconfig(kit, include_base_url) {
include.add(config_relative(`${test_folder}/**/*.ts`));
include.add(config_relative(`${test_folder}/**/*.svelte`));
- const exclude = [config_relative('node_modules/**'), './[!ambient.d.ts]**'];
+ const exclude = [config_relative('node_modules/**')];
if (path.extname(kit.files.serviceWorker)) {
exclude.push(config_relative(kit.files.serviceWorker));
} else {
@@ -122,30 +92,25 @@ export function get_tsconfig(kit, include_base_url) {
const config = {
compilerOptions: {
// generated options
- baseUrl: include_base_url ? config_relative('.') : undefined,
- paths: get_tsconfig_paths(kit, include_base_url),
+ paths: get_tsconfig_paths(kit),
rootDirs: [config_relative('.'), './types'],
// essential options
// svelte-preprocess cannot figure out whether you have a value or a type, so tell TypeScript
// to enforce using \`import type\` instead of \`import\` for Types.
- importsNotUsedAsValues: 'error',
+ // Also, TypeScript doesn't know about import usages in the template because it only sees the
+ // script of a Svelte file. Therefore preserve all value imports.
+ verbatimModuleSyntax: true,
// Vite compiles modules one at a time
isolatedModules: true,
- // TypeScript doesn't know about import usages in the template because it only sees the
- // script of a Svelte file. Therefore preserve all value imports. Requires TS 4.5 or higher.
- preserveValueImports: true,
// This is required for svelte-package to work as expected
// Can be overwritten
lib: ['esnext', 'DOM', 'DOM.Iterable'],
- moduleResolution: 'node', // TODO change to "bundler" in SvelteKit v2
+ moduleResolution: 'bundler',
module: 'esnext',
noEmit: true, // prevent tsconfig error "overwriting input files" - Vite handles the build and ignores this
- target: 'esnext',
-
- // TODO(v2): use the new flag verbatimModuleSyntax instead (requires support by Vite/Esbuild)
- ignoreDeprecations: ts && Number(ts.version.split('.')[0]) >= 5 ? '5.0' : undefined
+ target: 'esnext'
},
include: [...include],
exclude
@@ -170,12 +135,11 @@ function load_user_tsconfig(cwd) {
}
/**
- * @param {import('types').ValidatedKitConfig} kit
* @param {string} cwd
* @param {string} out
* @param {{ kind: string, options: any }} config
*/
-function validate_user_config(kit, cwd, out, config) {
+function validate_user_config(cwd, out, config) {
// we need to check that the user's tsconfig extends the framework config
const extend = config.options.extends;
const extends_framework_config =
@@ -188,29 +152,17 @@ function validate_user_config(kit, cwd, out, config) {
const options = config.options.compilerOptions || {};
if (extends_framework_config) {
- const { paths: user_paths } = options;
-
- if (user_paths && fs.existsSync(kit.files.lib)) {
- /** @type {string[]} */
- const lib = user_paths['$lib'] || [];
- /** @type {string[]} */
- const lib_ = user_paths['$lib/*'] || [];
-
- // TODO(v2): check needs to be adjusted when we remove the base path
- const missing_lib_paths =
- !lib.some((relative) => path.resolve(cwd, relative) === kit.files.lib) ||
- !lib_.some((relative) => path.resolve(cwd, relative) === path.join(kit.files.lib, '/*'));
-
- if (missing_lib_paths) {
- console.warn(
- colors
- .bold()
- .yellow(`Your compilerOptions.paths in ${config.kind} should include the following:`)
- );
- let relative = posixify(path.relative('.', kit.files.lib));
- if (!relative.startsWith('.')) relative = `./${relative}`;
- console.warn(`{\n "$lib":["${relative}"],\n "$lib/*":["${relative}/*"]\n}`);
- }
+ const { paths, baseUrl } = options;
+
+ if (baseUrl || paths) {
+ console.warn(
+ colors
+ .bold()
+ .yellow(
+ `You have specified a baseUrl and/or paths in your ${config.kind} which interferes with SvelteKit's auto-generated tsconfig.json. ` +
+ 'Remove it to avoid problems with intellisense. For path aliases, use `kit.alias` instead: https://kit.svelte.dev/docs/configuration#alias'
+ )
+ );
}
} else {
let relative = posixify(path.relative('.', out));
@@ -235,9 +187,8 @@ const value_regex = /^(.*?)((\/\*)|(\.\w+))?$/;
* Related to vite alias creation.
*
* @param {import('types').ValidatedKitConfig} config
- * @param {boolean} include_base_url
*/
-function get_tsconfig_paths(config, include_base_url) {
+function get_tsconfig_paths(config) {
/** @param {string} file */
const config_relative = (file) => posixify(path.relative(config.outDir, file));
@@ -256,9 +207,7 @@ function get_tsconfig_paths(config, include_base_url) {
const value_match = value_regex.exec(value);
if (!value_match) throw new Error(`Invalid alias value: ${value}`);
- const rel_path = (include_base_url ? project_relative : config_relative)(
- remove_trailing_slashstar(value)
- );
+ const rel_path = config_relative(remove_trailing_slashstar(value));
const slashstar = key_match[2];
if (slashstar) {
diff --git a/packages/kit/src/core/sync/write_tsconfig.spec.js b/packages/kit/src/core/sync/write_tsconfig.spec.js
index 6efa9368791e..fbfa18cc97b9 100644
--- a/packages/kit/src/core/sync/write_tsconfig.spec.js
+++ b/packages/kit/src/core/sync/write_tsconfig.spec.js
@@ -14,7 +14,7 @@ test('Creates tsconfig path aliases from kit.alias', () => {
}
});
- const { compilerOptions } = get_tsconfig(kit, false);
+ const { compilerOptions } = get_tsconfig(kit);
// $lib isn't part of the outcome because there's a "path exists"
// check in the implementation
@@ -27,31 +27,6 @@ test('Creates tsconfig path aliases from kit.alias', () => {
});
});
-test('Creates tsconfig path aliases from kit.alias with existing baseUrl', () => {
- const { kit } = validate_config({
- kit: {
- alias: {
- simpleKey: 'simple/value',
- key: 'value',
- 'key/*': 'some/other/value/*',
- keyToFile: 'path/to/file.ts'
- }
- }
- });
-
- const { compilerOptions } = get_tsconfig(kit, true);
-
- // $lib isn't part of the outcome because there's a "path exists"
- // check in the implementation
- expect(compilerOptions.paths).toEqual({
- simpleKey: ['simple/value'],
- 'simpleKey/*': ['simple/value/*'],
- key: ['value'],
- 'key/*': ['some/other/value/*'],
- keyToFile: ['path/to/file.ts']
- });
-});
-
test('Allows generated tsconfig to be mutated', () => {
const { kit } = validate_config({
kit: {
@@ -63,8 +38,9 @@ test('Allows generated tsconfig to be mutated', () => {
}
});
- const config = get_tsconfig(kit, false);
+ const config = get_tsconfig(kit);
+ // @ts-expect-error
assert.equal(config.extends, 'some/other/tsconfig.json');
});
@@ -80,8 +56,9 @@ test('Allows generated tsconfig to be replaced', () => {
}
});
- const config = get_tsconfig(kit, false);
+ const config = get_tsconfig(kit);
+ // @ts-expect-error
assert.equal(config.extends, 'some/other/tsconfig.json');
});
@@ -94,10 +71,11 @@ test('Creates tsconfig include from kit.files', () => {
}
});
- const { include } = get_tsconfig(kit, false);
+ const { include } = get_tsconfig(kit);
expect(include).toEqual([
'ambient.d.ts',
+ 'non-ambient.d.ts',
'./types/**/$types.d.ts',
'../vite.config.js',
'../vite.config.ts',
diff --git a/packages/kit/src/core/sync/write_types/index.js b/packages/kit/src/core/sync/write_types/index.js
index d21030e01109..2c2f0fa0b5b3 100644
--- a/packages/kit/src/core/sync/write_types/index.js
+++ b/packages/kit/src/core/sync/write_types/index.js
@@ -480,7 +480,7 @@ function process_node(node, outdir, is_page, proxies, all_pages_have_load = true
const from = proxy.modified
? `./proxy${replace_ext_with_js(path.basename(file_path))}`
: path_to_original(outdir, file_path);
- const type = `Kit.AwaitedProperties>>`;
+ const type = `Kit.LoadProperties>>`;
return expand ? `Expand>>` : type;
} else {
return fallback;
diff --git a/packages/kit/src/core/sync/write_types/test/package.json b/packages/kit/src/core/sync/write_types/test/package.json
index 47d157b4a13a..9ccb30aba068 100644
--- a/packages/kit/src/core/sync/write_types/test/package.json
+++ b/packages/kit/src/core/sync/write_types/test/package.json
@@ -1,5 +1,6 @@
{
"private": true,
+ "type": "module",
"scripts": {
"testtypes": "tsc"
}
diff --git a/packages/kit/src/core/sync/write_types/test/tsconfig.json b/packages/kit/src/core/sync/write_types/test/tsconfig.json
index fc3cf322453b..39ea59584873 100644
--- a/packages/kit/src/core/sync/write_types/test/tsconfig.json
+++ b/packages/kit/src/core/sync/write_types/test/tsconfig.json
@@ -4,9 +4,9 @@
"checkJs": true,
"noEmit": true,
"strict": true,
- "target": "es2020",
+ "target": "es2022",
"module": "es2022",
- "moduleResolution": "node",
+ "moduleResolution": "bundler",
"allowSyntheticDefaultImports": true,
"baseUrl": ".",
"paths": {
diff --git a/packages/kit/src/exports/index.js b/packages/kit/src/exports/index.js
index 07737d6aa6f9..283ba840c2ef 100644
--- a/packages/kit/src/exports/index.js
+++ b/packages/kit/src/exports/index.js
@@ -1,51 +1,106 @@
import { HttpError, Redirect, ActionFailure } from '../runtime/control.js';
import { BROWSER, DEV } from 'esm-env';
-import { get_route_segments } from '../utils/routing.js';
export { VERSION } from '../version.js';
/**
+ * @template {number} TNumber
+ * @template {any[]} [TArray=[]]
+ * @typedef {TNumber extends TArray['length'] ? TArray[number] : LessThan} LessThan
+ */
+
+/**
+ * @template {number} TStart
+ * @template {number} TEnd
+ * @typedef {Exclude, LessThan>} NumericRange
+ */
+
+// we have to repeat the JSDoc because the display for function overloads is broken
+// see https://github.com/microsoft/TypeScript/issues/55056
+
+/**
+ * Throws an error with a HTTP status code and an optional message.
+ * When called during request handling, this will cause SvelteKit to
+ * return an error response without invoking `handleError`.
+ * Make sure you're not catching the thrown error, which would prevent SvelteKit from handling it.
+ * @param {NumericRange<400, 599>} status The [HTTP status code](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status#client_error_responses). Must be in the range 400-599.
+ * @param {App.Error} body An object that conforms to the App.Error type. If a string is passed, it will be used as the message property.
* @overload
- * @param {number} status
+ * @param {NumericRange<400, 599>} status
* @param {App.Error} body
- * @return {HttpError}
+ * @return {never}
+ * @throws {HttpError} This error instructs SvelteKit to initiate HTTP error handling.
+ * @throws {Error} If the provided status is invalid (not between 400 and 599).
*/
-
/**
+ * Throws an error with a HTTP status code and an optional message.
+ * When called during request handling, this will cause SvelteKit to
+ * return an error response without invoking `handleError`.
+ * Make sure you're not catching the thrown error, which would prevent SvelteKit from handling it.
+ * @param {NumericRange<400, 599>} status The [HTTP status code](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status#client_error_responses). Must be in the range 400-599.
+ * @param {{ message: string } extends App.Error ? App.Error | string | undefined : never} [body] An object that conforms to the App.Error type. If a string is passed, it will be used as the message property.
* @overload
- * @param {number} status
+ * @param {NumericRange<400, 599>} status
* @param {{ message: string } extends App.Error ? App.Error | string | undefined : never} [body]
- * @return {HttpError}
+ * @return {never}
+ * @throws {HttpError} This error instructs SvelteKit to initiate HTTP error handling.
+ * @throws {Error} If the provided status is invalid (not between 400 and 599).
*/
-
/**
- * Creates an `HttpError` object with an HTTP status code and an optional message.
- * This object, if thrown during request handling, will cause SvelteKit to
+ * Throws an error with a HTTP status code and an optional message.
+ * When called during request handling, this will cause SvelteKit to
* return an error response without invoking `handleError`.
* Make sure you're not catching the thrown error, which would prevent SvelteKit from handling it.
- * @param {number} status The [HTTP status code](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status#client_error_responses). Must be in the range 400-599.
+ * @param {NumericRange<400, 599>} status The [HTTP status code](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status#client_error_responses). Must be in the range 400-599.
* @param {{ message: string } extends App.Error ? App.Error | string | undefined : never} body An object that conforms to the App.Error type. If a string is passed, it will be used as the message property.
+ * @return {never}
+ * @throws {HttpError} This error instructs SvelteKit to initiate HTTP error handling.
+ * @throws {Error} If the provided status is invalid (not between 400 and 599).
*/
export function error(status, body) {
if ((!BROWSER || DEV) && (isNaN(status) || status < 400 || status > 599)) {
throw new Error(`HTTP error status codes must be between 400 and 599 — ${status} is invalid`);
}
- return new HttpError(status, body);
+ throw new HttpError(status, body);
}
/**
- * Create a `Redirect` object. If thrown during request handling, SvelteKit will return a redirect response.
+ * Checks whether this is an error thrown by {@link error}.
+ * @template {number} T
+ * @param {unknown} e
+ * @param {T} [status] The status to filter for.
+ * @return {e is (HttpError & { status: T extends undefined ? never : T })}
+ */
+export function isHttpError(e, status) {
+ if (!(e instanceof HttpError)) return false;
+ return !status || e.status === status;
+}
+
+/**
+ * Redirect a request. When called during request handling, SvelteKit will return a redirect response.
* Make sure you're not catching the thrown redirect, which would prevent SvelteKit from handling it.
- * @param {300 | 301 | 302 | 303 | 304 | 305 | 306 | 307 | 308} status The [HTTP status code](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status#redirection_messages). Must be in the range 300-308.
+ * @param {NumericRange<300, 308>} status The [HTTP status code](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status#redirection_messages). Must be in the range 300-308.
* @param {string | URL} location The location to redirect to.
+ * @throws {Redirect} This error instructs SvelteKit to redirect to the specified location.
+ * @throws {Error} If the provided status is invalid.
+ * @return {never}
*/
export function redirect(status, location) {
if ((!BROWSER || DEV) && (isNaN(status) || status < 300 || status > 308)) {
throw new Error('Invalid status code');
}
- return new Redirect(status, location.toString());
+ throw new Redirect(status, location.toString());
+}
+
+/**
+ * Checks whether this is a redirect thrown by {@link redirect}.
+ * @param {unknown} e The object to check.
+ * @return {e is Redirect}
+ */
+export function isRedirect(e) {
+ return e instanceof Redirect;
}
/**
@@ -100,68 +155,30 @@ export function text(body, init) {
});
}
+/**
+ * Create an `ActionFailure` object.
+ * @param {number} status The [HTTP status code](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status#client_error_responses). Must be in the range 400-599.
+ * @overload
+ * @param {number} status
+ * @returns {import('./public.js').ActionFailure}
+ */
/**
* Create an `ActionFailure` object.
* @template {Record | undefined} [T=undefined]
* @param {number} status The [HTTP status code](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status#client_error_responses). Must be in the range 400-599.
- * @param {T} [data] Data associated with the failure (e.g. validation errors)
- * @returns {ActionFailure}
+ * @param {T} data Data associated with the failure (e.g. validation errors)
+ * @overload
+ * @param {number} status
+ * @param {T} data
+ * @returns {import('./public.js').ActionFailure}
*/
-export function fail(status, data) {
- return new ActionFailure(status, data);
-}
-
-const basic_param_pattern = /\[(\[)?(\.\.\.)?(\w+?)(?:=(\w+))?\]\]?/g;
-
-let warned = false;
-
/**
- * @deprecated Use `resolveRoute` from `$app/paths` instead.
- *
- * Populate a route ID with params to resolve a pathname.
- * @example
- * ```js
- * resolvePath(
- * `/blog/[slug]/[...somethingElse]`,
- * {
- * slug: 'hello-world',
- * somethingElse: 'something/else'
- * }
- * ); // `/blog/hello-world/something/else`
- * ```
- * @param {string} id
- * @param {Record} params
- * @returns {string}
+ * Create an `ActionFailure` object.
+ * @param {number} status
+ * @param {any} [data]
+ * @returns {import('./public.js').ActionFailure}
*/
-export function resolvePath(id, params) {
- if (!warned) {
- console.warn('`resolvePath` is deprecated. Use `resolveRoute` from `$app/paths` instead.');
- warned = true;
- }
-
- const segments = get_route_segments(id);
- return (
- '/' +
- segments
- .map((segment) =>
- segment.replace(basic_param_pattern, (_, optional, rest, name) => {
- const param_value = params[name];
-
- // This is nested so TS correctly narrows the type
- if (!param_value) {
- if (optional) return '';
- if (rest && param_value !== undefined) return '';
- throw new Error(`Missing parameter '${name}' in route ${id}`);
- }
-
- if (param_value.startsWith('/') || param_value.endsWith('/'))
- throw new Error(
- `Parameter '${name}' in route ${id} cannot start or end with a slash -- this would cause an invalid route like foo//bar`
- );
- return param_value;
- })
- )
- .filter(Boolean)
- .join('/')
- );
+export function fail(status, data) {
+ // @ts-expect-error unique symbol missing
+ return new ActionFailure(status, data);
}
diff --git a/packages/kit/src/exports/index.spec.js b/packages/kit/src/exports/index.spec.js
deleted file mode 100644
index 22fbf9784cef..000000000000
--- a/packages/kit/src/exports/index.spec.js
+++ /dev/null
@@ -1,59 +0,0 @@
-import { assert, expect, test } from 'vitest';
-import { resolvePath } from './index.js';
-
-const from_params_tests = [
- {
- route: '/blog/[one]/[two]',
- params: { one: 'one', two: 'two' },
- expected: '/blog/one/two'
- },
- {
- route: '/blog/[one=matcher]/[...two]',
- params: { one: 'one', two: 'two/three' },
- expected: '/blog/one/two/three'
- },
- {
- route: '/blog/[one=matcher]/[[two]]',
- params: { one: 'one' },
- expected: '/blog/one'
- },
- {
- route: '/blog/[one]/[two]-and-[three]',
- params: { one: 'one', two: '2', three: '3' },
- expected: '/blog/one/2-and-3'
- },
- {
- route: '/blog/[...one]',
- params: { one: '' },
- expected: '/blog'
- },
- {
- route: '/blog/[one]/[...two]-not-three',
- params: { one: 'one', two: 'two/2' },
- expected: '/blog/one/two/2-not-three'
- }
-];
-
-for (const { route, params, expected } of from_params_tests) {
- test(`resolvePath generates correct path for ${route}`, () => {
- const result = resolvePath(route, params);
- assert.equal(result, expected);
- });
-}
-
-test('resolvePath errors on missing params for required param', () => {
- expect(() => resolvePath('/blog/[one]/[two]', { one: 'one' })).toThrow(
- "Missing parameter 'two' in route /blog/[one]/[two]"
- );
-});
-
-test('resolvePath errors on params values starting or ending with slashes', () => {
- assert.throws(
- () => resolvePath('/blog/[one]/[two]', { one: 'one', two: '/two' }),
- "Parameter 'two' in route /blog/[one]/[two] cannot start or end with a slash -- this would cause an invalid route like foo//bar"
- );
- assert.throws(
- () => resolvePath('/blog/[one]/[two]', { one: 'one', two: 'two/' }),
- "Parameter 'two' in route /blog/[one]/[two] cannot start or end with a slash -- this would cause an invalid route like foo//bar"
- );
-});
diff --git a/packages/kit/src/exports/node/index.js b/packages/kit/src/exports/node/index.js
index ced8310907ce..ae3d57afc9cc 100644
--- a/packages/kit/src/exports/node/index.js
+++ b/packages/kit/src/exports/node/index.js
@@ -1,5 +1,5 @@
import * as set_cookie_parser from 'set-cookie-parser';
-import { error } from '../index.js';
+import { SvelteKitError } from '../../runtime/control.js';
/**
* @param {import('http').IncomingMessage} req
@@ -22,19 +22,6 @@ function get_raw_body(req, body_size_limit) {
return null;
}
- let length = content_length;
-
- if (body_size_limit) {
- if (!length) {
- length = body_size_limit;
- } else if (length > body_size_limit) {
- throw error(
- 413,
- `Received content-length of ${length}, but only accept up to ${body_size_limit} bytes.`
- );
- }
- }
-
if (req.destroyed) {
const readable = new ReadableStream();
readable.cancel();
@@ -46,6 +33,17 @@ function get_raw_body(req, body_size_limit) {
return new ReadableStream({
start(controller) {
+ if (body_size_limit !== undefined && content_length > body_size_limit) {
+ const error = new SvelteKitError(
+ 413,
+ 'Payload Too Large',
+ `Content-length of ${content_length} exceeds limit of ${body_size_limit} bytes.`
+ );
+
+ controller.error(error);
+ return;
+ }
+
req.on('error', (error) => {
cancelled = true;
controller.error(error);
@@ -60,16 +58,15 @@ function get_raw_body(req, body_size_limit) {
if (cancelled) return;
size += chunk.length;
- if (size > length) {
+ if (size > content_length) {
cancelled = true;
- controller.error(
- error(
- 413,
- `request body size exceeded ${
- content_length ? "'content-length'" : 'BODY_SIZE_LIMIT'
- } of ${length}`
- )
- );
+
+ const constraint = content_length ? 'content-length' : 'BODY_SIZE_LIMIT';
+ const message = `request body size exceeded ${constraint} of ${content_length}`;
+
+ const error = new SvelteKitError(413, 'Payload Too Large', message);
+ controller.error(error);
+
return;
}
diff --git a/packages/kit/src/exports/node/polyfills.js b/packages/kit/src/exports/node/polyfills.js
index 46d5e71bce02..347c68cea2e9 100644
--- a/packages/kit/src/exports/node/polyfills.js
+++ b/packages/kit/src/exports/node/polyfills.js
@@ -1,54 +1,25 @@
-import { ReadableStream, TransformStream, WritableStream } from 'node:stream/web';
import buffer from 'node:buffer';
import { webcrypto as crypto } from 'node:crypto';
-import { fetch, Response, Request, Headers, FormData, File as UndiciFile } from 'undici';
// `buffer.File` was added in Node 18.13.0 while the `File` global was added in Node 20.0.0
-const File = /** @type {import('node:buffer') & { File?: File}} */ (buffer).File ?? UndiciFile;
+const File = /** @type {import('node:buffer') & { File?: File}} */ (buffer).File;
/** @type {Record} */
-const globals_post_node_18_11 = {
+const globals = {
crypto,
File
};
-/** @type {Record} */
-// TODO: remove this once we only support Node 18.11+ (the version multipart/form-data was added)
-const globals_pre_node_18_11 = {
- crypto,
- fetch,
- Response,
- Request,
- Headers,
- ReadableStream,
- TransformStream,
- WritableStream,
- FormData,
- File
-};
-
// exported for dev/preview and node environments
/**
* Make various web APIs available as globals:
* - `crypto`
- * - `fetch` (only in node < 18.11)
- * - `Headers` (only in node < 18.11)
- * - `Request` (only in node < 18.11)
- * - `Response` (only in node < 18.11)
+ * - `File`
*/
export function installPolyfills() {
- // Be defensive (we don't know in which environments this is called) and always apply if something goes wrong
- let globals = globals_pre_node_18_11;
- try {
- const version = process.versions.node.split('.').map((n) => parseInt(n, 10));
- if ((version[0] === 18 && version[1] >= 11) || version[0] > 18) {
- globals = globals_post_node_18_11;
- }
- } catch (e) {
- // ignore
- }
-
for (const name in globals) {
+ if (name in globalThis) continue;
+
Object.defineProperty(globalThis, name, {
enumerable: true,
configurable: true,
diff --git a/packages/kit/src/exports/public.d.ts b/packages/kit/src/exports/public.d.ts
index 088605713797..7caa0c4bf038 100644
--- a/packages/kit/src/exports/public.d.ts
+++ b/packages/kit/src/exports/public.d.ts
@@ -2,7 +2,7 @@ import 'svelte'; // pick up `declare module "*.svelte"`
import 'vite/client'; // pick up `declare module "*.jpg"`, etc.
import '../types/ambient.js';
-import { CompileOptions } from 'svelte/types/compiler/interfaces';
+import { CompileOptions } from 'svelte/compiler';
import {
AdapterEntry,
CspDirectives,
@@ -17,12 +17,10 @@ import {
RequestOptions,
RouteSegment
} from '../types/private.js';
-import { ActionFailure } from '../runtime/control.js';
import { BuildData, SSRNodeLoader, SSRRoute, ValidatedConfig } from 'types';
import type { PluginOptions } from '@sveltejs/vite-plugin-svelte';
export { PrerenderOption } from '../types/private.js';
-export { ActionFailure };
/**
* [Adapters](https://kit.svelte.dev/docs/adapters) are responsible for taking the production build and turning it into something that can be deployed to a platform of your choosing.
@@ -39,20 +37,11 @@ export interface Adapter {
adapt(builder: Builder): MaybePromise;
}
-type AwaitedPropertiesUnion | void> = input extends void
+export type LoadProperties | void> = input extends void
? undefined // needs to be undefined, because void will break intellisense
: input extends Record
- ? {
- [key in keyof input]: Awaited;
- }
- : {} extends input // handles the any case
- ? input
- : unknown;
-
-export type AwaitedProperties | void> =
- AwaitedPropertiesUnion extends Record
- ? OptionalUnion>
- : AwaitedPropertiesUnion;
+ ? input
+ : unknown;
export type AwaitedActions any>> = OptionalUnion<
{
@@ -67,6 +56,14 @@ type OptionalUnion<
A extends keyof U = U extends U ? keyof U : never
> = U extends unknown ? { [P in Exclude]?: never } & U : never;
+declare const uniqueSymbol: unique symbol;
+
+export interface ActionFailure | undefined = undefined> {
+ status: number;
+ data: T;
+ [uniqueSymbol]: true; // necessary or else UnpackValidationError could wrongly unpack objects with the same shape as ActionFailure
+}
+
type UnpackValidationError = T extends ActionFailure
? X
: T extends void
@@ -104,6 +101,11 @@ export interface Builder {
*/
generateFallback(dest: string): Promise;
+ /**
+ * Generate a module exposing build-time environment variables as `$env/dynamic/public`.
+ */
+ generateEnvModule(): void;
+
/**
* Generate a server-side manifest to initialise the SvelteKit [server](https://kit.svelte.dev/docs/types#public-types-server) with.
* @param opts a relative path to the base directory of the app and optionally in which format (esm or cjs) the manifest should be generated
@@ -212,34 +214,42 @@ export interface Cookies {
*
* The `httpOnly` and `secure` options are `true` by default (except on http://localhost, where `secure` is `false`), and must be explicitly disabled if you want cookies to be readable by client-side JavaScript and/or transmitted over HTTP. The `sameSite` option defaults to `lax`.
*
- * By default, the `path` of a cookie is the 'directory' of the current pathname. In most cases you should explicitly set `path: '/'` to make the cookie available throughout your app.
+ * You must specify a `path` for the cookie. In most cases you should explicitly set `path: '/'` to make the cookie available throughout your app. You can use relative paths, or set `path: ''` to make the cookie only available on the current path and its children
* @param name the name of the cookie
* @param value the cookie value
* @param opts the options, passed directly to `cookie.serialize`. See documentation [here](https://github.com/jshttp/cookie#cookieserializename-value-options)
*/
- set(name: string, value: string, opts?: import('cookie').CookieSerializeOptions): void;
+ set(
+ name: string,
+ value: string,
+ opts: import('cookie').CookieSerializeOptions & { path: string }
+ ): void;
/**
* Deletes a cookie by setting its value to an empty string and setting the expiry date in the past.
*
- * By default, the `path` of a cookie is the 'directory' of the current pathname. In most cases you should explicitly set `path: '/'` to make the cookie available throughout your app.
+ * You must specify a `path` for the cookie. In most cases you should explicitly set `path: '/'` to make the cookie available throughout your app. You can use relative paths, or set `path: ''` to make the cookie only available on the current path and its children
* @param name the name of the cookie
* @param opts the options, passed directly to `cookie.serialize`. The `path` must match the path of the cookie you want to delete. See documentation [here](https://github.com/jshttp/cookie#cookieserializename-value-options)
*/
- delete(name: string, opts?: import('cookie').CookieSerializeOptions): void;
+ delete(name: string, opts: import('cookie').CookieSerializeOptions & { path: string }): void;
/**
* Serialize a cookie name-value pair into a `Set-Cookie` header string, but don't apply it to the response.
*
* The `httpOnly` and `secure` options are `true` by default (except on http://localhost, where `secure` is `false`), and must be explicitly disabled if you want cookies to be readable by client-side JavaScript and/or transmitted over HTTP. The `sameSite` option defaults to `lax`.
*
- * By default, the `path` of a cookie is the current pathname. In most cases you should explicitly set `path: '/'` to make the cookie available throughout your app.
+ * You must specify a `path` for the cookie. In most cases you should explicitly set `path: '/'` to make the cookie available throughout your app. You can use relative paths, or set `path: ''` to make the cookie only available on the current path and its children
*
* @param name the name of the cookie
* @param value the cookie value
* @param opts the options, passed directly to `cookie.serialize`. See documentation [here](https://github.com/jshttp/cookie#cookieserializename-value-options)
*/
- serialize(name: string, value: string, opts?: import('cookie').CookieSerializeOptions): string;
+ serialize(
+ name: string,
+ value: string,
+ opts: import('cookie').CookieSerializeOptions & { path: string }
+ ): string;
}
export interface KitConfig {
@@ -279,7 +289,9 @@ export interface KitConfig {
*/
alias?: Record;
/**
- * The directory relative to `paths.assets` where the built JS and CSS (and imported assets) are served from. (The filenames therein contain content-based hashes, meaning they can be cached indefinitely). Must not start or end with `/`.
+ * The directory where SvelteKit keeps its stuff, including static assets (such as JS and CSS) and internally-used routes.
+ *
+ * If `paths.assets` is specified, there will be two app directories — `${paths.assets}/${appDir}` and `${paths.base}/${appDir}`.
* @default "_app"
*/
appDir?: string;
@@ -343,16 +355,6 @@ export interface KitConfig {
*/
checkOrigin?: boolean;
};
- /**
- * Here be dragons. Enable at your peril.
- */
- dangerZone?: {
- /**
- * Automatically add server-side `fetch`ed URLs to the `dependencies` map of `load` functions. This will expose secrets
- * to the client if your URL contains them.
- */
- trackServerFetches?: boolean;
- };
/**
* Whether or not the app is embedded inside a larger app. If `true`, SvelteKit will add its event listeners related to navigation etc on the parent of `%sveltekit.body%` instead of `window`, and will pass `params` from the server rather than inferring them from `location.pathname`.
* @default false
@@ -473,17 +475,20 @@ export interface KitConfig {
*/
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.
+ * Whether to use relative asset paths.
*
- * 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 `true`, `base` and `assets` imported from `$app/paths` will be replaced with relative asset paths during server-side rendering, resulting in more portable HTML.
* If `false`, `%sveltekit.assets%` and references to build artifacts will always be root-relative paths, unless `paths.assets` is an external URL
*
* [Single-page app](https://kit.svelte.dev/docs/single-page-apps) fallback pages will always use absolute paths, regardless of this setting.
*
* If your app uses a `` element, you should set this to `false`, otherwise asset URLs will incorrectly be resolved against the `` URL rather than the current page.
- * @default undefined
+ *
+ * In 1.0, `undefined` was a valid value, which was set by default. In that case, if `paths.assets` was not external, SvelteKit would replace `%sveltekit.assets%` with a relative path and use relative paths to reference build artifacts, but `base` and `assets` imported from `$app/paths` would be as specified in your config.
+ *
+ * @default true
*/
- relative?: boolean | undefined;
+ relative?: boolean;
};
/**
* See [Prerendering](https://kit.svelte.dev/docs/page-options#prerender).
@@ -500,7 +505,7 @@ export interface KitConfig {
*/
crawl?: boolean;
/**
- * An array of pages to prerender, or start crawling from (if `crawl: true`). The `*` string includes all non-dynamic routes (i.e. pages with no `[parameters]`, because SvelteKit doesn't know what value the parameters should have).
+ * An array of pages to prerender, or start crawling from (if `crawl: true`). The `*` string includes all routes containing no required `[parameters]` with optional parameters included as being empty (since SvelteKit doesn't know what value any parameters should have).
* @default ["*"]
*/
entries?: Array<'*' | `/${string}`>;
@@ -652,6 +657,8 @@ export type Handle = (input: {
export type HandleServerError = (input: {
error: unknown;
event: RequestEvent;
+ status: number;
+ message: string;
}) => MaybePromise;
/**
@@ -663,6 +670,8 @@ export type HandleServerError = (input: {
export type HandleClientError = (input: {
error: unknown;
event: NavigationEvent;
+ status: number;
+ message: string;
}) => MaybePromise;
/**
@@ -781,7 +790,21 @@ export interface LoadEvent<
*
* ```
*/
- depends(...deps: string[]): void;
+ depends(...deps: Array<`${string}:${string}`>): void;
+ /**
+ * Use this function to opt out of dependency tracking for everything that is synchronously called within the callback. Example:
+ *
+ * ```js
+ * /// file: src/routes/+page.server.js
+ * export async function load({ untrack, url }) {
+ * // Untrack url.pathname so that path changes don't trigger a rerun
+ * if (untrack(() => url.pathname === '/')) {
+ * return { message: 'Welcome!' };
+ * }
+ * }
+ * ```
+ */
+ untrack(fn: () => T): T;
}
export interface NavigationEvent<
@@ -848,7 +871,7 @@ export interface Navigation {
/**
* The type of navigation:
* - `form`: The user submitted a `