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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions apps/svelte.dev/content/blog/2023-03-09-zero-config-type-safety.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,12 @@ This is where our automatic type generation comes in. Every route directory has

```js
/// file: src/routes/blog/[slug]/+page.server.ts
const database = {
getPost(slug: string | undefined): Promise<string> {
return Promise.resolve('hello world');
}
};
// ---cut---
---import type { ServerLoadEvent } from '@sveltejs/kit';---
+++import type { PageServerLoadEvent } from './$types';+++

Expand Down Expand Up @@ -133,7 +139,14 @@ src/
Thanks to the automatic type generation we get advanced type safety. Wouldn't it be great though if we could just omit writing the types at all? As of today you can do exactly that:

```js
// @errors: 7006
/// file: src/routes/blog/[slug]/+page.server.ts
const database = {
getPost(slug: string | undefined): Promise<string> {
return Promise.resolve('hello world');
}
};
// ---cut---
---import type { PageServerLoadEvent } from './$types';---

export async function load(event---: PageServerLoadEvent---) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -524,7 +524,14 @@ In addition to declarative schema validation, you can programmatically mark fiel
- It accepts multiple arguments that can be strings (for issues relating to the form as a whole — these will only show up in `fields.allIssues()`) or standard-schema-compliant issues (for those relating to a specific field). Use the `issue` parameter for type-safe creation of such issues:

