Merge pull request #5 from sveltejs/main

Merge
pull/15185/head
adiGuba 2 years ago committed by GitHub
commit 541be4699f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -1,26 +0,0 @@
# https://github.com/sveltejs/svelte.dev/blob/main/apps/svelte.dev/scripts/sync-docs/README.md
name: Docs preview create request
on:
pull_request_target:
branches:
- main
jobs:
dispatch:
runs-on: ubuntu-latest
steps:
- name: Repository Dispatch
uses: peter-evans/repository-dispatch@v3
with:
token: ${{ secrets.SYNC_REQUEST_TOKEN }}
repository: sveltejs/svelte.dev
event-type: docs-preview-create
client-payload: |-
{
"package": "svelte",
"repo": "${{ github.repository }}",
"owner": "${{ github.event.pull_request.head.repo.owner.login }}",
"branch": "${{ github.event.pull_request.head.ref }}",
"pr": ${{ github.event.pull_request.number }}
}

@ -1,27 +0,0 @@
# https://github.com/sveltejs/svelte.dev/blob/main/apps/svelte.dev/scripts/sync-docs/README.md
name: Docs preview delete request
on:
pull_request_target:
branches:
- main
types: [closed]
jobs:
dispatch:
runs-on: ubuntu-latest
steps:
- name: Repository Dispatch
uses: peter-evans/repository-dispatch@v3
with:
token: ${{ secrets.SYNC_REQUEST_TOKEN }}
repository: sveltejs/svelte.dev
event-type: docs-preview-delete
client-payload: |-
{
"package": "svelte",
"repo": "${{ github.repository }}",
"owner": "${{ github.event.pull_request.head.repo.owner.login }}",
"branch": "${{ github.event.pull_request.head.ref }}",
"pr": ${{ github.event.pull_request.number }}
}

@ -1,22 +0,0 @@
# https://github.com/sveltejs/svelte.dev/blob/main/apps/svelte.dev/scripts/sync-docs/README.md
name: Sync request
on:
push:
branches:
- main
jobs:
dispatch:
runs-on: ubuntu-latest
steps:
- name: Repository Dispatch
uses: peter-evans/repository-dispatch@v3
with:
token: ${{ secrets.SYNC_REQUEST_TOKEN }}
repository: sveltejs/svelte.dev
event-type: sync-request
client-payload: |-
{
"package": "svelte"
}

