Merge branch 'main' into omit-trailing-sibling-calls

omit-trailing-sibling-calls
Rich Harris 2 months ago committed by GitHub
commit 2b30affc22
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -0,0 +1,5 @@
---
'svelte': patch
---
breaking: throw error if derived creates state and then depends on it

@ -0,0 +1,5 @@
---
'svelte': patch
---
fix: align list of passive events with browser defaults

@ -0,0 +1,5 @@
---
'svelte': patch
---
fix: ensure assignments to state field inside constructor trigger effects

@ -1,5 +0,0 @@
---
'svelte': patch
---
fix: avoid extra work in mark_reactions

@ -0,0 +1,5 @@
---
'svelte': patch
---
feat: add `$state.link` rune

@ -0,0 +1,5 @@
---
'svelte': patch
---
fix: ensure $inspect works with SvelteMap and SvelteSet

@ -0,0 +1,5 @@
---
'svelte': patch
---
breaking: remove `$state.link` rune pending further design work

@ -0,0 +1,5 @@
---
'svelte': patch
---
fix: allow deleting non-existent `$$restProps` properties

@ -0,0 +1,5 @@
---
'svelte': patch
---
chore: default options.filename to "(unknown)"

@ -0,0 +1,5 @@
---
'svelte': patch
---
fix: ensure `$store` reads are properly transformed

@ -0,0 +1,5 @@
---
'svelte': patch
---
fix: properly transform destructured `$derived.by` declarations

@ -0,0 +1,5 @@
---
"svelte": patch
---
fix: ensure `{#await}` scope shadowing is computed in the correct order

@ -82,6 +82,7 @@
"clean-cats-wave",
"clean-eels-beg",
"clean-melons-wash",
"clean-shirts-yawn",
"clever-chefs-relate",
"clever-maps-travel",
"clever-rockets-burn",
@ -181,6 +182,7 @@
"famous-falcons-melt",
"famous-kiwis-thank",
"famous-knives-sneeze",
"famous-mirrors-count",
"famous-pants-pay",
"fast-donkeys-pay",
"fast-penguins-matter",
@ -197,6 +199,7 @@
"fifty-rice-wait",
"fifty-steaks-float",
"fifty-toys-invite",
"five-maps-reflect",
"five-tigers-search",
"flat-feet-visit",
"flat-ghosts-fly",
@ -231,6 +234,7 @@
"friendly-candles-relate",
"friendly-clouds-rhyme",
"friendly-lies-camp",
"friendly-rice-confess",
"funny-bugs-kiss",
"funny-cooks-clean",
"funny-dragons-double",
@ -271,11 +275,13 @@
"great-fans-unite",
"great-icons-retire",
"great-plums-pretend",
"green-baboons-sip",
"green-eggs-approve",
"green-fishes-lie",
"green-hounds-play",
"green-snails-tickle",
"green-tigers-judge",
"green-walls-clap",
"grumpy-avocados-fetch",
"grumpy-insects-sleep",
"grumpy-jars-sparkle",
@ -372,6 +378,7 @@
"light-humans-hang",
"light-penguins-invent",
"light-pens-watch",
"light-tigers-smoke",
"little-ligers-exist",
"little-pans-jog",
"little-seals-reflect",
@ -415,6 +422,7 @@
"mighty-files-hammer",
"mighty-frogs-obey",
"mighty-paws-smash",
"mighty-poets-fix",
"mighty-shoes-nail",
"modern-apricots-promise",
"modern-fishes-double",
@ -439,6 +447,7 @@
"neat-ducks-jam",
"neat-files-rescue",
"neat-jokes-beam",
"nervous-adults-sell",
"nervous-berries-boil",
"nervous-dolphins-allow",
"nervous-ducks-repeat",
@ -468,10 +477,12 @@
"old-jokes-deliver",
"old-mails-sneeze",
"old-oranges-compete",
"old-planets-bow",
"olive-apples-lick",
"olive-cobras-wonder",
"olive-forks-grin",
"olive-kangaroos-brake",
"olive-llamas-warn",
"olive-mice-fix",
"olive-moons-act",
"olive-seals-sell",
@ -515,12 +526,15 @@
"popular-games-hug",
"popular-ligers-perform",
"popular-mangos-rest",
"popular-news-happen",
"popular-roses-teach",
"popular-walls-hunt",
"pretty-ties-help",
"proud-pets-hang",
"proud-queens-sniff",
"purple-dragons-peel",
"quick-eagles-sit",
"quick-paws-wash",
"quick-pumpkins-study",
"quiet-apricots-dream",
"quiet-berries-end",
@ -529,6 +543,7 @@
"quiet-cobras-smile",
"quiet-crabs-nail",
"quiet-timers-speak",
"rare-ears-agree",
"rare-insects-tell",
"rare-mirrors-act",
"rare-pears-whisper",
@ -545,6 +560,7 @@
"rich-cobras-exist",
"rich-elephants-relax",
"rich-garlics-laugh",
"rich-ladybugs-admire",
"rich-olives-yell",
"rich-plums-thank",
"rich-sheep-burn",
@ -582,6 +598,7 @@
"shaggy-cameras-live",
"shaggy-comics-jog",
"sharp-fishes-serve",
"sharp-foxes-whisper",
"sharp-gorillas-impress",
"sharp-kids-happen",
"sharp-spies-live",
@ -595,6 +612,7 @@
"shiny-rats-heal",
"shiny-shrimps-march",
"shiny-starfishes-cross",
"shiny-wombats-argue",
"short-buses-camp",
"short-countries-rush",
"short-starfishes-beg",
@ -678,6 +696,7 @@
"spotty-rocks-destroy",
"spotty-shrimps-hug",
"spotty-spiders-compare",
"spotty-trees-provide",
"spotty-turkeys-sparkle",
"stale-books-perform",
"stale-comics-look",
@ -704,6 +723,7 @@
"swift-feet-juggle",
"swift-knives-tie",
"swift-poets-carry",
"swift-queens-begin",
"swift-rats-sing",
"swift-ravens-hunt",
"swift-seahorses-deliver",
@ -731,6 +751,7 @@
"ten-ties-repair",
"ten-trainers-juggle",
"ten-worms-reflect",
"tender-bats-switch",
"tender-lemons-judge",
"tender-rocks-walk",
"tender-suns-love",
@ -807,6 +828,7 @@
"weak-terms-destroy",
"wet-bats-exercise",
"wet-games-fly",
"wet-pears-buy",
"wet-pears-remain",
"wet-wombats-repeat",
"wicked-bikes-matter",
@ -821,6 +843,7 @@
"wild-cows-chew",
"wild-foxes-wonder",
"wild-moose-compare",
"wild-moose-destroy",
"wild-poems-design",
"wild-pumas-count",
"wise-apples-care",
@ -847,6 +870,7 @@
"yellow-taxis-double",
"yellow-trees-juggle",
"young-ads-roll",
"young-masks-refuse"
"young-masks-refuse",
"young-peaches-agree"
]
}

@ -0,0 +1,5 @@
---
'svelte': patch
---
breaking: deprecate `context="module"` in favor of `module`

@ -0,0 +1,5 @@
---
'svelte': patch
---
feat: only traverse trailing static nodes during hydration

@ -0,0 +1,5 @@
---
'svelte': patch
---
chore: update client check for smaller bundle size

@ -0,0 +1,5 @@
---
"svelte": patch
---
fix: small legibility improvement in `Snippet` type hint

@ -0,0 +1,5 @@
---
'svelte': patch
---
breaking: remove callback from `$state.link`

