diff --git a/.changeset/bright-lizards-deny.md b/.changeset/bright-lizards-deny.md
new file mode 100644
index 0000000000000..59c886b284d20
--- /dev/null
+++ b/.changeset/bright-lizards-deny.md
@@ -0,0 +1,5 @@
+---
+'@sveltejs/kit': minor
+---
+
+feat: richer error message for invalid exports
diff --git a/.changeset/seven-teachers-tell.md b/.changeset/seven-teachers-tell.md
new file mode 100644
index 0000000000000..29c1a75dbd573
--- /dev/null
+++ b/.changeset/seven-teachers-tell.md
@@ -0,0 +1,5 @@
+---
+'@sveltejs/kit': patch
+---
+
+chore: throw more helpful error when encoding uri fails during prerendering
diff --git a/.changeset/smooth-shrimps-look.md b/.changeset/smooth-shrimps-look.md
deleted file mode 100644
index a1a20c322cbdb..0000000000000
--- a/.changeset/smooth-shrimps-look.md
+++ /dev/null
@@ -1,5 +0,0 @@
----
-'@sveltejs/kit': patch
----
-
-fix: always default `paths.assets` to `paths.base`
diff --git a/documentation/docs/20-core-concepts/10-routing.md b/documentation/docs/20-core-concepts/10-routing.md
index 7f1f89906b488..b6026c1b45b68 100644
--- a/documentation/docs/20-core-concepts/10-routing.md
+++ b/documentation/docs/20-core-concepts/10-routing.md
@@ -248,7 +248,7 @@ Like `+layout.js`, `+layout.server.js` can export [page options](page-options)
## +server
-As well as pages, you can define routes with a `+server.js` file (sometimes referred to as an 'API route' or an 'endpoint'), which gives you full control over the response. Your `+server.js` file (or `+server.ts`) exports functions corresponding to HTTP verbs like `GET`, `POST`, `PATCH`, `PUT` and `DELETE` that take a `RequestEvent` argument and return a [`Response`](https://developer.mozilla.org/en-US/docs/Web/API/Response) object.
+As well as pages, you can define routes with a `+server.js` file (sometimes referred to as an 'API route' or an 'endpoint'), which gives you full control over the response. Your `+server.js` file (or `+server.ts`) exports functions corresponding to HTTP verbs like `GET`, `POST`, `PATCH`, `PUT`, `DELETE`, and `OPTIONS` that take a `RequestEvent` argument and return a [`Response`](https://developer.mozilla.org/en-US/docs/Web/API/Response) object.
For example we could create an `/api/random-number` route with a `GET` handler:
@@ -279,9 +279,11 @@ You can use the [`error`](modules#sveltejs-kit-error), [`redirect`](modules#svel
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).
+> 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.
+
### Receiving data
-By exporting `POST`/`PUT`/`PATCH`/`DELETE` handlers, `+server.js` files can be used to create a complete API:
+By exporting `POST`/`PUT`/`PATCH`/`DELETE`/`OPTIONS` handlers, `+server.js` files can be used to create a complete API:
```svelte
/// file: src/routes/add/+page.svelte
@@ -327,7 +329,7 @@ export async function POST({ request }) {
`+server.js` files can be placed in the same directory as `+page` files, allowing the same route to be either a page or an API endpoint. To determine which, SvelteKit applies the following rules:
-- `PUT`/`PATCH`/`DELETE` requests are always handled by `+server.js` since they do not apply to pages
+- `PUT`/`PATCH`/`DELETE`/`OPTIONS` requests are always handled by `+server.js` since they do not apply to pages
- `GET`/`POST` requests are treated as page requests if the `accept` header prioritises `text/html` (in other words, it's a browser page request), else they are handled by `+server.js`
## $types
diff --git a/documentation/docs/20-core-concepts/20-load.md b/documentation/docs/20-core-concepts/20-load.md
index 4a563afc9002a..967218babeed3 100644
--- a/documentation/docs/20-core-concepts/20-load.md
+++ b/documentation/docs/20-core-concepts/20-load.md
@@ -416,7 +416,7 @@ export function load({ locals }) {
}
```
-> Make sure you're not catching the thrown redirect, which results in a noop.
+> Make sure you're not catching the thrown redirect, which would prevent SvelteKit from handling it.
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).
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 2cc9c182be777..ca370579e99e3 100644
--- a/documentation/docs/25-build-and-deploy/40-adapter-node.md
+++ b/documentation/docs/25-build-and-deploy/40-adapter-node.md
@@ -122,7 +122,8 @@ export default {
// default options are shown
out: 'build',
precompress: false,
- envPrefix: ''
+ envPrefix: '',
+ polyfill: true
})
}
};
@@ -140,6 +141,10 @@ Enables precompressing using gzip and brotli for assets and prerendered pages. I
If you need to change the name of the environment variables used to configure the deployment (for example, to deconflict with environment variables you don't control), you can specify a prefix:
+### polyfill
+
+Controlls 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.
+
```js
envPrefix: 'MY_CUSTOM_';
```
diff --git a/documentation/docs/30-advanced/40-service-workers.md b/documentation/docs/30-advanced/40-service-workers.md
index 09c1b8a8fb0e3..92da584e51843 100644
--- a/documentation/docs/30-advanced/40-service-workers.md
+++ b/documentation/docs/30-advanced/40-service-workers.md
@@ -24,6 +24,7 @@ The following example caches the built app and any files in `static` eagerly, an
```js
// @errors: 2339
+///
import { build, files, version } from '$service-worker';
// Create a unique cache name for this deployment
@@ -108,6 +109,7 @@ navigator.serviceWorker.register('/service-worker.js', {
Setting up proper types for service workers requires some manual setup. Inside your `service-worker.js`, add the following to the top of your file:
```original-js
+///
///
///
///
@@ -115,6 +117,7 @@ Setting up proper types for service workers requires some manual setup. Inside y
const sw = /** @type {ServiceWorkerGlobalScope} */ (/** @type {unknown} */ (self));
```
```generated-ts
+///
///
///
///
@@ -122,7 +125,7 @@ const sw = /** @type {ServiceWorkerGlobalScope} */ (/** @type {unknown} */ (self
const sw = self as unknown as ServiceWorkerGlobalScope;
```
-This disables access to DOM typings like `HTMLElement` which are not available inside a service worker and instantiates the correct globals. The reassignment of `self` to `sw` allows you to type cast it in the process (there are a couple of ways to do this, but the easiest that requires no additional files). Use `sw` instead of `self` in the rest of the file.
+This disables access to DOM typings like `HTMLElement` which are not available inside a service worker and instantiates the correct globals. The reassignment of `self` to `sw` allows you to type cast it in the process (there are a couple of ways to do this, but the easiest that requires no additional files). Use `sw` instead of `self` in the rest of the file. The reference to the SvelteKit types ensures that the `$service-worker` import has proper type definitions.
## Other solutions
diff --git a/documentation/docs/30-advanced/65-snapshots.md b/documentation/docs/30-advanced/65-snapshots.md
index 41971b98e9416..fff0a2e372a0e 100644
--- a/documentation/docs/30-advanced/65-snapshots.md
+++ b/documentation/docs/30-advanced/65-snapshots.md
@@ -21,7 +21,8 @@ To do this, export a `snapshot` object with `capture` and `restore` methods from
```
@@ -30,4 +31,4 @@ When you navigate away from this page, the `capture` function is called immediat
The data must be serializable as JSON so that it can be persisted to `sessionStorage`. This allows the state to be restored when the page is reloaded, or when the user navigates back from a different site.
-> Avoid returning very large objects from `capture` — once captured, objects will be retained in memory for the duration of the session, and in extreme cases may be too large to persist to `sessionStorage`.
\ No newline at end of file
+> Avoid returning very large objects from `capture` — once captured, objects will be retained in memory for the duration of the session, and in extreme cases may be too large to persist to `sessionStorage`.
diff --git a/packages/adapter-cloudflare/CHANGELOG.md b/packages/adapter-cloudflare/CHANGELOG.md
index 43a523405886e..992a7be24eb92 100644
--- a/packages/adapter-cloudflare/CHANGELOG.md
+++ b/packages/adapter-cloudflare/CHANGELOG.md
@@ -1,5 +1,16 @@
# @sveltejs/adapter-cloudflare
+## 2.0.2
+
+### Patch Changes
+
+- fix: exclude `_headers` and `_redirects` files from Cloudflare Pages static request list ([#9042](https://github.com/sveltejs/kit/pull/9042))
+
+- fix: remove redundant cloudflare worker static asset serving ([#9040](https://github.com/sveltejs/kit/pull/9040))
+
+- Updated dependencies [[`19c0e62a`](https://github.com/sveltejs/kit/commit/19c0e62a6bd281e656061b453973c35fa2dd9d3d)]:
+ - @sveltejs/kit@1.5.7
+
## 2.0.1
### Patch Changes
diff --git a/packages/adapter-cloudflare/index.js b/packages/adapter-cloudflare/index.js
index 7779b3746de81..ee57e7007ce17 100644
--- a/packages/adapter-cloudflare/index.js
+++ b/packages/adapter-cloudflare/index.js
@@ -1,6 +1,6 @@
-import { writeFileSync } from 'fs';
-import { posix } from 'path';
-import { fileURLToPath } from 'url';
+import { writeFileSync } from 'node:fs';
+import { posix } from 'node:path';
+import { fileURLToPath } from 'node:url';
import * as esbuild from 'esbuild';
/** @type {import('.').default} */
@@ -70,7 +70,14 @@ function get_routes_json(builder, assets) {
const exclude = [
`/${builder.config.kit.appDir}/*`,
...assets
- .filter((file) => !file.startsWith(`${builder.config.kit.appDir}/`))
+ .filter(
+ (file) =>
+ !(
+ file.startsWith(`${builder.config.kit.appDir}/`) ||
+ file === '_headers' ||
+ file === '_redirects'
+ )
+ )
.map((file) => `/${file}`)
];
@@ -111,9 +118,12 @@ function get_routes_json(builder, assets) {
function generate_headers(app_dir) {
return `
# === START AUTOGENERATED SVELTE IMMUTABLE HEADERS ===
+/${app_dir}/*
+ X-Robots-Tag: noindex
+ Cache-Control: no-cache
/${app_dir}/immutable/*
+ ! Cache-Control
Cache-Control: public, immutable, max-age=31536000
- X-Robots-Tag: noindex
# === END AUTOGENERATED SVELTE IMMUTABLE HEADERS ===
`.trimEnd();
}
diff --git a/packages/adapter-cloudflare/package.json b/packages/adapter-cloudflare/package.json
index edb9e7241de41..0daf85f925a76 100644
--- a/packages/adapter-cloudflare/package.json
+++ b/packages/adapter-cloudflare/package.json
@@ -1,6 +1,6 @@
{
"name": "@sveltejs/adapter-cloudflare",
- "version": "2.0.1",
+ "version": "2.0.2",
"repository": {
"type": "git",
"url": "https://github.com/sveltejs/kit",
diff --git a/packages/adapter-cloudflare/src/worker.js b/packages/adapter-cloudflare/src/worker.js
index 9c643e547f9a6..545c0ebc671b8 100644
--- a/packages/adapter-cloudflare/src/worker.js
+++ b/packages/adapter-cloudflare/src/worker.js
@@ -4,8 +4,6 @@ import * as Cache from 'worktop/cfw.cache';
const server = new Server(manifest);
-const app_path = `/${manifest.appPath}/`;
-
/** @type {import('worktop/cfw').Module.Worker<{ ASSETS: import('worktop/cfw.durable').Durable.Object }>} */
const worker = {
async fetch(req, env, context) {
@@ -17,64 +15,42 @@ const worker = {
if (res) return res;
let { pathname } = new URL(req.url);
+ try {
+ pathname = decodeURIComponent(pathname);
+ } catch {
+ // ignore invalid URI
+ }
- // generated files
- if (pathname.startsWith(app_path)) {
- res = await env.ASSETS.fetch(req);
- if (!res.ok) return res;
+ const stripped_pathname = pathname.replace(/\/$/, '');
- const cache_control = pathname.startsWith(app_path + 'immutable/')
- ? 'public, immutable, max-age=31536000'
- : 'no-cache';
+ // prerendered pages and /static files
+ let is_static_asset = false;
+ const filename = stripped_pathname.substring(1);
+ if (filename) {
+ is_static_asset =
+ manifest.assets.has(filename) || manifest.assets.has(filename + '/index.html');
+ }
- res = new Response(res.body, {
+ const location = pathname.at(-1) === '/' ? stripped_pathname : pathname + '/';
+
+ if (is_static_asset || prerendered.has(pathname)) {
+ res = await env.ASSETS.fetch(req);
+ } else if (location && prerendered.has(location)) {
+ res = new Response('', {
+ status: 308,
headers: {
- // include original headers, minus cache-control which
- // is overridden, and etag which is no longer useful
- 'cache-control': cache_control,
- 'content-type': res.headers.get('content-type'),
- 'x-robots-tag': 'noindex'
+ location
}
});
} else {
- // prerendered pages and /static files
-
- try {
- pathname = decodeURIComponent(pathname);
- } catch {
- // ignore invalid URI
- }
-
- const stripped_pathname = pathname.replace(/\/$/, '');
-
- let is_static_asset = false;
- const filename = stripped_pathname.substring(1);
- if (filename) {
- is_static_asset =
- manifest.assets.has(filename) || manifest.assets.has(filename + '/index.html');
- }
-
- const counterpart_route = pathname.at(-1) === '/' ? stripped_pathname : pathname + '/';
-
- if (is_static_asset || prerendered.has(pathname)) {
- res = await env.ASSETS.fetch(req);
- } else if (counterpart_route && prerendered.has(counterpart_route)) {
- res = new Response('', {
- status: 308,
- headers: {
- location: counterpart_route
- }
- });
- } else {
- // dynamically-generated pages
- res = await server.respond(req, {
- // @ts-ignore
- platform: { env, context, caches },
- getClientAddress() {
- return req.headers.get('cf-connecting-ip');
- }
- });
- }
+ // dynamically-generated pages
+ res = await server.respond(req, {
+ // @ts-ignore
+ platform: { env, context, caches },
+ getClientAddress() {
+ return req.headers.get('cf-connecting-ip');
+ }
+ });
}
// Writes to Cache only if allowed & specified
diff --git a/packages/adapter-netlify/CHANGELOG.md b/packages/adapter-netlify/CHANGELOG.md
index 5e1223b3967f8..8caf895dec155 100644
--- a/packages/adapter-netlify/CHANGELOG.md
+++ b/packages/adapter-netlify/CHANGELOG.md
@@ -1,5 +1,25 @@
# @sveltejs/adapter-netlify
+## 2.0.4
+
+### Patch Changes
+
+- fix: Root route data endpoint redirect when using split routes ([#9006](https://github.com/sveltejs/kit/pull/9006))
+
+- Updated dependencies [[`74cfa8d5`](https://github.com/sveltejs/kit/commit/74cfa8d5f1f13f81759e20e90f4ff86a4f96040d), [`bfa2b6ec`](https://github.com/sveltejs/kit/commit/bfa2b6ec88a6d522d87c924d7c466c01e142e66e)]:
+ - @sveltejs/kit@1.5.6
+
+## 2.0.3
+
+### Patch Changes
+
+- chore: simplify functions-internal cleanup ([#8953](https://github.com/sveltejs/kit/pull/8953))
+
+- fix: correctly compare routes when generating split functions ([#8952](https://github.com/sveltejs/kit/pull/8952))
+
+- Updated dependencies [[`0abb8ebf`](https://github.com/sveltejs/kit/commit/0abb8ebffc6121f81c2bbfa0a0f68866d4cc1627), [`bef54f63`](https://github.com/sveltejs/kit/commit/bef54f63d2315066d30e8f1bcf471ddf2bd72c35), [`51cd6e64`](https://github.com/sveltejs/kit/commit/51cd6e643178e3a113fc2c3e8a63755bcbfe902d), [`930c8e4e`](https://github.com/sveltejs/kit/commit/930c8e4ee2e3046ed1b622777dafa23029a19fe5), [`ee8066fc`](https://github.com/sveltejs/kit/commit/ee8066fcb29ed1e7e3ab513cabb7997e38c984f2), [`49d2ec62`](https://github.com/sveltejs/kit/commit/49d2ec62e6385694f11701bf2fa411d07449344c), [`eb943565`](https://github.com/sveltejs/kit/commit/eb943565a4324dbed3da5a581924ca91a24366de)]:
+ - @sveltejs/kit@1.5.3
+
## 2.0.2
### Patch Changes
diff --git a/packages/adapter-netlify/index.js b/packages/adapter-netlify/index.js
index 987d55ca0dee6..3307a730935e4 100644
--- a/packages/adapter-netlify/index.js
+++ b/packages/adapter-netlify/index.js
@@ -1,16 +1,8 @@
-import {
- appendFileSync,
- existsSync,
- readFileSync,
- writeFileSync,
- unlinkSync,
- createReadStream
-} from 'fs';
-import { dirname, join, resolve, posix } from 'path';
-import { fileURLToPath } from 'url';
+import { appendFileSync, existsSync, readdirSync, readFileSync, writeFileSync } from 'node:fs';
+import { dirname, join, resolve, posix } from 'node:path';
+import { fileURLToPath } from 'node:url';
import esbuild from 'esbuild';
import toml from '@iarna/toml';
-import { createInterface } from 'readline';
/**
* @typedef {{
@@ -41,6 +33,8 @@ const edge_set_in_env_var =
process.env.NETLIFY_SVELTEKIT_USE_EDGE === 'true' ||
process.env.NETLIFY_SVELTEKIT_USE_EDGE === '1';
+const FUNCTION_PREFIX = 'sveltekit-';
+
/** @type {import('.').default} */
export default function ({ split = false, edge = edge_set_in_env_var } = {}) {
return {
@@ -59,40 +53,6 @@ export default function ({ split = false, edge = edge_set_in_env_var } = {}) {
// "build" is the default publish directory when Netlify detects SvelteKit
const publish = get_publish_directory(netlify_config, builder) || 'build';
- const redirects_file_path = join(publish, '_redirects');
-
- // If redirects file exists - empty any netlify generated files in functions-internal
- // Without removing other files that may have been auto generated by integrations
- if (existsSync(redirects_file_path)) {
- // Read each line of the file
- const fileStream = createReadStream(redirects_file_path);
- const rl = createInterface({
- input: fileStream,
- crlfDelay: Infinity
- });
-
- // Create an array of lines
- const lines = [];
- for await (const line of rl) {
- lines.push(line);
- }
-
- const functions_internal = join('.netlify', 'functions-internal');
-
- // Loop through redirects, and delete corresponding functions-internal files
- lines.forEach((line) => {
- if (line) {
- // example line /.netlify/functions/{function_name} 200
- const path = line.split(' ')[1];
- const function_name = path.split('/').pop();
- const mjsFile = join(functions_internal, `${function_name}.mjs`);
- const jsonFile = join(functions_internal, `${function_name}.json`);
- if (existsSync(mjsFile)) unlinkSync(mjsFile);
- if (existsSync(jsonFile)) unlinkSync(jsonFile);
- }
- });
- }
-
// empty out existing build directories
builder.rimraf(publish);
builder.rimraf('.netlify/edge-functions');
@@ -100,6 +60,14 @@ export default function ({ split = false, edge = edge_set_in_env_var } = {}) {
builder.rimraf('.netlify/package.json');
builder.rimraf('.netlify/serverless.js');
+ if (existsSync('.netlify/functions-internal')) {
+ for (const file of readdirSync('.netlify/functions-internal')) {
+ if (file.startsWith(FUNCTION_PREFIX)) {
+ builder.rimraf(join('.netlify/functions-internal', file));
+ }
+ }
+ }
+
builder.log.minor(`Publishing to "${publish}"`);
builder.log.minor('Copying assets...');
@@ -195,7 +163,7 @@ async function generate_edge_functions({ builder }) {
* @param { boolean } params.split
*/
async function generate_lambda_functions({ builder, publish, split }) {
- builder.mkdirp('.netlify/functions-internal');
+ builder.mkdirp('.netlify/functions-internal/.svelte-kit');
/** @type {string[]} */
const redirects = [];
@@ -236,7 +204,8 @@ async function generate_lambda_functions({ builder, publish, split }) {
}
const pattern = `/${parts.join('/')}`;
- const name = parts.join('-').replace(/[:.]/g, '_').replace('*', '__rest') || 'index';
+ const name =
+ FUNCTION_PREFIX + (parts.join('-').replace(/[:.]/g, '_').replace('*', '__rest') || 'index');
// skip routes with identical patterns, they were already folded into another function
if (seen.has(pattern)) continue;
@@ -244,10 +213,11 @@ async function generate_lambda_functions({ builder, publish, split }) {
// figure out which lower priority routes should be considered fallbacks
for (let j = i + 1; j < builder.routes.length; j += 1) {
- if (routes[j].prerender === true) continue;
+ const other = builder.routes[j];
+ if (other.prerender === true) continue;
- if (matches(route.segments, routes[j].segments)) {
- routes.push(builder.routes[j]);
+ if (matches(route.segments, other.segments)) {
+ routes.push(other);
}
}
@@ -261,8 +231,9 @@ async function generate_lambda_functions({ builder, publish, split }) {
writeFileSync(`.netlify/functions-internal/${name}.mjs`, fn);
writeFileSync(`.netlify/functions-internal/${name}.json`, fn_config);
- redirects.push(`${pattern} /.netlify/functions/${name} 200`);
- redirects.push(`${pattern}/__data.json /.netlify/functions/${name} 200`);
+ const redirect = `/.netlify/functions/${name} 200`;
+ redirects.push(`${pattern} ${redirect}`);
+ redirects.push(`${pattern === '/' ? '' : pattern}/__data.json ${redirect}`);
}
} else {
const manifest = builder.generateManifest({
@@ -271,9 +242,9 @@ async function generate_lambda_functions({ builder, publish, split }) {
const fn = `import { init } from '../serverless.js';\n\nexport const handler = init(${manifest});\n`;
- writeFileSync(`.netlify/functions-internal/render.json`, fn_config);
- writeFileSync('.netlify/functions-internal/render.mjs', fn);
- redirects.push('* /.netlify/functions/render 200');
+ writeFileSync(`.netlify/functions-internal/${FUNCTION_PREFIX}render.json`, fn_config);
+ writeFileSync(`.netlify/functions-internal/${FUNCTION_PREFIX}render.mjs`, fn);
+ redirects.push(`* /.netlify/functions/${FUNCTION_PREFIX}render 200`);
}
// this should happen at the end, after builder.writeClient(...),
diff --git a/packages/adapter-netlify/package.json b/packages/adapter-netlify/package.json
index 47d55bd40b57c..9be41afc91dbb 100644
--- a/packages/adapter-netlify/package.json
+++ b/packages/adapter-netlify/package.json
@@ -1,6 +1,6 @@
{
"name": "@sveltejs/adapter-netlify",
- "version": "2.0.2",
+ "version": "2.0.4",
"repository": {
"type": "git",
"url": "https://github.com/sveltejs/kit",
diff --git a/packages/adapter-node/CHANGELOG.md b/packages/adapter-node/CHANGELOG.md
index 5611479208abb..3d1d0fa5ea955 100644
--- a/packages/adapter-node/CHANGELOG.md
+++ b/packages/adapter-node/CHANGELOG.md
@@ -1,5 +1,25 @@
# @sveltejs/adapter-node
+## 1.2.0
+
+### Minor Changes
+
+- add polyfill option ([#8991](https://github.com/sveltejs/kit/pull/8991))
+
+### Patch Changes
+
+- Updated dependencies [[`74cfa8d5`](https://github.com/sveltejs/kit/commit/74cfa8d5f1f13f81759e20e90f4ff86a4f96040d), [`bfa2b6ec`](https://github.com/sveltejs/kit/commit/bfa2b6ec88a6d522d87c924d7c466c01e142e66e)]:
+ - @sveltejs/kit@1.5.6
+
+## 1.1.8
+
+### Patch Changes
+
+- fix: use `strictRequires: true` when bundling output ([#8958](https://github.com/sveltejs/kit/pull/8958))
+
+- Updated dependencies [[`0abb8ebf`](https://github.com/sveltejs/kit/commit/0abb8ebffc6121f81c2bbfa0a0f68866d4cc1627), [`bef54f63`](https://github.com/sveltejs/kit/commit/bef54f63d2315066d30e8f1bcf471ddf2bd72c35), [`51cd6e64`](https://github.com/sveltejs/kit/commit/51cd6e643178e3a113fc2c3e8a63755bcbfe902d), [`930c8e4e`](https://github.com/sveltejs/kit/commit/930c8e4ee2e3046ed1b622777dafa23029a19fe5), [`ee8066fc`](https://github.com/sveltejs/kit/commit/ee8066fcb29ed1e7e3ab513cabb7997e38c984f2), [`49d2ec62`](https://github.com/sveltejs/kit/commit/49d2ec62e6385694f11701bf2fa411d07449344c), [`eb943565`](https://github.com/sveltejs/kit/commit/eb943565a4324dbed3da5a581924ca91a24366de)]:
+ - @sveltejs/kit@1.5.3
+
## 1.1.7
### Patch Changes
diff --git a/packages/adapter-node/index.d.ts b/packages/adapter-node/index.d.ts
index d32510e25186f..12ea6273dd660 100644
--- a/packages/adapter-node/index.d.ts
+++ b/packages/adapter-node/index.d.ts
@@ -9,6 +9,7 @@ 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 ac16008bc1766..607e3a81797ad 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('.').default} */
export default function (opts = {}) {
- const { out = 'build', precompress, envPrefix = '' } = opts;
+ const { out = 'build', precompress, envPrefix = '', polyfill = true } = opts;
return {
name: '@sveltejs/adapter-node',
@@ -57,7 +57,7 @@ export default function (opts = {}) {
// dependencies could have deep exports, so we need a regex
...Object.keys(pkg.dependencies || {}).map((d) => new RegExp(`^${d}(\\/.*)?$`))
],
- plugins: [nodeResolve({ preferBuiltins: true }), commonjs(), json()]
+ plugins: [nodeResolve({ preferBuiltins: true }), commonjs({ strictRequires: true }), json()]
});
await bundle.write({
@@ -72,10 +72,16 @@ export default function (opts = {}) {
ENV: './env.js',
HANDLER: './handler.js',
MANIFEST: './server/manifest.js',
- SERVER: `./server/index.js`,
+ SERVER: './server/index.js',
+ SHIMS: './shims.js',
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 b4d5f982c5a8f..ef5edbd857196 100644
--- a/packages/adapter-node/package.json
+++ b/packages/adapter-node/package.json
@@ -1,6 +1,6 @@
{
"name": "@sveltejs/adapter-node",
- "version": "1.1.7",
+ "version": "1.2.0",
"repository": {
"type": "git",
"url": "https://github.com/sveltejs/kit",
diff --git a/packages/adapter-node/rollup.config.js b/packages/adapter-node/rollup.config.js
index 4c00ce5f1eb04..16c4a20c37a7c 100644
--- a/packages/adapter-node/rollup.config.js
+++ b/packages/adapter-node/rollup.config.js
@@ -30,6 +30,15 @@ export default [
inlineDynamicImports: true
},
plugins: [nodeResolve(), commonjs(), json()],
- external: ['ENV', 'MANIFEST', 'SERVER', ...builtinModules]
+ external: ['ENV', 'MANIFEST', 'SERVER', 'SHIMS', ...builtinModules]
+ },
+ {
+ input: 'src/shims.js',
+ output: {
+ file: 'files/shims.js',
+ format: 'esm'
+ },
+ plugins: [nodeResolve(), commonjs()],
+ external: builtinModules
}
];
diff --git a/packages/adapter-node/src/handler.js b/packages/adapter-node/src/handler.js
index 331b6cccaff27..b7660db2030fb 100644
--- a/packages/adapter-node/src/handler.js
+++ b/packages/adapter-node/src/handler.js
@@ -1,4 +1,4 @@
-import './shims';
+import 'SHIMS';
import fs from 'node:fs';
import path from 'node:path';
import sirv from 'sirv';
diff --git a/packages/adapter-static/CHANGELOG.md b/packages/adapter-static/CHANGELOG.md
index 5b67697a251a2..dcda7b9caa1c7 100644
--- a/packages/adapter-static/CHANGELOG.md
+++ b/packages/adapter-static/CHANGELOG.md
@@ -1,5 +1,14 @@
# @sveltejs/adapter-static
+## 2.0.1
+
+### Patch Changes
+
+- fix: generate fallback page before compressing ([#8972](https://github.com/sveltejs/kit/pull/8972))
+
+- Updated dependencies [[`0abb8ebf`](https://github.com/sveltejs/kit/commit/0abb8ebffc6121f81c2bbfa0a0f68866d4cc1627), [`bef54f63`](https://github.com/sveltejs/kit/commit/bef54f63d2315066d30e8f1bcf471ddf2bd72c35), [`51cd6e64`](https://github.com/sveltejs/kit/commit/51cd6e643178e3a113fc2c3e8a63755bcbfe902d), [`930c8e4e`](https://github.com/sveltejs/kit/commit/930c8e4ee2e3046ed1b622777dafa23029a19fe5), [`ee8066fc`](https://github.com/sveltejs/kit/commit/ee8066fcb29ed1e7e3ab513cabb7997e38c984f2), [`49d2ec62`](https://github.com/sveltejs/kit/commit/49d2ec62e6385694f11701bf2fa411d07449344c), [`eb943565`](https://github.com/sveltejs/kit/commit/eb943565a4324dbed3da5a581924ca91a24366de)]:
+ - @sveltejs/kit@1.5.3
+
## 2.0.0
### Major Changes
diff --git a/packages/adapter-static/index.js b/packages/adapter-static/index.js
index 09c11d574b35c..e4dd739fc4cca 100644
--- a/packages/adapter-static/index.js
+++ b/packages/adapter-static/index.js
@@ -64,7 +64,7 @@ See https://kit.svelte.dev/docs/page-options#prerender for more details`
builder.writePrerendered(pages);
if (fallback) {
- builder.generateFallback(path.join(pages, fallback));
+ await builder.generateFallback(path.join(pages, fallback));
}
if (precompress) {
diff --git a/packages/adapter-static/package.json b/packages/adapter-static/package.json
index cd35f8dfd731f..9fd8b1081ccac 100644
--- a/packages/adapter-static/package.json
+++ b/packages/adapter-static/package.json
@@ -1,6 +1,6 @@
{
"name": "@sveltejs/adapter-static",
- "version": "2.0.0",
+ "version": "2.0.1",
"repository": {
"type": "git",
"url": "https://github.com/sveltejs/kit",
diff --git a/packages/create-svelte/CHANGELOG.md b/packages/create-svelte/CHANGELOG.md
index 179e277dd6445..dfaffa97f16d8 100644
--- a/packages/create-svelte/CHANGELOG.md
+++ b/packages/create-svelte/CHANGELOG.md
@@ -1,5 +1,11 @@
# create-svelte
+## 2.3.4
+
+### Patch Changes
+
+- fix: use new locator API to improve demo test ([#8988](https://github.com/sveltejs/kit/pull/8988))
+
## 2.3.3
### Patch Changes
diff --git a/packages/create-svelte/bin.js b/packages/create-svelte/bin.js
index 868b6122351b1..994cb2d3b4462 100755
--- a/packages/create-svelte/bin.js
+++ b/packages/create-svelte/bin.js
@@ -165,7 +165,7 @@ async function main() {
}
console.log('\nInstall community-maintained integrations:');
- console.log(cyan(' https://github.com/svelte-add/svelte-adders'));
+ console.log(cyan(' https://github.com/svelte-add/svelte-add'));
console.log('\nNext steps:');
let i = 1;
diff --git a/packages/create-svelte/package.json b/packages/create-svelte/package.json
index 9f9d4dde1a7e8..6895401ce9bb2 100644
--- a/packages/create-svelte/package.json
+++ b/packages/create-svelte/package.json
@@ -1,6 +1,6 @@
{
"name": "create-svelte",
- "version": "2.3.3",
+ "version": "2.3.4",
"repository": {
"type": "git",
"url": "https://github.com/sveltejs/kit",
diff --git a/packages/create-svelte/shared/+playwright+default/tests/test.ts b/packages/create-svelte/shared/+playwright+default/tests/test.ts
index 38b7f95bd5bef..09d2c0346e576 100644
--- a/packages/create-svelte/shared/+playwright+default/tests/test.ts
+++ b/packages/create-svelte/shared/+playwright+default/tests/test.ts
@@ -2,5 +2,5 @@ import { expect, test } from '@playwright/test';
test('about page has expected h1', async ({ page }) => {
await page.goto('/about');
- await expect(page.locator('h1')).toHaveText('About this app');
+ await expect(page.getByRole('heading', { name: 'About this app' })).toBeVisible();
});
diff --git a/packages/create-svelte/shared/+playwright+skeleton/tests/test.ts b/packages/create-svelte/shared/+playwright+skeleton/tests/test.ts
index 4e579377eed92..5816be4132cbc 100644
--- a/packages/create-svelte/shared/+playwright+skeleton/tests/test.ts
+++ b/packages/create-svelte/shared/+playwright+skeleton/tests/test.ts
@@ -2,5 +2,5 @@ import { expect, test } from '@playwright/test';
test('index page has expected h1', async ({ page }) => {
await page.goto('/');
- expect(await page.textContent('h1')).toBe('Welcome to SvelteKit');
+ await expect(page.getByRole('heading', { name: 'Welcome to SvelteKit' })).toBeVisible();
});
diff --git a/packages/create-svelte/shared/+playwright+skeletonlib/tests/test.ts b/packages/create-svelte/shared/+playwright+skeletonlib/tests/test.ts
index 4e579377eed92..5816be4132cbc 100644
--- a/packages/create-svelte/shared/+playwright+skeletonlib/tests/test.ts
+++ b/packages/create-svelte/shared/+playwright+skeletonlib/tests/test.ts
@@ -2,5 +2,5 @@ import { expect, test } from '@playwright/test';
test('index page has expected h1', async ({ page }) => {
await page.goto('/');
- expect(await page.textContent('h1')).toBe('Welcome to SvelteKit');
+ await expect(page.getByRole('heading', { name: 'Welcome to SvelteKit' })).toBeVisible();
});
diff --git a/packages/create-svelte/templates/skeleton/package.json b/packages/create-svelte/templates/skeleton/package.json
index 587569066aa68..1706b9d759f84 100644
--- a/packages/create-svelte/templates/skeleton/package.json
+++ b/packages/create-svelte/templates/skeleton/package.json
@@ -1,5 +1,6 @@
{
"name": "skeleton-template",
+ "private": true,
"version": "0.0.1-next.0",
"devDependencies": {
"@sveltejs/adapter-auto": "workspace:*"
diff --git a/packages/create-svelte/test/check.js b/packages/create-svelte/test/check.js
index d0ca7b4024100..a1ee4bb538510 100644
--- a/packages/create-svelte/test/check.js
+++ b/packages/create-svelte/test/check.js
@@ -21,7 +21,8 @@ const overrides = { ...existing_workspace_overrides };
await glob(fileURLToPath(new URL('../../../packages', import.meta.url)) + '/*/package.json')
).forEach((pkgPath) => {
const name = JSON.parse(fs.readFileSync(pkgPath, 'utf-8')).name;
- overrides[name] = path.dirname(path.resolve(pkgPath));
+ // use `file:` protocol for opting into stricter resolve logic which catches more bugs
+ overrides[name] = `file:${path.dirname(path.resolve(pkgPath))}`;
});
try {
diff --git a/packages/kit/CHANGELOG.md b/packages/kit/CHANGELOG.md
index b20b0a65a189e..155b95a7d1771 100644
--- a/packages/kit/CHANGELOG.md
+++ b/packages/kit/CHANGELOG.md
@@ -1,5 +1,65 @@
# @sveltejs/kit
+## 1.6.0
+
+### Minor Changes
+
+- feat: add `OPTIONS` server method ([#8731](https://github.com/sveltejs/kit/pull/8731))
+
+### Patch Changes
+
+- fix: solve `missing "./paths" specifier in "@sveltejs/kit" package` error occurring in all projects ([#9050](https://github.com/sveltejs/kit/pull/9050))
+
+## 1.5.7
+
+### Patch Changes
+
+- fix: use internal alias that won't collide with user aliases ([#9022](https://github.com/sveltejs/kit/pull/9022))
+
+## 1.5.6
+
+### Patch Changes
+
+- fix: ssr defaults preventing minification for client build ([#9012](https://github.com/sveltejs/kit/pull/9012))
+
+- fix: client-side trailing slash redirect when preloading data ([#8982](https://github.com/sveltejs/kit/pull/8982))
+
+## 1.5.5
+
+### Patch Changes
+
+- fix: warn after failed data preloads in dev ([#8985](https://github.com/sveltejs/kit/pull/8985))
+
+## 1.5.4
+
+### Patch Changes
+
+- fix: support all relevant vite cli flags ([#8977](https://github.com/sveltejs/kit/pull/8977))
+
+## 1.5.3
+
+### Patch Changes
+
+- docs: clarify that `version.name` should be deterministic ([#8956](https://github.com/sveltejs/kit/pull/8956))
+
+- fix: correctly include exported http methods in allow header ([#8968](https://github.com/sveltejs/kit/pull/8968))
+
+- chore: polyfill File from node:buffer ([#8925](https://github.com/sveltejs/kit/pull/8925))
+
+- fix: provide helpful error/warning when calling `fetch` during render ([#8551](https://github.com/sveltejs/kit/pull/8551))
+
+- fix: print useful error when subscribing to SvelteKit's stores at the wrong time during SSR ([#8960](https://github.com/sveltejs/kit/pull/8960))
+
+- fix: ignore external links when automatically preloading ([#8961](https://github.com/sveltejs/kit/pull/8961))
+
+- chore: refactor fallback generation ([#8972](https://github.com/sveltejs/kit/pull/8972))
+
+## 1.5.2
+
+### Patch Changes
+
+- fix: always default `paths.assets` to `paths.base` ([#8928](https://github.com/sveltejs/kit/pull/8928))
+
## 1.5.1
### Patch Changes
diff --git a/packages/kit/package.json b/packages/kit/package.json
index 17a62db6fc805..d3ce7f6d87670 100644
--- a/packages/kit/package.json
+++ b/packages/kit/package.json
@@ -1,6 +1,6 @@
{
"name": "@sveltejs/kit",
- "version": "1.5.1",
+ "version": "1.6.0",
"repository": {
"type": "git",
"url": "https://github.com/sveltejs/kit",
@@ -57,7 +57,7 @@
"postinstall.js"
],
"scripts": {
- "lint": "prettier --check . --config ../../.prettierrc --ignore-path .gitignore && eslint src/**",
+ "lint": "prettier --check . --config ../../.prettierrc --ignore-path .gitignore && eslint src/**/*.js",
"check": "tsc",
"check:all": "tsc && pnpm -r --filter=\"./**\" check",
"format": "prettier --write . --config ../../.prettierrc --ignore-path .gitignore",
diff --git a/packages/kit/src/core/adapt/builder.js b/packages/kit/src/core/adapt/builder.js
index f30228d658230..e28ce02d9e158 100644
--- a/packages/kit/src/core/adapt/builder.js
+++ b/packages/kit/src/core/adapt/builder.js
@@ -1,7 +1,5 @@
-import { fork } from 'node:child_process';
import { existsSync, statSync, createReadStream, createWriteStream } from 'node:fs';
import { pipeline } from 'node:stream';
-import { fileURLToPath } from 'node:url';
import { promisify } from 'node:util';
import zlib from 'node:zlib';
import glob from 'tiny-glob';
@@ -9,6 +7,8 @@ import { copy, rimraf, mkdirp } from '../../utils/filesystem.js';
import { generate_manifest } from '../generate_manifest/index.js';
import { get_route_segments } from '../../utils/routing.js';
import { get_env } from '../../exports/vite/utils.js';
+import generate_fallback from '../postbuild/fallback.js';
+import { write } from '../sync/utils.js';
const pipe = promisify(pipeline);
@@ -142,31 +142,16 @@ export function create_builder({
}
},
- generateFallback(dest) {
- // do prerendering in a subprocess so any dangling stuff gets killed upon completion
- const script = fileURLToPath(new URL('../postbuild/fallback.js', import.meta.url));
-
+ async generateFallback(dest) {
const manifest_path = `${config.kit.outDir}/output/server/manifest-full.js`;
-
const env = get_env(config.kit.env, 'production');
- return new Promise((fulfil, reject) => {
- const child = fork(
- script,
- [dest, manifest_path, JSON.stringify({ ...env.private, ...env.public })],
- {
- stdio: 'inherit'
- }
- );
-
- child.on('exit', (code) => {
- if (code) {
- reject(new Error(`Could not create a fallback page — failed with code ${code}`));
- } else {
- fulfil(undefined);
- }
- });
+ const fallback = await generate_fallback({
+ manifest_path,
+ env: { ...env.private, ...env.public }
});
+
+ write(dest, fallback);
},
generateManifest: ({ relativePath, routes: subset }) => {
diff --git a/packages/kit/src/core/postbuild/analyse.js b/packages/kit/src/core/postbuild/analyse.js
index 7b9ec39a3a701..1588c4a4a8b3c 100644
--- a/packages/kit/src/core/postbuild/analyse.js
+++ b/packages/kit/src/core/postbuild/analyse.js
@@ -89,6 +89,7 @@ async function analyse({ manifest_path, env }) {
if (mod.PUT) methods.add('PUT');
if (mod.PATCH) methods.add('PATCH');
if (mod.DELETE) methods.add('DELETE');
+ if (mod.OPTIONS) methods.add('OPTIONS');
config = mod.config;
}
@@ -105,8 +106,8 @@ async function analyse({ manifest_path, env }) {
for (const layout of layouts) {
if (layout) {
- validate_common_exports(layout.server, route.id);
- validate_common_exports(layout.universal, route.id);
+ validate_common_exports(layout.server, layout.server_id);
+ validate_common_exports(layout.universal, layout.universal_id);
}
}
@@ -114,8 +115,8 @@ async function analyse({ manifest_path, env }) {
methods.add('GET');
if (page.server?.actions) methods.add('POST');
- validate_page_server_exports(page.server, route.id);
- validate_common_exports(page.universal, route.id);
+ validate_page_server_exports(page.server, page.server_id);
+ validate_common_exports(page.universal, page.universal_id);
}
const should_prerender = get_option(nodes, 'prerender');
diff --git a/packages/kit/src/core/postbuild/fallback.js b/packages/kit/src/core/postbuild/fallback.js
index de49c1681cfec..dbce677d7094b 100644
--- a/packages/kit/src/core/postbuild/fallback.js
+++ b/packages/kit/src/core/postbuild/fallback.js
@@ -1,43 +1,54 @@
-import { readFileSync, writeFileSync } from 'node:fs';
-import { dirname, join } from 'node:path';
+import { readFileSync } from 'node:fs';
+import { join } from 'node:path';
import { pathToFileURL } from 'node:url';
-import { mkdirp } from '../../utils/filesystem.js';
import { installPolyfills } from '../../exports/node/polyfills.js';
import { load_config } from '../config/index.js';
+import { forked } from '../../utils/fork.js';
-const [, , dest, manifest_path, env] = process.argv;
+export default forked(import.meta.url, generate_fallback);
-/** @type {import('types').ValidatedKitConfig} */
-const config = (await load_config()).kit;
+/**
+ * @param {{
+ * manifest_path: string;
+ * env: Record
+ * }} opts
+ */
+async function generate_fallback({ manifest_path, env }) {
+ /** @type {import('types').ValidatedKitConfig} */
+ const config = (await load_config()).kit;
-installPolyfills();
+ installPolyfills();
-const server_root = join(config.outDir, 'output');
+ const server_root = join(config.outDir, 'output');
-/** @type {import('types').ServerInternalModule} */
-const { set_building } = await import(pathToFileURL(`${server_root}/server/internal.js`).href);
+ /** @type {import('types').ServerInternalModule} */
+ const { set_building } = await import(pathToFileURL(`${server_root}/server/internal.js`).href);
-/** @type {import('types').ServerModule} */
-const { Server } = await import(pathToFileURL(`${server_root}/server/index.js`).href);
+ /** @type {import('types').ServerModule} */
+ const { Server } = await import(pathToFileURL(`${server_root}/server/index.js`).href);
-/** @type {import('types').SSRManifest} */
-const manifest = (await import(pathToFileURL(manifest_path).href)).manifest;
+ /** @type {import('types').SSRManifest} */
+ const manifest = (await import(pathToFileURL(manifest_path).href)).manifest;
-set_building(true);
+ set_building(true);
-const server = new Server(manifest);
-await server.init({ env: JSON.parse(env) });
+ const server = new Server(manifest);
+ await server.init({ env });
-const rendered = await server.respond(new Request(config.prerender.origin + '/[fallback]'), {
- getClientAddress: () => {
- throw new Error('Cannot read clientAddress during prerendering');
- },
- prerendering: {
- fallback: true,
- dependencies: new Map()
- },
- read: (file) => readFileSync(join(config.files.assets, file))
-});
+ const response = await server.respond(new Request(config.prerender.origin + '/[fallback]'), {
+ getClientAddress: () => {
+ throw new Error('Cannot read clientAddress during prerendering');
+ },
+ prerendering: {
+ fallback: true,
+ dependencies: new Map()
+ },
+ read: (file) => readFileSync(join(config.files.assets, file))
+ });
-mkdirp(dirname(dest));
-writeFileSync(dest, await rendered.text());
+ if (response.ok) {
+ return await response.text();
+ }
+
+ throw new Error(`Could not create a fallback page — failed with status ${response.status}`);
+}
diff --git a/packages/kit/src/core/postbuild/prerender.js b/packages/kit/src/core/postbuild/prerender.js
index 515d815e3303a..cdfaacb0d407d 100644
--- a/packages/kit/src/core/postbuild/prerender.js
+++ b/packages/kit/src/core/postbuild/prerender.js
@@ -4,7 +4,7 @@ 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 { is_root_relative, resolve } from '../../utils/url.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';
import { load_config } from '../config/index.js';
@@ -207,7 +207,7 @@ async function prerender({ out, manifest_path, metadata, verbose, env }) {
// this seems circuitous, but using new URL allows us to not care
// whether dependency_path is encoded or not
const encoded_dependency_path = new URL(dependency_path, 'http://localhost').pathname;
- const decoded_dependency_path = decodeURI(encoded_dependency_path);
+ const decoded_dependency_path = decode_uri(encoded_dependency_path);
const headers = Object.fromEntries(result.response.headers);
@@ -215,7 +215,7 @@ async function prerender({ out, manifest_path, metadata, verbose, env }) {
if (prerender) {
const encoded_route_id = headers['x-sveltekit-routeid'];
if (encoded_route_id != null) {
- const route_id = decodeURI(encoded_route_id);
+ const route_id = decode_uri(encoded_route_id);
const existing_value = prerender_map.get(route_id);
if (existing_value !== 'auto') {
prerender_map.set(route_id, prerender === 'true' ? true : 'auto');
@@ -257,7 +257,7 @@ async function prerender({ out, manifest_path, metadata, verbose, env }) {
}
if (hash) {
- const key = decodeURI(pathname + hash);
+ const key = decode_uri(pathname + hash);
if (!expected_hashlinks.has(key)) {
expected_hashlinks.set(key, new Set());
@@ -266,7 +266,7 @@ async function prerender({ out, manifest_path, metadata, verbose, env }) {
/** @type {Set} */ (expected_hashlinks.get(key)).add(decoded);
}
- enqueue(decoded, decodeURI(pathname), pathname);
+ enqueue(decoded, decode_uri(pathname), pathname);
}
}
}
@@ -293,7 +293,7 @@ async function prerender({ out, manifest_path, metadata, verbose, env }) {
if (written.has(file)) return;
const encoded_route_id = response.headers.get('x-sveltekit-routeid');
- const route_id = encoded_route_id != null ? decodeURI(encoded_route_id) : null;
+ const route_id = encoded_route_id != null ? decode_uri(encoded_route_id) : null;
if (route_id !== null) prerendered_routes.add(route_id);
if (response_type === REDIRECT) {
@@ -302,7 +302,7 @@ async function prerender({ out, manifest_path, metadata, verbose, env }) {
if (location) {
const resolved = resolve(encoded, location);
if (is_root_relative(resolved)) {
- enqueue(decoded, decodeURI(resolved), resolved);
+ enqueue(decoded, decode_uri(resolved), resolved);
}
if (!headers['x-sveltekit-normalize']) {
diff --git a/packages/kit/src/exports/node/polyfills.js b/packages/kit/src/exports/node/polyfills.js
index 28f9de5f9d9d5..950c645258bcf 100644
--- a/packages/kit/src/exports/node/polyfills.js
+++ b/packages/kit/src/exports/node/polyfills.js
@@ -1,6 +1,10 @@
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 } from 'undici';
+import { fetch, Response, Request, Headers, FormData, File as UndiciFile } from 'undici';
+
+// @ts-expect-error
+const File = buffer.File ?? UndiciFile;
/** @type {Record} */
const globals = {
@@ -12,7 +16,8 @@ const globals = {
ReadableStream,
TransformStream,
WritableStream,
- FormData
+ FormData,
+ File
};
// exported for dev/preview and node environments
diff --git a/packages/kit/src/exports/vite/build/build_server.js b/packages/kit/src/exports/vite/build/build_server.js
index 5d3b03bad95e5..795fca4d2cf54 100644
--- a/packages/kit/src/exports/vite/build/build_server.js
+++ b/packages/kit/src/exports/vite/build/build_server.js
@@ -73,11 +73,13 @@ export function build_server_nodes(out, kit, manifest_data, server_manifest, cli
imports.push(`import * as universal from '../${server_manifest[node.universal].file}';`);
exports.push(`export { universal };`);
+ exports.push(`export const universal_id = ${s(node.universal)};`);
}
if (node.server) {
imports.push(`import * as server from '../${server_manifest[node.server].file}';`);
exports.push(`export { server };`);
+ exports.push(`export const server_id = ${s(node.server)};`);
}
exports.push(
diff --git a/packages/kit/src/exports/vite/build/utils.js b/packages/kit/src/exports/vite/build/utils.js
index 540dbd2e85ece..3c1499316212b 100644
--- a/packages/kit/src/exports/vite/build/utils.js
+++ b/packages/kit/src/exports/vite/build/utils.js
@@ -88,7 +88,7 @@ export function assets_base(config) {
return (config.paths.assets || config.paths.base || '.') + '/';
}
-const method_names = new Set(['GET', 'HEAD', 'PUT', 'POST', 'DELETE', 'PATCH']);
+const method_names = new Set(['GET', 'HEAD', 'PUT', 'POST', 'DELETE', 'PATCH', 'OPTIONS']);
// If we'd written this in TypeScript, it could be easy...
/**
diff --git a/packages/kit/src/exports/vite/index.js b/packages/kit/src/exports/vite/index.js
index 4b6f471fd4d10..c193150571dce 100644
--- a/packages/kit/src/exports/vite/index.js
+++ b/packages/kit/src/exports/vite/index.js
@@ -179,6 +179,9 @@ function kit({ svelte_config }) {
/** @type {() => Promise} */
let finalise;
+ /** @type {import('vite').UserConfig} */
+ let initial_config;
+
const service_worker_entry_file = resolve_entry(kit.files.serviceWorker);
/** @type {import('vite').Plugin} */
@@ -190,6 +193,7 @@ function kit({ svelte_config }) {
* @see https://vitejs.dev/guide/api-plugin.html#config
*/
async config(config, config_env) {
+ initial_config = config;
vite_config_env = config_env;
is_build = config_env.command === 'build';
@@ -231,7 +235,11 @@ function kit({ svelte_config }) {
// Ignore all siblings of config.kit.outDir/generated
`${posixify(kit.outDir)}/!(generated)`
]
- }
+ },
+ cors: { preflightContinue: true }
+ },
+ preview: {
+ cors: { preflightContinue: true }
},
optimizeDeps: {
exclude: [
@@ -330,7 +338,7 @@ function kit({ svelte_config }) {
async resolveId(id) {
// treat $env/static/[public|private] as virtual
- if (id.startsWith('$env/') || id === '$internal/paths' || id === '$service-worker') {
+ if (id.startsWith('$env/') || id === '__sveltekit/paths' || id === '$service-worker') {
return `\0${id}`;
}
},
@@ -375,7 +383,9 @@ function kit({ svelte_config }) {
);
case '\0$service-worker':
return create_service_worker_module(svelte_config);
- case '\0$internal/paths':
+ // for internal use only. it's published as $app/paths externally
+ // we use this alias so that we won't collide with user aliases
+ case '\0__sveltekit/paths':
const { assets, base } = svelte_config.kit.paths;
return `export const base = ${s(base)};
export let assets = ${assets ? s(assets) : 'base'};
@@ -652,7 +662,15 @@ export function set_assets(path) {
// CLI args
mode: vite_config_env.mode,
logLevel: vite_config.logLevel,
- clearScreen: vite_config.clearScreen
+ clearScreen: vite_config.clearScreen,
+ build: {
+ minify: initial_config.build?.minify,
+ assetsInlineLimit: vite_config.build.assetsInlineLimit,
+ sourcemap: vite_config.build.sourcemap
+ },
+ optimizeDeps: {
+ force: vite_config.optimizeDeps.force
+ }
})
);
diff --git a/packages/kit/src/internal.d.ts b/packages/kit/src/internal.d.ts
new file mode 100644
index 0000000000000..b7b17ee325a82
--- /dev/null
+++ b/packages/kit/src/internal.d.ts
@@ -0,0 +1,6 @@
+/** Internal version of $app/paths */
+declare module '__sveltekit/paths' {
+ export const base: `/${string}`;
+ export let assets: `https://${string}` | `http://${string}`;
+ export function set_assets(path: string): void;
+}
diff --git a/packages/kit/src/runtime/app/paths.js b/packages/kit/src/runtime/app/paths.js
index 31d76ad8d7f84..20c6b1c4d7163 100644
--- a/packages/kit/src/runtime/app/paths.js
+++ b/packages/kit/src/runtime/app/paths.js
@@ -1 +1 @@
-export { base, assets } from '$internal/paths';
+export { base, assets } from '__sveltekit/paths';
diff --git a/packages/kit/src/runtime/app/stores.js b/packages/kit/src/runtime/app/stores.js
index 5c79a7ad3f941..67727a119d828 100644
--- a/packages/kit/src/runtime/app/stores.js
+++ b/packages/kit/src/runtime/app/stores.js
@@ -23,7 +23,7 @@ export const getStores = () => {
export const page = {
/** @param {(value: any) => void} fn */
subscribe(fn) {
- const store = getStores().page;
+ const store = __SVELTEKIT_DEV__ ? get_store('page') : getStores().page;
return store.subscribe(fn);
}
};
@@ -31,7 +31,7 @@ export const page = {
/** @type {typeof import('$app/stores').navigating} */
export const navigating = {
subscribe(fn) {
- const store = getStores().navigating;
+ const store = __SVELTEKIT_DEV__ ? get_store('navigating') : getStores().navigating;
return store.subscribe(fn);
}
};
@@ -39,7 +39,7 @@ export const navigating = {
/** @type {typeof import('$app/stores').updated} */
export const updated = {
subscribe(fn) {
- const store = getStores().updated;
+ const store = __SVELTEKIT_DEV__ ? get_store('updated') : getStores().updated;
if (browser) {
updated.check = store.check;
@@ -55,3 +55,18 @@ export const updated = {
);
}
};
+
+/**
+ * @template {keyof ReturnType} Name
+ * @param {Name} name
+ * @returns {ReturnType[Name]}
+ */
+function get_store(name) {
+ try {
+ return getStores()[name];
+ } catch (e) {
+ throw new Error(
+ `Cannot subscribe to '${name}' store on the server outside of a Svelte component, as it is bound to the current request via component context. This prevents state from leaking between users.`
+ );
+ }
+}
diff --git a/packages/kit/src/runtime/client/client.js b/packages/kit/src/runtime/client/client.js
index 926acd7862bf3..b09f9803fb695 100644
--- a/packages/kit/src/runtime/client/client.js
+++ b/packages/kit/src/runtime/client/client.js
@@ -25,7 +25,7 @@ import {
} from './fetcher.js';
import { parse } from './parse.js';
-import { base } from '$internal/paths';
+import { base } from '__sveltekit/paths';
import { HttpError, Redirect } from '../control.js';
import { stores } from './singletons.js';
import { unwrap_promises } from '../../utils/promises.js';
@@ -220,14 +220,8 @@ export function create_client({ app, target }) {
});
}
- /** @param {URL} url */
- async function preload_data(url) {
- const intent = get_navigation_intent(url, false);
-
- if (!intent) {
- throw new Error(`Attempted to preload a URL that does not belong to this app: ${url}`);
- }
-
+ /** @param {import('./types').NavigationIntent} intent */
+ async function preload_data(intent) {
load_cache = {
id: intent.id,
promise: load_route(intent).then((result) => {
@@ -316,7 +310,7 @@ export function create_client({ app, target }) {
);
return false;
}
- } else if (/** @type {number} */ (navigation_result.props?.page?.status) >= 400) {
+ } else if (/** @type {number} */ (navigation_result.props.page?.status) >= 400) {
const updated = await stores.updated.check();
if (updated) {
await native_navigation(url);
@@ -336,6 +330,14 @@ export function create_client({ app, target }) {
capture_snapshot(previous_history_index);
}
+ // ensure the url pathname matches the page's trailing slash option
+ if (
+ navigation_result.props.page?.url &&
+ navigation_result.props.page.url.pathname !== url.pathname
+ ) {
+ url.pathname = navigation_result.props.page?.url.pathname;
+ }
+
if (opts && opts.details) {
const { details } = opts;
const change = details.replaceState ? 0 : 1;
@@ -360,6 +362,7 @@ export function create_client({ app, target }) {
if (started) {
current = navigation_result.state;
+ // reset url before updating page store
if (navigation_result.props.page) {
navigation_result.props.page.url = url;
}
@@ -1260,7 +1263,23 @@ export function create_client({ app, target }) {
if (!options.reload) {
if (priority <= options.preload_data) {
- preload_data(/** @type {URL} */ (url));
+ const intent = get_navigation_intent(/** @type {URL} */ (url), false);
+ if (intent) {
+ if (__SVELTEKIT_DEV__) {
+ preload_data(intent).then((result) => {
+ if (result.type === 'loaded' && result.state.error) {
+ console.warn(
+ `Preloading data for ${intent.url.pathname} failed with the following error: ${result.state.error.message}\n` +
+ 'If this error is transient, you can ignore it. Otherwise, consider disabling preloading for this route. ' +
+ 'This route was preloaded due to a data-sveltekit-preload-data attribute. ' +
+ 'See https://kit.svelte.dev/docs/link-options for more info'
+ );
+ }
+ });
+ } else {
+ preload_data(intent);
+ }
+ }
} else if (priority <= options.preload_code) {
preload_code(get_url_path(/** @type {URL} */ (url)));
}
@@ -1361,7 +1380,13 @@ export function create_client({ app, target }) {
preload_data: async (href) => {
const url = new URL(href, get_base_uri(document));
- await preload_data(url);
+ const intent = get_navigation_intent(url, false);
+
+ if (!intent) {
+ throw new Error(`Attempted to preload a URL that does not belong to this app: ${url}`);
+ }
+
+ await preload_data(intent);
},
preload_code,
diff --git a/packages/kit/src/runtime/client/utils.js b/packages/kit/src/runtime/client/utils.js
index 1c3c392af6496..5874dd4ed771a 100644
--- a/packages/kit/src/runtime/client/utils.js
+++ b/packages/kit/src/runtime/client/utils.js
@@ -1,6 +1,6 @@
import { BROWSER, DEV } from 'esm-env';
import { writable } from 'svelte/store';
-import { assets } from '$internal/paths';
+import { assets } from '__sveltekit/paths';
import { version } from '../shared.js';
import { PRELOAD_PRIORITIES } from './constants.js';
diff --git a/packages/kit/src/runtime/server/endpoint.js b/packages/kit/src/runtime/server/endpoint.js
index 5710803686702..814fe907ed15c 100644
--- a/packages/kit/src/runtime/server/endpoint.js
+++ b/packages/kit/src/runtime/server/endpoint.js
@@ -75,7 +75,7 @@ export async function render_endpoint(event, route, mod, state) {
export function is_endpoint_request(event) {
const { method, headers } = event.request;
- if (method === 'PUT' || method === 'PATCH' || method === 'DELETE') {
+ if (method === 'PUT' || method === 'PATCH' || method === 'DELETE' || method === 'OPTIONS') {
// These methods exist exclusively for endpoints
return true;
}
diff --git a/packages/kit/src/runtime/server/fetch.js b/packages/kit/src/runtime/server/fetch.js
index ebde07df2b66f..2cbc013f00e75 100644
--- a/packages/kit/src/runtime/server/fetch.js
+++ b/packages/kit/src/runtime/server/fetch.js
@@ -1,6 +1,6 @@
import * as set_cookie_parser from 'set-cookie-parser';
import { respond } from './respond.js';
-import * as paths from '$internal/paths';
+import * as paths from '__sveltekit/paths';
/**
* @param {{
diff --git a/packages/kit/src/runtime/server/page/render.js b/packages/kit/src/runtime/server/page/render.js
index 85b0d40ab3471..5dfb1332a6843 100644
--- a/packages/kit/src/runtime/server/page/render.js
+++ b/packages/kit/src/runtime/server/page/render.js
@@ -1,7 +1,7 @@
import * as devalue from 'devalue';
import { readable, writable } from 'svelte/store';
import { DEV } from 'esm-env';
-import { assets, base } from '$internal/paths';
+import { assets, base } from '__sveltekit/paths';
import { hash } from '../../hash.js';
import { serialize_data } from './serialize_data.js';
import { s } from '../../../utils/misc.js';
@@ -113,7 +113,32 @@ export async function render_response({
form: form_value
};
- rendered = options.root.render(props);
+ if (__SVELTEKIT_DEV__) {
+ const fetch = globalThis.fetch;
+ let warned = false;
+ globalThis.fetch = (info, init) => {
+ if (typeof info === 'string' && !/^\w+:\/\//.test(info)) {
+ throw new Error(
+ `Cannot call \`fetch\` eagerly during server side rendering with relative URL (${info}) — put your \`fetch\` calls inside \`onMount\` or a \`load\` function instead`
+ );
+ } else if (!warned) {
+ console.warn(
+ `Avoid calling \`fetch\` eagerly during server side rendering — put your \`fetch\` calls inside \`onMount\` or a \`load\` function instead`
+ );
+ warned = true;
+ }
+
+ return fetch(info, init);
+ };
+
+ try {
+ rendered = options.root.render(props);
+ } finally {
+ globalThis.fetch = fetch;
+ }
+ } else {
+ rendered = options.root.render(props);
+ }
for (const { node } of branch) {
for (const url of node.imports) modulepreloads.add(url);
diff --git a/packages/kit/src/runtime/server/respond.js b/packages/kit/src/runtime/server/respond.js
index fb148e19b3317..3ad089176b24a 100644
--- a/packages/kit/src/runtime/server/respond.js
+++ b/packages/kit/src/runtime/server/respond.js
@@ -1,5 +1,5 @@
import { DEV } from 'esm-env';
-import { base } from '$internal/paths';
+import { base } from '__sveltekit/paths';
import { is_endpoint_request, render_endpoint } from './endpoint.js';
import { render_page } from './page/index.js';
import { render_response } from './page/render.js';
diff --git a/packages/kit/src/runtime/server/utils.js b/packages/kit/src/runtime/server/utils.js
index ed8c4803d4c02..9a58ee3bf0214 100644
--- a/packages/kit/src/runtime/server/utils.js
+++ b/packages/kit/src/runtime/server/utils.js
@@ -41,7 +41,7 @@ export function method_not_allowed(mod, method) {
export function allowed_methods(mod) {
const allowed = [];
- for (const method in ['GET', 'POST', 'PUT', 'PATCH', 'DELETE']) {
+ for (const method in ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS']) {
if (method in mod) allowed.push(method);
}
diff --git a/packages/kit/src/runtime/shared.js b/packages/kit/src/runtime/shared.js
index 40c372a51b452..004412f3ba669 100644
--- a/packages/kit/src/runtime/shared.js
+++ b/packages/kit/src/runtime/shared.js
@@ -1,4 +1,4 @@
-export { set_assets } from '$internal/paths';
+export { set_assets } from '__sveltekit/paths';
export let building = false;
export let version = '';
diff --git a/packages/kit/src/utils/exports.js b/packages/kit/src/utils/exports.js
index 3a38d9b46d17d..7f78ec0381a73 100644
--- a/packages/kit/src/utils/exports.js
+++ b/packages/kit/src/utils/exports.js
@@ -6,36 +6,52 @@ function validator(expected) {
/**
* @param {any} module
- * @param {string} [route_id]
+ * @param {string} [file]
*/
- function validate(module, route_id) {
+ function validate(module, file) {
if (!module) return;
for (const key in module) {
- if (key[0] !== '_' && !set.has(key)) {
- const valid = expected.join(', ');
- throw new Error(
- `Invalid export '${key}'${
- route_id ? ` in ${route_id}` : ''
- } (valid exports are ${valid}, or anything with a '_' prefix)`
- );
- }
+ if (key[0] === '_' || set.has(key)) continue; // key is valid in this module
+
+ const hint =
+ hint_for_supported_files(key, file?.slice(file.lastIndexOf('.'))) ??
+ `valid exports are ${expected.join(', ')}, or anything with a '_' prefix`;
+
+ throw new Error(`Invalid export '${key}'${file ? ` in ${file}` : ''} (${hint})`);
}
}
return validate;
}
-export const validate_common_exports = validator([
- 'load',
- 'prerender',
- 'csr',
- 'ssr',
- 'trailingSlash',
- 'config'
-]);
+/**
+ * @param {string} key
+ * @param {string} ext
+ * @returns {string | void}
+ */
+function hint_for_supported_files(key, ext = '.js') {
+ let supported_files = [];
-export const validate_page_server_exports = validator([
+ if (valid_common_exports.includes(key)) {
+ supported_files.push(`+page${ext}`);
+ }
+
+ if (valid_page_server_exports.includes(key)) {
+ supported_files.push(`+page.server${ext}`);
+ }
+
+ if (valid_server_exports.includes(key)) {
+ supported_files.push(`+server${ext}`);
+ }
+
+ if (supported_files.length > 0) {
+ return `'${key}' is a valid export in ${supported_files.join(` or `)}`;
+ }
+}
+
+const valid_common_exports = ['load', 'prerender', 'csr', 'ssr', 'trailingSlash', 'config'];
+const valid_page_server_exports = [
'load',
'prerender',
'csr',
@@ -43,15 +59,19 @@ export const validate_page_server_exports = validator([
'actions',
'trailingSlash',
'config'
-]);
-
-export const validate_server_exports = validator([
+];
+const valid_server_exports = [
'GET',
'POST',
'PATCH',
'PUT',
'DELETE',
+ 'OPTIONS',
'prerender',
'trailingSlash',
'config'
-]);
+];
+
+export const validate_common_exports = validator(valid_common_exports);
+export const validate_page_server_exports = validator(valid_page_server_exports);
+export const validate_server_exports = validator(valid_server_exports);
diff --git a/packages/kit/src/utils/exports.spec.js b/packages/kit/src/utils/exports.spec.js
index 46396395948fe..bbca70d0e7a16 100644
--- a/packages/kit/src/utils/exports.spec.js
+++ b/packages/kit/src/utils/exports.spec.js
@@ -6,6 +6,22 @@ import {
validate_server_exports
} from './exports.js';
+/**
+ * @param {() => void} fn
+ * @param {string} message
+ */
+function check_error(fn, message) {
+ let error;
+
+ try {
+ fn();
+ } catch (e) {
+ error = /** @type {Error} */ (e);
+ }
+
+ assert.equal(error?.message, message);
+}
+
test('validates +layout.server.js, +layout.js, +page.js', () => {
validate_common_exports({
load: () => {}
@@ -15,11 +31,26 @@ test('validates +layout.server.js, +layout.js, +page.js', () => {
_unknown: () => {}
});
- assert.throws(() => {
+ check_error(() => {
+ validate_common_exports({
+ answer: 42
+ });
+ }, `Invalid export 'answer' (valid exports are load, prerender, csr, ssr, trailingSlash, config, or anything with a '_' prefix)`);
+
+ check_error(() => {
+ validate_common_exports(
+ {
+ actions: {}
+ },
+ 'src/routes/foo/+page.ts'
+ );
+ }, `Invalid export 'actions' in src/routes/foo/+page.ts ('actions' is a valid export in +page.server.ts)`);
+
+ check_error(() => {
validate_common_exports({
- actions: {}
+ GET: {}
});
- }, /Invalid export 'actions' \(valid exports are load, prerender, csr, ssr, trailingSlash, config, or anything with a '_' prefix\)/);
+ }, `Invalid export 'GET' ('GET' is a valid export in +server.js)`);
});
test('validates +page.server.js', () => {
@@ -32,11 +63,17 @@ test('validates +page.server.js', () => {
_unknown: () => {}
});
- assert.throws(() => {
+ check_error(() => {
validate_page_server_exports({
answer: 42
});
- }, /Invalid export 'answer' \(valid exports are load, prerender, csr, ssr, actions, trailingSlash, config, or anything with a '_' prefix\)/);
+ }, `Invalid export 'answer' (valid exports are load, prerender, csr, ssr, actions, trailingSlash, config, or anything with a '_' prefix)`);
+
+ check_error(() => {
+ validate_page_server_exports({
+ POST: {}
+ });
+ }, `Invalid export 'POST' ('POST' is a valid export in +server.js)`);
});
test('validates +server.js', () => {
@@ -48,11 +85,17 @@ test('validates +server.js', () => {
_unknown: () => {}
});
- assert.throws(() => {
+ check_error(() => {
validate_server_exports({
answer: 42
});
- }, /Invalid export 'answer' \(valid exports are GET, POST, PATCH, PUT, DELETE, prerender, trailingSlash, config, or anything with a '_' prefix\)/);
+ }, `Invalid export 'answer' (valid exports are GET, POST, PATCH, PUT, DELETE, OPTIONS, prerender, trailingSlash, config, or anything with a '_' prefix)`);
+
+ check_error(() => {
+ validate_server_exports({
+ csr: false
+ });
+ }, `Invalid export 'csr' ('csr' is a valid export in +page.js or +page.server.js)`);
});
test.run();
diff --git a/packages/kit/src/utils/url.js b/packages/kit/src/utils/url.js
index d0c917b2bb646..239dace8b0896 100644
--- a/packages/kit/src/utils/url.js
+++ b/packages/kit/src/utils/url.js
@@ -75,6 +75,21 @@ export function decode_params(params) {
return params;
}
+/**
+ * The error when a URL is malformed is not very helpful, so we augment it with the URI
+ * @param {string} uri
+ */
+export function decode_uri(uri) {
+ try {
+ return decodeURI(uri);
+ } catch (e) {
+ if (e instanceof Error) {
+ e.message = `Failed to decode URI: ${uri}\n` + e.message;
+ }
+ throw e;
+ }
+}
+
/**
* URL properties that could change during the lifetime of the page,
* which excludes things like `origin`
diff --git a/packages/kit/test/apps/basics/src/routes/endpoint-output/options/+server.js b/packages/kit/test/apps/basics/src/routes/endpoint-output/options/+server.js
new file mode 100644
index 0000000000000..321b34ae71b9b
--- /dev/null
+++ b/packages/kit/test/apps/basics/src/routes/endpoint-output/options/+server.js
@@ -0,0 +1,4 @@
+/** @type {import('@sveltejs/kit').RequestHandler} */
+export function OPTIONS() {
+ return new Response('ok');
+}
diff --git a/packages/kit/test/apps/basics/src/routes/routing/trailing-slash/+layout.js b/packages/kit/test/apps/basics/src/routes/routing/trailing-slash/+layout.js
new file mode 100644
index 0000000000000..a3d15781a772c
--- /dev/null
+++ b/packages/kit/test/apps/basics/src/routes/routing/trailing-slash/+layout.js
@@ -0,0 +1 @@
+export const ssr = false;
diff --git a/packages/kit/test/apps/basics/src/routes/routing/trailing-slash/+layout.svelte b/packages/kit/test/apps/basics/src/routes/routing/trailing-slash/+layout.svelte
new file mode 100644
index 0000000000000..d9e54145e6ff9
--- /dev/null
+++ b/packages/kit/test/apps/basics/src/routes/routing/trailing-slash/+layout.svelte
@@ -0,0 +1,13 @@
+
+
+
+
+{$page.url.pathname}
+
+
diff --git a/packages/kit/test/apps/basics/src/routes/routing/trailing-slash/+page.svelte b/packages/kit/test/apps/basics/src/routes/routing/trailing-slash/+page.svelte
new file mode 100644
index 0000000000000..e69de29bb2d1d
diff --git a/packages/kit/test/apps/basics/src/routes/routing/trailing-slash/always/+page.js b/packages/kit/test/apps/basics/src/routes/routing/trailing-slash/always/+page.js
new file mode 100644
index 0000000000000..d3c325085ed2d
--- /dev/null
+++ b/packages/kit/test/apps/basics/src/routes/routing/trailing-slash/always/+page.js
@@ -0,0 +1 @@
+export const trailingSlash = 'always';
diff --git a/packages/kit/test/apps/basics/src/routes/routing/trailing-slash/always/+page.svelte b/packages/kit/test/apps/basics/src/routes/routing/trailing-slash/always/+page.svelte
new file mode 100644
index 0000000000000..e69de29bb2d1d
diff --git a/packages/kit/test/apps/basics/src/routes/routing/trailing-slash/ignore/+page.js b/packages/kit/test/apps/basics/src/routes/routing/trailing-slash/ignore/+page.js
new file mode 100644
index 0000000000000..42a828c116a31
--- /dev/null
+++ b/packages/kit/test/apps/basics/src/routes/routing/trailing-slash/ignore/+page.js
@@ -0,0 +1 @@
+export const trailingSlash = 'ignore';
diff --git a/packages/kit/test/apps/basics/src/routes/routing/trailing-slash/ignore/+page.svelte b/packages/kit/test/apps/basics/src/routes/routing/trailing-slash/ignore/+page.svelte
new file mode 100644
index 0000000000000..e69de29bb2d1d
diff --git a/packages/kit/test/apps/basics/src/routes/routing/trailing-slash/never/+page.svelte b/packages/kit/test/apps/basics/src/routes/routing/trailing-slash/never/+page.svelte
new file mode 100644
index 0000000000000..e69de29bb2d1d
diff --git a/packages/kit/test/apps/basics/test/cross-platform/client.test.js b/packages/kit/test/apps/basics/test/cross-platform/client.test.js
index 6ab72a150b641..f9109342f1308 100644
--- a/packages/kit/test/apps/basics/test/cross-platform/client.test.js
+++ b/packages/kit/test/apps/basics/test/cross-platform/client.test.js
@@ -660,6 +660,22 @@ test.describe('Routing', () => {
await page.locator(selector).click();
expect(await page.textContent(selector)).toBe('count: 1');
});
+
+ test('trailing slash redirect', async ({ page, clicknav }) => {
+ await page.goto('/routing/trailing-slash');
+
+ await clicknav('a[href="/routing/trailing-slash/always"]');
+ expect(new URL(page.url()).pathname).toBe('/routing/trailing-slash/always/');
+ await expect(page.locator('p')).toHaveText('/routing/trailing-slash/always/');
+
+ await clicknav('a[href="/routing/trailing-slash/never/"]');
+ expect(new URL(page.url()).pathname).toBe('/routing/trailing-slash/never');
+ await expect(page.locator('p')).toHaveText('/routing/trailing-slash/never');
+
+ await clicknav('a[href="/routing/trailing-slash/ignore/"]');
+ expect(new URL(page.url()).pathname).toBe('/routing/trailing-slash/ignore/');
+ await expect(page.locator('p')).toHaveText('/routing/trailing-slash/ignore/');
+ });
});
test.describe('Shadow DOM', () => {
diff --git a/packages/kit/test/apps/basics/test/server.test.js b/packages/kit/test/apps/basics/test/server.test.js
index cad3f9f88fd67..f466878de0ba3 100644
--- a/packages/kit/test/apps/basics/test/server.test.js
+++ b/packages/kit/test/apps/basics/test/server.test.js
@@ -108,6 +108,13 @@ test.describe('Endpoints', () => {
});
});
+ test('invalid request method returns allow header', async ({ request }) => {
+ const response = await request.post('/endpoint-output/body');
+
+ expect(response.status()).toBe(405);
+ expect(response.headers()['allow'].includes('GET'));
+ });
+
// TODO all the remaining tests in this section are really only testing
// setResponse, since we're not otherwise changing anything on the response.
// might be worth making these unit tests instead
@@ -156,6 +163,17 @@ test.describe('Endpoints', () => {
const response = await request.put('/endpoint-input/sha256', { data });
expect(await response.text()).toEqual(digest);
});
+
+ test('OPTIONS handler', async ({ request }) => {
+ const url = '/endpoint-output/options';
+
+ var response = await request.fetch(url, {
+ method: 'OPTIONS'
+ });
+
+ expect(response.status()).toBe(200);
+ expect(await response.text()).toBe('ok');
+ });
});
test.describe('Errors', () => {
diff --git a/packages/kit/test/github-flaky-warning-reporter.js b/packages/kit/test/github-flaky-warning-reporter.js
new file mode 100644
index 0000000000000..59eb5e5a4261e
--- /dev/null
+++ b/packages/kit/test/github-flaky-warning-reporter.js
@@ -0,0 +1,35 @@
+/**
+ * @class
+ * @implements {import('@playwright/test/reporter').Reporter}
+ */
+export default class GithubFlakyWarningReporter {
+ /**
+ * @type {{ file: string; line: number; title: string; message: string; }[]}
+ */
+ _flaky = [];
+
+ onBegin() {
+ this._flaky = [];
+ }
+ /**
+ * @param test {import('@playwright/test/reporter').TestCase}
+ */
+ onTestEnd(test) {
+ if (test.outcome() === 'flaky') {
+ const { file, line } = test.location;
+ const title = `flaky test: ${test.title}`;
+ const message = `retries: ${test.retries}`;
+ this._flaky.push({ file, line, title, message });
+ }
+ }
+
+ onEnd() {
+ this._flaky.forEach(({ file, line, title, message }) => {
+ console.log(`::warning file=${file},line=${line},title=${title}::${message}`);
+ });
+ }
+
+ printsToStdio() {
+ return true;
+ }
+}
diff --git a/packages/kit/test/utils.js b/packages/kit/test/utils.js
index 0e841275cab7d..64e8d68b33578 100644
--- a/packages/kit/test/utils.js
+++ b/packages/kit/test/utils.js
@@ -1,6 +1,8 @@
import fs from 'fs';
+import path from 'path';
import http from 'http';
import { test as base, devices } from '@playwright/test';
+import { fileURLToPath } from 'url';
export const test = base.extend({
app: async ({ page }, use) => {
@@ -245,5 +247,11 @@ export const config = {
screenshot: 'only-on-failure',
trace: 'retain-on-failure'
},
- workers: process.env.CI ? 2 : undefined
+ workers: process.env.CI ? 2 : undefined,
+ reporter: process.env.CI
+ ? [
+ ['dot'],
+ [path.resolve(fileURLToPath(import.meta.url), '../github-flaky-warning-reporter.js')]
+ ]
+ : 'list'
};
diff --git a/packages/kit/types/ambient.d.ts b/packages/kit/types/ambient.d.ts
index 606475b4c380f..fd6e115583611 100644
--- a/packages/kit/types/ambient.d.ts
+++ b/packages/kit/types/ambient.d.ts
@@ -292,16 +292,22 @@ declare module '$app/stores' {
/**
* A readable store whose value contains page data.
+ *
+ * On the server, this store can only be subscribed to during component initialization. In the browser, it can be subscribed to at any time.
*/
export const page: Readable;
/**
* A readable store.
* When navigating starts, its value is a `Navigation` object with `from`, `to`, `type` and (if `type === 'popstate'`) `delta` properties.
* When navigating finishes, its value reverts to `null`.
+ *
+ * On the server, this store can only be subscribed to during component initialization. In the browser, it can be subscribed to at any time.
*/
export const navigating: Readable;
/**
- * A readable store whose initial value is `false`. If [`version.pollInterval`](https://kit.svelte.dev/docs/configuration#version) is a non-zero value, SvelteKit will poll for new versions of the app and update the store value to `true` when it detects one. `updated.check()` will force an immediate check, regardless of polling.
+ * A readable store whose initial value is `false`. If [`version.pollInterval`](https://kit.svelte.dev/docs/configuration#version) is a non-zero value, SvelteKit will poll for new versions of the app and update the store value to `true` when it detects one. `updated.check()` will force an immediate check, regardless of polling.
+ *
+ * On the server, this store can only be subscribed to during component initialization. In the browser, it can be subscribed to at any time.
*/
export const updated: Readable & { check(): Promise };
@@ -432,10 +438,3 @@ declare module '@sveltejs/kit/vite' {
export function sveltekit(): Promise;
export { vitePreprocess } from '@sveltejs/vite-plugin-svelte';
}
-
-/** Internal version of $app/paths */
-declare module '$internal/paths' {
- export const base: `/${string}`;
- export let assets: `https://${string}` | `http://${string}`;
- export function set_assets(path: string): void;
-}
diff --git a/packages/kit/types/index.d.ts b/packages/kit/types/index.d.ts
index 690b253b7f8e9..e24c30b7f4289 100644
--- a/packages/kit/types/index.d.ts
+++ b/packages/kit/types/index.d.ts
@@ -304,6 +304,8 @@ export interface KitConfig {
* > When `mode` is `'auto'`, SvelteKit will use nonces for dynamically rendered pages and hashes for prerendered pages. Using nonces with prerendered pages is insecure and therefore forbidden.
*
* > Note that most [Svelte transitions](https://svelte.dev/tutorial/transition) work by creating an inline `