@sveltejs/kit
Advanced tools
Comparing version 1.25.1 to 2.3.3
{ | ||
"name": "@sveltejs/kit", | ||
"version": "1.25.1", | ||
"version": "2.3.3", | ||
"description": "The fastest way to build Svelte apps", | ||
@@ -14,35 +14,34 @@ "repository": { | ||
"dependencies": { | ||
"@sveltejs/vite-plugin-svelte": "^2.4.1", | ||
"@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", | ||
"import-meta-resolve": "^4.0.0", | ||
"kleur": "^4.1.5", | ||
"magic-string": "^0.30.0", | ||
"mime": "^3.0.0", | ||
"magic-string": "^0.30.5", | ||
"mrmime": "^2.0.0", | ||
"sade": "^1.8.1", | ||
"set-cookie-parser": "^2.6.0", | ||
"sirv": "^2.0.2", | ||
"tiny-glob": "^0.2.9", | ||
"undici": "~5.25.0" | ||
"sirv": "^2.0.4", | ||
"tiny-glob": "^0.2.9" | ||
}, | ||
"devDependencies": { | ||
"@playwright/test": "1.30.0", | ||
"@types/connect": "^3.4.35", | ||
"@types/mime": "^3.0.1", | ||
"@types/node": "^16.18.6", | ||
"@types/sade": "^1.7.4", | ||
"@types/set-cookie-parser": "^2.4.2", | ||
"dts-buddy": "^0.2.4", | ||
"marked": "^9.0.0", | ||
"rollup": "^3.7.0", | ||
"svelte": "^4.0.5", | ||
"svelte-preprocess": "^5.0.4", | ||
"typescript": "^4.9.4", | ||
"vite": "^4.4.2", | ||
"vitest": "^0.34.0" | ||
"@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", | ||
"vite": "^4.0.0" | ||
"@sveltejs/vite-plugin-svelte": "^3.0.0", | ||
"svelte": "^4.0.0 || ^5.0.0-next.0", | ||
"vite": "^5.0.3" | ||
}, | ||
@@ -86,9 +85,9 @@ "bin": { | ||
"engines": { | ||
"node": "^16.14 || >=18" | ||
"node": ">=18.13" | ||
}, | ||
"scripts": { | ||
"lint": "prettier --check . --config ../../.prettierrc --ignore-path .gitignore", | ||
"check": "tsc", | ||
"lint": "prettier --config ../../.prettierrc --check .", | ||
"check": "tsc && cd ./test/types && tsc", | ||
"check:all": "tsc && pnpm -r --filter=\"./**\" check", | ||
"format": "prettier --write . --config ../../.prettierrc --ignore-path .gitignore", | ||
"format": "prettier --config ../../.prettierrc --write .", | ||
"test": "pnpm test:unit && pnpm test:integration", | ||
@@ -95,0 +94,0 @@ "test:integration": "pnpm -r --workspace-concurrency 1 --filter=\"./test/**\" test", |
@@ -5,3 +5,3 @@ # The fastest way to build Svelte apps | ||
The quickest way to get started is via the [create-svelte](https://github.com/sveltejs/kit/tree/master/packages/create-svelte) package: | ||
The quickest way to get started is via the [create-svelte](https://github.com/sveltejs/kit/tree/main/packages/create-svelte) package: | ||
@@ -19,2 +19,2 @@ ```bash | ||
[The Changelog for this package is available on GitHub](https://github.com/sveltejs/kit/blob/master/packages/kit/CHANGELOG.md). | ||
[The Changelog for this package is available on GitHub](https://github.com/sveltejs/kit/blob/main/packages/kit/CHANGELOG.md). |
@@ -9,12 +9,4 @@ /** | ||
export const ENDPOINT_METHODS = new Set([ | ||
'GET', | ||
'POST', | ||
'PUT', | ||
'PATCH', | ||
'DELETE', | ||
'OPTIONS', | ||
'HEAD' | ||
]); | ||
export const ENDPOINT_METHODS = ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS', 'HEAD']; | ||
export const PAGE_METHODS = new Set(['GET', 'POST', 'HEAD']); | ||
export const PAGE_METHODS = ['GET', 'POST', 'HEAD']; |
@@ -27,2 +27,3 @@ import { existsSync, statSync, createReadStream, createWriteStream } from 'node:fs'; | ||
* log: import('types').Logger; | ||
* vite_config: import('vite').ResolvedConfig; | ||
* }} opts | ||
@@ -38,3 +39,4 @@ * @returns {import('@sveltejs/kit').Builder} | ||
prerender_map, | ||
log | ||
log, | ||
vite_config | ||
}) { | ||
@@ -149,3 +151,3 @@ /** @type {Map<import('@sveltejs/kit').RouteDefinition, import('types').RouteData>} */ | ||
const manifest_path = `${config.kit.outDir}/output/server/manifest-full.js`; | ||
const env = get_env(config.kit.env, 'production'); | ||
const env = get_env(config.kit.env, vite_config.mode); | ||
@@ -160,2 +162,9 @@ const fallback = await generate_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 }) { | ||
@@ -188,3 +197,6 @@ return generate_manifest({ | ||
writeClient(dest) { | ||
return copy(`${config.kit.outDir}/output/client`, dest); | ||
return copy(`${config.kit.outDir}/output/client`, dest, { | ||
// avoid making vite build artefacts public | ||
filter: (basename) => basename !== '.vite' | ||
}); | ||
}, | ||
@@ -216,3 +228,3 @@ | ||
} | ||
}) | ||
}) | ||
: zlib.createGzip({ level: zlib.constants.Z_BEST_COMPRESSION }); | ||
@@ -219,0 +231,0 @@ |
@@ -11,4 +11,13 @@ import colors from 'kleur'; | ||
* @param {import('types').Logger} log | ||
* @param {import('vite').ResolvedConfig} vite_config | ||
*/ | ||
export async function adapt(config, build_data, server_metadata, prerendered, prerender_map, log) { | ||
export async function adapt( | ||
config, | ||
build_data, | ||
server_metadata, | ||
prerendered, | ||
prerender_map, | ||
log, | ||
vite_config | ||
) { | ||
const { name, adapt } = config.kit.adapter; | ||
@@ -25,4 +34,6 @@ | ||
prerender_map, | ||
log | ||
log, | ||
vite_config | ||
}); | ||
await adapt(builder); | ||
@@ -29,0 +40,0 @@ |
@@ -72,3 +72,11 @@ import fs from 'node:fs'; | ||
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; | ||
} | ||
} | ||
@@ -89,2 +97,3 @@ | ||
validated.kit.files.hooks.server = path.resolve(cwd, validated.kit.files.hooks.server); | ||
validated.kit.files.hooks.universal = path.resolve(cwd, validated.kit.files.hooks.universal); | ||
} else { | ||
@@ -91,0 +100,0 @@ // @ts-expect-error |
import { join } from 'node:path'; | ||
/** @typedef {import('./types').Validator} Validator */ | ||
/** @typedef {import('./types.js').Validator} Validator */ | ||
@@ -114,7 +114,2 @@ const directives = object({ | ||
dangerZone: object({ | ||
// TODO 2.0: Remove this | ||
trackServerFetches: boolean(false) | ||
}), | ||
embedded: boolean(false), | ||
@@ -132,3 +127,4 @@ | ||
client: string(join('src', 'hooks.client')), | ||
server: string(join('src', 'hooks.server')) | ||
server: string(join('src', 'hooks.server')), | ||
universal: string(join('src', 'hooks')) | ||
}), | ||
@@ -184,9 +180,3 @@ lib: string(join('src', 'lib')), | ||
}), | ||
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) | ||
}), | ||
@@ -193,0 +183,0 @@ |
@@ -13,7 +13,6 @@ import { join } from 'node:path'; | ||
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'; | ||
@@ -40,14 +39,15 @@ export default forked(import.meta.url, analyse); | ||
if (should_polyfill) { | ||
installPolyfills(); | ||
} | ||
installPolyfills(); | ||
// configure `import { building } from '$app/environment'` — | ||
// essential we do this before analysing the code | ||
internal.set_building(true); | ||
internal.set_building(); | ||
// 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); | ||
@@ -60,6 +60,6 @@ /** @type {import('types').ServerMetadata} */ | ||
const nodes = await Promise.all(manifest._.nodes.map((loader) => loader())); | ||
// analyse nodes | ||
for (const loader of manifest._.nodes) { | ||
const node = await loader(); | ||
for (const node of nodes) { | ||
metadata.nodes[node.index] = { | ||
@@ -72,74 +72,31 @@ has_server_load: node.server?.load !== undefined || node.server?.trailingSlash !== undefined | ||
for (const route of manifest._.routes) { | ||
/** @type {Array<'GET' | 'POST'>} */ | ||
const page_methods = []; | ||
const page = | ||
route.page && | ||
analyse_page( | ||
route.page.layouts.map((n) => (n === undefined ? n : nodes[n])), | ||
nodes[route.page.leaf] | ||
); | ||
/** @type {(import('types').HttpMethod | '*')[]} */ | ||
const api_methods = []; | ||
const endpoint = route.endpoint && analyse_endpoint(route, await route.endpoint()); | ||
/** @type {import('types').PrerenderOption | undefined} */ | ||
let prerender = undefined; | ||
/** @type {any} */ | ||
let config = undefined; | ||
/** @type {import('types').PrerenderEntryGenerator | undefined} */ | ||
let entries = undefined; | ||
if (page?.prerender && endpoint?.prerender) { | ||
throw new Error(`Cannot prerender a route with both +page and +server files (${route.id})`); | ||
} | ||
if (route.endpoint) { | ||
const mod = await route.endpoint(); | ||
if (mod.prerender !== undefined) { | ||
validate_server_exports(mod, 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; | ||
} | ||
Object.values(mod).forEach((/** @type {import('types').HttpMethod} */ method) => { | ||
if (mod[method] && ENDPOINT_METHODS.has(method)) { | ||
api_methods.push(method); | ||
} else 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 page_methods = page?.methods ?? []; | ||
const api_methods = endpoint?.methods ?? []; | ||
const entries = page?.entries ?? endpoint?.entries; | ||
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'); | ||
} | ||
metadata.routes.set(route.id, { | ||
config, | ||
config: page?.config ?? endpoint?.config, | ||
methods: Array.from(new Set([...page_methods, ...api_methods])), | ||
@@ -152,5 +109,5 @@ page: { | ||
}, | ||
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)) | ||
}); | ||
@@ -163,2 +120,61 @@ } | ||
/** | ||
* @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<import('types').HttpMethod | '*'>} */ | ||
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<import('types').SSRNode | undefined>} 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 | ||
@@ -168,11 +184,13 @@ * @param {Array<import('types').SSRNode | undefined>} 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 | ||
}; | ||
} | ||
@@ -179,0 +197,0 @@ |
@@ -33,3 +33,3 @@ import { readFileSync } from 'node:fs'; | ||
set_building(true); | ||
set_building(); | ||
@@ -36,0 +36,0 @@ const server = new Server(manifest); |
@@ -6,3 +6,2 @@ import { existsSync, readFileSync, statSync, writeFileSync } from 'node:fs'; | ||
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'; | ||
@@ -41,3 +40,4 @@ import { escape_html_attr } from '../../utils/escape.js'; | ||
// essential we do this before analysing the code | ||
internal.set_building(true); | ||
internal.set_building(); | ||
internal.set_prerendering(); | ||
@@ -99,5 +99,3 @@ /** | ||
if (should_polyfill) { | ||
installPolyfills(); | ||
} | ||
installPolyfills(); | ||
@@ -107,5 +105,2 @@ /** @type {Map<string, string>} */ | ||
const server = new Server(manifest); | ||
await server.init({ env }); | ||
const handle_http_error = normalise_error_handler( | ||
@@ -160,2 +155,3 @@ log, | ||
const files = new Set(walk(`${out}/client`).map(posixify)); | ||
files.add(`${config.appDir}/env.js`); | ||
@@ -423,12 +419,23 @@ const immutable = `${config.appDir}/immutable`; | ||
let has_prerenderable_routes = false; | ||
for (const value of prerender_map.values()) { | ||
if (value) { | ||
has_prerenderable_routes = true; | ||
break; | ||
} | ||
} | ||
if ( | ||
config.prerender.entries.length > 1 || | ||
config.prerender.entries[0] !== '*' || | ||
route_level_entries.length > 0 || | ||
prerender_map.size > 0 | ||
(config.prerender.entries.length === 0 && route_level_entries.length === 0) || | ||
!has_prerenderable_routes | ||
) { | ||
// Only log if we're actually going to do something to not confuse users | ||
log.info('Prerendering'); | ||
return { prerendered, prerender_map }; | ||
} | ||
log.info('Prerendering'); | ||
const server = new Server(manifest); | ||
await server.init({ env }); | ||
for (const entry of config.prerender.entries) { | ||
@@ -438,4 +445,8 @@ if (entry === '*') { | ||
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); | ||
@@ -482,6 +493,6 @@ } | ||
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` | ||
); | ||
@@ -488,0 +499,0 @@ } |
import fs from 'node:fs'; | ||
import path from 'node:path'; | ||
import colors from 'kleur'; | ||
import mime from 'mime'; | ||
import { lookup } from 'mrmime'; | ||
import { list_files, runtime_directory } from '../../utils.js'; | ||
@@ -51,3 +51,3 @@ import { posixify } from '../../../utils/filesystem.js'; | ||
size: fs.statSync(path.resolve(config.kit.files.assets, file)).size, | ||
type: mime.getType(file) | ||
type: lookup(file) || null | ||
})); | ||
@@ -448,3 +448,3 @@ } | ||
* @param {string[]} module_extensions | ||
* @returns {import('./types').RouteFile} | ||
* @returns {import('./types.js').RouteFile} | ||
*/ | ||
@@ -451,0 +451,0 @@ function analyze(project_relative, file, component_extensions, module_extensions) { |
@@ -8,2 +8,3 @@ import path from 'node:path'; | ||
import { write_ambient } from './write_ambient.js'; | ||
import { write_non_ambient } from './write_non_ambient.js'; | ||
import { write_server } from './write_server.js'; | ||
@@ -19,2 +20,3 @@ | ||
write_ambient(config.kit, mode); | ||
write_non_ambient(config.kit); | ||
} | ||
@@ -21,0 +23,0 @@ |
import fs from 'node:fs'; | ||
import path from 'node:path'; | ||
import { VERSION } from 'svelte/compiler'; | ||
import { mkdirp } from '../../utils/filesystem.js'; | ||
@@ -71,1 +72,5 @@ | ||
} | ||
export function isSvelte5Plus() { | ||
return Number(VERSION[0]) >= 5; | ||
} |
import path from 'node:path'; | ||
import { relative_path, resolve_entry } from '../../utils/filesystem.js'; | ||
import { s } from '../../utils/misc.js'; | ||
import { dedent, write_if_changed } from './utils.js'; | ||
import { dedent, isSvelte5Plus, write_if_changed } from './utils.js'; | ||
import colors from 'kleur'; | ||
@@ -111,3 +111,4 @@ | ||
const hooks_file = resolve_entry(kit.files.hooks.client); | ||
const client_hooks_file = resolve_entry(kit.files.hooks.client); | ||
const universal_hooks_file = resolve_entry(kit.files.hooks.universal); | ||
@@ -129,3 +130,12 @@ const typo = resolve_entry('src/+hooks.client'); | ||
dedent` | ||
${hooks_file ? `import * as client_hooks from '${relative_path(output, hooks_file)}';` : ''} | ||
${ | ||
client_hooks_file | ||
? `import * as client_hooks from '${relative_path(output, client_hooks_file)}';` | ||
: '' | ||
} | ||
${ | ||
universal_hooks_file | ||
? `import * as universal_hooks from '${relative_path(output, universal_hooks_file)}';` | ||
: '' | ||
} | ||
@@ -144,7 +154,9 @@ export { matchers } from './matchers.js'; | ||
handleError: ${ | ||
hooks_file ? 'client_hooks.handleError || ' : '' | ||
client_hooks_file ? 'client_hooks.handleError || ' : '' | ||
}(({ error }) => { console.error(error) }), | ||
reroute: ${universal_hooks_file ? 'universal_hooks.reroute || ' : ''}(() => {}) | ||
}; | ||
export { default as root } from '../root.svelte'; | ||
export { default as root } from '../root.${isSvelte5Plus() ? 'js' : 'svelte'}'; | ||
` | ||
@@ -151,0 +163,0 @@ ); |
@@ -1,2 +0,2 @@ | ||
import { dedent, write_if_changed } from './utils.js'; | ||
import { dedent, isSvelte5Plus, write_if_changed } from './utils.js'; | ||
@@ -42,14 +42,25 @@ /** | ||
<!-- This file is generated by @sveltejs/kit — do not edit it! --> | ||
${isSvelte5Plus() ? '<svelte:options runes={true} />' : ''} | ||
<script> | ||
import { setContext, afterUpdate, onMount, tick } from 'svelte'; | ||
import { setContext, ${isSvelte5Plus() ? '' : 'afterUpdate, '}onMount, tick } from 'svelte'; | ||
import { browser } from '$app/environment'; | ||
// stores | ||
export let stores; | ||
export let page; | ||
${ | ||
isSvelte5Plus() | ||
? dedent` | ||
let { stores, page, constructors, components = [], form, ${levels | ||
.map((l) => `data_${l} = null`) | ||
.join(', ')} } = $props(); | ||
` | ||
: dedent` | ||
export let stores; | ||
export let page; | ||
export let constructors; | ||
export let components = []; | ||
export let form; | ||
${levels.map((l) => `export let data_${l} = null;`).join('\n')} | ||
export let constructors; | ||
export let components = []; | ||
export let form; | ||
${levels.map((l) => `export let data_${l} = null;`).join('\n')} | ||
` | ||
} | ||
@@ -60,8 +71,27 @@ if (!browser) { | ||
$: stores.page.set(page); | ||
afterUpdate(stores.page.notify); | ||
${ | ||
isSvelte5Plus() | ||
? dedent` | ||
if (browser) { | ||
$effect.pre(() => stores.page.set(page)); | ||
} else { | ||
stores.page.set(page); | ||
} | ||
` | ||
: '$: stores.page.set(page);' | ||
} | ||
${ | ||
isSvelte5Plus() | ||
? dedent` | ||
$effect(() => { | ||
stores;page;constructors;components;form;${levels.map((l) => `data_${l}`).join(';')}; | ||
stores.page.notify(); | ||
}); | ||
` | ||
: 'afterUpdate(stores.page.notify);' | ||
} | ||
let mounted = false; | ||
let navigated = false; | ||
let title = null; | ||
let mounted = ${isSvelte5Plus() ? '$state(false)' : 'false'}; | ||
let navigated = ${isSvelte5Plus() ? '$state(false)' : 'false'}; | ||
let title = ${isSvelte5Plus() ? '$state(null)' : 'null'}; | ||
@@ -94,2 +124,13 @@ onMount(() => { | ||
); | ||
if (isSvelte5Plus()) { | ||
write_if_changed( | ||
`${output}/root.js`, | ||
dedent` | ||
import { asClassComponent } from 'svelte/legacy'; | ||
import Root from './root.svelte'; | ||
export default asClassComponent(Root); | ||
` | ||
); | ||
} | ||
} |
@@ -7,3 +7,3 @@ import path from 'node:path'; | ||
import { runtime_directory } from '../utils.js'; | ||
import { write_if_changed } from './utils.js'; | ||
import { isSvelte5Plus, write_if_changed } from './utils.js'; | ||
import colors from 'kleur'; | ||
@@ -13,3 +13,4 @@ | ||
* @param {{ | ||
* hooks: string | null; | ||
* server_hooks: string | null; | ||
* universal_hooks: string | null; | ||
* config: import('types').ValidatedConfig; | ||
@@ -24,3 +25,4 @@ * has_service_worker: boolean; | ||
config, | ||
hooks, | ||
server_hooks, | ||
universal_hooks, | ||
has_service_worker, | ||
@@ -31,12 +33,12 @@ runtime_directory, | ||
}) => ` | ||
import root from '../root.svelte'; | ||
import { set_building } from '__sveltekit/environment'; | ||
import root from '../root.${isSvelte5Plus() ? 'js' : 'svelte'}'; | ||
import { set_building, set_prerendering } 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}, | ||
@@ -66,7 +68,10 @@ env_public_prefix: '${config.kit.env.publicPrefix}', | ||
export function get_hooks() { | ||
return ${hooks ? `import(${s(hooks)})` : '{}'}; | ||
export async function get_hooks() { | ||
return { | ||
${server_hooks ? `...(await import(${s(server_hooks)})),` : ''} | ||
${universal_hooks ? `...(await import(${s(universal_hooks)})),` : ''} | ||
}; | ||
} | ||
export { set_assets, set_building, set_private_env, set_public_env }; | ||
export { set_assets, set_building, set_prerendering, set_private_env, set_public_env, set_safe_public_env }; | ||
`; | ||
@@ -84,3 +89,4 @@ | ||
export function write_server(config, output) { | ||
const hooks_file = resolve_entry(config.kit.files.hooks.server); | ||
const server_hooks_file = resolve_entry(config.kit.files.hooks.server); | ||
const universal_hooks_file = resolve_entry(config.kit.files.hooks.universal); | ||
@@ -108,3 +114,4 @@ const typo = resolve_entry('src/+hooks.server'); | ||
config, | ||
hooks: hooks_file ? relative(hooks_file) : null, | ||
server_hooks: server_hooks_file ? relative(server_hooks_file) : null, | ||
universal_hooks: universal_hooks_file ? relative(universal_hooks_file) : null, | ||
has_service_worker: | ||
@@ -111,0 +118,0 @@ config.kit.serviceWorker.register && !!resolve_entry(config.kit.files.serviceWorker), |
@@ -6,3 +6,2 @@ import fs from 'node:fs'; | ||
import { write_if_changed } from './utils.js'; | ||
import { ts } from './ts.js'; | ||
@@ -46,34 +45,5 @@ /** | ||
const user_config = load_user_tsconfig(cwd); | ||
if (user_config) validate_user_config(kit, cwd, out, user_config); | ||
if (user_config) validate_user_config(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')); | ||
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')); | ||
} | ||
@@ -84,5 +54,4 @@ | ||
* @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 */ | ||
@@ -93,3 +62,5 @@ const config_relative = (file) => posixify(path.relative(kit.outDir, file)); | ||
'ambient.d.ts', | ||
'non-ambient.d.ts', | ||
'./types/**/$types.d.ts', | ||
config_relative('vite.config.js'), | ||
config_relative('vite.config.ts') | ||
@@ -116,10 +87,3 @@ ]); | ||
const exclude = [config_relative('node_modules/**'), './[!ambient.d.ts]**']; | ||
if (path.extname(kit.files.serviceWorker)) { | ||
exclude.push(config_relative(kit.files.serviceWorker)); | ||
} else { | ||
exclude.push(config_relative(`${kit.files.serviceWorker}.js`)); | ||
exclude.push(config_relative(`${kit.files.serviceWorker}.ts`)); | ||
exclude.push(config_relative(`${kit.files.serviceWorker}.d.ts`)); | ||
} | ||
const exclude = [config_relative('node_modules/**')]; | ||
@@ -129,4 +93,3 @@ const config = { | ||
// 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'], | ||
@@ -137,8 +100,7 @@ | ||
// 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, | ||
@@ -148,8 +110,6 @@ // This is required for svelte-package to work as expected | ||
lib: ['esnext', 'DOM', 'DOM.Iterable'], | ||
moduleResolution: 'node', | ||
moduleResolution: 'bundler', | ||
module: 'esnext', | ||
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 | ||
noEmit: true, // prevent tsconfig error "overwriting input files" - Vite handles the build and ignores this | ||
target: 'esnext' | ||
}, | ||
@@ -179,3 +139,2 @@ include: [...include], | ||
/** | ||
* @param {import('types').ValidatedKitConfig} kit | ||
* @param {string} cwd | ||
@@ -185,3 +144,3 @@ * @param {string} out | ||
*/ | ||
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 | ||
@@ -193,4 +152,4 @@ const extend = config.options.extends; | ||
: Array.isArray(extend) | ||
? extend.some((e) => path.resolve(cwd, e) === out) | ||
: false; | ||
? extend.some((e) => path.resolve(cwd, e) === out) | ||
: false; | ||
@@ -200,25 +159,13 @@ const options = config.options.compilerOptions || {}; | ||
if (extends_framework_config) { | ||
const { paths: user_paths } = options; | ||
const { paths, baseUrl } = 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}`); | ||
} | ||
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' | ||
) | ||
); | ||
} | ||
@@ -248,5 +195,4 @@ } else { | ||
* @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 */ | ||
@@ -270,5 +216,3 @@ const config_relative = (file) => posixify(path.relative(config.outDir, file)); | ||
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]; | ||
@@ -275,0 +219,0 @@ |
@@ -191,8 +191,16 @@ import fs from 'node:fs'; | ||
declarations.push('type Expand<T> = T extends infer O ? { [K in keyof O]: O[K] } : never;'); | ||
// returns the predicate of a matcher's type guard - or string if there is no type guard | ||
declarations.push( | ||
`type RouteParams = { ${route.params | ||
.map((param) => `${param.name}${param.optional ? '?' : ''}: string`) | ||
.join('; ')} }` | ||
// TS complains on infer U, which seems weird, therefore ts-ignore it | ||
[ | ||
'// @ts-ignore', | ||
'type MatcherParam<M> = M extends (param : string) => param is infer U ? U extends string ? U : string : string;' | ||
].join('\n') | ||
); | ||
declarations.push( | ||
'type RouteParams = ' + generate_params_type(route.params, outdir, config) + ';' | ||
); | ||
if (route.params.length > 0) { | ||
@@ -269,3 +277,4 @@ exports.push( | ||
let all_pages_have_load = true; | ||
const layout_params = new Set(); | ||
/** @type {import('types').RouteParam[]} */ | ||
const layout_params = []; | ||
const ids = ['RouteId']; | ||
@@ -279,3 +288,5 @@ | ||
for (const param of leaf.route.params) { | ||
layout_params.add(param.name); | ||
// skip if already added | ||
if (layout_params.some((p) => p.name === param.name)) continue; | ||
layout_params.push({ ...param, optional: true }); | ||
} | ||
@@ -307,5 +318,3 @@ | ||
declarations.push( | ||
`type LayoutParams = RouteParams & { ${Array.from(layout_params).map( | ||
(param) => `${param}?: string` | ||
)} }` | ||
'type LayoutParams = RouteParams & ' + generate_params_type(layout_params, outdir, config) | ||
); | ||
@@ -478,3 +487,3 @@ | ||
: path_to_original(outdir, file_path); | ||
const type = `Kit.AwaitedProperties<Awaited<ReturnType<typeof import('${from}').load>>>`; | ||
const type = `Kit.LoadProperties<Awaited<ReturnType<typeof import('${from}').load>>>`; | ||
return expand ? `Expand<OptionalUnion<EnsureDefined<${type}>>>` : type; | ||
@@ -576,2 +585,24 @@ } else { | ||
/** | ||
* @param {import('types').RouteParam[]} params | ||
* @param {string} outdir | ||
* @param {import('types').ValidatedConfig} config | ||
*/ | ||
function generate_params_type(params, outdir, config) { | ||
/** @param {string} matcher */ | ||
const path_to_matcher = (matcher) => | ||
posixify(path.relative(outdir, path.join(config.kit.files.params, matcher))); | ||
return `{ ${params | ||
.map( | ||
(param) => | ||
`${param.name}${param.optional ? '?' : ''}: ${ | ||
param.matcher | ||
? `MatcherParam<typeof import('${path_to_matcher(param.matcher)}').match>` | ||
: 'string' | ||
}` | ||
) | ||
.join('; ')} }`; | ||
} | ||
/** | ||
* @param {string} content | ||
@@ -578,0 +609,0 @@ * @param {boolean} is_server |
@@ -114,3 +114,3 @@ /** | ||
preload | ||
}) | ||
}) | ||
: resolve(event, { transformPageChunk, filterSerializedResponseHeaders, preload }); | ||
@@ -117,0 +117,0 @@ } |
import { HttpError, Redirect, ActionFailure } from '../runtime/control.js'; | ||
import { BROWSER, DEV } from 'esm-env'; | ||
import { get_route_segments } from '../utils/routing.js'; | ||
@@ -8,22 +7,54 @@ export { VERSION } from '../version.js'; | ||
/** | ||
* @template {number} TNumber | ||
* @template {any[]} [TArray=[]] | ||
* @typedef {TNumber extends TArray['length'] ? TArray[number] : LessThan<TNumber, [...TArray, TArray['length']]>} LessThan | ||
*/ | ||
/** | ||
* @template {number} TStart | ||
* @template {number} TEnd | ||
* @typedef {Exclude<TEnd | LessThan<TEnd>, LessThan<TStart>>} 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). | ||
*/ | ||
@@ -35,10 +66,25 @@ export function error(status, body) { | ||
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} | ||
*/ | ||
@@ -50,6 +96,15 @@ export function redirect(status, location) { | ||
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; | ||
} | ||
/** | ||
* Create a JSON `Response` object from the supplied data. | ||
@@ -108,55 +163,26 @@ * @param {any} data The value that will be serialized as JSON. | ||
* 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<undefined>} | ||
*/ | ||
/** | ||
* Create an `ActionFailure` object. | ||
* @template {Record<string, unknown> | 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<T>} | ||
* @param {T} data Data associated with the failure (e.g. validation errors) | ||
* @overload | ||
* @param {number} status | ||
* @param {T} data | ||
* @returns {import('./public.js').ActionFailure<T>} | ||
*/ | ||
/** | ||
* Create an `ActionFailure` object. | ||
* @param {number} status | ||
* @param {any} [data] | ||
* @returns {import('./public.js').ActionFailure<any>} | ||
*/ | ||
export function fail(status, data) { | ||
// @ts-expect-error unique symbol missing | ||
return new ActionFailure(status, data); | ||
} | ||
const basic_param_pattern = /\[(\[)?(\.\.\.)?(\w+?)(?:=(\w+))?\]\]?/g; | ||
/** | ||
* 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<string, string | undefined>} params | ||
* @returns {string} | ||
*/ | ||
export function resolvePath(id, params) { | ||
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('/') | ||
); | ||
} |
import * as set_cookie_parser from 'set-cookie-parser'; | ||
import { error } from '../index.js'; | ||
import { SvelteKitError } from '../../runtime/control.js'; | ||
@@ -25,15 +25,2 @@ /** | ||
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) { | ||
@@ -50,2 +37,17 @@ const readable = new ReadableStream(); | ||
start(controller) { | ||
if (body_size_limit !== undefined && content_length > body_size_limit) { | ||
let message = `Content-length of ${content_length} exceeds limit of ${body_size_limit} bytes.`; | ||
if (body_size_limit === 0) { | ||
// https://github.com/sveltejs/kit/pull/11589 | ||
// TODO this exists to aid migration — remove in a future version | ||
message += ' To disable body size limits, specify Infinity rather than 0.'; | ||
} | ||
const error = new SvelteKitError(413, 'Payload Too Large', message); | ||
controller.error(error); | ||
return; | ||
} | ||
req.on('error', (error) => { | ||
@@ -65,12 +67,11 @@ cancelled = true; | ||
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; | ||
@@ -130,3 +131,3 @@ } | ||
/** @type {string}*/ (response.headers.get(key)) | ||
) | ||
) | ||
: value | ||
@@ -133,0 +134,0 @@ ); |
@@ -1,8 +0,6 @@ | ||
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'; | ||
// @ts-expect-error | ||
const File = buffer.File ?? UndiciFile; | ||
// `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; | ||
@@ -12,10 +10,2 @@ /** @type {Record<string, any>} */ | ||
crypto, | ||
fetch, | ||
Response, | ||
Request, | ||
Headers, | ||
ReadableStream, | ||
TransformStream, | ||
WritableStream, | ||
FormData, | ||
File | ||
@@ -25,13 +15,11 @@ }; | ||
// exported for dev/preview and node environments | ||
// TODO: remove this once we only support Node 18.11+ (the version multipart/form-data was added) | ||
/** | ||
* Make various web APIs available as globals: | ||
* - `crypto` | ||
* - `fetch` | ||
* - `Headers` | ||
* - `Request` | ||
* - `Response` | ||
* - `File` | ||
*/ | ||
export function installPolyfills() { | ||
for (const name in globals) { | ||
if (name in globalThis) continue; | ||
Object.defineProperty(globalThis, name, { | ||
@@ -38,0 +26,0 @@ enumerable: true, |
@@ -5,3 +5,3 @@ import 'svelte'; // pick up `declare module "*.svelte"` | ||
import { CompileOptions } from 'svelte/types/compiler/interfaces'; | ||
import { CompileOptions } from 'svelte/compiler'; | ||
import { | ||
@@ -21,3 +21,2 @@ AdapterEntry, | ||
} from '../types/private.js'; | ||
import { ActionFailure } from '../runtime/control.js'; | ||
import { BuildData, SSRNodeLoader, SSRRoute, ValidatedConfig } from 'types'; | ||
@@ -27,3 +26,2 @@ import type { PluginOptions } from '@sveltejs/vite-plugin-svelte'; | ||
export { PrerenderOption } from '../types/private.js'; | ||
export { ActionFailure }; | ||
@@ -45,17 +43,8 @@ /** | ||
type AwaitedPropertiesUnion<input extends Record<string, any> | void> = input extends void | ||
export type LoadProperties<input extends Record<string, any> | void> = input extends void | ||
? undefined // needs to be undefined, because void will break intellisense | ||
: input extends Record<string, any> | ||
? { | ||
[key in keyof input]: Awaited<input[key]>; | ||
} | ||
: {} extends input // handles the any case | ||
? input | ||
: unknown; | ||
? input | ||
: unknown; | ||
export type AwaitedProperties<input extends Record<string, any> | void> = | ||
AwaitedPropertiesUnion<input> extends Record<string, any> | ||
? OptionalUnion<AwaitedPropertiesUnion<input>> | ||
: AwaitedPropertiesUnion<input>; | ||
export type AwaitedActions<T extends Record<string, (...args: any) => any>> = OptionalUnion< | ||
@@ -74,7 +63,15 @@ { | ||
declare const uniqueSymbol: unique symbol; | ||
export interface ActionFailure<T extends Record<string, unknown> | 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> = T extends ActionFailure<infer X> | ||
? X | ||
: T extends void | ||
? undefined // needs to be undefined, because void will corrupt union type | ||
: T; | ||
? undefined // needs to be undefined, because void will corrupt union type | ||
: T; | ||
@@ -113,2 +110,7 @@ /** | ||
/** | ||
* 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. | ||
@@ -186,10 +188,2 @@ * @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 | ||
kit?: KitConfig; | ||
/** [`@sveltejs/package`](/docs/packaging) options. */ | ||
package?: { | ||
source?: string; | ||
dir?: string; | ||
emitTypes?: boolean; | ||
exports?(filepath: string): boolean; | ||
files?(filepath: string): boolean; | ||
}; | ||
/** Preprocessor options, if any. Preprocessing can alternatively also be done through Vite's preprocessor capabilities. */ | ||
@@ -213,3 +207,3 @@ preprocess?: any; | ||
* Gets all cookies that were previously set with `cookies.set`, or from the request headers. | ||
* @param opts the options, passed directily to `cookie.parse`. See documentation [here](https://github.com/jshttp/cookie#cookieparsestr-options) | ||
* @param opts the options, passed directly to `cookie.parse`. See documentation [here](https://github.com/jshttp/cookie#cookieparsestr-options) | ||
*/ | ||
@@ -223,8 +217,12 @@ getAll(opts?: import('cookie').CookieParseOptions): Array<{ name: string; value: string }>; | ||
* | ||
* 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 directory to `cookie.serialize`. See documentation [here](https://github.com/jshttp/cookie#cookieserializename-value-options) | ||
* @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; | ||
@@ -234,7 +232,7 @@ /** | ||
* | ||
* 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 directory 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) | ||
* @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; | ||
@@ -246,9 +244,13 @@ /** | ||
* | ||
* 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 directory to `cookie.serialize`. See documentation [here](https://github.com/jshttp/cookie#cookieserializename-value-options) | ||
* @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; | ||
} | ||
@@ -293,3 +295,5 @@ | ||
/** | ||
* 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" | ||
@@ -359,13 +363,4 @@ */ | ||
/** | ||
* 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`. | ||
* Note that it is generally not supported to embed multiple SvelteKit apps on the same page and use client-side SvelteKit features within them (things such as pushing to the history state assume a single instance). | ||
* @default false | ||
@@ -391,2 +386,3 @@ */ | ||
* @default "" | ||
* @since 1.21.0 | ||
*/ | ||
@@ -415,2 +411,8 @@ privatePrefix?: string; | ||
server?: string; | ||
/** | ||
* The location of your universal [hooks](https://kit.svelte.dev/docs/hooks). | ||
* @default "src/hooks" | ||
* @since 2.3.0 | ||
*/ | ||
universal?: string; | ||
}; | ||
@@ -476,2 +478,3 @@ /** | ||
* @default "modulepreload" | ||
* @since 1.8.4 | ||
*/ | ||
@@ -492,11 +495,17 @@ preloadStrategy?: 'modulepreload' | 'preload-js' | 'preload-mjs'; | ||
/** | ||
* 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 `<base>` element, you should set this to `false`, otherwise asset URLs will incorrectly be resolved against the `<base>` 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 | ||
* @since 1.9.0 | ||
*/ | ||
relative?: boolean | undefined; | ||
relative?: boolean; | ||
}; | ||
@@ -518,3 +527,3 @@ /** | ||
/** | ||
* 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 ["*"] | ||
@@ -552,2 +561,3 @@ */ | ||
* @default "fail" | ||
* @since 1.15.7 | ||
*/ | ||
@@ -564,2 +574,3 @@ handleHttpError?: PrerenderHttpErrorHandlerValue; | ||
* @default "fail" | ||
* @since 1.15.7 | ||
*/ | ||
@@ -576,2 +587,3 @@ handleMissingId?: PrerenderMissingIdHandlerValue; | ||
* @default "fail" | ||
* @since 1.16.0 | ||
*/ | ||
@@ -602,2 +614,3 @@ handleEntryGeneratorMismatch?: PrerenderEntryGeneratorMismatchHandlerValue; | ||
* @default (config) => config | ||
* @since 1.3.0 | ||
*/ | ||
@@ -675,2 +688,4 @@ config?: (config: Record<string, any>) => Record<string, any> | void; | ||
event: RequestEvent; | ||
status: number; | ||
message: string; | ||
}) => MaybePromise<void | App.Error>; | ||
@@ -687,2 +702,4 @@ | ||
event: NavigationEvent; | ||
status: number; | ||
message: string; | ||
}) => MaybePromise<void | App.Error>; | ||
@@ -700,2 +717,8 @@ | ||
/** | ||
* The [`reroute`](https://kit.svelte.dev/docs/hooks#universal-hooks-reroute) hook allows you to modify the URL before it is used to determine which route to render. | ||
* @since 2.3.0 | ||
*/ | ||
export type Reroute = (event: { url: URL }) => void | string; | ||
/** | ||
* The generic form of `PageLoad` and `LayoutLoad`. You should import those from `./$types` (see [generated types](https://kit.svelte.dev/docs/types#generated-types)) | ||
@@ -807,3 +830,17 @@ * rather than using `Load` directly. | ||
*/ | ||
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<T>(fn: () => T): T; | ||
} | ||
@@ -875,3 +912,3 @@ | ||
* - `form`: The user submitted a `<form>` | ||
* - `leave`: The user is leaving the app by closing the tab or using the back/forward buttons to go to a different document | ||
* - `leave`: The app is being left either because the tab is being closed or a navigation to a different document is occurring | ||
* - `link`: Navigation was triggered by a link click | ||
@@ -981,2 +1018,6 @@ * - `goto`: Navigation was triggered by a `goto(...)` call or a redirect | ||
/** | ||
* The page state, which can be manipulated using the [`pushState`](https://kit.svelte.dev/docs/modules#$app-navigation-pushstate) and [`replaceState`](https://kit.svelte.dev/docs/modules#$app-navigation-replacestate) functions from `$app/navigation`. | ||
*/ | ||
state: App.PageState; | ||
/** | ||
* Filled only after a form submission. See [form actions](https://kit.svelte.dev/docs/form-actions) for more info. | ||
@@ -1212,2 +1253,16 @@ */ | ||
depends(...deps: 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.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<T>(fn: () => T): T; | ||
} | ||
@@ -1279,13 +1334,3 @@ | ||
action: URL; | ||
/** | ||
* use `formData` instead of `data` | ||
* @deprecated | ||
*/ | ||
data: FormData; | ||
formData: FormData; | ||
/** | ||
* use `formElement` instead of `form` | ||
* @deprecated | ||
*/ | ||
form: HTMLFormElement; | ||
formElement: HTMLFormElement; | ||
@@ -1298,13 +1343,3 @@ controller: AbortController; | ||
| ((opts: { | ||
/** | ||
* use `formData` instead of `data` | ||
* @deprecated | ||
*/ | ||
data: FormData; | ||
formData: FormData; | ||
/** | ||
* use `formElement` instead of `form` | ||
* @deprecated | ||
*/ | ||
form: HTMLFormElement; | ||
formElement: HTMLFormElement; | ||
@@ -1316,4 +1351,5 @@ action: URL; | ||
* @param options Set `reset: false` if you don't want the `<form>` values to be reset after a successful submission. | ||
* @param invalidateAll Set `invalidateAll: false` if you don't want the action to call `invalidateAll` after submission. | ||
*/ | ||
update(options?: { reset: boolean }): Promise<void>; | ||
update(options?: { reset?: boolean; invalidateAll?: boolean }): Promise<void>; | ||
}) => void) | ||
@@ -1320,0 +1356,0 @@ >; |
@@ -13,3 +13,3 @@ import fs from 'node:fs'; | ||
* @param {import('vite').Manifest | null} client_manifest | ||
* @param {import('rollup').OutputAsset[] | null} css | ||
* @param {import('vite').Rollup.OutputAsset[] | null} css | ||
*/ | ||
@@ -16,0 +16,0 @@ export function build_server_nodes(out, kit, manifest_data, server_manifest, client_manifest, css) { |
@@ -5,4 +5,5 @@ import fs from 'node:fs'; | ||
import { s } from '../../../utils/misc.js'; | ||
import { get_config_aliases } from '../utils.js'; | ||
import { assets_base } from './utils.js'; | ||
import { get_config_aliases, strip_virtual_prefix, get_env } from '../utils.js'; | ||
import { create_static_module } from '../../../core/env.js'; | ||
import { env_static_public, service_worker } from '../module_ids.js'; | ||
@@ -35,4 +36,2 @@ /** | ||
const service_worker = `${kit.outDir}/generated/service-worker.js`; | ||
// in a service worker, `location` is the location of the service worker itself, | ||
@@ -42,39 +41,68 @@ // which is guaranteed to be `<base>/service-worker.js` | ||
fs.writeFileSync( | ||
service_worker, | ||
dedent` | ||
export const base = /*@__PURE__*/ ${base}; | ||
const service_worker_code = dedent` | ||
export const base = /*@__PURE__*/ ${base}; | ||
export const build = [ | ||
${Array.from(build) | ||
.map((file) => `base + ${s(`/${file}`)}`) | ||
.join(',\n')} | ||
]; | ||
export const build = [ | ||
${Array.from(build) | ||
.map((file) => `base + ${s(`/${file}`)}`) | ||
.join(',\n')} | ||
]; | ||
export const files = [ | ||
${manifest_data.assets | ||
.filter((asset) => kit.serviceWorker.files(asset.file)) | ||
.map((asset) => `base + ${s(`/${asset.file}`)}`) | ||
.join(',\n')} | ||
]; | ||
export const files = [ | ||
${manifest_data.assets | ||
.filter((asset) => kit.serviceWorker.files(asset.file)) | ||
.map((asset) => `base + ${s(`/${asset.file}`)}`) | ||
.join(',\n')} | ||
]; | ||
export const prerendered = [ | ||
${prerendered.paths.map((path) => `base + ${s(path.replace(kit.paths.base, ''))}`).join(',\n')} | ||
]; | ||
export const prerendered = [ | ||
${prerendered.paths.map((path) => `base + ${s(path.replace(kit.paths.base, ''))}`).join(',\n')} | ||
]; | ||
export const version = ${s(kit.version.name)}; | ||
` | ||
); | ||
export const version = ${s(kit.version.name)}; | ||
`; | ||
const env = get_env(kit.env, vite_config.mode); | ||
/** | ||
* @type {import('vite').Plugin} | ||
*/ | ||
const sw_virtual_modules = { | ||
name: 'service-worker-build-virtual-modules', | ||
async resolveId(id) { | ||
if (id.startsWith('$env/') || id.startsWith('$app/') || id === '$service-worker') { | ||
return `\0virtual:${id}`; | ||
} | ||
}, | ||
async load(id) { | ||
if (!id.startsWith('\0virtual:')) return; | ||
if (id === service_worker) { | ||
return service_worker_code; | ||
} | ||
if (id === env_static_public) { | ||
return create_static_module('$env/static/public', env.public); | ||
} | ||
const stripped = strip_virtual_prefix(id); | ||
throw new Error( | ||
`Cannot import ${stripped} into service-worker code. Only the modules $service-worker and $env/static/public are available in service workers.` | ||
); | ||
} | ||
}; | ||
await vite.build({ | ||
base: assets_base(kit), | ||
build: { | ||
lib: { | ||
entry: /** @type {string} */ (service_worker_entry_file), | ||
name: 'app', | ||
formats: ['es'] | ||
}, | ||
modulePreload: false, | ||
rollupOptions: { | ||
input: { | ||
'service-worker': service_worker_entry_file | ||
}, | ||
output: { | ||
entryFileNames: 'service-worker.js' | ||
// .mjs so that esbuild doesn't incorrectly inject `export` https://github.com/vitejs/vite/issues/15379 | ||
entryFileNames: 'service-worker.mjs', | ||
assetFileNames: `${kit.appDir}/immutable/assets/[name].[hash][extname]`, | ||
inlineDynamicImports: true | ||
} | ||
@@ -88,6 +116,17 @@ }, | ||
publicDir: false, | ||
plugins: [sw_virtual_modules], | ||
resolve: { | ||
alias: [...get_config_aliases(kit), { find: '$service-worker', replacement: service_worker }] | ||
alias: [...get_config_aliases(kit)] | ||
}, | ||
experimental: { | ||
renderBuiltUrl(filename) { | ||
return { | ||
runtime: `new URL(${JSON.stringify(filename)}, location.href).pathname` | ||
}; | ||
} | ||
} | ||
}); | ||
// rename .mjs to .js to avoid incorrect MIME types with ancient webservers | ||
fs.renameSync(`${out}/client/service-worker.mjs`, `${out}/client/service-worker.js`); | ||
} |
@@ -86,3 +86,14 @@ import fs from 'node:fs'; | ||
const method_names = new Set(['GET', 'HEAD', 'PUT', 'POST', 'DELETE', 'PATCH', 'OPTIONS']); | ||
// If we'd written this in TypeScript, it could be easy... | ||
/** | ||
* @param {string} str | ||
* @returns {str is import('types').HttpMethod} | ||
*/ | ||
export function is_http_method(str) { | ||
return method_names.has(str); | ||
} | ||
/** | ||
* @param {import('types').ValidatedKitConfig} config | ||
@@ -89,0 +100,0 @@ * @returns {string} |
@@ -11,3 +11,2 @@ import fs from 'node:fs'; | ||
import { posixify, resolve_entry, to_fs } from '../../../utils/filesystem.js'; | ||
import { should_polyfill } from '../../../utils/platform.js'; | ||
import { load_error_page } from '../../../core/config/index.js'; | ||
@@ -30,5 +29,3 @@ import { SVELTE_KIT_ASSETS } from '../../../constants.js'; | ||
export async function dev(vite, vite_config, svelte_config) { | ||
if (should_polyfill) { | ||
installPolyfills(); | ||
} | ||
installPolyfills(); | ||
@@ -124,7 +121,8 @@ const fetch = globalThis.fetch; | ||
client: { | ||
start: `${runtime_base}/client/start.js`, | ||
start: `${runtime_base}/client/entry.js`, | ||
app: `${to_fs(svelte_config.kit.outDir)}/generated/client/app.js`, | ||
imports: [], | ||
stylesheets: [], | ||
fonts: [] | ||
fonts: [], | ||
uses_env_dynamic_public: true | ||
}, | ||
@@ -229,3 +227,3 @@ nodes: manifest_data.nodes.map((node, index) => { | ||
return await loud_ssr_load_module(url); | ||
} | ||
} | ||
: null, | ||
@@ -364,3 +362,4 @@ endpoint_id: endpoint?.file | ||
HttpError: control_module_vite.HttpError, | ||
Redirect: control_module_vite.Redirect | ||
Redirect: control_module_vite.Redirect, | ||
SvelteKitError: control_module_vite.SvelteKitError | ||
}); | ||
@@ -481,14 +480,7 @@ } | ||
let request; | ||
const request = await getRequest({ | ||
base, | ||
request: req | ||
}); | ||
try { | ||
request = await getRequest({ | ||
base, | ||
request: req | ||
}); | ||
} catch (/** @type {any} */ err) { | ||
res.statusCode = err.status || 400; | ||
return res.end('Invalid request body'); | ||
} | ||
if (manifest_error) { | ||
@@ -546,3 +538,3 @@ console.error(colors.bold().red(manifest_error.message)); | ||
function remove_static_middlewares(server) { | ||
const static_middlewares = ['viteServeStaticMiddleware']; | ||
const static_middlewares = ['viteServeStaticMiddleware', 'viteServePublicMiddleware']; | ||
for (let i = server.stack.length - 1; i > 0; i--) { | ||
@@ -549,0 +541,0 @@ // @ts-expect-error using internals |
import path from 'node:path'; | ||
import { posixify } from '../../../utils/filesystem.js'; | ||
import { strip_virtual_prefix } from '../utils.js'; | ||
import { env_dynamic_private, env_static_private } from '../module_ids.js'; | ||
const ILLEGAL_IMPORTS = new Set(['\0$env/dynamic/private', '\0$env/static/private']); | ||
const ILLEGAL_IMPORTS = new Set([env_dynamic_private, env_static_private]); | ||
const ILLEGAL_MODULE_NAME_PATTERN = /.*\.server\..+/; | ||
@@ -24,3 +26,3 @@ | ||
* Creates a guard that checks that no id imports a module that is not allowed to be imported into client-side code. | ||
* @param {import('rollup').PluginContext} context | ||
* @param {import('vite').Rollup.PluginContext} context | ||
* @param {{ cwd: string; lib: string }} paths | ||
@@ -55,6 +57,10 @@ */ | ||
return `${' '.repeat(i * 2)}- ${id} ${dynamic ? 'dynamically imports' : 'imports'}\n`; | ||
}) + `${' '.repeat(chain.length)}- ${id}`; | ||
return `${' '.repeat(i * 2)}- ${strip_virtual_prefix(id)} ${ | ||
dynamic ? 'dynamically imports' : 'imports' | ||
}\n`; | ||
}) + `${' '.repeat(chain.length)}- ${strip_virtual_prefix(id)}`; | ||
const message = `Cannot import ${id} into client-side code:\n${pyramid}`; | ||
const message = `Cannot import ${strip_virtual_prefix( | ||
id | ||
)} into client-side code:\n${pyramid}`; | ||
@@ -61,0 +67,0 @@ throw new Error(message); |
import fs from 'node:fs'; | ||
import path from 'node:path'; | ||
import { svelte } from '@sveltejs/vite-plugin-svelte'; | ||
import * as imr from 'import-meta-resolve'; | ||
import colors from 'kleur'; | ||
import * as vite from 'vite'; | ||
@@ -21,3 +20,3 @@ import { copy, mkdirp, posixify, read, resolve_entry, rimraf } from '../../utils/filesystem.js'; | ||
import { preview } from './preview/index.js'; | ||
import { get_config_aliases, get_env } from './utils.js'; | ||
import { get_config_aliases, get_env, strip_virtual_prefix } from './utils.js'; | ||
import { write_client_manifest } from '../../core/sync/write_client_manifest.js'; | ||
@@ -28,9 +27,17 @@ import prerender from '../../core/postbuild/prerender.js'; | ||
import { hash } from '../../runtime/hash.js'; | ||
import { dedent } from '../../core/sync/utils.js'; | ||
import { dedent, isSvelte5Plus } from '../../core/sync/utils.js'; | ||
import { | ||
env_dynamic_private, | ||
env_dynamic_public, | ||
env_static_private, | ||
env_static_public, | ||
service_worker, | ||
sveltekit_environment, | ||
sveltekit_paths | ||
} from './module_ids.js'; | ||
import { pathToFileURL } from 'node:url'; | ||
export { vitePreprocess } from '@sveltejs/vite-plugin-svelte'; | ||
const cwd = process.cwd(); | ||
/** @type {import('./types').EnforcedConfig} */ | ||
/** @type {import('./types.js').EnforcedConfig} */ | ||
const enforced_config = { | ||
@@ -77,3 +84,3 @@ appType: true, | ||
/** @type {import('@sveltejs/vite-plugin-svelte').PreprocessorGroup} */ | ||
/** @type {import('svelte/compiler').PreprocessorGroup} */ | ||
const warning_preprocessor = { | ||
@@ -104,6 +111,10 @@ script: ({ content, filename }) => { | ||
const basename = path.basename(filename); | ||
if (basename.startsWith('+layout.') && !content.includes('<slot')) { | ||
const has_children = | ||
content.includes('<slot') || (isSvelte5Plus() && content.includes('{@render')); | ||
if (basename.startsWith('+layout.') && !has_children) { | ||
const message = | ||
`\n${colors.bold().red(path.relative('.', filename))}\n` + | ||
'`<slot />` missing — inner content will not be rendered'; | ||
`\`<slot />\`${isSvelte5Plus() ? ' or `{@render ...}` tag' : ''}` + | ||
' missing — inner content will not be rendered'; | ||
@@ -119,2 +130,19 @@ if (!warned.has(message)) { | ||
/** | ||
* Resolve a dependency relative to the current working directory, | ||
* rather than relative to this package | ||
* @param {string} dependency | ||
*/ | ||
async function resolve_peer_dependency(dependency) { | ||
try { | ||
// @ts-expect-error the types are wrong | ||
const resolved = await imr.resolve(dependency, pathToFileURL(process.cwd() + '/dummy.js')); | ||
return import(resolved); | ||
} catch (e) { | ||
throw new Error( | ||
`Could not resolve peer dependency "${dependency}" relative to your project — please install it and try again.` | ||
); | ||
} | ||
} | ||
/** | ||
* Returns the SvelteKit Vite plugins. | ||
@@ -144,3 +172,3 @@ * @returns {Promise<import('vite').Plugin[]>} | ||
// @ts-expect-error SvelteKit requires hydratable true by default | ||
hydratable: true, | ||
hydratable: isSvelte5Plus() ? undefined : true, | ||
...svelte_config.compilerOptions | ||
@@ -151,3 +179,5 @@ }, | ||
return [...svelte(vite_plugin_svelte_options), ...kit({ svelte_config })]; | ||
const { svelte } = await resolve_peer_dependency('@sveltejs/vite-plugin-svelte'); | ||
return [...svelte(vite_plugin_svelte_options), ...(await kit({ svelte_config }))]; | ||
} | ||
@@ -173,5 +203,7 @@ | ||
* @param {{ svelte_config: import('types').ValidatedConfig }} options | ||
* @return {import('vite').Plugin[]} | ||
* @return {Promise<import('vite').Plugin[]>} | ||
*/ | ||
function kit({ svelte_config }) { | ||
async function kit({ svelte_config }) { | ||
const vite = await resolve_peer_dependency('vite'); | ||
const { kit } = svelte_config; | ||
@@ -201,2 +233,3 @@ const out = `${kit.outDir}/output`; | ||
const service_worker_entry_file = resolve_entry(kit.files.serviceWorker); | ||
const parsed_service_worker = path.parse(kit.files.serviceWorker); | ||
@@ -334,6 +367,2 @@ const sourcemapIgnoreList = /** @param {string} relative_path */ (relative_path) => | ||
vite_config = config; | ||
// This is a hack to prevent Vite from nuking useful logs, | ||
// pending https://github.com/vitejs/vite/issues/9378 | ||
config.logger.warn(''); | ||
} | ||
@@ -346,6 +375,23 @@ }; | ||
async resolveId(id) { | ||
async resolveId(id, importer) { | ||
// If importing from a service-worker, only allow $service-worker & $env/static/public, but none of the other virtual modules. | ||
// This check won't catch transitive imports, but it will warn when the import comes from a service-worker directly. | ||
// Transitive imports will be caught during the build. | ||
if (importer) { | ||
const parsed_importer = path.parse(importer); | ||
const importer_is_service_worker = | ||
parsed_importer.dir === parsed_service_worker.dir && | ||
parsed_importer.name === parsed_service_worker.name; | ||
if (importer_is_service_worker && id !== '$service-worker' && id !== '$env/static/public') { | ||
throw new Error( | ||
`Cannot import ${id} into service-worker code. Only the modules $service-worker and $env/static/public are available in service workers.` | ||
); | ||
} | ||
} | ||
// treat $env/static/[public|private] as virtual | ||
if (id.startsWith('$env/') || id.startsWith('__sveltekit/') || id === '$service-worker') { | ||
return `\0${id}`; | ||
return `\0virtual:${id}`; | ||
} | ||
@@ -372,3 +418,3 @@ }, | ||
const relative = normalize_id(id, normalized_lib, normalized_cwd); | ||
throw new Error(`Cannot import ${relative} into client-side code`); | ||
throw new Error(`Cannot import ${strip_virtual_prefix(relative)} into client-side code`); | ||
} | ||
@@ -378,9 +424,9 @@ } | ||
switch (id) { | ||
case '\0$env/static/private': | ||
case env_static_private: | ||
return create_static_module('$env/static/private', env.private); | ||
case '\0$env/static/public': | ||
case env_static_public: | ||
return create_static_module('$env/static/public', env.public); | ||
case '\0$env/dynamic/private': | ||
case env_dynamic_private: | ||
return create_dynamic_module( | ||
@@ -391,3 +437,3 @@ 'private', | ||
case '\0$env/dynamic/public': | ||
case env_dynamic_public: | ||
// populate `$env/dynamic/public` from `window` | ||
@@ -403,3 +449,3 @@ if (browser) { | ||
case '\0$service-worker': | ||
case service_worker: | ||
return create_service_worker_module(svelte_config); | ||
@@ -409,3 +455,3 @@ | ||
// we use this alias so that we won't collide with user aliases | ||
case '\0__sveltekit/paths': { | ||
case sveltekit_paths: { | ||
const { assets, base } = svelte_config.kit.paths; | ||
@@ -448,3 +494,3 @@ | ||
case '\0__sveltekit/environment': { | ||
case sveltekit_environment: { | ||
const { version } = svelte_config.kit; | ||
@@ -455,2 +501,3 @@ | ||
export let building = false; | ||
export let prerendering = false; | ||
@@ -460,2 +507,6 @@ export function set_building() { | ||
} | ||
export function set_prerendering() { | ||
prerendering = true; | ||
} | ||
`; | ||
@@ -550,3 +601,3 @@ } | ||
} else { | ||
input['entry/start'] = `${runtime_directory}/client/start.js`; | ||
input['entry/start'] = `${runtime_directory}/client/entry.js`; | ||
input['entry/app'] = `${kit.outDir}/generated/client-optimized/app.js`; | ||
@@ -578,3 +629,3 @@ | ||
// don't use the default name to avoid collisions with 'static/manifest.json' | ||
manifest: 'vite-manifest.json', | ||
manifest: '.vite/manifest.json', // TODO: remove this after bumping peer dep to vite 5 | ||
outDir: `${out}/${ssr ? 'server' : 'client'}`, | ||
@@ -594,3 +645,3 @@ rollupOptions: { | ||
ssrEmitAssets: true, | ||
target: ssr ? 'node16.14' : undefined | ||
target: ssr ? 'node18.13' : 'es2022' | ||
}, | ||
@@ -617,3 +668,3 @@ publicDir: kit.files.assets, | ||
// eventhough server otherwise works without it | ||
input: `${runtime_directory}/client/start.js` | ||
input: `${runtime_directory}/client/entry.js` | ||
} | ||
@@ -728,3 +779,3 @@ }, | ||
const { output } = /** @type {import('rollup').RollupOutput} */ ( | ||
const { output } = /** @type {import('vite').Rollup.RollupOutput} */ ( | ||
await vite.build({ | ||
@@ -757,3 +808,3 @@ configFile: vite_config.configFile, | ||
find_deps(client_manifest, posixify(path.relative('.', f)), false); | ||
const start = deps_of(`${runtime_directory}/client/start.js`); | ||
const start = deps_of(`${runtime_directory}/client/entry.js`); | ||
const app = deps_of(`${kit.outDir}/generated/client-optimized/app.js`); | ||
@@ -766,7 +817,10 @@ | ||
stylesheets: [...start.stylesheets, ...app.stylesheets], | ||
fonts: [...start.fonts, ...app.fonts] | ||
fonts: [...start.fonts, ...app.fonts], | ||
uses_env_dynamic_public: output.some( | ||
(chunk) => chunk.type === 'chunk' && chunk.modules[env_dynamic_public] | ||
) | ||
}; | ||
const css = output.filter( | ||
/** @type {(value: any) => value is import('rollup').OutputAsset} */ | ||
/** @type {(value: any) => value is import('vite').Rollup.OutputAsset} */ | ||
(value) => value.type === 'asset' && value.fileName.endsWith('.css') | ||
@@ -834,9 +888,13 @@ ); | ||
// avoid making the manifest available to users | ||
fs.unlinkSync(`${out}/client/${vite_config.build.manifest}`); | ||
fs.unlinkSync(`${out}/server/${vite_config.build.manifest}`); | ||
if (kit.adapter) { | ||
const { adapt } = await import('../../core/adapt/index.js'); | ||
await adapt(svelte_config, build_data, metadata, prerendered, prerender_map, log); | ||
await adapt( | ||
svelte_config, | ||
build_data, | ||
metadata, | ||
prerendered, | ||
prerender_map, | ||
log, | ||
vite_config | ||
); | ||
} else { | ||
@@ -889,3 +947,3 @@ console.log(colors.bold().yellow('\nNo adapter specified')); | ||
* @param {Record<string, any>} resolved_config | ||
* @param {import('./types').EnforcedConfig} enforced_config | ||
* @param {import('./types.js').EnforcedConfig} enforced_config | ||
* @param {string} path | ||
@@ -892,0 +950,0 @@ * @param {string[]} out used locally to compute the return value |
import fs from 'node:fs'; | ||
import { join } from 'node:path'; | ||
import { pathToFileURL } from 'node:url'; | ||
import { lookup } from 'mrmime'; | ||
import sirv from 'sirv'; | ||
@@ -9,3 +10,2 @@ import { loadEnv, normalizePath } from 'vite'; | ||
import { SVELTE_KIT_ASSETS } from '../../../constants.js'; | ||
import { should_polyfill } from '../../../utils/platform.js'; | ||
import { not_found } from '../utils.js'; | ||
@@ -18,6 +18,3 @@ | ||
/** | ||
* @param {{ | ||
* middlewares: import('connect').Server; | ||
* httpServer: import('http').Server; | ||
* }} vite | ||
* @param {{ middlewares: import('connect').Server }} vite | ||
* @param {import('vite').ResolvedConfig} vite_config | ||
@@ -27,5 +24,3 @@ * @param {import('types').ValidatedConfig} svelte_config | ||
export async function preview(vite, vite_config, svelte_config) { | ||
if (should_polyfill) { | ||
installPolyfills(); | ||
} | ||
installPolyfills(); | ||
@@ -62,2 +57,14 @@ const { paths } = svelte_config.kit; | ||
return () => { | ||
// Remove the base middleware. It screws with the URL. | ||
// It also only lets through requests beginning with the base path, so that requests beginning | ||
// with the assets URL never reach us. We could serve assets separately before the base | ||
// middleware, but we'd need that to occur after the compression and cors middlewares, so would | ||
// need to insert it manually into the stack, which would be at least as bad as doing this. | ||
for (let i = vite.middlewares.stack.length - 1; i > 0; i--) { | ||
// @ts-expect-error using internals | ||
if (vite.middlewares.stack[i].handle.name === 'viteBaseMiddleware') { | ||
vite.middlewares.stack.splice(i, 1); | ||
} | ||
} | ||
// generated client assets and the contents of `static` | ||
@@ -80,4 +87,16 @@ vite.middlewares.use( | ||
const original_url = /** @type {string} */ (req.url); | ||
const { pathname } = new URL(original_url, 'http://dummy'); | ||
const { pathname, search } = new URL(original_url, 'http://dummy'); | ||
// if `paths.base === '/a/b/c`, then the root route is `/a/b/c/`, | ||
// regardless of the `trailingSlash` route option | ||
if (base.length > 1 && pathname === base) { | ||
let location = base + '/'; | ||
if (search) location += search; | ||
res.writeHead(307, { | ||
location | ||
}); | ||
res.end(); | ||
return; | ||
} | ||
if (pathname.startsWith(base)) { | ||
@@ -112,3 +131,3 @@ next(); | ||
const { pathname } = new URL(/** @type {string} */ (req.url), 'http://dummy'); | ||
const { pathname, search } = new URL(/** @type {string} */ (req.url), 'http://dummy'); | ||
@@ -124,2 +143,3 @@ let filename = normalizePath( | ||
/** @type {string | undefined} */ | ||
let redirect; | ||
@@ -139,2 +159,3 @@ | ||
if (redirect) { | ||
if (search) redirect += search; | ||
res.writeHead(307, { | ||
@@ -152,3 +173,3 @@ location: redirect | ||
res.writeHead(200, { | ||
'content-type': 'text/html', | ||
'content-type': lookup(pathname) || 'text/html', | ||
etag | ||
@@ -168,14 +189,7 @@ }); | ||
let request; | ||
const request = await getRequest({ | ||
base: `${protocol}://${host}`, | ||
request: req | ||
}); | ||
try { | ||
request = await getRequest({ | ||
base: `${protocol}://${host}`, | ||
request: req | ||
}); | ||
} catch (/** @type {any} */ err) { | ||
res.statusCode = err.status || 400; | ||
return res.end('Invalid request body'); | ||
} | ||
setResponse( | ||
@@ -205,3 +219,3 @@ res, | ||
maxAge: 0 | ||
}) | ||
}) | ||
: (_req, _res, next) => next(); | ||
@@ -208,0 +222,0 @@ |
@@ -100,1 +100,3 @@ import path from 'node:path'; | ||
} | ||
export const strip_virtual_prefix = /** @param {string} id */ (id) => id.replace('\0virtual:', ''); |
import * as devalue from 'devalue'; | ||
import { BROWSER, DEV } from 'esm-env'; | ||
import { client } from '../client/singletons.js'; | ||
import { DEV } from 'esm-env'; | ||
import { invalidateAll } from './navigation.js'; | ||
import { applyAction } from '../client/client.js'; | ||
/** | ||
* This action updates the `form` property of the current page with the given data and updates `$page.status`. | ||
* In case of an error, it redirects to the nearest error page. | ||
* @template {Record<string, unknown> | undefined} Success | ||
* @template {Record<string, unknown> | undefined} Failure | ||
* @param {import('@sveltejs/kit').ActionResult<Success, Failure>} result | ||
* @returns {Promise<void>} | ||
*/ | ||
export function applyAction(result) { | ||
if (BROWSER) { | ||
return client.apply_action(result); | ||
} else { | ||
throw new Error('Cannot call applyAction(...) on the server'); | ||
} | ||
} | ||
export { applyAction }; | ||
@@ -53,16 +39,2 @@ /** | ||
/** | ||
* @param {string} old_name | ||
* @param {string} new_name | ||
* @param {string} call_location | ||
* @returns void | ||
*/ | ||
function warn_on_access(old_name, new_name, call_location) { | ||
if (!DEV) return; | ||
// TODO 2.0: Remove this code | ||
console.warn( | ||
`\`${old_name}\` has been deprecated in favor of \`${new_name}\`. \`${old_name}\` will be removed in a future version. (Called from ${call_location})` | ||
); | ||
} | ||
/** | ||
* Shallow clone an element, so that we can access e.g. `form.action` without worrying | ||
@@ -108,2 +80,3 @@ * that someone has added an `<input name="action">` (https://github.com/sveltejs/kit/issues/7593) | ||
* action: URL; | ||
* invalidateAll?: boolean; | ||
* result: import('@sveltejs/kit').ActionResult; | ||
@@ -113,9 +86,16 @@ * reset?: boolean | ||
*/ | ||
const fallback_callback = async ({ action, result, reset }) => { | ||
const fallback_callback = async ({ | ||
action, | ||
result, | ||
reset = true, | ||
invalidateAll: shouldInvalidateAll = true | ||
}) => { | ||
if (result.type === 'success') { | ||
if (reset !== false) { | ||
if (reset) { | ||
// We call reset from the prototype to avoid DOM clobbering | ||
HTMLFormElement.prototype.reset.call(form_element); | ||
} | ||
await invalidateAll(); | ||
if (shouldInvalidateAll) { | ||
await invalidateAll(); | ||
} | ||
} | ||
@@ -155,7 +135,5 @@ | ||
if (value instanceof File) { | ||
// TODO 2.0: Upgrade to `throw Error` | ||
console.warn( | ||
'Your form contains <input type="file"> fields, but is missing the `enctype="multipart/form-data"` attribute. This will lead to inconsistent behavior between enhanced and native forms. For more details, see https://github.com/sveltejs/kit/issues/9819. This will be upgraded to an error in v2.0.' | ||
throw new Error( | ||
'Your form contains <input type="file"> fields, but is missing the necessary `enctype="multipart/form-data"` attribute. This will lead to inconsistent behavior between enhanced and native forms. For more details, see https://github.com/sveltejs/kit/issues/9819.' | ||
); | ||
break; | ||
} | ||
@@ -175,3 +153,2 @@ } | ||
// TODO 2.0: Remove `data` and `form` | ||
const callback = | ||
@@ -182,11 +159,3 @@ (await submit({ | ||
controller, | ||
get data() { | ||
warn_on_access('data', 'formData', 'use:enhance submit function'); | ||
return form_data; | ||
}, | ||
formData: form_data, | ||
get form() { | ||
warn_on_access('form', 'formElement', 'use:enhance submit function'); | ||
return form_element; | ||
}, | ||
formElement: form_element, | ||
@@ -221,13 +190,11 @@ submitter: event.submitter | ||
action, | ||
get data() { | ||
warn_on_access('data', 'formData', 'callback returned from use:enhance submit function'); | ||
return form_data; | ||
}, | ||
formData: form_data, | ||
get form() { | ||
warn_on_access('form', 'formElement', 'callback returned from use:enhance submit function'); | ||
return form_element; | ||
}, | ||
formElement: form_element, | ||
update: (opts) => fallback_callback({ action, result, reset: opts?.reset }), | ||
update: (opts) => | ||
fallback_callback({ | ||
action, | ||
result, | ||
reset: opts?.reset, | ||
invalidateAll: opts?.invalidateAll | ||
}), | ||
// @ts-expect-error generic constraints stuff we don't care about | ||
@@ -234,0 +201,0 @@ result |
@@ -1,126 +0,13 @@ | ||
import { client_method } from '../client/singletons.js'; | ||
/** | ||
* If called when the page is being updated following a navigation (in `onMount` or `afterNavigate` or an action, for example), this disables SvelteKit's built-in scroll handling. | ||
* This is generally discouraged, since it breaks user expectations. | ||
* @returns {void} | ||
*/ | ||
export const disableScrollHandling = /* @__PURE__ */ client_method('disable_scroll_handling'); | ||
/** | ||
* Returns a Promise that resolves when SvelteKit navigates (or fails to navigate, in which case the promise rejects) to the specified `url`. | ||
* For external URLs, use `window.location = url` instead of calling `goto(url)`. | ||
* | ||
* @type {(url: string | URL, opts?: { | ||
* replaceState?: boolean; | ||
* noScroll?: boolean; | ||
* keepFocus?: boolean; | ||
* invalidateAll?: boolean; | ||
* state?: any | ||
* }) => Promise<void>} | ||
* @param {string | URL} url Where to navigate to. Note that if you've set [`config.kit.paths.base`](https://kit.svelte.dev/docs/configuration#paths) and the URL is root-relative, you need to prepend the base path if you want to navigate within the app. | ||
* @param {Object} [opts] Options related to the navigation | ||
* @param {boolean} [opts.replaceState] If `true`, will replace the current `history` entry rather than creating a new one with `pushState` | ||
* @param {boolean} [opts.noScroll] If `true`, the browser will maintain its scroll position rather than scrolling to the top of the page after navigation | ||
* @param {boolean} [opts.keepFocus] If `true`, the currently focused element will retain focus after navigation. Otherwise, focus will be reset to the body | ||
* @param {boolean} [invalidateAll] If `true`, all `load` functions of the page will be rerun. See https://kit.svelte.dev/docs/load#rerunning-load-functions for more info on invalidation. | ||
* @param {any} [opts.state] The state of the new/updated history entry | ||
* @returns {Promise<void>} | ||
*/ | ||
export const goto = /* @__PURE__ */ client_method('goto'); | ||
/** | ||
* Causes any `load` functions belonging to the currently active page to re-run if they depend on the `url` in question, via `fetch` or `depends`. Returns a `Promise` that resolves when the page is subsequently updated. | ||
* | ||
* If the argument is given as a `string` or `URL`, it must resolve to the same URL that was passed to `fetch` or `depends` (including query parameters). | ||
* To create a custom identifier, use a string beginning with `[a-z]+:` (e.g. `custom:state`) — this is a valid URL. | ||
* | ||
* The `function` argument can be used define a custom predicate. It receives the full `URL` and causes `load` to rerun if `true` is returned. | ||
* This can be useful if you want to invalidate based on a pattern instead of a exact match. | ||
* | ||
* ```ts | ||
* // Example: Match '/path' regardless of the query parameters | ||
* import { invalidate } from '$app/navigation'; | ||
* | ||
* invalidate((url) => url.pathname === '/path'); | ||
* ``` | ||
* @type {(url: string | URL | ((url: URL) => boolean)) => Promise<void>} | ||
* @param {string | URL | ((url: URL) => boolean)} url The invalidated URL | ||
* @returns {Promise<void>} | ||
*/ | ||
export const invalidate = /* @__PURE__ */ client_method('invalidate'); | ||
/** | ||
* Causes all `load` functions belonging to the currently active page to re-run. Returns a `Promise` that resolves when the page is subsequently updated. | ||
* @type {() => Promise<void>} | ||
* @returns {Promise<void>} | ||
*/ | ||
export const invalidateAll = /* @__PURE__ */ client_method('invalidate_all'); | ||
/** | ||
* Programmatically preloads the given page, which means | ||
* 1. ensuring that the code for the page is loaded, and | ||
* 2. calling the page's load function with the appropriate options. | ||
* | ||
* This is the same behaviour that SvelteKit triggers when the user taps or mouses over an `<a>` element with `data-sveltekit-preload-data`. | ||
* If the next navigation is to `href`, the values returned from load will be used, making navigation instantaneous. | ||
* Returns a Promise that resolves when the preload is complete. | ||
* | ||
* @type {(href: string) => Promise<void>} | ||
* @param {string} href Page to preload | ||
* @returns {Promise<void>} | ||
*/ | ||
export const preloadData = /* @__PURE__ */ client_method('preload_data'); | ||
/** | ||
* Programmatically imports the code for routes that haven't yet been fetched. | ||
* Typically, you might call this to speed up subsequent navigation. | ||
* | ||
* You can specify routes by any matching pathname such as `/about` (to match `src/routes/about/+page.svelte`) or `/blog/*` (to match `src/routes/blog/[slug]/+page.svelte`). | ||
* | ||
* Unlike `preloadData`, this won't call `load` functions. | ||
* Returns a Promise that resolves when the modules have been imported. | ||
* | ||
* @type {(...urls: string[]) => Promise<void>} | ||
* @param {...string[]} urls | ||
* @returns {Promise<void>} | ||
*/ | ||
export const preloadCode = /* @__PURE__ */ client_method('preload_code'); | ||
/** | ||
* A navigation interceptor that triggers before we navigate to a new URL, whether by clicking a link, calling `goto(...)`, or using the browser back/forward controls. | ||
* Calling `cancel()` will prevent the navigation from completing. If the navigation would have directly unloaded the current page, calling `cancel` will trigger the native | ||
* browser unload confirmation dialog. In these cases, `navigation.willUnload` is `true`. | ||
* | ||
* When a navigation isn't client side, `navigation.to.route.id` will be `null`. | ||
* | ||
* `beforeNavigate` must be called during a component initialization. It remains active as long as the component is mounted. | ||
* @type {(callback: (navigation: import('@sveltejs/kit').BeforeNavigate) => void) => void} | ||
* @param {(navigation: import('@sveltejs/kit').BeforeNavigate) => void} callback | ||
* @returns {void} | ||
*/ | ||
export const beforeNavigate = /* @__PURE__ */ client_method('before_navigate'); | ||
/** | ||
* A lifecycle function that runs the supplied `callback` immediately before we navigate to a new URL. | ||
* | ||
* If you return a `Promise`, SvelteKit will wait for it to resolve before completing the navigation. This allows you to — for example — use `document.startViewTransition`. Avoid promises that are slow to resolve, since navigation will appear stalled to the user. | ||
* | ||
* If a function (or a `Promise` that resolves to a function) is returned from the callback, it will be called once the DOM has updated. | ||
* | ||
* `onNavigate` must be called during a component initialization. It remains active as long as the component is mounted. | ||
* @type {(callback: (navigation: import('@sveltejs/kit').OnNavigate) => import('../../types/internal.js').MaybePromise<(() => void) | void>) => void} | ||
* @param {(navigation: import('@sveltejs/kit').OnNavigate) => void} callback | ||
* @returns {void} | ||
*/ | ||
export const onNavigate = /* @__PURE__ */ client_method('on_navigate'); | ||
/** | ||
* A lifecycle function that runs the supplied `callback` when the current component mounts, and also whenever we navigate to a new URL. | ||
* | ||
* `afterNavigate` must be called during a component initialization. It remains active as long as the component is mounted. | ||
* @type {(callback: (navigation: import('@sveltejs/kit').AfterNavigate) => void) => void} | ||
* @param {(navigation: import('@sveltejs/kit').AfterNavigate) => void} callback | ||
* @returns {void} | ||
*/ | ||
export const afterNavigate = /* @__PURE__ */ client_method('after_navigate'); | ||
export { | ||
afterNavigate, | ||
beforeNavigate, | ||
disableScrollHandling, | ||
goto, | ||
invalidate, | ||
invalidateAll, | ||
onNavigate, | ||
preloadCode, | ||
preloadData, | ||
pushState, | ||
replaceState | ||
} from '../client/client.js'; |
import { getContext } from 'svelte'; | ||
import { browser } from './environment.js'; | ||
import { stores as browser_stores } from '../client/singletons.js'; | ||
import { BROWSER } from 'esm-env'; | ||
import { stores as browser_stores } from '../client/client.js'; | ||
@@ -10,3 +10,3 @@ /** | ||
export const getStores = () => { | ||
const stores = browser ? browser_stores : getContext('__svelte__'); | ||
const stores = BROWSER ? browser_stores : getContext('__svelte__'); | ||
@@ -66,3 +66,3 @@ return { | ||
if (browser) { | ||
if (BROWSER) { | ||
updated.check = store.check; | ||
@@ -75,3 +75,3 @@ } | ||
throw new Error( | ||
browser | ||
BROWSER | ||
? 'Cannot check updated store before subscribing' | ||
@@ -78,0 +78,0 @@ : 'Can only check updated store in browser' |
export const SNAPSHOT_KEY = 'sveltekit:snapshot'; | ||
export const SCROLL_KEY = 'sveltekit:scroll'; | ||
export const INDEX_KEY = 'sveltekit:index'; | ||
export const STATES_KEY = 'sveltekit:states'; | ||
export const PAGE_URL_KEY = 'sveltekit:pageurl'; | ||
export const HISTORY_INDEX = 'sveltekit:history'; | ||
export const NAVIGATION_INDEX = 'sveltekit:navigation'; | ||
export const PRELOAD_PRIORITIES = /** @type {const} */ ({ | ||
@@ -10,3 +14,4 @@ tap: 1, | ||
eager: 4, | ||
off: -1 | ||
off: -1, | ||
false: -1 | ||
}); |
@@ -1,2 +0,2 @@ | ||
import { DEV } from 'esm-env'; | ||
import { BROWSER, DEV } from 'esm-env'; | ||
import { hash } from '../hash.js'; | ||
@@ -6,3 +6,4 @@ | ||
export const native_fetch = window.fetch; | ||
/** @type {typeof fetch} */ | ||
export const native_fetch = BROWSER ? window.fetch : /** @type {any} */ (() => {}); | ||
@@ -17,3 +18,3 @@ export function lock_fetch() { | ||
if (DEV) { | ||
if (DEV && BROWSER) { | ||
let can_inspect_stack_trace = false; | ||
@@ -28,2 +29,6 @@ | ||
/** | ||
* @param {RequestInfo | URL} input | ||
* @param {RequestInit & Record<string, any> | undefined} init | ||
*/ | ||
window.fetch = (input, init) => { | ||
@@ -42,6 +47,10 @@ // Check if fetch was called via load_node. the lock method only checks if it was called at the | ||
const heuristic = can_inspect_stack_trace | ||
const in_load_heuristic = can_inspect_stack_trace | ||
? stack.includes('src/runtime/client/client.js') | ||
: loading; | ||
if (heuristic) { | ||
// This flag is set in initial_fetch and subsequent_fetch | ||
const used_kit_fetch = init?.__sveltekit_fetch__; | ||
if (in_load_heuristic && !used_kit_fetch) { | ||
console.warn( | ||
@@ -60,3 +69,3 @@ `Loading ${url} using \`window.fetch\`. For best results, use the \`fetch\` that is passed to your \`load\` function: https://kit.svelte.dev/docs/load#making-fetch-requests` | ||
}; | ||
} else { | ||
} else if (BROWSER) { | ||
window.fetch = (input, init) => { | ||
@@ -76,2 +85,18 @@ const method = input instanceof Request ? input.method : init?.method || 'GET'; | ||
/** | ||
* @param {string} text | ||
* @returns {ArrayBufferLike} | ||
*/ | ||
function b64_decode(text) { | ||
const d = atob(text); | ||
const u8 = new Uint8Array(d.length); | ||
for (let i = 0; i < d.length; i++) { | ||
u8[i] = d.charCodeAt(i); | ||
} | ||
return u8.buffer; | ||
} | ||
/** | ||
* Should be called on the initial run of load functions that hydrate the page. | ||
@@ -87,6 +112,12 @@ * Saves any requests with cache-control max-age to the cache. | ||
if (script?.textContent) { | ||
const { body, ...init } = JSON.parse(script.textContent); | ||
let { body, ...init } = JSON.parse(script.textContent); | ||
const ttl = script.getAttribute('data-ttl'); | ||
if (ttl) cache.set(selector, { body, init, ttl: 1000 * Number(ttl) }); | ||
const b64 = script.getAttribute('data-b64'); | ||
if (b64 !== null) { | ||
// Can't use native_fetch('data:...;base64,${body}') | ||
// csp can block the request | ||
body = b64_decode(body); | ||
} | ||
@@ -96,3 +127,3 @@ return Promise.resolve(new Response(body, init)); | ||
return native_fetch(resource, opts); | ||
return DEV ? dev_fetch(resource, opts) : window.fetch(resource, opts); | ||
} | ||
@@ -123,6 +154,21 @@ | ||
return native_fetch(resolved, opts); | ||
return DEV ? dev_fetch(resolved, opts) : window.fetch(resolved, opts); | ||
} | ||
/** | ||
* @param {RequestInfo | URL} resource | ||
* @param {RequestInit & Record<string, any> | undefined} opts | ||
*/ | ||
function dev_fetch(resource, opts) { | ||
const patched_opts = { ...opts }; | ||
// This assigns the __sveltekit_fetch__ flag and makes it non-enumerable | ||
Object.defineProperty(patched_opts, '__sveltekit_fetch__', { | ||
value: true, | ||
writable: true, | ||
configurable: true | ||
}); | ||
return window.fetch(resource, patched_opts); | ||
} | ||
/** | ||
* Build the cache key for a given request | ||
@@ -129,0 +175,0 @@ * @param {URL | RequestInfo} resource |
import { exec, parse_route_id } from '../../utils/routing.js'; | ||
/** | ||
* @param {import('./types').SvelteKitApp} app | ||
* @param {import('./types.js').SvelteKitApp} app | ||
* @returns {import('types').CSRRoute[]} | ||
@@ -6,0 +6,0 @@ */ |
/** | ||
* Read a value from `sessionStorage` | ||
* @param {string} key | ||
* @param {(value: string) => any} parse | ||
*/ | ||
export function get(key) { | ||
export function get(key, parse = JSON.parse) { | ||
try { | ||
return JSON.parse(sessionStorage[key]); | ||
return parse(sessionStorage[key]); | ||
} catch { | ||
@@ -17,7 +18,8 @@ // do nothing | ||
* @param {any} value | ||
* @param {(value: any) => string} stringify | ||
*/ | ||
export function set(key, value) { | ||
const json = JSON.stringify(value); | ||
export function set(key, value, stringify = JSON.stringify) { | ||
const data = stringify(value); | ||
try { | ||
sessionStorage[key] = json; | ||
sessionStorage[key] = data; | ||
} catch { | ||
@@ -24,0 +26,0 @@ // do nothing |
@@ -1,12 +0,1 @@ | ||
import { applyAction } from '../app/forms'; | ||
import { | ||
afterNavigate, | ||
beforeNavigate, | ||
onNavigate, | ||
goto, | ||
invalidate, | ||
invalidateAll, | ||
preloadCode, | ||
preloadData | ||
} from '../app/navigation'; | ||
import { SvelteComponent } from 'svelte'; | ||
@@ -43,28 +32,2 @@ import { ClientHooks, CSRPageNode, CSRPageNodeLoader, CSRRoute, TrailingSlash, Uses } from 'types'; | ||
export interface Client { | ||
// public API, exposed via $app/navigation | ||
after_navigate: typeof afterNavigate; | ||
before_navigate: typeof beforeNavigate; | ||
on_navigate: typeof onNavigate; | ||
disable_scroll_handling(): void; | ||
goto: typeof goto; | ||
invalidate: typeof invalidate; | ||
invalidate_all: typeof invalidateAll; | ||
preload_code: typeof preloadCode; | ||
preload_data: typeof preloadData; | ||
apply_action: typeof applyAction; | ||
// private API | ||
_hydrate(opts: { | ||
status: number; | ||
error: App.Error | null; | ||
node_ids: number[]; | ||
params: Record<string, string>; | ||
route: { id: string | null }; | ||
data: Array<import('types').ServerDataNode | null>; | ||
form: Record<string, any> | null; | ||
}): Promise<void>; | ||
_start_router(): void; | ||
} | ||
export type NavigationIntent = { | ||
@@ -94,4 +57,5 @@ /** `url.pathname + url.search` */ | ||
props: { | ||
components: Array<typeof SvelteComponent>; | ||
page?: Page; | ||
constructors: Array<typeof SvelteComponent>; | ||
components?: Array<SvelteComponent>; | ||
page: Page; | ||
form?: Record<string, any> | null; | ||
@@ -98,0 +62,0 @@ [key: `data_${number}`]: Record<string, any>; |
@@ -9,12 +9,16 @@ import { BROWSER, DEV } from 'esm-env'; | ||
/** @param {HTMLDocument} doc */ | ||
export function get_base_uri(doc) { | ||
let baseURI = doc.baseURI; | ||
export const origin = BROWSER ? location.origin : ''; | ||
/** @param {string | URL} url */ | ||
export function resolve_url(url) { | ||
if (url instanceof URL) return url; | ||
let baseURI = document.baseURI; | ||
if (!baseURI) { | ||
const baseTags = doc.getElementsByTagName('base'); | ||
baseURI = baseTags.length ? baseTags[0].href : doc.URL; | ||
const baseTags = document.getElementsByTagName('base'); | ||
baseURI = baseTags.length ? baseTags[0].href : document.URL; | ||
} | ||
return baseURI; | ||
return new URL(url, baseURI); | ||
} | ||
@@ -34,4 +38,4 @@ | ||
const valid_link_options = /** @type {const} */ ({ | ||
'preload-code': ['', 'off', 'tap', 'hover', 'viewport', 'eager'], | ||
'preload-data': ['', 'off', 'tap', 'hover'], | ||
'preload-code': ['', 'off', 'false', 'tap', 'hover', 'viewport', 'eager'], | ||
'preload-data': ['', 'off', 'false', 'tap', 'hover'], | ||
keepfocus: ['', 'true', 'off', 'false'], | ||
@@ -140,3 +144,3 @@ noscroll: ['', 'true', 'off', 'false'], | ||
const download = url?.origin === location.origin && a.hasAttribute('download'); | ||
const download = url?.origin === origin && a.hasAttribute('download'); | ||
@@ -151,3 +155,3 @@ return { url, external, target, download }; | ||
/** @type {ValidLinkOptions<'keepfocus'> | null} */ | ||
let keep_focus = null; | ||
let keepfocus = null; | ||
@@ -175,3 +179,3 @@ /** @type {ValidLinkOptions<'noscroll'> | null} */ | ||
if (preload_data === null) preload_data = link_option(el, 'preload-data'); | ||
if (keep_focus === null) keep_focus = link_option(el, 'keepfocus'); | ||
if (keepfocus === null) keepfocus = link_option(el, 'keepfocus'); | ||
if (noscroll === null) noscroll = link_option(el, 'noscroll'); | ||
@@ -194,3 +198,3 @@ if (reload === null) reload = link_option(el, 'reload'); | ||
default: | ||
return null; | ||
return undefined; | ||
} | ||
@@ -202,3 +206,3 @@ } | ||
preload_data: levels[preload_data ?? 'off'], | ||
keep_focus: get_option_state(keep_focus), | ||
keepfocus: get_option_state(keepfocus), | ||
noscroll: get_option_state(noscroll), | ||
@@ -300,3 +304,3 @@ reload: get_option_state(reload), | ||
export function is_external_url(url, base) { | ||
return url.origin !== location.origin || !url.pathname.startsWith(base); | ||
return url.origin !== origin || !url.pathname.startsWith(base); | ||
} |
@@ -34,2 +34,20 @@ export class HttpError { | ||
/** | ||
* An error that was thrown from within the SvelteKit runtime that is not fatal and doesn't result in a 500, such as a 404. | ||
* `SvelteKitError` goes through `handleError`. | ||
* @extends Error | ||
*/ | ||
export class SvelteKitError extends Error { | ||
/** | ||
* @param {number} status | ||
* @param {string} text | ||
* @param {string} message | ||
*/ | ||
constructor(status, text, message) { | ||
super(message); | ||
this.status = status; | ||
this.text = text; | ||
} | ||
} | ||
/** | ||
* @template {Record<string, unknown> | undefined} [T=undefined] | ||
@@ -40,3 +58,3 @@ */ | ||
* @param {number} status | ||
* @param {T} [data] | ||
* @param {T} data | ||
*/ | ||
@@ -59,2 +77,3 @@ constructor(status, data) { | ||
* Redirect: typeof Redirect; | ||
* SvelteKitError: typeof SvelteKitError; | ||
* }} implementations | ||
@@ -69,2 +88,4 @@ */ | ||
Redirect = implementations.Redirect; // eslint-disable-line no-class-assign | ||
// @ts-expect-error | ||
SvelteKitError = implementations.SvelteKitError; // eslint-disable-line no-class-assign | ||
} |
declare module '__SERVER__/internal.js' { | ||
export const options: import('types').SSROptions; | ||
export const get_hooks: () => Promise<{ | ||
handle?: import('@sveltejs/kit').Handle; | ||
handleError?: import('@sveltejs/kit').HandleServerError; | ||
handleFetch?: import('@sveltejs/kit').HandleFetch; | ||
}>; | ||
export const get_hooks: () => Promise<Partial<import('types').ServerHooks>>; | ||
} |
import { parse, serialize } from 'cookie'; | ||
import { normalize_path } from '../../utils/url.js'; | ||
import { add_data_suffix, normalize_path, resolve } from '../../utils/url.js'; | ||
@@ -17,2 +17,10 @@ /** | ||
// TODO 3.0 remove this check | ||
/** @param {import('./page/types.js').Cookie['options']} options */ | ||
function validate_options(options) { | ||
if (options?.path === undefined) { | ||
throw new Error('You must specify a `path` when setting, deleting or serializing cookies'); | ||
} | ||
} | ||
/** | ||
@@ -28,6 +36,4 @@ * @param {Request} request | ||
const normalized_url = normalize_path(url.pathname, trailing_slash); | ||
// Emulate browser-behavior: if the cookie is set at '/foo/bar', its path is '/foo' | ||
const default_path = normalized_url.split('/').slice(0, -1).join('/') || '/'; | ||
/** @type {Record<string, import('./page/types').Cookie>} */ | ||
/** @type {Record<string, import('./page/types.js').Cookie>} */ | ||
const new_cookies = {}; | ||
@@ -109,6 +115,7 @@ | ||
* @param {string} value | ||
* @param {import('cookie').CookieSerializeOptions} opts | ||
* @param {import('./page/types.js').Cookie['options']} options | ||
*/ | ||
set(name, value, opts = {}) { | ||
set_internal(name, value, { ...defaults, ...opts }); | ||
set(name, value, options) { | ||
validate_options(options); | ||
set_internal(name, value, { ...defaults, ...options }); | ||
}, | ||
@@ -118,9 +125,7 @@ | ||
* @param {string} name | ||
* @param {import('cookie').CookieSerializeOptions} opts | ||
* @param {import('./page/types.js').Cookie['options']} options | ||
*/ | ||
delete(name, opts = {}) { | ||
cookies.set(name, '', { | ||
...opts, | ||
maxAge: 0 | ||
}); | ||
delete(name, options) { | ||
validate_options(options); | ||
cookies.set(name, '', { ...options, maxAge: 0 }); | ||
}, | ||
@@ -131,9 +136,14 @@ | ||
* @param {string} value | ||
* @param {import('cookie').CookieSerializeOptions} opts | ||
* @param {import('./page/types.js').Cookie['options']} options | ||
*/ | ||
serialize(name, value, opts) { | ||
return serialize(name, value, { | ||
...defaults, | ||
...opts | ||
}); | ||
serialize(name, value, options) { | ||
validate_options(options); | ||
let path = options.path; | ||
if (!options.domain || options.domain === url.hostname) { | ||
path = resolve(normalized_url, path); | ||
} | ||
return serialize(name, value, { ...defaults, ...options, path }); | ||
} | ||
@@ -179,16 +189,13 @@ }; | ||
* @param {string} value | ||
* @param {import('cookie').CookieSerializeOptions} opts | ||
* @param {import('./page/types.js').Cookie['options']} options | ||
*/ | ||
function set_internal(name, value, opts) { | ||
const path = opts.path ?? default_path; | ||
function set_internal(name, value, options) { | ||
let path = options.path; | ||
new_cookies[name] = { | ||
name, | ||
value, | ||
options: { | ||
...opts, | ||
path | ||
} | ||
}; | ||
if (!options.domain || options.domain === url.hostname) { | ||
path = resolve(normalized_url, path); | ||
} | ||
new_cookies[name] = { name, value, options: { ...options, path } }; | ||
if (__SVELTEKIT_DEV__) { | ||
@@ -241,3 +248,3 @@ const serialized = serialize(name, value, new_cookies[name].options); | ||
* @param {Headers} headers | ||
* @param {import('./page/types').Cookie[]} cookies | ||
* @param {import('./page/types.js').Cookie[]} cookies | ||
*/ | ||
@@ -248,2 +255,10 @@ export function add_cookies_to_headers(headers, cookies) { | ||
headers.append('set-cookie', serialize(name, value, options)); | ||
// special case — for routes ending with .html, the route data lives in a sibling | ||
// `.html__data.json` file rather than a child `/__data.json` file, which means | ||
// we need to duplicate the cookie | ||
if (options.path.endsWith('.html')) { | ||
const path = add_data_suffix(options.path); | ||
headers.append('set-cookie', serialize(name, value, { ...options, path })); | ||
} | ||
} | ||
@@ -250,0 +265,0 @@ } |
@@ -1,2 +0,2 @@ | ||
import { HttpError, Redirect } from '../../control.js'; | ||
import { HttpError, SvelteKitError, Redirect } from '../../control.js'; | ||
import { normalize_error } from '../../../utils/error.js'; | ||
@@ -79,4 +79,3 @@ import { once } from '../../../utils/functions.js'; | ||
return data; | ||
}, | ||
track_server_fetches: options.track_server_fetches | ||
} | ||
}); | ||
@@ -114,3 +113,6 @@ } catch (e) { | ||
error: await handle_error_and_jsonify(event, options, error), | ||
status: error instanceof HttpError ? error.status : undefined | ||
status: | ||
error instanceof HttpError || error instanceof SvelteKitError | ||
? error.status | ||
: undefined | ||
}); | ||
@@ -117,0 +119,0 @@ }) |
@@ -84,3 +84,3 @@ import { ENDPOINT_METHODS, PAGE_METHODS } from '../../constants.js'; | ||
// These methods exist exclusively for endpoints | ||
if (ENDPOINT_METHODS.has(method) && !PAGE_METHODS.has(method)) { | ||
if (ENDPOINT_METHODS.includes(method) && !PAGE_METHODS.includes(method)) { | ||
return true; | ||
@@ -87,0 +87,0 @@ } |
@@ -12,3 +12,3 @@ import * as set_cookie_parser from 'set-cookie-parser'; | ||
* get_cookie_header: (url: URL, header: string | null) => string; | ||
* set_internal: (name: string, value: string, opts: import('cookie').CookieSerializeOptions) => void; | ||
* set_internal: (name: string, value: string, opts: import('./page/types.js').Cookie['options']) => void; | ||
* }} opts | ||
@@ -18,3 +18,6 @@ * @returns {typeof fetch} | ||
export function create_fetch({ event, options, manifest, state, get_cookie_header, set_internal }) { | ||
return async (info, init) => { | ||
/** | ||
* @type {typeof fetch} | ||
*/ | ||
const server_fetch = async (info, init) => { | ||
const original_request = normalize_fetch_input(info, init, event.url); | ||
@@ -28,3 +31,3 @@ | ||
return await options.hooks.handleFetch({ | ||
return options.hooks.handleFetch({ | ||
event, | ||
@@ -137,8 +140,9 @@ request: original_request, | ||
const path = options.path ?? (url.pathname.split('/').slice(0, -1).join('/') || '/'); | ||
// options.sameSite is string, something more specific is required - type cast is safe | ||
set_internal( | ||
name, | ||
value, | ||
/** @type {import('cookie').CookieSerializeOptions} */ (options) | ||
); | ||
set_internal(name, value, { | ||
path, | ||
.../** @type {import('cookie').CookieSerializeOptions} */ (options) | ||
}); | ||
} | ||
@@ -151,2 +155,11 @@ } | ||
}; | ||
// Don't make this function `async`! Otherwise, the user has to `catch` promises they use for streaming responses or else | ||
// it will be an unhandled rejection. Instead, we add a `.catch(() => {})` ourselves below to this from happening. | ||
return (input, init) => { | ||
// See docs in fetch.js for why we need to do this | ||
const response = server_fetch(input, init); | ||
response.catch(() => {}); | ||
return response; | ||
}; | ||
} | ||
@@ -153,0 +166,0 @@ |
import { respond } from './respond.js'; | ||
import { set_private_env, set_public_env } from '../shared-server.js'; | ||
import { set_private_env, set_public_env, set_safe_public_env } from '../shared-server.js'; | ||
import { options, get_hooks } from '__SERVER__/internal.js'; | ||
import { DEV } from 'esm-env'; | ||
import { filter_private_env, filter_public_env } from '../../utils/env.js'; | ||
import { prerendering } from '__sveltekit/environment'; | ||
/** @type {ProxyHandler<{ type: 'public' | 'private' }>} */ | ||
const prerender_env_handler = { | ||
get({ type }, prop) { | ||
throw new Error( | ||
`Cannot read values from $env/dynamic/${type} while prerendering (attempted to read env.${prop.toString()}). Use $env/static/${type} instead` | ||
); | ||
} | ||
}; | ||
export class Server { | ||
@@ -30,15 +40,19 @@ /** @type {import('types').SSROptions} */ | ||
// been done already. | ||
// set env, in case it's used in initialisation | ||
const prefixes = { | ||
public_prefix: this.#options.env_public_prefix, | ||
private_prefix: this.#options.env_private_prefix | ||
}; | ||
const private_env = filter_private_env(env, prefixes); | ||
const public_env = filter_public_env(env, prefixes); | ||
set_private_env( | ||
filter_private_env(env, { | ||
public_prefix: this.#options.env_public_prefix, | ||
private_prefix: this.#options.env_private_prefix | ||
}) | ||
prerendering ? new Proxy({ type: 'private' }, prerender_env_handler) : private_env | ||
); | ||
set_public_env( | ||
filter_public_env(env, { | ||
public_prefix: this.#options.env_public_prefix, | ||
private_prefix: this.#options.env_private_prefix | ||
}) | ||
prerendering ? new Proxy({ type: 'public' }, prerender_env_handler) : public_env | ||
); | ||
set_safe_public_env(public_env); | ||
@@ -52,3 +66,4 @@ if (!this.#options.hooks) { | ||
handleError: module.handleError || (({ error }) => console.error(error)), | ||
handleFetch: module.handleFetch || (({ request, fetch }) => fetch(request)) | ||
handleFetch: module.handleFetch || (({ request, fetch }) => fetch(request)), | ||
reroute: module.reroute || (() => {}) | ||
}; | ||
@@ -62,3 +77,4 @@ } catch (error) { | ||
handleError: ({ error }) => console.error(error), | ||
handleFetch: ({ request, fetch }) => fetch(request) | ||
handleFetch: ({ request, fetch }) => fetch(request), | ||
reroute: () => {} | ||
}; | ||
@@ -77,9 +93,2 @@ } else { | ||
async respond(request, options) { | ||
// TODO this should probably have been removed for 1.0 — i think we can get rid of it? | ||
if (!(request instanceof Request)) { | ||
throw new Error( | ||
'The first argument to server.respond must be a Request object. See https://github.com/sveltejs/kit/pull/3384 for details' | ||
); | ||
} | ||
return respond(request, this.#options, this.#manifest, { | ||
@@ -86,0 +95,0 @@ ...options, |
import * as devalue from 'devalue'; | ||
import { error, json } from '../../../exports/index.js'; | ||
import { normalize_error } from '../../../utils/error.js'; | ||
import { 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 } from '../../control.js'; | ||
import { HttpError, Redirect, ActionFailure, SvelteKitError } from '../../control.js'; | ||
import { handle_error_and_jsonify } from '../utils.js'; | ||
@@ -27,4 +27,7 @@ | ||
if (!actions) { | ||
// TODO should this be a different error altogether? | ||
const no_actions_error = error(405, 'POST method not allowed. No actions exist for this page'); | ||
const no_actions_error = new SvelteKitError( | ||
405, | ||
'Method Not Allowed', | ||
'POST method not allowed. No actions exist for this page' | ||
); | ||
return action_json( | ||
@@ -85,3 +88,3 @@ { | ||
{ | ||
status: err instanceof HttpError ? err.status : 500 | ||
status: get_status(err) | ||
} | ||
@@ -144,3 +147,7 @@ ); | ||
type: 'error', | ||
error: error(405, 'POST method not allowed. No actions exist for this page') | ||
error: new SvelteKitError( | ||
405, | ||
'Method Not Allowed', | ||
'POST method not allowed. No actions exist for this page' | ||
) | ||
}; | ||
@@ -204,3 +211,3 @@ } | ||
* @param {NonNullable<import('types').SSRNode['server']['actions']>} actions | ||
* @throws {Redirect | ActionFailure | HttpError | Error} | ||
* @throws {Redirect | HttpError | SvelteKitError | Error} | ||
*/ | ||
@@ -223,8 +230,12 @@ async function call_action(event, actions) { | ||
if (!action) { | ||
throw new Error(`No action with name '${name}' found`); | ||
throw new SvelteKitError(404, 'Not Found', `No action with name '${name}' found`); | ||
} | ||
if (!is_form_content_type(event.request)) { | ||
throw new Error( | ||
`Actions expect form-encoded data (received ${event.request.headers.get('content-type')})` | ||
throw new SvelteKitError( | ||
415, | ||
'Unsupported Media Type', | ||
`Form actions expect form-encoded data — received ${event.request.headers.get( | ||
'content-type' | ||
)}` | ||
); | ||
@@ -239,9 +250,7 @@ } | ||
if (data instanceof Redirect) { | ||
throw new Error('Cannot `return redirect(...)` — use `throw redirect(...)` instead'); | ||
throw new Error('Cannot `return redirect(...)` — use `redirect(...)` instead'); | ||
} | ||
if (data instanceof HttpError) { | ||
throw new Error( | ||
'Cannot `return error(...)` — use `throw error(...)` or `return fail(...)` instead' | ||
); | ||
throw new Error('Cannot `return error(...)` — use `error(...)` or `return fail(...)` instead'); | ||
} | ||
@@ -248,0 +257,0 @@ } |
@@ -44,4 +44,13 @@ import { escape_html_attr } from '../../../utils/escape.js'; | ||
/** @type {import('types').Csp.Source[]} */ | ||
#script_src_elem; | ||
/** @type {import('types').Csp.Source[]} */ | ||
#style_src; | ||
/** @type {import('types').Csp.Source[]} */ | ||
#style_src_attr; | ||
/** @type {import('types').Csp.Source[]} */ | ||
#style_src_elem; | ||
/** @type {string} */ | ||
@@ -61,2 +70,14 @@ #nonce; | ||
this.#script_src = []; | ||
this.#script_src_elem = []; | ||
this.#style_src = []; | ||
this.#style_src_attr = []; | ||
this.#style_src_elem = []; | ||
const effective_script_src = d['script-src'] || d['default-src']; | ||
const script_src_elem = d['script-src-elem']; | ||
const effective_style_src = d['style-src'] || d['default-src']; | ||
const style_src_attr = d['style-src-attr']; | ||
const style_src_elem = d['style-src-elem']; | ||
if (__SVELTEKIT_DEV__) { | ||
@@ -75,24 +96,46 @@ // remove strict-dynamic in dev... | ||
const effective_style_src = d['style-src'] || d['default-src']; | ||
// ...and add unsafe-inline so we can inject <style> elements | ||
// Note that 'unsafe-inline' is ignored if either a hash or nonce value is present in the source list, so we remove those during dev when injecting unsafe-inline | ||
if (effective_style_src && !effective_style_src.includes('unsafe-inline')) { | ||
d['style-src'] = [...effective_style_src, 'unsafe-inline']; | ||
d['style-src'] = [ | ||
...effective_style_src.filter( | ||
(value) => !(value.startsWith('sha256-') || value.startsWith('nonce-')) | ||
), | ||
'unsafe-inline' | ||
]; | ||
} | ||
} | ||
this.#script_src = []; | ||
this.#style_src = []; | ||
if (style_src_attr && !style_src_attr.includes('unsafe-inline')) { | ||
d['style-src-attr'] = [ | ||
...style_src_attr.filter( | ||
(value) => !(value.startsWith('sha256-') || value.startsWith('nonce-')) | ||
), | ||
'unsafe-inline' | ||
]; | ||
} | ||
const effective_script_src = d['script-src'] || d['default-src']; | ||
const effective_style_src = d['style-src'] || d['default-src']; | ||
if (style_src_elem && !style_src_elem.includes('unsafe-inline')) { | ||
d['style-src-elem'] = [ | ||
...style_src_elem.filter( | ||
(value) => !(value.startsWith('sha256-') || value.startsWith('nonce-')) | ||
), | ||
'unsafe-inline' | ||
]; | ||
} | ||
} | ||
this.#script_needs_csp = | ||
!!effective_script_src && | ||
effective_script_src.filter((value) => value !== 'unsafe-inline').length > 0; | ||
(!!effective_script_src && | ||
effective_script_src.filter((value) => value !== 'unsafe-inline').length > 0) || | ||
(!!script_src_elem && | ||
script_src_elem.filter((value) => value !== 'unsafe-inline').length > 0); | ||
this.#style_needs_csp = | ||
!__SVELTEKIT_DEV__ && | ||
!!effective_style_src && | ||
effective_style_src.filter((value) => value !== 'unsafe-inline').length > 0; | ||
((!!effective_style_src && | ||
effective_style_src.filter((value) => value !== 'unsafe-inline').length > 0) || | ||
(!!style_src_attr && | ||
style_src_attr.filter((value) => value !== 'unsafe-inline').length > 0) || | ||
(!!style_src_elem && | ||
style_src_elem.filter((value) => value !== 'unsafe-inline').length > 0)); | ||
@@ -107,6 +150,19 @@ this.script_needs_nonce = this.#script_needs_csp && !this.#use_hashes; | ||
if (this.#script_needs_csp) { | ||
const d = this.#directives; | ||
if (this.#use_hashes) { | ||
this.#script_src.push(`sha256-${sha256(content)}`); | ||
} else if (this.#script_src.length === 0) { | ||
this.#script_src.push(`nonce-${this.#nonce}`); | ||
const hash = sha256(content); | ||
this.#script_src.push(`sha256-${hash}`); | ||
if (d['script-src-elem']?.length) { | ||
this.#script_src_elem.push(`sha256-${hash}`); | ||
} | ||
} else { | ||
if (this.#script_src.length === 0) { | ||
this.#script_src.push(`nonce-${this.#nonce}`); | ||
} | ||
if (d['script-src-elem']?.length) { | ||
this.#script_src_elem.push(`nonce-${this.#nonce}`); | ||
} | ||
} | ||
@@ -119,6 +175,41 @@ } | ||
if (this.#style_needs_csp) { | ||
// this is the hash for "/* empty */" | ||
// adding it so that svelte does not break csp | ||
// see https://github.com/sveltejs/svelte/pull/7800 | ||
const empty_comment_hash = '9OlNO0DNEeaVzHL4RZwCLsBHA8WBQ8toBp/4F5XV2nc='; | ||
const d = this.#directives; | ||
if (this.#use_hashes) { | ||
this.#style_src.push(`sha256-${sha256(content)}`); | ||
} else if (this.#style_src.length === 0) { | ||
this.#style_src.push(`nonce-${this.#nonce}`); | ||
const hash = sha256(content); | ||
this.#style_src.push(`sha256-${hash}`); | ||
if (d['style-src-attr']?.length) { | ||
this.#style_src_attr.push(`sha256-${hash}`); | ||
} | ||
if (d['style-src-elem']?.length) { | ||
if ( | ||
hash !== empty_comment_hash && | ||
!d['style-src-elem'].includes(`sha256-${empty_comment_hash}`) | ||
) { | ||
this.#style_src_elem.push(`sha256-${empty_comment_hash}`); | ||
} | ||
this.#style_src_elem.push(`sha256-${hash}`); | ||
} | ||
} else { | ||
if (this.#style_src.length === 0 && !d['style-src']?.includes('unsafe-inline')) { | ||
this.#style_src.push(`nonce-${this.#nonce}`); | ||
} | ||
if (d['style-src-attr']?.length) { | ||
this.#style_src_attr.push(`nonce-${this.#nonce}`); | ||
} | ||
if (d['style-src-elem']?.length) { | ||
if (!d['style-src-elem'].includes(`sha256-${empty_comment_hash}`)) { | ||
this.#style_src_elem.push(`sha256-${empty_comment_hash}`); | ||
} | ||
this.#style_src_elem.push(`nonce-${this.#nonce}`); | ||
} | ||
} | ||
@@ -147,2 +238,16 @@ } | ||
if (this.#style_src_attr.length > 0) { | ||
directives['style-src-attr'] = [ | ||
...(directives['style-src-attr'] || []), | ||
...this.#style_src_attr | ||
]; | ||
} | ||
if (this.#style_src_elem.length > 0) { | ||
directives['style-src-elem'] = [ | ||
...(directives['style-src-elem'] || []), | ||
...this.#style_src_elem | ||
]; | ||
} | ||
if (this.#script_src.length > 0) { | ||
@@ -155,2 +260,9 @@ directives['script-src'] = [ | ||
if (this.#script_src_elem.length > 0) { | ||
directives['script-src-elem'] = [ | ||
...(directives['script-src-elem'] || []), | ||
...this.#script_src_elem | ||
]; | ||
} | ||
for (const key in directives) { | ||
@@ -233,4 +345,4 @@ if (is_meta && (key === 'frame-ancestors' || key === 'report-uri' || key === 'sandbox')) { | ||
/** | ||
* @param {import('./types').CspConfig} config | ||
* @param {import('./types').CspOpts} opts | ||
* @param {import('./types.js').CspConfig} config | ||
* @param {import('./types.js').CspOpts} opts | ||
*/ | ||
@@ -237,0 +349,0 @@ constructor({ mode, directives, reportOnly }, { prerender }) { |
import { text } from '../../../exports/index.js'; | ||
import { compact } from '../../../utils/array.js'; | ||
import { normalize_error } from '../../../utils/error.js'; | ||
import { get_status, normalize_error } from '../../../utils/error.js'; | ||
import { add_data_suffix } from '../../../utils/url.js'; | ||
import { HttpError, Redirect } from '../../control.js'; | ||
import { Redirect } from '../../control.js'; | ||
import { redirect_response, static_error_page, handle_error_and_jsonify } from '../utils.js'; | ||
@@ -68,4 +68,3 @@ import { | ||
if (action_result?.type === 'error') { | ||
const error = action_result.error; | ||
status = error instanceof HttpError ? error.status : 500; | ||
status = get_status(action_result.error); | ||
} | ||
@@ -77,3 +76,3 @@ if (action_result?.type === 'failure') { | ||
const should_prerender_data = nodes.some((node) => node?.server); | ||
const should_prerender_data = nodes.some((node) => node?.server?.load); | ||
const data_pathname = add_data_suffix(event.url.pathname); | ||
@@ -83,3 +82,3 @@ | ||
// SvelteKit will erroneously believe that the path has been prerendered, | ||
// causing functions to be omitted from the manifesst generated later | ||
// causing functions to be omitted from the manifest generated later | ||
const should_prerender = get_option(nodes, 'prerender') ?? false; | ||
@@ -102,6 +101,9 @@ if (should_prerender) { | ||
/** @type {import('./types').Fetched[]} */ | ||
/** @type {import('./types.js').Fetched[]} */ | ||
const fetched = []; | ||
if (get_option(nodes, 'ssr') === false) { | ||
// renders an empty 'shell' page if SSR is turned off and if there is | ||
// no server data to prerender. As a result, the load functions and rendering | ||
// only occur client-side. | ||
if (get_option(nodes, 'ssr') === false && !(state.prerendering && should_prerender_data)) { | ||
return await render_response({ | ||
@@ -157,4 +159,3 @@ branch: [], | ||
return data; | ||
}, | ||
track_server_fetches: options.track_server_fetches | ||
} | ||
}); | ||
@@ -230,3 +231,3 @@ } catch (e) { | ||
const status = err instanceof HttpError ? err.status : 500; | ||
const status = get_status(err); | ||
const error = await handle_error_and_jsonify(event, options, err); | ||
@@ -292,2 +293,4 @@ | ||
const ssr = get_option(nodes, 'ssr') ?? true; | ||
return await render_response({ | ||
@@ -301,7 +304,7 @@ event, | ||
csr: get_option(nodes, 'csr') ?? true, | ||
ssr: true | ||
ssr | ||
}, | ||
status, | ||
error: null, | ||
branch: compact(branch), | ||
branch: ssr === false ? [] : compact(branch), | ||
action_result, | ||
@@ -308,0 +311,0 @@ fetched |
@@ -0,4 +1,3 @@ | ||
import { DEV } from 'esm-env'; | ||
import { disable_search, make_trackable } from '../../../utils/url.js'; | ||
import { unwrap_promises } from '../../../utils/promises.js'; | ||
import { DEV } from 'esm-env'; | ||
import { validate_depends } from '../../shared.js'; | ||
@@ -13,17 +12,10 @@ | ||
* parent: () => Promise<Record<string, any>>; | ||
* track_server_fetches: boolean; | ||
* }} opts | ||
* @returns {Promise<import('types').ServerDataNode | null>} | ||
*/ | ||
export async function load_server_data({ | ||
event, | ||
state, | ||
node, | ||
parent, | ||
// TODO 2.0: Remove this | ||
track_server_fetches | ||
}) { | ||
export async function load_server_data({ event, state, node, parent }) { | ||
if (!node?.server) return null; | ||
let done = false; | ||
let is_tracking = true; | ||
@@ -35,15 +27,32 @@ const uses = { | ||
route: false, | ||
url: false | ||
url: false, | ||
search_params: new Set() | ||
}; | ||
const url = make_trackable(event.url, () => { | ||
if (DEV && done && !uses.url) { | ||
console.warn( | ||
`${node.server_id}: Accessing URL properties in a promise handler after \`load(...)\` has returned will not cause the function to re-run when the URL changes` | ||
); | ||
const url = make_trackable( | ||
event.url, | ||
() => { | ||
if (DEV && done && !uses.url) { | ||
console.warn( | ||
`${node.server_id}: Accessing URL properties in a promise handler after \`load(...)\` has returned will not cause the function to re-run when the URL changes` | ||
); | ||
} | ||
if (is_tracking) { | ||
uses.url = true; | ||
} | ||
}, | ||
(param) => { | ||
if (DEV && done && !uses.search_params.has(param)) { | ||
console.warn( | ||
`${node.server_id}: Accessing URL properties in a promise handler after \`load(...)\` has returned will not cause the function to re-run when the URL changes` | ||
); | ||
} | ||
if (is_tracking) { | ||
uses.search_params.add(param); | ||
} | ||
} | ||
); | ||
uses.url = true; | ||
}); | ||
if (state.prerendering) { | ||
@@ -64,7 +73,3 @@ disable_search(url); | ||
// TODO 2.0: Remove this | ||
if (track_server_fetches) { | ||
uses.dependencies.add(url.href); | ||
} | ||
// Note: server fetches are not added to uses.depends due to security concerns | ||
return event.fetch(info, init); | ||
@@ -100,3 +105,5 @@ }, | ||
uses.params.add(key); | ||
if (is_tracking) { | ||
uses.params.add(key); | ||
} | ||
return target[/** @type {string} */ (key)]; | ||
@@ -112,3 +119,5 @@ } | ||
uses.parent = true; | ||
if (is_tracking) { | ||
uses.parent = true; | ||
} | ||
return parent(); | ||
@@ -126,12 +135,21 @@ }, | ||
uses.route = true; | ||
if (is_tracking) { | ||
uses.route = true; | ||
} | ||
return target[/** @type {'id'} */ (key)]; | ||
} | ||
}), | ||
url | ||
url, | ||
untrack(fn) { | ||
is_tracking = false; | ||
try { | ||
return fn(); | ||
} finally { | ||
is_tracking = true; | ||
} | ||
} | ||
}); | ||
const data = result ? await unwrap_promises(result) : null; | ||
if (__SVELTEKIT_DEV__) { | ||
validate_load_response(data, /** @type {string} */ (event.route.id)); | ||
validate_load_response(result, node.server_id); | ||
} | ||
@@ -143,3 +161,3 @@ | ||
type: 'data', | ||
data, | ||
data: result ?? null, | ||
uses, | ||
@@ -154,3 +172,3 @@ slash: node.server.trailingSlash | ||
* event: import('@sveltejs/kit').RequestEvent; | ||
* fetched: import('./types').Fetched[]; | ||
* fetched: import('./types.js').Fetched[]; | ||
* node: import('types').SSRNode | undefined; | ||
@@ -189,19 +207,39 @@ * parent: () => Promise<Record<string, any>>; | ||
depends: () => {}, | ||
parent | ||
parent, | ||
untrack: (fn) => fn() | ||
}); | ||
const data = result ? await unwrap_promises(result) : null; | ||
if (__SVELTEKIT_DEV__) { | ||
validate_load_response(data, /** @type {string} */ (event.route.id)); | ||
validate_load_response(result, node.universal_id); | ||
} | ||
return data; | ||
return result ?? null; | ||
} | ||
/** | ||
* @param {ArrayBuffer} buffer | ||
* @returns {string} | ||
*/ | ||
function b64_encode(buffer) { | ||
if (globalThis.Buffer) { | ||
return Buffer.from(buffer).toString('base64'); | ||
} | ||
const little_endian = new Uint8Array(new Uint16Array([1]).buffer)[0] > 0; | ||
// The Uint16Array(Uint8Array(...)) ensures the code points are padded with 0's | ||
return btoa( | ||
new TextDecoder(little_endian ? 'utf-16le' : 'utf-16be').decode( | ||
new Uint16Array(new Uint8Array(buffer)) | ||
) | ||
); | ||
} | ||
/** | ||
* @param {Pick<import('@sveltejs/kit').RequestEvent, 'fetch' | 'url' | 'request' | 'route'>} event | ||
* @param {import('types').SSRState} state | ||
* @param {import('./types').Fetched[]} fetched | ||
* @param {import('./types.js').Fetched[]} fetched | ||
* @param {boolean} csr | ||
* @param {Pick<Required<import('@sveltejs/kit').ResolveOptions>, 'filterSerializedResponseHeaders'>} resolve_opts | ||
* @returns {typeof fetch} | ||
*/ | ||
@@ -213,3 +251,3 @@ export function create_universal_fetch(event, state, fetched, csr, resolve_opts) { | ||
*/ | ||
return async (input, init) => { | ||
const universal_fetch = async (input, init) => { | ||
const cloned_body = input instanceof Request && input.body ? input.clone().body : null; | ||
@@ -258,34 +296,29 @@ | ||
get(response, key, _receiver) { | ||
async function text() { | ||
const body = await response.text(); | ||
if (!body || typeof body === 'string') { | ||
const status_number = Number(response.status); | ||
if (isNaN(status_number)) { | ||
throw new Error( | ||
`response.status is not a number. value: "${ | ||
response.status | ||
}" type: ${typeof response.status}` | ||
); | ||
} | ||
fetched.push({ | ||
url: same_origin ? url.href.slice(event.url.origin.length) : url.href, | ||
method: event.request.method, | ||
request_body: /** @type {string | ArrayBufferView | undefined} */ ( | ||
input instanceof Request && cloned_body | ||
? await stream_to_string(cloned_body) | ||
: init?.body | ||
), | ||
request_headers: cloned_headers, | ||
response_body: body, | ||
response | ||
}); | ||
/** | ||
* @param {string} body | ||
* @param {boolean} is_b64 | ||
*/ | ||
async function push_fetched(body, is_b64) { | ||
const status_number = Number(response.status); | ||
if (isNaN(status_number)) { | ||
throw new Error( | ||
`response.status is not a number. value: "${ | ||
response.status | ||
}" type: ${typeof response.status}` | ||
); | ||
} | ||
if (dependency) { | ||
dependency.body = body; | ||
} | ||
return body; | ||
fetched.push({ | ||
url: same_origin ? url.href.slice(event.url.origin.length) : url.href, | ||
method: event.request.method, | ||
request_body: /** @type {string | ArrayBufferView | undefined} */ ( | ||
input instanceof Request && cloned_body | ||
? await stream_to_string(cloned_body) | ||
: init?.body | ||
), | ||
request_headers: cloned_headers, | ||
response_body: body, | ||
response, | ||
is_b64 | ||
}); | ||
} | ||
@@ -301,4 +334,5 @@ | ||
// TODO should buffer be inlined into the page (albeit base64'd)? | ||
// any conditions in which it shouldn't be? | ||
if (buffer instanceof ArrayBuffer) { | ||
await push_fetched(b64_encode(buffer), true); | ||
} | ||
@@ -309,2 +343,16 @@ return buffer; | ||
async function text() { | ||
const body = await response.text(); | ||
if (!body || typeof body === 'string') { | ||
await push_fetched(body, false); | ||
} | ||
if (dependency) { | ||
dependency.body = body; | ||
} | ||
return body; | ||
} | ||
if (key === 'text') { | ||
@@ -345,2 +393,11 @@ return text; | ||
}; | ||
// Don't make this function `async`! Otherwise, the user has to `catch` promises they use for streaming responses or else | ||
// it will be an unhandled rejection. Instead, we add a `.catch(() => {})` ourselves below to this from happening. | ||
return (input, init) => { | ||
// See docs in fetch.js for why we need to do this | ||
const response = universal_fetch(input, init); | ||
response.catch(() => {}); | ||
return response; | ||
}; | ||
} | ||
@@ -367,15 +424,15 @@ | ||
* @param {any} data | ||
* @param {string} [routeId] | ||
* @param {string} [id] | ||
*/ | ||
function validate_load_response(data, routeId) { | ||
function validate_load_response(data, id) { | ||
if (data != null && Object.getPrototypeOf(data) !== Object.prototype) { | ||
throw new Error( | ||
`a load function related to route '${routeId}' returned ${ | ||
`a load function in ${id} returned ${ | ||
typeof data !== 'object' | ||
? `a ${typeof data}` | ||
: data instanceof Response | ||
? 'a Response object' | ||
: Array.isArray(data) | ||
? 'an array' | ||
: 'a non-plain object' | ||
? 'a Response object' | ||
: Array.isArray(data) | ||
? 'an array' | ||
: 'a non-plain object' | ||
}, but must return a plain object at the top level (i.e. \`return {...}\`)` | ||
@@ -382,0 +439,0 @@ ); |
@@ -11,3 +11,3 @@ import * as devalue from 'devalue'; | ||
import { clarify_devalue_error, stringify_uses, handle_error_and_jsonify } from '../utils.js'; | ||
import { public_env } from '../../shared-server.js'; | ||
import { public_env, safe_public_env } from '../../shared-server.js'; | ||
import { text } from '../../../exports/index.js'; | ||
@@ -30,4 +30,4 @@ import { create_async_iterator } from '../../../utils/streaming.js'; | ||
* @param {{ | ||
* branch: Array<import('./types').Loaded>; | ||
* fetched: Array<import('./types').Fetched>; | ||
* branch: Array<import('./types.js').Loaded>; | ||
* fetched: Array<import('./types.js').Fetched>; | ||
* options: import('types').SSROptions; | ||
@@ -100,3 +100,3 @@ * manifest: import('@sveltejs/kit').SSRManifest; | ||
// if appropriate, use relative paths for greater portability | ||
if (paths.relative !== false && !state.prerendering?.fallback) { | ||
if (paths.relative && !state.prerendering?.fallback) { | ||
const segments = event.url.pathname.slice(paths.base.length).split('/').slice(2); | ||
@@ -147,3 +147,4 @@ | ||
data, | ||
form: form_value | ||
form: form_value, | ||
state: {} | ||
}; | ||
@@ -283,2 +284,6 @@ | ||
if (page_config.csr) { | ||
if (client.uses_env_dynamic_public && state.prerendering) { | ||
modulepreloads.add(`${options.app_dir}/env.js`); | ||
} | ||
const included_modulepreloads = Array.from(modulepreloads, (dep) => prefixed(dep)).filter( | ||
@@ -300,8 +305,18 @@ (path) => resolve_opts.preload({ type: 'js', path }) | ||
const properties = [ | ||
paths.assets && `assets: ${s(paths.assets)}`, | ||
`base: ${base_expression}`, | ||
`env: ${s(public_env)}` | ||
].filter(Boolean); | ||
// when serving a prerendered page in an app that uses $env/dynamic/public, we must | ||
// import the env.js module so that it evaluates before any user code can evaluate. | ||
// TODO revert to using top-level await once https://bugs.webkit.org/show_bug.cgi?id=242740 is fixed | ||
// https://github.com/sveltejs/kit/pull/11601 | ||
const load_env_eagerly = client.uses_env_dynamic_public && state.prerendering; | ||
const properties = [`base: ${base_expression}`]; | ||
if (paths.assets) { | ||
properties.push(`assets: ${s(paths.assets)}`); | ||
} | ||
if (client.uses_env_dynamic_public) { | ||
properties.push(`env: ${load_env_eagerly ? 'null' : s(public_env)}`); | ||
} | ||
if (chunks) { | ||
@@ -323,2 +338,3 @@ blocks.push('const deferred = new Map();'); | ||
// create this before declaring `data`, which may contain references to `${global}` | ||
blocks.push(`${global} = { | ||
@@ -363,6 +379,19 @@ ${properties.join(',\n\t\t\t\t\t\t')} | ||
args.push(`{\n\t\t\t\t\t\t\t${hydrate.join(',\n\t\t\t\t\t\t\t')}\n\t\t\t\t\t\t}`); | ||
const indent = '\t'.repeat(load_env_eagerly ? 7 : 6); | ||
args.push(`{\n${indent}\t${hydrate.join(`,\n${indent}\t`)}\n${indent}}`); | ||
} | ||
blocks.push(`Promise.all([ | ||
if (load_env_eagerly) { | ||
blocks.push(`import(${s(`${base}/${options.app_dir}/env.js`)}).then(({ env }) => { | ||
${global}.env = env; | ||
Promise.all([ | ||
import(${s(prefixed(client.start))}), | ||
import(${s(prefixed(client.app))}) | ||
]).then(([kit, app]) => { | ||
kit.start(${args.join(', ')}); | ||
}); | ||
});`); | ||
} else { | ||
blocks.push(`Promise.all([ | ||
import(${s(prefixed(client.start))}), | ||
@@ -373,2 +402,3 @@ import(${s(prefixed(client.app))}) | ||
});`); | ||
} | ||
@@ -443,3 +473,3 @@ if (options.service_worker) { | ||
nonce: /** @type {string} */ (csp.nonce), | ||
env: public_env | ||
env: safe_public_env | ||
}); | ||
@@ -480,3 +510,3 @@ | ||
headers | ||
}) | ||
}) | ||
: new Response( | ||
@@ -499,3 +529,3 @@ new ReadableStream({ | ||
} | ||
); | ||
); | ||
} | ||
@@ -502,0 +532,0 @@ |
@@ -5,3 +5,4 @@ import { render_response } from './render.js'; | ||
import { get_option } from '../../../utils/options.js'; | ||
import { HttpError, Redirect } from '../../control.js'; | ||
import { Redirect } from '../../control.js'; | ||
import { get_status } from '../../../utils/error.js'; | ||
@@ -37,3 +38,3 @@ /** | ||
/** @type {import('./types').Fetched[]} */ | ||
/** @type {import('./types.js').Fetched[]} */ | ||
const fetched = []; | ||
@@ -54,4 +55,3 @@ | ||
node: default_layout, | ||
parent: async () => ({}), | ||
track_server_fetches: options.track_server_fetches | ||
parent: async () => ({}) | ||
}); | ||
@@ -92,3 +92,3 @@ | ||
ssr, | ||
csr: get_option([default_layout], 'csr') ?? true | ||
csr | ||
}, | ||
@@ -111,3 +111,3 @@ status, | ||
options, | ||
e instanceof HttpError ? e.status : 500, | ||
get_status(e), | ||
(await handle_error_and_jsonify(event, options, e)).message | ||
@@ -114,0 +114,0 @@ ); |
@@ -76,2 +76,6 @@ import { escape_html_attr } from '../../../utils/escape.js'; | ||
if (fetched.is_b64) { | ||
attrs.push('data-b64'); | ||
} | ||
if (fetched.request_headers || fetched.request_body) { | ||
@@ -78,0 +82,0 @@ /** @type {import('types').StrictBody[]} */ |
@@ -11,2 +11,3 @@ import { CookieSerializeOptions } from 'cookie'; | ||
response: Response; | ||
is_b64?: boolean; | ||
} | ||
@@ -35,3 +36,3 @@ | ||
value: string; | ||
options: CookieSerializeOptions; | ||
options: CookieSerializeOptions & { path: string }; | ||
} |
@@ -21,3 +21,3 @@ import { DEV } from 'esm-env'; | ||
import { create_fetch } from './fetch.js'; | ||
import { Redirect } from '../control.js'; | ||
import { HttpError, Redirect, SvelteKitError } from '../control.js'; | ||
import { | ||
@@ -31,5 +31,6 @@ validate_layout_exports, | ||
import { get_option } from '../../utils/options.js'; | ||
import { error, json, text } from '../../exports/index.js'; | ||
import { json, text } from '../../exports/index.js'; | ||
import { action_json_redirect, is_action_json_request } from './page/actions.js'; | ||
import { INVALIDATED_PARAM, TRAILING_SLASH_PARAM } from '../shared.js'; | ||
import { get_public_env } from './env_module.js'; | ||
@@ -72,3 +73,6 @@ /* global __SVELTEKIT_ADAPTER_NAME__ */ | ||
if (forbidden) { | ||
const csrf_error = error(403, `Cross-site ${request.method} form submissions are forbidden`); | ||
const csrf_error = new HttpError( | ||
403, | ||
`Cross-site ${request.method} form submissions are forbidden` | ||
); | ||
if (request.headers.get('accept') === 'application/json') { | ||
@@ -81,5 +85,15 @@ return json(csrf_error.body, { status: csrf_error.status }); | ||
// reroute could alter the given URL, so we pass a copy | ||
let rerouted_path; | ||
try { | ||
rerouted_path = options.hooks.reroute({ url: new URL(url) }) ?? url.pathname; | ||
} catch (e) { | ||
return text('Internal Server Error', { | ||
status: 500 | ||
}); | ||
} | ||
let decoded; | ||
try { | ||
decoded = decode_pathname(url.pathname); | ||
decoded = decode_pathname(rerouted_path); | ||
} catch { | ||
@@ -102,2 +116,10 @@ return text('Malformed URI', { status: 400 }); | ||
if (decoded === `/${options.app_dir}/env.js`) { | ||
return get_public_env(request); | ||
} | ||
if (decoded.startsWith(`/${options.app_dir}`)) { | ||
return text('Not found', { status: 404 }); | ||
} | ||
const is_data_request = has_data_suffix(decoded); | ||
@@ -142,3 +164,3 @@ /** @type {boolean[] | undefined} */ | ||
/** @type {Record<string, import('./page/types').Cookie>} */ | ||
/** @type {Record<string, import('./page/types.js').Cookie>} */ | ||
let cookies_to_add = {}; | ||
@@ -350,4 +372,4 @@ | ||
: route?.page && is_action_json_request(event) | ||
? action_json_redirect(e) | ||
: redirect_response(e.status, e.location); | ||
? action_json_redirect(e) | ||
: redirect_response(e.status, e.location); | ||
add_cookies_to_headers(response.headers, Object.values(cookies_to_add)); | ||
@@ -366,8 +388,2 @@ return response; | ||
if (opts) { | ||
if ('ssr' in opts) { | ||
throw new Error( | ||
'ssr has been removed, set it in the appropriate +layout.js instead. See the PR for more information: https://github.com/sveltejs/kit/pull/6197' | ||
); | ||
} | ||
resolve_opts = { | ||
@@ -491,3 +507,3 @@ transformPageChunk: opts.transformPageChunk || default_transform, | ||
status: 404, | ||
error: new Error(`Not found: ${event.url.pathname}`), | ||
error: new SvelteKitError(404, 'Not Found', `Not found: ${event.url.pathname}`), | ||
resolve_opts | ||
@@ -494,0 +510,0 @@ }); |
import { DEV } from 'esm-env'; | ||
import { json, text } from '../../exports/index.js'; | ||
import { coalesce_to_error } from '../../utils/error.js'; | ||
import { coalesce_to_error, get_message, get_status } from '../../utils/error.js'; | ||
import { negotiate } from '../../utils/http.js'; | ||
@@ -38,3 +38,3 @@ import { HttpError } from '../control.js'; | ||
export function allowed_methods(mod) { | ||
const allowed = Array.from(ENDPOINT_METHODS).filter((method) => method in mod); | ||
const allowed = ENDPOINT_METHODS.filter((method) => method in mod); | ||
@@ -74,3 +74,3 @@ if ('GET' in mod || 'HEAD' in mod) allowed.push('HEAD'); | ||
error = error instanceof HttpError ? error : coalesce_to_error(error); | ||
const status = error instanceof HttpError ? error.status : 500; | ||
const status = get_status(error); | ||
const body = await handle_error_and_jsonify(event, options, error); | ||
@@ -102,13 +102,12 @@ | ||
return error.body; | ||
} else { | ||
if (__SVELTEKIT_DEV__ && typeof error == 'object') { | ||
fix_stack_trace(error); | ||
} | ||
} | ||
return ( | ||
(await options.hooks.handleError({ error, event })) ?? { | ||
message: event.route.id != null ? 'Internal Error' : 'Not Found' | ||
} | ||
); | ||
if (__SVELTEKIT_DEV__ && typeof error == 'object') { | ||
fix_stack_trace(error); | ||
} | ||
const status = get_status(error); | ||
const message = get_message(error); | ||
return (await options.hooks.handleError({ error, event, status, message })) ?? { message }; | ||
} | ||
@@ -155,2 +154,6 @@ | ||
if (node.uses && node.uses.search_params.size > 0) { | ||
uses.push(`"search_params":${JSON.stringify(Array.from(node.uses.search_params))}`); | ||
} | ||
if (node.uses && node.uses.params.size > 0) { | ||
@@ -166,1 +169,15 @@ uses.push(`"params":${JSON.stringify(Array.from(node.uses.params))}`); | ||
} | ||
/** | ||
* @param {string} message | ||
* @param {number} offset | ||
*/ | ||
export function warn_with_callsite(message, offset = 0) { | ||
if (DEV) { | ||
const stack = fix_stack_trace(new Error()).split('\n'); | ||
const line = stack.at(3 + offset); | ||
message += `\n${line}`; | ||
} | ||
console.warn(message); | ||
} |
@@ -1,7 +0,19 @@ | ||
/** @type {Record<string, string>} */ | ||
/** | ||
* `$env/dynamic/private` | ||
* @type {Record<string, string>} | ||
*/ | ||
export let private_env = {}; | ||
/** @type {Record<string, string>} */ | ||
/** | ||
* `$env/dynamic/public`. When prerendering, this will be a proxy that forbids reads | ||
* @type {Record<string, string>} | ||
*/ | ||
export let public_env = {}; | ||
/** | ||
* The same as `public_env`, but without the proxy. Use for `%sveltekit.env.PUBLIC_FOO%` | ||
* @type {Record<string, string>} | ||
*/ | ||
export let safe_public_env = {}; | ||
/** @param {any} error */ | ||
@@ -20,2 +32,7 @@ export let fix_stack_trace = (error) => error?.stack; | ||
/** @type {(environment: Record<string, string>) => void} */ | ||
export function set_safe_public_env(environment) { | ||
safe_public_env = environment; | ||
} | ||
/** @param {(error: Error) => string} value */ | ||
@@ -22,0 +39,0 @@ export function set_fix_stack_trace(value) { |
@@ -1,11 +0,18 @@ | ||
declare global { | ||
const __SVELTEKIT_ADAPTER_NAME__: string; | ||
const __SVELTEKIT_APP_VERSION_FILE__: string; | ||
const __SVELTEKIT_APP_VERSION_POLL_INTERVAL__: number; | ||
const __SVELTEKIT_DEV__: boolean; | ||
const __SVELTEKIT_EMBEDDED__: boolean; | ||
var Bun: object; | ||
var Deno: object; | ||
/** Internal version of $app/environment */ | ||
declare module '__sveltekit/environment' { | ||
export const building: boolean; | ||
export const prerendering: boolean; | ||
export const version: string; | ||
export function set_building(): void; | ||
export function set_prerendering(): void; | ||
} | ||
export {}; | ||
/** Internal version of $app/paths */ | ||
declare module '__sveltekit/paths' { | ||
export let base: '' | `/${string}`; | ||
export let assets: '' | `https://${string}` | `http://${string}` | '/_svelte_kit_assets'; | ||
export let relative: boolean; | ||
export function reset(): void; | ||
export function override(paths: { base: string; assets: string }): void; | ||
export function set_assets(path: string): void; | ||
} |
@@ -10,2 +10,3 @@ /** | ||
* // interface PageData {} | ||
* // interface PageState {} | ||
* // interface Platform {} | ||
@@ -44,2 +45,7 @@ * } | ||
/** | ||
* The shape of the `$page.state` object, which can be manipulated using the [`pushState`](https://kit.svelte.dev/docs/modules#$app-navigation-pushstate) and [`replaceState`](https://kit.svelte.dev/docs/modules#$app-navigation-replacestate) functions from `$app/navigation`. | ||
*/ | ||
export interface PageState {} | ||
/** | ||
* If your adapter provides [platform-specific context](https://kit.svelte.dev/docs/adapters#platform-specific-context) via `event.platform`, you can specify it here. | ||
@@ -78,34 +84,1 @@ */ | ||
} | ||
/** Internal version of $app/environment */ | ||
declare module '__sveltekit/environment' { | ||
/** | ||
* SvelteKit analyses your app during the `build` step by running it. During this process, `building` is `true`. This also applies during prerendering. | ||
*/ | ||
export const building: boolean; | ||
/** | ||
* The value of `config.kit.version.name`. | ||
*/ | ||
export const version: string; | ||
export function set_building(): void; | ||
} | ||
/** Internal version of $app/paths */ | ||
declare module '__sveltekit/paths' { | ||
/** | ||
* A string that matches [`config.kit.paths.base`](https://kit.svelte.dev/docs/configuration#paths). | ||
* | ||
* Example usage: `<a href="{base}/your-page">Link</a>` | ||
*/ | ||
export let base: '' | `/${string}`; | ||
/** | ||
* An absolute path that matches [`config.kit.paths.assets`](https://kit.svelte.dev/docs/configuration#paths). | ||
* | ||
* > If a value for `config.kit.paths.assets` is specified, it will be replaced with `'/_svelte_kit_assets'` during `vite dev` or `vite preview`, since the assets don't yet live at their eventual URL. | ||
*/ | ||
export let assets: '' | `https://${string}` | `http://${string}` | '/_svelte_kit_assets'; | ||
export let relative: boolean | undefined; // TODO in 2.0, make this a `boolean` that defaults to `true` | ||
export function reset(): void; | ||
export function override(paths: { base: string; assets: string }): void; | ||
export function set_assets(path: string): void; | ||
} |
@@ -15,3 +15,4 @@ import { SvelteComponent } from 'svelte'; | ||
Actions, | ||
HandleClientError | ||
HandleClientError, | ||
Reroute | ||
} from '@sveltejs/kit'; | ||
@@ -31,6 +32,8 @@ import { | ||
export interface ServerInternalModule { | ||
set_building(building: boolean): void; | ||
set_assets(path: string): void; | ||
set_building(): void; | ||
set_prerendering(): void; | ||
set_private_env(environment: Record<string, string>): void; | ||
set_public_env(environment: Record<string, string>): void; | ||
set_safe_public_env(environment: Record<string, string>): void; | ||
set_version(version: string): void; | ||
@@ -64,2 +67,3 @@ set_fix_stack_trace(fix_stack_trace: (error: unknown) => string): void; | ||
fonts: string[]; | ||
uses_env_dynamic_public: boolean; | ||
} | null; | ||
@@ -102,2 +106,3 @@ server_manifest: import('vite').Manifest; | ||
handleError: HandleServerError; | ||
reroute: Reroute; | ||
} | ||
@@ -107,2 +112,3 @@ | ||
handleError: HandleClientError; | ||
reroute: Reroute; | ||
} | ||
@@ -338,6 +344,6 @@ | ||
export interface SSROptions { | ||
app_dir: string; | ||
app_template_contains_nonce: boolean; | ||
csp: ValidatedConfig['kit']['csp']; | ||
csrf_check_origin: boolean; | ||
track_server_fetches: boolean; | ||
embedded: boolean; | ||
@@ -417,2 +423,3 @@ env_public_prefix: string; | ||
url: boolean; | ||
search_params: Set<string>; | ||
} | ||
@@ -424,3 +431,3 @@ | ||
export * from '../exports/index'; | ||
export * from '../exports/index.js'; | ||
export * from './private.js'; |
@@ -1,5 +0,7 @@ | ||
This module provides access to runtime environment variables, as defined by the platform you're running on. For example if you're using [`adapter-node`](https://github.com/sveltejs/kit/tree/master/packages/adapter-node) (or running [`vite preview`](https://kit.svelte.dev/docs/cli)), this is equivalent to `process.env`. This module only includes variables that _do not_ begin with [`config.kit.env.publicPrefix`](https://kit.svelte.dev/docs/configuration#env) _and do_ start with [`config.kit.env.privatePrefix`](https://kit.svelte.dev/docs/configuration#env) (if configured). | ||
This module provides access to runtime environment variables, as defined by the platform you're running on. For example if you're using [`adapter-node`](https://github.com/sveltejs/kit/tree/main/packages/adapter-node) (or running [`vite preview`](https://kit.svelte.dev/docs/cli)), this is equivalent to `process.env`. This module only includes variables that _do not_ begin with [`config.kit.env.publicPrefix`](https://kit.svelte.dev/docs/configuration#env) _and do_ start with [`config.kit.env.privatePrefix`](https://kit.svelte.dev/docs/configuration#env) (if configured). | ||
This module cannot be imported into client-side code. | ||
Dynamic environment variables cannot be used during prerendering. | ||
```ts | ||
@@ -6,0 +8,0 @@ import { env } from '$env/dynamic/private'; |
@@ -5,2 +5,4 @@ Similar to [`$env/dynamic/private`](https://kit.svelte.dev/docs/modules#$env-dynamic-private), but only includes variables that begin with [`config.kit.env.publicPrefix`](https://kit.svelte.dev/docs/configuration#env) (which defaults to `PUBLIC_`), and can therefore safely be exposed to client-side code. | ||
Dynamic environment variables cannot be used during prerendering. | ||
```ts | ||
@@ -7,0 +9,0 @@ import { env } from '$env/dynamic/public'; |
@@ -0,1 +1,3 @@ | ||
import { HttpError, SvelteKitError } from '../runtime/control.js'; | ||
/** | ||
@@ -19,5 +21,19 @@ * @param {unknown} err | ||
export function normalize_error(error) { | ||
return /** @type {import('../runtime/control.js').Redirect | import('../runtime/control.js').HttpError | Error} */ ( | ||
return /** @type {import('../runtime/control.js').Redirect | HttpError | SvelteKitError | Error} */ ( | ||
error | ||
); | ||
} | ||
/** | ||
* @param {unknown} error | ||
*/ | ||
export function get_status(error) { | ||
return error instanceof HttpError || error instanceof SvelteKitError ? error.status : 500; | ||
} | ||
/** | ||
* @param {unknown} error | ||
*/ | ||
export function get_message(error) { | ||
return error instanceof SvelteKitError ? error.text : 'Internal Error'; | ||
} |
@@ -35,3 +35,3 @@ import { fileURLToPath } from 'node:url'; | ||
*/ | ||
const fn = function (opts) { | ||
return function (opts) { | ||
return new Promise((fulfil, reject) => { | ||
@@ -57,3 +57,3 @@ const worker = new Worker(fileURLToPath(module), { | ||
if (data?.type === 'result' && data.module === module) { | ||
worker.terminate(); | ||
worker.unref(); | ||
fulfil(data.payload); | ||
@@ -71,4 +71,2 @@ } | ||
}; | ||
return fn; | ||
} |
@@ -0,1 +1,3 @@ | ||
import { BROWSER } from 'esm-env'; | ||
const param_pattern = /^(\[)?(\.\.\.)?(\w+)(?:=(\w+))?(\])?$/; | ||
@@ -65,4 +67,7 @@ | ||
const match = param_pattern.exec(content); | ||
if (!match) { | ||
// We know the match cannot be null in the browser because manifest generation | ||
// would have invoked this during build and failed if we hit an invalid | ||
// param/matcher name with non-alphanumeric character. | ||
const match = /** @type {RegExpExecArray} */ (param_pattern.exec(content)); | ||
if (!BROWSER && !match) { | ||
throw new Error( | ||
@@ -95,3 +100,3 @@ `Invalid param: ${content}. Params and matcher names can only have underscores and alphanumeric characters.` | ||
.join('')}/?$` | ||
); | ||
); | ||
@@ -219,1 +224,47 @@ return { pattern, params }; | ||
} | ||
const basic_param_pattern = /\[(\[)?(\.\.\.)?(\w+?)(?:=(\w+))?\]\]?/g; | ||
/** | ||
* Populate a route ID with params to resolve a pathname. | ||
* @example | ||
* ```js | ||
* resolveRoute( | ||
* `/blog/[slug]/[...somethingElse]`, | ||
* { | ||
* slug: 'hello-world', | ||
* somethingElse: 'something/else' | ||
* } | ||
* ); // `/blog/hello-world/something/else` | ||
* ``` | ||
* @param {string} id | ||
* @param {Record<string, string | undefined>} params | ||
* @returns {string} | ||
*/ | ||
export function resolve_route(id, params) { | ||
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('/') | ||
); | ||
} |
@@ -1,2 +0,2 @@ | ||
import { BROWSER } from 'esm-env'; | ||
import { BROWSER, DEV } from 'esm-env'; | ||
@@ -9,3 +9,3 @@ /** | ||
const absolute = /^([a-z]+:)?\/?\//; | ||
const internal = new URL('sveltekit-internal://'); | ||
@@ -17,27 +17,9 @@ /** | ||
export function resolve(base, path) { | ||
if (SCHEME.test(path)) return path; | ||
if (path[0] === '#') return base + path; | ||
// special case | ||
if (path[0] === '/' && path[1] === '/') return path; | ||
const base_match = absolute.exec(base); | ||
const path_match = absolute.exec(path); | ||
let url = new URL(base, internal); | ||
url = new URL(path, url); | ||
if (!base_match) { | ||
throw new Error(`bad base path: "${base}"`); | ||
} | ||
const baseparts = path_match ? [] : base.slice(base_match[0].length).split('/'); | ||
const pathparts = path_match ? path.slice(path_match[0].length).split('/') : path.split('/'); | ||
baseparts.pop(); | ||
for (let i = 0; i < pathparts.length; i += 1) { | ||
const part = pathparts[i]; | ||
if (part === '.') continue; | ||
else if (part === '..') baseparts.pop(); | ||
else baseparts.push(part); | ||
} | ||
const prefix = (path_match && path_match[0]) || (base_match && base_match[0]) || ''; | ||
return `${prefix}${baseparts.join('/')}`; | ||
return url.protocol === internal.protocol ? url.pathname + url.search + url.hash : url.href; | ||
} | ||
@@ -101,2 +83,10 @@ | ||
/** | ||
* Returns everything up to the first `#` in a URL | ||
* @param {{href: string}} url_like | ||
*/ | ||
export function strip_hash({ href }) { | ||
return href.split('#')[0]; | ||
} | ||
/** | ||
* URL properties that could change during the lifetime of the page, | ||
@@ -109,3 +99,2 @@ * which excludes things like `origin` | ||
'search', | ||
'searchParams', | ||
'toString', | ||
@@ -118,6 +107,29 @@ 'toJSON' | ||
* @param {() => void} callback | ||
* @param {(search_param: string) => void} search_params_callback | ||
*/ | ||
export function make_trackable(url, callback) { | ||
export function make_trackable(url, callback, search_params_callback) { | ||
const tracked = new URL(url); | ||
Object.defineProperty(tracked, 'searchParams', { | ||
value: new Proxy(tracked.searchParams, { | ||
get(obj, key) { | ||
if (key === 'get' || key === 'getAll' || key === 'has') { | ||
return (/**@type {string}*/ param) => { | ||
search_params_callback(param); | ||
return obj[key](param); | ||
}; | ||
} | ||
// if they try to access something different from what is in `tracked_search_params_properties` | ||
// we track the whole url (entries, values, keys etc) | ||
callback(); | ||
const value = Reflect.get(obj, key); | ||
return typeof value === 'function' ? value.bind(obj) : value; | ||
} | ||
}), | ||
enumerable: true, | ||
configurable: true | ||
}); | ||
for (const property of tracked_url_properties) { | ||
@@ -142,3 +154,5 @@ Object.defineProperty(tracked, property, { | ||
disable_hash(tracked); | ||
if (DEV || !BROWSER) { | ||
disable_hash(tracked); | ||
} | ||
@@ -152,3 +166,3 @@ return tracked; | ||
*/ | ||
export function disable_hash(url) { | ||
function disable_hash(url) { | ||
allow_nodejs_console_log(url); | ||
@@ -195,6 +209,7 @@ | ||
const DATA_SUFFIX = '/__data.json'; | ||
const HTML_DATA_SUFFIX = '.html__data.json'; | ||
/** @param {string} pathname */ | ||
export function has_data_suffix(pathname) { | ||
return pathname.endsWith(DATA_SUFFIX); | ||
return pathname.endsWith(DATA_SUFFIX) || pathname.endsWith(HTML_DATA_SUFFIX); | ||
} | ||
@@ -204,2 +219,3 @@ | ||
export function add_data_suffix(pathname) { | ||
if (pathname.endsWith('.html')) return pathname.replace(/\.html$/, HTML_DATA_SUFFIX); | ||
return pathname.replace(/\/$/, '') + DATA_SUFFIX; | ||
@@ -210,3 +226,7 @@ } | ||
export function strip_data_suffix(pathname) { | ||
if (pathname.endsWith(HTML_DATA_SUFFIX)) { | ||
return pathname.slice(0, -HTML_DATA_SUFFIX.length) + '.html'; | ||
} | ||
return pathname.slice(0, -DATA_SUFFIX.length); | ||
} |
// generated during release, do not modify | ||
/** @type {string} */ | ||
export const VERSION = '1.25.1'; | ||
export const VERSION = '2.3.3'; |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
708623
13
122
20692
12
+ Addedimport-meta-resolve@^4.0.0
+ Addedmrmime@^2.0.0
+ Added@esbuild/aix-ppc64@0.21.5(transitive)
+ Added@esbuild/android-arm@0.21.5(transitive)
+ Added@esbuild/android-arm64@0.21.5(transitive)
+ Added@esbuild/android-x64@0.21.5(transitive)
+ Added@esbuild/darwin-arm64@0.21.5(transitive)
+ Added@esbuild/darwin-x64@0.21.5(transitive)
+ Added@esbuild/freebsd-arm64@0.21.5(transitive)
+ Added@esbuild/freebsd-x64@0.21.5(transitive)
+ Added@esbuild/linux-arm@0.21.5(transitive)
+ Added@esbuild/linux-arm64@0.21.5(transitive)
+ Added@esbuild/linux-ia32@0.21.5(transitive)
+ Added@esbuild/linux-loong64@0.21.5(transitive)
+ Added@esbuild/linux-mips64el@0.21.5(transitive)
+ Added@esbuild/linux-ppc64@0.21.5(transitive)
+ Added@esbuild/linux-riscv64@0.21.5(transitive)
+ Added@esbuild/linux-s390x@0.21.5(transitive)
+ Added@esbuild/linux-x64@0.21.5(transitive)
+ Added@esbuild/netbsd-x64@0.21.5(transitive)
+ Added@esbuild/openbsd-x64@0.21.5(transitive)
+ Added@esbuild/sunos-x64@0.21.5(transitive)
+ Added@esbuild/win32-arm64@0.21.5(transitive)
+ Added@esbuild/win32-ia32@0.21.5(transitive)
+ Added@esbuild/win32-x64@0.21.5(transitive)
+ Added@rollup/rollup-android-arm-eabi@4.21.0(transitive)
+ Added@rollup/rollup-android-arm64@4.21.0(transitive)
+ Added@rollup/rollup-darwin-arm64@4.21.0(transitive)
+ Added@rollup/rollup-darwin-x64@4.21.0(transitive)
+ Added@rollup/rollup-linux-arm-gnueabihf@4.21.0(transitive)
+ Added@rollup/rollup-linux-arm-musleabihf@4.21.0(transitive)
+ Added@rollup/rollup-linux-arm64-gnu@4.21.0(transitive)
+ Added@rollup/rollup-linux-arm64-musl@4.21.0(transitive)
+ Added@rollup/rollup-linux-powerpc64le-gnu@4.21.0(transitive)
+ Added@rollup/rollup-linux-riscv64-gnu@4.21.0(transitive)
+ Added@rollup/rollup-linux-s390x-gnu@4.21.0(transitive)
+ Added@rollup/rollup-linux-x64-gnu@4.21.0(transitive)
+ Added@rollup/rollup-linux-x64-musl@4.21.0(transitive)
+ Added@rollup/rollup-win32-arm64-msvc@4.21.0(transitive)
+ Added@rollup/rollup-win32-ia32-msvc@4.21.0(transitive)
+ Added@rollup/rollup-win32-x64-msvc@4.21.0(transitive)
+ Added@sveltejs/vite-plugin-svelte@3.1.2(transitive)
+ Added@sveltejs/vite-plugin-svelte-inspector@2.1.0(transitive)
+ Added@types/cookie@0.6.0(transitive)
+ Addedacorn-typescript@1.4.13(transitive)
+ Addedcookie@0.6.0(transitive)
+ Addedesbuild@0.21.5(transitive)
+ Addedesrap@1.2.2(transitive)
+ Addedimport-meta-resolve@4.1.0(transitive)
+ Addedrollup@4.21.0(transitive)
+ Addedsvelte@5.0.0-next.238(transitive)
+ Addedsvelte-hmr@0.16.0(transitive)
+ Addedvite@5.4.2(transitive)
+ Addedzimmerframe@1.1.2(transitive)
- Removed@sveltejs/vite-plugin-svelte@^2.4.1
- Removedmime@^3.0.0
- Removedundici@~5.25.0
- Removed@esbuild/android-arm@0.18.20(transitive)
- Removed@esbuild/android-arm64@0.18.20(transitive)
- Removed@esbuild/android-x64@0.18.20(transitive)
- Removed@esbuild/darwin-arm64@0.18.20(transitive)
- Removed@esbuild/darwin-x64@0.18.20(transitive)
- Removed@esbuild/freebsd-arm64@0.18.20(transitive)
- Removed@esbuild/freebsd-x64@0.18.20(transitive)
- Removed@esbuild/linux-arm@0.18.20(transitive)
- Removed@esbuild/linux-arm64@0.18.20(transitive)
- Removed@esbuild/linux-ia32@0.18.20(transitive)
- Removed@esbuild/linux-loong64@0.18.20(transitive)
- Removed@esbuild/linux-mips64el@0.18.20(transitive)
- Removed@esbuild/linux-ppc64@0.18.20(transitive)
- Removed@esbuild/linux-riscv64@0.18.20(transitive)
- Removed@esbuild/linux-s390x@0.18.20(transitive)
- Removed@esbuild/linux-x64@0.18.20(transitive)
- Removed@esbuild/netbsd-x64@0.18.20(transitive)
- Removed@esbuild/openbsd-x64@0.18.20(transitive)
- Removed@esbuild/sunos-x64@0.18.20(transitive)
- Removed@esbuild/win32-arm64@0.18.20(transitive)
- Removed@esbuild/win32-ia32@0.18.20(transitive)
- Removed@esbuild/win32-x64@0.18.20(transitive)
- Removed@fastify/busboy@2.1.1(transitive)
- Removed@sveltejs/vite-plugin-svelte@2.5.3(transitive)
- Removed@sveltejs/vite-plugin-svelte-inspector@1.0.4(transitive)
- Removed@types/cookie@0.5.4(transitive)
- Removedcookie@0.5.0(transitive)
- Removedesbuild@0.18.20(transitive)
- Removedmime@3.0.0(transitive)
- Removedrollup@3.29.4(transitive)
- Removedsvelte-hmr@0.15.3(transitive)
- Removedundici@5.25.4(transitive)
- Removedvite@4.5.3(transitive)
Updated@types/cookie@^0.6.0
Updatedcookie@^0.6.0
Updateddevalue@^4.3.2
Updatedmagic-string@^0.30.5
Updatedsirv@^2.0.4