Skip to content

Commit

Permalink
first stab at shiki-twoslash integration (#3986)
Browse files Browse the repository at this point in the history
* first stab at shiki-twoslash integration

* revert this line

* get it to build

* style tweaks

* css variable based highlighting, css hovers

* tweak hover styles

* show filenames

* use filename mechanism everywhere

* update some docs

* more updates

* more docs

* update FAQ docs

* fail build if typechecking fails

* fix FAQ styles

* small tweaks

* hovers

* keep tooltips from overflowing the body
  • Loading branch information
Rich-Harris committed Feb 18, 2022
1 parent f766a54 commit e34af0f
Show file tree
Hide file tree
Showing 29 changed files with 652 additions and 221 deletions.
115 changes: 69 additions & 46 deletions documentation/docs/01-routing.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ Pages are Svelte components written in `.svelte` files (or any file with an exte
The filename determines the route. For example, `src/routes/index.svelte` is the root of your site:

```html
<!-- src/routes/index.svelte -->
/// file: src/routes/index.svelte
<svelte:head>
<title>Welcome</title>
</svelte:head>
Expand All @@ -30,7 +30,7 @@ The filename determines the route. For example, `src/routes/index.svelte` is the
A file called either `src/routes/about.svelte` or `src/routes/about/index.svelte` would correspond to the `/about` route:

```html
<!-- src/routes/about.svelte -->
/// file: src/routes/about.svelte
<svelte:head>
<title>About</title>
</svelte:head>
Expand All @@ -47,42 +47,19 @@ A file or directory can have multiple dynamic parts, like `[id]-[category].svelt

Endpoints are modules written in `.js` (or `.ts`) files that export functions corresponding to HTTP methods. Their job is to allow pages to read and write data that is only available on the server (for example in a database, or on the filesystem).

```ts
// Type declarations for endpoints (declarations marked with
// an `export` keyword can be imported from `@sveltejs/kit`)
If an endpoint has the same filename as a page (except for the extension), the page will get its props from the endpoint. So a page like `src/routes/items/[id].svelte` could get its props from this file:

export interface RequestHandler<Output = Record<string, any>> {
(event: RequestEvent): MaybePromise<
Either<Output extends Response ? Response : EndpointOutput<Output>, Fallthrough>
>;
}

export interface RequestEvent {
request: Request;
url: URL;
params: Record<string, string>;
locals: App.Locals;
platform: App.Platform;
}

export interface EndpointOutput<Output = Record<string, any>> {
status?: number;
headers?: Headers | Partial<ResponseHeaders>;
body?: Record<string, any>;
}

type MaybePromise<T> = T | Promise<T>;
```js
/// file: src/routes/items/[id].js
// @filename: ambient.d.ts
type Item = {};

interface Fallthrough {
fallthrough: true;
declare module '$lib/database' {
export const get: (id: string) => Promise<Item>;
}
```

> See the [TypeScript](/docs/typescript) section for information on `App.Locals` and `App.Platform`.

If an endpoint has the same filename as a page (except for the extension), the page will get its props from the endpoint. So a page like `src/routes/items/[id].svelte` could get its props from `src/routes/items/[id].js`:

```js
// @filename: index.js
// ---cut---
import db from '$lib/database';

/** @type {import('@sveltejs/kit').RequestHandler} */
Expand All @@ -99,7 +76,7 @@ export async function get({ params }) {
return {
status: 404
};
}
};
```

> All server-side code, including endpoints, has access to `fetch` in case you need to request data from external APIs. Don't worry about the `$lib` import, we'll get to that [later](/docs/modules#$lib).
Expand All @@ -116,6 +93,7 @@ The job of this function is to return a `{ status, headers, body }` object repre
The returned `body` corresponds to the page's props:

```svelte
/// file: src/routes/items/[id].svelte
<script>
// populated with data from the endpoint
export let item;
Expand All @@ -129,6 +107,7 @@ The returned `body` corresponds to the page's props:
Endpoints can handle any HTTP method — not just `GET` — by exporting the corresponding function:

```js
// @noErrors
export function post(event) {...}
export function put(event) {...}
export function patch(event) {...}
Expand All @@ -138,9 +117,23 @@ export function del(event) {...} // `delete` is a reserved word
These functions can, like `get`, return a `body` that will be passed to the page as props. Whereas 4xx/5xx responses from `get` will result in an error page rendering, similar responses to non-GET requests do not, allowing you to do things like render form validation errors:

```js
// src/routes/items.js
/// file: src/routes/items.js
// @filename: ambient.d.ts
type Item = {
id: string;
};
type ValidationError = {};

declare module '$lib/database' {
export const list: () => Promise<Item[]>;
export const create: (request: Request) => Promise<[Record<string, ValidationError>, Item]>;
}

// @filename: index.js
// ---cut---
import * as db from '$lib/database';

/** @type {import('@sveltejs/kit').RequestHandler} */
export async function get() {
const items = await db.list();

Expand All @@ -149,6 +142,7 @@ export async function get() {
};
}

/** @type {import('@sveltejs/kit').RequestHandler} */
export async function post({ request }) {
const [errors, item] = await db.create(request);

Expand All @@ -171,7 +165,7 @@ export async function post({ request }) {
```

```svelte
<!-- src/routes/items.svelte -->
/// file: src/routes/items.svelte
<script>
// The page always has access to props from `get`...
export let items;
Expand Down Expand Up @@ -204,8 +198,21 @@ If you request the route with an `accept: application/json` header, SvelteKit wi
The `request` object is an instance of the standard [Request](https://developer.mozilla.org/en-US/docs/Web/API/Request) class. As such, accessing the request body is easy:

```js
// @filename: ambient.d.ts
declare global {
const create: (data: any) => any;
}

export {};

// @filename: index.js
// ---cut---
/** @type {import('@sveltejs/kit').RequestHandler} */
export async function post({ request }) {
const data = await request.formData(); // or .json(), or .text(), etc

await create(data);
return { status: 201 };
}
```

Expand All @@ -214,26 +221,38 @@ export async function post({ request }) {
Endpoints can set cookies by returning a `headers` object with `set-cookie`. To set multiple cookies simultaneously, return an array:

```js
return {
headers: {
'set-cookie': [cookie1, cookie2]
}
};
// @filename: ambient.d.ts
const cookie1: string;
const cookie2: string;

// @filename: index.js
// ---cut---
/** @type {import('@sveltejs/kit').RequestHandler} */
export function get() {
return {
headers: {
'set-cookie': [cookie1, cookie2]
}
};
}
```

#### HTTP method overrides

HTML `<form>` elements only support `GET` and `POST` methods natively. You can allow other methods, like `PUT` and `DELETE`, by specifying them in your [configuration](/docs/configuration#methodoverride) and adding a `_method=VERB` parameter (you can configure the name) to the form's `action`:

```js
// svelte.config.js
export default {
/// file: svelte.config.js
/** @type {import('@sveltejs/kit').Config} */
const config = {
kit: {
methodOverride: {
allowed: ['PUT', 'PATCH', 'DELETE']
}
}
};

export default config;
```

```html
Expand Down Expand Up @@ -267,6 +286,7 @@ A route can have multiple dynamic parameters, for example `src/routes/[category]
...in which case a request for `/sveltejs/kit/tree/master/documentation/docs/01-routing.md` would result in the following parameters being available to the page:

```js
// @noErrors
{
org: 'sveltejs',
repo: 'kit',
Expand Down Expand Up @@ -313,7 +333,7 @@ In rare cases, the ordering above might not be want you want for a given path. F
Higher priority routes can _fall through_ to lower priority routes by returning `{ fallthrough: true }`, either from `load` (for pages) or a request handler (for endpoints):

```svelte
<!-- src/routes/foo-[bar].svelte -->
/// file: src/routes/foo-[bar].svelte
<script context="module">
export function load({ params }) {
if (params.bar === 'def') {
Expand All @@ -326,7 +346,10 @@ Higher priority routes can _fall through_ to lower priority routes by returning
```

```js
// src/routes/[a].js
/// file: src/routes/[a].js
// @errors: 2366
/** @type {import('@sveltejs/kit').RequestHandler} */
// ---cut---
export function get({ params }) {
if (params.a === 'foo-def') {
return { fallthrough: true };
Expand Down
21 changes: 5 additions & 16 deletions documentation/docs/02-layouts.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ To create a layout that applies to every page, make a file called `src/routes/__
...but we can add whatever markup, styles and behaviour we want. The only requirement is that the component includes a `<slot>` for the page content. For example, let's add a nav bar:

```html
<!-- src/routes/__layout.svelte -->
/// file: src/routes/__layout.svelte
<nav>
<a href="/">Home</a>
<a href="/about">About</a>
Expand All @@ -28,17 +28,17 @@ To create a layout that applies to every page, make a file called `src/routes/__
If we create pages for `/`, `/about` and `/settings`...

```html
<!-- src/routes/index.svelte -->
/// file: src/routes/index.svelte
<h1>Home</h1>
```

```html
<!-- src/routes/about.svelte -->
/// file: src/routes/about.svelte
<h1>About</h1>
```

```html
<!-- src/routes/settings.svelte -->
/// file: src/routes/settings.svelte
<h1>Settings</h1>
```

Expand All @@ -51,7 +51,7 @@ Suppose we don't just have a single `/settings` page, but instead have nested pa
We can create a layout that only applies to pages below `/settings` (while inheriting the root layout with the top-level nav):

```html
<!-- src/routes/settings/__layout.svelte -->
/// file: src/routes/settings/__layout.svelte
<h1>Settings</h1>

<div class="submenu">
Expand All @@ -76,17 +76,6 @@ For example, if `src/routes/settings/notifications/index.svelte` failed to load,

> SvelteKit provides a default error page in case you don't supply `src/routes/__error.svelte`, but it's recommended that you bring your own.
```ts
// declaration type
// * also see type for `LoadOutput` in the Loading section

export interface ErrorLoadInput<Params extends Record<string, string> = Record<string, string>>
extends LoadInput<Params> {
status?: number;
error?: Error;
}
```

If an error component has a [`load`](/docs/loading) function, it will be called with `error` and `status` properties:

```html
Expand Down
20 changes: 16 additions & 4 deletions documentation/docs/03-loading.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,26 @@ A component that defines a page or a layout can export a `load` function that ru
If the data for a page comes from its endpoint, you may not need a `load` function. It's useful when you need more flexibility, for example loading data from an external API.

```ts
// @filename: ambient.d.ts
declare namespace App {
interface Locals {}
interface Platform {}
interface Session {}
interface Stuff {}
}

type Either<T, U> = Only<T, U> | Only<U, T>;

// @filename: index.ts
// ---cut---
// Type declarations for `load` (declarations marked with
// an `export` keyword can be imported from `@sveltejs/kit`)

export interface Load<Params = Record<string, string>, Props = Record<string, any>> {
(input: LoadInput<Params>): MaybePromise<Either<Fallthrough, LoadOutput<Props>>>;
}

export interface LoadInput<Params extends Record<string, string> = Record<string, string>> {
export interface LoadInput<Params = Record<string, string>> {
url: URL;
params: Params;
props: Record<string, any>;
Expand All @@ -23,7 +35,7 @@ export interface LoadInput<Params extends Record<string, string> = Record<string
stuff: Partial<App.Stuff>;
}

export interface LoadOutput<Props extends Record<string, any> = Record<string, any>> {
export interface LoadOutput<Props = Record<string, any>> {
status?: number;
error?: string | Error;
redirect?: string;
Expand All @@ -44,7 +56,7 @@ interface Fallthrough {
A page that loads data from an external API might look like this:

```html
<!-- src/routes/blog/[slug].svelte -->
/// file: src/routes/blog/[slug].svelte
<script context="module">
/** @type {import('@sveltejs/kit').Load} */
export async function load({ params, fetch, session, stuff }) {
Expand Down Expand Up @@ -102,7 +114,7 @@ The `load` function receives an object containing six fields — `url`, `params`

For a route filename example like `src/routes/a/[b]/[...c]` and a `url.pathname` of `/a/x/y/z`, the `params` object would look like this:

```js
```json
{
"b": "x",
"c": "y/z"
Expand Down
Loading

0 comments on commit e34af0f

Please sign in to comment.