Skip to content

Commit

Permalink
document module types (#4099)
Browse files Browse the repository at this point in the history
* document module types

* lint

* document modules from ambient.d.ts

* shuffle things about

* tweak styles

* tweak

* omg yes scroll-margin-top. i love you chris coyier

* various tweaks

* lint

* typo

* sort manually
  • Loading branch information
Rich-Harris committed Feb 24, 2022
1 parent 11b4d97 commit 77590cb
Show file tree
Hide file tree
Showing 11 changed files with 306 additions and 332 deletions.
132 changes: 1 addition & 131 deletions documentation/docs/05-modules.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,134 +4,4 @@ title: Modules

SvelteKit makes a number of modules available to your application.

### $app/env

```js
/// <reference types="@sveltejs/kit" />
// ---cut---
import { amp, browser, dev, mode, prerendering } from '$app/env';
```

- `amp` is `true` or `false` depending on the corresponding value in your [project configuration](/docs/configuration)
- `browser` is `true` or `false` depending on whether the app is running in the browser or on the server
- `dev` is `true` in development mode, `false` in production
- `mode` is the [Vite mode](https://vitejs.dev/guide/env-and-mode.html#modes), which is `development` in dev mode or `production` during build unless configured otherwise in `config.kit.vite.mode`.
- `prerendering` is `true` when [prerendering](/docs/page-options#prerender), `false` otherwise

### $app/navigation

```js
/// <reference types="@sveltejs/kit" />
// ---cut---
import {
afterNavigate,
beforeNavigate,
disableScrollHandling,
goto,
invalidate,
prefetch,
prefetchRoutes
} from '$app/navigation';
```

- `afterNavigate(({ from, to }: { from: URL, to: URL }) => void)` - a lifecycle function that runs when the components mounts, and after subsequent navigations while the component remains mounted
- `beforeNavigate(({ from, to, cancel }: { from: URL, to: URL | null, cancel: () => void }) => void)` — a function that runs whenever navigation is triggered whether by clicking a link, calling `goto`, or using the browser back/forward controls. This includes navigation to external sites. `to` will be `null` if the user is closing the page. Calling `cancel` will prevent the navigation from proceeding
- `disableScrollHandling` will, if called when the page is being updated following a navigation (in `onMount` or an action, for example), prevent SvelteKit from applying its normal scroll management. You should generally avoid this, as breaking user expectations of scroll behaviour can be disorienting.
- `goto(href, { replaceState, noscroll, keepfocus, state })` returns a `Promise` that resolves when SvelteKit navigates (or fails to navigate, in which case the promise rejects) to the specified `href`. The second argument is optional:
- `replaceState` (boolean, default `false`) If `true`, will replace the current `history` entry rather than creating a new one with `pushState`
- `noscroll` (boolean, default `false`) If `true`, the browser will maintain its scroll position rather than scrolling to the top of the page after navigation
- `keepfocus` (boolean, default `false`) If `true`, the currently focused element will retain focus after navigation. Otherwise, focus will be reset to the body
- `state` (object, default `{}`) The state of the new/updated history entry
- `invalidate(href)` causes any `load` functions belonging to the currently active page to re-run if they `fetch` the resource in question. It returns a `Promise` that resolves when the page is subsequently updated.
- `prefetch(href)` programmatically prefetches the given page, which means a) ensuring that the code for the page is loaded, and b) calling the page's `load` function with the appropriate options. This is the same behaviour that SvelteKit triggers when the user taps or mouses over an `<a>` element with [sveltekit:prefetch](/docs/a-options#sveltekit-prefetch). 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 prefetch is complete.
- `prefetchRoutes(routes)` — programmatically prefetches the code for routes that haven't yet been fetched. Typically, you might call this to speed up subsequent navigation. If no argument is given, all routes will be fetched; otherwise, you can specify routes by any matching pathname such as `/about` (to match `src/routes/about.svelte`) or `/blog/*` (to match `src/routes/blog/[slug].svelte`). Unlike `prefetch`, this won't call `load` for individual pages. Returns a `Promise` that resolves when the routes have been prefetched.

### $app/paths

```js
/// <reference types="@sveltejs/kit" />
// ---cut---
import { base, assets } from '$app/paths';
```

- `base` — a root-relative (i.e. begins with a `/`) string that matches [`config.kit.paths.base`](/docs/configuration#paths), or the empty string if unspecified
- `assets` — an absolute URL that matches [`config.kit.paths.assets`](/docs/configuration#paths), if specified, otherwise equal to `base`

> If a value for `config.kit.paths.assets` is specified, it will be replaced with `'/_svelte_kit_assets'` during [`svelte-kit dev`](/docs/cli#svelte-kit-dev) or [`svelte-kit preview`](/docs/cli#svelte-kit-preview), since the assets don't yet live at their eventual URL.
### $app/stores

```js
/// <reference types="@sveltejs/kit" />
// ---cut---
import { getStores, navigating, page, session, updated } from '$app/stores';
```

Stores are _contextual_ — they are added to the [context](https://svelte.dev/tutorial/context-api) of your root component. This means that `session` and `page` are unique to each request on the server, rather than shared between multiple requests handled by the same server simultaneously, which is what makes it safe to include user-specific data in `session`.

Because of that, the stores are not free-floating objects: they must be accessed during component initialisation, like anything else that would be accessed with `getContext`.

- `getStores` is a convenience function around `getContext` that returns `{ navigating, page, session, updated }`. This needs to be called at the top-level or synchronously during component or page initialisation.

The stores themselves attach to the correct context at the point of subscription, which means you can import and use them directly in components without boilerplate. However, it still needs to be called synchronously on component or page initialisation when the `$`-prefix isn't used. Use `getStores` to safely `.subscribe` asynchronously instead.

- `navigating` is a [readable store](https://svelte.dev/tutorial/readable-stores). When navigating starts, its value is `{ from, to }`, where `from` and `to` are both [`URL`](https://developer.mozilla.org/en-US/docs/Web/API/URL) instances. When navigating finishes, its value reverts to `null`.
- `page` contains an object with the current [`url`](https://developer.mozilla.org/en-US/docs/Web/API/URL), [`params`](/docs/loading#input-params), [`stuff`](/docs/loading#output-stuff), [`status`](/docs/loading#output-status) and [`error`](/docs/loading#output-error).
- `session` is a [writable store](https://svelte.dev/tutorial/writable-stores) whose initial value is whatever was returned from [`getSession`](/docs/hooks#getsession). It can be written to, but this will _not_ cause changes to persist on the server — this is something you must implement yourself.
- `updated` is a [readable store](https://svelte.dev/tutorial/readable-stores) whose initial value is false. If [`version.pollInterval`](/docs/configuration#version) is a non-zero value, SvelteKit will poll for new versions of the app and update the store value to `true` when it detects one. `updated.check()` will force an immediate check, regardless of polling.

### $lib

This is a simple alias to `src/lib`, or whatever directory is specified as [`config.kit.files.lib`](/docs/configuration#files). It allows you to access common components and utility modules without `../../../../` nonsense.

### $service-worker

This module is only available to [service workers](/docs/service-workers).

```js
/// <reference types="@sveltejs/kit" />
// ---cut---
import { build, files, timestamp } from '$service-worker';
```

- `build` is an array of URL strings representing the files generated by Vite, suitable for caching with `cache.addAll(build)`
- `files` is an array of URL strings representing the files in your `static` directory, or whatever directory is specified by [`config.kit.files.assets`](/docs/configuration). You can customize which files are included from `static` directory using [`config.kit.serviceWorker.files`](/docs/configuration)
- `timestamp` is the result of calling `Date.now()` at build time. It's useful for generating unique cache names inside your service worker, so that a later deployment of your app can invalidate old caches

### @sveltejs/kit/hooks

This module provides a helper function to sequence multiple `handle` calls.

```js
/// file: src/hooks.js
/// <reference types="@sveltejs/kit" />
// ---cut---
import { sequence } from '@sveltejs/kit/hooks';

/** @type {import('@sveltejs/kit').Handle} */
async function first({ event, resolve }) {
console.log('first pre-processing');
const result = await resolve(event);
console.log('first post-processing');
return result;
}

/** @type {import('@sveltejs/kit').Handle} */
async function second({ event, resolve }) {
console.log('second pre-processing');
const result = await resolve(event);
console.log('second post-processing');
return result;
}

export const handle = sequence(first, second);
```

The example above would print:

```
first pre-processing
second pre-processing
second post-processing
first post-processing
```
**EXPORTS**
40 changes: 0 additions & 40 deletions documentation/docs/14-types.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,44 +2,4 @@
title: Types
---

### @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" />

declare namespace App {
interface Locals {}

interface Platform {}

interface Session {}

interface Stuff {}
}
```

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

#### 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

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

#### 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

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).
134 changes: 96 additions & 38 deletions packages/kit/scripts/extract-types.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,55 +2,113 @@ 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;
}
/** @typedef {{ name: string, comment: string, snippet: string }} Extracted */

/** @type {Array<{ name: string, comment: string, exports: Extracted[], types: Extracted[] }>} */
const modules = [];

/**
* @param {string} code
* @param {ts.NodeArray<ts.Statement>} statements
*/
function get_types(code, statements) {
/** @type {Extracted[]} */
const exports = [];

/** @type {Extracted[]} */
const types = [];

for (const statement of statements) {
if (
ts.isClassDeclaration(statement) ||
ts.isInterfaceDeclaration(statement) ||
ts.isTypeAliasDeclaration(statement) ||
ts.isModuleDeclaration(statement) ||
ts.isVariableStatement(statement) ||
ts.isFunctionDeclaration(statement)
) {
const name_node = ts.isVariableStatement(statement)
? statement.declarationList.declarations[0]
: statement;

const i = code.indexOf('export', start);
start = i + 6;
// @ts-ignore no idea why it's complaining here
const name = name_node.name?.escapedText;

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

types.push({ name, comment, snippet });
// @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'
});

const collection =
ts.isVariableStatement(statement) || ts.isFunctionDeclaration(statement) ? exports : types;

collection.push({ name, comment, snippet });
} else {
// console.log(statement.kind);
}
}

types.sort((a, b) => (a.name < b.name ? -1 : 1));
exports.sort((a, b) => (a.name < b.name ? -1 : 1));

return { types, exports };
}

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

modules.push({
name: '@sveltejs/kit',
comment: 'The following types can be imported from `@sveltejs/kit`:',
...get_types(code, node.statements)
});
}

// should already be sorted, but just in case
types.sort((a, b) => (a.name < b.name ? -1 : 1));
{
const code = fs.readFileSync('types/ambient.d.ts', 'utf-8');
const node = ts.createSourceFile('ambient.d.ts', code, ts.ScriptTarget.Latest);

for (const statement of node.statements) {
if (ts.isModuleDeclaration(statement)) {
// @ts-ignore
const name = statement.name.text || statement.name.escapedText;

// @ts-ignore
const comment = statement.jsDoc?.[0].comment ?? '';

modules.push({
name,
comment,
// @ts-ignore
...get_types(code, statement.body.statements)
});
}
}
}

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, ' ')};
export const modules = ${JSON.stringify(modules, null, ' ')};
`.trim()
);
7 changes: 3 additions & 4 deletions packages/kit/src/node.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Readable } from 'stream';

/** @type {import('@sveltejs/kit/node').GetRawBody} */
/** @param {import('http').IncomingMessage} req */
function get_raw_body(req) {
return new Promise((fulfil, reject) => {
const h = req.headers;
Expand Down Expand Up @@ -50,7 +50,7 @@ function get_raw_body(req) {
});
}

/** @type {import('@sveltejs/kit/node').GetRequest} */
/** @type {import('@sveltejs/kit/node').getRequest} */
export async function getRequest(base, req) {
let headers = /** @type {Record<string, string>} */ (req.headers);
if (req.httpVersionMajor === 2) {
Expand All @@ -69,9 +69,8 @@ export async function getRequest(base, req) {
});
}

/** @type {import('@sveltejs/kit/node').SetResponse} */
/** @type {import('@sveltejs/kit/node').setResponse} */
export async function setResponse(res, response) {
/** @type {import('types').ResponseHeaders} */
const headers = Object.fromEntries(response.headers);

if (response.headers.has('set-cookie')) {
Expand Down
Loading

0 comments on commit 77590cb

Please sign in to comment.