@ -51,7 +51,7 @@ We use [GitHub issues](https://github.com/sveltejs/svelte/issues) for our public
If you have questions about using Svelte, contact us on Discord at [svelte.dev/chat](https://svelte.dev/chat), and we will do our best to answer your questions.
If you see anything you'd like to be implemented, create a [feature request issue](https://github.com/sveltejs/svelte/issues/new?template=feature_request.yml)
If you see anything you'd like to be implemented, create a [feature request issue](https://github.com/sveltejs/svelte/issues/new?template=feature_request.yml).
### Reporting new issues
@ -62,8 +62,6 @@ When [opening a new issue](https://github.com/sveltejs/svelte/issues/new/choose)
## Pull requests
> HEADS UP: Svelte 5 will likely change a lot on the compiler. For that reason, please don't open PRs that are large in scope, touch more than a couple of files etc. In other words, bug fixes are fine, but big feature PRs will likely not be merged.
### Proposing a change
If you would like to request a new feature or enhancement but are not yet thinking about opening a pull request, you can also file an issue with [feature template](https://github.com/sveltejs/svelte/issues/new?template=feature_request.yml).

@ -1,4 +1,4 @@
Copyright (c) 2016-24 [these people](https://github.com/sveltejs/svelte/graphs/contributors)
Copyright (c) 2016-2025 [these people](https://github.com/sveltejs/svelte/graphs/contributors)
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

@ -1,4 +1,9 @@
[![Cybernetically enhanced web apps: Svelte](https://sveltejs.github.io/assets/banner.png)](https://svelte.dev)
<a href="https://svelte.dev">
<picture>
<source media="(prefers-color-scheme: dark)" srcset="assets/banner_dark.png">
<img src="assets/banner.png" alt="Svelte - web development for the rest of us" />
</picture>
</a>
[![license](https://img.shields.io/npm/l/svelte.svg)](LICENSE.md) [![Chat](https://img.shields.io/discord/457912077277855764?label=chat&logo=discord)](https://svelte.dev/chat)
@ -24,10 +29,6 @@ You may view [our roadmap](https://svelte.dev/roadmap) if you'd like to see what
Please see the [Contributing Guide](CONTRIBUTING.md) and the [`svelte`](packages/svelte) package for information on contributing to Svelte.
### svelte.dev
The source code for https://svelte.dev lives in the [sites](https://github.com/sveltejs/svelte/tree/master/sites/svelte.dev) folder, with all the documentation right [here](https://github.com/sveltejs/svelte/tree/master/documentation). The site is built with [SvelteKit](https://svelte.dev/docs/kit).
## Is svelte.dev down?
Probably not, but it's possible. If you can't seem to access any `.dev` sites, check out [this SuperUser question and answer](https://superuser.com/q/1413402).

Binary file not shown.

After

Width:  |  Height:  |  Size: 97 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 102 KiB

@ -45,7 +45,7 @@ export async function kairo_avoidable_unowned() {
const { run, destroy } = setup();
const { timing } = await fastest_test(10, () => {
for (let i = 0; i < 100; i++) {
for (let i = 0; i < 1000; i++) {
run();
}
});
@ -74,7 +74,7 @@ export async function kairo_avoidable_owned() {
});
const { timing } = await fastest_test(10, () => {
for (let i = 0; i < 100; i++) {
for (let i = 0; i < 1000; i++) {
run();
}
});

@ -51,7 +51,7 @@ export async function kairo_broad_unowned() {
const { run, destroy } = setup();
const { timing } = await fastest_test(10, () => {
for (let i = 0; i < 100; i++) {
for (let i = 0; i < 1000; i++) {
run();
}
});
@ -80,7 +80,7 @@ export async function kairo_broad_owned() {
});
const { timing } = await fastest_test(10, () => {
for (let i = 0; i < 100; i++) {
for (let i = 0; i < 1000; i++) {
run();
}
});

@ -51,7 +51,7 @@ export async function kairo_deep_unowned() {
const { run, destroy } = setup();
const { timing } = await fastest_test(10, () => {
for (let i = 0; i < 100; i++) {
for (let i = 0; i < 1000; i++) {
run();
}
});
@ -80,7 +80,7 @@ export async function kairo_deep_owned() {
});
const { timing } = await fastest_test(10, () => {
for (let i = 0; i < 100; i++) {
for (let i = 0; i < 1000; i++) {
run();
}
});

@ -55,7 +55,7 @@ export async function kairo_diamond_unowned() {
const { run, destroy } = setup();
const { timing } = await fastest_test(10, () => {
for (let i = 0; i < 100; i++) {
for (let i = 0; i < 1000; i++) {
run();
}
});
@ -84,7 +84,7 @@ export async function kairo_diamond_owned() {
});
const { timing } = await fastest_test(10, () => {
for (let i = 0; i < 100; i++) {
for (let i = 0; i < 1000; i++) {
run();
}
});

@ -48,7 +48,7 @@ export async function kairo_mux_unowned() {
const { run, destroy } = setup();
const { timing } = await fastest_test(10, () => {
for (let i = 0; i < 100; i++) {
for (let i = 0; i < 1000; i++) {
run();
}
});
@ -77,7 +77,7 @@ export async function kairo_mux_owned() {
});
const { timing } = await fastest_test(10, () => {
for (let i = 0; i < 100; i++) {
for (let i = 0; i < 1000; i++) {
run();
}
});

@ -52,7 +52,7 @@ export async function kairo_repeated_unowned() {
const { run, destroy } = setup();
const { timing } = await fastest_test(10, () => {
for (let i = 0; i < 100; i++) {
for (let i = 0; i < 1000; i++) {
run();
}
});
@ -81,7 +81,7 @@ export async function kairo_repeated_owned() {
});
const { timing } = await fastest_test(10, () => {
for (let i = 0; i < 100; i++) {
for (let i = 0; i < 1000; i++) {
run();
}
});

@ -65,7 +65,7 @@ export async function kairo_triangle_unowned() {
const { run, destroy } = setup();
const { timing } = await fastest_test(10, () => {
for (let i = 0; i < 100; i++) {
for (let i = 0; i < 1000; i++) {
run();
}
});
@ -94,7 +94,7 @@ export async function kairo_triangle_owned() {
});
const { timing } = await fastest_test(10, () => {
for (let i = 0; i < 100; i++) {
for (let i = 0; i < 1000; i++) {
run();
}
});

@ -51,7 +51,7 @@ export async function kairo_unstable_unowned() {
const { run, destroy } = setup();
const { timing } = await fastest_test(10, () => {
for (let i = 0; i < 100; i++) {
for (let i = 0; i < 1000; i++) {
run();
}
});
@ -80,7 +80,7 @@ export async function kairo_unstable_owned() {
});
const { timing } = await fastest_test(10, () => {
for (let i = 0; i < 100; i++) {
for (let i = 0; i < 1000; i++) {
run();
}
});

@ -44,12 +44,7 @@ todos[0].done = !todos[0].done;
If you push a new object to the array, it will also be proxified:
```js
// @filename: ambient.d.ts
declare global {
const todos: Array<{ done: boolean, text: string }>
}
// @filename: index.js
let todos = [{ done: false, text: 'add more todos' }];
// ---cut---
todos.push({
done: false,

@ -51,3 +51,20 @@ In essence, `$derived(expression)` is equivalent to `$derived.by(() => expressio
Anything read synchronously inside the `$derived` expression (or `$derived.by` function body) is considered a _dependency_ of the derived state. When the state changes, the derived will be marked as _dirty_ and recalculated when it is next read.
To exempt a piece of state from being treated as a dependency, use [`untrack`](svelte#untrack).
## Update propagation
Svelte uses something called _push-pull reactivity_ — when state is updated, everything that depends on the state (whether directly or indirectly) is immediately notified of the change (the 'push'), but derived values are not re-evaluated until they are actually read (the 'pull').
If the new value of a derived is referentially identical to its previous value, downstream updates will be skipped. In other words, Svelte will only update the text inside the button when `large` changes, not when `count` changes, even though `large` depends on `count`:
```svelte
<script>
let count = $state(0);
let large = $derived(count > 10);
</script>
<button onclick={() => count++}>
{large}
</button>
```

@ -66,7 +66,7 @@ You can return a function from `$effect`, which will run immediately before the
### Understanding dependencies
`$effect` automatically picks up any reactive values (`$state`, `$derived`, `$props`) that are _synchronously_ read inside its function body and registers them as dependencies. When those dependencies change, the `$effect` schedules a rerun.
`$effect` automatically picks up any reactive values (`$state`, `$derived`, `$props`) that are _synchronously_ read inside its function body (including indirectly, via function calls) and registers them as dependencies. When those dependencies change, the `$effect` schedules a rerun.
Values that are read _asynchronously_ — after an `await` or inside a `setTimeout`, for example — will not be tracked. Here, the canvas will be repainted when `color` changes, but not when `size` changes ([demo](/playground/untitled#H4sIAAAAAAAAE31T246bMBD9lZF3pWSlBEirfaEQqdo_2PatVIpjBrDkGGQPJGnEv1e2IZfVal-wfHzmzJyZ4cIqqdCy9M-F0blDlnqArZjmB3f72XWRHVCRw_bc4me4aDWhJstSlllhZEfbQhekkMDKfwg5PFvihMvX5OXH_CJa1Zrb0-Kpqr5jkiwC48rieuDWQbqgZ6wqFLRcvkC-hYvnkWi1dWqa8ESQTxFRjfQWsOXiWzmr0sSLhEJu3p1YsoJkNUcdZUnN9dagrBu6FVRQHAM10sJRKgUG16bXcGxQ44AGdt7SDkTDdY02iqLHnJVU6hedlWuIp94JW6Tf8oBt_8GdTxlF0b4n0C35ZLBzXb3mmYn3ae6cOW74zj0YVzDNYXRHFt9mprNgHfZSl6mzml8CMoLvTV6wTZIUDEJv5us2iwMtiJRyAKG4tXnhl8O0yhbML0Wm-B7VNlSSSd31BG7z8oIZZ6dgIffAVY_5xdU9Qrz1Bnx8fCfwtZ7v8Qc9j3nB8PqgmMWlHIID6-bkVaPZwDySfWtKNGtquxQ23Qlsq2QJT0KIqb8dL0up6xQ2eIBkAg_c1FI_YqW0neLnFCqFpwmreedJYT7XX8FVOBfwWRhXstZrSXiwKQjUhOZeMIleb5JZfHWn2Yq5pWEpmR7Hv-N_wEqT8hEEAAA=)):
@ -125,7 +125,11 @@ An effect only reruns when the object it reads changes, not when a property insi
<p>{state.value} doubled is {derived.value}</p>
```
An effect only depends on the values that it read the last time it ran. If `a` is true, changes to `b` will [not cause this effect to rerun](/playground/untitled#H4sIAAAAAAAAE3WQ0WrDMAxFf0U1hTow1vcsMfQ7lj3YjlxEXTvEymC4_vfFC6Ewtidxde8RkrJw5DGJ9j2LoO8oWnGZJvEi-GuqIn2iZ1x1istsa6dLdqaJ1RAG9sigoYdjYs0onfYJm7fdMX85q3dE59CylA30CnJtDWxjSNHjq49XeZqXEChcT9usLUAOpIbHA0yzM78oColGhDVofLS3neZSS6mqOz-XD51ZmGOAGKwne-vztk-956CL0kAJsi7decupf4l658EUZX4I8yTWt93jSI5wFC3PC5aP8g0Aje5DcQEAAA==):
An effect only depends on the values that it read the last time it ran. This has interesting implications for effects that have conditional code.
For instance, if `a` is `true` in the code snippet below, the code inside the `if` block will run and `b` will be evaluated. As such, changes to either `a` or `b` [will cause the effect to re-run](/playground/untitled#H4sIAAAAAAAAE3VQzWrDMAx-FdUU4kBp71li6EPstOxge0ox8-QQK2PD-N1nLy2F0Z2Evj9_chKkP1B04pnYscc3cRCT8xhF95IEf8-Vq0DBr8rzPB_jJ3qumNERH-E2ECNxiRF9tIubWY00lgcYNAywj6wZJS8rtk83wjwgCrXHaULLUrYwKEgVGrnkx-Dx6MNFNstK5OjSbFGbwE0gdXuT_zGYrjmAuco515Hr1p_uXak3K3MgCGS9s-9D2grU-judlQYXIencnzad-tdR79qZrMyvw9wd5Z8Yv1h09dz8mn8AkM7Pfo0BAAA=).
Conversely, if `a` is `false`, `b` will not be evaluated, and the effect will _only_ re-run when `a` changes.
```ts
let a = false;
@ -134,8 +138,8 @@ let b = false;
$effect(() => {
console.log('running');
if (a || b) {
console.log('inside if block');
if (a) {
console.log('b:', b);
}
});
```
@ -193,53 +197,7 @@ The `$effect.tracking` rune is an advanced feature that tells you whether or not
<p>in template: {$effect.tracking()}</p> <!-- true -->
```
This allows you to (for example) add things like subscriptions without causing memory leaks, by putting them in child effects. Here's a `readable` function that listens to changes from a callback function as long as it's inside a tracking context:
```ts
import { tick } from 'svelte';
export default function readable<T>(
initial_value: T,
start: (callback: (update: (v: T) => T) => T) => () => void
) {
let value = $state(initial_value);
let subscribers = 0;
let stop: null | (() => void) = null;
return {
get value() {
// If in a tracking context ...
if ($effect.tracking()) {
$effect(() => {
// ...and there's no subscribers yet...
if (subscribers === 0) {
// ...invoke the function and listen to changes to update state
stop = start((fn) => (value = fn(value)));
}
subscribers++;
// The return callback is called once a listener unlistens
return () => {
tick().then(() => {
subscribers--;
// If it was the last subscriber...
if (subscribers === 0) {
// ...stop listening to changes
stop?.();
stop = null;
}
});
};
});
}
return value;
}
};
}
```
It is used to implement abstractions like [`createSubscriber`](/docs/svelte/svelte-reactivity#createSubscriber), which will create listeners to update reactive values but _only_ if those values are being tracked (rather than, for example, read inside an event handler).
## `$effect.root`

@ -196,4 +196,6 @@ You can, of course, separate the type declaration from the annotation:
</script>
```
> [!NOTE] Interfaces for native DOM elements are provided in the `svelte/elements` module (see [Typing wrapper components](typescript#Typing-wrapper-components))
Adding types is recommended, as it ensures that people using your component can easily discover which props they should provide.

@ -11,4 +11,4 @@ The `{@const ...}` tag defines a local constant.
{/each}
```
`{@const}` is only allowed as an immediate child of a block — `{#if ...}`, `{#each ...}`, `{#snippet ...}` and so on — or a `<Component />`.
`{@const}` is only allowed as an immediate child of a block — `{#if ...}`, `{#each ...}`, `{#snippet ...}` and so on — a `<Component />` or a `<svelte:boundary>`.

@ -235,7 +235,7 @@ You can give the `<select>` a default value by adding a `selected` attribute to
## `<video>`
`<video>` elements have all the same bindings as [#audio] elements, plus readonly [`videoWidth`](https://developer.mozilla.org/en-US/docs/Web/API/HTMLVideoElement/videoWidth) and [`videoHeight`](https://developer.mozilla.org/en-US/docs/Web/API/HTMLVideoElement/videoHeight) bindings.
`<video>` elements have all the same bindings as [`<audio>`](#audio) elements, plus readonly [`videoWidth`](https://developer.mozilla.org/en-US/docs/Web/API/HTMLVideoElement/videoWidth) and [`videoHeight`](https://developer.mozilla.org/en-US/docs/Web/API/HTMLVideoElement/videoHeight) bindings.
## `<img>`

@ -50,17 +50,16 @@ The `Action` interface receives three optional type arguments — a node type (w
```svelte
<!--- file: App.svelte --->
<script>
import { on } from 'svelte/events';
/**
* @type {import('svelte/action').Action<
* HTMLDivElement,
* null,
* undefined,
* {
* onswiperight: (e: CustomEvent) => void;
* onswipeleft: (e: CustomEvent) => void;
* // ...
* }>}
* }
* >}
*/
function gestures(node) {
$effect(() => {

@ -71,6 +71,18 @@ The user of this component has the same flexibility to use a mixture of objects,
</Button>
```
Svelte also exposes the `ClassValue` type, which is the type of value that the `class` attribute on elements accept. This is useful if you want to use a type-safe class name in component props:
```svelte
<script lang="ts">
import type { ClassValue } from 'svelte/elements';
const props: { class: ClassValue } = $props();
</script>
<div class={['original', props.class]}>...</div>
```
## The `class:` directive
Prior to Svelte 5.16, the `class:` directive was the most convenient way to set classes on elements conditionally.

@ -33,10 +33,7 @@ If a component defines `@keyframes`, the name is scoped to the component using t
/* these keyframes are only accessible inside this component */
@keyframes bounce {
/* ... *.
/* ... */
}
</style>
```

@ -13,7 +13,7 @@ Boundaries allow you to guard against errors in part of your app from breaking t
If an error occurs while rendering or updating the children of a `<svelte:boundary>`, or running any [`$effect`]($effect) functions contained therein, the contents will be removed.
Errors occurring outside the rendering process (for example, in event handlers) are _not_ caught by error boundaries.
Errors occurring outside the rendering process (for example, in event handlers or after a `setTimeout` or async work) are _not_ caught by error boundaries.
## Properties

@ -40,7 +40,7 @@ You can now write unit tests for code inside your `.js/.ts` files:
/// file: multiplier.svelte.test.js
import { flushSync } from 'svelte';
import { expect, test } from 'vitest';
import { multiplier } from './multiplier.js';
import { multiplier } from './multiplier.svelte.js';
test('Multiplier', () => {
let double = multiplier(0, 2);
@ -53,9 +53,30 @@ test('Multiplier', () => {
});
```
```js
/// file: multiplier.svelte.js
/**
* @param {number} initial
* @param {number} k
*/
export function multiplier(initial, k) {
let count = $state(initial);
return {
get value() {
return count * k;
},
/** @param {number} c */
set: (c) => {
count = c;
}
};
}
```
### Using runes inside your test files
It is possible to use runes inside your test files. First ensure your bundler knows to route the file through the Svelte compiler before running the test by adding `.svelte` to the filename (e.g `multiplier.svelte.test.js`). After that, you can use runes inside your tests.
Since Vitest processes your test files the same way as your source files, you can use runes inside your tests as long as the filename includes `.svelte`:
```js
/// file: multiplier.svelte.test.js
@ -75,6 +96,21 @@ test('Multiplier', () => {
});
```
```js
/// file: multiplier.svelte.js
/**
* @param {() => number} getCount
* @param {number} k
*/
export function multiplier(getCount, k) {
return {
get value() {
return getCount() * k;
}
};
}
```
If the code being tested uses effects, you need to wrap the test inside `$effect.root`:
```js
@ -105,6 +141,27 @@ test('Effect', () => {
});
```
```js
/// file: logger.svelte.js
/**
* @param {() => any} getValue
*/
export function logger(getValue) {
/** @type {any[]} */
let log = $state([]);
$effect(() => {
log.push(getValue());
});
return {
get value() {
return log;
}
};
}
```
### Component testing
It is possible to test your components in isolation using Vitest.

@ -125,3 +125,4 @@ Custom elements can be a useful way to package components for consumption in a n
- The deprecated `let:` directive has no effect, because custom elements do not have a way to pass data to the parent component that fills the slot
- Polyfills are required to support older browsers
- You can use Svelte's context feature between regular Svelte components within a custom element, but you can't use them across custom elements. In other words, you can't use `setContext` on a parent custom element and read that with `getContext` in a child custom element.
- Don't declare properties or attributes starting with `on`, as their usage will be interpreted as an event listener. In other words, Svelte treats `<custom-element oneworld={true}></custom-element>` as `customElement.addEventListener('eworld', true)` (and not as `customElement.oneworld = true`)

@ -339,7 +339,31 @@ When spreading props, local event handlers must go _after_ the spread, or they r
In Svelte 4, content can be passed to components using slots. Svelte 5 replaces them with snippets which are more powerful and flexible, and as such slots are deprecated in Svelte 5.
They continue to work, however, and you can mix and match snippets and slots in your components.
They continue to work, however, and you can pass snippets to a component that uses slots:
```svelte
<!--- file: Child.svelte --->
<slot />
<hr />
<slot name="foo" message="hello" />
```
```svelte
<!--- file: Parent.svelte --->
<script>
import Child from './Child.svelte';
</script>
<Child>
default child content
{#snippet foo({ message })}
message from child: {message}
{/snippet}
</Child>
```
(The reverse is not true — you cannot pass slotted content to a component that uses [`{@render ...}`](/docs/svelte/@render) tags.)
When using custom elements, you should still use `<slot />` like before. In a future version, when Svelte removes its internal version of slots, it will leave those slots as-is, i.e. output a regular DOM tag instead of transforming it.
@ -597,28 +621,57 @@ export declare const MyComponent: Component<{
To declare that a component of a certain type is required:
```js
import { ComponentA, ComponentB } from 'component-library';
---import type { SvelteComponent } from 'svelte';---
+++import type { Component } from 'svelte';+++
---let C: typeof SvelteComponent<{ foo: string }> = $state(---
+++let C: Component<{ foo: string }> = $state(+++
Math.random() ? ComponentA : ComponentB
);
```
The two utility types `ComponentEvents` and `ComponentType` are also deprecated. `ComponentEvents` is obsolete because events are defined as callback props now, and `ComponentType` is obsolete because the new `Component` type is the component type already (i.e. `ComponentType<SvelteComponent<{ prop: string }>>` is equivalent to `Component<{ prop: string }>`).
### bind:this changes
Because components are no longer classes, using `bind:this` no longer returns a class instance with `$set`, `$on` and `$destroy` methods on it. It only returns the instance exports (`export function/const`) and, if you're using the `accessors` option, a getter/setter-pair for each property.
## `<svelte:component>` is no longer necessary
In Svelte 4, components are _static_ — if you render `<Thing>`, and the value of `Thing` changes, [nothing happens](/playground/7f1fa24f0ab44c1089dcbb03568f8dfa?version=4.2.18). To make it dynamic you had to use `<svelte:component>`.
This is no longer true in Svelte 5:
```svelte
<script lang="ts">
import type { ---SvelteComponent--- +++Component+++ } from 'svelte';
import {
ComponentA,
ComponentB
} from 'component-library';
<script>
import A from './A.svelte';
import B from './B.svelte';
---let component: typeof SvelteComponent<{ foo: string }>---
+++let component: Component<{ foo: string }>+++ = $state(
Math.random() ? ComponentA : ComponentB
);
let Thing = $state();
</script>
<svelte:component this={component} foo="bar" />
<select bind:value={Thing}>
<option value={A}>A</option>
<option value={B}>B</option>
</select>
<!-- these are equivalent -->
<Thing />
<svelte:component this={Thing} />
```
While migrating, keep in mind that your component's name should be capitalized (`Thing`) to distinguish it from elements, unless using dot notation.
The two utility types `ComponentEvents` and `ComponentType` are also deprecated. `ComponentEvents` is obsolete because events are defined as callback props now, and `ComponentType` is obsolete because the new `Component` type is the component type already (e.g. `ComponentType<SvelteComponent<{ prop: string }>>` == `Component<{ prop: string }>`).
### Dot notation indicates a component
### bind:this changes
In Svelte 4, `<foo.bar>` would create an element with a tag name of `"foo.bar"`. In Svelte 5, `foo.bar` is treated as a component instead. This is particularly useful inside `each` blocks:
Because components are no longer classes, using `bind:this` no longer returns a class instance with `$set`, `$on` and `$destroy` methods on it. It only returns the instance exports (`export function/const`) and, if you're using the `accessors` option, a getter/setter-pair for each property.
```svelte
{#each items as item}
<item.component {...item.props} />
{/each}
```
## Whitespace handling changed
@ -653,16 +706,6 @@ The `legacy` compiler option, which generated bulkier but IE-friendly code, no l
Content inside component tags becomes a snippet prop called `children`. You cannot have a separate prop by that name.
## Dot notation indicates a component
In Svelte 4, `<foo.bar>` would create an element with a tag name of `"foo.bar"`. In Svelte 5, `foo.bar` is treated as a component instead. This is particularly useful inside `each` blocks:
```svelte
{#each items as item}
<item.component {...item.props} />
{/each}
```
## Breaking changes in runes mode
Some breaking changes only apply once your component is in runes mode.
@ -700,30 +743,6 @@ In Svelte 4, doing the following triggered reactivity:
This is because the Svelte compiler treated the assignment to `foo.value` as an instruction to update anything that referenced `foo`. In Svelte 5, reactivity is determined at runtime rather than compile time, so you should define `value` as a reactive `$state` field on the `Foo` class. Wrapping `new Foo()` with `$state(...)` will have no effect — only vanilla objects and arrays are made deeply reactive.
### `<svelte:component>` is no longer necessary
In Svelte 4, components are _static_ — if you render `<Thing>`, and the value of `Thing` changes, [nothing happens](/playground/7f1fa24f0ab44c1089dcbb03568f8dfa?version=4.2.18). To make it dynamic you must use `<svelte:component>`.
This is no longer true in Svelte 5:
```svelte
<script>
import A from './A.svelte';
import B from './B.svelte';
let Thing = $state();
</script>
<select bind:value={Thing}>
<option value={A}>A</option>
<option value={B}>B</option>
</select>
<!-- these are equivalent -->
<Thing />
<svelte:component this={Thing} />
```
### Touch and wheel events are passive
When using `onwheel`, `onmousewheel`, `ontouchstart` and `ontouchmove` event attributes, the handlers are [passive](https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener#using_passive_listeners) to align with browser defaults. This greatly improves responsiveness by allowing the browser to scroll the document immediately, rather than waiting to see if the event handler calls `event.preventDefault()`.

@ -222,3 +222,15 @@ Reactive `$state(...)` proxies and the values they proxy have different identiti
```
To resolve this, ensure you're comparing values where both values were created with `$state(...)`, or neither were. Note that `$state.raw(...)` will _not_ create a state proxy.
### transition_slide_display
```
The `slide` transition does not work correctly for elements with `display: %value%`
```
The [slide](/docs/svelte/svelte-transition#slide) transition works by animating the `height` of the element, which requires a `display` style like `block`, `flex` or `grid`. It does not work for:
- `display: inline` (which is the default for elements like `<span>`), and its variants like `inline-block`, `inline-flex` and `inline-grid`
- `display: table` and `table-[name]`, which are the defaults for elements like `<table>` and `<tr>`
- `display: contents`

@ -187,7 +187,7 @@ Cyclical dependency detected: %cycle%
### const_tag_invalid_placement
```
`{@const}` must be the immediate child of `{#snippet}`, `{#if}`, `{:else if}`, `{:else}`, `{#each}`, `{:then}`, `{:catch}`, `<svelte:fragment>` or `<Component>`
`{@const}` must be the immediate child of `{#snippet}`, `{#if}`, `{:else if}`, `{:else}`, `{#each}`, `{:then}`, `{:catch}`, `<svelte:fragment>`, `<svelte:boundary` or `<Component>`
```
### constant_assignment
@ -331,7 +331,39 @@ The $ prefix is reserved, and cannot be used for variables and imports
### each_item_invalid_assignment
```
Cannot reassign or bind to each block argument in runes mode. Use the array and index variables instead (e.g. `array[i] = value` instead of `entry = value`)
Cannot reassign or bind to each block argument in runes mode. Use the array and index variables instead (e.g. `array[i] = value` instead of `entry = value`, or `bind:value={array[i]}` instead of `bind:value={entry}`)
```
In legacy mode, it was possible to reassign or bind to the each block argument itself:
```svelte
<script>
let array = [1, 2, 3];
</script>
{#each array as entry}
<!-- reassignment -->
<button on:click={() => entry = 4}>change</button>
<!-- binding -->
<input bind:value={entry}>
{/each}
```
This turned out to be buggy and unpredictable, particularly when working with derived values (such as `array.map(...)`), and as such is forbidden in runes mode. You can achieve the same outcome by using the index instead:
```svelte
<script>
let array = $state([1, 2, 3]);
</script>
{#each array as entry, i}
<!-- reassignment -->
<button onclick={() => array[i] = 4}>change</button>
<!-- binding -->
<input bind:value={array[i]}>
{/each}
```
### effect_invalid_placement
@ -1012,6 +1044,12 @@ Unexpected end of input
'%word%' is a reserved word in JavaScript and cannot be used here
```
### unterminated_string_constant
```
Unterminated string constant
```
### void_element_invalid_content
```

@ -2,7 +2,7 @@
title: <svelte:component>
---
In runes mode, `<MyComponent>` will re-render if the value of `MyComponent` changes. See the [Svelte 5 migration guide](/docs/svelte/v5-migration-guide#Breaking-changes-in-runes-mode-svelte:component-is-no-longer-necessary) for an example.
In runes mode, `<MyComponent>` will re-render if the value of `MyComponent` changes. See the [Svelte 5 migration guide](/docs/svelte/v5-migration-guide#svelte:component-is-no-longer-necessary) for an example.
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:

@ -1,5 +1,217 @@
# svelte
## 5.19.6
### Patch Changes
- fix: do not prune selectors like `:global(.foo):has(.scoped)` ([#15140](https://github.com/sveltejs/svelte/pull/15140))
- fix: don't error on slot prop inside block inside other component ([#15148](https://github.com/sveltejs/svelte/pull/15148))
- fix: ensure reactions are correctly attached for unowned deriveds ([#15158](https://github.com/sveltejs/svelte/pull/15158))
- fix: silence a11y attribute warnings when spread attributes present ([#15150](https://github.com/sveltejs/svelte/pull/15150))
- fix: prevent false-positive ownership validations due to hot reload ([#15154](https://github.com/sveltejs/svelte/pull/15154))
- fix: widen ownership when calling setContext ([#15153](https://github.com/sveltejs/svelte/pull/15153))
## 5.19.5
### Patch Changes
- fix: improve derived connection to ownership graph ([#15137](https://github.com/sveltejs/svelte/pull/15137))
- fix: correctly look for sibling elements inside blocks and components when pruning CSS ([#15106](https://github.com/sveltejs/svelte/pull/15106))
## 5.19.4
### Patch Changes
- fix: Add `bind:focused` property to `HTMLAttributes` type ([#15122](https://github.com/sveltejs/svelte/pull/15122))
- fix: lazily connect derievds (in deriveds) to their parent ([#15129](https://github.com/sveltejs/svelte/pull/15129))
- fix: disallow $state/$derived in const tags ([#15115](https://github.com/sveltejs/svelte/pull/15115))
## 5.19.3
### Patch Changes
- fix: don't throw for `undefined` non delegated event handlers ([#15087](https://github.com/sveltejs/svelte/pull/15087))
- fix: consistently set value to blank string when value attribute is undefined ([#15057](https://github.com/sveltejs/svelte/pull/15057))
- fix: optimise || expressions in template ([#15092](https://github.com/sveltejs/svelte/pull/15092))
- fix: correctly handle `novalidate` attribute casing ([#15083](https://github.com/sveltejs/svelte/pull/15083))
- fix: expand boolean attribute support ([#15095](https://github.com/sveltejs/svelte/pull/15095))
- fix: avoid double deriveds in component props ([#15089](https://github.com/sveltejs/svelte/pull/15089))
- fix: add check for `is` attribute to correctly detect custom elements ([#15086](https://github.com/sveltejs/svelte/pull/15086))
## 5.19.2
### Patch Changes
- fix: address regression with untrack ([#15079](https://github.com/sveltejs/svelte/pull/15079))
## 5.19.1
### Patch Changes
- fix: omit unnecessary nullish coallescing in template expressions ([#15056](https://github.com/sveltejs/svelte/pull/15056))
- fix: more efficient template effect grouping ([#15050](https://github.com/sveltejs/svelte/pull/15050))
- fix: ensure untrack correctly retains the active reaction ([#15065](https://github.com/sveltejs/svelte/pull/15065))
- fix: initialize `files` bind on hydration ([#15059](https://github.com/sveltejs/svelte/pull/15059))
## 5.19.0
### Minor Changes
- feat: Expose `ClassValue` from `svelte/elements` ([#15035](https://github.com/sveltejs/svelte/pull/15035))
### Patch Changes
- fix: create fewer deriveds for concatenated strings ([#15041](https://github.com/sveltejs/svelte/pull/15041))
- fix: correctly parse leading comments in function binding ([#15020](https://github.com/sveltejs/svelte/pull/15020))
## 5.18.0
### Minor Changes
- feat: allow `<template>` elements to contain any child ([#15007](https://github.com/sveltejs/svelte/pull/15007))
### Patch Changes
- fix: ensure resume effects are scheduled in topological order ([#15012](https://github.com/sveltejs/svelte/pull/15012))
- fix: bump esrap ([#15015](https://github.com/sveltejs/svelte/pull/15015))
- fix: remove listener on `bind_current_time` teardown ([#15013](https://github.com/sveltejs/svelte/pull/15013))
## 5.17.5
### Patch Changes
- feat: allow const tag inside `svelte:boundary` ([#14993](https://github.com/sveltejs/svelte/pull/14993))
- fix: ensure signal write invalidation within effects is consistent ([#14989](https://github.com/sveltejs/svelte/pull/14989))
## 5.17.4
### Patch Changes
- fix: never consider inert boundary effects ([#14999](https://github.com/sveltejs/svelte/pull/14999))
- fix: store access on component destroy ([#14968](https://github.com/sveltejs/svelte/pull/14968))
- fix: correctly transform `pre` with no content ([#14973](https://github.com/sveltejs/svelte/pull/14973))
- fix: wrap each block expression in derived to encapsulate effects ([#14967](https://github.com/sveltejs/svelte/pull/14967))
## 5.17.3
### Patch Changes
- fix: reset dependency read versions after reaction execution ([#14964](https://github.com/sveltejs/svelte/pull/14964))
## 5.17.2
### Patch Changes
- fix: account for parent scale when animating elements ([#14957](https://github.com/sveltejs/svelte/pull/14957))
- fix: apply `overflow: hidden` style when transitioning elements, where necessary ([#14930](https://github.com/sveltejs/svelte/pull/14930))
- fix: properly add owners to function bindings ([#14962](https://github.com/sveltejs/svelte/pull/14962))
## 5.17.1
### Patch Changes
- fix: remove bindable prop validation ([#14946](https://github.com/sveltejs/svelte/pull/14946))
- chore: tweak "invalid assignment" compiler error message ([#14955](https://github.com/sveltejs/svelte/pull/14955))
- fix: silence false-positive stale value warning ([#14958](https://github.com/sveltejs/svelte/pull/14958))
## 5.17.0
### Minor Changes
- feat: allow non-numeric values to be tweened by snapping immediately to new value ([#14941](https://github.com/sveltejs/svelte/pull/14941))
### Patch Changes
- fix: handle default values in object destructuring within "each" blocks when using characters like "}" and "]" ([#14554](https://github.com/sveltejs/svelte/pull/14554))
- fix: account for min-width/height in `slide` transition ([#14942](https://github.com/sveltejs/svelte/pull/14942))
- fix: prevent long delays causing erratic spring behaviour ([#14940](https://github.com/sveltejs/svelte/pull/14940))
- feat: warn on using `slide` transition with table elements ([#14936](https://github.com/sveltejs/svelte/pull/14936))
- chore: improve signal performance by reducing duplicate deps ([#14945](https://github.com/sveltejs/svelte/pull/14945))
## 5.16.6
### Patch Changes
- fix: Make Tween duration 0 set current to target immediately ([#14937](https://github.com/sveltejs/svelte/pull/14937))
- fix: guard against `customElements` being unavailable in browser extension contexts ([#14933](https://github.com/sveltejs/svelte/pull/14933))
- fix: treat `inert` as a boolean attribute ([#14935](https://github.com/sveltejs/svelte/pull/14935))
- fix: remove leading newline from `<pre>` contents ([#14922](https://github.com/sveltejs/svelte/pull/14922))
## 5.16.5
### Patch Changes
- fix: inherit correct namespace for `<title>` elements ([#14817](https://github.com/sveltejs/svelte/pull/14817))
- fix: don't throw `bind_invalid_export` if there's also a bindable prop with the same name ([#14813](https://github.com/sveltejs/svelte/pull/14813))
## 5.16.4
### Patch Changes
- fix: use cached indexOf array prototype method internally ([#14912](https://github.com/sveltejs/svelte/pull/14912))
- fix: make Tween work with continuous target changes ([#14895](https://github.com/sveltejs/svelte/pull/14895))
## 5.16.3
### Patch Changes
- fix: correctly parse `each` with loose parser ([#14887](https://github.com/sveltejs/svelte/pull/14887))
- fix: apply `clsx` logic to custom element `class` attributes ([#14907](https://github.com/sveltejs/svelte/pull/14907))
## 5.16.2
### Patch Changes
- fix: ensure disconnected deriveds correctly connect again ([#14899](https://github.com/sveltejs/svelte/pull/14899))
- fix: correctly highlight sources reassigned inside `trace` ([#14811](https://github.com/sveltejs/svelte/pull/14811))
## 5.16.1
### Patch Changes
- fix: ensure unowned deriveds correctly get re-linked to the graph ([#14855](https://github.com/sveltejs/svelte/pull/14855))
- fix: ensure $inspect.trace works correctly with null values ([#14853](https://github.com/sveltejs/svelte/pull/14853))
## 5.16.0
### Minor Changes

@ -1,4 +1,9 @@
[![Cybernetically enhanced web apps: Svelte](https://sveltejs.github.io/assets/banner.png)](https://svelte.dev)
<a href="https://svelte.dev">
<picture>
<source media="(prefers-color-scheme: dark)" srcset="../../assets/banner_dark.png">
<img src="../../assets/banner.png" alt="Svelte - web development for the rest of us" />
</picture>
</a>
[![npm version](https://img.shields.io/npm/v/svelte.svg)](https://www.npmjs.com/package/svelte) [![license](https://img.shields.io/npm/l/svelte.svg)](LICENSE.md) [![Chat](https://img.shields.io/discord/457912077277855764?label=chat&logo=discord)](https://svelte.dev/chat)

@ -741,7 +741,7 @@ export interface HTMLAttributes<T extends EventTarget> extends AriaAttributes, D
accesskey?: string | undefined | null;
autocapitalize?: 'characters' | 'off' | 'on' | 'none' | 'sentences' | 'words' | undefined | null;
autofocus?: boolean | undefined | null;
class?: string | import('clsx').ClassArray | import('clsx').ClassDictionary | undefined | null;
class?: ClassValue | undefined | null;
contenteditable?: Booleanish | 'inherit' | 'plaintext-only' | undefined | null;
contextmenu?: string | undefined | null;
dir?: 'ltr' | 'rtl' | 'auto' | undefined | null;
@ -839,6 +839,7 @@ export interface HTMLAttributes<T extends EventTarget> extends AriaAttributes, D
readonly 'bind:contentBoxSize'?: Array<ResizeObserverSize> | undefined | null;
readonly 'bind:borderBoxSize'?: Array<ResizeObserverSize> | undefined | null;
readonly 'bind:devicePixelContentBoxSize'?: Array<ResizeObserverSize> | undefined | null;
readonly 'bind:focused'?: boolean | undefined | null;
// SvelteKit
'data-sveltekit-keepfocus'?: true | '' | 'off' | undefined | null;
@ -1522,7 +1523,7 @@ export interface SvelteWindowAttributes extends HTMLAttributes<Window> {
export interface SVGAttributes<T extends EventTarget> extends AriaAttributes, DOMAttributes<T> {
// Attributes which also defined in HTMLAttributes
className?: string | undefined | null;
class?: string | import('clsx').ClassArray | import('clsx').ClassDictionary | undefined | null;
class?: ClassValue | undefined | null;
color?: string | undefined | null;
height?: number | string | undefined | null;
id?: string | undefined | null;
@ -2059,3 +2060,5 @@ export interface SvelteHTMLElements {
[name: string]: { [name: string]: any };
}
export type ClassValue = string | import('clsx').ClassArray | import('clsx').ClassDictionary;

@ -186,3 +186,13 @@ To fix it, either create callback props to communicate changes, or mark `person`
```
To resolve this, ensure you're comparing values where both values were created with `$state(...)`, or neither were. Note that `$state.raw(...)` will _not_ create a state proxy.
## transition_slide_display
> The `slide` transition does not work correctly for elements with `display: %value%`
The [slide](/docs/svelte/svelte-transition#slide) transition works by animating the `height` of the element, which requires a `display` style like `block`, `flex` or `grid`. It does not work for:
- `display: inline` (which is the default for elements like `<span>`), and its variants like `inline-block`, `inline-flex` and `inline-grid`
- `display: table` and `table-[name]`, which are the defaults for elements like `<table>` and `<tr>`
- `display: contents`

@ -32,7 +32,39 @@
## each_item_invalid_assignment
> Cannot reassign or bind to each block argument in runes mode. Use the array and index variables instead (e.g. `array[i] = value` instead of `entry = value`)
> Cannot reassign or bind to each block argument in runes mode. Use the array and index variables instead (e.g. `array[i] = value` instead of `entry = value`, or `bind:value={array[i]}` instead of `bind:value={entry}`)
In legacy mode, it was possible to reassign or bind to the each block argument itself:
```svelte
<script>
let array = [1, 2, 3];
</script>
{#each array as entry}
<!-- reassignment -->
<button on:click={() => entry = 4}>change</button>
<!-- binding -->
<input bind:value={entry}>
{/each}
```
This turned out to be buggy and unpredictable, particularly when working with derived values (such as `array.map(...)`), and as such is forbidden in runes mode. You can achieve the same outcome by using the index instead:
```svelte
<script>
let array = $state([1, 2, 3]);
</script>
{#each array as entry, i}
<!-- reassignment -->
<button onclick={() => array[i] = 4}>change</button>
<!-- binding -->
<input bind:value={array[i]}>
{/each}
```
## effect_invalid_placement

@ -118,7 +118,7 @@
## const_tag_invalid_placement
> `{@const}` must be the immediate child of `{#snippet}`, `{#if}`, `{:else if}`, `{:else}`, `{#each}`, `{:then}`, `{:catch}`, `<svelte:fragment>` or `<Component>`
> `{@const}` must be the immediate child of `{#snippet}`, `{#if}`, `{:else if}`, `{:else}`, `{#each}`, `{:then}`, `{:catch}`, `<svelte:fragment>`, `<svelte:boundary` or `<Component>`
## debug_tag_invalid_arguments
@ -418,6 +418,10 @@ See https://html.spec.whatwg.org/multipage/custom-elements.html#valid-custom-ele
> '%word%' is a reserved word in JavaScript and cannot be used here
## unterminated_string_constant
> Unterminated string constant
## void_element_invalid_content
> Void elements cannot have children or closing tags

@ -2,7 +2,7 @@
"name": "svelte",
"description": "Cybernetically enhanced web apps",
"license": "MIT",
"version": "5.16.0",
"version": "5.19.6",
"type": "module",
"types": "./types/index.d.ts",
"engines": {
@ -155,7 +155,7 @@
"axobject-query": "^4.1.0",
"clsx": "^2.1.1",
"esm-env": "^1.2.1",
"esrap": "^1.3.2",
"esrap": "^1.4.3",
"is-reference": "^3.0.3",
"locate-character": "^3.0.0",
"magic-string": "^0.30.11",

@ -8,8 +8,6 @@ fs.writeFileSync(
/**
* The current version, as set in package.json.
*
* https://svelte.dev/docs/svelte-compiler#svelte-version
* @type {string}
*/
export const VERSION = '${pkg.version}';

@ -433,7 +433,7 @@ declare namespace $inspect {
* });
* </script>
*/
export function trace(name: string): void;
export function trace(name?: string): void;
// prevent intellisense from being unhelpful
/** @deprecated */

@ -11,18 +11,37 @@ import { cubicOut } from '../easing/index.js';
* @returns {AnimationConfig}
*/
export function flip(node, { from, to }, params = {}) {
var { delay = 0, duration = (d) => Math.sqrt(d) * 120, easing = cubicOut } = params;
var style = getComputedStyle(node);
var zoom = get_zoom(node); // https://drafts.csswg.org/css-viewport/#effective-zoom
// find the transform origin, expressed as a pair of values between 0 and 1
var transform = style.transform === 'none' ? '' : style.transform;
var [ox, oy] = style.transformOrigin.split(' ').map(parseFloat);
ox /= node.clientWidth;
oy /= node.clientHeight;
// calculate effect of parent transforms and zoom
var zoom = get_zoom(node); // https://drafts.csswg.org/css-viewport/#effective-zoom
var sx = node.clientWidth / to.width / zoom;
var sy = node.clientHeight / to.height / zoom;
// find the starting position of the transform origin
var fx = from.left + from.width * ox;
var fy = from.top + from.height * oy;
// find the ending position of the transform origin
var tx = to.left + to.width * ox;
var ty = to.top + to.height * oy;
// find the translation at the start of the transform
var dx = (fx - tx) * sx;
var dy = (fy - ty) * sy;
// find the relative scale at the start of the transform
var dsx = from.width / to.width;
var dsy = from.height / to.height;
var dx = (from.left + dsx * ox - (to.left + ox)) / zoom;
var dy = (from.top + dsy * oy - (to.top + oy)) / zoom;
var { delay = 0, duration = (d) => Math.sqrt(d) * 120, easing = cubicOut } = params;
return {
delay,
duration: typeof duration === 'function' ? duration(Math.sqrt(dx * dx + dy * dy)) : duration,
@ -32,7 +51,8 @@ export function flip(node, { from, to }, params = {}) {
var y = u * dy;
var sx = t + u * dsx;
var sy = t + u * dsy;
return `transform: ${transform} scale(${sx}, ${sy}) translate(${x}px, ${y}px);`;
return `transform: ${transform} translate(${x}px, ${y}px) scale(${sx}, ${sy});`;
}
};
}

File diff suppressed because it is too large Load Diff

@ -3,7 +3,6 @@
import { isIdentifierStart, isIdentifierChar } from 'acorn';
import fragment from './state/fragment.js';
import { regex_whitespace } from '../patterns.js';
import full_char_code_at from './utils/full_char_code_at.js';
import * as e from '../../errors.js';
import { create_fragment } from './utils/create.js';
import read_options from './read/options.js';
@ -230,13 +229,13 @@ export class Parser {
let i = this.index;
const code = full_char_code_at(this.template, i);
const code = /** @type {number} */ (this.template.codePointAt(i));
if (!isIdentifierStart(code, true)) return null;
i += code <= 0xffff ? 1 : 2;
while (i < this.template.length) {
const code = full_char_code_at(this.template, i);
const code = /** @type {number} */ (this.template.codePointAt(i));
if (!isIdentifierChar(code, true)) break;
i += code <= 0xffff ? 1 : 2;

@ -1,15 +1,7 @@
/** @import { Location } from 'locate-character' */
/** @import { Pattern } from 'estree' */
/** @import { Parser } from '../index.js' */
// @ts-expect-error acorn type definitions are borked in the release we use
import { isIdentifierStart } from 'acorn';
import full_char_code_at from '../utils/full_char_code_at.js';
import {
is_bracket_open,
is_bracket_close,
is_bracket_pair,
get_bracket_close
} from '../utils/bracket.js';
import { is_bracket_open, is_bracket_close, get_bracket_close } from '../utils/bracket.js';
import { parse_expression_at } from '../acorn.js';
import { regex_not_newline_characters } from '../../patterns.js';
import * as e from '../../../errors.js';
@ -23,9 +15,9 @@ export default function read_pattern(parser) {
const start = parser.index;
let i = parser.index;
const code = full_char_code_at(parser.template, i);
if (isIdentifierStart(code, true)) {
const name = /** @type {string} */ (parser.read_identifier());
const name = parser.read_identifier();
if (name !== null) {
const annotation = read_type_annotation(parser);
return {
@ -41,33 +33,15 @@ export default function read_pattern(parser) {
};
}
if (!is_bracket_open(code)) {
if (!is_bracket_open(parser.template[i])) {
e.expected_pattern(i);
}
const bracket_stack = [code];
i += code <= 0xffff ? 1 : 2;
while (i < parser.template.length) {
const code = full_char_code_at(parser.template, i);
if (is_bracket_open(code)) {
bracket_stack.push(code);
} else if (is_bracket_close(code)) {
const popped = /** @type {number} */ (bracket_stack.pop());
if (!is_bracket_pair(popped, code)) {
e.expected_token(i, String.fromCharCode(/** @type {number} */ (get_bracket_close(popped))));
}
if (bracket_stack.length === 0) {
i += code <= 0xffff ? 1 : 2;
break;
}
}
i += code <= 0xffff ? 1 : 2;
}
i = match_bracket(parser, start);
parser.index = i;
const pattern_string = parser.template.slice(start, i);
try {
// the length of the `space_with_newline` has to be start - 1
// because we added a `(` in front of the pattern_string,
@ -97,6 +71,75 @@ export default function read_pattern(parser) {
}
}
/**
* @param {Parser} parser
* @param {number} start
*/
function match_bracket(parser, start) {
const bracket_stack = [];
let i = start;
while (i < parser.template.length) {
let char = parser.template[i++];
if (char === "'" || char === '"' || char === '`') {
i = match_quote(parser, i, char);
continue;
}
if (is_bracket_open(char)) {
bracket_stack.push(char);
} else if (is_bracket_close(char)) {
const popped = /** @type {string} */ (bracket_stack.pop());
const expected = /** @type {string} */ (get_bracket_close(popped));
if (char !== expected) {
e.expected_token(i - 1, expected);
}
if (bracket_stack.length === 0) {
return i;
}
}
}
e.unexpected_eof(parser.template.length);
}
/**
* @param {Parser} parser
* @param {number} start
* @param {string} quote
*/
function match_quote(parser, start, quote) {
let is_escaped = false;
let i = start;
while (i < parser.template.length) {
const char = parser.template[i++];
if (is_escaped) {
is_escaped = false;
continue;
}
if (char === quote) {
return i;
}
if (char === '\\') {
is_escaped = true;
}
if (quote === '`' && char === '$' && parser.template[i] === '{') {
i = match_bracket(parser, i);
}
}
e.unterminated_string_constant(start);
}
/**
* @param {Parser} parser
* @returns {any}

@ -8,14 +8,40 @@ import { find_matching_bracket } from '../utils/bracket.js';
/**
* @param {Parser} parser
* @param {string} [opening_token]
* @returns {Expression | undefined}
*/
export function get_loose_identifier(parser, opening_token) {
// Find the next } and treat it as the end of the expression
const end = find_matching_bracket(parser.template, parser.index, opening_token ?? '{');
if (end) {
const start = parser.index;
parser.index = end;
// We don't know what the expression is and signal this by returning an empty identifier
return {
type: 'Identifier',
start,
end,
name: ''
};
}
}
/**
* @param {Parser} parser
* @param {string} [opening_token]
* @param {boolean} [disallow_loose]
* @returns {Expression}
*/
export default function read_expression(parser, opening_token) {
export default function read_expression(parser, opening_token, disallow_loose) {
try {
const node = parse_expression_at(parser.template, parser.ts, parser.index);
let num_parens = 0;
if (node.leadingComments !== undefined && node.leadingComments.length > 0) {
parser.index = node.leadingComments.at(-1).end;
}
for (let i = parser.index; i < /** @type {number} */ (node.start); i += 1) {
if (parser.template[i] === '(') num_parens += 1;
}
@ -41,19 +67,12 @@ export default function read_expression(parser, opening_token) {
return /** @type {Expression} */ (node);
} catch (err) {
if (parser.loose) {
// Find the next } and treat it as the end of the expression
const end = find_matching_bracket(parser.template, parser.index, opening_token ?? '{');
if (end) {
const start = parser.index;
parser.index = end;
// We don't know what the expression is and signal this by returning an empty identifier
return {
type: 'Identifier',
start,
end,
name: ''
};
// If we are in an each loop we need the error to be thrown in cases like
// `as { y = z }` so we still throw and handle the error there
if (parser.loose && !disallow_loose) {
const expression = get_loose_identifier(parser, opening_token);
if (expression) {
return expression;
}
}

@ -1,13 +1,13 @@
/** @import { ArrowFunctionExpression, Expression, Identifier, Pattern } from 'estree' */
/** @import { AST } from '#compiler' */
/** @import { Parser } from '../index.js' */
import read_pattern from '../read/context.js';
import read_expression from '../read/expression.js';
import * as e from '../../../errors.js';
import { create_fragment } from '../utils/create.js';
import { walk } from 'zimmerframe';
import { parse_expression_at } from '../acorn.js';
import * as e from '../../../errors.js';
import { create_expression_metadata } from '../../nodes.js';
import { parse_expression_at } from '../acorn.js';
import read_pattern from '../read/context.js';
import read_expression, { get_loose_identifier } from '../read/expression.js';
import { create_fragment } from '../utils/create.js';
const regex_whitespace_with_closing_curly_brace = /^\s*}/;
@ -87,7 +87,7 @@ function open(parser) {
// we get a valid expression
while (!expression) {
try {
expression = read_expression(parser);
expression = read_expression(parser, undefined, true);
} catch (err) {
end = /** @type {any} */ (err).position[0] - 2;
@ -95,7 +95,15 @@ function open(parser) {
end -= 1;
}
if (end <= start) throw err;
if (end <= start) {
if (parser.loose) {
expression = get_loose_identifier(parser);
if (expression) {
break;
}
}
throw err;
}
// @ts-expect-error parser.template is meant to be readonly, this is a special case
parser.template = template.slice(0, end);
@ -698,7 +706,7 @@ function special(parser) {
expression: /** @type {AST.RenderTag['expression']} */ (expression),
metadata: {
dynamic: false,
args_with_call_expression: new Set(),
arguments: [],
path: [],
snippets: new Set()
}

@ -1,41 +1,30 @@
import full_char_code_at from './full_char_code_at.js';
const SQUARE_BRACKET_OPEN = '[';
const SQUARE_BRACKET_CLOSE = ']';
const CURLY_BRACKET_OPEN = '{';
const CURLY_BRACKET_CLOSE = '}';
const PARENTHESES_OPEN = '(';
const PARENTHESES_CLOSE = ')';
const SQUARE_BRACKET_OPEN = '['.charCodeAt(0);
const SQUARE_BRACKET_CLOSE = ']'.charCodeAt(0);
const CURLY_BRACKET_OPEN = '{'.charCodeAt(0);
const CURLY_BRACKET_CLOSE = '}'.charCodeAt(0);
const PARENTHESES_OPEN = '('.charCodeAt(0);
const PARENTHESES_CLOSE = ')'.charCodeAt(0);
/** @param {number} code */
export function is_bracket_open(code) {
return code === SQUARE_BRACKET_OPEN || code === CURLY_BRACKET_OPEN;
/** @param {string} char */
export function is_bracket_open(char) {
return char === SQUARE_BRACKET_OPEN || char === CURLY_BRACKET_OPEN;
}
/** @param {number} code */
export function is_bracket_close(code) {
return code === SQUARE_BRACKET_CLOSE || code === CURLY_BRACKET_CLOSE;
/** @param {string} char */
export function is_bracket_close(char) {
return char === SQUARE_BRACKET_CLOSE || char === CURLY_BRACKET_CLOSE;
}
/**
* @param {number} open
* @param {number} close
*/
export function is_bracket_pair(open, close) {
return (
(open === SQUARE_BRACKET_OPEN && close === SQUARE_BRACKET_CLOSE) ||
(open === CURLY_BRACKET_OPEN && close === CURLY_BRACKET_CLOSE)
);
}
/** @param {number} open */
/** @param {string} open */
export function get_bracket_close(open) {
if (open === SQUARE_BRACKET_OPEN) {
return SQUARE_BRACKET_CLOSE;
}
if (open === CURLY_BRACKET_OPEN) {
return CURLY_BRACKET_CLOSE;
}
if (open === PARENTHESES_OPEN) {
return PARENTHESES_CLOSE;
}
@ -132,8 +121,7 @@ function count_leading_backslashes(string, search_start_index) {
* @returns {number | undefined} The index of the closing bracket, or undefined if not found.
*/
export function find_matching_bracket(template, index, open) {
const open_code = full_char_code_at(open, 0);
const close_code = get_bracket_close(open_code);
const close = get_bracket_close(open);
let brackets = 1;
let i = index;
while (brackets > 0 && i < template.length) {
@ -159,10 +147,10 @@ export function find_matching_bracket(template, index, open) {
continue;
}
default: {
const code = full_char_code_at(template, i);
if (code === open_code) {
const char = template[i];
if (char === open) {
brackets++;
} else if (code === close_code) {
} else if (char === close) {
brackets--;
}
if (brackets === 0) {

@ -1,15 +0,0 @@
// Adapted from https://github.com/acornjs/acorn/blob/6584815dca7440e00de841d1dad152302fdd7ca5/src/tokenize.js
// Reproduced under MIT License https://github.com/acornjs/acorn/blob/master/LICENSE
/**
* @param {string} str
* @param {number} i
* @returns {number}
*/
export default function full_char_code_at(str, i) {
const code = str.charCodeAt(i);
if (code <= 0xd7ff || code >= 0xe000) return code;
const next = str.charCodeAt(i + 1);
return (code << 10) + next - 0x35fdc00;
}

@ -339,13 +339,18 @@ function relative_selector_might_apply_to_node(relative_selector, rule, element)
let sibling_elements; // do them lazy because it's rarely used and expensive to calculate
// If this is a :has inside a global selector, we gotta include the element itself, too,
// because the global selector might be for an element that's outside the component (e.g. :root).
// because the global selector might be for an element that's outside the component,
// e.g. :root:has(.scoped), :global(.foo):has(.scoped), or :root { &:has(.scoped) {} }
const rules = get_parent_rules(rule);
const include_self =
rules.some((r) => r.prelude.children.some((c) => c.children.some((s) => is_global(s, r)))) ||
rules[rules.length - 1].prelude.children.some((c) =>
c.children.some((r) =>
r.selectors.some((s) => s.type === 'PseudoClassSelector' && s.name === 'root')
r.selectors.some(
(s) =>
s.type === 'PseudoClassSelector' &&
(s.name === 'root' || (s.name === 'global' && s.args))
)
)
);
if (include_self) {
@ -638,19 +643,30 @@ function get_following_sibling_elements(element, include_self) {
/** @type {Array<Compiler.AST.RegularElement | Compiler.AST.SvelteElement>} */
const siblings = [];
// ...then walk them, starting from the node after the one
// containing the element in question
// ...then walk them, starting from the node containing the element in question
// skipping nodes that appears before the element
const seen = new Set();
let skip = true;
/** @param {Compiler.AST.SvelteNode} node */
function get_siblings(node) {
walk(node, null, {
RegularElement(node) {
siblings.push(node);
if (node === element) {
skip = false;
if (include_self) siblings.push(node);
} else if (!skip) {
siblings.push(node);
}
},
SvelteElement(node) {
siblings.push(node);
if (node === element) {
skip = false;
if (include_self) siblings.push(node);
} else if (!skip) {
siblings.push(node);
}
},
RenderTag(node) {
for (const snippet of node.metadata.snippets) {
@ -663,14 +679,10 @@ function get_following_sibling_elements(element, include_self) {
});
}
for (const node of nodes.slice(nodes.indexOf(start) + 1)) {
for (const node of nodes.slice(nodes.indexOf(start))) {
get_siblings(node);
}
if (include_self) {
siblings.push(element);
}
return siblings;
}

@ -243,7 +243,15 @@ export function analyze_module(ast, options) {
}
}
const analysis = { runes: true, tracing: false };
/** @type {Analysis} */
const analysis = {
module: { ast, scope, scopes },
name: options.filename,
accessors: false,
runes: true,
immutable: true,
tracing: false
};
walk(
/** @type {Node} */ (ast),
@ -256,14 +264,7 @@ export function analyze_module(ast, options) {
visitors
);
return {
module: { ast, scope, scopes },
name: options.filename,
accessors: false,
runes: true,
immutable: true,
tracing: analysis.tracing
};
return analysis;
}
/**
@ -604,7 +605,6 @@ export function analyze_component(root, source, options) {
has_props_rune: false,
component_slots: new Set(),
expression: null,
render_tag: null,
private_derived_state: [],
function_depth: scope.function_depth,
instance_scope: instance.scope,
@ -676,7 +676,6 @@ export function analyze_component(root, source, options) {
reactive_statements: analysis.reactive_statements,
component_slots: new Set(),
expression: null,
render_tag: null,
private_derived_state: [],
function_depth: scope.function_depth
};

@ -19,8 +19,6 @@ export interface AnalysisState {
component_slots: Set<string>;
/** Information about the current expression/directive/block value */
expression: ExpressionMetadata | null;
/** The current {@render ...} tag, if any */
render_tag: null | AST.RenderTag;
private_derived_state: string[];
function_depth: number;

@ -61,9 +61,6 @@ export function Attribute(node, context) {
) {
continue;
}
node.metadata.expression.has_state ||= chunk.metadata.expression.has_state;
node.metadata.expression.has_call ||= chunk.metadata.expression.has_call;
}
if (is_event_attribute(node)) {

@ -132,8 +132,19 @@ export function BindDirective(node, context) {
}
let i = /** @type {number} */ (node.expression.start);
let leading_comments_start = /**@type {any}*/ (node.expression.leadingComments?.at(0))?.start;
let leading_comments_end = /**@type {any}*/ (node.expression.leadingComments?.at(-1))?.end;
while (context.state.analysis.source[--i] !== '{') {
if (context.state.analysis.source[i] === '(') {
if (
context.state.analysis.source[i] === '(' &&
// if the parenthesis is in a leading comment we don't need to throw the error
!(
leading_comments_start &&
leading_comments_end &&
i <= leading_comments_end &&
i >= leading_comments_start
)
) {
e.bind_invalid_parens(node, node.name);
}
}

@ -79,7 +79,8 @@ export function CallExpression(node, context) {
case '$derived':
case '$derived.by':
if (
parent.type !== 'VariableDeclarator' &&
(parent.type !== 'VariableDeclarator' ||
get_parent(context.path, -3).type === 'ConstTag') &&
!(parent.type === 'PropertyDefinition' && !parent.static && !parent.computed)
) {
e.state_invalid_placement(node, rune);
@ -186,18 +187,6 @@ export function CallExpression(node, context) {
break;
}
if (context.state.render_tag) {
// Find out which of the render tag arguments contains this call expression
const arg_idx = unwrap_optional(context.state.render_tag.expression).arguments.findIndex(
(arg) => arg === node || context.path.includes(arg)
);
// -1 if this is the call expression of the render tag itself
if (arg_idx !== -1) {
context.state.render_tag.metadata.args_with_call_expression.add(arg_idx);
}
}
if (node.callee.type === 'Identifier') {
const binding = context.state.scope.get(node.callee.name);

@ -24,6 +24,7 @@ export function ConstTag(node, context) {
grand_parent?.type !== 'EachBlock' &&
grand_parent?.type !== 'AwaitBlock' &&
grand_parent?.type !== 'SnippetBlock' &&
grand_parent?.type !== 'SvelteBoundary' &&
((grand_parent?.type !== 'RegularElement' && grand_parent?.type !== 'SvelteElement') ||
!grand_parent.attributes.some((a) => a.type === 'Attribute' && a.name === 'slot')))
) {

@ -90,8 +90,11 @@ export function RegularElement(node, context) {
if (is_svg(node.name)) {
return true;
}
if (node.name === 'a') {
for (let i = context.path.length - 1; i >= 0; i--) {
if (node.name === 'a' || node.name === 'title') {
let i = context.path.length;
while (i--) {
const ancestor = context.path[i];
if (ancestor.type === 'RegularElement') {
return ancestor.metadata.svg;

@ -5,6 +5,7 @@ import * as e from '../../../errors.js';
import { validate_opening_tag } from './shared/utils.js';
import { mark_subtree_dynamic } from './shared/fragment.js';
import { is_resolved_snippet } from './shared/snippets.js';
import { create_expression_metadata } from '../../nodes.js';
/**
* @param {AST.RenderTag} node
@ -15,7 +16,8 @@ export function RenderTag(node, context) {
node.metadata.path = [...context.path];
const callee = unwrap_optional(node.expression).callee;
const expression = unwrap_optional(node.expression);
const callee = expression.callee;
const binding = callee.type === 'Identifier' ? context.state.scope.get(callee.name) : null;
@ -52,5 +54,15 @@ export function RenderTag(node, context) {
mark_subtree_dynamic(context.path);
context.next({ ...context.state, render_tag: node });
context.visit(callee);
for (const arg of expression.arguments) {
const metadata = create_expression_metadata();
node.metadata.arguments.push(metadata);
context.visit(arg, {
...context.state,
expression: metadata
});
}
}

@ -756,7 +756,8 @@ export function check_element(node, context) {
name === 'aria-activedescendant' &&
!is_dynamic_element &&
!is_interactive_element(node.name, attribute_map) &&
!attribute_map.has('tabindex')
!attribute_map.has('tabindex') &&
!has_spread
) {
w.a11y_aria_activedescendant_has_tabindex(attribute);
}
@ -810,9 +811,9 @@ export function check_element(node, context) {
const role = roles_map.get(current_role);
if (role) {
const required_role_props = Object.keys(role.requiredProps);
const has_missing_props = required_role_props.some(
(prop) => !attributes.find((a) => a.name === prop)
);
const has_missing_props =
!has_spread &&
required_role_props.some((prop) => !attributes.find((a) => a.name === prop));
if (has_missing_props) {
w.a11y_role_has_required_aria_props(
attribute,
@ -828,6 +829,7 @@ export function check_element(node, context) {
// interactive-supports-focus
if (
!has_spread &&
!has_disabled_attribute(attribute_map) &&
!is_hidden_from_screen_reader(node.name, attribute_map) &&
!is_presentation_role(current_role) &&
@ -845,6 +847,7 @@ export function check_element(node, context) {
// no-interactive-element-to-noninteractive-role
if (
!has_spread &&
is_interactive_element(node.name, attribute_map) &&
(is_non_interactive_roles(current_role) || is_presentation_role(current_role))
) {
@ -853,6 +856,7 @@ export function check_element(node, context) {
// no-noninteractive-element-to-interactive-role
if (
!has_spread &&
is_non_interactive_element(node.name, attribute_map) &&
is_interactive_roles(current_role) &&
!a11y_non_interactive_element_to_interactive_role_exceptions[node.name]?.includes(
@ -947,6 +951,7 @@ export function check_element(node, context) {
// no-noninteractive-element-interactions
if (
!has_spread &&
!has_contenteditable_attr &&
!is_hidden_from_screen_reader(node.name, attribute_map) &&
!is_presentation_role(role_static_value) &&
@ -964,6 +969,7 @@ export function check_element(node, context) {
// no-static-element-interactions
if (
!has_spread &&
(!role || role_static_value !== null) &&
!is_hidden_from_screen_reader(node.name, attribute_map) &&
!is_presentation_role(role_static_value) &&
@ -981,11 +987,11 @@ export function check_element(node, context) {
}
}
if (handlers.has('mouseover') && !handlers.has('focus')) {
if (!has_spread && handlers.has('mouseover') && !handlers.has('focus')) {
w.a11y_mouse_events_have_key_events(node, 'mouseover', 'focus');
}
if (handlers.has('mouseout') && !handlers.has('blur')) {
if (!has_spread && handlers.has('mouseout') && !handlers.has('blur')) {
w.a11y_mouse_events_have_key_events(node, 'mouseout', 'blur');
}
@ -995,7 +1001,7 @@ export function check_element(node, context) {
if (node.name === 'a' || node.name === 'button') {
const is_hidden = get_static_value(attribute_map.get('aria-hidden')) === 'true';
if (!is_hidden && !is_labelled && !has_content(node)) {
if (!has_spread && !is_hidden && !is_labelled && !has_content(node)) {
w.a11y_consider_explicit_label(node);
}
}
@ -1054,7 +1060,7 @@ export function check_element(node, context) {
if (node.name === 'img') {
const alt_attribute = get_static_text_value(attribute_map.get('alt'));
const aria_hidden = get_static_value(attribute_map.get('aria-hidden'));
if (alt_attribute && !aria_hidden) {
if (alt_attribute && !aria_hidden && !has_spread) {
if (/\b(image|picture|photo)\b/i.test(alt_attribute)) {
w.a11y_img_redundant_alt(node);
}
@ -1087,7 +1093,7 @@ export function check_element(node, context) {
);
return has;
};
if (!attribute_map.has('for') && !has_input_child(node)) {
if (!has_spread && !attribute_map.has('for') && !has_input_child(node)) {
w.a11y_label_has_associated_control(node);
}
}
@ -1095,7 +1101,7 @@ export function check_element(node, context) {
if (node.name === 'video') {
const aria_hidden_attribute = attribute_map.get('aria-hidden');
const aria_hidden_exist = aria_hidden_attribute && get_static_value(aria_hidden_attribute);
if (attribute_map.has('muted') || aria_hidden_exist === 'true') {
if (attribute_map.has('muted') || aria_hidden_exist === 'true' || has_spread) {
return;
}
let has_caption = false;
@ -1141,6 +1147,7 @@ export function check_element(node, context) {
// Check content
if (
!has_spread &&
!is_labelled &&
!has_contenteditable_binding &&
a11y_required_content.includes(node.name) &&

@ -80,40 +80,42 @@ export function validate_slot_attribute(context, attribute, is_component = false
}
if (owner) {
if (!is_text_attribute(attribute)) {
e.slot_attribute_invalid(attribute);
}
if (
owner.type === 'Component' ||
owner.type === 'SvelteComponent' ||
owner.type === 'SvelteSelf'
) {
if (owner !== parent) {
e.slot_attribute_invalid_placement(attribute);
}
if (!is_component) {
e.slot_attribute_invalid_placement(attribute);
}
} else {
if (!is_text_attribute(attribute)) {
e.slot_attribute_invalid(attribute);
}
const name = attribute.value[0].data;
const name = attribute.value[0].data;
if (context.state.component_slots.has(name)) {
e.slot_attribute_duplicate(attribute, name, owner.name);
}
context.state.component_slots.add(name);
if (context.state.component_slots.has(name)) {
e.slot_attribute_duplicate(attribute, name, owner.name);
}
if (name === 'default') {
for (const node of owner.fragment.nodes) {
if (node.type === 'Text' && regex_only_whitespaces.test(node.data)) {
continue;
}
context.state.component_slots.add(name);
if (node.type === 'RegularElement' || node.type === 'SvelteFragment') {
if (node.attributes.some((a) => a.type === 'Attribute' && a.name === 'slot')) {
if (name === 'default') {
for (const node of owner.fragment.nodes) {
if (node.type === 'Text' && regex_only_whitespaces.test(node.data)) {
continue;
}
}
e.slot_default_duplicate(node);
if (node.type === 'RegularElement' || node.type === 'SvelteFragment') {
if (node.attributes.some((a) => a.type === 'Attribute' && a.name === 'slot')) {
continue;
}
}
e.slot_default_duplicate(node);
}
}
}
}

@ -15,6 +15,7 @@ export function visit_function(node, context) {
context.next({
...context.state,
function_depth: context.state.function_depth + 1
function_depth: context.state.function_depth + 1,
expression: null
});
}

@ -169,9 +169,9 @@ export function client_component(analysis, options) {
module_level_snippets: [],
// these are set inside the `Fragment` visitor, and cannot be used until then
before_init: /** @type {any} */ (null),
init: /** @type {any} */ (null),
update: /** @type {any} */ (null),
expressions: /** @type {any} */ (null),
after_update: /** @type {any} */ (null),
template: /** @type {any} */ (null),
locations: /** @type {any} */ (null)
@ -214,6 +214,8 @@ export function client_component(analysis, options) {
/** @type {ESTree.VariableDeclaration[]} */
const legacy_reactive_declarations = [];
let needs_store_cleanup = false;
for (const [name, binding] of analysis.instance.scope.declarations) {
if (binding.kind === 'legacy_reactive') {
legacy_reactive_declarations.push(
@ -222,7 +224,10 @@ export function client_component(analysis, options) {
}
if (binding.kind === 'store_sub') {
if (store_setup.length === 0) {
store_setup.push(b.const('$$stores', b.call('$.setup_stores')));
needs_store_cleanup = true;
store_setup.push(
b.const(b.array_pattern([b.id('$$stores'), b.id('$$cleanup')]), b.call('$.setup_stores'))
);
}
// We're creating an arrow function that gets the store value which minifies better for two or more references
@ -299,28 +304,6 @@ export function client_component(analysis, options) {
(binding.kind === 'prop' || binding.kind === 'bindable_prop') && !name.startsWith('$$')
);
if (dev && analysis.runes) {
const exports = analysis.exports.map(({ name, alias }) => b.literal(alias ?? name));
/** @type {ESTree.Literal[]} */
const bindable = [];
for (const [name, binding] of properties) {
if (binding.kind === 'bindable_prop') {
bindable.push(b.literal(binding.prop_alias ?? name));
}
}
instance.body.unshift(
b.stmt(
b.call(
'$.validate_prop_bindings',
b.id('$$props'),
b.array(bindable),
b.array(exports),
b.id(`${analysis.name}`)
)
)
);
}
if (analysis.accessors) {
for (const [name, binding] of properties) {
const key = binding.prop_alias ?? name;
@ -413,14 +396,28 @@ export function client_component(analysis, options) {
analysis.reactive_statements.size > 0 ||
component_returned_object.length > 0;
// we want the cleanup function for the stores to run as the very last thing
// so that it can effectively clean up the store subscription even after the user effects runs
if (should_inject_context) {
component_block.body.unshift(b.stmt(b.call('$.push', ...push_args)));
component_block.body.push(
component_returned_object.length > 0
? b.return(b.call('$.pop', b.object(component_returned_object)))
: b.stmt(b.call('$.pop'))
);
let to_push;
if (component_returned_object.length > 0) {
let pop_call = b.call('$.pop', b.object(component_returned_object));
to_push = needs_store_cleanup ? b.var('$$pop', pop_call) : b.return(pop_call);
} else {
to_push = b.stmt(b.call('$.pop'));
}
component_block.body.push(to_push);
}
if (needs_store_cleanup) {
component_block.body.push(b.stmt(b.call('$$cleanup')));
if (component_returned_object.length > 0) {
component_block.body.push(b.return(b.id('$$pop')));
}
}
if (analysis.uses_rest_props) {

@ -46,14 +46,14 @@ export interface ComponentClientTransformState extends ClientTransformState {
readonly events: Set<string>;
readonly is_instance: boolean;
/** Stuff that happens before the render effect(s) */
readonly before_init: Statement[];
/** Stuff that happens before the render effect(s) */
readonly init: Statement[];
/** Stuff that happens inside the render effect */
readonly update: Statement[];
/** Stuff that happens after the render effect (control blocks, dynamic elements, bindings, actions, etc) */
readonly after_update: Statement[];
/** Expressions used inside the render effect */
readonly expressions: Expression[];
/** The HTML template string */
readonly template: Array<string | Expression>;
readonly locations: SourceLocation[];

@ -269,41 +269,6 @@ export function should_proxy(node, scope) {
return true;
}
/**
* @param {Pattern} node
* @param {import('zimmerframe').Context<AST.SvelteNode, ComponentClientTransformState>} context
* @returns {{ id: Pattern, declarations: null | Statement[] }}
*/
export function create_derived_block_argument(node, context) {
if (node.type === 'Identifier') {
context.state.transform[node.name] = { read: get_value };
return { id: node, declarations: null };
}
const pattern = /** @type {Pattern} */ (context.visit(node));
const identifiers = extract_identifiers(node);
const id = b.id('$$source');
const value = b.id('$$value');
const block = b.block([
b.var(pattern, b.call('$.get', id)),
b.return(b.object(identifiers.map((identifier) => b.prop('init', identifier, identifier))))
]);
const declarations = [b.var(value, create_derived(context.state, b.thunk(block)))];
for (const id of identifiers) {
context.state.transform[id.name] = { read: get_value };
declarations.push(
b.var(id, create_derived(context.state, b.thunk(b.member(b.call('$.get', value), id))))
);
}
return { id, declarations };
}
/**
* Svelte legacy mode should use safe equals in most places, runes mode shouldn't
* @param {ComponentClientTransformState} state

@ -171,6 +171,7 @@ function build_assignment(operator, left, right, context) {
// special case — ignore `bind:prop={getter, (v) => (...)}` / `bind:value={x.y}`
if (
path.at(-1) === 'BindDirective' ||
path.at(-1) === 'Component' ||
path.at(-1) === 'SvelteComponent' ||
(path.at(-1) === 'ArrowFunctionExpression' &&

@ -1,8 +1,10 @@
/** @import { BlockStatement, Expression, Pattern, Statement } from 'estree' */
/** @import { AST } from '#compiler' */
/** @import { ComponentContext } from '../types' */
/** @import { ComponentClientTransformState, ComponentContext } from '../types' */
import { extract_identifiers } from '../../../../utils/ast.js';
import * as b from '../../../../utils/builders.js';
import { create_derived_block_argument } from '../utils.js';
import { create_derived } from '../utils.js';
import { get_value } from './shared/declarations.js';
/**
* @param {AST.AwaitBlock} node
@ -65,3 +67,38 @@ export function AwaitBlock(node, context) {
)
);
}
/**
* @param {Pattern} node
* @param {import('zimmerframe').Context<AST.SvelteNode, ComponentClientTransformState>} context
* @returns {{ id: Pattern, declarations: null | Statement[] }}
*/
function create_derived_block_argument(node, context) {
if (node.type === 'Identifier') {
context.state.transform[node.name] = { read: get_value };
return { id: node, declarations: null };
}
const pattern = /** @type {Pattern} */ (context.visit(node));
const identifiers = extract_identifiers(node);
const id = b.id('$$source');
const value = b.id('$$value');
const block = b.block([
b.var(pattern, b.call('$.get', id)),
b.return(b.object(identifiers.map((identifier) => b.prop('init', identifier, identifier))))
]);
const declarations = [b.var(value, create_derived(context.state, b.thunk(block)))];
for (const id of identifiers) {
context.state.transform[id.name] = { read: get_value };
declarations.push(
b.var(id, create_derived(context.state, b.thunk(b.member(b.call('$.get', value), id))))
);
}
return { id, declarations };
}

@ -35,10 +35,6 @@ export function EachBlock(node, context) {
context.state.template.push('<!>');
}
if (each_node_meta.array_name !== null) {
context.state.init.push(b.const(each_node_meta.array_name, b.thunk(collection)));
}
let flags = 0;
if (node.metadata.keyed && node.index) {
@ -120,8 +116,21 @@ export function EachBlock(node, context) {
return [array, ...transitive_dependencies];
});
if (each_node_meta.array_name) {
indirect_dependencies.push(b.call(each_node_meta.array_name));
/** @type {Identifier | null} */
let collection_id = null;
// Check if inner scope shadows something from outer scope.
// This is necessary because we need access to the array expression of the each block
// in the inner scope if bindings are used, in order to invalidate the array.
for (const [name] of context.state.scope.declarations) {
if (context.state.scope.parent?.get(name) != null) {
collection_id = context.state.scope.root.unique('$$array');
break;
}
}
if (collection_id) {
indirect_dependencies.push(b.call(collection_id));
} else {
indirect_dependencies.push(collection);
@ -195,7 +204,7 @@ export function EachBlock(node, context) {
// TODO 6.0 this only applies in legacy mode, reassignments are
// forbidden in runes mode
return b.member(
each_node_meta.array_name ? b.call(each_node_meta.array_name) : collection,
collection_id ? b.call(collection_id) : collection,
(flags & EACH_INDEX_REACTIVE) !== 0 ? get_value(index) : index,
true
);
@ -207,7 +216,7 @@ export function EachBlock(node, context) {
uses_index = true;
const left = b.member(
each_node_meta.array_name ? b.call(each_node_meta.array_name) : collection,
collection_id ? b.call(collection_id) : collection,
(flags & EACH_INDEX_REACTIVE) !== 0 ? get_value(index) : index,
true
);
@ -283,16 +292,17 @@ export function EachBlock(node, context) {
);
}
const render_args = [b.id('$$anchor'), item];
if (uses_index || collection_id) render_args.push(index);
if (collection_id) render_args.push(collection_id);
/** @type {Expression[]} */
const args = [
context.state.node,
b.literal(flags),
each_node_meta.array_name ? each_node_meta.array_name : b.thunk(collection),
b.thunk(collection),
key_function,
b.arrow(
uses_index ? [b.id('$$anchor'), item, index] : [b.id('$$anchor'), item],
b.block(declarations.concat(block.body))
)
b.arrow(render_args, b.block(declarations.concat(block.body)))
];
if (node.fallback) {

@ -61,9 +61,9 @@ export function Fragment(node, context) {
/** @type {ComponentClientTransformState} */
const state = {
...context.state,
before_init: [],
init: [],
update: [],
expressions: [],
after_update: [],
template: [],
locations: [],
@ -124,18 +124,13 @@ export function Fragment(node, context) {
add_template(template_name, args);
body.push(b.var(id, b.call(template_name)), ...state.before_init, ...state.init);
body.push(b.var(id, b.call(template_name)));
close = b.stmt(b.call('$.append', b.id('$$anchor'), id));
} else if (is_single_child_not_needing_template) {
context.visit(trimmed[0], state);
body.push(...state.before_init, ...state.init);
} else if (trimmed.length === 1 && trimmed[0].type === 'Text') {
const id = b.id(context.state.scope.generate('text'));
body.push(
b.var(id, b.call('$.text', b.literal(trimmed[0].data))),
...state.before_init,
...state.init
);
body.push(b.var(id, b.call('$.text', b.literal(trimmed[0].data))));
close = b.stmt(b.call('$.append', b.id('$$anchor'), id));
} else if (trimmed.length > 0) {
const id = b.id(context.state.scope.generate('fragment'));
@ -153,7 +148,7 @@ export function Fragment(node, context) {
state
});
body.push(b.var(id, b.call('$.text')), ...state.before_init, ...state.init);
body.push(b.var(id, b.call('$.text')));
close = b.stmt(b.call('$.append', b.id('$$anchor'), id));
} else {
if (is_standalone) {
@ -182,15 +177,13 @@ export function Fragment(node, context) {
close = b.stmt(b.call('$.append', b.id('$$anchor'), id));
}
body.push(...state.before_init, ...state.init);
}
} else {
body.push(...state.before_init, ...state.init);
}
body.push(...state.init);
if (state.update.length > 0) {
body.push(build_render_statement(state.update));
body.push(build_render_statement(state));
}
body.push(...state.after_update);

@ -1,4 +1,4 @@
/** @import { Expression, ExpressionStatement, Identifier, MemberExpression, ObjectExpression, Statement } from 'estree' */
/** @import { Expression, ExpressionStatement, Identifier, MemberExpression, Statement } from 'estree' */
/** @import { AST } from '#compiler' */
/** @import { SourceLocation } from '#shared' */
/** @import { ComponentClientTransformState, ComponentContext } from '../types' */
@ -16,7 +16,7 @@ import { is_event_attribute, is_text_attribute } from '../../../../utils/ast.js'
import * as b from '../../../../utils/builders.js';
import { is_custom_element_node } from '../../../nodes.js';
import { clean_nodes, determine_namespace_for_children } from '../../utils.js';
import { build_getter, create_derived } from '../utils.js';
import { build_getter } from '../utils.js';
import {
get_attribute_name,
build_attribute_value,
@ -28,8 +28,8 @@ import { process_children } from './shared/fragment.js';
import {
build_render_statement,
build_template_chunk,
build_update,
build_update_assignment
build_update_assignment,
get_expression_id
} from './shared/utils.js';
import { visit_event_attribute } from './shared/events.js';
@ -227,7 +227,7 @@ export function RegularElement(node, context) {
node_id,
attributes_id,
(node.metadata.svg || node.metadata.mathml || is_custom_element_node(node)) && b.true,
node.name.includes('-') && b.true,
is_custom_element_node(node) && b.true,
context.state
);
@ -409,7 +409,7 @@ export function RegularElement(node, context) {
b.block([
...child_state.init,
...element_state.init,
child_state.update.length > 0 ? build_render_statement(child_state.update) : b.empty,
child_state.update.length > 0 ? build_render_statement(child_state) : b.empty,
...child_state.after_update,
...element_state.after_update
])
@ -536,7 +536,10 @@ function build_element_attribute_update_assignment(
const name = get_attribute_name(element, attribute);
const is_svg = context.state.metadata.namespace === 'svg' || element.name === 'svg';
const is_mathml = context.state.metadata.namespace === 'mathml';
let { has_call, value } = build_attribute_value(attribute.value, context);
let { value, has_state } = build_attribute_value(attribute.value, context, (value, metadata) =>
metadata.has_call ? get_expression_id(state, value) : value
);
if (name === 'autofocus') {
state.init.push(b.stmt(b.call('$.autofocus', node_id, value)));
@ -557,21 +560,14 @@ function build_element_attribute_update_assignment(
value = b.call('$.clsx', value);
}
if (attribute.metadata.expression.has_state && has_call) {
// ensure we're not creating a separate template effect for this so that
// potential class directives are added to the same effect and therefore always apply
const id = b.id(state.scope.generate('class_derived'));
state.init.push(b.const(id, create_derived(state, b.thunk(value))));
value = b.call('$.get', id);
has_call = false;
}
update = b.stmt(
b.call(
is_svg ? '$.set_svg_class' : is_mathml ? '$.set_mathml_class' : '$.set_class',
node_id,
value,
attribute.metadata.needs_clsx ? b.literal(context.state.analysis.css.hash) : undefined
attribute.metadata.needs_clsx && context.state.analysis.css.hash
? b.literal(context.state.analysis.css.hash)
: undefined
)
);
} else if (name === 'value') {
@ -603,14 +599,6 @@ function build_element_attribute_update_assignment(
} else if (is_dom_property(name)) {
update = b.stmt(b.assignment('=', b.member(node_id, name), value));
} else {
if (name === 'style' && attribute.metadata.expression.has_state && has_call) {
// ensure we're not creating a separate template effect for this so that
// potential style directives are added to the same effect and therefore always apply
const id = b.id(state.scope.generate('style_derived'));
state.init.push(b.const(id, create_derived(state, b.thunk(value))));
value = b.call('$.get', id);
has_call = false;
}
const callee = name.startsWith('xlink') ? '$.set_xlink_attribute' : '$.set_attribute';
update = b.stmt(
b.call(
@ -623,12 +611,8 @@ function build_element_attribute_update_assignment(
);
}
if (attribute.metadata.expression.has_state) {
if (has_call) {
state.init.push(build_update(update));
} else {
state.update.push(update);
}
if (has_state) {
state.update.push(update);
return true;
} else {
state.init.push(update);
@ -646,16 +630,22 @@ function build_element_attribute_update_assignment(
function build_custom_element_attribute_update_assignment(node_id, attribute, context) {
const state = context.state;
const name = attribute.name; // don't lowercase, as we set the element's property, which might be case sensitive
let { has_call, value } = build_attribute_value(attribute.value, context);
let { value, has_state } = build_attribute_value(attribute.value, context);
// We assume that noone's going to redefine the semantics of the class attribute on custom elements, i.e. it's still used for CSS classes
if (name === 'class' && attribute.metadata.needs_clsx) {
if (context.state.analysis.css.hash) {
value = b.array([value, b.literal(context.state.analysis.css.hash)]);
}
value = b.call('$.clsx', value);
}
const update = b.stmt(b.call('$.set_custom_element_data', node_id, b.literal(name), value));
if (attribute.metadata.expression.has_state) {
if (has_call) {
state.init.push(build_update(update));
} else {
state.update.push(update);
}
if (has_state) {
// this is different from other updates — it doesn't get grouped,
// because set_custom_element_data may not be idempotent
state.init.push(b.stmt(b.call('$.template_effect', b.thunk(update.expression))));
return true;
} else {
state.init.push(update);
@ -675,7 +665,9 @@ function build_custom_element_attribute_update_assignment(node_id, attribute, co
*/
function build_element_special_value_attribute(element, node_id, attribute, context) {
const state = context.state;
const { value } = build_attribute_value(attribute.value, context);
const { value, has_state } = build_attribute_value(attribute.value, context, (value, metadata) =>
metadata.has_call ? get_expression_id(state, value) : value
);
const inner_assignment = b.assignment(
'=',
@ -709,7 +701,7 @@ function build_element_special_value_attribute(element, node_id, attribute, cont
state.init.push(b.stmt(b.call('$.init_select', node_id, b.thunk(value))));
}
if (attribute.metadata.expression.has_state) {
if (has_state) {
const id = state.scope.generate(`${node_id.name}_value`);
build_update_assignment(
state,

@ -10,20 +10,24 @@ import * as b from '../../../../utils/builders.js';
*/
export function RenderTag(node, context) {
context.state.template.push('<!>');
const callee = unwrap_optional(node.expression).callee;
const raw_args = unwrap_optional(node.expression).arguments;
const expression = unwrap_optional(node.expression);
const callee = expression.callee;
const raw_args = expression.arguments;
/** @type {Expression[]} */
let args = [];
for (let i = 0; i < raw_args.length; i++) {
const raw = raw_args[i];
const arg = /** @type {Expression} */ (context.visit(raw));
if (node.metadata.args_with_call_expression.has(i)) {
let thunk = b.thunk(/** @type {Expression} */ (context.visit(raw_args[i])));
const { has_call } = node.metadata.arguments[i];
if (has_call) {
const id = b.id(context.state.scope.generate('render_arg'));
context.state.init.push(b.var(id, b.call('$.derived_safe_equal', b.thunk(arg))));
context.state.init.push(b.var(id, b.call('$.derived_safe_equal', thunk)));
args.push(b.thunk(b.call('$.get', id)));
} else {
args.push(b.thunk(arg));
args.push(thunk);
}
}

@ -3,6 +3,7 @@
/** @import { ComponentContext } from '../types' */
import * as b from '../../../../utils/builders.js';
import { build_attribute_value } from './shared/element.js';
import { memoize_expression } from './shared/utils.js';
/**
* @param {AST.SlotElement} node
@ -29,13 +30,17 @@ export function SlotElement(node, context) {
if (attribute.type === 'SpreadAttribute') {
spreads.push(b.thunk(/** @type {Expression} */ (context.visit(attribute))));
} else if (attribute.type === 'Attribute') {
const { value } = build_attribute_value(attribute.value, context);
const { value, has_state } = build_attribute_value(
attribute.value,
context,
(value, metadata) => (metadata.has_call ? memoize_expression(context.state, value) : value)
);
if (attribute.name === 'name') {
name = /** @type {Literal} */ (value);
is_default = false;
} else if (attribute.name !== 'slot') {
if (attribute.metadata.expression.has_state) {
if (has_state) {
props.push(b.get(attribute.name, [b.return(value)]));
} else {
props.push(b.init(attribute.name, value));

@ -23,7 +23,7 @@ export function SvelteBoundary(node, context) {
const expression = /** @type {Expression} */ (context.visit(chunk.expression, context.state));
if (attribute.metadata.expression.has_state) {
if (chunk.metadata.expression.has_state) {
props.properties.push(b.get(attribute.name, [b.return(expression)]));
} else {
props.properties.push(b.init(attribute.name, expression));
@ -33,21 +33,34 @@ export function SvelteBoundary(node, context) {
const nodes = [];
/** @type {Statement[]} */
const snippet_statements = [];
const external_statements = [];
const snippets_visits = [];
// Capture the `failed` implicit snippet prop
for (const child of node.fragment.nodes) {
if (child.type === 'SnippetBlock' && child.expression.name === 'failed') {
// we need to delay the visit of the snippets in case they access a ConstTag that is declared
// after the snippets so that the visitor for the const tag can be updated
snippets_visits.push(() => {
/** @type {Statement[]} */
const init = [];
context.visit(child, { ...context.state, init });
props.properties.push(b.prop('init', child.expression, child.expression));
external_statements.push(...init);
});
} else if (child.type === 'ConstTag') {
/** @type {Statement[]} */
const init = [];
context.visit(child, { ...context.state, init });
props.properties.push(b.prop('init', child.expression, child.expression));
snippet_statements.push(...init);
external_statements.push(...init);
} else {
nodes.push(child);
}
}
snippets_visits.forEach((visit) => visit());
const block = /** @type {BlockStatement} */ (context.visit({ ...node.fragment, nodes }));
const boundary = b.stmt(
@ -56,6 +69,6 @@ export function SvelteBoundary(node, context) {
context.state.template.push('<!>');
context.state.init.push(
snippet_statements.length > 0 ? b.block([...snippet_statements, boundary]) : boundary
external_statements.length > 0 ? b.block([...external_statements, boundary]) : boundary
);
}

@ -1,12 +1,8 @@
/** @import { BlockStatement, Expression, ExpressionStatement, Identifier, ObjectExpression, Statement } from 'estree' */
/** @import { BlockStatement, Expression, ExpressionStatement, Statement } from 'estree' */
/** @import { AST } from '#compiler' */
/** @import { ComponentContext } from '../types' */
import { dev, is_ignored, locator } from '../../../../state.js';
import {
get_attribute_expression,
is_event_attribute,
is_text_attribute
} from '../../../../utils/ast.js';
import { dev, locator } from '../../../../state.js';
import { is_text_attribute } from '../../../../utils/ast.js';
import * as b from '../../../../utils/builders.js';
import { determine_namespace_for_children } from '../../utils.js';
import {
@ -15,7 +11,7 @@ import {
build_set_attributes,
build_style_directives
} from './shared/element.js';
import { build_render_statement, build_update } from './shared/utils.js';
import { build_render_statement } from './shared/utils.js';
/**
* @param {AST.SvelteElement} node
@ -49,9 +45,9 @@ export function SvelteElement(node, context) {
state: {
...context.state,
node: element_id,
before_init: [],
init: [],
update: [],
expressions: [],
after_update: []
}
};
@ -123,7 +119,7 @@ export function SvelteElement(node, context) {
/** @type {Statement[]} */
const inner = inner_context.state.init;
if (inner_context.state.update.length > 0) {
inner.push(build_render_statement(inner_context.state.update));
inner.push(build_render_statement(inner_context.state));
}
inner.push(...inner_context.state.after_update);
inner.push(

@ -4,8 +4,7 @@
import { dev, is_ignored } from '../../../../../state.js';
import { get_attribute_chunks, object } from '../../../../../utils/ast.js';
import * as b from '../../../../../utils/builders.js';
import { create_derived } from '../../utils.js';
import { build_bind_this, validate_binding } from '../shared/utils.js';
import { build_bind_this, memoize_expression, validate_binding } from '../shared/utils.js';
import { build_attribute_value } from '../shared/element.js';
import { build_event_handler } from './events.js';
import { determine_slot } from '../../../../../utils/slot.js';
@ -132,7 +131,13 @@ export function build_component(node, component_name, context, anchor = context.
} else if (attribute.type === 'Attribute') {
if (attribute.name.startsWith('--')) {
custom_css_props.push(
b.init(attribute.name, build_attribute_value(attribute.value, context).value)
b.init(
attribute.name,
build_attribute_value(attribute.value, context, (value, metadata) =>
// TODO put the derived in the local block
metadata.has_call ? memoize_expression(context.state, value) : value
).value
)
);
continue;
}
@ -145,52 +150,61 @@ export function build_component(node, component_name, context, anchor = context.
has_children_prop = true;
}
const { value } = build_attribute_value(attribute.value, context);
if (attribute.metadata.expression.has_state) {
let arg = value;
// When we have a non-simple computation, anything other than an Identifier or Member expression,
// then there's a good chance it needs to be memoized to avoid over-firing when read within the
// child component.
const should_wrap_in_derived = get_attribute_chunks(attribute.value).some((n) => {
return (
n.type === 'ExpressionTag' &&
n.expression.type !== 'Identifier' &&
n.expression.type !== 'MemberExpression'
);
});
const { value, has_state } = build_attribute_value(
attribute.value,
context,
(value, metadata) => {
if (!metadata.has_state) return value;
// When we have a non-simple computation, anything other than an Identifier or Member expression,
// then there's a good chance it needs to be memoized to avoid over-firing when read within the
// child component (e.g. `active={i === index}`)
const should_wrap_in_derived = get_attribute_chunks(attribute.value).some((n) => {
return (
n.type === 'ExpressionTag' &&
n.expression.type !== 'Identifier' &&
n.expression.type !== 'MemberExpression'
);
});
if (should_wrap_in_derived) {
const id = b.id(context.state.scope.generate(attribute.name));
context.state.init.push(b.var(id, create_derived(context.state, b.thunk(value))));
arg = b.call('$.get', id);
return should_wrap_in_derived ? memoize_expression(context.state, value) : value;
}
);
push_prop(b.get(attribute.name, [b.return(arg)]));
if (has_state) {
push_prop(b.get(attribute.name, [b.return(value)]));
} else {
push_prop(b.init(attribute.name, value));
}
} else if (attribute.type === 'BindDirective') {
const expression = /** @type {Expression} */ (context.visit(attribute.expression));
if (dev && attribute.name !== 'this' && attribute.expression.type !== 'SequenceExpression') {
const left = object(attribute.expression);
let binding;
if (dev && attribute.name !== 'this') {
let should_add_owner = true;
if (attribute.expression.type !== 'SequenceExpression') {
const left = object(attribute.expression);
if (left?.type === 'Identifier') {
const binding = context.state.scope.get(left.name);
if (left?.type === 'Identifier') {
binding = context.state.scope.get(left.name);
// Only run ownership addition on $state fields.
// Theoretically someone could create a `$state` while creating `$state.raw` or inside a `$derived.by`,
// but that feels so much of an edge case that it doesn't warrant a perf hit for the common case.
if (binding?.kind === 'derived' || binding?.kind === 'raw_state') {
should_add_owner = false;
}
}
}
// Only run ownership addition on $state fields.
// Theoretically someone could create a `$state` while creating `$state.raw` or inside a `$derived.by`,
// but that feels so much of an edge case that it doesn't warrant a perf hit for the common case.
if (binding?.kind !== 'derived' && binding?.kind !== 'raw_state') {
if (should_add_owner) {
binding_initializers.push(
b.stmt(
b.call(
b.id('$.add_owner_effect'),
b.thunk(expression),
expression.type === 'SequenceExpression'
? expression.expressions[0]
: b.thunk(expression),
b.id(component_name),
is_ignored(node, 'ownership_invalid_binding') && b.true
)

@ -1,12 +1,12 @@
/** @import { Expression, Identifier, ObjectExpression } from 'estree' */
/** @import { AST, Namespace } from '#compiler' */
/** @import { AST, ExpressionMetadata } from '#compiler' */
/** @import { ComponentClientTransformState, ComponentContext } from '../../types' */
import { normalize_attribute } from '../../../../../../utils.js';
import { is_ignored } from '../../../../../state.js';
import { get_attribute_expression, is_event_attribute } from '../../../../../utils/ast.js';
import { is_event_attribute } from '../../../../../utils/ast.js';
import * as b from '../../../../../utils/builders.js';
import { build_getter, create_derived } from '../../utils.js';
import { build_template_chunk, build_update } from './utils.js';
import { build_getter } from '../../utils.js';
import { build_template_chunk, get_expression_id } from './utils.js';
/**
* @param {Array<AST.Attribute | AST.SpreadAttribute>} attributes
@ -28,14 +28,18 @@ export function build_set_attributes(
is_custom_element,
state
) {
let has_state = false;
let is_dynamic = false;
/** @type {ObjectExpression['properties']} */
const values = [];
for (const attribute of attributes) {
if (attribute.type === 'Attribute') {
const { value } = build_attribute_value(attribute.value, context);
const { value, has_state } = build_attribute_value(
attribute.value,
context,
(value, metadata) => (metadata.has_call ? get_expression_id(context.state, value) : value)
);
if (
is_event_attribute(attribute) &&
@ -49,18 +53,17 @@ export function build_set_attributes(
values.push(b.init(attribute.name, value));
}
has_state ||= attribute.metadata.expression.has_state;
is_dynamic ||= has_state;
} else {
// objects could contain reactive getters -> play it safe and always assume spread attributes are reactive
has_state = true;
is_dynamic = true;
let value = /** @type {Expression} */ (context.visit(attribute));
if (attribute.metadata.expression.has_call) {
const id = b.id(state.scope.generate('spread_with_call'));
state.init.push(b.const(id, create_derived(state, b.thunk(value))));
value = b.call('$.get', id);
value = get_expression_id(context.state, value);
}
values.push(b.spread(value));
}
}
@ -68,7 +71,7 @@ export function build_set_attributes(
const call = b.call(
'$.set_attributes',
element_id,
has_state ? attributes_id : b.literal(null),
is_dynamic ? attributes_id : b.literal(null),
b.object(values),
context.state.analysis.css.hash !== '' && b.literal(context.state.analysis.css.hash),
preserve_attribute_case,
@ -76,7 +79,7 @@ export function build_set_attributes(
is_ignored(element, 'hydration_attribute_changed') && b.true
);
if (has_state) {
if (is_dynamic) {
context.state.init.push(b.let(attributes_id));
const update = b.stmt(b.assignment('=', attributes_id, call));
context.state.update.push(update);
@ -104,19 +107,14 @@ export function build_style_directives(
const state = context.state;
for (const directive of style_directives) {
const { has_state, has_call } = directive.metadata.expression;
const { has_state } = directive.metadata.expression;
let value =
directive.value === true
? build_getter({ name: directive.name, type: 'Identifier' }, context.state)
: build_attribute_value(directive.value, context).value;
if (has_call) {
const id = b.id(state.scope.generate('style_directive'));
state.init.push(b.const(id, create_derived(state, b.thunk(value))));
value = b.call('$.get', id);
}
: build_attribute_value(directive.value, context, (value, metadata) =>
metadata.has_call ? get_expression_id(context.state, value) : value
).value;
const update = b.stmt(
b.call(
@ -128,9 +126,7 @@ export function build_style_directives(
)
);
if (!is_attributes_reactive && has_call) {
state.init.push(build_update(update));
} else if (is_attributes_reactive || has_state || has_call) {
if (has_state || is_attributes_reactive) {
state.update.push(update);
} else {
state.init.push(update);
@ -158,17 +154,12 @@ export function build_class_directives(
let value = /** @type {Expression} */ (context.visit(directive.expression));
if (has_call) {
const id = b.id(state.scope.generate('class_directive'));
state.init.push(b.const(id, create_derived(state, b.thunk(value))));
value = b.call('$.get', id);
value = get_expression_id(state, value);
}
const update = b.stmt(b.call('$.toggle_class', element_id, b.literal(directive.name), value));
if (!is_attributes_reactive && has_call) {
state.init.push(build_update(update));
} else if (is_attributes_reactive || has_state || has_call) {
if (is_attributes_reactive || has_state) {
state.update.push(update);
} else {
state.init.push(update);
@ -179,28 +170,30 @@ export function build_class_directives(
/**
* @param {AST.Attribute['value']} value
* @param {ComponentContext} context
* @returns {{ value: Expression, has_state: boolean, has_call: boolean }}
* @param {(value: Expression, metadata: ExpressionMetadata) => Expression} memoize
* @returns {{ value: Expression, has_state: boolean }}
*/
export function build_attribute_value(value, context) {
export function build_attribute_value(value, context, memoize = (value) => value) {
if (value === true) {
return { has_state: false, has_call: false, value: b.literal(true) };
return { value: b.literal(true), has_state: false };
}
if (!Array.isArray(value) || value.length === 1) {
const chunk = Array.isArray(value) ? value[0] : value;
if (chunk.type === 'Text') {
return { has_state: false, has_call: false, value: b.literal(chunk.data) };
return { value: b.literal(chunk.data), has_state: false };
}
let expression = /** @type {Expression} */ (context.visit(chunk.expression));
return {
has_state: chunk.metadata.expression.has_state,
has_call: chunk.metadata.expression.has_call,
value: /** @type {Expression} */ (context.visit(chunk.expression))
value: memoize(expression, chunk.metadata.expression),
has_state: chunk.metadata.expression.has_state
};
}
return build_template_chunk(value, context.visit, context.state);
return build_template_chunk(value, context.visit, context.state, memoize);
}
/**

@ -4,7 +4,8 @@
import { cannot_be_set_statically } from '../../../../../../utils.js';
import { is_event_attribute, is_text_attribute } from '../../../../../utils/ast.js';
import * as b from '../../../../../utils/builders.js';
import { build_template_chunk, build_update } from './utils.js';
import { is_custom_element_node } from '../../../../nodes.js';
import { build_template_chunk } from './utils.js';
/**
* Processes an array of template nodes, joining sibling text/expression nodes
@ -69,7 +70,7 @@ export function process_children(nodes, initial, is_element, { visit, state }) {
state.template.push(' ');
const { has_state, has_call, value } = build_template_chunk(sequence, visit, state);
const { has_state, value } = build_template_chunk(sequence, visit, state);
// if this is a standalone `{expression}`, make sure we handle the case where
// no text node was created because the expression was empty during SSR
@ -78,9 +79,7 @@ export function process_children(nodes, initial, is_element, { visit, state }) {
const update = b.stmt(b.call('$.set_text', id, value));
if (has_call && !within_bound_contenteditable) {
state.init.push(build_update(update));
} else if (has_state && !within_bound_contenteditable) {
if (has_state && !within_bound_contenteditable) {
state.update.push(update);
} else {
state.init.push(b.stmt(b.assignment('=', b.member(id, 'nodeValue'), value)));
@ -130,7 +129,7 @@ export function process_children(nodes, initial, is_element, { visit, state }) {
function is_static_element(node, state) {
if (node.type !== 'RegularElement') return false;
if (node.fragment.metadata.dynamic) return false;
if (node.name.includes('-')) return false; // we're setting all attributes on custom elements through properties
if (is_custom_element_node(node)) return false; // we're setting all attributes on custom elements through properties
for (const attribute of node.attributes) {
if (attribute.type !== 'Attribute') {

@ -1,41 +1,100 @@
/** @import { Expression, ExpressionStatement, Identifier, MemberExpression, SequenceExpression, Statement, Super } from 'estree' */
/** @import { AST } from '#compiler' */
/** @import { AST, ExpressionMetadata } from '#compiler' */
/** @import { ComponentClientTransformState } from '../../types' */
import { walk } from 'zimmerframe';
import { object } from '../../../../../utils/ast.js';
import * as b from '../../../../../utils/builders.js';
import { sanitize_template_string } from '../../../../../utils/sanitize_template_string.js';
import { regex_is_valid_identifier } from '../../../../patterns.js';
import { create_derived } from '../../utils.js';
import is_reference from 'is-reference';
import { locator } from '../../../../../state.js';
import { create_derived } from '../../utils.js';
/**
* @param {ComponentClientTransformState} state
* @param {Expression} value
*/
export function memoize_expression(state, value) {
const id = b.id(state.scope.generate('expression'));
state.init.push(b.const(id, create_derived(state, b.thunk(value))));
return b.call('$.get', id);
}
/**
*
* @param {ComponentClientTransformState} state
* @param {Expression} value
*/
export function get_expression_id(state, value) {
for (let i = 0; i < state.expressions.length; i += 1) {
if (compare_expressions(state.expressions[i], value)) {
return b.id(`$${i}`);
}
}
return b.id(`$${state.expressions.push(value) - 1}`);
}
/**
* Returns true of two expressions have an identical AST shape
* @param {Expression} a
* @param {Expression} b
*/
function compare_expressions(a, b) {
if (a.type !== b.type) {
return false;
}
for (const key in a) {
if (key === 'type' || key === 'metadata' || key === 'loc' || key === 'start' || key === 'end') {
continue;
}
const va = /** @type {any} */ (a)[key];
const vb = /** @type {any} */ (b)[key];
if ((typeof va === 'object') !== (typeof vb === 'object')) {
return false;
}
if (typeof va !== 'object' || va === null || vb === null) {
if (va !== vb) return false;
} else if (Array.isArray(va)) {
if (va.length !== vb.length) {
return false;
}
if (va.some((v, i) => !compare_expressions(v, vb[i]))) {
return false;
}
} else if (!compare_expressions(va, vb)) {
return false;
}
}
return true;
}
/**
* @param {Array<AST.Text | AST.ExpressionTag>} values
* @param {(node: AST.SvelteNode, state: any) => any} visit
* @param {ComponentClientTransformState} state
* @returns {{ value: Expression, has_state: boolean, has_call: boolean }}
* @param {(value: Expression, metadata: ExpressionMetadata) => Expression} memoize
* @returns {{ value: Expression, has_state: boolean }}
*/
export function build_template_chunk(values, visit, state) {
export function build_template_chunk(
values,
visit,
state,
memoize = (value, metadata) => (metadata.has_call ? get_expression_id(state, value) : value)
) {
/** @type {Expression[]} */
const expressions = [];
let quasi = b.quasi('');
const quasis = [quasi];
let has_call = false;
let has_state = false;
let contains_multiple_call_expression = false;
for (const node of values) {
if (node.type === 'ExpressionTag') {
const metadata = node.metadata.expression;
contains_multiple_call_expression ||= has_call && metadata.has_call;
has_call ||= metadata.has_call;
has_state ||= metadata.has_state;
}
}
for (let i = 0; i < values.length; i++) {
const node = values[i];
@ -47,30 +106,34 @@ export function build_template_chunk(values, visit, state) {
quasi.value.cooked += node.expression.value + '';
}
} else {
if (contains_multiple_call_expression) {
const id = b.id(state.scope.generate('stringified_text'));
state.init.push(
b.const(
id,
create_derived(
state,
b.thunk(
b.logical(
'??',
/** @type {Expression} */ (visit(node.expression, state)),
b.literal('')
)
)
)
)
);
expressions.push(b.call('$.get', id));
} else if (values.length === 1) {
let value = memoize(
/** @type {Expression} */ (visit(node.expression, state)),
node.metadata.expression
);
has_state ||= node.metadata.expression.has_state;
if (values.length === 1) {
// If we have a single expression, then pass that in directly to possibly avoid doing
// extra work in the template_effect (instead we do the work in set_text).
return { value: visit(node.expression, state), has_state, has_call };
return { value, has_state };
} else {
expressions.push(b.logical('??', visit(node.expression, state), b.literal('')));
// add `?? ''` where necessary (TODO optimise more cases)
if (
value.type === 'LogicalExpression' &&
value.right.type === 'Literal' &&
(value.operator === '??' || value.operator === '||')
) {
// `foo ?? null` -=> `foo ?? ''`
// otherwise leave the expression untouched
if (value.right.value === null) {
value = { ...value, right: b.literal('') };
}
} else {
value = b.logical('??', value, b.literal(''));
}
expressions.push(value);
}
quasi = b.quasi('', i + 1 === values.length);
@ -84,26 +147,27 @@ export function build_template_chunk(values, visit, state) {
const value = b.template(quasis, expressions);
return { value, has_state, has_call };
return { value, has_state };
}
/**
* @param {Statement} statement
*/
export function build_update(statement) {
const body =
statement.type === 'ExpressionStatement' ? statement.expression : b.block([statement]);
return b.stmt(b.call('$.template_effect', b.thunk(body)));
}
/**
* @param {Statement[]} update
* @param {ComponentClientTransformState} state
*/
export function build_render_statement(update) {
return update.length === 1
? build_update(update[0])
: b.stmt(b.call('$.template_effect', b.thunk(b.block(update))));
export function build_render_statement(state) {
return b.stmt(
b.call(
'$.template_effect',
b.arrow(
state.expressions.map((_, i) => b.id(`$${i}`)),
state.update.length === 1 && state.update[0].type === 'ExpressionStatement'
? state.update[0].expression
: b.block(state.update)
),
state.expressions.length > 0 &&
b.array(state.expressions.map((expression) => b.thunk(expression))),
state.expressions.length > 0 && !state.analysis.runes && b.id('$.derived_safe_equal')
)
);
}
/**

@ -5,6 +5,7 @@
import {
regex_ends_with_whitespaces,
regex_not_whitespace,
regex_starts_with_newline,
regex_starts_with_whitespaces
} from '../patterns.js';
import * as b from '../../utils/builders.js';
@ -270,6 +271,22 @@ export function clean_nodes(
var first = trimmed[0];
// initial newline inside a `<pre>` is disregarded, if not followed by another newline
if (parent.type === 'RegularElement' && parent.name === 'pre' && first?.type === 'Text') {
const text = first.data.replace(regex_starts_with_newline, '');
if (text !== first.data) {
const tmp = text.replace(regex_starts_with_newline, '');
if (text === tmp) {
first.data = text;
first.raw = first.raw.replace(regex_starts_with_newline, '');
if (first.data === '') {
trimmed.shift();
first = trimmed[0];
}
}
}
}
// Special case: Add a comment if this is a lone script tag. This ensures that our run_scripts logic in template.js
// will always be able to call node.replaceWith() on the script tag in order to make it run. If we don't add this
// and would still call node.replaceWith() on the script tag, it would be a no-op because the script tag has no parent.

@ -23,10 +23,14 @@ export function is_element_node(node) {
/**
* @param {AST.RegularElement | AST.SvelteElement} node
* @returns {node is AST.RegularElement}
* @returns {boolean}
*/
export function is_custom_element_node(node) {
return node.type === 'RegularElement' && node.name.includes('-');
return (
node.type === 'RegularElement' &&
(node.name.includes('-') ||
node.attributes.some((attr) => attr.type === 'Attribute' && attr.name === 'is'))
);
}
/**
@ -44,7 +48,6 @@ export function create_attribute(name, start, end, value) {
name,
value,
metadata: {
expression: create_expression_metadata(),
delegated: null,
needs_clsx: false
}

@ -575,21 +575,10 @@ export function create_scopes(ast, root, allow_reactive_declarations, parent) {
}
if (node.fallback) visit(node.fallback, { scope });
// Check if inner scope shadows something from outer scope.
// This is necessary because we need access to the array expression of the each block
// in the inner scope if bindings are used, in order to invalidate the array.
let needs_array_deduplication = false;
for (const [name] of scope.declarations) {
if (state.scope.get(name) !== null) {
needs_array_deduplication = true;
}
}
node.metadata = {
expression: create_expression_metadata(),
keyed: false,
contains_group_binding: false,
array_name: needs_array_deduplication ? state.scope.root.unique('$$array') : null,
index: scope.root.unique('$$index'),
declarations: scope.declarations,
is_controlled: false

@ -166,7 +166,7 @@ export namespace AST {
/** @internal */
metadata: {
dynamic: boolean;
args_with_call_expression: Set<number>;
arguments: ExpressionMetadata[];
path: SvelteNode[];
/** The set of locally-defined snippets that this render tag could correspond to,
* used for CSS pruning purposes */
@ -414,8 +414,6 @@ export namespace AST {
expression: ExpressionMetadata;
keyed: boolean;
contains_group_binding: boolean;
/** Set if something in the array expression is shadowed within the each block */
array_name: Identifier | null;
index: Identifier;
declarations: Map<string, Binding>;
/**
@ -479,7 +477,6 @@ export namespace AST {
value: true | ExpressionTag | Array<Text | ExpressionTag>;
/** @internal */
metadata: {
expression: ExpressionMetadata;
/** May be set if this is an event attribute */
delegated: null | DelegatedEvent;
/** May be `true` if this is a `class` attribute that needs `clsx` */

@ -44,84 +44,84 @@ function w(node, code, message) {
}
export const codes = [
"a11y_accesskey",
"a11y_aria_activedescendant_has_tabindex",
"a11y_aria_attributes",
"a11y_autocomplete_valid",
"a11y_autofocus",
"a11y_click_events_have_key_events",
"a11y_consider_explicit_label",
"a11y_distracting_elements",
"a11y_figcaption_index",
"a11y_figcaption_parent",
"a11y_hidden",
"a11y_img_redundant_alt",
"a11y_incorrect_aria_attribute_type",
"a11y_incorrect_aria_attribute_type_boolean",
"a11y_incorrect_aria_attribute_type_id",
"a11y_incorrect_aria_attribute_type_idlist",
"a11y_incorrect_aria_attribute_type_integer",
"a11y_incorrect_aria_attribute_type_token",
"a11y_incorrect_aria_attribute_type_tokenlist",
"a11y_incorrect_aria_attribute_type_tristate",
"a11y_interactive_supports_focus",
"a11y_invalid_attribute",
"a11y_label_has_associated_control",
"a11y_media_has_caption",
"a11y_misplaced_role",
"a11y_misplaced_scope",
"a11y_missing_attribute",
"a11y_missing_content",
"a11y_mouse_events_have_key_events",
"a11y_no_abstract_role",
"a11y_no_interactive_element_to_noninteractive_role",
"a11y_no_noninteractive_element_interactions",
"a11y_no_noninteractive_element_to_interactive_role",
"a11y_no_noninteractive_tabindex",
"a11y_no_redundant_roles",
"a11y_no_static_element_interactions",
"a11y_positive_tabindex",
"a11y_role_has_required_aria_props",
"a11y_role_supports_aria_props",
"a11y_role_supports_aria_props_implicit",
"a11y_unknown_aria_attribute",
"a11y_unknown_role",
"legacy_code",
"unknown_code",
"options_deprecated_accessors",
"options_deprecated_immutable",
"options_missing_custom_element",
"options_removed_enable_sourcemap",
"options_removed_hydratable",
"options_removed_loop_guard_timeout",
"options_renamed_ssr_dom",
"export_let_unused",
"legacy_component_creation",
"non_reactive_update",
"perf_avoid_inline_class",
"perf_avoid_nested_class",
"reactive_declaration_invalid_placement",
"reactive_declaration_module_script_dependency",
"state_referenced_locally",
"store_rune_conflict",
"css_unused_selector",
"attribute_avoid_is",
"attribute_global_event_reference",
"attribute_illegal_colon",
"attribute_invalid_property_name",
"attribute_quoted",
"bind_invalid_each_rest",
"block_empty",
"component_name_lowercase",
"element_invalid_self_closing_tag",
"event_directive_deprecated",
"node_invalid_placement_ssr",
"script_context_deprecated",
"script_unknown_attribute",
"slot_element_deprecated",
"svelte_component_deprecated",
"svelte_element_invalid_this",
"svelte_self_deprecated"
'a11y_accesskey',
'a11y_aria_activedescendant_has_tabindex',
'a11y_aria_attributes',
'a11y_autocomplete_valid',
'a11y_autofocus',
'a11y_click_events_have_key_events',
'a11y_consider_explicit_label',
'a11y_distracting_elements',
'a11y_figcaption_index',
'a11y_figcaption_parent',
'a11y_hidden',
'a11y_img_redundant_alt',
'a11y_incorrect_aria_attribute_type',
'a11y_incorrect_aria_attribute_type_boolean',
'a11y_incorrect_aria_attribute_type_id',
'a11y_incorrect_aria_attribute_type_idlist',
'a11y_incorrect_aria_attribute_type_integer',
'a11y_incorrect_aria_attribute_type_token',
'a11y_incorrect_aria_attribute_type_tokenlist',
'a11y_incorrect_aria_attribute_type_tristate',
'a11y_interactive_supports_focus',
'a11y_invalid_attribute',
'a11y_label_has_associated_control',
'a11y_media_has_caption',
'a11y_misplaced_role',
'a11y_misplaced_scope',
'a11y_missing_attribute',
'a11y_missing_content',
'a11y_mouse_events_have_key_events',
'a11y_no_abstract_role',
'a11y_no_interactive_element_to_noninteractive_role',
'a11y_no_noninteractive_element_interactions',
'a11y_no_noninteractive_element_to_interactive_role',
'a11y_no_noninteractive_tabindex',
'a11y_no_redundant_roles',
'a11y_no_static_element_interactions',
'a11y_positive_tabindex',
'a11y_role_has_required_aria_props',
'a11y_role_supports_aria_props',
'a11y_role_supports_aria_props_implicit',
'a11y_unknown_aria_attribute',
'a11y_unknown_role',
'legacy_code',
'unknown_code',
'options_deprecated_accessors',
'options_deprecated_immutable',
'options_missing_custom_element',
'options_removed_enable_sourcemap',
'options_removed_hydratable',
'options_removed_loop_guard_timeout',
'options_renamed_ssr_dom',
'export_let_unused',
'legacy_component_creation',
'non_reactive_update',
'perf_avoid_inline_class',
'perf_avoid_nested_class',
'reactive_declaration_invalid_placement',
'reactive_declaration_module_script_dependency',
'state_referenced_locally',
'store_rune_conflict',
'css_unused_selector',
'attribute_avoid_is',
'attribute_global_event_reference',
'attribute_illegal_colon',
'attribute_invalid_property_name',
'attribute_quoted',
'bind_invalid_each_rest',
'block_empty',
'component_name_lowercase',
'element_invalid_self_closing_tag',
'event_directive_deprecated',
'node_invalid_placement_ssr',
'script_context_deprecated',
'script_unknown_attribute',
'slot_element_deprecated',
'svelte_component_deprecated',
'svelte_element_invalid_this',
'svelte_self_deprecated'
];
/**
@ -129,7 +129,7 @@ export const codes = [
* @param {null | NodeLike} node
*/
export function a11y_accesskey(node) {
w(node, "a11y_accesskey", `Avoid using accesskey\nhttps://svelte.dev/e/a11y_accesskey`);
w(node, 'a11y_accesskey', `Avoid using accesskey\nhttps://svelte.dev/e/a11y_accesskey`);
}
/**
@ -137,7 +137,7 @@ export function a11y_accesskey(node) {
* @param {null | NodeLike} node
*/
export function a11y_aria_activedescendant_has_tabindex(node) {
w(node, "a11y_aria_activedescendant_has_tabindex", `An element with an aria-activedescendant attribute should have a tabindex value\nhttps://svelte.dev/e/a11y_aria_activedescendant_has_tabindex`);
w(node, 'a11y_aria_activedescendant_has_tabindex', `An element with an aria-activedescendant attribute should have a tabindex value\nhttps://svelte.dev/e/a11y_aria_activedescendant_has_tabindex`);
}
/**
@ -146,7 +146,7 @@ export function a11y_aria_activedescendant_has_tabindex(node) {
* @param {string} name
*/
export function a11y_aria_attributes(node, name) {
w(node, "a11y_aria_attributes", `\`<${name}>\` should not have aria-* attributes\nhttps://svelte.dev/e/a11y_aria_attributes`);
w(node, 'a11y_aria_attributes', `\`<${name}>\` should not have aria-* attributes\nhttps://svelte.dev/e/a11y_aria_attributes`);
}
/**
@ -156,7 +156,7 @@ export function a11y_aria_attributes(node, name) {
* @param {string} type
*/
export function a11y_autocomplete_valid(node, value, type) {
w(node, "a11y_autocomplete_valid", `'${value}' is an invalid value for 'autocomplete' on \`<input type="${type}">\`\nhttps://svelte.dev/e/a11y_autocomplete_valid`);
w(node, 'a11y_autocomplete_valid', `'${value}' is an invalid value for 'autocomplete' on \`<input type="${type}">\`\nhttps://svelte.dev/e/a11y_autocomplete_valid`);
}
/**
@ -164,7 +164,7 @@ export function a11y_autocomplete_valid(node, value, type) {
* @param {null | NodeLike} node
*/
export function a11y_autofocus(node) {
w(node, "a11y_autofocus", `Avoid using autofocus\nhttps://svelte.dev/e/a11y_autofocus`);
w(node, 'a11y_autofocus', `Avoid using autofocus\nhttps://svelte.dev/e/a11y_autofocus`);
}
/**
@ -172,7 +172,7 @@ export function a11y_autofocus(node) {
* @param {null | NodeLike} node
*/
export function a11y_click_events_have_key_events(node) {
w(node, "a11y_click_events_have_key_events", `Visible, non-interactive elements with a click event must be accompanied by a keyboard event handler. Consider whether an interactive element such as \`<button type="button">\` or \`<a>\` might be more appropriate\nhttps://svelte.dev/e/a11y_click_events_have_key_events`);
w(node, 'a11y_click_events_have_key_events', `Visible, non-interactive elements with a click event must be accompanied by a keyboard event handler. Consider whether an interactive element such as \`<button type="button">\` or \`<a>\` might be more appropriate\nhttps://svelte.dev/e/a11y_click_events_have_key_events`);
}
/**
@ -180,7 +180,7 @@ export function a11y_click_events_have_key_events(node) {
* @param {null | NodeLike} node
*/
export function a11y_consider_explicit_label(node) {
w(node, "a11y_consider_explicit_label", `Buttons and links should either contain text or have an \`aria-label\` or \`aria-labelledby\` attribute\nhttps://svelte.dev/e/a11y_consider_explicit_label`);
w(node, 'a11y_consider_explicit_label', `Buttons and links should either contain text or have an \`aria-label\` or \`aria-labelledby\` attribute\nhttps://svelte.dev/e/a11y_consider_explicit_label`);
}
/**
@ -189,7 +189,7 @@ export function a11y_consider_explicit_label(node) {
* @param {string} name
*/
export function a11y_distracting_elements(node, name) {
w(node, "a11y_distracting_elements", `Avoid \`<${name}>\` elements\nhttps://svelte.dev/e/a11y_distracting_elements`);
w(node, 'a11y_distracting_elements', `Avoid \`<${name}>\` elements\nhttps://svelte.dev/e/a11y_distracting_elements`);
}
/**
@ -197,7 +197,7 @@ export function a11y_distracting_elements(node, name) {
* @param {null | NodeLike} node
*/
export function a11y_figcaption_index(node) {
w(node, "a11y_figcaption_index", `\`<figcaption>\` must be first or last child of \`<figure>\`\nhttps://svelte.dev/e/a11y_figcaption_index`);
w(node, 'a11y_figcaption_index', `\`<figcaption>\` must be first or last child of \`<figure>\`\nhttps://svelte.dev/e/a11y_figcaption_index`);
}
/**
@ -205,7 +205,7 @@ export function a11y_figcaption_index(node) {
* @param {null | NodeLike} node
*/
export function a11y_figcaption_parent(node) {
w(node, "a11y_figcaption_parent", `\`<figcaption>\` must be an immediate child of \`<figure>\`\nhttps://svelte.dev/e/a11y_figcaption_parent`);
w(node, 'a11y_figcaption_parent', `\`<figcaption>\` must be an immediate child of \`<figure>\`\nhttps://svelte.dev/e/a11y_figcaption_parent`);
}
/**
@ -214,7 +214,7 @@ export function a11y_figcaption_parent(node) {
* @param {string} name
*/
export function a11y_hidden(node, name) {
w(node, "a11y_hidden", `\`<${name}>\` element should not be hidden\nhttps://svelte.dev/e/a11y_hidden`);
w(node, 'a11y_hidden', `\`<${name}>\` element should not be hidden\nhttps://svelte.dev/e/a11y_hidden`);
}
/**
@ -222,7 +222,7 @@ export function a11y_hidden(node, name) {
* @param {null | NodeLike} node
*/
export function a11y_img_redundant_alt(node) {
w(node, "a11y_img_redundant_alt", `Screenreaders already announce \`<img>\` elements as an image\nhttps://svelte.dev/e/a11y_img_redundant_alt`);
w(node, 'a11y_img_redundant_alt', `Screenreaders already announce \`<img>\` elements as an image\nhttps://svelte.dev/e/a11y_img_redundant_alt`);
}
/**
@ -232,7 +232,7 @@ export function a11y_img_redundant_alt(node) {
* @param {string} type
*/
export function a11y_incorrect_aria_attribute_type(node, attribute, type) {
w(node, "a11y_incorrect_aria_attribute_type", `The value of '${attribute}' must be a ${type}\nhttps://svelte.dev/e/a11y_incorrect_aria_attribute_type`);
w(node, 'a11y_incorrect_aria_attribute_type', `The value of '${attribute}' must be a ${type}\nhttps://svelte.dev/e/a11y_incorrect_aria_attribute_type`);
}
/**
@ -241,7 +241,7 @@ export function a11y_incorrect_aria_attribute_type(node, attribute, type) {
* @param {string} attribute
*/
export function a11y_incorrect_aria_attribute_type_boolean(node, attribute) {
w(node, "a11y_incorrect_aria_attribute_type_boolean", `The value of '${attribute}' must be either 'true' or 'false'. It cannot be empty\nhttps://svelte.dev/e/a11y_incorrect_aria_attribute_type_boolean`);
w(node, 'a11y_incorrect_aria_attribute_type_boolean', `The value of '${attribute}' must be either 'true' or 'false'. It cannot be empty\nhttps://svelte.dev/e/a11y_incorrect_aria_attribute_type_boolean`);
}
/**
@ -250,7 +250,7 @@ export function a11y_incorrect_aria_attribute_type_boolean(node, attribute) {
* @param {string} attribute
*/
export function a11y_incorrect_aria_attribute_type_id(node, attribute) {
w(node, "a11y_incorrect_aria_attribute_type_id", `The value of '${attribute}' must be a string that represents a DOM element ID\nhttps://svelte.dev/e/a11y_incorrect_aria_attribute_type_id`);
w(node, 'a11y_incorrect_aria_attribute_type_id', `The value of '${attribute}' must be a string that represents a DOM element ID\nhttps://svelte.dev/e/a11y_incorrect_aria_attribute_type_id`);
}
/**
@ -259,7 +259,7 @@ export function a11y_incorrect_aria_attribute_type_id(node, attribute) {
* @param {string} attribute
*/
export function a11y_incorrect_aria_attribute_type_idlist(node, attribute) {
w(node, "a11y_incorrect_aria_attribute_type_idlist", `The value of '${attribute}' must be a space-separated list of strings that represent DOM element IDs\nhttps://svelte.dev/e/a11y_incorrect_aria_attribute_type_idlist`);
w(node, 'a11y_incorrect_aria_attribute_type_idlist', `The value of '${attribute}' must be a space-separated list of strings that represent DOM element IDs\nhttps://svelte.dev/e/a11y_incorrect_aria_attribute_type_idlist`);
}
/**
@ -268,7 +268,7 @@ export function a11y_incorrect_aria_attribute_type_idlist(node, attribute) {
* @param {string} attribute
*/
export function a11y_incorrect_aria_attribute_type_integer(node, attribute) {
w(node, "a11y_incorrect_aria_attribute_type_integer", `The value of '${attribute}' must be an integer\nhttps://svelte.dev/e/a11y_incorrect_aria_attribute_type_integer`);
w(node, 'a11y_incorrect_aria_attribute_type_integer', `The value of '${attribute}' must be an integer\nhttps://svelte.dev/e/a11y_incorrect_aria_attribute_type_integer`);
}
/**
@ -278,7 +278,7 @@ export function a11y_incorrect_aria_attribute_type_integer(node, attribute) {
* @param {string} values
*/
export function a11y_incorrect_aria_attribute_type_token(node, attribute, values) {
w(node, "a11y_incorrect_aria_attribute_type_token", `The value of '${attribute}' must be exactly one of ${values}\nhttps://svelte.dev/e/a11y_incorrect_aria_attribute_type_token`);
w(node, 'a11y_incorrect_aria_attribute_type_token', `The value of '${attribute}' must be exactly one of ${values}\nhttps://svelte.dev/e/a11y_incorrect_aria_attribute_type_token`);
}
/**
@ -288,7 +288,7 @@ export function a11y_incorrect_aria_attribute_type_token(node, attribute, values
* @param {string} values
*/
export function a11y_incorrect_aria_attribute_type_tokenlist(node, attribute, values) {
w(node, "a11y_incorrect_aria_attribute_type_tokenlist", `The value of '${attribute}' must be a space-separated list of one or more of ${values}\nhttps://svelte.dev/e/a11y_incorrect_aria_attribute_type_tokenlist`);
w(node, 'a11y_incorrect_aria_attribute_type_tokenlist', `The value of '${attribute}' must be a space-separated list of one or more of ${values}\nhttps://svelte.dev/e/a11y_incorrect_aria_attribute_type_tokenlist`);
}
/**
@ -297,7 +297,7 @@ export function a11y_incorrect_aria_attribute_type_tokenlist(node, attribute, va
* @param {string} attribute
*/
export function a11y_incorrect_aria_attribute_type_tristate(node, attribute) {
w(node, "a11y_incorrect_aria_attribute_type_tristate", `The value of '${attribute}' must be exactly one of true, false, or mixed\nhttps://svelte.dev/e/a11y_incorrect_aria_attribute_type_tristate`);
w(node, 'a11y_incorrect_aria_attribute_type_tristate', `The value of '${attribute}' must be exactly one of true, false, or mixed\nhttps://svelte.dev/e/a11y_incorrect_aria_attribute_type_tristate`);
}
/**
@ -306,7 +306,7 @@ export function a11y_incorrect_aria_attribute_type_tristate(node, attribute) {
* @param {string} role
*/
export function a11y_interactive_supports_focus(node, role) {
w(node, "a11y_interactive_supports_focus", `Elements with the '${role}' interactive role must have a tabindex value\nhttps://svelte.dev/e/a11y_interactive_supports_focus`);
w(node, 'a11y_interactive_supports_focus', `Elements with the '${role}' interactive role must have a tabindex value\nhttps://svelte.dev/e/a11y_interactive_supports_focus`);
}
/**
@ -316,7 +316,7 @@ export function a11y_interactive_supports_focus(node, role) {
* @param {string} href_attribute
*/
export function a11y_invalid_attribute(node, href_value, href_attribute) {
w(node, "a11y_invalid_attribute", `'${href_value}' is not a valid ${href_attribute} attribute\nhttps://svelte.dev/e/a11y_invalid_attribute`);
w(node, 'a11y_invalid_attribute', `'${href_value}' is not a valid ${href_attribute} attribute\nhttps://svelte.dev/e/a11y_invalid_attribute`);
}
/**
@ -324,7 +324,7 @@ export function a11y_invalid_attribute(node, href_value, href_attribute) {
* @param {null | NodeLike} node
*/
export function a11y_label_has_associated_control(node) {
w(node, "a11y_label_has_associated_control", `A form label must be associated with a control\nhttps://svelte.dev/e/a11y_label_has_associated_control`);
w(node, 'a11y_label_has_associated_control', `A form label must be associated with a control\nhttps://svelte.dev/e/a11y_label_has_associated_control`);
}
/**
@ -332,7 +332,7 @@ export function a11y_label_has_associated_control(node) {
* @param {null | NodeLike} node
*/
export function a11y_media_has_caption(node) {
w(node, "a11y_media_has_caption", `\`<video>\` elements must have a \`<track kind="captions">\`\nhttps://svelte.dev/e/a11y_media_has_caption`);
w(node, 'a11y_media_has_caption', `\`<video>\` elements must have a \`<track kind="captions">\`\nhttps://svelte.dev/e/a11y_media_has_caption`);
}
/**
@ -341,7 +341,7 @@ export function a11y_media_has_caption(node) {
* @param {string} name
*/
export function a11y_misplaced_role(node, name) {
w(node, "a11y_misplaced_role", `\`<${name}>\` should not have role attribute\nhttps://svelte.dev/e/a11y_misplaced_role`);
w(node, 'a11y_misplaced_role', `\`<${name}>\` should not have role attribute\nhttps://svelte.dev/e/a11y_misplaced_role`);
}
/**
@ -349,7 +349,7 @@ export function a11y_misplaced_role(node, name) {
* @param {null | NodeLike} node
*/
export function a11y_misplaced_scope(node) {
w(node, "a11y_misplaced_scope", `The scope attribute should only be used with \`<th>\` elements\nhttps://svelte.dev/e/a11y_misplaced_scope`);
w(node, 'a11y_misplaced_scope', `The scope attribute should only be used with \`<th>\` elements\nhttps://svelte.dev/e/a11y_misplaced_scope`);
}
/**
@ -360,7 +360,7 @@ export function a11y_misplaced_scope(node) {
* @param {string} sequence
*/
export function a11y_missing_attribute(node, name, article, sequence) {
w(node, "a11y_missing_attribute", `\`<${name}>\` element should have ${article} ${sequence} attribute\nhttps://svelte.dev/e/a11y_missing_attribute`);
w(node, 'a11y_missing_attribute', `\`<${name}>\` element should have ${article} ${sequence} attribute\nhttps://svelte.dev/e/a11y_missing_attribute`);
}
/**
@ -369,7 +369,7 @@ export function a11y_missing_attribute(node, name, article, sequence) {
* @param {string} name
*/
export function a11y_missing_content(node, name) {
w(node, "a11y_missing_content", `\`<${name}>\` element should contain text\nhttps://svelte.dev/e/a11y_missing_content`);
w(node, 'a11y_missing_content', `\`<${name}>\` element should contain text\nhttps://svelte.dev/e/a11y_missing_content`);
}
/**
@ -379,7 +379,7 @@ export function a11y_missing_content(node, name) {
* @param {string} accompanied_by
*/
export function a11y_mouse_events_have_key_events(node, event, accompanied_by) {
w(node, "a11y_mouse_events_have_key_events", `'${event}' event must be accompanied by '${accompanied_by}' event\nhttps://svelte.dev/e/a11y_mouse_events_have_key_events`);
w(node, 'a11y_mouse_events_have_key_events', `'${event}' event must be accompanied by '${accompanied_by}' event\nhttps://svelte.dev/e/a11y_mouse_events_have_key_events`);
}
/**
@ -388,7 +388,7 @@ export function a11y_mouse_events_have_key_events(node, event, accompanied_by) {
* @param {string} role
*/
export function a11y_no_abstract_role(node, role) {
w(node, "a11y_no_abstract_role", `Abstract role '${role}' is forbidden\nhttps://svelte.dev/e/a11y_no_abstract_role`);
w(node, 'a11y_no_abstract_role', `Abstract role '${role}' is forbidden\nhttps://svelte.dev/e/a11y_no_abstract_role`);
}
/**
@ -398,7 +398,7 @@ export function a11y_no_abstract_role(node, role) {
* @param {string} role
*/
export function a11y_no_interactive_element_to_noninteractive_role(node, element, role) {
w(node, "a11y_no_interactive_element_to_noninteractive_role", `\`<${element}>\` cannot have role '${role}'\nhttps://svelte.dev/e/a11y_no_interactive_element_to_noninteractive_role`);
w(node, 'a11y_no_interactive_element_to_noninteractive_role', `\`<${element}>\` cannot have role '${role}'\nhttps://svelte.dev/e/a11y_no_interactive_element_to_noninteractive_role`);
}
/**
@ -407,7 +407,7 @@ export function a11y_no_interactive_element_to_noninteractive_role(node, element
* @param {string} element
*/
export function a11y_no_noninteractive_element_interactions(node, element) {
w(node, "a11y_no_noninteractive_element_interactions", `Non-interactive element \`<${element}>\` should not be assigned mouse or keyboard event listeners\nhttps://svelte.dev/e/a11y_no_noninteractive_element_interactions`);
w(node, 'a11y_no_noninteractive_element_interactions', `Non-interactive element \`<${element}>\` should not be assigned mouse or keyboard event listeners\nhttps://svelte.dev/e/a11y_no_noninteractive_element_interactions`);
}
/**
@ -417,7 +417,7 @@ export function a11y_no_noninteractive_element_interactions(node, element) {
* @param {string} role
*/
export function a11y_no_noninteractive_element_to_interactive_role(node, element, role) {
w(node, "a11y_no_noninteractive_element_to_interactive_role", `Non-interactive element \`<${element}>\` cannot have interactive role '${role}'\nhttps://svelte.dev/e/a11y_no_noninteractive_element_to_interactive_role`);
w(node, 'a11y_no_noninteractive_element_to_interactive_role', `Non-interactive element \`<${element}>\` cannot have interactive role '${role}'\nhttps://svelte.dev/e/a11y_no_noninteractive_element_to_interactive_role`);
}
/**
@ -425,7 +425,7 @@ export function a11y_no_noninteractive_element_to_interactive_role(node, element
* @param {null | NodeLike} node
*/
export function a11y_no_noninteractive_tabindex(node) {
w(node, "a11y_no_noninteractive_tabindex", `noninteractive element cannot have nonnegative tabIndex value\nhttps://svelte.dev/e/a11y_no_noninteractive_tabindex`);
w(node, 'a11y_no_noninteractive_tabindex', `noninteractive element cannot have nonnegative tabIndex value\nhttps://svelte.dev/e/a11y_no_noninteractive_tabindex`);
}
/**
@ -434,7 +434,7 @@ export function a11y_no_noninteractive_tabindex(node) {
* @param {string} role
*/
export function a11y_no_redundant_roles(node, role) {
w(node, "a11y_no_redundant_roles", `Redundant role '${role}'\nhttps://svelte.dev/e/a11y_no_redundant_roles`);
w(node, 'a11y_no_redundant_roles', `Redundant role '${role}'\nhttps://svelte.dev/e/a11y_no_redundant_roles`);
}
/**
@ -444,7 +444,7 @@ export function a11y_no_redundant_roles(node, role) {
* @param {string} handler
*/
export function a11y_no_static_element_interactions(node, element, handler) {
w(node, "a11y_no_static_element_interactions", `\`<${element}>\` with a ${handler} handler must have an ARIA role\nhttps://svelte.dev/e/a11y_no_static_element_interactions`);
w(node, 'a11y_no_static_element_interactions', `\`<${element}>\` with a ${handler} handler must have an ARIA role\nhttps://svelte.dev/e/a11y_no_static_element_interactions`);
}
/**
@ -452,7 +452,7 @@ export function a11y_no_static_element_interactions(node, element, handler) {
* @param {null | NodeLike} node
*/
export function a11y_positive_tabindex(node) {
w(node, "a11y_positive_tabindex", `Avoid tabindex values above zero\nhttps://svelte.dev/e/a11y_positive_tabindex`);
w(node, 'a11y_positive_tabindex', `Avoid tabindex values above zero\nhttps://svelte.dev/e/a11y_positive_tabindex`);
}
/**
@ -462,7 +462,7 @@ export function a11y_positive_tabindex(node) {
* @param {string} props
*/
export function a11y_role_has_required_aria_props(node, role, props) {
w(node, "a11y_role_has_required_aria_props", `Elements with the ARIA role "${role}" must have the following attributes defined: ${props}\nhttps://svelte.dev/e/a11y_role_has_required_aria_props`);
w(node, 'a11y_role_has_required_aria_props', `Elements with the ARIA role "${role}" must have the following attributes defined: ${props}\nhttps://svelte.dev/e/a11y_role_has_required_aria_props`);
}
/**
@ -472,7 +472,7 @@ export function a11y_role_has_required_aria_props(node, role, props) {
* @param {string} role
*/
export function a11y_role_supports_aria_props(node, attribute, role) {
w(node, "a11y_role_supports_aria_props", `The attribute '${attribute}' is not supported by the role '${role}'\nhttps://svelte.dev/e/a11y_role_supports_aria_props`);
w(node, 'a11y_role_supports_aria_props', `The attribute '${attribute}' is not supported by the role '${role}'\nhttps://svelte.dev/e/a11y_role_supports_aria_props`);
}
/**
@ -483,7 +483,7 @@ export function a11y_role_supports_aria_props(node, attribute, role) {
* @param {string} name
*/
export function a11y_role_supports_aria_props_implicit(node, attribute, role, name) {
w(node, "a11y_role_supports_aria_props_implicit", `The attribute '${attribute}' is not supported by the role '${role}'. This role is implicit on the element \`<${name}>\`\nhttps://svelte.dev/e/a11y_role_supports_aria_props_implicit`);
w(node, 'a11y_role_supports_aria_props_implicit', `The attribute '${attribute}' is not supported by the role '${role}'. This role is implicit on the element \`<${name}>\`\nhttps://svelte.dev/e/a11y_role_supports_aria_props_implicit`);
}
/**
@ -493,7 +493,7 @@ export function a11y_role_supports_aria_props_implicit(node, attribute, role, na
* @param {string | undefined | null} [suggestion]
*/
export function a11y_unknown_aria_attribute(node, attribute, suggestion) {
w(node, "a11y_unknown_aria_attribute", `${suggestion ? `Unknown aria attribute 'aria-${attribute}'. Did you mean '${suggestion}'?` : `Unknown aria attribute 'aria-${attribute}'`}\nhttps://svelte.dev/e/a11y_unknown_aria_attribute`);
w(node, 'a11y_unknown_aria_attribute', `${suggestion ? `Unknown aria attribute 'aria-${attribute}'. Did you mean '${suggestion}'?` : `Unknown aria attribute 'aria-${attribute}'`}\nhttps://svelte.dev/e/a11y_unknown_aria_attribute`);
}
/**
@ -503,7 +503,7 @@ export function a11y_unknown_aria_attribute(node, attribute, suggestion) {
* @param {string | undefined | null} [suggestion]
*/
export function a11y_unknown_role(node, role, suggestion) {
w(node, "a11y_unknown_role", `${suggestion ? `Unknown role '${role}'. Did you mean '${suggestion}'?` : `Unknown role '${role}'`}\nhttps://svelte.dev/e/a11y_unknown_role`);
w(node, 'a11y_unknown_role', `${suggestion ? `Unknown role '${role}'. Did you mean '${suggestion}'?` : `Unknown role '${role}'`}\nhttps://svelte.dev/e/a11y_unknown_role`);
}
/**
@ -513,7 +513,7 @@ export function a11y_unknown_role(node, role, suggestion) {
* @param {string} suggestion
*/
export function legacy_code(node, code, suggestion) {
w(node, "legacy_code", `\`${code}\` is no longer valid — please use \`${suggestion}\` instead\nhttps://svelte.dev/e/legacy_code`);
w(node, 'legacy_code', `\`${code}\` is no longer valid — please use \`${suggestion}\` instead\nhttps://svelte.dev/e/legacy_code`);
}
/**
@ -523,7 +523,7 @@ export function legacy_code(node, code, suggestion) {
* @param {string | undefined | null} [suggestion]
*/
export function unknown_code(node, code, suggestion) {
w(node, "unknown_code", `${suggestion ? `\`${code}\` is not a recognised code (did you mean \`${suggestion}\`?)` : `\`${code}\` is not a recognised code`}\nhttps://svelte.dev/e/unknown_code`);
w(node, 'unknown_code', `${suggestion ? `\`${code}\` is not a recognised code (did you mean \`${suggestion}\`?)` : `\`${code}\` is not a recognised code`}\nhttps://svelte.dev/e/unknown_code`);
}
/**
@ -531,7 +531,7 @@ export function unknown_code(node, code, suggestion) {
* @param {null | NodeLike} node
*/
export function options_deprecated_accessors(node) {
w(node, "options_deprecated_accessors", `The \`accessors\` option has been deprecated. It will have no effect in runes mode\nhttps://svelte.dev/e/options_deprecated_accessors`);
w(node, 'options_deprecated_accessors', `The \`accessors\` option has been deprecated. It will have no effect in runes mode\nhttps://svelte.dev/e/options_deprecated_accessors`);
}
/**
@ -539,7 +539,7 @@ export function options_deprecated_accessors(node) {
* @param {null | NodeLike} node
*/
export function options_deprecated_immutable(node) {
w(node, "options_deprecated_immutable", `The \`immutable\` option has been deprecated. It will have no effect in runes mode\nhttps://svelte.dev/e/options_deprecated_immutable`);
w(node, 'options_deprecated_immutable', `The \`immutable\` option has been deprecated. It will have no effect in runes mode\nhttps://svelte.dev/e/options_deprecated_immutable`);
}
/**
@ -547,7 +547,7 @@ export function options_deprecated_immutable(node) {
* @param {null | NodeLike} node
*/
export function options_missing_custom_element(node) {
w(node, "options_missing_custom_element", `The \`customElement\` option is used when generating a custom element. Did you forget the \`customElement: true\` compile option?\nhttps://svelte.dev/e/options_missing_custom_element`);
w(node, 'options_missing_custom_element', `The \`customElement\` option is used when generating a custom element. Did you forget the \`customElement: true\` compile option?\nhttps://svelte.dev/e/options_missing_custom_element`);
}
/**
@ -555,7 +555,7 @@ export function options_missing_custom_element(node) {
* @param {null | NodeLike} node
*/
export function options_removed_enable_sourcemap(node) {
w(node, "options_removed_enable_sourcemap", `The \`enableSourcemap\` option has been removed. Source maps are always generated now, and tooling can choose to ignore them\nhttps://svelte.dev/e/options_removed_enable_sourcemap`);
w(node, 'options_removed_enable_sourcemap', `The \`enableSourcemap\` option has been removed. Source maps are always generated now, and tooling can choose to ignore them\nhttps://svelte.dev/e/options_removed_enable_sourcemap`);
}
/**
@ -563,7 +563,7 @@ export function options_removed_enable_sourcemap(node) {
* @param {null | NodeLike} node
*/
export function options_removed_hydratable(node) {
w(node, "options_removed_hydratable", `The \`hydratable\` option has been removed. Svelte components are always hydratable now\nhttps://svelte.dev/e/options_removed_hydratable`);
w(node, 'options_removed_hydratable', `The \`hydratable\` option has been removed. Svelte components are always hydratable now\nhttps://svelte.dev/e/options_removed_hydratable`);
}
/**
@ -571,7 +571,7 @@ export function options_removed_hydratable(node) {
* @param {null | NodeLike} node
*/
export function options_removed_loop_guard_timeout(node) {
w(node, "options_removed_loop_guard_timeout", `The \`loopGuardTimeout\` option has been removed\nhttps://svelte.dev/e/options_removed_loop_guard_timeout`);
w(node, 'options_removed_loop_guard_timeout', `The \`loopGuardTimeout\` option has been removed\nhttps://svelte.dev/e/options_removed_loop_guard_timeout`);
}
/**
@ -579,7 +579,7 @@ export function options_removed_loop_guard_timeout(node) {
* @param {null | NodeLike} node
*/
export function options_renamed_ssr_dom(node) {
w(node, "options_renamed_ssr_dom", `\`generate: "dom"\` and \`generate: "ssr"\` options have been renamed to "client" and "server" respectively\nhttps://svelte.dev/e/options_renamed_ssr_dom`);
w(node, 'options_renamed_ssr_dom', `\`generate: "dom"\` and \`generate: "ssr"\` options have been renamed to "client" and "server" respectively\nhttps://svelte.dev/e/options_renamed_ssr_dom`);
}
/**
@ -588,7 +588,7 @@ export function options_renamed_ssr_dom(node) {
* @param {string} name
*/
export function export_let_unused(node, name) {
w(node, "export_let_unused", `Component has unused export property '${name}'. If it is for external reference only, please consider using \`export const ${name}\`\nhttps://svelte.dev/e/export_let_unused`);
w(node, 'export_let_unused', `Component has unused export property '${name}'. If it is for external reference only, please consider using \`export const ${name}\`\nhttps://svelte.dev/e/export_let_unused`);
}
/**
@ -596,7 +596,7 @@ export function export_let_unused(node, name) {
* @param {null | NodeLike} node
*/
export function legacy_component_creation(node) {
w(node, "legacy_component_creation", `Svelte 5 components are no longer classes. Instantiate them using \`mount\` or \`hydrate\` (imported from 'svelte') instead.\nhttps://svelte.dev/e/legacy_component_creation`);
w(node, 'legacy_component_creation', `Svelte 5 components are no longer classes. Instantiate them using \`mount\` or \`hydrate\` (imported from 'svelte') instead.\nhttps://svelte.dev/e/legacy_component_creation`);
}
/**
@ -605,7 +605,7 @@ export function legacy_component_creation(node) {
* @param {string} name
*/
export function non_reactive_update(node, name) {
w(node, "non_reactive_update", `\`${name}\` is updated, but is not declared with \`$state(...)\`. Changing its value will not correctly trigger updates\nhttps://svelte.dev/e/non_reactive_update`);
w(node, 'non_reactive_update', `\`${name}\` is updated, but is not declared with \`$state(...)\`. Changing its value will not correctly trigger updates\nhttps://svelte.dev/e/non_reactive_update`);
}
/**
@ -613,7 +613,7 @@ export function non_reactive_update(node, name) {
* @param {null | NodeLike} node
*/
export function perf_avoid_inline_class(node) {
w(node, "perf_avoid_inline_class", `Avoid 'new class' — instead, declare the class at the top level scope\nhttps://svelte.dev/e/perf_avoid_inline_class`);
w(node, 'perf_avoid_inline_class', `Avoid 'new class' — instead, declare the class at the top level scope\nhttps://svelte.dev/e/perf_avoid_inline_class`);
}
/**
@ -621,7 +621,7 @@ export function perf_avoid_inline_class(node) {
* @param {null | NodeLike} node
*/
export function perf_avoid_nested_class(node) {
w(node, "perf_avoid_nested_class", `Avoid declaring classes below the top level scope\nhttps://svelte.dev/e/perf_avoid_nested_class`);
w(node, 'perf_avoid_nested_class', `Avoid declaring classes below the top level scope\nhttps://svelte.dev/e/perf_avoid_nested_class`);
}
/**
@ -629,7 +629,7 @@ export function perf_avoid_nested_class(node) {
* @param {null | NodeLike} node
*/
export function reactive_declaration_invalid_placement(node) {
w(node, "reactive_declaration_invalid_placement", `Reactive declarations only exist at the top level of the instance script\nhttps://svelte.dev/e/reactive_declaration_invalid_placement`);
w(node, 'reactive_declaration_invalid_placement', `Reactive declarations only exist at the top level of the instance script\nhttps://svelte.dev/e/reactive_declaration_invalid_placement`);
}
/**
@ -637,7 +637,7 @@ export function reactive_declaration_invalid_placement(node) {
* @param {null | NodeLike} node
*/
export function reactive_declaration_module_script_dependency(node) {
w(node, "reactive_declaration_module_script_dependency", `Reassignments of module-level declarations will not cause reactive statements to update\nhttps://svelte.dev/e/reactive_declaration_module_script_dependency`);
w(node, 'reactive_declaration_module_script_dependency', `Reassignments of module-level declarations will not cause reactive statements to update\nhttps://svelte.dev/e/reactive_declaration_module_script_dependency`);
}
/**
@ -645,7 +645,7 @@ export function reactive_declaration_module_script_dependency(node) {
* @param {null | NodeLike} node
*/
export function state_referenced_locally(node) {
w(node, "state_referenced_locally", `State referenced in its own scope will never update. Did you mean to reference it inside a closure?\nhttps://svelte.dev/e/state_referenced_locally`);
w(node, 'state_referenced_locally', `State referenced in its own scope will never update. Did you mean to reference it inside a closure?\nhttps://svelte.dev/e/state_referenced_locally`);
}
/**
@ -654,7 +654,7 @@ export function state_referenced_locally(node) {
* @param {string} name
*/
export function store_rune_conflict(node, name) {
w(node, "store_rune_conflict", `It looks like you're using the \`$${name}\` rune, but there is a local binding called \`${name}\`. Referencing a local variable with a \`$\` prefix will create a store subscription. Please rename \`${name}\` to avoid the ambiguity\nhttps://svelte.dev/e/store_rune_conflict`);
w(node, 'store_rune_conflict', `It looks like you're using the \`$${name}\` rune, but there is a local binding called \`${name}\`. Referencing a local variable with a \`$\` prefix will create a store subscription. Please rename \`${name}\` to avoid the ambiguity\nhttps://svelte.dev/e/store_rune_conflict`);
}
/**
@ -663,7 +663,7 @@ export function store_rune_conflict(node, name) {
* @param {string} name
*/
export function css_unused_selector(node, name) {
w(node, "css_unused_selector", `Unused CSS selector "${name}"\nhttps://svelte.dev/e/css_unused_selector`);
w(node, 'css_unused_selector', `Unused CSS selector "${name}"\nhttps://svelte.dev/e/css_unused_selector`);
}
/**
@ -671,7 +671,7 @@ export function css_unused_selector(node, name) {
* @param {null | NodeLike} node
*/
export function attribute_avoid_is(node) {
w(node, "attribute_avoid_is", `The "is" attribute is not supported cross-browser and should be avoided\nhttps://svelte.dev/e/attribute_avoid_is`);
w(node, 'attribute_avoid_is', `The "is" attribute is not supported cross-browser and should be avoided\nhttps://svelte.dev/e/attribute_avoid_is`);
}
/**
@ -680,7 +680,7 @@ export function attribute_avoid_is(node) {
* @param {string} name
*/
export function attribute_global_event_reference(node, name) {
w(node, "attribute_global_event_reference", `You are referencing \`globalThis.${name}\`. Did you forget to declare a variable with that name?\nhttps://svelte.dev/e/attribute_global_event_reference`);
w(node, 'attribute_global_event_reference', `You are referencing \`globalThis.${name}\`. Did you forget to declare a variable with that name?\nhttps://svelte.dev/e/attribute_global_event_reference`);
}
/**
@ -688,7 +688,7 @@ export function attribute_global_event_reference(node, name) {
* @param {null | NodeLike} node
*/
export function attribute_illegal_colon(node) {
w(node, "attribute_illegal_colon", `Attributes should not contain ':' characters to prevent ambiguity with Svelte directives\nhttps://svelte.dev/e/attribute_illegal_colon`);
w(node, 'attribute_illegal_colon', `Attributes should not contain ':' characters to prevent ambiguity with Svelte directives\nhttps://svelte.dev/e/attribute_illegal_colon`);
}
/**
@ -698,7 +698,7 @@ export function attribute_illegal_colon(node) {
* @param {string} right
*/
export function attribute_invalid_property_name(node, wrong, right) {
w(node, "attribute_invalid_property_name", `'${wrong}' is not a valid HTML attribute. Did you mean '${right}'?\nhttps://svelte.dev/e/attribute_invalid_property_name`);
w(node, 'attribute_invalid_property_name', `'${wrong}' is not a valid HTML attribute. Did you mean '${right}'?\nhttps://svelte.dev/e/attribute_invalid_property_name`);
}
/**
@ -706,7 +706,7 @@ export function attribute_invalid_property_name(node, wrong, right) {
* @param {null | NodeLike} node
*/
export function attribute_quoted(node) {
w(node, "attribute_quoted", `Quoted attributes on components and custom elements will be stringified in a future version of Svelte. If this isn't what you want, remove the quotes\nhttps://svelte.dev/e/attribute_quoted`);
w(node, 'attribute_quoted', `Quoted attributes on components and custom elements will be stringified in a future version of Svelte. If this isn't what you want, remove the quotes\nhttps://svelte.dev/e/attribute_quoted`);
}
/**
@ -715,7 +715,7 @@ export function attribute_quoted(node) {
* @param {string} name
*/
export function bind_invalid_each_rest(node, name) {
w(node, "bind_invalid_each_rest", `The rest operator (...) will create a new object and binding '${name}' with the original object will not work\nhttps://svelte.dev/e/bind_invalid_each_rest`);
w(node, 'bind_invalid_each_rest', `The rest operator (...) will create a new object and binding '${name}' with the original object will not work\nhttps://svelte.dev/e/bind_invalid_each_rest`);
}
/**
@ -723,7 +723,7 @@ export function bind_invalid_each_rest(node, name) {
* @param {null | NodeLike} node
*/
export function block_empty(node) {
w(node, "block_empty", `Empty block\nhttps://svelte.dev/e/block_empty`);
w(node, 'block_empty', `Empty block\nhttps://svelte.dev/e/block_empty`);
}
/**
@ -732,7 +732,7 @@ export function block_empty(node) {
* @param {string} name
*/
export function component_name_lowercase(node, name) {
w(node, "component_name_lowercase", `\`<${name}>\` will be treated as an HTML element unless it begins with a capital letter\nhttps://svelte.dev/e/component_name_lowercase`);
w(node, 'component_name_lowercase', `\`<${name}>\` will be treated as an HTML element unless it begins with a capital letter\nhttps://svelte.dev/e/component_name_lowercase`);
}
/**
@ -741,7 +741,7 @@ export function component_name_lowercase(node, name) {
* @param {string} name
*/
export function element_invalid_self_closing_tag(node, name) {
w(node, "element_invalid_self_closing_tag", `Self-closing HTML tags for non-void elements are ambiguous — use \`<${name} ...></${name}>\` rather than \`<${name} ... />\`\nhttps://svelte.dev/e/element_invalid_self_closing_tag`);
w(node, 'element_invalid_self_closing_tag', `Self-closing HTML tags for non-void elements are ambiguous — use \`<${name} ...></${name}>\` rather than \`<${name} ... />\`\nhttps://svelte.dev/e/element_invalid_self_closing_tag`);
}
/**
@ -750,7 +750,7 @@ export function element_invalid_self_closing_tag(node, name) {
* @param {string} name
*/
export function event_directive_deprecated(node, name) {
w(node, "event_directive_deprecated", `Using \`on:${name}\` to listen to the ${name} event is deprecated. Use the event attribute \`on${name}\` instead\nhttps://svelte.dev/e/event_directive_deprecated`);
w(node, 'event_directive_deprecated', `Using \`on:${name}\` to listen to the ${name} event is deprecated. Use the event attribute \`on${name}\` instead\nhttps://svelte.dev/e/event_directive_deprecated`);
}
/**
@ -759,7 +759,7 @@ export function event_directive_deprecated(node, name) {
* @param {string} message
*/
export function node_invalid_placement_ssr(node, message) {
w(node, "node_invalid_placement_ssr", `${message}. When rendering this component on the server, the resulting HTML will be modified by the browser (by moving, removing, or inserting elements), likely resulting in a \`hydration_mismatch\` warning\nhttps://svelte.dev/e/node_invalid_placement_ssr`);
w(node, 'node_invalid_placement_ssr', `${message}. When rendering this component on the server, the resulting HTML will be modified by the browser (by moving, removing, or inserting elements), likely resulting in a \`hydration_mismatch\` warning\nhttps://svelte.dev/e/node_invalid_placement_ssr`);
}
/**
@ -767,7 +767,7 @@ export function node_invalid_placement_ssr(node, message) {
* @param {null | NodeLike} node
*/
export function script_context_deprecated(node) {
w(node, "script_context_deprecated", `\`context="module"\` is deprecated, use the \`module\` attribute instead\nhttps://svelte.dev/e/script_context_deprecated`);
w(node, 'script_context_deprecated', `\`context="module"\` is deprecated, use the \`module\` attribute instead\nhttps://svelte.dev/e/script_context_deprecated`);
}
/**
@ -775,7 +775,7 @@ export function script_context_deprecated(node) {
* @param {null | NodeLike} node
*/
export function script_unknown_attribute(node) {
w(node, "script_unknown_attribute", `Unrecognized attribute — should be one of \`generics\`, \`lang\` or \`module\`. If this exists for a preprocessor, ensure that the preprocessor removes it\nhttps://svelte.dev/e/script_unknown_attribute`);
w(node, 'script_unknown_attribute', `Unrecognized attribute — should be one of \`generics\`, \`lang\` or \`module\`. If this exists for a preprocessor, ensure that the preprocessor removes it\nhttps://svelte.dev/e/script_unknown_attribute`);
}
/**
@ -783,7 +783,7 @@ export function script_unknown_attribute(node) {
* @param {null | NodeLike} node
*/
export function slot_element_deprecated(node) {
w(node, "slot_element_deprecated", `Using \`<slot>\` to render parent content is deprecated. Use \`{@render ...}\` tags instead\nhttps://svelte.dev/e/slot_element_deprecated`);
w(node, 'slot_element_deprecated', `Using \`<slot>\` to render parent content is deprecated. Use \`{@render ...}\` tags instead\nhttps://svelte.dev/e/slot_element_deprecated`);
}
/**
@ -791,7 +791,7 @@ export function slot_element_deprecated(node) {
* @param {null | NodeLike} node
*/
export function svelte_component_deprecated(node) {
w(node, "svelte_component_deprecated", `\`<svelte:component>\` is deprecated in runes mode — components are dynamic by default\nhttps://svelte.dev/e/svelte_component_deprecated`);
w(node, 'svelte_component_deprecated', `\`<svelte:component>\` is deprecated in runes mode — components are dynamic by default\nhttps://svelte.dev/e/svelte_component_deprecated`);
}
/**
@ -799,7 +799,7 @@ export function svelte_component_deprecated(node) {
* @param {null | NodeLike} node
*/
export function svelte_element_invalid_this(node) {
w(node, "svelte_element_invalid_this", `\`this\` should be an \`{expression}\`. Using a string attribute value will cause an error in future versions of Svelte\nhttps://svelte.dev/e/svelte_element_invalid_this`);
w(node, 'svelte_element_invalid_this', `\`this\` should be an \`{expression}\`. Using a string attribute value will cause an error in future versions of Svelte\nhttps://svelte.dev/e/svelte_element_invalid_this`);
}
/**
@ -809,5 +809,5 @@ export function svelte_element_invalid_this(node) {
* @param {string} basename
*/
export function svelte_self_deprecated(node, name, basename) {
w(node, "svelte_self_deprecated", `\`<svelte:self>\` is deprecated — use self-imports (e.g. \`import ${name} from './${basename}'\`) instead\nhttps://svelte.dev/e/svelte_self_deprecated`);
w(node, 'svelte_self_deprecated', `\`<svelte:self>\` is deprecated — use self-imports (e.g. \`import ${name} from './${basename}'\`) instead\nhttps://svelte.dev/e/svelte_self_deprecated`);
}

@ -186,6 +186,8 @@ export function is_tag_valid_with_ancestor(child_tag, ancestors, child_loc, ance
export function is_tag_valid_with_parent(child_tag, parent_tag, child_loc, parent_loc) {
if (child_tag.includes('-') || parent_tag?.includes('-')) return null; // custom elements can be anything
if (parent_tag === 'template') return null; // no errors or warning should be thrown in immediate children of template tags
const disallowed = disallowed_children[parent_tag];
const child = child_loc ? `\`<${child_tag}>\` (${child_loc})` : `\`<${child_tag}>\``;

@ -1,12 +1,48 @@
/** @import { ComponentContext, ComponentContextLegacy } from '#client' */
/** @import { EventDispatcher } from './index.js' */
/** @import { NotFunction } from './internal/types.js' */
import { component_context, flush_sync, untrack } from './internal/client/runtime.js';
import { flush_sync, untrack } from './internal/client/runtime.js';
import { is_array } from './internal/shared/utils.js';
import { user_effect } from './internal/client/index.js';
import * as e from './internal/client/errors.js';
import { lifecycle_outside_component } from './internal/shared/errors.js';
import { legacy_mode_flag } from './internal/flags/index.js';
import { component_context } from './internal/client/context.js';
import { DEV } from 'esm-env';
if (DEV) {
/**
* @param {string} rune
*/
function throw_rune_error(rune) {
if (!(rune in globalThis)) {
// TODO if people start adjusting the "this can contain runes" config through v-p-s more, adjust this message
/** @type {any} */
let value; // let's hope noone modifies this global, but belts and braces
Object.defineProperty(globalThis, rune, {
configurable: true,
// eslint-disable-next-line getter-return
get: () => {
if (value !== undefined) {
return value;
}
e.rune_outside_svelte(rune);
},
set: (v) => {
value = v;
}
});
}
}
throw_rune_error('$state');
throw_rune_error('$effect');
throw_rune_error('$derived');
throw_rune_error('$inspect');
throw_rune_error('$props');
throw_rune_error('$bindable');
}
/**
* The `onMount` function schedules a callback to run as soon as the component has been mounted to the DOM.
@ -179,15 +215,7 @@ export function flushSync(fn) {
flush_sync(fn);
}
export { getContext, getAllContexts, hasContext, setContext } from './internal/client/context.js';
export { hydrate, mount, unmount } from './internal/client/render.js';
export {
getContext,
getAllContexts,
hasContext,
setContext,
tick,
untrack
} from './internal/client/runtime.js';
export { tick, untrack } from './internal/client/runtime.js';
export { createRawSnippet } from './internal/client/dom/blocks/snippet.js';

@ -278,7 +278,7 @@ declare const SnippetReturn: unique symbol;
* ```
* You can only call a snippet through the `{@render ...}` tag.
*
* https://svelte.dev/docs/svelte/snippet
* See the [snippet documentation](https://svelte.dev/docs/svelte/snippet) for more info.
*
* @template Parameters the parameters that the snippet expects (if any) as a tuple.
*/

@ -0,0 +1,211 @@
/** @import { ComponentContext } from '#client' */
import { DEV } from 'esm-env';
import { add_owner } from './dev/ownership.js';
import { lifecycle_outside_component } from '../shared/errors.js';
import { source } from './reactivity/sources.js';
import {
active_effect,
active_reaction,
set_active_effect,
set_active_reaction,
untrack
} from './runtime.js';
import { effect } from './reactivity/effects.js';
import { legacy_mode_flag } from '../flags/index.js';
/** @type {ComponentContext | null} */
export let component_context = null;
/** @param {ComponentContext | null} context */
export function set_component_context(context) {
component_context = context;
}
/**
* The current component function. Different from current component context:
* ```html
* <!-- App.svelte -->
* <Foo>
* <Bar /> <!-- context == Foo.svelte, function == App.svelte -->
* </Foo>
* ```
* @type {ComponentContext['function']}
*/
export let dev_current_component_function = null;
/** @param {ComponentContext['function']} fn */
export function set_dev_current_component_function(fn) {
dev_current_component_function = fn;
}
/**
* Retrieves the context that belongs to the closest parent component with the specified `key`.
* Must be called during component initialisation.
*
* @template T
* @param {any} key
* @returns {T}
*/
export function getContext(key) {
const context_map = get_or_init_context_map('getContext');
const result = /** @type {T} */ (context_map.get(key));
return result;
}
/**
* Associates an arbitrary `context` object with the current component and the specified `key`
* and returns that object. The context is then available to children of the component
* (including slotted content) with `getContext`.
*
* Like lifecycle functions, this must be called during component initialisation.
*
* @template T
* @param {any} key
* @param {T} context
* @returns {T}
*/
export function setContext(key, context) {
const context_map = get_or_init_context_map('setContext');
if (DEV) {
// When state is put into context, we treat as if it's global from now on.
// We do for performance reasons (it's for example very expensive to call
// getContext on a big object many times when part of a list component)
// and danger of false positives.
untrack(() => add_owner(context, null, true));
}
context_map.set(key, context);
return context;
}
/**
* Checks whether a given `key` has been set in the context of a parent component.
* Must be called during component initialisation.
*
* @param {any} key
* @returns {boolean}
*/
export function hasContext(key) {
const context_map = get_or_init_context_map('hasContext');
return context_map.has(key);
}
/**
* Retrieves the whole context map that belongs to the closest parent component.
* Must be called during component initialisation. Useful, for example, if you
* programmatically create a component and want to pass the existing context to it.
*
* @template {Map<any, any>} [T=Map<any, any>]
* @returns {T}
*/
export function getAllContexts() {
const context_map = get_or_init_context_map('getAllContexts');
return /** @type {T} */ (context_map);
}
/**
* @param {Record<string, unknown>} props
* @param {any} runes
* @param {Function} [fn]
* @returns {void}
*/
export function push(props, runes = false, fn) {
component_context = {
p: component_context,
c: null,
e: null,
m: false,
s: props,
x: null,
l: null
};
if (legacy_mode_flag && !runes) {
component_context.l = {
s: null,
u: null,
r1: [],
r2: source(false)
};
}
if (DEV) {
// component function
component_context.function = fn;
dev_current_component_function = fn;
}
}
/**
* @template {Record<string, any>} T
* @param {T} [component]
* @returns {T}
*/
export function pop(component) {
const context_stack_item = component_context;
if (context_stack_item !== null) {
if (component !== undefined) {
context_stack_item.x = component;
}
const component_effects = context_stack_item.e;
if (component_effects !== null) {
var previous_effect = active_effect;
var previous_reaction = active_reaction;
context_stack_item.e = null;
try {
for (var i = 0; i < component_effects.length; i++) {
var component_effect = component_effects[i];
set_active_effect(component_effect.effect);
set_active_reaction(component_effect.reaction);
effect(component_effect.fn);
}
} finally {
set_active_effect(previous_effect);
set_active_reaction(previous_reaction);
}
}
component_context = context_stack_item.p;
if (DEV) {
dev_current_component_function = context_stack_item.p?.function ?? null;
}
context_stack_item.m = true;
}
// Micro-optimization: Don't set .a above to the empty object
// so it can be garbage-collected when the return here is unused
return component || /** @type {T} */ ({});
}
/** @returns {boolean} */
export function is_runes() {
return !legacy_mode_flag || (component_context !== null && component_context.l === null);
}
/**
* @param {string} name
* @returns {Map<unknown, unknown>}
*/
function get_or_init_context_map(name) {
if (component_context === null) {
lifecycle_outside_component(name);
}
return (component_context.c ??= new Map(get_parent_context(component_context) || undefined));
}
/**
* @param {ComponentContext} component_context
* @returns {Map<unknown, unknown> | null}
*/
function get_parent_context(component_context) {
let parent = component_context.p;
while (parent !== null) {
const context_map = parent.c;
if (context_map !== null) {
return context_map;
}
parent = parent.p;
}
return null;
}

@ -1,5 +1,5 @@
import * as e from '../errors.js';
import { component_context } from '../runtime.js';
import { component_context } from '../context.js';
import { FILENAME } from '../../../constants.js';
import { get_component } from './ownership.js';

@ -3,7 +3,7 @@
import { STATE_SYMBOL_METADATA } from '../constants.js';
import { render_effect, user_pre_effect } from '../reactivity/effects.js';
import { dev_current_component_function } from '../runtime.js';
import { dev_current_component_function } from '../context.js';
import { get_prototype_of } from '../../shared/utils.js';
import * as w from '../warnings.js';
import { FILENAME } from '../../../constants.js';
@ -109,7 +109,7 @@ export function mark_module_end(component) {
/**
* @param {any} object
* @param {any} owner
* @param {any | null} owner
* @param {boolean} [global]
* @param {boolean} [skip_warning]
*/
@ -120,7 +120,7 @@ export function add_owner(object, owner, global = false, skip_warning = false) {
if (metadata && !has_owner(metadata, component)) {
let original = get_owner(metadata);
if (owner[FILENAME] !== component[FILENAME] && !skip_warning) {
if (owner && owner[FILENAME] !== component[FILENAME] && !skip_warning) {
w.ownership_invalid_binding(component[FILENAME], owner[FILENAME], original[FILENAME]);
}
}
@ -165,7 +165,7 @@ export function widen_ownership(from, to) {
/**
* @param {any} object
* @param {Function} owner
* @param {Function | null} owner If `null`, then the object is globally owned and will not be checked
* @param {Set<any>} seen
*/
function add_owner_to_object(object, owner, seen) {
@ -174,7 +174,11 @@ function add_owner_to_object(object, owner, seen) {
if (metadata) {
// this is a state proxy, add owner directly, if not globally shared
if ('owners' in metadata && metadata.owners != null) {
metadata.owners.add(owner);
if (owner) {
metadata.owners.add(owner);
} else {
metadata.owners = null;
}
}
} else if (object && typeof object === 'object') {
if (seen.has(object)) return;
@ -216,6 +220,10 @@ function has_owner(metadata, component) {
return (
metadata.owners.has(component) ||
// This helps avoid false positives when using HMR, where the component function is replaced
[...metadata.owners].some(
(owner) => /** @type {any} */ (owner)[FILENAME] === /** @type {any} */ (component)?.[FILENAME]
) ||
(metadata.parent !== null && has_owner(metadata.parent, component))
);
}

@ -15,7 +15,7 @@ export let tracing_expressions = null;
*/
function log_entry(signal, entry) {
const debug = signal.debug;
const value = signal.v;
const value = signal.trace_need_increase ? signal.trace_v : signal.v;
if (value === UNINITIALIZED) {
return;
@ -42,16 +42,15 @@ function log_entry(signal, entry) {
const type = (signal.f & DERIVED) !== 0 ? '$derived' : '$state';
const current_reaction = /** @type {Reaction} */ (active_reaction);
const status =
signal.version > current_reaction.version || current_reaction.version === 0 ? 'dirty' : 'clean';
const dirty = signal.wv > current_reaction.wv || current_reaction.wv === 0;
// eslint-disable-next-line no-console
console.groupCollapsed(
`%c${type}`,
status !== 'clean'
? 'color: CornflowerBlue; font-weight: bold'
: 'color: grey; font-weight: bold',
typeof value === 'object' && STATE_SYMBOL in value ? snapshot(value, true) : value
dirty ? 'color: CornflowerBlue; font-weight: bold' : 'color: grey; font-weight: bold',
typeof value === 'object' && value !== null && STATE_SYMBOL in value
? snapshot(value, true)
: value
);
if (type === '$derived') {
@ -119,7 +118,7 @@ export function trace(label, fn) {
console.groupEnd();
}
if (previously_tracing_expressions !== null) {
if (previously_tracing_expressions !== null && tracing_expressions !== null) {
for (const [signal, entry] of tracing_expressions.entries) {
var prev_entry = previously_tracing_expressions.get(signal);

@ -3,18 +3,16 @@ import { DEV } from 'esm-env';
import { is_promise } from '../../../shared/utils.js';
import { block, branch, pause_effect, resume_effect } from '../../reactivity/effects.js';
import { internal_set, mutable_source, source } from '../../reactivity/sources.js';
import { flush_sync, set_active_effect, set_active_reaction } from '../../runtime.js';
import { hydrate_next, hydrate_node, hydrating } from '../hydration.js';
import { queue_micro_task } from '../task.js';
import { UNINITIALIZED } from '../../../../constants.js';
import {
component_context,
flush_sync,
is_runes,
set_active_effect,
set_active_reaction,
set_component_context,
set_dev_current_component_function
} from '../../runtime.js';
import { hydrate_next, hydrate_node, hydrating } from '../hydration.js';
import { queue_micro_task } from '../task.js';
import { UNINITIALIZED } from '../../../../constants.js';
} from '../../context.js';
const PENDING = 0;
const THEN = 1;

@ -1,15 +1,14 @@
/** @import { Effect, TemplateNode, } from '#client' */
import { BOUNDARY_EFFECT, EFFECT_TRANSPARENT } from '../../constants.js';
import { component_context, set_component_context } from '../../context.js';
import { block, branch, destroy_effect, pause_effect } from '../../reactivity/effects.js';
import {
active_effect,
active_reaction,
component_context,
handle_error,
set_active_effect,
set_active_reaction,
set_component_context,
reset_is_throwing_error
} from '../../runtime.js';
import {

@ -35,8 +35,9 @@ import { source, mutable_source, internal_set } from '../../reactivity/sources.j
import { array_from, is_array } from '../../../shared/utils.js';
import { INERT } from '../../constants.js';
import { queue_micro_task } from '../task.js';
import { active_effect, active_reaction } from '../../runtime.js';
import { active_effect, active_reaction, get } from '../../runtime.js';
import { DEV } from 'esm-env';
import { derived_safe_equal } from '../../reactivity/deriveds.js';
/**
* The row of a keyed each block that is currently updating. We track this
@ -135,15 +136,17 @@ export function each(node, flags, get_collection, get_key, render_fn, fallback_f
var was_empty = false;
block(() => {
// TODO: ideally we could use derived for runes mode but because of the ability
// to use a store which can be mutated, we can't do that here as mutating a store
// will still result in the collection array being the same from the store
var each_array = derived_safe_equal(() => {
var collection = get_collection();
var array = is_array(collection)
? collection
: collection == null
? []
: array_from(collection);
return is_array(collection) ? collection : collection == null ? [] : array_from(collection);
});
block(() => {
var array = get(each_array);
var length = array.length;
if (was_empty && length === 0) {
@ -216,17 +219,7 @@ export function each(node, flags, get_collection, get_key, render_fn, fallback_f
}
if (!hydrating) {
var effect = /** @type {Effect} */ (active_reaction);
reconcile(
array,
state,
anchor,
render_fn,
flags,
(effect.f & INERT) !== 0,
get_key,
get_collection
);
reconcile(array, state, anchor, render_fn, flags, get_key, get_collection);
}
if (fallback_fn !== null) {
@ -254,7 +247,7 @@ export function each(node, flags, get_collection, get_key, render_fn, fallback_f
// that a mutation occurred and it's made the collection MAYBE_DIRTY, so reading the
// collection again can provide consistency to the reactive graph again as the deriveds
// will now be `CLEAN`.
get_collection();
get(each_array);
});
if (hydrating) {
@ -268,14 +261,13 @@ export function each(node, flags, get_collection, get_key, render_fn, fallback_f
* @param {Array<V>} array
* @param {EachState} state
* @param {Element | Comment | Text} anchor
* @param {(anchor: Node, item: MaybeSource<V>, index: number | Source<number>) => void} render_fn
* @param {(anchor: Node, item: MaybeSource<V>, index: number | Source<number>, collection: () => V[]) => void} render_fn
* @param {number} flags
* @param {boolean} is_inert
* @param {(value: V, index: number) => any} get_key
* @param {() => V[]} get_collection
* @returns {void}
*/
function reconcile(array, state, anchor, render_fn, flags, is_inert, get_key, get_collection) {
function reconcile(array, state, anchor, render_fn, flags, get_key, get_collection) {
var is_animated = (flags & EACH_IS_ANIMATED) !== 0;
var should_update = (flags & (EACH_ITEM_REACTIVE | EACH_INDEX_REACTIVE)) !== 0;
@ -417,7 +409,7 @@ function reconcile(array, state, anchor, render_fn, flags, is_inert, get_key, ge
while (current !== null && current.k !== key) {
// If the each block isn't inert and an item has an effect that is already inert,
// skip over adding it to our seen Set as the item is already being handled
if (is_inert || (current.e.f & INERT) === 0) {
if ((current.e.f & INERT) === 0) {
(seen ??= new Set()).add(current);
}
stashed.push(current);
@ -441,7 +433,7 @@ function reconcile(array, state, anchor, render_fn, flags, is_inert, get_key, ge
while (current !== null) {
// If the each block isn't inert, then inert effects are currently outroing and will be removed once the transition is finished
if (is_inert || (current.e.f & INERT) === 0) {
if ((current.e.f & INERT) === 0) {
to_destroy.push(current);
}
current = current.next;
@ -507,7 +499,7 @@ function update_item(item, value, index, type) {
* @param {V} value
* @param {unknown} key
* @param {number} index
* @param {(anchor: Node, item: V | Source<V>, index: number | Value<number>) => void} render_fn
* @param {(anchor: Node, item: V | Source<V>, index: number | Value<number>, collection: () => V[]) => void} render_fn
* @param {number} flags
* @param {() => V[]} get_collection
* @returns {EachItem}
@ -556,7 +548,7 @@ function create_item(
current_each_item = item;
try {
item.e = branch(() => render_fn(anchor, v, i), hydrating);
item.e = branch(() => render_fn(anchor, v, i, get_collection), hydrating);
item.e.prev = prev && prev.e;
item.e.next = next && next.e;

@ -7,7 +7,7 @@ import { assign_nodes } from '../template.js';
import * as w from '../../warnings.js';
import { hash, sanitize_location } from '../../../../utils.js';
import { DEV } from 'esm-env';
import { dev_current_component_function } from '../../runtime.js';
import { dev_current_component_function } from '../../context.js';
import { get_first_child, get_next_sibling } from '../operations.js';
/**

@ -2,7 +2,7 @@
import { UNINITIALIZED } from '../../../../constants.js';
import { block, branch, pause_effect } from '../../reactivity/effects.js';
import { not_equal, safe_not_equal } from '../../reactivity/equality.js';
import { is_runes } from '../../runtime.js';
import { is_runes } from '../../context.js';
import { hydrate_next, hydrate_node, hydrating } from '../hydration.js';
/**

@ -6,7 +6,7 @@ import { branch, block, destroy_effect, teardown } from '../../reactivity/effect
import {
dev_current_component_function,
set_dev_current_component_function
} from '../../runtime.js';
} from '../../context.js';
import { hydrate_next, hydrate_node, hydrating } from '../hydration.js';
import { create_fragment_from_html } from '../reconciler.js';
import { assign_nodes } from '../template.js';

@ -17,7 +17,8 @@ import {
} from '../../reactivity/effects.js';
import { set_should_intro } from '../../render.js';
import { current_each_item, set_current_each_item } from './each.js';
import { component_context, active_effect } from '../../runtime.js';
import { active_effect } from '../../runtime.js';
import { component_context } from '../../context.js';
import { DEV } from 'esm-env';
import { EFFECT_TRANSPARENT } from '../../constants.js';
import { assign_nodes } from '../template.js';

@ -68,14 +68,14 @@ export function set_value(element, value) {
// treat null and undefined the same for the initial value
value ?? undefined) ||
// @ts-expect-error
// `progress` elements always need their value set when its `0`
// `progress` elements always need their value set when it's `0`
(element.value === value && (value !== 0 || element.nodeName !== 'PROGRESS'))
) {
return;
}
// @ts-expect-error
element.value = value;
element.value = value ?? '';
}
/**
@ -221,7 +221,10 @@ export function set_custom_element_data(node, prop, value) {
// Don't compute setters for custom elements while they aren't registered yet,
// because during their upgrade/instantiation they might add more setters.
// Instead, fall back to a simple "an object, then set as property" heuristic.
setters_cache.has(node.nodeName) || customElements.get(node.tagName.toLowerCase())
setters_cache.has(node.nodeName) ||
// customElements may not be available in browser extension contexts
!customElements ||
customElements.get(node.tagName.toLowerCase())
? get_setters(node).includes(prop)
: value && typeof value === 'object'
) {

@ -5,7 +5,8 @@ import * as e from '../../../errors.js';
import { is } from '../../../proxy.js';
import { queue_micro_task } from '../../task.js';
import { hydrating } from '../../hydration.js';
import { is_runes, untrack } from '../../../runtime.js';
import { untrack } from '../../../runtime.js';
import { is_runes } from '../../../context.js';
/**
* @param {HTMLInputElement} input
@ -259,6 +260,15 @@ export function bind_files(input, get, set = get) {
set(input.files);
});
if (
// If we are hydrating and the value has since changed,
// then use the updated value from the input instead.
hydrating &&
input.files
) {
set(input.files);
}
render_effect(() => {
input.files = get();
});

@ -52,7 +52,10 @@ export function bind_current_time(media, get, set = get) {
}
});
teardown(() => cancelAnimationFrame(raf_id));
teardown(() => {
cancelAnimationFrame(raf_id);
media.removeEventListener('timeupdate', callback);
});
}
/**

@ -49,10 +49,10 @@ export function replay_events(dom) {
/**
* @param {string} event_name
* @param {EventTarget} dom
* @param {EventListener} handler
* @param {AddEventListenerOptions} options
* @param {EventListener} [handler]
* @param {AddEventListenerOptions} [options]
*/
export function create_event(event_name, dom, handler, options) {
export function create_event(event_name, dom, handler, options = {}) {
/**
* @this {EventTarget}
*/
@ -63,7 +63,7 @@ export function create_event(event_name, dom, handler, options) {
}
if (!event.cancelBubble) {
return without_reactive_context(() => {
return handler.call(this, event);
return handler?.call(this, event);
});
}
}
@ -108,8 +108,8 @@ export function on(element, type, handler, options = {}) {
/**
* @param {string} event_name
* @param {Element} dom
* @param {EventListener} handler
* @param {boolean} capture
* @param {EventListener} [handler]
* @param {boolean} [capture]
* @param {boolean} [passive]
* @returns {void}
*/

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save