docs: legacy docs (#13756)

* docs: legacy docs

add docs on old syntax

* rename section

* tweaks

* tweak

* tweaks

* tweaks

* tweaks

* fix link

---------

Co-authored-by: Rich Harris <rich.harris@vercel.com>
transition-out-effect-tracking
Simon H 2 months ago committed by GitHub
parent 8ff2af52d3
commit 4c7cfff434
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -50,6 +50,9 @@ A `<script>` tag with a `module` attribute runs once when the module first evalu
You can `export` bindings from this block, and they will become exports of the compiled module. You cannot `export default`, since the default export is the component itself.
> [!LEGACY]
> In Svelte 4, this script tag was created using `<script context="module">`
## `<style>`
CSS inside a `<style>` block will be scoped to that component.

@ -5,3 +5,6 @@ title: .svelte.js and .svelte.ts files
Besides `.svelte` files, Svelte also operates on `.svelte.js` and `.svelte.ts` files.
These behave like any other `.js` or `.ts` module, except that you can use runes. This is useful for creating reusable reactive logic, or sharing reactive state across your app.
> [!LEGACY]
> This is a concept that didn't exist prior to Svelte 5

@ -19,3 +19,6 @@ They differ from normal JavaScript functions in important ways, however:
- You don't need to import them — they are part of the language
- They're not values — you can't assign them to a variable or pass them as arguments to a function
- Just like JavaScript keywords, they are only valid in certain positions (the compiler will help you if you put them in the wrong place)
> [!LEGACY]
> Runes didn't exist prior to Svelte 5.

@ -18,7 +18,7 @@ A lowercase tag, like `<div>`, denotes a regular HTML element. A capitalised tag
</div>
```
## Attributes and props
## Element attributes
By default, attributes work exactly like their HTML counterparts.
@ -72,6 +72,8 @@ When the attribute name and value match (`name={name}`), they can be replaced wi
-->
```
## Component props
By convention, values passed to components are referred to as _properties_ or _props_ rather than _attributes_, which are a feature of the DOM.
As with elements, `name={name}` can be replaced with the `{name}` shorthand.

@ -36,7 +36,7 @@ document.body.innerHTML = `
`;
```
Any [props](basic-markup#Attributes-and-props) are exposed as properties of the DOM element (as well as being readable/writable as attributes, where possible).
Any [props](basic-markup#Component-props) are exposed as properties of the DOM element (as well as being readable/writable as attributes, where possible).
```js
// @noErrors

@ -0,0 +1,12 @@
---
title: Overview
---
Svelte 5 introduced some significant changes to Svelte's API, including [runes](what-are-runes), [snippets](snippet) and event attributes. As a result, some Svelte 3/4 features are deprecated (though supported for now, unless otherwise specified) and will eventually be removed. We recommend that you incrementally [migrate your existing code](v5-migration-guide).
The following pages document these features for
- people still using Svelte 3/4
- people using Svelte 5, but with components that haven't yet been migrated
Since Svelte 3/4 syntax still works in Svelte 5, we will distinguish between _legacy mode_ and _runes mode_. Once a component is in runes mode (which you can opt into by using runes, or by explicitly setting the `runes: true` compiler option), legacy mode features are no longer available.

@ -0,0 +1,34 @@
---
title: Reactive let/var declarations
---
In runes mode, reactive state is explicitly declared with the [`$state` rune]($state).
In legacy mode, variables declared at the top level of a component are automatically considered _reactive_. Reassigning or mutating these variables (`count += 1` or `object.x = y`) will cause the UI to update.
```svelte
<script>
let count = 0;
</script>
<button on:click={() => count += 1}>
clicks: {count}
</button>
```
Because Svelte's legacy mode reactivity is based on _assignments_, using array methods like `.push()` and `.splice()` won't automatically trigger updates. A subsequent assignment is required to 'tell' the compiler to update the UI:
```svelte
<script>
let numbers = [1, 2, 3, 4];
function addNumber() {
// this method call does not trigger an update
numbers.push(numbers.length + 1);
// this assignment will update anything
// that depends on `numbers`
numbers = numbers;
}
</script>
```

@ -0,0 +1,87 @@
---
title: Reactive $: statements
---
In runes mode, reactions to state updates are handled with the [`$derived`]($derived) and [`$effect`]($effect) runes.
In legacy mode, any top-level statement (i.e. not inside a block or a function) can be made reactive by prefixing it with a `$:` [label](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/label). These statements run after other code in the `<script>` and before the component markup is rendered, then whenever the values that they depend on change.
```svelte
<script>
let a = 1;
let b = 2;
// this is a 'reactive statement', and it will re-run
// when `a`, `b` or `sum` change
$: console.log(`${a} + ${b} = ${sum}`);
// this is a 'reactive assignment' — `sum` will be
// recalculated when `a` or `b` change. It is
// not necessary to declare `sum` separately
$: sum = a + b;
</script>
```
Statements are ordered _topologically_ by their dependencies and their assignments: since the `console.log` statement depends on `sum`, `sum` is calculated first even though it appears later in the source.
Multiple statements can be combined by putting them in a block:
```js
// @noErrors
$: {
// recalculate `total` when `items` changes
total = 0;
for (const item of items) {
total += item.value;
}
}
```
The left-hand side of a reactive assignments can be an identifier, or it can be a destructuring assignment:
```js
// @noErrors
$: ({ larry, moe, curly } = stooges);
```
## Understanding dependencies
The dependencies of a `$:` statement are determined at compile time — they are whichever variables are referenced (but not assigned to) inside the statement.
In other words, a statement like this will _not_ re-run when `count` changes, because the compiler cannot 'see' the dependency:
```js
// @noErrors
let count = 0;
let double = () => count * 2;
$: doubled = double();
```
Similarly, topological ordering will fail if dependencies are referenced indirectly: `z` will never update, because `y` is not considered 'dirty' when the update occurs. Moving `$: z = y` below `$: setY(x)` will fix it:
```svelte
<script>
let x = 0;
let y = 0;
$: z = y;
$: setY(x);
function setY(value) {
y = value;
}
</script>
```
## Browser-only code
Reactive statements run during server-side rendering as well as in the browser. This means that any code that should only run in the browser must be wrapped in an `if` block:
```js
// @noErrors
$: if (browser) {
document.title = title;
}
```

@ -0,0 +1,72 @@
---
title: export let
---
In runes mode, [component props](basic-markup#Component-props) are declared with the [`$props`]($props) rune, allowing parent components to pass in data.
In legacy mode, props are marked with the `export` keyword, and can have a default value:
```svelte
<script>
export let foo;
export let bar = 'default value';
// Values that are passed in as props
// are immediately available
console.log({ foo });
</script>
```
The default value is used if it would otherwise be `undefined` when the component is created.
> [!NOTE] Unlike in runes mode, if the parent component changes a prop from a defined value to `undefined`, it does not revert to the initial value.
Props without default values are considered _required_, and Svelte will print a warning during development if no value is provided, which you can squelch by specifying `undefined` as the default value:
```js
export let foo +++= undefined;+++
```
## Component exports
An exported `const`, `class` or `function` declaration is _not_ considered a prop — instead, it becomes part of the component's API:
```svelte
<!--- file: Greeter.svelte--->
<script>
export function greet(name) {
alert(`hello ${name}!`);
}
</script>
```
```svelte
<!--- file: App.svelte --->
<script>
import Greeter from './Greeter.svelte';
let greeter;
</script>
<Greeter bind:this={greeter} />
<button on:click={() => greeter.greet('world')}>
greet
</button>
```
## Renaming props
The `export` keyword can appear separately from the declaration. This is useful for renaming props, for example in the case of a reserved word:
```svelte
<!--- file: App.svelte --->
<script>
/** @type {string} */
let className;
// creates a `class` property, even
// though it is a reserved word
export { className as class };
</script>
```

@ -0,0 +1,30 @@
---
title: $$props and $$restProps
---
In runes mode, getting an object containing all the props that were passed in is easy, using the [`$props`]($props) rune.
In legacy mode, we use `$$props` and `$$restProps`:
- `$$props` contains all the props that were passed in, including ones that are not individually declared with the `export` keyword
- `$$restProps` contains all the props that were passed in _except_ the ones that were individually declared
For example, a `<Button>` component might need to pass along all its props to its own `<button>` element, except the `variant` prop:
```svelte
<script>
export let variant;
</script>
<button {...$$restProps} class="variant-{variant} {$$props.class ?? ''}">
click me
</button>
<style>
.variant-danger {
background: red;
}
</style>
```
In Svelte 3/4 using `$$props` and `$$restProps` creates a modest performance penalty, so they should only be used when needed.

@ -0,0 +1,136 @@
---
title: on:
---
In runes mode, event handlers are just like any other attribute or prop.
In legacy mode, we use the `on:` directive:
```svelte
<!--- file: App.svelte --->
<script>
let count = 0;
/** @param {MouseEvent} event */
function handleClick(event) {
count += 1;
}
</script>
<button on:click={handleClick}>
count: {count}
</button>
```
Handlers can be declared inline with no performance penalty:
```svelte
<button on:click={() => (count += 1)}>
count: {count}
</button>
```
Add _modifiers_ to element event handlers with the `|` character.
```svelte
<form on:submit|preventDefault={handleSubmit}>
<!-- the `submit` event's default is prevented,
so the page won't reload -->
</form>
```
The following modifiers are available:
- `preventDefault` — calls `event.preventDefault()` before running the handler
- `stopPropagation` — calls `event.stopPropagation()`, preventing the event reaching the next element
- `stopImmediatePropagation` - calls `event.stopImmediatePropagation()`, preventing other listeners of the same event from being fired.
- `passive` — improves scrolling performance on touch/wheel events (Svelte will add it automatically where it's safe to do so)
- `nonpassive` — explicitly set `passive: false`
- `capture` — fires the handler during the _capture_ phase instead of the _bubbling_ phase
- `once` — remove the handler after the first time it runs
- `self` — only trigger handler if `event.target` is the element itself
- `trusted` — only trigger handler if `event.isTrusted` is `true`. I.e. if the event is triggered by a user action.
Modifiers can be chained together, e.g. `on:click|once|capture={...}`.
If the `on:` directive is used without a value, the component will _forward_ the event, meaning that a consumer of the component can listen for it.
```svelte
<button on:click>
The component itself will emit the click event
</button>
```
It's possible to have multiple event listeners for the same event:
```svelte
<!--- file: App.svelte --->
<script>
let count = 0;
function increment() {
count += 1;
}
/** @param {MouseEvent} event */
function log(event) {
console.log(event);
}
</script>
<button on:click={increment} on:click={log}>
clicks: {count}
</button>
```
## Component events
Components can dispatch events by creating a _dispatcher_ when they are initialised:
```svelte
<!--- file: Stepper.svelte -->
<script>
import { createEventDispatcher } from 'svelte';
const dispatch = createEventDispatcher();
</script>
<button on:click={() => dispatch('decrement')}>decrement</button>
<button on:click={() => dispatch('increment')}>increment</button>
```
`dispatch` creates a [`CustomEvent`](https://developer.mozilla.org/en-US/docs/Web/API/CustomEvent). If a second argument is provided, it becomes the `detail` property of the event object.
A consumer of this component can listen for the dispatched events:
```svelte
<script>
import Stepper from './Stepper.svelte';
let n = 0;
</script>
<Stepper
on:decrement={() => n -= 1}
on:increment={() => n += 1}
/>
<p>n: {n}</p>
```
Component events do not bubble — a parent component can only listen for events on its immediate children.
Other than `once`, modifiers are not valid on component event handlers.
> [!NOTE]
> If you're planning an eventual migration to Svelte 5, use callback props instead. This will make upgrading easier as `createEventDispatcher` is deprecated:
>
> ```svelte
> <!--- file: Stepper.svelte --->
> <script>
> export let decrement;
> export let increment;
> </script>
>
> <button on:click={decrement}>decrement</button>
> <button on:click={increment}>increment</button>
> ```

@ -0,0 +1,116 @@
---
title: <slot>
---
In Svelte 5, content can be passed to components in the form of [snippets](snippet) and rendered using [render tags](@render).
In legacy mode, content inside component tags is considered _slotted content_, which can be rendered by the component using a `<slot>` element:
```svelte
<!--- file: App.svelte --->
<script>
import Modal from './Modal.svelte';
</script>
<Modal>This is some slotted content</Modal>
```
```svelte
<!--- file: Modal.svelte --->
<div class="modal">
<slot></slot>
</div>
```
> [!NOTE] If you want to render a regular `<slot>` element, you can use `<svelte:element this={'slot'} />`.
## Named slots
A component can have _named_ slots in addition to the default slot. On the parent side, add a `slot="..."` attribute to an element, component or [`<svelte:fragment>`](legacy-svelte-fragment) directly inside the component tags.
```svelte
<!--- file: App.svelte --->
<script>
import Modal from './Modal.svelte';
let open = true;
</script>
{#if open}
<Modal>
This is some slotted content
+++<div slot="buttons">+++
<button on:click={() => open = false}>
close
</button>
+++</div>+++
</Modal>
{/if}
```
On the child side, add a corresponding `<slot name="...">` element:
```svelte
<!--- file: Modal.svelte --->
<div class="modal">
<slot></slot>
<hr>
+++<slot name="buttons"></slot>+++
</div>
```
## Fallback content
If no slotted content is provided, a component can define fallback content by putting it inside the `<slot>` element:
```svelte
<slot>
This will be rendered if no slotted content is provided
</slot>
```
## Passing data to slotted content
Slots can be rendered zero or more times and can pass values _back_ to the parent using props. The parent exposes the values to the slot template using the `let:` directive.
The usual shorthand rules apply — `let:item` is equivalent to `let:item={item}`, and `<slot {item}>` is equivalent to `<slot item={item}>`.
```svelte
<!-- FancyList.svelte -->
<ul>
{#each items as item}
<li class="fancy">
<slot prop={item} />
</li>
{/each}
</ul>
<!-- App.svelte -->
<FancyList {items} let:prop={thing}>
<div>{thing.text}</div>
</FancyList>
```
Named slots can also expose values. The `let:` directive goes on the element with the `slot` attribute.
```svelte
<!-- FancyList.svelte -->
<ul>
{#each items as item}
<li class="fancy">
<slot name="item" {item} />
</li>
{/each}
</ul>
<slot name="footer" />
<!-- App.svelte -->
<FancyList {items}>
<div slot="item" let:item>{item.text}</div>
<p slot="footer">Copyright (c) 2019 Svelte Industries</p>
</FancyList>
```

@ -0,0 +1,27 @@
---
title: $$slots
---
In runes mode, we know which [snippets](snippet) were provided to a component, as they're just normal props.
In legacy mode, the way to know if content was provided for a given slot is with the `$$slots` object, whose keys are the names of the slots passed into the component by the parent.
```svelte
<!--- file: Card.svelte --->
<div>
<slot name="title" />
{#if $$slots.description}
<!-- This <hr> and slot will render only if `slot="description"` is provided. -->
<hr />
<slot name="description" />
{/if}
</div>
```
```svelte
<!--- file: App.svelte --->
<Card>
<h1 slot="title">Blog Post Title</h1>
<!-- No slot named "description" was provided so the optional slot will not be rendered. -->
</Card>
```

@ -0,0 +1,32 @@
---
title: <svelte:fragment>
---
The `<svelte:fragment>` element allows you to place content in a [named slot](/docs/special-elements#slot-slot-name-name) without wrapping it in a container DOM element. This keeps the flow layout of your document intact.
```svelte
<!--- file: Widget.svelte --->
<div>
<slot name="header">No header was provided</slot>
<p>Some content between header and footer</p>
<slot name="footer" />
</div>
```
```svelte
<!--- file: App.svelte --->
<script>
import Widget from './Widget.svelte';
</script>
<Widget>
<h1 slot="header">Hello</h1>
<svelte:fragment slot="footer">
<p>All rights reserved.</p>
<p>Copyright (c) 2019 Svelte Industries</p>
</svelte:fragment>
</Widget>
```
> [!NOTE]
> In Svelte 5+, this concept is obsolete, as snippets don't create a wrapping element

@ -0,0 +1,13 @@
---
title: <svelte:component>
---
In runes mode, `<MyComponent>` will re-render if the value of `MyComponent` changes.
In legacy mode, it won't — we must use `<svelte:component>`, which destroys and recreates the component instance when the value of its `this` expression changes:
```svelte
<svelte:component this={MyComponent} />
```
If `this` is falsy, no component is rendered.

@ -0,0 +1,37 @@
---
title: <svelte:self>
---
The `<svelte:self>` element allows a component to include itself, recursively.
It cannot appear at the top level of your markup; it must be inside an if or each block or passed to a component's slot to prevent an infinite loop.
```svelte
<script>
export let count;
</script>
{#if count > 0}
<p>counting down... {count}</p>
<svelte:self count={count - 1} />
{:else}
<p>lift-off!</p>
{/if}
```
> [!NOTE]
> This concept is obsolete, as components can import themselves:
> ```svelte
> <!--- file: App.svelte --->
> <script>
> import Self from './App.svelte'
> export let count;
> </script>
>
> {#if count > 0}
> <p>counting down... {count}</p>
> <Self count={count - 1} />
> {:else}
> <p>lift-off!</p>
> {/if}
> ```

@ -0,0 +1,200 @@
---
title: Imperative component API
---
In Svelte 3 and 4, the API for interacting with a component is different than in Svelte 5. Note that this page does _not_ apply to legacy mode components in a Svelte 5 application.
## Creating a component
```ts
// @noErrors
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.
```ts
// @noErrors
import App from './App.svelte';
const app = new App({
target: document.body,
props: {
// assuming App.svelte contains something like
// `export let answer`:
answer: 42
}
});
```
The following initialisation options can be provided:
| option | default | description |
| --------- | ----------- | ---------------------------------------------------------------------------------------------------- |
| `target` | **none** | An `HTMLElement` or `ShadowRoot` to render to. This option is required |
| `anchor` | `null` | A child of `target` to render the component immediately before |
| `props` | `{}` | An object of properties to supply to the component |
| `context` | `new Map()` | A `Map` of root-level context key-value pairs to supply to the component |
| `hydrate` | `false` | See below |
| `intro` | `false` | If `true`, will play transitions on initial render, rather than waiting for subsequent state changes |
Existing children of `target` are left where they are.
The `hydrate` option instructs Svelte to upgrade existing DOM (usually from server-side rendering) rather than creating new elements. It will only work if the component was compiled with the [`hydratable: true` option](/docs/svelte-compiler#compile). Hydration of `<head>` elements only works properly if the server-side rendering code was also compiled with `hydratable: true`, which adds a marker to each element in the `<head>` so that the component knows which elements it's responsible for removing during hydration.
Whereas children of `target` are normally left alone, `hydrate: true` will cause any children to be removed. For that reason, the `anchor` option cannot be used alongside `hydrate: true`.
The existing DOM doesn't need to match the component — Svelte will 'repair' the DOM as it goes.
```ts
/// file: index.js
// @noErrors
import App from './App.svelte';
const app = new App({
target: document.querySelector('#server-rendered-html'),
hydrate: true
});
```
> [!NOTE]
> In Svelte 5+, use [`mount`](svelte#mount) instead
## `$set`
```ts
// @noErrors
component.$set(props);
```
Programmatically sets props on an instance. `component.$set({ x: 1 })` is equivalent to `x = 1` inside the component's `<script>` block.
Calling this method schedules an update for the next microtask — the DOM is _not_ updated synchronously.
```ts
// @noErrors
component.$set({ answer: 42 });
```
> [!NOTE]
> In Svelte 5+, use `$state` instead to create a component props and update that
>
> ```js
> // @noErrors
> let props = $state({ answer: 42 });
> const component = mount(Component);
> // ...
> props.answer = 24;
> ```
## `$on`
```ts
// @noErrors
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.
```ts
// @noErrors
const off = component.$on('selected', (event) => {
console.log(event.detail.selection);
});
off();
```
> [!NOTE]
> In Svelte 5+, pass callback props instead
## `$destroy`
```js
// @noErrors
component.$destroy();
```
Removes a component from the DOM and triggers any `onDestroy` handlers.
> [!NOTE]
> In Svelte 5+, use [`unmount`](svelte#unmount) instead
## Component props
```js
// @noErrors
component.prop;
```
```js
// @noErrors
component.prop = value;
```
If a component is compiled with `accessors: true`, each instance will have getters and setters corresponding to each of the component's props. Setting a value will cause a _synchronous_ update, rather than the default async update caused by `component.$set(...)`.
By default, `accessors` is `false`, unless you're compiling as a custom element.
```js
// @noErrors
console.log(component.count);
component.count += 1;
```
> [!NOTE]
> In Svelte 5+, this concept is obsolete. If you want to make properties accessible from the outside, `export` them
## Server-side component API
```js
// @noErrors
const result = Component.render(...)
```
Unlike client-side components, server-side components don't have a lifespan after you render them — their whole job is to create some HTML and CSS. For that reason, the API is somewhat different.
A server-side component exposes a `render` method that can be called with optional props. It returns an object with `head`, `html`, and `css` properties, where `head` contains the contents of any `<svelte:head>` elements encountered.
You can import a Svelte component directly into Node using `svelte/register`.
```js
// @noErrors
require('svelte/register');
const App = require('./App.svelte').default;
const { head, html, css } = App.render({
answer: 42
});
```
The `.render()` method accepts the following parameters:
| parameter | default | description |
| --------- | ------- | -------------------------------------------------- |
| `props` | `{}` | An object of properties to supply to the component |
| `options` | `{}` | An object of options |
The `options` object takes in the following options:
| option | default | description |
| --------- | ----------- | ------------------------------------------------------------------------ |
| `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 },
// options
{
context: new Map([['context-key', 'context-value']])
}
);
```
> [!NOTE]
> In Svelte 5+, use [`render`](svelte-server#render) instead

@ -0,0 +1,3 @@
---
title: Legacy APIs
---
Loading…
Cancel
Save