Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions documentation/docs/60-appendix/10-faq.md
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,12 @@ export default config;

See [Vite's `configureServer` docs](https://vitejs.dev/guide/api-plugin.html#configureserver) for more details including how to control ordering.

## Why is SvelteKit being loaded twice?

Sometimes, due to incorrect configuration, SvelteKit and other libraries can end up being loaded twice. The most common cause for this is package misconfiguration -- either when a package depends on a specific version of `@sveltejs/kit` rather than listing it as a `peerDependency` or when a package is using an incorrect combination of `main`/`module`/`exports` configuration that's causing your code to load both an ESM and CJS copy of SvelteKit.

SvelteKit will try to figure out when this is happening and warn you by logging to the console during build. If you see one of these logs, try using `pnpm why` or `npm ls` to determine which package is loading SvelteKit, then file an issue with that package.

## How do I use Yarn?

### Does it work with Yarn 2?
Expand Down
3 changes: 1 addition & 2 deletions packages/kit/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,7 @@
"mrmime": "^2.0.0",
"sade": "^1.8.1",
"set-cookie-parser": "^2.6.0",
"sirv": "^3.0.0",
"vitefu": "^1.0.6"
"sirv": "^3.0.0"
},
"devDependencies": {
"@playwright/test": "catalog:",
Expand Down
2 changes: 2 additions & 0 deletions packages/kit/src/exports/hooks/index.js
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
import '../multi-load-test.js';

export { sequence } from './sequence.js';
25 changes: 21 additions & 4 deletions packages/kit/src/exports/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
strip_data_suffix,
strip_resolution_suffix
} from '../runtime/pathname.js';
import './multi-load-test.js';

export { VERSION } from '../version.js';

Expand Down Expand Up @@ -85,8 +86,14 @@ export function error(status, body) {
* @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;
return (
typeof e === 'object' &&
e !== null &&
Object.hasOwn(e, '_tag') &&
Object.hasOwn(e, 'status') &&
/** @type {{ _tag: string; }} */ (e)._tag === 'SvelteKitHttpError' &&
(!status || /** @type {{ status: number }} */ (e).status === status)
);
}

/**
Expand Down Expand Up @@ -124,7 +131,12 @@ export function redirect(status, location) {
* @return {e is Redirect}
*/
export function isRedirect(e) {
return e instanceof Redirect;
return (
typeof e === 'object' &&
e !== null &&
Object.hasOwn(e, '_tag') &&
/** @type {{ _tag: string }} */ (e)._tag === 'SvelteKitRedirect'
);
}

/**
Expand Down Expand Up @@ -213,7 +225,12 @@ export function fail(status, data) {
* @return {e is import('./public.js').ActionFailure}
*/
export function isActionFailure(e) {
return e instanceof ActionFailure;
return (
typeof e === 'object' &&
e !== null &&
Object.hasOwn(e, '_tag') &&
/** @type {{ _tag: string }} */ (e)._tag === 'SvelteKitActionFailure'
);
}