@ -0,0 +1,5 @@
---
'svelte': patch
---
fix: correctly hydrate empty raw blocks

@ -0,0 +1,5 @@
---
'svelte': patch
---
fix: more robust handling of var declarations

@ -0,0 +1,5 @@
---
'svelte': patch
---
feat: allow non-synchronous legacy component instantiation

@ -0,0 +1,5 @@
---
'svelte': patch
---
fix: don't skip custom elements with attributes

@ -0,0 +1,5 @@
---
'svelte': patch
---
fix: remove buggy `validate_dynamic_component` check

@ -161,16 +161,16 @@ If you'd like to react to changes to a prop, use the `$derived` or `$effect` run
For more information on reactivity, read the documentation around runes.
## <script context="module">
## <script module>
A `<script>` tag with a `context="module"` attribute runs once when the module first evaluates, rather than for each component instance. Values declared in this block are accessible from a regular `<script>` (and the component markup) but not vice versa.
A `<script>` tag with a `module` attribute runs once when the module first evaluates, rather than for each component instance. Values declared in this block are accessible from a regular `<script>` (and the component markup) but not vice versa.
You can `export` bindings from this block, and they will become exports of the compiled module.
You cannot `export default`, since the default export is the component itself.
```svelte
<script context="module">
<script module>
let totalComponents = 0;
// the export keyword allows this function to imported with e.g.

@ -112,6 +112,10 @@ Because events are just attributes, the same rules as for attributes apply:
Timing-wise, event attributes always fire after events from bindings (e.g. `oninput` always fires after an update to `bind:value`). Under the hood, some event handlers are attached directly with `addEventListener`, while others are _delegated_.
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()`.
In the very rare cases that you need to prevent these event defaults, you should use [`on`](https://svelte-5-preview.vercel.app/docs/imports#svelte-events) instead (for example inside an action).
### Event delegation
To reduce memory footprint and increase performance, Svelte uses a technique called event delegation. This means that for certain events — see the list below — a single event listener at the application root takes responsibility for running any handlers on the event's path.

@ -33,6 +33,8 @@ To run _side-effects_ when the component is mounted to the DOM, and when values
The function passed to `$effect` will run when the component mounts, and will re-run after any changes to the values it reads that were declared with `$state` or `$derived` (including those passed in with `$props`). Re-runs are batched (i.e. changing `color` and `size` in the same moment won't cause two separate runs), and happen after any DOM updates have been applied.
You can place `$effect` anywhere, not just at the top level of a component, as long as it is called during component initialization (or while a parent effect is active). It is then tied to the lifecycle of the component (or parent effect) and will therefore destroy itself when the component unmounts (or the parent effect is destroyed).
You can return a function from `$effect`, which will run immediately before the effect re-runs, and before it is destroyed ([demo](/#H4sIAAAAAAAAE42SzW6DMBCEX2Vl5RDaVCQ9JoDUY--9lUox9lKsGBvZC1GEePcaKPnpqSe86_m0M2t6ViqNnu0_e2Z4jWzP3pqGbRhdmrHwHWrCUHvbOjF2Ei-caijLTU4aCYRtDUEKK0-ccL2NDstNrbRWHoU10t8Eu-121gTVCssSBa3XEaQZ9GMrpziGj0p5OAccCgSHwmEgJZwrNNihg6MyhK7j-gii4uYb_YyGUZ5guQwzPdL7b_U4ZNSOvp9T2B3m1rB5cLx4zMkhtc7AHz7YVCVwEFzrgosTBMuNs52SKDegaPbvWnMH8AhUXaNUIY6-hHCldQhUIcyLCFlfAuHvkCKaYk8iYevGGgy2wyyJnpy9oLwG0sjdNe2yhGhJN32HsUzi2xOapNpl_bSLIYnDeeoVLZE1YI3QSpzSfo7-8J5PKbwOmdf2jC6JZyD7HxpPaMk93aHhF6utVKVCyfbkWhy-hh9Z3o_2nQIAAA==)).
```svelte
@ -122,6 +124,21 @@ 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](/#H4sIAAAAAAAAE3WQ0WrDMAxFf0U1hTow1vcsMfQ7lj3YjlxEXTvEymC4_vfFC6Ewtidxde8RkrJw5DGJ9j2LoO8oWnGZJvEi-GuqIn2iZ1x1istsa6dLdqaJ1RAG9sigoYdjYs0onfYJm7fdMX85q3dE59CylA30CnJtDWxjSNHjq49XeZqXEChcT9usLUAOpIbHA0yzM78oColGhDVofLS3neZSS6mqOz-XD51ZmGOAGKwne-vztk-956CL0kAJsi7decupf4l658EUZX4I8yTWt93jSI5wFC3PC5aP8g0Aje5DcQEAAA==):
```ts
let a = false;
let b = false;
// ---cut---
$effect(() => {
console.log('running');
if (a || b) {
console.log('inside if block');
}
});
```
## When not to use `$effect`
In general, `$effect` is best considered something of an escape hatch — useful for things like analytics and direct DOM manipulation — rather than a tool you should use frequently. In particular, avoid using it to synchronise state. Instead of this...

@ -1,5 +1,5 @@
---
title: Reactivity indepth
title: Reactivity in depth
---
- how to think about Runes ("just JavaScript" with added reactivity, what this means for keeping reactivity alive across boundaries)

@ -40,6 +40,7 @@
"playwright": "^1.41.1",
"prettier": "^3.2.4",
"prettier-plugin-svelte": "^3.1.2",
"svelte": "workspace:^",
"typescript": "^5.5.2",
"typescript-eslint": "^8.0.0-alpha.34",
"v8-natives": "^1.2.5",

@ -1,5 +1,89 @@
# svelte
## 5.0.0-next.237
### Patch Changes
- breaking: throw error if derived creates state and then depends on it ([#12985](https://github.com/sveltejs/svelte/pull/12985))
- fix: ensure assignments to state field inside constructor trigger effects ([#12985](https://github.com/sveltejs/svelte/pull/12985))
- fix: ensure $inspect works with SvelteMap and SvelteSet ([#12994](https://github.com/sveltejs/svelte/pull/12994))
- chore: default options.filename to "(unknown)" ([#12997](https://github.com/sveltejs/svelte/pull/12997))
- feat: allow non-synchronous legacy component instantiation ([#12970](https://github.com/sveltejs/svelte/pull/12970))
## 5.0.0-next.236
### Patch Changes
- fix: properly transform destructured `$derived.by` declarations ([#12984](https://github.com/sveltejs/svelte/pull/12984))
## 5.0.0-next.235
### Patch Changes
- chore: update client check for smaller bundle size ([#12975](https://github.com/sveltejs/svelte/pull/12975))
- fix: correctly hydrate empty raw blocks ([#12979](https://github.com/sveltejs/svelte/pull/12979))
## 5.0.0-next.234
### Patch Changes
- fix: allow deleting non-existent `$restProps` properties ([#12971](https://github.com/sveltejs/svelte/pull/12971))
- feat: only traverse trailing static nodes during hydration ([#12935](https://github.com/sveltejs/svelte/pull/12935))
## 5.0.0-next.233
### Patch Changes
- fix: more robust handling of var declarations ([#12949](https://github.com/sveltejs/svelte/pull/12949))
- fix: remove buggy `validate_dynamic_component` check ([#12960](https://github.com/sveltejs/svelte/pull/12960))
## 5.0.0-next.232
### Patch Changes
- breaking: remove `$state.link` rune pending further design work ([#12943](https://github.com/sveltejs/svelte/pull/12943))
- fix: ensure `$store` reads are properly transformed ([#12952](https://github.com/sveltejs/svelte/pull/12952))
- breaking: deprecate `context="module"` in favor of `module` ([#12948](https://github.com/sveltejs/svelte/pull/12948))
## 5.0.0-next.231
### Patch Changes
- breaking: remove callback from `$state.link` ([#12942](https://github.com/sveltejs/svelte/pull/12942))
## 5.0.0-next.230
### Patch Changes
- fix: align list of passive events with browser defaults ([#12933](https://github.com/sveltejs/svelte/pull/12933))
- fix: ensure `{#await}` scope shadowing is computed in the correct order ([#12945](https://github.com/sveltejs/svelte/pull/12945))
- fix: don't skip custom elements with attributes ([#12939](https://github.com/sveltejs/svelte/pull/12939))
## 5.0.0-next.229
### Patch Changes
- feat: add `$state.link` rune ([#12545](https://github.com/sveltejs/svelte/pull/12545))
- fix: allow mixing slots and snippets in custom elements mode ([#12929](https://github.com/sveltejs/svelte/pull/12929))
- fix: small legibility improvement in `Snippet` type hint ([#12928](https://github.com/sveltejs/svelte/pull/12928))
- feat: support HMR with custom elements ([#12926](https://github.com/sveltejs/svelte/pull/12926))
- feat: error on invalid component name ([#12821](https://github.com/sveltejs/svelte/pull/12821))
## 5.0.0-next.228
### Patch Changes

@ -72,6 +72,10 @@
> Cannot set prototype of `$state` object
## state_unsafe_local_read
> Reading state that was created inside the same derived is forbidden. Consider using `untrack` to read locally created state
## state_unsafe_mutation
> Updating state inside a derived is forbidden. If the value should not be reactive, declare it without `$state`

@ -16,7 +16,7 @@
## declaration_duplicate_module_import
> Cannot declare same variable name which is imported inside `<script context="module">`
> Cannot declare a variable with the same name as an import inside `<script module>`
## derived_invalid_export
@ -152,7 +152,7 @@
## store_invalid_subscription
> Cannot reference store value inside `<script context="module">`
> Cannot reference store value inside `<script module>`
## store_invalid_subscription_module

@ -216,12 +216,20 @@ HTML restricts where certain elements can appear. In case of a violation the bro
## script_duplicate
> A component can have a single top-level `<script>` element and/or a single top-level `<script context="module">` element
> A component can have a single top-level `<script>` element and/or a single top-level `<script module>` element
## script_invalid_attribute_value
> If the `%name%` attribute is supplied, it must be a boolean attribute
## script_invalid_context
> If the context attribute is supplied, its value must be "module"
## script_reserved_attribute
> The `%name%` attribute is reserved and cannot be used
## slot_attribute_duplicate
> Duplicate slot name '%name%' in <%component%>

@ -50,6 +50,14 @@ HTML restricts where certain elements can appear. In case of a violation the bro
This code will work when the component is rendered on the client (which is why this is a warning rather than an error), but if you use server rendering it will cause hydration to fail.
## script_context_deprecated
> `context="module"` is deprecated, use the `module` attribute instead
## 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
## slot_element_deprecated
> Using `<slot>` to render parent content is deprecated. Use `{@render ...}` tags instead

@ -2,7 +2,7 @@
"name": "svelte",
"description": "Cybernetically enhanced web apps",
"license": "MIT",
"version": "5.0.0-next.228",
"version": "5.0.0-next.237",
"type": "module",
"types": "./types/index.d.ts",
"engines": {

@ -99,12 +99,12 @@ export function declaration_duplicate(node, name) {
}
/**
* Cannot declare same variable name which is imported inside `<script context="module">`
* Cannot declare a variable with the same name as an import inside `<script module>`
* @param {null | number | NodeLike} node
* @returns {never}
*/
export function declaration_duplicate_module_import(node) {
e(node, "declaration_duplicate_module_import", "Cannot declare same variable name which is imported inside `<script context=\"module\">`");
e(node, "declaration_duplicate_module_import", "Cannot declare a variable with the same name as an import inside `<script module>`");
}
/**
@ -417,12 +417,12 @@ export function store_invalid_scoped_subscription(node) {
}
/**
* Cannot reference store value inside `<script context="module">`
* Cannot reference store value inside `<script module>`
* @param {null | number | NodeLike} node
* @returns {never}
*/
export function store_invalid_subscription(node) {
e(node, "store_invalid_subscription", "Cannot reference store value inside `<script context=\"module\">`");
e(node, "store_invalid_subscription", "Cannot reference store value inside `<script module>`");
}
/**
@ -1044,12 +1044,22 @@ export function render_tag_invalid_spread_argument(node) {
}
/**
* A component can have a single top-level `<script>` element and/or a single top-level `<script context="module">` element
* A component can have a single top-level `<script>` element and/or a single top-level `<script module>` element
* @param {null | number | NodeLike} node
* @returns {never}
*/
export function script_duplicate(node) {
e(node, "script_duplicate", "A component can have a single top-level `<script>` element and/or a single top-level `<script context=\"module\">` element");
e(node, "script_duplicate", "A component can have a single top-level `<script>` element and/or a single top-level `<script module>` element");
}
/**
* If the `%name%` attribute is supplied, it must be a boolean attribute
* @param {null | number | NodeLike} node
* @param {string} name
* @returns {never}
*/
export function script_invalid_attribute_value(node, name) {
e(node, "script_invalid_attribute_value", `If the \`${name}\` attribute is supplied, it must be a boolean attribute`);
}
/**
@ -1061,6 +1071,16 @@ export function script_invalid_context(node) {
e(node, "script_invalid_context", "If the context attribute is supplied, its value must be \"module\"");
}
/**
* The `%name%` attribute is reserved and cannot be used
* @param {null | number | NodeLike} node
* @param {string} name
* @returns {never}
*/
export function script_reserved_attribute(node, name) {
e(node, "script_reserved_attribute", `The \`${name}\` attribute is reserved and cannot be used`);
}
/**
* Duplicate slot name '%name%' in <%component%>
* @param {null | number | NodeLike} node

@ -106,7 +106,7 @@ export function compileModule(source, options) {
*/
export function parse(source, { filename, rootDir, modern } = {}) {
state.reset_warning_filter(() => false);
state.reset(source, { filename, rootDir }); // TODO it's weird to require filename/rootDir here. reconsider the API
state.reset(source, { filename: filename ?? '(unknown)', rootDir });
const ast = _parse(source);
return to_public_ast(source, ast, modern);

@ -1,5 +1,4 @@
/** @import { VariableDeclarator, Node, Identifier } from 'estree' */
/** @import { SvelteNode } from '../types/template.js' */
/** @import { Visitors } from 'zimmerframe' */
/** @import { ComponentAnalysis } from '../phases/types.js' */
/** @import { Scope } from '../phases/scope.js' */
@ -58,6 +57,13 @@ export function migrate(source) {
needs_run: false
};
if (parsed.module) {
const context = parsed.module.attributes.find((attr) => attr.name === 'context');
if (context) {
state.str.update(context.start, context.end, 'module');
}
}
if (parsed.instance) {
walk(parsed.instance.content, state, instance_script);
}
@ -223,7 +229,7 @@ export function migrate(source) {
* }} State
*/
/** @type {Visitors<SvelteNode, State>} */
/** @type {Visitors<Compiler.SvelteNode, State>} */
const instance_script = {
_(node, { state, next }) {
// @ts-expect-error
@ -472,7 +478,7 @@ const instance_script = {
}
};
/** @type {Visitors<SvelteNode, State>} */
/** @type {Visitors<Compiler.SvelteNode, State>} */
const template = {
Identifier(node, { state, path }) {
handle_identifier(node, state, path);
@ -590,7 +596,7 @@ const template = {
/**
* @param {VariableDeclarator} declarator
* @param {MagicString} str
* @param {Compiler.SvelteNode[]} path
* @param {Array<Compiler.SvelteNode>} path
*/
function extract_type_and_comment(declarator, str, path) {
const parent = path.at(-1);

@ -4,32 +4,14 @@
import * as acorn from '../acorn.js';
import { regex_not_newline_characters } from '../../patterns.js';
import * as e from '../../../errors.js';
import * as w from '../../../warnings.js';
import { is_text_attribute } from '../../../utils/ast.js';
const regex_closing_script_tag = /<\/script\s*>/;
const regex_starts_with_closing_script_tag = /^<\/script\s*>/;
/**
* @param {any[]} attributes
* @returns {string}
*/
function get_context(attributes) {
const context = attributes.find(
/** @param {any} attribute */ (attribute) => attribute.name === 'context'
);
if (!context) return 'default';
if (context.value.length !== 1 || context.value[0].type !== 'Text') {
e.script_invalid_context(context.start);
}
const value = context.value[0].data;
if (value !== 'module') {
e.script_invalid_context(context.start);
}
return value;
}
const RESERVED_ATTRIBUTES = ['server', 'client', 'worker', 'test', 'default'];
const ALLOWED_ATTRIBUTES = ['context', 'generics', 'lang', 'module'];
/**
* @param {Parser} parser
@ -60,14 +42,52 @@ export function read_script(parser, start, attributes) {
// TODO is this necessary?
ast.start = script_start;
/** @type {'default' | 'module'} */
let context = 'default';
for (const attribute of /** @type {Attribute[]} */ (attributes)) {
if (RESERVED_ATTRIBUTES.includes(attribute.name)) {
e.script_reserved_attribute(attribute, attribute.name);
}
if (!ALLOWED_ATTRIBUTES.includes(attribute.name)) {
w.script_unknown_attribute(attribute);
}
if (attribute.name === 'module') {
if (attribute.value !== true) {
// Deliberately a generic code to future-proof for potential other attributes
e.script_invalid_attribute_value(attribute, attribute.name);
}
context = 'module';
}
if (attribute.name === 'context') {
if (attribute.value === true || !is_text_attribute(attribute)) {
e.script_invalid_context(attribute);
}
const value = attribute.value[0].data;
if (value !== 'module') {
e.script_invalid_context(attribute);
}
w.script_context_deprecated(attribute);
context = 'module';
}
}
return {
type: 'Script',
start,
end: parser.index,
context: get_context(attributes),
context,
content: ast,
parent: null,
// @ts-ignore
attributes: attributes
attributes
};
}

@ -243,7 +243,7 @@ export function analyze_module(ast, options) {
return {
module: { ast, scope, scopes },
name: options.filename || 'module',
name: options.filename,
accessors: false,
runes: true,
immutable: true
@ -330,7 +330,7 @@ export function analyze_component(root, source, options) {
if (module.ast) {
for (const { node, path } of references) {
// if the reference is inside context="module", error. this is a bit hacky but it works
// if the reference is inside module, error. this is a bit hacky but it works
if (
/** @type {number} */ (node.start) > /** @type {number} */ (module.ast.start) &&
/** @type {number} */ (node.end) < /** @type {number} */ (module.ast.end) &&
@ -349,7 +349,7 @@ export function analyze_component(root, source, options) {
}
}
const component_name = get_component_name(options.filename ?? 'Component');
const component_name = get_component_name(options.filename);
const runes = options.runes ?? Array.from(module.scope.references.keys()).some(is_rune);
@ -390,7 +390,7 @@ export function analyze_component(root, source, options) {
hash: root.css
? options.cssHash({
css: root.css.content.styles,
filename: options.filename ?? '<unknown>',
filename: options.filename,
name: component_name,
hash
})

@ -7,10 +7,11 @@ import {
} from '../../../../html-tree-validation.js';
import * as e from '../../../errors.js';
import * as w from '../../../warnings.js';
import { create_attribute } from '../../nodes.js';
import { create_attribute, is_custom_element_node } from '../../nodes.js';
import { regex_starts_with_newline } from '../../patterns.js';
import { check_element } from './shared/a11y.js';
import { validate_element } from './shared/element.js';
import { mark_subtree_dynamic } from './shared/fragment.js';
/**
* @param {RegularElement} node
@ -88,6 +89,11 @@ export function RegularElement(node, context) {
node.metadata.svg = is_svg(node.name);
node.metadata.mathml = is_mathml(node.name);
if (is_custom_element_node(node) && node.attributes.length > 0) {
// we're setting all attributes on custom elements through properties
mark_subtree_dynamic(context.path);
}
if (context.state.parent_element) {
let past_parent = false;
let only_warn = false;

@ -505,14 +505,12 @@ export function client_component(analysis, options) {
}
if (dev) {
if (filename) {
// add `App[$.FILENAME] = 'App.svelte'` so that we can print useful messages later
body.unshift(
b.stmt(
b.assignment('=', b.member(b.id(analysis.name), '$.FILENAME', true), b.literal(filename))
)
);
}
// add `App[$.FILENAME] = 'App.svelte'` so that we can print useful messages later
body.unshift(
b.stmt(
b.assignment('=', b.member(b.id(analysis.name), '$.FILENAME', true), b.literal(filename))
)
);
body.unshift(b.stmt(b.call(b.id('$.mark_module_start'))));
body.push(b.stmt(b.call(b.id('$.mark_module_end'), b.id(analysis.name))));

@ -57,20 +57,6 @@ function build_assignment(operator, left, right, context) {
return b.assignment(operator, /** @type {Pattern} */ (context.visit(left)), value);
}
}
} else if (left.property.type === 'Identifier' && context.state.in_constructor) {
const public_state = context.state.public_state.get(left.property.name);
if (public_state !== undefined && should_proxy(right, context.state.scope)) {
const value = /** @type {Expression} */ (context.visit(right));
return b.assignment(
operator,
/** @type {Pattern} */ (context.visit(left)),
public_state.kind === 'raw_state'
? value
: build_proxy_reassignment(value, public_state.id)
);
}
}
}

@ -11,6 +11,9 @@ import { create_derived_block_argument } from '../utils.js';
export function AwaitBlock(node, context) {
context.state.template.push('<!>');
// Visit {#await <expression>} first to ensure that scopes are in the correct order
const expression = b.thunk(/** @type {Expression} */ (context.visit(node.expression)));
let then_block;
let catch_block;
@ -45,7 +48,7 @@ export function AwaitBlock(node, context) {
b.call(
'$.await',
context.state.node,
b.thunk(/** @type {Expression} */ (context.visit(node.expression))),
expression,
node.pending
? b.arrow([b.id('$$anchor')], /** @type {BlockStatement} */ (context.visit(node.pending)))
: b.literal(null),

@ -13,15 +13,6 @@ export function MemberExpression(node, context) {
if (field) {
return context.state.in_constructor ? b.member(node, 'v') : b.call('$.get', node);
}
} else if (node.object.type === 'ThisExpression') {
// rewrite `this.foo` as `this.#foo.v` inside a constructor
if (node.property.type === 'Identifier' && !node.computed) {
const field = context.state.public_state.get(node.property.name);
if (field && context.state.in_constructor) {
return b.member(b.member(b.this, field.id), 'v');
}
}
}
context.next();

@ -42,11 +42,17 @@ export function Program(_, context) {
for (const [name, binding] of context.state.scope.declarations) {
if (binding.kind === 'store_sub') {
const store = /** @type {Expression} */ (context.visit(b.id(name.slice(1))));
// read lazily, so that transforms added later are still applied
/** @type {Expression} */
let cached;
const get_store = () => {
return (cached ??= /** @type {Expression} */ (context.visit(b.id(name.slice(1)))));
};
context.state.transform[name] = {
read: b.call,
assign: (_, value) => b.call('$.store_set', store, value),
assign: (_, value) => b.call('$.store_set', get_store(), value),
mutate: (node, mutation) => {
// We need to untrack the store read, for consistency with Svelte 4
const untracked = b.call('$.untrack', node);
@ -70,7 +76,7 @@ export function Program(_, context) {
return b.call(
'$.store_mutate',
store,
get_store(),
b.assignment(
mutation.operator,
/** @type {MemberExpression} */ (

@ -173,7 +173,7 @@ export function VariableDeclaration(node, context) {
let id;
let rhs = value;
if (init.arguments[0].type === 'Identifier') {
if (rune === '$derived' && init.arguments[0].type === 'Identifier') {
id = init.arguments[0];
} else {
id = b.id(context.state.scope.generate('$$d'));
@ -284,7 +284,7 @@ export function VariableDeclaration(node, context) {
}
/**
* Creates the output for a state declaration.
* Creates the output for a state declaration in legacy mode.
* @param {VariableDeclarator} declarator
* @param {Scope} scope
* @param {Expression} value

@ -348,14 +348,7 @@ export function build_component(node, component_name, context, anchor = context.
b.thunk(/** @type {Expression} */ (context.visit(node.expression))),
b.arrow(
[b.id('$$anchor'), b.id(component_name)],
b.block([
...binding_initializers,
b.stmt(
dev
? b.call('$.validate_dynamic_component', b.thunk(prev(b.id('$$anchor'))))
: prev(b.id('$$anchor'))
)
])
b.block([...binding_initializers, b.stmt(prev(b.id('$$anchor')))])
)
);
};

@ -23,7 +23,7 @@ export function add_state_transformers(context) {
binding.kind === 'legacy_reactive'
) {
context.state.transform[name] = {
read: get_value,
read: binding.declaration_kind === 'var' ? (node) => b.call('$.safe_get', node) : get_value,
assign: (node, value) => {
let call = b.call('$.set', node, value);

@ -1,6 +1,6 @@
/** @import { Expression } from 'estree' */
/** @import { ExpressionTag, SvelteNode, Text } from '#compiler' */
/** @import { ComponentClientTransformState, ComponentContext } from '../../types' */
/** @import { ComponentContext } from '../../types' */
import { is_event_attribute, is_text_attribute } from '../../../../../utils/ast.js';
import * as b from '../../../../../utils/builders.js';
import { build_template_literal, build_update } from './utils.js';
@ -119,7 +119,7 @@ export function process_children(nodes, initial, is_element, needs_reset, { visi
// traverse to the last (n - 1) one when hydrating
if (skipped > 1 && !needs_reset) {
skipped -= 1;
state.init.push(b.stmt(get_node(false)));
state.init.push(b.stmt(b.call('$.next', skipped !== 1 && b.literal(skipped))));
}
}

@ -80,7 +80,7 @@ export function transform_module(analysis, source, options) {
? server_module(analysis, options)
: client_module(analysis, options);
const basename = (options.filename ?? 'Module').split(/[/\\]/).at(-1);
const basename = options.filename.split(/[/\\]/).at(-1);
if (program.body.length > 0) {
program.body[0].leadingComments = [
{

@ -356,7 +356,7 @@ export function server_component(analysis, options) {
body.push(b.export_default(component_function));
}
if (dev && filename) {
if (dev) {
// add `App[$.FILENAME] = 'App.svelte'` so that we can print useful messages later
body.unshift(
b.stmt(

@ -461,8 +461,20 @@ export function create_scopes(ast, root, allow_reactive_declarations, parent) {
ForStatement: create_block_scope,
ForInStatement: create_block_scope,
ForOfStatement: create_block_scope,
BlockStatement: create_block_scope,
SwitchStatement: create_block_scope,
BlockStatement(node, context) {
const parent = context.path.at(-1);
if (
parent?.type === 'FunctionDeclaration' ||
parent?.type === 'FunctionExpression' ||
parent?.type === 'ArrowFunctionExpression'
) {
// We already created a new scope for the function
context.next();
} else {
create_block_scope(node, context);
}
},
ClassDeclaration(node, { state, next }) {
if (node.id) state.scope.declare(node.id, 'normal', 'let', node);

@ -8,9 +8,9 @@ import { getLocator } from 'locate-character';
export let warnings = [];
/**
* The filename (if specified in the compiler options) relative to the rootDir (if specified).
* The filename relative to the rootDir (if specified).
* This should not be used in the compiler output except in dev mode
* @type {string | undefined}
* @type {string}
*/
export let filename;
@ -77,20 +77,16 @@ export function is_ignored(node, code) {
/**
* @param {string} _source
* @param {{ dev?: boolean; filename?: string; rootDir?: string }} options
* @param {{ dev?: boolean; filename: string; rootDir?: string }} options
*/
export function reset(_source, options) {
source = _source;
const root_dir = options.rootDir?.replace(/\\/g, '/');
filename = options.filename?.replace(/\\/g, '/');
filename = options.filename.replace(/\\/g, '/');
dev = !!options.dev;
if (
typeof filename === 'string' &&
typeof root_dir === 'string' &&
filename.startsWith(root_dir)
) {
if (typeof root_dir === 'string' && filename.startsWith(root_dir)) {
// make filename relative to rootDir
filename = filename.replace(root_dir, '').replace(/^[/\\]/, '');
}

@ -56,7 +56,7 @@ export interface CompileError extends ICompileDiagnostic {}
export type CssHashGetter = (args: {
name: string;
filename: string | undefined;
filename: string;
css: string;
hash: (input: string) => string;
}) => string;
@ -219,11 +219,7 @@ export interface ModuleCompileOptions {
// The following two somewhat scary looking types ensure that certain types are required but can be undefined still
export type ValidatedModuleCompileOptions = Omit<
Required<ModuleCompileOptions>,
'filename' | 'rootDir'
> & {
filename: ModuleCompileOptions['filename'];
export type ValidatedModuleCompileOptions = Omit<Required<ModuleCompileOptions>, 'rootDir'> & {
rootDir: ModuleCompileOptions['rootDir'];
};

@ -61,7 +61,7 @@ export interface Root extends BaseNode {
css: Css.StyleSheet | null;
/** The parsed `<script>` element, if exists */
instance: Script | null;
/** The parsed `<script context="module">` element, if exists */
/** The parsed `<script module>` element, if exists */
module: Script | null;
metadata: {
/** Whether the component was parsed with typescript */
@ -488,7 +488,7 @@ export type SvelteNode = Node | TemplateNode | Fragment | Css.Node;
export interface Script extends BaseNode {
type: 'Script';
context: string;
context: 'default' | 'module';
content: Program;
attributes: Attribute[];
}

@ -393,7 +393,7 @@ export function parse_attached_sourcemap(processed, tag_name) {
*/
export function merge_with_preprocessor_map(result, options, source_name) {
if (options.sourcemap) {
const file_basename = get_basename(options.filename || 'input.svelte');
const file_basename = get_basename(options.filename);
// The preprocessor map is expected to contain `sources: [basename_of_filename]`, but our own
// map may contain a different file name. Patch our map beforehand to align sources so merging
// with the preprocessor map works correctly.
@ -442,11 +442,10 @@ export function get_basename(filename) {
}
/**
* @param {string | undefined} filename
* @param {string} filename
* @param {string | undefined} output_filename
* @param {string} fallback
*/
export function get_source_name(filename, output_filename, fallback) {
if (!filename) return fallback;
return output_filename ? get_relative_path(output_filename, filename) : get_basename(filename);
}

@ -9,7 +9,7 @@ import * as w from './warnings.js';
*/
const common = {
filename: string(undefined),
filename: string('(unknown)'),
// default to process.cwd() where it exists to replicate svelte4 behavior
// see https://github.com/sveltejs/svelte/blob/b62fc8c8fd2640c9b99168f01b9d958cb2f7574f/packages/svelte/src/compiler/compile/Component.js#L211

@ -117,6 +117,8 @@ export const codes = [
"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"
@ -769,6 +771,22 @@ export function node_invalid_placement_ssr(node, thing, parent) {
w(node, "node_invalid_placement_ssr", `${thing} is invalid inside \`<${parent}>\`. When rendering this component on the server, the resulting HTML will be modified by the browser, likely resulting in a \`hydration_mismatch\` warning`);
}
/**
* `context="module"` is deprecated, use the `module` attribute instead
* @param {null | NodeLike} node
*/
export function script_context_deprecated(node) {
w(node, "script_context_deprecated", "`context=\"module\"` is deprecated, use the `module` attribute instead");
}
/**
* Unrecognized attribute should be one of `generics`, `lang` or `module`. If this exists for a preprocessor, ensure that the preprocessor removes it
* @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");
}
/**
* Using `<slot>` to render parent content is deprecated. Use `{@render ...}` tags instead
* @param {null | NodeLike} node

@ -17,6 +17,8 @@ export interface ComponentConstructorOptions<
context?: Map<any, any>;
hydrate?: boolean;
intro?: boolean;
recover?: boolean;
sync?: boolean;
$$inline?: boolean;
}
@ -285,9 +287,9 @@ export interface Snippet<Parameters extends unknown[] = []> {
// rest parameter type, which is not supported. If rest parameters are added
// in the future, the condition can be removed.
...args: number extends Parameters['length'] ? never : Parameters
): typeof SnippetReturn & {
_: 'functions passed to {@render ...} tags must use the `Snippet` type imported from "svelte"';
};
): {
'{@render ...} must be called with a Snippet': "import type { Snippet } from 'svelte'";
} & typeof SnippetReturn;
}
interface DispatchOptions {

@ -224,6 +224,7 @@ export function each(node, flags, get_collection, get_key, render_fn, fallback_f
}
/**
* Add, remove, or reorder items output by an each block as its input changes
* @template V
* @param {Array<V>} array
* @param {EachState} state

@ -46,15 +46,20 @@ export function html(node, get_value, svg, mathml, skip_warning) {
var value = '';
/** @type {Effect | null} */
/** @type {Effect | undefined} */
var effect;
block(() => {
if (value === (value = get_value())) return;
if (value === (value = get_value())) {
if (hydrating) {
hydrate_next();
}
return;
}
if (effect) {
if (effect !== undefined) {
destroy_effect(effect);
effect = null;
effect = undefined;
}
if (value === '') return;

@ -310,11 +310,7 @@ export function apply(
handler.apply(element, args);
} else if (has_side_effects || handler != null) {
const filename = component?.[FILENAME];
const location = filename
? loc
? ` at ${filename}:${loc[0]}:${loc[1]}`
: ` in ${filename}`
: '';
const location = loc ? ` at ${filename}:${loc[0]}:${loc[1]}` : ` in ${filename}`;
const event_name = args[0].type;
const description = `\`${event_name}\` handler${location}`;

@ -66,9 +66,16 @@ export function hydrate_template(template) {
}
}
export function next() {
export function next(count = 1) {
if (hydrating) {
hydrate_next();
var i = count;
var node = hydrate_node;
while (i--) {
node = /** @type {TemplateNode} */ (get_next_sibling(node));
}
hydrate_node = node;
}
}

@ -310,6 +310,22 @@ export function state_prototype_fixed() {
}
}
/**
* Reading state that was created inside the same derived is forbidden. Consider using `untrack` to read locally created state
* @returns {never}
*/
export function state_unsafe_local_read() {
if (DEV) {
const error = new Error(`state_unsafe_local_read\nReading state that was created inside the same derived is forbidden. Consider using \`untrack\` to read locally created state`);
error.name = 'Svelte error';
throw error;
} else {
// TODO print a link to the documentation
throw new Error("state_unsafe_local_read");
}
}
/**
* Updating state inside a derived is forbidden. If the value should not be reactive, declare it without `$state`
* @returns {never}

@ -127,6 +127,7 @@ export {
export { set_text } from './render.js';
export {
get,
safe_get,
invalidate_inner_signals,
flush_sync,
tick,
@ -143,12 +144,7 @@ export {
setContext,
hasContext
} from './runtime.js';
export {
validate_binding,
validate_dynamic_component,
validate_each_keys,
validate_prop_bindings
} from './validate.js';
export { validate_binding, validate_each_keys, validate_prop_bindings } from './validate.js';
export { raf } from './timing.js';
export { proxy } from './proxy.js';
export { create_custom_element } from './dom/elements/custom-element.js';

@ -118,7 +118,7 @@ export function proxy(value, parent = null, prev) {
// create a source, but only if it's an own property and not a prototype property
if (s === undefined && (!exists || get_descriptor(target, prop)?.writable)) {
s = source(proxy(exists ? target[prop] : UNINITIALIZED, metadata));
s = source(proxy(exists ? target[prop] : UNINITIALIZED, metadata), null);
sources.set(prop, s);
}
@ -170,7 +170,7 @@ export function proxy(value, parent = null, prev) {
(current_effect !== null && (!has || get_descriptor(target, prop)?.writable))
) {
if (s === undefined) {
s = source(has ? proxy(target[prop], metadata) : UNINITIALIZED);
s = source(has ? proxy(target[prop], metadata) : UNINITIALIZED, null);
sources.set(prop, s);
}

@ -130,7 +130,7 @@ const legacy_rest_props_handler = {
},
deleteProperty(target, key) {
// Svelte 4 allowed for deletions on $$restProps
if (target.exclude.includes(key)) return false;
if (target.exclude.includes(key)) return true;
target.exclude.push(key);
update(target.version);
return true;

@ -1,4 +1,4 @@
/** @import { Derived, Effect, Source, Value } from '#client' */
/** @import { Derived, Effect, Reaction, Source, Value } from '#client' */
import { DEV } from 'esm-env';
import {
current_component_context,
@ -13,7 +13,9 @@ import {
set_signal_status,
untrack,
increment_version,
update_effect
update_effect,
derived_sources,
set_derived_sources
} from '../runtime.js';
import { equals, safe_equals } from './equality.js';
import {
@ -32,27 +34,39 @@ let inspect_effects = new Set();
/**
* @template V
* @param {V} v
* @param {Reaction | null} [owner]
* @returns {Source<V>}
*/
/*#__NO_SIDE_EFFECTS__*/
export function source(v) {
return {
export function source(v, owner = current_reaction) {
var source = {
f: 0, // TODO ideally we could skip this altogether, but it causes type errors
v,
reactions: null,
equals,
version: 0
};
if (owner !== null && (owner.f & DERIVED) !== 0) {
if (derived_sources === null) {
set_derived_sources([source]);
} else {
derived_sources.push(source);
}
}
return source;
}
/**
* @template V
* @param {V} initial_value
* @param {Reaction | null} [owner]
* @returns {Source<V>}
*/
/*#__NO_SIDE_EFFECTS__*/
export function mutable_source(initial_value) {
const s = source(initial_value);
export function mutable_source(initial_value, owner) {
const s = source(initial_value, owner);
s.equals = safe_equals;
// bind the signal to the component context, in case we need to
@ -84,7 +98,14 @@ export function mutate(source, value) {
* @returns {V}
*/
export function set(source, value) {
if (current_reaction !== null && is_runes() && (current_reaction.f & DERIVED) !== 0) {
if (
current_reaction !== null &&
is_runes() &&
(current_reaction.f & DERIVED) !== 0 &&
// If the source was created locally within the current derived, then
// we allow the mutation.
(derived_sources === null || !derived_sources.includes(source))
) {
e.state_unsafe_mutation();
}
@ -145,8 +166,8 @@ function mark_reactions(signal, status) {
var reaction = reactions[i];
var flags = reaction.f;
// If a reaction is already dirty, skip it (but always mark unowned deriveds)
if ((flags & (CLEAN | UNOWNED)) === 0) continue;
// Skip any effects that are already dirty
if ((flags & DIRTY) !== 0) continue;
// In legacy mode, skip the current effect to prevent infinite loops
if (!runes && reaction === current_effect) continue;

@ -19,7 +19,7 @@ import { mutable_source, set } from './sources.js';
export function store_get(store, store_name, stores) {
const entry = (stores[store_name] ??= {
store: null,
source: mutable_source(undefined),
source: mutable_source(undefined, null),
unsubscribe: noop
});

@ -80,6 +80,20 @@ export function set_current_effect(effect) {
current_effect = effect;
}
/**
* When sources are created within a derived, we record them so that we can safely allow
* local mutations to these sources without the side-effect error being invoked unnecessarily.
* @type {null | Source[]}
*/
export let derived_sources = null;
/**
* @param {Source[] | null} sources
*/
export function set_derived_sources(sources) {
derived_sources = sources;
}
/**
* The dependencies of the reaction that is currently being executed. In many cases,
* the dependencies are unchanged between runs, and so this will be `null` unless
@ -230,12 +244,14 @@ function handle_error(error, effect, component_context) {
let current_context = component_context;
while (current_context !== null) {
/** @type {string} */
var filename = current_context.function?.[FILENAME];
if (DEV) {
/** @type {string} */
var filename = current_context.function?.[FILENAME];
if (filename) {
const file = filename.split('/').pop();
component_stack.push(file);
if (filename) {
const file = filename.split('/').pop();
component_stack.push(file);
}
}
current_context = current_context.p;
@ -279,12 +295,14 @@ export function update_reaction(reaction) {
var previous_untracked_writes = current_untracked_writes;
var previous_reaction = current_reaction;
var previous_skip_reaction = current_skip_reaction;
var prev_derived_sources = derived_sources;
new_deps = /** @type {null | Value[]} */ (null);
skipped_deps = 0;
current_untracked_writes = null;
current_reaction = (reaction.f & (BRANCH_EFFECT | ROOT_EFFECT)) === 0 ? reaction : null;
current_skip_reaction = !is_flushing_effect && (reaction.f & UNOWNED) !== 0;
derived_sources = null;
try {
var result = /** @type {Function} */ (0, reaction.fn)();
@ -321,6 +339,7 @@ export function update_reaction(reaction) {
current_untracked_writes = previous_untracked_writes;
current_reaction = previous_reaction;
current_skip_reaction = previous_skip_reaction;
derived_sources = prev_derived_sources;
}
}
@ -698,6 +717,9 @@ export function get(signal) {
// Register the dependency on the current reaction signal.
if (current_reaction !== null) {
if (derived_sources !== null && derived_sources.includes(signal)) {
e.state_unsafe_local_read();
}
var deps = current_reaction.deps;
// If the signal is accessing the same dependencies in the same
@ -734,6 +756,16 @@ export function get(signal) {
return signal.v;
}
/**
* Like `get`, but checks for `undefined`. Used for `var` declarations because they can be accessed before being declared
* @template V
* @param {Value<V> | undefined} signal
* @returns {V | undefined}
*/
export function safe_get(signal) {
return signal && get(signal);
}
/**
* Invokes a function and captures all signals that are read during the invocation,
* then invalidates them.

@ -1,11 +1,11 @@
/** @import { Raf } from '#client' */
import { noop } from '../shared/utils.js';
const is_client = typeof window !== 'undefined';
import { BROWSER } from 'esm-env';
const request_animation_frame = is_client ? requestAnimationFrame : noop;
const request_animation_frame = BROWSER ? requestAnimationFrame : noop;
const now = is_client ? () => performance.now() : () => Date.now();
const now = BROWSER ? () => performance.now() : () => Date.now();
/** @type {Raf} */
export const raf = {

@ -14,31 +14,6 @@ function is_void(tag) {
return void_element_names.test(tag) || tag.toLowerCase() === '!doctype';
}
/**
* @template Component
* @param {() => Component} component_fn
* @returns {Component}
*/
export function validate_dynamic_component(component_fn) {
try {
const instance = component_fn();
if (instance !== undefined && typeof instance !== 'object') {
e.svelte_component_invalid_this_value();
}
return instance;
} catch (err) {
const { message } = /** @type {Error} */ (err);
if (typeof message === 'string' && message.indexOf('is not a function') !== -1) {
e.svelte_component_invalid_this_value();
}
throw err;
}
}
/**
* @param {() => any} collection
* @param {(item: any, index: number) => string} key_fn
@ -127,7 +102,7 @@ export function validate_binding(binding, get_object, get_property, line, column
ran = true;
if (effect.deps === null) {
var location = filename && `${filename}:${line}:${column}`;
var location = `${filename}:${line}:${column}`;
w.binding_property_non_reactive(binding, location);
warned = true;

@ -56,6 +56,9 @@ function clone(value, cloned, path, paths) {
const unwrapped = cloned.get(value);
if (unwrapped !== undefined) return unwrapped;
if (value instanceof Map) return /** @type {Snapshot<T>} */ (new Map(value));
if (value instanceof Set) return /** @type {Snapshot<T>} */ (new Set(value));
if (is_array(value)) {
const copy = /** @type {Snapshot<any>} */ ([]);
cloned.set(value, copy);

@ -17,9 +17,6 @@ import { define_property } from '../internal/shared/utils.js';
*
* @param {ComponentConstructorOptions<Props> & {
* component: ComponentType<SvelteComponent<Props, Events, Slots>> | Component<Props>;
* immutable?: boolean;
* hydrate?: boolean;
* recover?: boolean;
* }} options
* @returns {SvelteComponent<Props, Events, Slots> & Exports}
*/
@ -64,9 +61,6 @@ class Svelte4Component {
/**
* @param {ComponentConstructorOptions & {
* component: any;
* immutable?: boolean;
* hydrate?: boolean;
* recover?: false;
* }} options
*/
constructor(options) {
@ -110,8 +104,8 @@ class Svelte4Component {
recover: options.recover
});
// We don't flush_sync for custom element wrappers
if (!options?.props?.$$host) {
// We don't flush_sync for custom element wrappers or if the user doesn't want it
if (!options?.props?.$$host || options.sync === false) {
flush_sync();
}

@ -222,7 +222,7 @@ export function is_dom_property(name) {
return DOM_PROPERTIES.includes(name);
}
const PASSIVE_EVENTS = ['wheel', 'touchstart', 'touchmove', 'touchend', 'touchcancel'];
const PASSIVE_EVENTS = ['wheel', 'mousewheel', 'touchstart', 'touchmove'];
/**
* Returns `true` if `name` is a passive event

@ -6,5 +6,5 @@
* https://svelte.dev/docs/svelte-compiler#svelte-version
* @type {string}
*/
export const VERSION = '5.0.0-next.228';
export const VERSION = '5.0.0-next.237';
export const PUBLIC_VERSION = '5';

@ -5,6 +5,6 @@ export default test({
code: 'state_invalid_export',
message:
"Cannot export state from a module if it is reassigned. Either export a function returning the state value or only mutate the state value's properties",
position: [76, 114]
position: [66, 104]
}
});

@ -1,4 +1,4 @@
<script context="module">
<script module>
export const object = $state({
ok: true
});

@ -1,4 +1,4 @@
<script context="module">
<script module>
export let bar = ''; // check that it doesn't error here already
</script>

@ -3,7 +3,7 @@ import { test } from '../../test';
export default test({
error: {
code: 'store_invalid_subscription',
message: 'Cannot reference store value inside `<script context="module">`',
position: [164, 168]
message: 'Cannot reference store value inside `<script module>`',
position: [154, 158]
}
});

@ -1,4 +1,4 @@
<script context="module">
<script module>
// this should be fine (state rune is not treated as a store)
const state = $state(0);
// this is not

@ -0,0 +1,15 @@
import { test } from '../../test';
export default test({
server_props: {
html: '<div></div>'
},
props: {
html: '<div></div>'
},
test(assert, target) {
assert.htmlEqual(target.innerHTML, '<div></div>');
}
});

@ -0,0 +1,3 @@
<script context="module">
let foo = true;
</script>

@ -1,6 +1,6 @@
<svelte:options customElement="my-custom-element" runes={true} />
<script context="module" lang="ts">
<script module lang="ts">
</script>
<script lang="ts" generics="T extends { foo: number }">

@ -2,7 +2,7 @@
"css": null,
"js": [],
"start": 0,
"end": 112,
"end": 102,
"type": "Root",
"fragment": {
"type": "Fragment",
@ -16,8 +16,8 @@
},
{
"type": "Text",
"start": 112,
"end": 114,
"start": 102,
"end": 104,
"raw": "\n\n",
"data": "\n\n"
}
@ -79,12 +79,12 @@
"module": {
"type": "Script",
"start": 67,
"end": 112,
"end": 102,
"context": "module",
"content": {
"type": "Program",
"start": 102,
"end": 103,
"start": 92,
"end": 93,
"loc": {
"start": {
"line": 1,
@ -102,27 +102,19 @@
{
"type": "Attribute",
"start": 75,
"end": 91,
"name": "context",
"value": [
{
"start": 84,
"end": 90,
"type": "Text",
"raw": "module",
"data": "module"
}
]
"end": 81,
"name": "module",
"value": true
},
{
"type": "Attribute",
"start": 92,
"end": 101,
"start": 82,
"end": 91,
"name": "lang",
"value": [
{
"start": 98,
"end": 100,
"start": 88,
"end": 90,
"type": "Text",
"raw": "ts",
"data": "ts"
@ -133,13 +125,13 @@
},
"instance": {
"type": "Script",
"start": 114,
"end": 179,
"start": 104,
"end": 169,
"context": "default",
"content": {
"type": "Program",
"start": 169,
"end": 170,
"start": 159,
"end": 160,
"loc": {
"start": {
"line": 1,
@ -156,13 +148,13 @@
"attributes": [
{
"type": "Attribute",
"start": 122,
"end": 131,
"start": 112,
"end": 121,
"name": "lang",
"value": [
{
"start": 128,
"end": 130,
"start": 118,
"end": 120,
"type": "Text",
"raw": "ts",
"data": "ts"
@ -171,13 +163,13 @@
},
{
"type": "Attribute",
"start": 132,
"end": 168,
"start": 122,
"end": 158,
"name": "generics",
"value": [
{
"start": 142,
"end": 167,
"start": 132,
"end": 157,
"type": "Text",
"raw": "T extends { foo: number }",
"data": "T extends { foo: number }"

@ -0,0 +1,10 @@
import { test } from '../../test';
export default test({
html: '<div>Loading...</div>',
async test({ assert, target }) {
await Promise.resolve();
assert.htmlEqual(target.innerHTML, '<div>10</div>');
}
});

@ -0,0 +1,11 @@
<script>
const x = Promise.resolve(10);
</script>
<div>
{#await x}
Loading...
{:then x}
{x}
{/await}
</div>

@ -1,5 +1,5 @@
<script context="module">
import Tooltip from './Tooltip.svelte';
<script module>
import Tooltip from './Tooltip.svelte';
export const Widget = { Tooltip };
</script>
export const Widget = { Tooltip };
</script>

@ -1,5 +1,5 @@
<script context="module">
import Foo from './Foo.svelte';
<script module>
import Foo from './Foo.svelte';
export const Components = { Foo };
</script>
export const Components = { Foo };
</script>

@ -1,12 +0,0 @@
import { test } from '../../test';
export default test({
mode: ['client'],
compileOptions: {
dev: true
},
error:
'svelte_component_invalid_this_value\nThe `this={...}` property of a `<svelte:component>` must be a Svelte component, if defined'
});

@ -1,5 +0,0 @@
<script>
let banana = {};
</script>
<svelte:component this={banana} />

@ -1,24 +0,0 @@
import { test } from '../../test';
export default test({
compileOptions: {
dev: true
},
get props() {
return { componentName: 'Sub' };
},
html: '<div>Sub</div>',
test({ assert, component, target }) {
component.componentName = 'Proxy';
assert.htmlEqual(target.innerHTML, '<div>Sub</div>');
try {
component.componentName = 'banana';
throw new Error('Expected an error');
} catch (err) {
assert.include(
/** @type {Error} */ (err).message,
'svelte_component_invalid_this_value\nThe `this={...}` property of a `<svelte:component>` must be a Svelte component, if defined'
);
}
}
});

@ -1,14 +0,0 @@
<script>
import Sub from './Sub.svelte';
export let componentName = 'Sub';
let proxy = new Proxy(Sub, {});
let banana = {};
let component;
$: {
if (componentName === 'Sub') component = Sub;
else if (componentName === 'Proxy') component = proxy;
else component = banana;
};
</script>
<svelte:component this={component} />

@ -1,23 +1,23 @@
<script context="module">
let value = 'Blub';
let count = 0;
const subscribers = new Set();
export const model = {
subscribe(fn) {
subscribers.add(fn);
count ++;
fn(value);
return () => {
count--;
subscribers.delete(fn);
};
},
set(v) {
value = v;
subscribers.forEach(fn => fn(v));
},
getCount() {
return count;
}
};
</script>
<script module>
let value = 'Blub';
let count = 0;
const subscribers = new Set();
export const model = {
subscribe(fn) {
subscribers.add(fn);
count++;
fn(value);
return () => {
count--;
subscribers.delete(fn);
};
},
set(v) {
value = v;
subscribers.forEach((fn) => fn(v));
},
getCount() {
return count;
}
};
</script>

@ -1,6 +1,6 @@
<script context="module">
<script module>
export const TABS = {};
</script>

@ -1,5 +1,5 @@
<script context="module">
<script module>
let set = new Set(['x']);
</script>
<p>{set.has('x')}</p>
<p>{set.has('x')}</p>

@ -1,5 +1,7 @@
<script context="module">
export function foo() { return 42; };
<script module>
export function foo() {
return 42;
}
</script>
<button on:click={foo}>foo</button>

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

Loading…
Cancel
Save