diff --git a/documentation/docs/00-introduction.md b/documentation/docs/00-introduction.md index 073e9183e8ef..bb43ddaee962 100644 --- a/documentation/docs/00-introduction.md +++ b/documentation/docs/00-introduction.md @@ -6,13 +6,13 @@ title: Introduction > SvelteKit is in early development, and some things may change before we hit version 1.0. This document is a work-in-progress. If you get stuck, reach out for help in the [Discord chatroom](https://svelte.dev/chat). > -> See the [migration guides](/migrating) for help upgrading from Sapper. +> See the [migration guides](/docs/migrating) for help upgrading from Sapper. ### What is SvelteKit? SvelteKit is a framework for building extremely high-performance web apps. -Building an app with all the modern best practices is fiendishly complicated. Those practices include [build optimizations](https://vitejs.dev/guide/features.html#build-optimizations), so that you load only the minimal required code; [offline support](#service-workers); [prefetching](#anchor-options-sveltekit-prefetch) pages before the user initiates navigation; and [configurable rendering](#page-options) that allows you to generate HTML [on the server](#appendix-ssr) or [in the browser](#page-options-router) at runtime or [at build-time](#page-options-prerender). SvelteKit does all the boring stuff for you so that you can get on with the creative part. +Building an app with all the modern best practices is fiendishly complicated. Those practices include [build optimizations](https://vitejs.dev/guide/features.html#build-optimizations), so that you load only the minimal required code; [offline support](/docs/service-workers); [prefetching](/docs/a-options#sveltekit-prefetch) pages before the user initiates navigation; and [configurable rendering](/docs/page-options) that allows you to generate HTML [on the server](/docs/appendix#ssr) or [in the browser](/docs/page-options#router) at runtime or [at build-time](/docs/page-options#prerender). SvelteKit does all the boring stuff for you so that you can get on with the creative part. It uses [Vite](https://vitejs.dev/) with a [Svelte plugin](https://github.com/sveltejs/vite-plugin-svelte) to provide a lightning-fast and feature-rich development experience with [Hot Module Replacement (HMR)](https://github.com/sveltejs/vite-plugin-svelte/blob/main/docs/config.md#hot), where changes to your code are reflected in the browser instantly. diff --git a/documentation/docs/01-routing.md b/documentation/docs/01-routing.md index 7f462a039156..9ba9e5792e40 100644 --- a/documentation/docs/01-routing.md +++ b/documentation/docs/01-routing.md @@ -4,17 +4,17 @@ title: Routing At the heart of SvelteKit is a _filesystem-based router_. This means that the structure of your application is defined by the structure of your codebase — specifically, the contents of `src/routes`. -> You can change this to a different directory by editing the [project config](#configuration). +> You can change this to a different directory by editing the [project config](/docs/configuration). There are two types of route — **pages** and **endpoints**. Pages typically generate HTML to display to the user (as well as any CSS and JavaScript needed for the page). By default, pages are rendered on both the client and server, though this behaviour is configurable. -Endpoints run only on the server (or when you build your site, if [prerendering](#page-options-prerender)). This means it's the place to do things like access databases or APIs that require private credentials or return data that lives on a machine in your production network. Pages can request data from endpoints. Endpoints return JSON by default, though may also return data in other formats. +Endpoints run only on the server (or when you build your site, if [prerendering](/docs/page-options#prerender)). This means it's the place to do things like access databases or APIs that require private credentials or return data that lives on a machine in your production network. Pages can request data from endpoints. Endpoints return JSON by default, though may also return data in other formats. ### Pages -Pages are Svelte components written in `.svelte` files (or any file with an extension listed in [`config.extensions`](#configuration)). By default, when a user first visits the application, they will be served a server-rendered version of the page in question, plus some JavaScript that 'hydrates' the page and initialises a client-side router. From that point forward, navigating to other pages is handled entirely on the client for a fast, app-like feel where the common portions in the layout do not need to be rerendered. +Pages are Svelte components written in `.svelte` files (or any file with an extension listed in [`config.extensions`](/docs/configuration)). By default, when a user first visits the application, they will be served a server-rendered version of the page in question, plus some JavaScript that 'hydrates' the page and initialises a client-side router. From that point forward, navigating to other pages is handled entirely on the client for a fast, app-like feel where the common portions in the layout do not need to be rerendered. The filename determines the route. For example, `src/routes/index.svelte` is the root of your site: @@ -78,7 +78,7 @@ interface Fallthrough { } ``` -> See the [TypeScript](#typescript) section for information on `App.Locals` and `App.Platform`. +> See the [TypeScript](/docs/typescript) section for information on `App.Locals` and `App.Platform`. A page like `src/routes/items/[id].svelte` could get its data from `src/routes/items/[id].js`: @@ -102,7 +102,7 @@ export async function get({ params }) { } ``` -> 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](#modules-$lib). +> 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). The job of this function is to return a `{ status, headers, body }` object representing the response, where `status` is an [HTTP status code](https://httpstatusdogs.com): @@ -111,7 +111,7 @@ The job of this function is to return a `{ status, headers, body }` object repre - `4xx` — client error - `5xx` — server error -> If `{fallthrough: true}` is returned SvelteKit will [fall through](#routing-advanced-routing-fallthrough-routes) to other routes until something responds, or will respond with a generic 404. +> If `{fallthrough: true}` is returned SvelteKit will [fall through](/docs/routing#advanced-routing-fallthrough-routes) to other routes until something responds, or will respond with a generic 404. The returned `body` corresponds to the page's props: @@ -223,7 +223,7 @@ return { #### HTTP method overrides -HTML `
` elements only support `GET` and `POST` methods natively. You can allow other methods, like `PUT` and `DELETE`, by specifying them in your [configuration](#configuration-methodoverride) and adding a `_method=VERB` parameter (you can configure the name) to the form's `action`: +HTML `` 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 @@ -252,7 +252,7 @@ Most commonly, endpoints exist to provide data to the page with which they're pa ### Private modules -Files and directories with a leading `_` or `.` (other than [`.well-known`](https://en.wikipedia.org/wiki/Well-known_URI)) are private by default, meaning that they do not create routes (but can be imported by files that do). You can configure which modules are considered public or private with the [`routes`](#configuration-routes) configuration. +Files and directories with a leading `_` or `.` (other than [`.well-known`](https://en.wikipedia.org/wiki/Well-known_URI)) are private by default, meaning that they do not create routes (but can be imported by files that do). You can configure which modules are considered public or private with the [`routes`](/docs/configuration#routes) configuration. ### Advanced routing @@ -288,6 +288,6 @@ src/routes/[qux].svelte src/routes/foo-[bar].svelte ``` -... and you navigate to `/foo-xyz`, then SvelteKit will first try `foo-[bar].svelte` because it is the best match. If that yields no response, SvelteKit will try other less specific yet still valid matches for `/foo-xyz`. Since endpoints have higher precedence than pages, the next attempt will be `[baz].js`. Then alphabetical order takes precedence and thus `[baz].svelte` will be tried before `[qux].svelte`. The first route that responds — a page that returns something from [`load`](#loading) or has no `load` function, or an endpoint that returns something — will handle the request. +... and you navigate to `/foo-xyz`, then SvelteKit will first try `foo-[bar].svelte` because it is the best match. If that yields no response, SvelteKit will try other less specific yet still valid matches for `/foo-xyz`. Since endpoints have higher precedence than pages, the next attempt will be `[baz].js`. Then alphabetical order takes precedence and thus `[baz].svelte` will be tried before `[qux].svelte`. The first route that responds — a page that returns something from [`load`](/docs/loading) or has no `load` function, or an endpoint that returns something — will handle the request. If no page or endpoint responds to a request, SvelteKit will respond with a generic 404. diff --git a/documentation/docs/02-layouts.md b/documentation/docs/02-layouts.md index 9174dc651a9b..f661c6225527 100644 --- a/documentation/docs/02-layouts.md +++ b/documentation/docs/02-layouts.md @@ -70,7 +70,7 @@ Layout resets are otherwise identical to normal layout components. ### Error pages -If a page fails to load (see [Loading](#loading)), SvelteKit will render an error page. You can customise this page by creating `__error.svelte` components alongside your layout and page components. +If a page fails to load (see [Loading](/docs/loading)), SvelteKit will render an error page. You can customise this page by creating `__error.svelte` components alongside your layout and page components. For example, if `src/routes/settings/notifications/index.svelte` failed to load, SvelteKit would render `src/routes/settings/notifications/__error.svelte` in the same layout, if it existed. If not, it would render `src/routes/settings/__error.svelte` in the parent layout, or `src/routes/__error.svelte` in the root layout. @@ -87,7 +87,7 @@ export interface ErrorLoadInput = Record @@ -108,6 +108,6 @@ If an error component has a [`load`](#loading) function, it will be called with

{title}

``` -> Layout components also have access to `error` and `status` via the [page store](#modules-$app-stores) +> Layout components also have access to `error` and `status` via the [page store](/docs/modules#$app-stores) > > Server-side stack traces will be removed from `error` in production, to avoid exposing privileged information to users. diff --git a/documentation/docs/03-loading.md b/documentation/docs/03-loading.md index 6eb0a978d501..70ac88f6ef8e 100644 --- a/documentation/docs/03-loading.md +++ b/documentation/docs/03-loading.md @@ -39,7 +39,7 @@ interface Fallthrough { } ``` -> See the [TypeScript](#typescript) section for information on `App.Session` and `App.Stuff`. +> 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: @@ -64,7 +64,7 @@ A page that loads data from an external API might look like this: `load` is similar to `getStaticProps` or `getServerSideProps` in Next.js, except that it runs on both the server and the client. In the example above, if a user clicks on a link to this page the data will be fetched from `cms.example.com` without going via our server. -If `load` returns `{fallthrough: true}`, SvelteKit will [fall through](#routing-advanced-routing-fallthrough-routes) to other routes until something responds, or will respond with a generic 404. +If `load` returns `{fallthrough: true}`, SvelteKit will [fall through](/docs/routing#advanced-routing-fallthrough-routes) to other routes until something responds, or will respond with a generic 404. SvelteKit's `load` receives an implementation of `fetch`, which has the following special properties: @@ -72,11 +72,11 @@ SvelteKit's `load` receives an implementation of `fetch`, which has the followin - it can make requests against the app's own endpoints without issuing an HTTP call - it makes a copy of the response when you use it, and then sends it embedded in the initial page load for hydration -`load` only applies to [page](#routing-pages) and [layout](#layouts) components (not components they import), and runs on both the server and in the browser with the default rendering options. +`load` only applies to [page](/docs/routing#pages) and [layout](/docs/layouts) components (not components they import), and runs on both the server and in the browser with the default rendering options. > Code called inside `load` blocks: > -> - should use the SvelteKit-provided [`fetch`](#loading-input-fetch) wrapper rather than using the native `fetch` +> - should use the SvelteKit-provided [`fetch`](/docs/loading#input-fetch) wrapper rather than using the native `fetch` > - should not reference `window`, `document`, or any browser-specific objects > - should not directly reference any API keys or secrets, which will be exposed to the client, but instead call an endpoint that uses any required secrets @@ -94,7 +94,7 @@ The `load` function receives an object containing six fields — `url`, `params` `url` is an instance of [`URL`](https://developer.mozilla.org/en-US/docs/Web/API/URL), containing properties like the `origin`, `hostname`, `pathname` and `searchParams`. -> In some environments this is derived from request headers, which you [may need to configure](#configuration-headers), during server-side rendering +> In some environments this is derived from request headers during server-side rendering. If you're using [adapter-node]/docs/adapters#supported-environments-node-js, for example, you may need to configure the adapter in order for the URL to be correct. #### params @@ -123,7 +123,7 @@ If the page you're loading has an endpoint, the data returned from it is accessi #### session -`session` can be used to pass data from the server related to the current request, e.g. the current user. By default it is `undefined`. See [`getSession`](#hooks-getsession) to learn how to use it. +`session` can be used to pass data from the server related to the current request, e.g. the current user. By default it is `undefined`. See [`getSession`](/docs/hooks#getsession) to learn how to use it. #### stuff @@ -161,4 +161,4 @@ If the `load` function returns a `props` object, the props will be passed to the This will be merged with any existing `stuff` and passed to the `load` functions of subsequent layout and page components. -The combined `stuff` is available to components using the [page store](#modules-$app-stores) as `$page.stuff`, providing a mechanism for pages to pass data 'upward' to layouts. +The combined `stuff` is available to components using the [page store](/docs/modules#$app-stores) as `$page.stuff`, providing a mechanism for pages to pass data 'upward' to layouts. diff --git a/documentation/docs/04-hooks.md b/documentation/docs/04-hooks.md index 46e704713462..af30e83ac071 100644 --- a/documentation/docs/04-hooks.md +++ b/documentation/docs/04-hooks.md @@ -4,11 +4,11 @@ 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**. -> The location of this file can be [configured](#configuration) as `config.kit.files.hooks` +> The location of this file can be [configured](/docs/configuration) as `config.kit.files.hooks` ### handle -This function runs every time SvelteKit receives a request — whether that happens while the app is running, or during [prerendering](#page-options-prerender) — and determines the response. It receives an `event` object representing the request and a function called `resolve`, which invokes SvelteKit's router and generates a response (rendering a page, or invoking an endpoint) accordingly. This allows you to modify response headers or bodies, or bypass SvelteKit entirely (for implementing endpoints programmatically, for example). +This function runs every time SvelteKit receives a request — whether that happens while the app is running, or during [prerendering](/docs/page-options#prerender) — and determines the response. It receives an `event` object representing the request and a function called `resolve`, which invokes SvelteKit's router and generates a response (rendering a page, or invoking an endpoint) accordingly. This allows you to modify response headers or bodies, or bypass SvelteKit entirely (for implementing endpoints programmatically, for example). > Requests for static assets — which includes pages that were already prerendered — are _not_ handled by SvelteKit. @@ -38,7 +38,7 @@ export interface Handle { } ``` -> See the [TypeScript](#typescript) section for information on `App.Locals` and `App.Platform`. +> See the [TypeScript](/docs/typescript) section for information on `App.Locals` and `App.Platform`. To add custom data to the request, which is passed to endpoints, populate the `event.locals` object, as shown below. @@ -54,7 +54,7 @@ export async function handle({ event, resolve }) { } ``` -You can add call multiple `handle` functions with [the `sequence` helper function](#modules-sveltejs-kit-hooks). +You can add call multiple `handle` functions with [the `sequence` helper function](/docs/modules#sveltejs-kit-hooks). `resolve` also supports a second, optional parameter that gives you more control over how the response will be rendered. That parameter is an object that can have the following fields: @@ -71,7 +71,7 @@ export async function handle({ event, resolve }) { } ``` -> Disabling [server-side rendering](#appendix-ssr) effectively turns your SvelteKit app into a [**single-page app** or SPA](#appendix-csr-and-spa). In most situations this is not recommended ([see appendix](#appendix-ssr)). Consider whether it's truly appropriate to disable it, and do so selectively rather than for all requests. +> Disabling [server-side rendering](/docs/appendix#ssr) effectively turns your SvelteKit app into a [**single-page app** or SPA](/docs/appendix#csr-and-spa). In most situations this is not recommended ([see appendix](/docs/appendix#ssr)). Consider whether it's truly appropriate to disable it, and do so selectively rather than for all requests. ### handleError @@ -99,7 +99,7 @@ export async function handleError({ error, event }) { ### getSession -This function takes the `event` object and returns a `session` object that is [accessible on the client](#modules-$app-stores) and therefore must be safe to expose to users. It runs whenever SvelteKit server-renders a page. +This function takes the `event` object and returns a `session` object that is [accessible on the client](/docs/modules#$app-stores) and therefore must be safe to expose to users. It runs whenever SvelteKit server-renders a page. If unimplemented, session is `{}`. diff --git a/documentation/docs/05-modules.md b/documentation/docs/05-modules.md index 37734412dd1f..14c90ab3ece7 100644 --- a/documentation/docs/05-modules.md +++ b/documentation/docs/05-modules.md @@ -10,11 +10,11 @@ SvelteKit makes a number of modules available to your application. import { amp, browser, dev, mode, prerendering } from '$app/env'; ``` -- `amp` is `true` or `false` depending on the corresponding value in your [project configuration](#configuration) +- `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](#page-options-prerender), `false` otherwise +- `prerendering` is `true` when [prerendering](/docs/page-options#prerender), `false` otherwise ### $app/navigation @@ -39,7 +39,7 @@ import { - `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 `` element with [sveltekit:prefetch](#anchor-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. +- `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 `` 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 @@ -48,10 +48,10 @@ import { import { base, assets } from '$app/paths'; ``` -- `base` — a root-relative (i.e. begins with a `/`) string that matches [`config.kit.paths.base`](#configuration-paths), or the empty string if unspecified -- `assets` — an absolute URL that matches [`config.kit.paths.assets`](#configuration-paths), if specified, otherwise equal to `base` +- `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`](#command-line-interface-svelte-kit-dev) or [`svelte-kit preview`](#command-line-interface-svelte-kit-preview), since the assets don't yet live at their eventual URL. +> 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 @@ -68,9 +68,9 @@ Because of that, the stores are not free-floating objects: they must be accessed 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`](#loading-input-params), [`stuff`](#loading-output-stuff), [`status`](#loading-output-status) and [`error`](#loading-output-error). -- `session` is a [writable store](https://svelte.dev/tutorial/writable-stores) whose initial value is whatever was returned from [`getSession`](#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`](#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. +- `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 @@ -78,14 +78,14 @@ This is a simple alias to `src/lib`, or whatever directory is specified as [`con ### $service-worker -This module is only available to [service workers](#service-workers). +This module is only available to [service workers](/docs/service-workers). ```js 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`](#configuration). You can customize which files are included from `static` directory using [`config.kit.serviceWorker.files`](#configuration) +- `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 diff --git a/documentation/docs/06-service-workers.md b/documentation/docs/06-service-workers.md index b0eb335458bf..539a46f57d80 100644 --- a/documentation/docs/06-service-workers.md +++ b/documentation/docs/06-service-workers.md @@ -6,8 +6,8 @@ Service workers act as proxy servers that handle network requests inside your ap In SvelteKit, if you have a `src/service-worker.js` file (or `src/service-worker.ts`, or `src/service-worker/index.js`, etc) it will be built with Vite and automatically registered. You can disable automatic registration if you need to register the service worker with your own logic (e.g. prompt user for update, configure periodic updates, use `workbox`, etc). -> You can change the location of your service worker and disable automatic registration in your [project configuration](#configuration-files). +> You can change the location of your service worker and disable automatic registration in your [project configuration](/docs/configuration#files). -Inside the service worker you have access to the [`$service-worker` module](#modules-$service-worker). +Inside the service worker you have access to the [`$service-worker` module](/docs/modules#$service-worker). -Because it needs to be bundled (since browsers don't yet support `import` in this context), and depends on the client-side app's build manifest, **service workers only work in the production build, not in development**. To test it locally, use [`svelte-kit preview`](#command-line-interface-svelte-kit-preview). +Because it needs to be bundled (since browsers don't yet support `import` in this context), and depends on the client-side app's build manifest, **service workers only work in the production build, not in development**. To test it locally, use [`svelte-kit preview`](/docs/cli#svelte-kit-preview). diff --git a/documentation/docs/07-a-options.md b/documentation/docs/07-a-options.md index 09674c14ffac..b81d5afc7daf 100644 --- a/documentation/docs/07-a-options.md +++ b/documentation/docs/07-a-options.md @@ -16,7 +16,7 @@ We can mitigate that by _prefetching_ the data. Adding a `sveltekit:prefetch` at ...will cause SvelteKit to run the page's `load` function as soon as the user hovers over the link (on a desktop) or touches it (on mobile), rather than waiting for the `click` event to trigger navigation. Typically, this buys us an extra couple of hundred milliseconds, which is the difference between a user interface that feels laggy, and one that feels snappy. -Note that prefetching will not work if the [`router`](#page-options-router) setting is `false`. +Note that prefetching will not work if the [`router`](/docs/page-options#router) setting is `false`. You can also programmatically invoke `prefetch` from `$app/navigation`. diff --git a/documentation/docs/09-amp.md b/documentation/docs/09-amp.md index 9feae9cada1a..b3f6d4673c94 100644 --- a/documentation/docs/09-amp.md +++ b/documentation/docs/09-amp.md @@ -2,7 +2,7 @@ title: AMP --- -An unfortunate reality of modern web development is that it is sometimes necessary to create an [AMP](https://amp.dev/) version of your site. In SvelteKit this can be done by setting the [`amp`](#configuration-amp) config option, which has the following effects: +An unfortunate reality of modern web development is that it is sometimes necessary to create an [AMP](https://amp.dev/) version of your site. In SvelteKit this can be done by setting the [`amp`](/docs/configuration#amp) config option, which has the following effects: - Client-side JavaScript, including the router, is disabled - Styles are concatenated into ` diff --git a/sites/kit.svelte.dev/src/lib/docs/index.js b/sites/kit.svelte.dev/src/lib/docs/index.js index 1f35017b6128..775941bcb749 100644 --- a/sites/kit.svelte.dev/src/lib/docs/index.js +++ b/sites/kit.svelte.dev/src/lib/docs/index.js @@ -1,11 +1,10 @@ import fs from 'fs'; -import { marked } from 'marked'; import PrismJS from 'prismjs'; - import 'prismjs/components/prism-bash.js'; import 'prismjs/components/prism-diff.js'; import 'prismjs/components/prism-typescript.js'; import 'prism-svelte'; +import { extract_frontmatter, transform } from './markdown'; const languages = { bash: 'bash', @@ -19,113 +18,135 @@ const languages = { '': '' }; -/** @param {string} dir */ -export function read(dir) { - return fs - .readdirSync(`../../documentation/${dir}`) - .map((file) => { - const match = /\d{2}-(.+)\.md/.exec(file); - if (!match) return; +const base = '../../documentation'; + +/** + * @param {string} dir + * @param {string} file + */ +export function read_file(dir, file) { + const match = /\d{2}-(.+)\.md/.exec(file); + if (!match) return null; + + const slug = match[1]; + + const markdown = fs.readFileSync(`${base}/${dir}/${file}`, 'utf-8'); + + return { + file: `${dir}/${file}`, + slug: match[1], + // third argument is a gross hack to accommodate FAQ + ...parse(markdown, file, dir === 'faq' ? slug : undefined) + }; +} + +/** + * @param {string} dir + * @param {string} slug + */ +export function read(dir, slug) { + const files = fs.readdirSync(`${base}/${dir}`).filter((file) => /^\d{2}-(.+)\.md$/.test(file)); + const index = files.findIndex((file) => file.slice(3, -3) === slug); - const slug = match[1]; + if (index === -1) return null; - const markdown = fs.readFileSync(`../../documentation/${dir}/${file}`, 'utf-8'); - const { title, sections, content } = parse(markdown, file); + const prev = index > 0 && files[index - 1]; + const next = index < files.length - 1 && files[index + 1]; - return { title, slug, file, sections, content }; - }) + const summarise = (file) => ({ + slug: file.slice(3, -3), // remove 00- prefix and .md suffix + title: extract_frontmatter(fs.readFileSync(`${base}/${dir}/${file}`, 'utf8')).metadata.title + }); + + return { + prev: prev && summarise(prev), + next: next && summarise(next), + section: read_file(dir, files[index]) + }; +} + +/** @param {string} dir */ +export function read_all(dir) { + return fs + .readdirSync(`${base}/${dir}`) + .map((file) => read_file(dir, file)) .filter(Boolean); } -function parse(markdown, file) { +/** + * @param {string} markdown + * @param {string} file + * @param {string} [main_slug] + */ +function parse(markdown, file, main_slug) { const { body, metadata } = extract_frontmatter(markdown); - const slug = slugify(metadata.title); - - const headings = [, slug]; + const headings = main_slug ? [main_slug] : []; const sections = []; let section; - marked.use({ - renderer: { - heading(html, level) { - const title = html - .replace(/"/g, '"') - .replace(/</g, '<') - .replace(/>/g, '>'); - - const normalized = slugify(title); - - headings[level - 1] = normalized; - headings.length = level; - - const slug = headings.filter(Boolean).join('-'); - - if (level === 3) { - section = { - title, - slug, - sections: [] - }; - - sections.push(section); - } else if (level === 4) { - section.sections.push({ - title, - slug - }); - } else { - throw new Error(`Unexpected in ${file}`); - } + const content = transform(body, { + heading(html, level) { + const title = html + .replace(/"/g, '"') + .replace(/</g, '<') + .replace(/>/g, '>'); - return `${html}`; - }, - code(source, lang) { - // for no good reason at all, marked replaces tabs with spaces - source = source.replace(/^( )+/gm, (match) => { - let tabs = ''; - for (let i = 0; i < match.length; i += 4) { - tabs += '\t'; - } - return tabs; - }); + const normalized = slugify(title); + + headings[level - 1] = normalized; + headings.length = level; - const plang = languages[lang]; - const highlighted = plang - ? PrismJS.highlight(source, PrismJS.languages[plang], lang) - : source.replace(/[&<>]/g, (c) => ({ '&': '&', '<': '<', '>': '>' }[c])); + const slug = headings.filter(Boolean).join('-'); - return `
${highlighted}
`; + if (level === 3) { + section = { + title, + slug, + sections: [] + }; + + sections.push(section); + } else if (level === 4) { + section.sections.push({ + title, + slug + }); + } else { + throw new Error(`Unexpected in ${file}`); } + + return `${html}
permalink`; + }, + code(source, lang) { + // for no good reason at all, marked replaces tabs with spaces + source = source.replace(/^( )+/gm, (match) => { + let tabs = ''; + for (let i = 0; i < match.length; i += 4) { + tabs += '\t'; + } + return tabs; + }); + + const plang = languages[lang]; + const highlighted = plang + ? PrismJS.highlight(source, PrismJS.languages[plang], lang) + : source.replace(/[&<>]/g, (c) => ({ '&': '&', '<': '<', '>': '>' }[c])); + + return `
${highlighted}
`; } }); - const content = marked(body); - return { - slug, title: metadata.title, sections, content }; } -function extract_frontmatter(markdown) { - const match = /---\r?\n([\s\S]+?)\r?\n---/.exec(markdown); - const frontmatter = match[1]; - const body = markdown.slice(match[0].length); - - const metadata = {}; - frontmatter.split('\n').forEach((pair) => { - const i = pair.indexOf(':'); - metadata[pair.slice(0, i).trim()] = pair.slice(i + 1).trim(); - }); - - return { metadata, body }; -} - -function slugify(title) { +/** @param {string} title */ +export function slugify(title) { return title .toLowerCase() .replace(/[^a-z0-9-$]/g, '-') diff --git a/sites/kit.svelte.dev/src/lib/docs/markdown.js b/sites/kit.svelte.dev/src/lib/docs/markdown.js new file mode 100644 index 000000000000..568a69e77e4c --- /dev/null +++ b/sites/kit.svelte.dev/src/lib/docs/markdown.js @@ -0,0 +1,186 @@ +import { marked } from 'marked'; + +const escapeTest = /[&<>"']/; +const escapeReplace = /[&<>"']/g; +const escapeTestNoEncode = /[<>"']|&(?!#?\w+;)/; +const escapeReplaceNoEncode = /[<>"']|&(?!#?\w+;)/g; +const escapeReplacements = { + '&': '&', + '<': '<', + '>': '>', + '"': '"', + "'": ''' +}; +const getEscapeReplacement = (ch) => escapeReplacements[ch]; + +/** + * @param {string} html + * @param {boolean} encode + */ +export function escape(html, encode) { + if (encode) { + if (escapeTest.test(html)) { + return html.replace(escapeReplace, getEscapeReplacement); + } + } else { + if (escapeTestNoEncode.test(html)) { + return html.replace(escapeReplaceNoEncode, getEscapeReplacement); + } + } + + return html; +} + +/** @type {Partial} */ +const default_renderer = { + code(code, infostring, escaped) { + const lang = (infostring || '').match(/\S*/)[0]; + + code = code.replace(/\n$/, '') + '\n'; + + if (!lang) { + return '
' + (escaped ? code : escape(code, true)) + '
\n'; + } + + return ( + '
' +
+			(escaped ? code : escape(code, true)) +
+			'
\n' + ); + }, + + blockquote(quote) { + return '
\n' + quote + '
\n'; + }, + + html(html) { + return html; + }, + + heading(text, level) { + return '' + text + '\n'; + }, + + hr() { + return '
\n'; + }, + + list(body, ordered, start) { + const type = ordered ? 'ol' : 'ul', + startatt = ordered && start !== 1 ? ' start="' + start + '"' : ''; + return '<' + type + startatt + '>\n' + body + '\n'; + }, + + listitem(text) { + return '
  • ' + text + '
  • \n'; + }, + + checkbox(checked) { + return ' '; + }, + + paragraph(text) { + return '

    ' + text + '

    \n'; + }, + + table(header, body) { + if (body) body = '' + body + ''; + + return '\n' + '\n' + header + '\n' + body + '
    \n'; + }, + + tablerow(content) { + return '\n' + content + '\n'; + }, + + tablecell(content, flags) { + const type = flags.header ? 'th' : 'td'; + const tag = flags.align ? '<' + type + ' align="' + flags.align + '">' : '<' + type + '>'; + return tag + content + '\n'; + }, + + // span level renderer + strong(text) { + return '' + text + ''; + }, + + em(text) { + return '' + text + ''; + }, + + codespan(text) { + return '' + text + ''; + }, + + br() { + return '
    '; + }, + + del(text) { + return '' + text + ''; + }, + + link(href, title, text) { + if (href === null) { + return text; + } + let out = ''; + return out; + }, + + image(href, title, text) { + if (href === null) { + return text; + } + + let out = '' + text + '} renderer + */ +export function transform(markdown, renderer = {}) { + marked.use({ + renderer: { + // we have to jump through these hoops because of marked's API design choices — + // options are global, and merged in confusing ways. You can't do e.g. + // `new Marked(options).parse(markdown)` + ...default_renderer, + ...renderer + } + }); + + return marked(markdown); +} + +/** @param {string} markdown */ +export function extract_frontmatter(markdown) { + const match = /---\r?\n([\s\S]+?)\r?\n---/.exec(markdown); + const frontmatter = match[1]; + const body = markdown.slice(match[0].length); + + const metadata = {}; + frontmatter.split('\n').forEach((pair) => { + const i = pair.indexOf(':'); + metadata[pair.slice(0, i).trim()] = pair.slice(i + 1).trim(); + }); + + return { metadata, body }; +} diff --git a/sites/kit.svelte.dev/src/lib/search/Search.svelte b/sites/kit.svelte.dev/src/lib/search/Search.svelte new file mode 100644 index 000000000000..784792c97f7a --- /dev/null +++ b/sites/kit.svelte.dev/src/lib/search/Search.svelte @@ -0,0 +1,139 @@ + + +
    + { + $searching = true; + $query = e.target.value; + e.target.value = ''; + }} + on:mousedown|preventDefault={() => ($searching = true)} + on:touchstart|preventDefault={() => ($searching = true)} + type="search" + /> + + +
    + + diff --git a/sites/kit.svelte.dev/src/lib/search/SearchBox.svelte b/sites/kit.svelte.dev/src/lib/search/SearchBox.svelte new file mode 100644 index 000000000000..e5377b12c5ff --- /dev/null +++ b/sites/kit.svelte.dev/src/lib/search/SearchBox.svelte @@ -0,0 +1,441 @@ + + + { + if (e.code === 'KeyK' && (navigator.platform === 'MacIntel' ? e.metaKey : e.ctrlKey)) { + e.preventDefault(); + $query = ''; + $searching = !$searching; + } + + if (e.code === 'Escape') { + close(); + } + }} +/> + +{#if $searching && index} +