/**
Expand Down
9 changes: 9 additions & 0 deletions packages/kit/src/exports/multi-load-test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
const key = '__sveltekit_module_loaded';
// @ts-ignore
if (globalThis[key]) {
console.warn(
'SvelteKit has been loaded twice. This is likely a misconfiguration, and could cause subtle bugs. For more information, visit https://svelte.dev/docs/kit/faq#Why-is-SvelteKit-being-loaded-twice'
);
}
// @ts-ignore
globalThis[key] = true;
1 change: 1 addition & 0 deletions packages/kit/src/exports/node/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { createReadStream } from 'node:fs';
import { Readable } from 'node:stream';
import * as set_cookie_parser from 'set-cookie-parser';
import { SvelteKitError } from '../../runtime/control.js';
import '../multi-load-test.js';

/**
* @param {import('http').IncomingMessage} req
Expand Down
1 change: 1 addition & 0 deletions packages/kit/src/exports/node/polyfills.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import buffer from 'node:buffer';
import { webcrypto as crypto } from 'node:crypto';
import '../multi-load-test.js';

// `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;
Expand Down
26 changes: 5 additions & 21 deletions packages/kit/src/exports/vite/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ import {
} from './module_ids.js';
import { import_peer } from '../../utils/import.js';
import { compact } from '../../utils/array.js';
import { crawlFrameworkPkgs } from 'vitefu';
import '../multi-load-test.js';

const cwd = process.cwd();

Expand Down Expand Up @@ -229,7 +229,7 @@ async function kit({ svelte_config }) {
* Build the SvelteKit-provided Vite config to be merged with the user's vite.config.js file.
* @see https://vitejs.dev/guide/api-plugin.html#config
*/
async config(config, config_env) {
config(config, config_env) {
initial_config = config;
vite_config_env = config_env;
is_build = config_env.command === 'build';
Expand All @@ -252,20 +252,6 @@ async function kit({ svelte_config }) {

const generated = path.posix.join(kit.outDir, 'generated');

const packages_depending_on_svelte_kit = (
await crawlFrameworkPkgs({
root: cwd,
isBuild: is_build,
viteUserConfig: config,
isSemiFrameworkPkgByJson: (pkg_json) => {
return (
!!pkg_json.dependencies?.['@sveltejs/kit'] ||
!!pkg_json.peerDependencies?.['@sveltejs/kit']
);
}
})
).ssr.noExternal;

// dev and preview config can be shared
/** @type {import('vite').UserConfig} */
const new_config = {
Expand Down Expand Up @@ -322,11 +308,9 @@ async function kit({ svelte_config }) {
// when it is detected to keep our virtual modules working.
// See https://github.com/sveltejs/kit/pull/9172
// and https://vitest.dev/config/#deps-registernodeloader
'@sveltejs/kit',
// We need to bundle any packages depending on @sveltejs/kit so that
// everyone uses the same instances of classes such as `Redirect`
// which we use in `instanceof` checks
...packages_depending_on_svelte_kit
'@sveltejs/kit'
// Don't bundle packages that depend on `@sveltejs/kit` because they
// may include CJS syntax/dependencies
]
}
};
Expand Down
9 changes: 5 additions & 4 deletions packages/kit/src/runtime/client/client.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,12 +35,13 @@ import {
} from './constants.js';
import { validate_page_exports } from '../../utils/exports.js';
import { compact } from '../../utils/array.js';
import { HttpError, Redirect, SvelteKitError } from '../control.js';
import { HttpError, SvelteKitError } from '../control.js';
import { INVALIDATED_PARAM, TRAILING_SLASH_PARAM, validate_depends } from '../shared.js';
import { get_message, get_status } from '../../utils/error.js';
import { writable } from 'svelte/store';
import { page, update, navigating } from './state.svelte.js';
import { add_data_suffix, add_resolution_suffix } from '../pathname.js';
import { isRedirect } from '../../exports/index.js';

export { load_css };

Expand Down Expand Up @@ -1049,7 +1050,7 @@ async function load_route({ id, invalidating, url, params, route, preload }) {
try {
branch.push(await branch_promises[i]);
} catch (err) {
if (err instanceof Redirect) {
if (isRedirect(err)) {
return {
type: 'redirect',
location: err.location
Expand Down Expand Up @@ -1219,7 +1220,7 @@ async function load_root_error_page({ status, error, url, route }) {
route: null
});
} catch (error) {
if (error instanceof Redirect) {
if (isRedirect(error)) {
return _goto(new URL(error.location, location.href), {}, 0);
}

Expand Down Expand Up @@ -2641,7 +2642,7 @@ async function _hydrate(
route: parsed_route ?? null
});
} catch (error) {
if (error instanceof Redirect) {
if (isRedirect(error)) {
// this is a real edge case — `load` would need to return
// a redirect but only in the browser
await native_navigation(new URL(error.location, location.href));
Expand Down
12 changes: 12 additions & 0 deletions packages/kit/src/runtime/control.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
export class HttpError {
/** @private */
// @ts-ignore this property is checked by the `isHttpError` helper
_tag = 'SvelteKitHttpError';

/**
* @param {number} status
* @param {{message: string} extends App.Error ? (App.Error | string | undefined) : App.Error} body
Expand All @@ -20,6 +24,10 @@ export class HttpError {
}

export class Redirect {
/** @private */
// @ts-ignore this property is checked by the `isRedirect` helper
_tag = 'SvelteKitRedirect';

/**
* @param {300 | 301 | 302 | 303 | 304 | 305 | 306 | 307 | 308} status
* @param {string} location
Expand Down Expand Up @@ -52,6 +60,10 @@ export class SvelteKitError extends Error {
* @template {Record<string, unknown> | undefined} [T=undefined]
*/
export class ActionFailure {
/** @private */
// @ts-ignore this property is checked by the `isActionFailure` helper
_tag = 'SvelteKitActionFailure';

/**
* @param {number} status
* @param {T} data
Expand Down
9 changes: 5 additions & 4 deletions packages/kit/src/runtime/server/data/index.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import { HttpError, SvelteKitError, Redirect } from '../../control.js';
/** @import { Redirect } from '../../control.js'; */
import { HttpError, SvelteKitError } from '../../control.js';
import { normalize_error } from '../../../utils/error.js';
import { once } from '../../../utils/functions.js';
import { load_server_data } from '../page/load_data.js';
import { clarify_devalue_error, handle_error_and_jsonify, serialize_uses } from '../utils.js';
import { normalize_path } from '../../../utils/url.js';
import { text } from '../../../exports/index.js';
import { isRedirect, text } from '../../../exports/index.js';
import * as devalue from 'devalue';
import { create_async_iterator } from '../../../utils/streaming.js';

Expand Down Expand Up @@ -99,7 +100,7 @@ export async function render_data(
const nodes = await Promise.all(
promises.map((p, i) =>
p.catch(async (error) => {
if (error instanceof Redirect) {
if (isRedirect(error)) {
throw error;
}

Expand Down Expand Up @@ -150,7 +151,7 @@ export async function render_data(
} catch (e) {
const error = normalize_error(e);

if (error instanceof Redirect) {
if (isRedirect(error)) {
return redirect_json_response(error);
} else {
return json_response(await handle_error_and_jsonify(event, options, error), 500);
Expand Down
4 changes: 2 additions & 2 deletions packages/kit/src/runtime/server/endpoint.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { ENDPOINT_METHODS, PAGE_METHODS } from '../../constants.js';
import { isRedirect } from '../../exports/index.js';
import { negotiate } from '../../utils/http.js';
import { with_event } from '../app/server/event.js';
import { Redirect } from '../control.js';
import { method_not_allowed } from './utils.js';

/**
Expand Down Expand Up @@ -77,7 +77,7 @@ export async function render_endpoint(event, mod, state) {

return response;
} catch (e) {
if (e instanceof Redirect) {
if (isRedirect(e)) {
return new Response(undefined, {
status: e.status,
headers: { location: e.location }
Expand Down
36 changes: 8 additions & 28 deletions packages/kit/src/runtime/server/page/actions.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
/** @import { HttpError } from '../../control.js'; */
import * as devalue from 'devalue';
import { DEV } from 'esm-env';
import { json } from '../../../exports/index.js';
import { isActionFailure, isRedirect, json } from '../../../exports/index.js';
import { get_status, normalize_error } from '../../../utils/error.js';
import { is_form_content_type, negotiate } from '../../../utils/http.js';
import { HttpError, Redirect, ActionFailure, SvelteKitError } from '../../control.js';
import { SvelteKitError } from '../../control.js';
import { handle_error_and_jsonify } from '../utils.js';
import { with_event } from '../../app/server/event.js';

Expand Down Expand Up @@ -53,11 +54,7 @@ export async function handle_action_json_request(event, options, server) {
try {
const data = await call_action(event, actions);

if (__SVELTEKIT_DEV__) {
validate_action_return(data);
}

if (data instanceof ActionFailure) {
if (isActionFailure(data)) {
return action_json({
type: 'failure',
status: data.status,
Expand Down Expand Up @@ -85,7 +82,7 @@ export async function handle_action_json_request(event, options, server) {
} catch (e) {
const err = normalize_error(e);

if (err instanceof Redirect) {
if (isRedirect(err)) {
return action_json_redirect(err);
}

Expand All @@ -105,9 +102,7 @@ export async function handle_action_json_request(event, options, server) {
* @param {HttpError | Error} error
*/
function check_incorrect_fail_use(error) {
return error instanceof ActionFailure
? new Error('Cannot "throw fail()". Use "return fail()"')
: error;
return isActionFailure(error) ? new Error('Cannot "throw fail()". Use "return fail()"') : error;
}

/**
Expand Down Expand Up @@ -166,11 +161,7 @@ export async function handle_action_request(event, server) {
try {
const data = await call_action(event, actions);

if (__SVELTEKIT_DEV__) {
validate_action_return(data);
}

if (data instanceof ActionFailure) {
if (isActionFailure(data)) {
return {
type: 'failure',
status: data.status,
Expand All @@ -187,7 +178,7 @@ export async function handle_action_request(event, server) {
} catch (e) {
const err = normalize_error(e);

if (err instanceof Redirect) {
if (isRedirect(err)) {
return {
type: 'redirect',
status: err.status,
Expand Down Expand Up @@ -250,17 +241,6 @@ async function call_action(event, actions) {
return with_event(event, () => action(event));
}

/** @param {any} data */
function validate_action_return(data) {
if (data instanceof Redirect) {
throw new Error('Cannot `return redirect(...)` — use `redirect(...)` instead');
}

if (data instanceof HttpError) {
throw new Error('Cannot `return error(...)` — use `error(...)` or `return fail(...)` instead');
}
}

/**
* Try to `devalue.uneval` the data object, and if it fails, return a proper Error with context
* @param {any} data
Expand Down
5 changes: 2 additions & 3 deletions packages/kit/src/runtime/server/page/index.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import { text } from '../../../exports/index.js';
import { isRedirect, text } from '../../../exports/index.js';
import { compact } from '../../../utils/array.js';
import { get_status, normalize_error } from '../../../utils/error.js';
import { add_data_suffix } from '../../pathname.js';
import { Redirect } from '../../control.js';
import { redirect_response, static_error_page, handle_error_and_jsonify } from '../utils.js';
import {
handle_action_json_request,
Expand Down Expand Up @@ -219,7 +218,7 @@ export async function render_page(event, page, options, manifest, state, nodes,
} catch (e) {
const err = normalize_error(e);

if (err instanceof Redirect) {
if (isRedirect(err)) {
if (state.prerendering && should_prerender_data) {
const body = JSON.stringify({
type: 'redirect',
Expand Down
Loading
Loading