Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Document types #4011

Merged
merged 23 commits into from
Feb 21, 2022
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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ test-results/
package-lock.json
yarn.lock
/packages/create-svelte/template/CHANGELOG.md
/documentation/types.js
.env
.vercel_build_output
.svelte-kit
Expand Down
54 changes: 3 additions & 51 deletions documentation/docs/03-loading.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,63 +4,15 @@ title: Loading

A component that defines a page or a layout can export a `load` function that runs before the component is created. This function runs both during server-side rendering and in the client, and allows you to fetch and manipulate data before the page is rendered, thus preventing loading spinners.

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 = Record<string, string>> {
url: URL;
params: Params;
props: Record<string, any>;
fetch(info: RequestInfo, init?: RequestInit): Promise<Response>;
session: App.Session;
stuff: Partial<App.Stuff>;
}

export interface LoadOutput<Props = Record<string, any>> {
status?: number;
error?: string | Error;
redirect?: string;
props?: Props;
stuff?: Partial<App.Stuff>;
maxage?: number;
}

type MaybePromise<T> = T | Promise<T>;

interface Fallthrough {
fallthrough: true;
}
```

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

A page that loads data from an external API might look like this:
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, which might look like this:

```html
/// file: src/routes/blog/[slug].svelte
<script context="module">
/** @type {import('@sveltejs/kit').Load} */
export async function load({ params, fetch, session, stuff }) {
const response = await fetch(`https://cms.example.com/article/${params.slug}.json`);
const url = `https://cms.example.com/article/${params.slug}.json`;
const response = await fetch(url);

return {
status: response.status,
Expand Down
2 changes: 1 addition & 1 deletion documentation/docs/04-hooks.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
title: Hooks
---

An optional `src/hooks.js` (or `src/hooks.ts`, or `src/hooks/index.js`) file exports four functions, all optional, that run on the server — **handle**, **handleError**, **getSession**, and **externalFetch**.
An optional `src/hooks.js` (or `src/hooks.ts`, or `src/hooks/index.js`) file exports four functions, all optional, that run on the server — `handle`, `handleError`, `getSession`, and `externalFetch`.

> The location of this file can be [configured](/docs/configuration) as `config.kit.files.hooks`

Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,16 @@
---
title: TypeScript
title: Types
---

All APIs in SvelteKit are fully typed. Additionally, it's possible to tell SvelteKit how to type objects inside your app by declaring the `App` namespace. By default, a new project will have a file called `src/app.d.ts` containing the following:
### @sveltejs/kit

All APIs in SvelteKit are fully typed. The following types can be imported from `@sveltejs/kit`:

**TYPES**

### The `App` namespace

It's possible to tell SvelteKit how to type objects inside your app by declaring the `App` namespace. By default, a new project will have a file called `src/app.d.ts` containing the following:

```ts
/// <reference types="@sveltejs/kit" />
Expand All @@ -20,18 +28,18 @@ declare namespace App {

By populating these interfaces, you will gain type safety when using `event.locals`, `event.platform`, `session` and `stuff`:

### App.Locals
#### App.Locals

The interface that defines `event.locals`, which can be accessed in [hooks](/docs/hooks) (`handle`, `handleError` and `getSession`) and [endpoints](/docs/routing#endpoints).

### App.Platform
#### App.Platform

If your adapter provides [platform-specific context](/docs/adapters#supported-environments-platform-specific-context) via `event.platform`, you can specify it here.

### App.Session
#### App.Session

The interface that defines `session`, both as an argument to [`load`](/docs/loading) functions and the value of the [session store](/docs/modules#$app-stores).

### App.Stuff
#### App.Stuff

The interface that defines `stuff`, as input or output to [`load`](/docs/loading) or as the value of the `stuff` property of the [page store](/docs/modules#$app-stores).
5 changes: 3 additions & 2 deletions packages/kit/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@
"svelte-kit.js"
],
"scripts": {
"build": "rollup -c && node scripts/cp.js src/runtime/components assets/components",
"build": "rollup -c && node scripts/cp.js src/runtime/components assets/components && npm run types",
"dev": "rollup -cw",
"lint": "eslint --ignore-path .gitignore --ignore-pattern \"src/packaging/test/**\" \"{src,test}/**/*.{ts,mjs,js,svelte}\" && npm run check-format",
"check": "tsc && svelte-check --ignore test/prerendering,src/packaging/test",
Expand All @@ -74,7 +74,8 @@
"test:integration:amp": "cd test/apps/amp && pnpm test",
"test:integration:basics": "cd test/apps/basics && pnpm test",
"test:integration:options": "cd test/apps/options && pnpm test",
"test:integration:options-2": "cd test/apps/options-2 && pnpm test"
"test:integration:options-2": "cd test/apps/options-2 && pnpm test",
"types": "node scripts/extract-types.js"
},
"exports": {
"./package.json": "./package.json",
Expand Down
56 changes: 56 additions & 0 deletions packages/kit/scripts/extract-types.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import fs from 'fs';
import ts from 'typescript';
import prettier from 'prettier';

const code = fs.readFileSync('types/index.d.ts', 'utf-8');
const node = ts.createSourceFile('index.d.ts', code, ts.ScriptTarget.Latest);

const types = [];

for (const statement of node.statements) {
if (
ts.isClassDeclaration(statement) ||
ts.isInterfaceDeclaration(statement) ||
ts.isTypeAliasDeclaration(statement) ||
ts.isModuleDeclaration(statement)
) {
// @ts-ignore no idea why it's complaining here
const name = statement.name?.escapedText;

let start = statement.pos;
let comment = '';

// @ts-ignore i think typescript is bad at typescript
if (statement.jsDoc) {
// @ts-ignore
comment = statement.jsDoc[0].comment;
// @ts-ignore
start = statement.jsDoc[0].end;
}

const i = code.indexOf('export', start);
start = i + 6;

const snippet = prettier.format(code.slice(start, statement.end).trim(), {
parser: 'typescript',
printWidth: 60,
useTabs: true,
singleQuote: true,
trailingComma: 'none'
});

types.push({ name, comment, snippet });
}
}

// should already be sorted, but just in case
types.sort((a, b) => (a.name < b.name ? -1 : 1));

fs.writeFileSync(
'../../documentation/types.js',
`
/* This file is generated by running \`node scripts/extract-types.js\`
in the packages/kit directory — do not edit it */
export const types = ${JSON.stringify(types, null, ' ')};
`.trim()
);
2 changes: 1 addition & 1 deletion packages/kit/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,6 @@
"types": ["./types/internal"]
}
},
"include": ["src/**/*", "test/**/*", "types/**/*"],
"include": ["scripts/**/*", "src/**/*", "test/**/*", "types/**/*"],
"exclude": ["src/packaging/test/fixtures/**/*", "test/prerendering/*/build/**/*"]
}
87 changes: 39 additions & 48 deletions packages/kit/types/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,6 @@ export class App {

export interface Adapter {
name: string;
headers?: {
host?: string;
protocol?: string;
};
adapt(builder: Builder): Promise<void>;
}

Expand Down Expand Up @@ -150,55 +146,44 @@ export interface Config {
preprocess?: any;
}

/**
* Based on https://github.com/josh-hemphill/csp-typed-directives/blob/latest/src/csp.types.ts
*
* MIT License
*
* Copyright (c) 2021-present, Joshua Hemphill
* Copyright (c) 2021, Tecnico Corporation
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
// Based on https://github.com/josh-hemphill/csp-typed-directives/blob/latest/src/csp.types.ts
//
// MIT License
//
// Copyright (c) 2021-present, Joshua Hemphill
// Copyright (c) 2021, Tecnico Corporation
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.

export namespace Csp {
type SchemeSource = 'http:' | 'https:' | 'data:' | 'mediastream:' | 'blob:' | 'filesystem:';

type HostProtocolSchemes = `${string}://` | '';
type PortScheme = `:${number}` | '' | ':*';
/** Can actually be any string, but typed more explicitly to
* restrict the combined optional types of Source from collapsing to just bing `string` */
type HostNameScheme = `${string}.${string}` | `localhost`;
type HostSource = `${HostProtocolSchemes}${HostNameScheme}${PortScheme}`;

type CryptoSource = `${'nonce' | 'sha256' | 'sha384' | 'sha512'}-${string}`;

type BaseSource = 'self' | 'unsafe-eval' | 'unsafe-hashes' | 'unsafe-inline' | 'none';

export type Source = HostSource | SchemeSource | CryptoSource | BaseSource;
type Sources = Source[];

type ActionSource = 'strict-dynamic' | 'report-sample';

type BaseSource = 'self' | 'unsafe-eval' | 'unsafe-hashes' | 'unsafe-inline' | 'none';
type CryptoSource = `${'nonce' | 'sha256' | 'sha384' | 'sha512'}-${string}`;
type FrameSource = HostSource | SchemeSource | 'self' | 'none';

type HostNameScheme = `${string}.${string}` | `localhost`;
type HostSource = `${HostProtocolSchemes}${HostNameScheme}${PortScheme}`;
type HostProtocolSchemes = `${string}://` | '';
type HttpDelineator = '/' | '?' | '#' | '\\';
type PortScheme = `:${number}` | '' | ':*';
type SchemeSource = 'http:' | 'https:' | 'data:' | 'mediastream:' | 'blob:' | 'filesystem:';
type Source = HostSource | SchemeSource | CryptoSource | BaseSource;
type Sources = Source[];
type UriPath = `${HttpDelineator}${string}`;
}

Expand Down Expand Up @@ -368,6 +353,12 @@ export interface RequestEvent {
platform: Readonly<App.Platform>;
}

/**
* A function exported from an endpoint that corresponds to an
* HTTP verb (get, put, patch, etc) and handles requests with
* that method. Note that since 'delete' is a reserved word in
* JavaScript, delete handles are called 'del' instead.
*/
export interface RequestHandler<Output extends Body = Body> {
(event: RequestEvent): MaybePromise<
Either<Output extends Response ? Response : EndpointOutput<Output>, Fallthrough>
Expand Down
30 changes: 16 additions & 14 deletions sites/kit.svelte.dev/src/lib/docs/Contents.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -84,20 +84,22 @@
{subsection.title}
</a>

<ul>
{#each subsection.sections as subsection}
<li>
<a
class="nested subsection"
class:active={subsection.path === path}
href={subsection.path}
sveltekit:prefetch
>
{subsection.title}
</a>
</li>
{/each}
</ul>
{#if section.path === $page.url.pathname}
<ul>
{#each subsection.sections as subsection}
<li>
<a
class="nested subsection"
class:active={subsection.path === path}
href={subsection.path}
sveltekit:prefetch
>
{subsection.title}
</a>
</li>
{/each}
</ul>
{/if}
</li>
{/each}
</ul>
Expand Down
Loading