Add two-slash to docs pages

pull/8452/head
Puru Vijay 3 years ago
parent 9b439393e6
commit 4a288a5e33

@ -239,7 +239,8 @@ Local variables (that do not represent store values) must _not_ have a `$` prefi
#### Store contract
```js
```ts
// @noErrors
store = { subscribe: (subscription: (value: any) => void) => (() => void), set?: (value: any) => void }
```

@ -397,7 +397,8 @@ use:action
use:action={parameters}
```
```js
```ts
// @noErrors
action = (node: HTMLElement, parameters: any) => {
update?: (parameters: any) => void,
destroy?: () => void
@ -471,6 +472,7 @@ transition:fn|local={params}
```
```js
// @noErrors
transition = (node: HTMLElement, params: any, options: { direction: 'in' | 'out' | 'both' }) => {
delay?: number,
duration?: number,
@ -670,6 +672,7 @@ animate:name={params}
```
```js
// @noErrors
animation = (node: HTMLElement, { from: DOMRect, to: DOMRect } , params: any) => {
delay?: number,
duration?: number,
@ -680,6 +683,7 @@ animation = (node: HTMLElement, { from: DOMRect, to: DOMRect } , params: any) =>
```
```ts
// @noErrors
DOMRect {
bottom: number,
height: number,

@ -36,6 +36,7 @@ count.update((n) => n + 1); // logs '2'
If a function is passed as the second argument, it will be called when the number of subscribers goes from zero to one (but not from one to two, etc). That function will be passed a `set` function which changes the value of the store. It must return a `stop` function that is called when the subscriber count goes from one to zero.
```js
/// file: store.js
import { writable } from 'svelte/store';
const count = writable(0, () => {
@ -64,8 +65,7 @@ Creates a store whose value cannot be set from 'outside', the first argument is
/// file: store.js
import { readable } from 'svelte/store';
/** @type {import('svelte/store').Readable<Date>} */
const time = readable(null, (set) => {
const time = readable(new Date(), (set) => {
set(new Date());
const interval = setInterval(() => {
@ -84,7 +84,18 @@ Derives a store from one or more other stores. The callback runs initially when
In the simplest version, `derived` takes a single store, and the callback returns a derived value.
```js
```ts
// @filename: ambient.d.ts
import { type Writable } from 'svelte/store';
declare global {
const a: Writable<number>;
}
export {};
// @filename: index.ts
// ---cut---
import { derived } from 'svelte/store';
const doubled = derived(a, ($a) => $a * 2);
@ -94,9 +105,18 @@ The callback can set a value asynchronously by accepting a second argument, `set
In this case, you can also pass a third argument to `derived` — the initial value of the derived store before `set` is first called.
<!-- TODO types -->
```js
// @filename: ambient.d.ts
import { type Writable } from 'svelte/store';
declare global {
const a: Writable<number>;
}
export {};
// @filename: index.ts
// ---cut---
import { derived } from 'svelte/store';
const delayed = derived(
@ -104,15 +124,24 @@ const delayed = derived(
($a, set) => {
setTimeout(() => set($a), 1000);
},
'one moment...'
2000
);
```
If you return a function from the callback, it will be called when a) the callback runs again, or b) the last subscriber unsubscribes.
<!-- TODO types -->
```js
// @filename: ambient.d.ts
import { type Writable } from 'svelte/store';
declare global {
const frequency: Writable<number>;
}
export {};
// @filename: index.ts
// ---cut---
import { derived } from 'svelte/store';
const tick = derived(
@ -126,15 +155,26 @@ const tick = derived(
clearInterval(interval);
};
},
'one moment...'
2000
);
```
In both cases, an array of arguments can be passed as the first argument instead of a single store.
<!-- TODO type -->
```ts
// @filename: ambient.d.ts
import { type Writable } from 'svelte/store';
```js
declare global {
const a: Writable<number>;
const b: Writable<number>;
}
export {};
// @filename: index.ts
// ---cut---
import { derived } from 'svelte/store';
const summed = derived([a, b], ([$a, $b]) => $a + $b);
@ -151,7 +191,7 @@ const delayed = derived([a, b], ([$a, $b], set) => {
This simple helper function makes a store readonly. You can still subscribe to the changes from the original one using this new readable store.
```js
import { readonly } from 'svelte/store';
import { readonly, writable } from 'svelte/store';
const writableStore = writable(1);
const readableStore = readonly(writableStore);
@ -159,6 +199,7 @@ const readableStore = readonly(writableStore);
readableStore.subscribe(console.log);
writableStore.set(2); // console: 2
// @errors: 2339
readableStore.set(2); // ERROR
```
@ -171,6 +212,17 @@ Generally, you should read the value of a store by subscribing to it and using t
> This works by creating a subscription, reading the value, then unsubscribing. It's therefore not recommended in hot code paths.
```js
// @filename: ambient.d.ts
import { type Writable } from 'svelte/store';
declare global {
const store: Writable<string>;
}
export {};
// @filename: index.ts
// ---cut---
import { get } from 'svelte/store';
const value = get(store);

@ -44,7 +44,19 @@ Out of the box, Svelte will interpolate between two numbers, two arrays or two o
If the initial value is `undefined` or `null`, the first value change will take effect immediately. This is useful when you have tweened values that are based on props, and don't want any motion when the component first renders.
```js
```ts
// @filename: ambient.d.ts
declare global {
var $size: number;
var big: number;
}
export {};
// @filename: motion.ts
// ---cut---
import { tweened } from 'svelte/motion';
import { cubicOut } from 'svelte/easing';
const size = tweened(undefined, {
duration: 300,
easing: cubicOut
@ -90,6 +102,8 @@ A `spring` store gradually changes to its target value based on its `stiffness`
All of the options above can be changed while the spring is in motion, and will take immediate effect.
```js
import { spring } from 'svelte/motion';
const size = spring(100);
size.stiffness = 0.3;
size.damping = 0.4;
@ -101,6 +115,8 @@ As with [`tweened`](/docs/svelte-motion#tweened) stores, `set` and `update` retu
Both `set` and `update` can take a second argument — an object with `hard` or `soft` properties. `{ hard: true }` sets the target value immediately; `{ soft: n }` preserves existing momentum for `n` seconds before settling. `{ soft: true }` is equivalent to `{ soft: 0.5 }`.
```js
import { spring } from 'svelte/motion';
const coords = spring({ x: 50, y: 50 });
// updates the value immediately
coords.set({ x: 100, y: 200 }, { hard: true });
@ -131,7 +147,19 @@ coords.update(
If the initial value is `undefined` or `null`, the first value change will take effect immediately, just as with `tweened` values (see above).
```js
```ts
// @filename: ambient.d.ts
declare global {
var $size: number;
var big: number;
}
export {};
// @filename: motion.ts
// ---cut---
import { spring } from 'svelte/motion';
const size = spring();
$: $size = big ? 100 : 10;
```

@ -13,6 +13,15 @@ Nonetheless, it's useful to understand how to use the compiler, since bundler pl
This is where the magic happens. `svelte.compile` takes your component source code, and turns it into a JavaScript module that exports a class.
```js
// @filename: ambient.d.ts
declare global {
var source: string
}
export {}
// @filename: index.ts
// ---cut---
import { compile } from 'svelte/compiler';
const result = compile(source, {
@ -74,7 +83,17 @@ The following options can be passed to the compiler. None are required:
The returned `result` object contains the code for your component, along with useful bits of metadata.
```js
```ts
// @filename: ambient.d.ts
declare global {
const source: string;
}
export {};
// @filename: main.ts
import { compile } from 'svelte/compiler';
// ---cut---
const { js, css, ast, warnings, vars, stats } = compile(source);
```
@ -143,6 +162,15 @@ compiled: {
The `parse` function parses a component, returning only its abstract syntax tree. Unlike compiling with the `generate: false` option, this will not perform any validation or other analysis of the component beyond parsing it. Note that the returned AST is not considered public API, so breaking changes could occur at any point in time.
```js
// @filename: ambient.d.ts
declare global {
var source: string;
}
export {};
// @filename: main.ts
// ---cut---
import { parse } from 'svelte/compiler';
const ast = parse(source, { filename: 'App.svelte' });
@ -167,6 +195,15 @@ The `markup` function receives the entire component source text, along with the
> Preprocessor functions should additionally return a `map` object alongside `code` and `dependencies`, where `map` is a sourcemap representing the transformation.
```js
// @filename: ambient.d.ts
declare global {
var source: string;
}
export {};
// @filename: main.ts
// ---cut---
import { preprocess } from 'svelte/compiler';
import MagicString from 'magic-string';
@ -196,10 +233,21 @@ The `script` and `style` functions receive the contents of `<script>` and `<styl
If a `dependencies` array is returned, it will be included in the result object. This is used by packages like [vite-plugin-svelte](https://github.com/sveltejs/vite-plugin-svelte) and [rollup-plugin-svelte](https://github.com/sveltejs/rollup-plugin-svelte) to watch additional files for changes, in the case where your `<style>` tag has an `@import` (for example).
```js
```ts
// @filename: ambient.d.ts
declare global {
var source: string;
}
export {};
// @filename: main.ts
// @errors: 2322 2345
/// <reference types="@types/node" />
// ---cut---
import { dirname } from 'node:path';
import { preprocess } from 'svelte/compiler';
import sass from 'sass';
import { dirname } from 'path';
const { code, dependencies } = await preprocess(
source,
@ -227,6 +275,15 @@ const { code, dependencies } = await preprocess(
Multiple preprocessors can be used together. The output of the first becomes the input to the second. `markup` functions run first, then `script` and `style`.
```js
// @filename: ambient.d.ts
declare global {
var source: string;
}
export {};
// @filename: main.ts
// ---cut---
import { preprocess } from 'svelte/compiler';
const { code } = await preprocess(
@ -270,6 +327,18 @@ The `walk` function provides a way to walk the abstract syntax trees generated b
The walker takes an abstract syntax tree to walk and an object with two optional methods: `enter` and `leave`. For each node, `enter` is called (if present). Then, unless `this.skip()` is called during `enter`, each of the children are traversed, and then `leave` is called on the node.
```js
// @filename: ambient.d.ts
declare global {
var ast: import('estree').Node;
function do_something(node: import('estree').Node): void;
function do_something_else(node: import('estree').Node): void;
function should_skip_children(node: import('estree').Node): boolean;
}
export {};
// @filename: main.ts
// ---cut---
import { walk } from 'svelte/compiler';
walk(ast, {

@ -4,13 +4,33 @@ title: 'Client-side component API'
## Creating a component
```js
```ts
// @filename: ambiend.d.ts
import { SvelteComponent, ComponentConstructorOptions } from 'svelte';
declare global {
class Component extends SvelteComponent {}
var options: ComponentConstructorOptions<Record<string, any>>;
}
// @filename: index.ts
// ---cut---
const component = new Component(options);
```
A client-side component — that is, a component compiled with `generate: 'dom'` (or the `generate` option left unspecified) is a JavaScript class.
```js
```ts
// @filename: ambiend.d.ts
import { SvelteComponent, ComponentConstructorOptions } from 'svelte';
declare module './App.svelte' {
class Component extends SvelteComponent {}
export default Component;
}
// @filename: index.ts
// ---cut---
import App from './App.svelte';
const app = new App({
@ -42,7 +62,18 @@ Whereas children of `target` are normally left alone, `hydrate: true` will cause
The existing DOM doesn't need to match the component — Svelte will 'repair' the DOM as it goes.
```js
```ts
// @filename: ambiend.d.ts
import { SvelteComponent, ComponentConstructorOptions } from 'svelte';
declare module './App.svelte' {
class Component extends SvelteComponent {}
export default Component;
}
// @filename: index.ts
// @errors: 2322
// ---cut---
import App from './App.svelte';
const app = new App({
@ -53,7 +84,20 @@ const app = new App({
## `$set`
```js
```ts
// @filename: ambiend.d.ts
import { SvelteComponent, ComponentConstructorOptions } from 'svelte';
declare global {
class Component extends SvelteComponent {}
var component: Component;
var props: Record<string, any>;
}
export {};
// @filename: index.ts
// ---cut---
component.$set(props);
```
@ -61,22 +105,60 @@ Programmatically sets props on an instance. `component.$set({ x: 1 })` is equiva
Calling this method schedules an update for the next microtask — the DOM is _not_ updated synchronously.
```js
```ts
// @filename: ambiend.d.ts
import { SvelteComponent, ComponentConstructorOptions } from 'svelte';
declare global {
class Component extends SvelteComponent {}
var component: Component;
}
export {};
// @filename: index.ts
// ---cut---
component.$set({ answer: 42 });
```
## `$on`
```js
component.$on(event, callback);
```ts
// @filename: ambiend.d.ts
import { SvelteComponent, ComponentConstructorOptions } from 'svelte';
declare global {
class Component extends SvelteComponent {}
var component: Component;
var ev: string;
var callback: (event: CustomEvent) => void;
}
export {};
// @filename: index.ts
// ---cut---
component.$on(ev, callback);
```
Causes the `callback` function to be called whenever the component dispatches an `event`.
A function is returned that will remove the event listener when called.
```js
const off = app.$on('selected', (event) => {
```ts
// @filename: ambiend.d.ts
import { SvelteComponent, ComponentConstructorOptions } from 'svelte';
declare global {
class Component extends SvelteComponent {}
var component: Component;
}
export {};
// @filename: index.ts
// ---cut---
const off = component.$on('selected', (event) => {
console.log(event.detail.selection);
});
@ -86,6 +168,18 @@ off();
## `$destroy`
```js
// @filename: ambiend.d.ts
import { SvelteComponent, ComponentConstructorOptions } from 'svelte';
declare global {
class Component extends SvelteComponent {}
var component: Component;
}
export {}
// @filename: index.ts
// ---cut---
component.$destroy();
```
@ -94,10 +188,35 @@ Removes a component from the DOM and triggers any `onDestroy` handlers.
## Component props
```js
// @filename: ambiend.d.ts
import { SvelteComponent, ComponentConstructorOptions } from 'svelte';
declare global {
class Component extends SvelteComponent {}
var component: Component;
}
export {}
// @filename: index.ts
// ---cut---
component.prop;
```
```js
// @filename: ambiend.d.ts
import { SvelteComponent, ComponentConstructorOptions } from 'svelte';
declare global {
class Component extends SvelteComponent {}
var component: Component;
var value: unknown;
}
export {}
// @filename: index.ts
// ---cut---
component.prop = value;
```
@ -106,6 +225,19 @@ If a component is compiled with `accessors: true`, each instance will have gette
By default, `accessors` is `false`, unless you're compiling as a custom element.
```js
console.log(app.count);
app.count += 1;
// @filename: ambiend.d.ts
import { SvelteComponent, ComponentConstructorOptions } from 'svelte';
declare global {
class Component extends SvelteComponent {}
var component: Component;
var props: Record<string, any>;
}
export {}
// @filename: index.ts
// ---cut---
console.log(component.count);
component.count += 1;
```

@ -3,6 +3,7 @@ title: 'Server-side component API'
---
```js
// @noErrors
const result = Component.render(...)
```
@ -13,6 +14,7 @@ A server-side component exposes a `render` method that can be called with option
You can import a Svelte component directly into Node using [`svelte/register`](/docs/svelte-register).
```js
// @noErrors
require('svelte/register');
const App = require('./App.svelte').default;
@ -36,6 +38,7 @@ The `options` object takes in the following options:
| `context` | `new Map()` | A `Map` of root-level context key-value pairs to supply to the component |
```js
// @noErrors
const { head, html, css } = App.render(
// props
{ answer: 42 },

@ -18,6 +18,7 @@ Svelte components can also be compiled to custom elements (aka web components) u
Alternatively, use `tag={null}` to indicate that the consumer of the custom element should name it.
```js
// @noErrors
import MyElement from './MyElement.svelte';
customElements.define('my-element', MyElement);
@ -38,6 +39,7 @@ By default, custom elements are compiled with `accessors: true`, which means tha
To prevent this, add `accessors={false}` to `<svelte:options>`.
```js
// @noErrors
const el = document.querySelector('my-element');
// get the current value of the 'name' prop

@ -5,11 +5,12 @@ title: 'svelte/register'
To render Svelte components in Node.js without bundling, use `require('svelte/register')`. After that, you can use `require` to include any `.svelte` file.
```js
// @noErrors
require('svelte/register');
const App = require('./App.svelte').default;
...
// ...
const { html, css, head } = App.render({ answer: 42 });
```
@ -19,6 +20,7 @@ const { html, css, head } = App.render({ answer: 42 });
To set compile options, or to use a custom file extension, call the `register` hook as a function:
```js
// @noErrors
require('svelte/register')({
extensions: ['.customextension'], // defaults to ['.html', '.svelte']
preserveComments: true

@ -23,6 +23,7 @@
"@sveltejs/kit": "^1.15.4",
"@sveltejs/site-kit": "^4.1.0",
"@types/marked": "^4.0.8",
"@types/node": "^18.15.11",
"@types/prismjs": "^1.26.0",
"degit": "^2.8.4",
"dotenv": "^16.0.3",
@ -35,6 +36,7 @@
"prismjs": "^1.29.0",
"rollup": "^3.20.2",
"rollup-plugin-dts": "^5.3.0",
"sass": "^1.62.0",
"satori": "^0.4.7",
"satori-html": "^0.3.2",
"shelljs": "^0.8.5",
@ -2632,6 +2634,12 @@
"node": ">=12.0.0"
}
},
"node_modules/immutable": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/immutable/-/immutable-4.3.0.tgz",
"integrity": "sha512-0AOCmOip+xgJwEVTQj1EfiDDOkPmuyllDuTuEX+DDXUgapLAsBIfkg3sxCYyCEA8mQqZrrxPUGjcOQ2JS3WLkg==",
"dev": true
},
"node_modules/import-fresh": {
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz",
@ -3804,6 +3812,23 @@
"rimraf": "bin.js"
}
},
"node_modules/sass": {
"version": "1.62.0",
"resolved": "https://registry.npmjs.org/sass/-/sass-1.62.0.tgz",
"integrity": "sha512-Q4USplo4pLYgCi+XlipZCWUQz5pkg/ruSSgJ0WRDSb/+3z9tXUOkQ7QPYn4XrhZKYAK4HlpaQecRwKLJX6+DBg==",
"dev": true,
"dependencies": {
"chokidar": ">=3.0.0 <4.0.0",
"immutable": "^4.0.0",
"source-map-js": ">=0.6.2 <2.0.0"
},
"bin": {
"sass": "sass.js"
},
"engines": {
"node": ">=14.0.0"
}
},
"node_modules/satori": {
"version": "0.4.7",
"resolved": "https://registry.npmjs.org/satori/-/satori-0.4.7.tgz",

@ -32,6 +32,7 @@
"@sveltejs/kit": "^1.15.4",
"@sveltejs/site-kit": "^4.1.0",
"@types/marked": "^4.0.8",
"@types/node": "^18.15.11",
"@types/prismjs": "^1.26.0",
"degit": "^2.8.4",
"dotenv": "^16.0.3",
@ -44,6 +45,7 @@
"prismjs": "^1.29.0",
"rollup": "^3.20.2",
"rollup-plugin-dts": "^5.3.0",
"sass": "^1.62.0",
"satori": "^0.4.7",
"satori-html": "^0.3.2",
"shelljs": "^0.8.5",

@ -1,9 +1,5 @@
// import 'prism-svelte';
// import 'prismjs/components/prism-bash.js';
// import 'prismjs/components/prism-diff.js';
// import 'prismjs/components/prism-typescript.js';
import { createShikiHighlighter } from 'shiki-twoslash';
import { SHIKI_LANGUAGE_MAP, normalizeSlugify, transform } from '../markdown';
import { createShikiHighlighter, renderCodeToHTML, runTwoSlash } from 'shiki-twoslash';
import { SHIKI_LANGUAGE_MAP, escape, normalizeSlugify, transform } from '../markdown';
import { replace_placeholders } from './render.js';
// import { parse_route_id } from '../../../../../../packages/kit/src/utils/routing.js';
import { createHash } from 'crypto';
@ -57,7 +53,6 @@ export async function get_parsed_docs(docs_data, slug) {
/(?:<!---\s*([\w-]+):\s*(.*?)\s*--->|\/\/\/\s*([\w-]+):\s*(.*))\n/gm,
(_, key, value) => {
options[key] = value;
console.log(options);
return '';
}
)
@ -82,123 +77,112 @@ export async function get_parsed_docs(docs_data, slug) {
version_class = 'js-version';
}
// TODO: Replace later
html = highlighter.codeToHtml(source, { lang: SHIKI_LANGUAGE_MAP[language] });
// if (source.includes('$env/')) {
// // TODO we're hardcoding static env vars that are used in code examples
// // in the types, which isn't... totally ideal, but will do for now
// 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/public' { export const PUBLIC_BASE_URL: string }`
// );
// }
// if (source.includes('./$types') && !source.includes('@filename: $types.d.ts')) {
// const params = parse_route_id(options.file || `+page.${language}`)
// .params.map((param) => `${param.name}: string`)
// .join(', ');
// injected.push(
// `// @filename: $types.d.ts`,
// `import type * as Kit from '@sveltejs/kit';`,
// `export type PageLoad = Kit.Load<{${params}}>;`,
// `export type PageServerLoad = Kit.ServerLoad<{${params}}>;`,
// `export type LayoutLoad = Kit.Load<{${params}}>;`,
// `export type LayoutServerLoad = Kit.ServerLoad<{${params}}>;`,
// `export type RequestHandler = Kit.RequestHandler<{${params}}>;`,
// `export type Action = Kit.Action<{${params}}>;`,
// `export type Actions = Kit.Actions<{${params}}>;`
// );
// }
// // special case — we need to make allowances for code snippets coming
// // from e.g. ambient.d.ts
// if (file.endsWith('30-modules.md')) {
// injected.push('// @errors: 7006 7031');
// }
// // another special case
// if (source.includes('$lib/types')) {
// injected.push(`declare module '$lib/types' { export interface User {} }`);
// }
// if (injected.length) {
// const injected_str = injected.join('\n');
// if (source.includes('// @filename:')) {
// source = source.replace('// @filename:', `${injected_str}\n\n// @filename:`);
// } else {
// source = source.replace(
// /^(?!\/\/ @)/m,
// `${injected_str}\n\n// @filename: index.${language}\n// ---cut---\n`
// );
// }
// }
// const twoslash = runTwoSlash(source, language, {
// defaultCompilerOptions: {
// allowJs: true,
// checkJs: true,
// target: 'es2021',
// },
// });
// html = renderCodeToHTML(
// twoslash.code,
// 'ts',
// { twoslash: true },
// {},
// highlighter,
// twoslash
// );
// } catch (e) {
// console.error(`Error compiling snippet in ${file}`);
// console.error(e.code);
// throw e;
// }
// // we need to be able to inject the LSP attributes as HTML, not text, so we
// // turn &lt; into &amp;lt;
// html = html.replace(
// /<data-lsp lsp='([^']*)'([^>]*)>(\w+)<\/data-lsp>/g,
// (match, lsp, attrs, name) => {
// if (!lsp) return name;
// return `<data-lsp lsp='${lsp.replace(/&/g, '&amp;')}'${attrs}>${name}</data-lsp>`;
// }
// );
// // preserve blank lines in output (maybe there's a more correct way to do this?)
// html = html.replace(/<div class='line'><\/div>/g, '<div class="line"> </div>');
// } else if (language === 'diff') {
// const lines = source.split('\n').map((content) => {
// let type = null;
// if (/^[\+\-]/.test(content)) {
// type = content[0] === '+' ? 'inserted' : 'deleted';
// content = content.slice(1);
// }
// return {
// type,
// content: escape(content),
// };
// });
// html = `<pre class="language-diff"><code>${lines
// .map((line) => {
// if (line.type) return `<span class="${line.type}">${line.content}\n</span>`;
// return line.content + '\n';
// })
// .join('')}</code></pre>`;
// } else {
// const plang = languages[language];
// const highlighted = plang
// ? PrismJS.highlight(source, PrismJS.languages[plang], language)
// : source.replace(/[&<>]/g, (c) => ({ '&': '&amp;', '<': '&lt;', '>': '&gt;' }[c]));
// html = `<pre class='language-${plang}'><code>${highlighted}</code></pre>`;
// }
if (language === 'dts') {
html = renderCodeToHTML(
source,
'ts',
{ twoslash: false },
{ themeName: 'css-variables' },
highlighter
);
} else if (language === 'js' || language === 'ts') {
try {
const injected = [];
// For the snippets that are not proper JS or TS
// if (!source.includes('import')) injected.push('// @errors: 2552 1005 1109 2304');
if (source.includes('svelte')) {
injected.push(
`// @filename: ambient.d.ts`,
`/// <reference types="svelte" />`,
`/// <reference types="svelte/action" />`,
`/// <reference types="svelte/compiler" />`,
`/// <reference types="svelte/easing" />`,
`/// <reference types="svelte/motion" />`,
`/// <reference types="svelte/transition" />`,
`/// <reference types="svelte/store" />`
);
}
if (page.file.includes('svelte-compiler')) {
injected.push('// @esModuleInterop');
}
if (injected.length) {
const injected_str = injected.join('\n');
if (source.includes('// @filename:')) {
source = source.replace('// @filename:', `${injected_str}\n\n// @filename:`);
} else {
source = source.replace(
/^(?!\/\/ @)/m,
`${injected_str}\n\n// @filename: index.${language}\n// ---cut---\n`
);
}
}
const twoslash = runTwoSlash(source, language, {
defaultCompilerOptions: {
allowJs: true,
checkJs: true,
target: ts.ScriptTarget.ES2022
}
});
html = renderCodeToHTML(
twoslash.code,
'ts',
{ twoslash: true },
// @ts-ignore Why shiki-twoslash requires a theme name?
{},
highlighter,
twoslash
);
} catch (e) {
console.error(`Error compiling snippet in ${page.file}`);
console.error(e.code);
throw e;
}
// we need to be able to inject the LSP attributes as HTML, not text, so we
// turn &lt; into &amp;lt;
html = html.replace(
/<data-lsp lsp='([^']*)'([^>]*)>(\w+)<\/data-lsp>/g,
(match, lsp, attrs, name) => {
if (!lsp) return name;
return `<data-lsp lsp='${lsp.replace(/&/g, '&amp;')}'${attrs}>${name}</data-lsp>`;
}
);
// preserve blank lines in output (maybe there's a more correct way to do this?)
html = html.replace(/<div class='line'><\/div>/g, '<div class="line"> </div>');
} else if (language === 'diff') {
const lines = source.split('\n').map((content) => {
let type = null;
if (/^[\+\-]/.test(content)) {
type = content[0] === '+' ? 'inserted' : 'deleted';
content = content.slice(1);
}
return {
type,
content: escape(content)
};
});
html = `<pre class="language-diff"><code>${lines
.map((line) => {
if (line.type) return `<span class="${line.type}">${line.content}\n</span>`;
return line.content + '\n';
})
.join('')}</code></pre>`;
} else {
const highlighted = highlighter.codeToHtml(source, {
lang: SHIKI_LANGUAGE_MAP[language]
});
html = highlighted.replace(/<div class='line'><\/div>/g, '<div class="line"> </div>');
}
if (options.file) {
html = `<div class="code-block"><span class="filename">${options.file}</span>${html}</div>`;
@ -417,7 +401,7 @@ function convert_to_ts(js_code, indent = '', offset = '') {
if (variable_statement.name.getText() === 'actions') {
code.appendLeft(variable_statement.getEnd(), ` satisfies ${name}`);
} else {
code.appendLeft(variable_statement.name.getEnd(), `: ${name}`);
code.appendLeft(variable_statement.name.getEnd(), `: ${name}${generics ?? ''}`);
}
modified = true;

@ -144,7 +144,14 @@ export function replace_placeholders(content) {
* @param {string} lang
*/
function fence(code, lang = 'ts') {
return '\n\n```' + lang + '\n' + code + '\n```\n\n';
return (
'\n\n```' +
lang +
'\n' +
(['js', 'ts'].includes(lang) ? '// @noErrors\n' : '') +
code +
'\n```\n\n'
);
}
/**

@ -0,0 +1,67 @@
<script>
import { tick } from 'svelte';
export let html = '';
export let x = 0;
export let y = 0;
let width = 1;
let tooltip;
// bit of a gross hack but it works — this prevents the
// tooltip from disappearing off the side of the screen
$: if (html && tooltip) {
tick().then(() => {
width = tooltip.getBoundingClientRect().width;
});
}
</script>
<!-- svelte-ignore a11y-mouse-events-have-key-events -->
<div
on:mouseenter
on:mouseleave
class="tooltip-container"
style="left: {x}px; top: {y}px; --offset: {Math.min(-10, window.innerWidth - (x + width + 10))}px"
>
<div bind:this={tooltip} class="tooltip">
<span>{@html html}</span>
</div>
</div>
<style>
.tooltip-container {
--bg: var(--sk-theme-2);
--arrow-size: 0.4rem;
position: absolute;
transform: translate(var(--offset), calc(2rem + var(--arrow-size)));
}
.tooltip {
margin: 0 2rem 0 0;
background-color: var(--bg);
color: #fff;
text-align: left;
padding: 0.4rem 0.6rem;
border-radius: var(--sk-border-radius);
font-family: var(--sk-font-mono);
font-size: 1.2rem;
white-space: pre-wrap;
z-index: 100;
filter: drop-shadow(2px 4px 6px #67677866);
}
.tooltip::after {
content: '';
position: absolute;
left: calc(-1 * var(--offset) - var(--arrow-size));
top: calc(-2 * var(--arrow-size));
border: var(--arrow-size) solid transparent;
border-bottom-color: var(--bg);
}
.tooltip :global(a) {
color: white;
text-decoration: underline;
}
</style>

@ -0,0 +1,60 @@
import { onMount } from 'svelte';
import Tooltip from './Tooltip.svelte';
export function setup() {
onMount(() => {
let tooltip;
let timeout;
function over(event) {
if (event.target.tagName === 'DATA-LSP') {
clearTimeout(timeout);
if (!tooltip) {
tooltip = new Tooltip({
target: document.body
});
tooltip.$on('mouseenter', () => {
clearTimeout(timeout);
});
tooltip.$on('mouseleave', () => {
clearTimeout(timeout);
tooltip.$destroy();
tooltip = null;
});
}
const rect = event.target.getBoundingClientRect();
const html = event.target.getAttribute('lsp');
const x = (rect.left + rect.right) / 2 + window.scrollX;
const y = rect.top + window.scrollY;
tooltip.$set({
html,
x,
y
});
}
}
function out(event) {
if (event.target.tagName === 'DATA-LSP') {
timeout = setTimeout(() => {
tooltip.$destroy();
tooltip = null;
}, 200);
}
}
window.addEventListener('mouseover', over);
window.addEventListener('mouseout', out);
return () => {
window.removeEventListener('mouseover', over);
window.removeEventListener('mouseout', out);
};
});
}

@ -1,15 +1,75 @@
<script>
import { page } from '$app/stores';
import OnThisPage from './OnThisPage.svelte';
import * as hovers from '$lib/utils/hovers';
export let data;
$: pages = data.sections.flatMap((section) => section.pages);
$: index = pages.findIndex(({ path }) => path === $page.url.pathname);
$: prev = pages[index - 1];
$: next = pages[index + 1];
hovers.setup();
</script>
<svelte:head>
<title>{data.page.title} - Svelte</title>
<title>{data.page.title} • Docs • SvelteKit</title>
<meta name="twitter:title" content="SvelteKit docs" />
<meta name="twitter:description" content="{data.page.title} • SvelteKit documentation" />
<meta name="Description" content="{data.page.title} • SvelteKit documentation" />
</svelte:head>
<div class="text">
{@html data.page.content}
</div>
<div class="controls">
<div>
<span class:faded={!prev}>previous</span>
{#if prev}
<a href={prev.path}>{prev.title}</a>
{/if}
</div>
<div>
<span class:faded={!next}>next</span>
{#if next}
<a href={next.path}>{next.title}</a>
{/if}
</div>
</div>
<OnThisPage details={data.page} />
<style>
.controls {
max-width: calc(var(--sk-line-max-width) + 1rem);
border-top: 1px solid var(--sk-back-4);
padding: 1rem 0 0 0;
display: grid;
grid-template-columns: 1fr 1fr;
margin: 6rem 0 0 0;
}
.controls > :first-child {
text-align: left;
}
.controls > :last-child {
text-align: right;
}
.controls span {
display: block;
font-size: 1.2rem;
text-transform: uppercase;
font-weight: 600;
color: var(--sk-text-3);
}
.controls span.faded {
opacity: 0.4;
}
</style>

Loading…
Cancel
Save