@sveltejs/kit
Advanced tools
Comparing version 1.25.1 to 2.0.4
{ | ||
"name": "@sveltejs/kit", | ||
"version": "1.25.1", | ||
"version": "2.0.4", | ||
"description": "The fastest way to build Svelte apps", | ||
@@ -14,35 +14,33 @@ "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", | ||
"kleur": "^4.1.5", | ||
"magic-string": "^0.30.0", | ||
"mime": "^3.0.0", | ||
"magic-string": "^0.30.5", | ||
"mrmime": "^1.0.1", | ||
"sade": "^1.8.1", | ||
"set-cookie-parser": "^2.6.0", | ||
"sirv": "^2.0.2", | ||
"tiny-glob": "^0.2.9", | ||
"undici": "~5.25.0" | ||
"sirv": "^2.0.3", | ||
"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 +84,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 +93,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; | ||
} | ||
} | ||
@@ -75,0 +83,0 @@ |
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), | ||
@@ -183,9 +178,3 @@ | ||
}), | ||
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) | ||
}), | ||
@@ -192,0 +181,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,5 +39,3 @@ export default forked(import.meta.url, analyse); | ||
if (should_polyfill) { | ||
installPolyfills(); | ||
} | ||
installPolyfills(); | ||
@@ -51,4 +48,7 @@ // configure `import { building } from '$app/environment'` — | ||
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); | ||
@@ -61,6 +61,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] = { | ||
@@ -73,74 +73,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])), | ||
@@ -153,5 +110,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)) | ||
}); | ||
@@ -164,2 +121,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 | ||
@@ -169,11 +185,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 | ||
}; | ||
} | ||
@@ -180,0 +198,0 @@ |
@@ -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'; | ||
@@ -98,5 +97,3 @@ import { escape_html_attr } from '../../utils/escape.js'; | ||
if (should_polyfill) { | ||
installPolyfills(); | ||
} | ||
installPolyfills(); | ||
@@ -158,2 +155,3 @@ /** @type {Map<string, string>} */ | ||
const files = new Set(walk(`${out}/client`).map(posixify)); | ||
files.add(`${config.appDir}/env.js`); | ||
@@ -435,4 +433,8 @@ const immutable = `${config.appDir}/immutable`; | ||
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); | ||
@@ -479,6 +481,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` | ||
); | ||
@@ -485,0 +487,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'; | ||
@@ -146,3 +146,3 @@ | ||
export { default as root } from '../root.svelte'; | ||
export { default as root } from '../root.${isSvelte5Plus() ? 'js' : 'svelte'}'; | ||
` | ||
@@ -149,0 +149,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'; | ||
@@ -29,12 +29,12 @@ | ||
}) => ` | ||
import root from '../root.svelte'; | ||
import root from '../root.${isSvelte5Plus() ? 'js' : 'svelte'}'; | ||
import { set_building } from '__sveltekit/environment'; | ||
import { set_assets } from '__sveltekit/paths'; | ||
import { set_private_env, set_public_env } from '${runtime_directory}/shared-server.js'; | ||
import { set_private_env, set_public_env, set_safe_public_env } from '${runtime_directory}/shared-server.js'; | ||
export const options = { | ||
app_dir: ${s(config.kit.appDir)}, | ||
app_template_contains_nonce: ${template.includes('%sveltekit.nonce%')}, | ||
csp: ${s(config.kit.csp)}, | ||
csrf_check_origin: ${s(config.kit.csrf.checkOrigin)}, | ||
track_server_fetches: ${s(config.kit.dangerZone.trackServerFetches)}, | ||
embedded: ${config.kit.embedded}, | ||
@@ -68,3 +68,3 @@ env_public_prefix: '${config.kit.env.publicPrefix}', | ||
export { set_assets, set_building, set_private_env, set_public_env }; | ||
export { set_assets, set_building, set_private_env, set_public_env, set_safe_public_env }; | ||
`; | ||
@@ -71,0 +71,0 @@ |
@@ -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,3 +87,3 @@ ]); | ||
const exclude = [config_relative('node_modules/**'), './[!ambient.d.ts]**']; | ||
const exclude = [config_relative('node_modules/**')]; | ||
if (path.extname(kit.files.serviceWorker)) { | ||
@@ -129,4 +100,3 @@ exclude.push(config_relative(kit.files.serviceWorker)); | ||
// 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 +107,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 +117,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 +146,2 @@ include: [...include], | ||
/** | ||
* @param {import('types').ValidatedKitConfig} kit | ||
* @param {string} cwd | ||
@@ -185,3 +151,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 +159,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 +166,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 +202,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 +223,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 +226,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,13 @@ const readable = new ReadableStream(); | ||
start(controller) { | ||
if (body_size_limit !== undefined && content_length > body_size_limit) { | ||
const error = new SvelteKitError( | ||
413, | ||
'Payload Too Large', | ||
`Content-length of ${content_length} exceeds limit of ${body_size_limit} bytes.` | ||
); | ||
controller.error(error); | ||
return; | ||
} | ||
req.on('error', (error) => { | ||
@@ -65,12 +63,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 +127,3 @@ } | ||
/** @type {string}*/ (response.headers.get(key)) | ||
) | ||
) | ||
: value | ||
@@ -133,0 +130,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. | ||
@@ -212,3 +214,3 @@ * @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 | ||
* 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) | ||
*/ | ||
@@ -222,8 +224,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; | ||
@@ -233,7 +239,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; | ||
@@ -245,9 +251,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; | ||
} | ||
@@ -292,3 +302,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" | ||
@@ -358,12 +370,2 @@ */ | ||
/** | ||
* 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`. | ||
@@ -488,11 +490,16 @@ * @default false | ||
/** | ||
* 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 | ||
*/ | ||
relative?: boolean | undefined; | ||
relative?: boolean; | ||
}; | ||
@@ -514,3 +521,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 ["*"] | ||
@@ -667,2 +674,4 @@ */ | ||
event: RequestEvent; | ||
status: number; | ||
message: string; | ||
}) => MaybePromise<void | App.Error>; | ||
@@ -679,2 +688,4 @@ | ||
event: NavigationEvent; | ||
status: number; | ||
message: string; | ||
}) => MaybePromise<void | App.Error>; | ||
@@ -798,3 +809,17 @@ | ||
*/ | ||
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; | ||
} | ||
@@ -866,3 +891,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 | ||
@@ -972,2 +997,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. | ||
@@ -1203,2 +1232,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; | ||
} | ||
@@ -1270,13 +1313,3 @@ | ||
action: URL; | ||
/** | ||
* use `formData` instead of `data` | ||
* @deprecated | ||
*/ | ||
data: FormData; | ||
formData: FormData; | ||
/** | ||
* use `formElement` instead of `form` | ||
* @deprecated | ||
*/ | ||
form: HTMLFormElement; | ||
formElement: HTMLFormElement; | ||
@@ -1289,13 +1322,3 @@ controller: AbortController; | ||
| ((opts: { | ||
/** | ||
* use `formData` instead of `data` | ||
* @deprecated | ||
*/ | ||
data: FormData; | ||
formData: FormData; | ||
/** | ||
* use `formElement` instead of `form` | ||
* @deprecated | ||
*/ | ||
form: HTMLFormElement; | ||
formElement: HTMLFormElement; | ||
@@ -1307,4 +1330,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) | ||
@@ -1311,0 +1335,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) { |
@@ -6,3 +6,2 @@ import fs from 'node:fs'; | ||
import { get_config_aliases } from '../utils.js'; | ||
import { assets_base } from './utils.js'; | ||
@@ -68,12 +67,13 @@ /** | ||
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 | ||
} | ||
@@ -89,4 +89,14 @@ }, | ||
alias: [...get_config_aliases(kit), { find: '$service-worker', replacement: service_worker }] | ||
}, | ||
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(); | ||
@@ -128,3 +125,4 @@ const fetch = globalThis.fetch; | ||
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); |
@@ -21,3 +21,3 @@ import fs from 'node:fs'; | ||
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 +28,16 @@ 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'; | ||
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'; | ||
@@ -143,3 +154,3 @@ if (!warned.has(message)) { | ||
// @ts-expect-error SvelteKit requires hydratable true by default | ||
hydratable: true, | ||
hydratable: isSvelte5Plus() ? undefined : true, | ||
...svelte_config.compilerOptions | ||
@@ -330,6 +341,2 @@ }, | ||
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(''); | ||
} | ||
@@ -345,3 +352,3 @@ }; | ||
if (id.startsWith('$env/') || id.startsWith('__sveltekit/') || id === '$service-worker') { | ||
return `\0${id}`; | ||
return `\0virtual:${id}`; | ||
} | ||
@@ -368,3 +375,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`); | ||
} | ||
@@ -374,9 +381,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( | ||
@@ -387,6 +394,6 @@ 'private', | ||
case '\0$env/dynamic/public': | ||
case env_dynamic_public: | ||
// populate `$env/dynamic/public` from `window` | ||
if (browser) { | ||
return `export const env = ${global}.env;`; | ||
return `export const env = ${global}.env ?? (await import(/* @vite-ignore */ ${global}.base + '/' + '${kit.appDir}/env.js')).env;`; | ||
} | ||
@@ -399,3 +406,3 @@ | ||
case '\0$service-worker': | ||
case service_worker: | ||
return create_service_worker_module(svelte_config); | ||
@@ -405,3 +412,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; | ||
@@ -444,3 +451,3 @@ | ||
case '\0__sveltekit/environment': { | ||
case sveltekit_environment: { | ||
const { version } = svelte_config.kit; | ||
@@ -571,3 +578,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'}`, | ||
@@ -587,3 +594,3 @@ rollupOptions: { | ||
ssrEmitAssets: true, | ||
target: ssr ? 'node16.14' : undefined | ||
target: ssr ? 'node18.13' : 'es2022' | ||
}, | ||
@@ -720,3 +727,3 @@ publicDir: kit.files.assets, | ||
const { output } = /** @type {import('rollup').RollupOutput} */ ( | ||
const { output } = /** @type {import('vite').Rollup.RollupOutput} */ ( | ||
await vite.build({ | ||
@@ -757,7 +764,10 @@ configFile: vite_config.configFile, | ||
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') | ||
@@ -825,9 +835,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 { | ||
@@ -880,3 +894,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 | ||
@@ -883,0 +897,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:', ''); |
@@ -53,16 +53,2 @@ import * as devalue from 'devalue'; | ||
/** | ||
* @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 +94,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 +100,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 +149,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 +167,2 @@ } | ||
// TODO 2.0: Remove `data` and `form` | ||
const callback = | ||
@@ -182,11 +173,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 +204,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 +215,0 @@ result |
@@ -14,9 +14,3 @@ import { client_method } from '../client/singletons.js'; | ||
* | ||
* @type {(url: string | URL, opts?: { | ||
* replaceState?: boolean; | ||
* noScroll?: boolean; | ||
* keepFocus?: boolean; | ||
* invalidateAll?: boolean; | ||
* state?: any | ||
* }) => Promise<void>} | ||
* @type {(url: string | URL, opts?: { replaceState?: boolean; noScroll?: boolean; keepFocus?: boolean; invalidateAll?: boolean; state?: App.PageState }) => 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. | ||
@@ -27,4 +21,4 @@ * @param {Object} [opts] Options related to the 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 | ||
* @param {boolean} [opts.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 {App.PageState} [opts.state] An optional object that will be available on the `$page.state` store | ||
* @returns {Promise<void>} | ||
@@ -69,7 +63,7 @@ */ | ||
* 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. | ||
* Returns a Promise that resolves with the result of running the new route's `load` functions once the preload is complete. | ||
* | ||
* @type {(href: string) => Promise<void>} | ||
* @type {(href: string) => Promise<Record<string, any>>} | ||
* @param {string} href Page to preload | ||
* @returns {Promise<void>} | ||
* @returns {Promise<{ type: 'loaded'; status: number; data: Record<string, any> } | { type: 'redirect'; location: string }>} | ||
*/ | ||
@@ -87,4 +81,4 @@ export const preloadData = /* @__PURE__ */ client_method('preload_data'); | ||
* | ||
* @type {(...urls: string[]) => Promise<void>} | ||
* @param {...string[]} urls | ||
* @type {(url: string) => Promise<void>} | ||
* @param {string} url | ||
* @returns {Promise<void>} | ||
@@ -96,7 +90,9 @@ */ | ||
* 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`. | ||
* Calling `cancel()` will prevent the navigation from completing. If `navigation.type === 'leave'` — meaning the user is navigating away from the app (or closing the tab) — calling `cancel` will trigger the native browser unload confirmation dialog. In this case, the navigation may or may not be cancelled depending on the user's response. | ||
* | ||
* When a navigation isn't to a SvelteKit-owned route (and therefore controlled by SvelteKit's client-side router), `navigation.to.route.id` will be `null`. | ||
* | ||
* If the navigation will (if not cancelled) cause the document to unload — in other words `'leave'` navigations and `'link'` navigations where `navigation.to.route === null` — `navigation.willUnload` is `true`. | ||
* | ||
* `beforeNavigate` must be called during a component initialization. It remains active as long as the component is mounted. | ||
@@ -110,3 +106,3 @@ * @type {(callback: (navigation: import('@sveltejs/kit').BeforeNavigate) => void) => void} | ||
/** | ||
* A lifecycle function that runs the supplied `callback` immediately before we navigate to a new URL. | ||
* A lifecycle function that runs the supplied `callback` immediately before we navigate to a new URL except during full-page navigations. | ||
* | ||
@@ -118,3 +114,3 @@ * 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. | ||
* `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} | ||
* @type {(callback: (navigation: import('@sveltejs/kit').OnNavigate) => import('types').MaybePromise<(() => void) | void>) => void} | ||
* @param {(navigation: import('@sveltejs/kit').OnNavigate) => void} callback | ||
@@ -134,1 +130,21 @@ * @returns {void} | ||
export const afterNavigate = /* @__PURE__ */ client_method('after_navigate'); | ||
/** | ||
* Programmatically create a new history entry with the given `$page.state`. To use the current URL, you can pass `''` as the first argument. Used for [shallow routing](https://kit.svelte.dev/docs/shallow-routing). | ||
* | ||
* @type {(url: string | URL, state: App.PageState) => void} | ||
* @param {string | URL} url | ||
* @param {App.PageState} state | ||
* @returns {void} | ||
*/ | ||
export const pushState = /* @__PURE__ */ client_method('push_state'); | ||
/** | ||
* Programmatically replace the current history entry with the given `$page.state`. To use the current URL, you can pass `''` as the first argument. Used for [shallow routing](https://kit.svelte.dev/docs/shallow-routing). | ||
* | ||
* @type {(url: string | URL, state: App.PageState) => void} | ||
* @param {string | URL} url | ||
* @param {App.PageState} state | ||
* @returns {void} | ||
*/ | ||
export const replaceState = /* @__PURE__ */ client_method('replace_state'); |
export { base, assets } from '__sveltekit/paths'; | ||
import { base } from '__sveltekit/paths'; | ||
import { resolve_route } from '../../utils/routing.js'; | ||
/** | ||
* 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 resolveRoute(id, params) { | ||
return base + resolve_route(id, params); | ||
} |
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 | ||
}); |
@@ -26,2 +26,6 @@ import { DEV } from 'esm-env'; | ||
/** | ||
* @param {RequestInfo | URL} input | ||
* @param {RequestInit & Record<string, any> | undefined} init | ||
*/ | ||
window.fetch = (input, init) => { | ||
@@ -40,6 +44,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( | ||
@@ -73,2 +81,18 @@ `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` | ||
/** | ||
* @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. | ||
@@ -84,6 +108,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); | ||
} | ||
@@ -93,3 +123,3 @@ return Promise.resolve(new Response(body, init)); | ||
return native_fetch(resource, opts); | ||
return DEV ? dev_fetch(resource, opts) : window.fetch(resource, opts); | ||
} | ||
@@ -120,6 +150,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 | ||
@@ -126,0 +171,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 |
@@ -5,3 +5,3 @@ import { writable } from 'svelte/store'; | ||
/** @type {import('./types').Client} */ | ||
/** @type {import('./types.js').Client} */ | ||
export let client; | ||
@@ -11,3 +11,3 @@ | ||
* @param {{ | ||
* client: import('./types').Client; | ||
* client: import('./types.js').Client; | ||
* }} opts | ||
@@ -26,3 +26,9 @@ */ | ||
if (!BROWSER) { | ||
if (key === 'before_navigate' || key === 'after_navigate' || key === 'on_navigate') { | ||
if ( | ||
key === 'before_navigate' || | ||
key === 'after_navigate' || | ||
key === 'on_navigate' || | ||
key === 'push_state' || | ||
key === 'replace_state' | ||
) { | ||
// @ts-expect-error doesn't recognize that both keys here return void so expects a async function | ||
@@ -29,0 +35,0 @@ return () => {}; |
@@ -6,5 +6,5 @@ import { DEV } from 'esm-env'; | ||
/** | ||
* @param {import('./types').SvelteKitApp} app | ||
* @param {import('./types.js').SvelteKitApp} app | ||
* @param {HTMLElement} target | ||
* @param {Parameters<import('./types').Client['_hydrate']>[0]} [hydrate] | ||
* @param {Parameters<import('./types.js').Client['_hydrate']>[0]} [hydrate] | ||
*/ | ||
@@ -11,0 +11,0 @@ export async function start(app, target, hydrate) { |
@@ -1,2 +0,2 @@ | ||
import { applyAction } from '../app/forms'; | ||
import { applyAction } from '../app/forms.js'; | ||
import { | ||
@@ -10,4 +10,6 @@ afterNavigate, | ||
preloadCode, | ||
preloadData | ||
} from '../app/navigation'; | ||
preloadData, | ||
pushState, | ||
replaceState | ||
} from '../app/navigation.js'; | ||
import { SvelteComponent } from 'svelte'; | ||
@@ -55,2 +57,4 @@ import { ClientHooks, CSRPageNode, CSRPageNodeLoader, CSRRoute, TrailingSlash, Uses } from 'types'; | ||
preload_data: typeof preloadData; | ||
push_state: typeof pushState; | ||
replace_state: typeof replaceState; | ||
apply_action: typeof applyAction; | ||
@@ -95,4 +99,5 @@ | ||
props: { | ||
components: Array<typeof SvelteComponent>; | ||
page?: Page; | ||
constructors: Array<typeof SvelteComponent>; | ||
components?: Array<SvelteComponent>; | ||
page: Page; | ||
form?: Record<string, any> | null; | ||
@@ -99,0 +104,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 | ||
} |
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 { building } from '../app/environment.js'; | ||
/** @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,16 +40,16 @@ /** @type {import('types').SSROptions} */ | ||
// been done already. | ||
// set env, in case it's used in initialisation | ||
set_private_env( | ||
filter_private_env(env, { | ||
public_prefix: this.#options.env_public_prefix, | ||
private_prefix: this.#options.env_private_prefix | ||
}) | ||
); | ||
set_public_env( | ||
filter_public_env(env, { | ||
public_prefix: this.#options.env_public_prefix, | ||
private_prefix: this.#options.env_private_prefix | ||
}) | ||
); | ||
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(building ? new Proxy({ type: 'private' }, prerender_env_handler) : private_env); | ||
set_public_env(building ? new Proxy({ type: 'public' }, prerender_env_handler) : public_env); | ||
set_safe_public_env(public_env); | ||
if (!this.#options.hooks) { | ||
@@ -75,9 +85,2 @@ try { | ||
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, { | ||
@@ -84,0 +87,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 @@ } |
@@ -226,4 +226,4 @@ import { escape_html_attr } from '../../../utils/escape.js'; | ||
/** | ||
* @param {import('./types').CspConfig} config | ||
* @param {import('./types').CspOpts} opts | ||
* @param {import('./types.js').CspConfig} config | ||
* @param {import('./types.js').CspOpts} opts | ||
*/ | ||
@@ -230,0 +230,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); | ||
} | ||
@@ -82,3 +81,3 @@ if (action_result?.type === 'failure') { | ||
// 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; | ||
@@ -101,6 +100,6 @@ if (should_prerender) { | ||
/** @type {import('./types').Fetched[]} */ | ||
/** @type {import('./types.js').Fetched[]} */ | ||
const fetched = []; | ||
if (get_option(nodes, 'ssr') === false) { | ||
if (get_option(nodes, 'ssr') === false && !state.prerendering) { | ||
return await render_response({ | ||
@@ -156,4 +155,3 @@ branch: [], | ||
return data; | ||
}, | ||
track_server_fetches: options.track_server_fetches | ||
} | ||
}); | ||
@@ -229,3 +227,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); | ||
@@ -299,3 +297,3 @@ | ||
csr: get_option(nodes, 'csr') ?? true, | ||
ssr: true | ||
ssr: get_option(nodes, 'ssr') ?? true | ||
}, | ||
@@ -302,0 +300,0 @@ status, |
@@ -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( | ||
@@ -303,3 +308,3 @@ (path) => resolve_opts.preload({ type: 'js', path }) | ||
`base: ${base_expression}`, | ||
`env: ${s(public_env)}` | ||
`env: ${!client.uses_env_dynamic_public || state.prerendering ? null : s(public_env)}` | ||
].filter(Boolean); | ||
@@ -440,3 +445,3 @@ | ||
nonce: /** @type {string} */ (csp.nonce), | ||
env: public_env | ||
env: safe_public_env | ||
}); | ||
@@ -477,3 +482,3 @@ | ||
headers | ||
}) | ||
}) | ||
: new Response( | ||
@@ -496,3 +501,3 @@ new ReadableStream({ | ||
} | ||
); | ||
); | ||
} | ||
@@ -499,0 +504,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') { | ||
@@ -101,2 +105,6 @@ return json(csrf_error.body, { status: csrf_error.status }); | ||
if (decoded === `/${options.app_dir}/env.js`) { | ||
return get_public_env(request); | ||
} | ||
const is_data_request = has_data_suffix(decoded); | ||
@@ -141,3 +149,3 @@ /** @type {boolean[] | undefined} */ | ||
/** @type {Record<string, import('./page/types').Cookie>} */ | ||
/** @type {Record<string, import('./page/types.js').Cookie>} */ | ||
let cookies_to_add = {}; | ||
@@ -349,4 +357,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)); | ||
@@ -365,8 +373,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 = { | ||
@@ -490,3 +492,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 | ||
@@ -493,0 +495,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) { |
@@ -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. | ||
@@ -106,3 +112,3 @@ */ | ||
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 let relative: boolean; | ||
export function reset(): void; | ||
@@ -109,0 +115,0 @@ export function override(paths: { base: string; assets: string }): void; |
@@ -34,2 +34,3 @@ import { SvelteComponent } from 'svelte'; | ||
set_public_env(environment: Record<string, string>): void; | ||
set_safe_public_env(environment: Record<string, string>): void; | ||
set_version(version: string): void; | ||
@@ -63,2 +64,3 @@ set_fix_stack_trace(fix_stack_trace: (error: unknown) => string): void; | ||
fonts: string[]; | ||
uses_env_dynamic_public: boolean; | ||
} | null; | ||
@@ -335,6 +337,6 @@ server_manifest: import('vite').Manifest; | ||
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; | ||
@@ -414,2 +416,3 @@ env_public_prefix: string; | ||
url: boolean; | ||
search_params: Set<string>; | ||
} | ||
@@ -421,3 +424,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; | ||
} |
@@ -94,3 +94,3 @@ const param_pattern = /^(\[)?(\.\.\.)?(\w+)(?:=(\w+))?(\])?$/; | ||
.join('')}/?$` | ||
); | ||
); | ||
@@ -218,1 +218,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('/') | ||
); | ||
} |
@@ -9,3 +9,3 @@ import { BROWSER } from 'esm-env'; | ||
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) { | ||
@@ -193,6 +205,7 @@ Object.defineProperty(tracked, property, { | ||
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); | ||
} | ||
@@ -202,2 +215,3 @@ | ||
export function add_data_suffix(pathname) { | ||
if (pathname.endsWith('.html')) return pathname.replace(/\.html$/, HTML_DATA_SUFFIX); | ||
return pathname.replace(/\/$/, '') + DATA_SUFFIX; | ||
@@ -208,3 +222,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.0.4'; |
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
AI-detected potential malware
Supply chain riskAI has identified this package as malware. This is a strong signal that the package may be malicious.
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
701843
14
13
121
20480
12
2
+ Addedmrmime@^1.0.1
+ 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)
+ Addedmrmime@1.0.1(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.3