```js
// @errors: 18046
/// file: src/routes/shop/data.remote.js
// @filename: ambient.d.ts
declare module '$lib/server/database' {
export function buy(qty: number): Promise<void>
}
// @filename: index.js
// ---cut---
import * as v from 'valibot';
import { invalid } from '@sveltejs/kit';
import { form } from '$app/server';
Expand Down Expand Up @@ -1166,7 +1173,7 @@ As long as _you're_ not passing invalid data to your remote functions, there are
In the second case, we don't want to give the attacker any help, so SvelteKit will generate a generic [400 Bad Request](https://http.dog/400) response. You can control the message by implementing the [`handleValidationError`](hooks#Server-hooks-handleValidationError) server hook, which, like [`handleError`](hooks#Shared-hooks-handleError), must return an [`App.Error`](errors#Type-safety) (which defaults to `{ message: string }`):

```js
/// file: src/hooks.server.ts
/// file: src/hooks.server.js
/** @type {import('@sveltejs/kit').HandleValidationError} */
export function handleValidationError({ event, issues }) {
return {
Expand All @@ -1193,13 +1200,25 @@ Inside `query`, `form` and `command` you can use [`getRequestEvent`]($app-server

```ts
/// file: user.remote.ts
// @filename: ambient.d.ts
interface User {
name: string;
avatar: string;
}

declare module '$lib/server/database' {
export function findUser(sessionId: string | undefined): Promise<User | null>;
}

// @filename: index.js
// ---cut---
import { getRequestEvent, query } from '$app/server';
import { findUser } from '$lib/server/database';

export const getProfile = query(async () => {
const user = await getUser();

return {
return user && {
name: user.name,
avatar: user.avatar
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ This will prerender your entire site as a collection of static files. If you'd l
Install with `npm i -D @sveltejs/adapter-static`, then add the adapter to your `svelte.config.js`:

```js
// @errors: 2307
/// file: svelte.config.js
import adapter from '@sveltejs/adapter-static';

Expand Down Expand Up @@ -56,6 +57,7 @@ Some platforms have zero-config support (more to come in future):
On these platforms, you should omit the adapter options so that `adapter-static` can provide the optimal configuration:

```js
// @errors: 2307
/// file: svelte.config.js
import adapter from '@sveltejs/adapter-static';

Expand All @@ -81,7 +83,7 @@ The directory to write static assets (the contents of `static`, plus client-side

### fallback

To create a [single page app (SPA)](single-page-apps) you must specify the name of the fallback page to be generated by SvelteKit, which is used as the entry point for URLs that have not been prerendered. This is commonly `200.html`, but can vary depending on your deployment platform. You should avoid `index.html` where possible to avoid conflicting with a prerendered homepage.
To create a [single page app (SPA)](single-page-apps) you must specify the name of the fallback page to be generated by SvelteKit, which is used as the entry point for URLs that have not been prerendered. This is commonly `200.html`, but can vary depending on your deployment platform. You should avoid `index.html` where possible to avoid conflicting with a prerendered homepage.

> This option has large negative performance and SEO impacts. It is only recommended in certain circumstances such as wrapping the site in a mobile app. See the [single page apps](single-page-apps) documentation for more details and alternatives.

Expand All @@ -102,7 +104,7 @@ You'll also want to generate a fallback `404.html` page to replace the default 4
A config for GitHub Pages might look like the following:

```js
// @errors: 2322
// @errors: 2307 2322
/// file: svelte.config.js
import adapter from '@sveltejs/adapter-static';

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ export const ssr = false;
If you don't have any server-side logic (i.e. `+page.server.js`, `+layout.server.js` or `+server.js` files) you can use [`adapter-static`](adapter-static) to create your SPA. Install `adapter-static` with `npm i -D @sveltejs/adapter-static` and add it to your `svelte.config.js` with the `fallback` option:

```js
// @errors: 2307
/// file: svelte.config.js
import adapter from '@sveltejs/adapter-static';

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ This adapter will be installed by default when you use [`adapter-auto`](adapter-
Install with `npm i -D @sveltejs/adapter-cloudflare`, then add the adapter to your `svelte.config.js`:

```js
// @errors: 2307
/// file: svelte.config.js
import adapter from '@sveltejs/adapter-cloudflare';

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ This adapter will be installed by default when you use [`adapter-auto`](adapter-
Install with `npm i -D @sveltejs/adapter-netlify`, then add the adapter to your `svelte.config.js`:

```js
// @errors: 2307
/// file: svelte.config.js
import adapter from '@sveltejs/adapter-netlify';

Expand Down Expand Up @@ -54,6 +55,7 @@ New projects will use the current Node LTS version by default. However, if you'r
SvelteKit supports [Netlify Edge Functions](https://docs.netlify.com/build/edge-functions/overview/). If you pass the option `edge: true` to the `adapter` function, server-side rendering will happen in a Deno-based edge function that's deployed close to the site visitor. If set to `false` (the default), the site will deploy to Node-based Netlify Functions.

```js
// @errors: 2307
/// file: svelte.config.js
import adapter from '@sveltejs/adapter-netlify';

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ To view your first trace, you'll need to set up a local collector. We'll use [Ja
- Create `src/instrumentation.server.js` with the following:

```js
// @errors: 2307
/// file: src/instrumentation.server.js
import { NodeSDK } from '@opentelemetry/sdk-node';
import { getNodeAutoInstrumentations } from '@opentelemetry/auto-instrumentations-node';
Expand All @@ -98,4 +99,4 @@ Now, server-side requests will begin generating traces, which you can view in Ja

## `@opentelemetry/api`

SvelteKit uses `@opentelemetry/api` to generate its spans. This is declared as an optional peer dependency so that users not needing traces see no impact on install size or runtime performance. In most cases, if you're configuring your application to collect SvelteKit's spans, you'll end up installing a library like `@opentelemetry/sdk-node` or `@vercel/otel`, which in turn depend on `@opentelemetry/api`, which will satisfy SvelteKit's dependency as well. If you see an error from SvelteKit telling you it can't find `@opentelemetry/api`, it may just be because you haven't set up your trace collection yet. If you _have_ done that and are still seeing the error, you can install `@opentelemetry/api` yourself.
SvelteKit uses `@opentelemetry/api` to generate its spans. This is declared as an optional peer dependency so that users not needing traces see no impact on install size or runtime performance. In most cases, if you're configuring your application to collect SvelteKit's spans, you'll end up installing a library like `@opentelemetry/sdk-node` or `@vercel/otel`, which in turn depend on `@opentelemetry/api`, which will satisfy SvelteKit's dependency as well. If you see an error from SvelteKit telling you it can't find `@opentelemetry/api`, it may just be because you haven't set up your trace collection yet. If you _have_ done that and are still seeing the error, you can install `@opentelemetry/api` yourself.
Original file line number Diff line number Diff line change
Expand Up @@ -66,12 +66,13 @@ If your content is available in multiple languages, you should set the `lang` at

```js
/// file: src/hooks.server.js
/**
* @param {import('@sveltejs/kit').RequestEvent} event
*/
function get_lang(event) {
// @filename: utils.ts
export function get_lang(event: import('@sveltejs/kit').RequestEvent) {
return 'en';
}

// @filename: hooks.server.js
import { get_lang } from './utils';
// ---cut---
/** @type {import('@sveltejs/kit').Handle} */
export function handle({ event, resolve }) {
Expand Down
4 changes: 3 additions & 1 deletion apps/svelte.dev/src/lib/server/renderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ export const render_content = (
injected.push(
`declare module '$env/dynamic/private' { export const env: Record<string, string> }`,
`declare module '$env/dynamic/public' { export const env: Record<string, string> }`,
`declare module '$env/static/private' { export const API_KEY: string }`,
`declare module '$env/static/private' { export const API_KEY: string; export const VERCEL_COMMIT_REF: string }`,
`declare module '$env/static/public' { export const PUBLIC_BASE_URL: string }`
);
}
Expand All @@ -56,8 +56,10 @@ export const render_content = (
`import type * as Kit from '@sveltejs/kit';`,
`export type PageLoad = Kit.Load<Record<string, any>>;`,
`export type PageServerLoad = Kit.ServerLoad<Record<string, any>>;`,
`export type PageServerLoadEvent = Parameters<PageServerLoad>[0];`,
`export type LayoutLoad = Kit.Load<Record<string, any>>;`,
`export type LayoutServerLoad = Kit.ServerLoad<Record<string, any>>;`,
`export type LayoutServerLoadEvent = Parameters<LayoutServerLoad>[0];`,
`export type RequestHandler = Kit.RequestHandler<Record<string, any>>;`,
`export type Action = Kit.Action<Record<string, any>>;`,
`export type Actions = Kit.Actions<Record<string, any>>;`,
Expand Down
6 changes: 1 addition & 5 deletions packages/site-kit/src/lib/docs/Tooltip.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@
font: var(--sk-font-body-small);
}

.tags {
.twoslash-popup-docs-tags {
display: grid;
grid-template-columns: auto 1fr;
column-gap: 1rem;
Expand All @@ -105,10 +105,6 @@
.param {
font: var(--sk-font-mono);
}

.tag {
min-width: 8rem;
}
}

/* Disable highlight styles inside tooltips (popup content) */
Expand Down
30 changes: 22 additions & 8 deletions packages/site-kit/src/lib/markdown/renderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -449,7 +449,12 @@ export async function render_content_markdown(
[, prelude = '// ---cut---\n', source] = match;

const banner = twoslashBanner?.(filename, source);
if (banner) prelude = '// @filename: injected.d.ts\n' + banner + '\n' + prelude;
if (banner)
prelude =
'// @filename: injected.d.ts\n' +
banner +
(options.file ? `\n// @filename: ${options.file.split('/').pop()}\n` : '\n') +
prelude;
}

source = source.replace(
Expand Down Expand Up @@ -1130,7 +1135,7 @@ async function syntax_highlight({
const redactions: string[] = [];

const sub = delimiter_substitutes['---'];
const pattern = new RegExp(`${sub}(?:[^ ]|[^ ][^]+?[^ ])${sub}`, 'g');
const pattern = new RegExp(`${sub}([^ ]|[^ ][^]+?[^ ])${sub}`, 'g');

const redacted = source.replace(pattern, (_, content) => {
redactions.push(content);
Expand Down Expand Up @@ -1166,7 +1171,7 @@ async function syntax_highlight({

html = html.replace(
new RegExp(` {${delimiter_substitutes['---'].length + 1},}`, 'g'),
() => redactions.shift()!
() => `<span class="highlight remove">${redactions.shift()!}</span>`
);

if (check) {
Expand Down Expand Up @@ -1206,7 +1211,17 @@ async function syntax_highlight({
for (const match of html.matchAll(
/<span class="twoslash-popup-docs-tag"><span class="twoslash-popup-docs-tag-name">([^]+?)<\/span><span class="twoslash-popup-docs-tag-value">([^]+?)<\/span><\/span>/g
)) {
const start = match.index;
const end = match.index + match[0].length;

const tag = match[1];

if (tag === '@type') {
// remove `@type` tags altogether
replacements.push({ start, end, content: '' });
continue;
}

let value = match[2];
let content = `<div class="tag">${tag}</div><div class="value">`;

Expand Down Expand Up @@ -1234,18 +1249,17 @@ async function syntax_highlight({

content += '</div>';

replacements.push({
start: match.index,
end: match.index + match[0].length,
content: '<div class="tags">' + content + '</div>'
});
replacements.push({ start, end, content });
}

while (replacements.length > 0) {
const { start, end, content } = replacements.pop()!;
html = html.slice(0, start) + content + html.slice(end);
}

// if no tags, remove this <div> to avoid an unnecessary flex gap
html = html.replace('<div class="twoslash-popup-docs twoslash-popup-docs-tags"></div>', '');

html = injectReferenceLinks(html, references, extractImportedSymbols(source));
}
} catch (e) {
Expand Down
Loading