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

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

@ -1,5 +1,89 @@
# svelte # 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 ## 5.0.0-next.228
### Patch Changes ### Patch Changes

@ -72,6 +72,10 @@
> Cannot set prototype of `$state` object > 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 ## state_unsafe_mutation
> Updating state inside a derived is forbidden. If the value should not be reactive, declare it without `$state` > 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 ## 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 ## derived_invalid_export
@ -152,7 +152,7 @@
## store_invalid_subscription ## store_invalid_subscription
> Cannot reference store value inside `<script context="module">` > Cannot reference store value inside `<script module>`
## store_invalid_subscription_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 ## 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 ## script_invalid_context
> If the context attribute is supplied, its value must be "module" > 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 ## slot_attribute_duplicate
> Duplicate slot name '%name%' in <%component%> > 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. 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 ## slot_element_deprecated
> Using `<slot>` to render parent content is deprecated. Use `{@render ...}` tags instead > Using `<slot>` to render parent content is deprecated. Use `{@render ...}` tags instead

@ -2,7 +2,7 @@
"name": "svelte", "name": "svelte",
"description": "Cybernetically enhanced web apps", "description": "Cybernetically enhanced web apps",
"license": "MIT", "license": "MIT",
"version": "5.0.0-next.228", "version": "5.0.0-next.237",
"type": "module", "type": "module",
"types": "./types/index.d.ts", "types": "./types/index.d.ts",
"engines": { "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 * @param {null | number | NodeLike} node
* @returns {never} * @returns {never}
*/ */
export function declaration_duplicate_module_import(node) { 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 * @param {null | number | NodeLike} node
* @returns {never} * @returns {never}
*/ */
export function store_invalid_subscription(node) { 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 * @param {null | number | NodeLike} node
* @returns {never} * @returns {never}
*/ */
export function script_duplicate(node) { 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\""); 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%> * Duplicate slot name '%name%' in <%component%>
* @param {null | number | NodeLike} node * @param {null | number | NodeLike} node

@ -106,7 +106,7 @@ export function compileModule(source, options) {
*/ */
export function parse(source, { filename, rootDir, modern } = {}) { export function parse(source, { filename, rootDir, modern } = {}) {
state.reset_warning_filter(() => false); 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); const ast = _parse(source);
return to_public_ast(source, ast, modern); return to_public_ast(source, ast, modern);

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

@ -4,32 +4,14 @@
import * as acorn from '../acorn.js'; import * as acorn from '../acorn.js';
import { regex_not_newline_characters } from '../../patterns.js'; import { regex_not_newline_characters } from '../../patterns.js';
import * as e from '../../../errors.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_closing_script_tag = /<\/script\s*>/;
const regex_starts_with_closing_script_tag = /^<\/script\s*>/; const regex_starts_with_closing_script_tag = /^<\/script\s*>/;
/** const RESERVED_ATTRIBUTES = ['server', 'client', 'worker', 'test', 'default'];
* @param {any[]} attributes const ALLOWED_ATTRIBUTES = ['context', 'generics', 'lang', 'module'];
* @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;
}
/** /**
* @param {Parser} parser * @param {Parser} parser
@ -60,14 +42,52 @@ export function read_script(parser, start, attributes) {
// TODO is this necessary? // TODO is this necessary?
ast.start = script_start; 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 { return {
type: 'Script', type: 'Script',
start, start,
end: parser.index, end: parser.index,
context: get_context(attributes), context,
content: ast, content: ast,
parent: null, parent: null,
// @ts-ignore // @ts-ignore
attributes: attributes attributes
}; };
} }

@ -243,7 +243,7 @@ export function analyze_module(ast, options) {
return { return {
module: { ast, scope, scopes }, module: { ast, scope, scopes },
name: options.filename || 'module', name: options.filename,
accessors: false, accessors: false,
runes: true, runes: true,
immutable: true immutable: true
@ -330,7 +330,7 @@ export function analyze_component(root, source, options) {
if (module.ast) { if (module.ast) {
for (const { node, path } of references) { 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 ( if (
/** @type {number} */ (node.start) > /** @type {number} */ (module.ast.start) && /** @type {number} */ (node.start) > /** @type {number} */ (module.ast.start) &&
/** @type {number} */ (node.end) < /** @type {number} */ (module.ast.end) && /** @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); 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 hash: root.css
? options.cssHash({ ? options.cssHash({
css: root.css.content.styles, css: root.css.content.styles,
filename: options.filename ?? '<unknown>', filename: options.filename,
name: component_name, name: component_name,
hash hash
}) })

@ -7,10 +7,11 @@ import {
} from '../../../../html-tree-validation.js'; } from '../../../../html-tree-validation.js';
import * as e from '../../../errors.js'; import * as e from '../../../errors.js';
import * as w from '../../../warnings.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 { regex_starts_with_newline } from '../../patterns.js';
import { check_element } from './shared/a11y.js'; import { check_element } from './shared/a11y.js';
import { validate_element } from './shared/element.js'; import { validate_element } from './shared/element.js';
import { mark_subtree_dynamic } from './shared/fragment.js';
/** /**
* @param {RegularElement} node * @param {RegularElement} node
@ -88,6 +89,11 @@ export function RegularElement(node, context) {
node.metadata.svg = is_svg(node.name); node.metadata.svg = is_svg(node.name);
node.metadata.mathml = is_mathml(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) { if (context.state.parent_element) {
let past_parent = false; let past_parent = false;
let only_warn = false; let only_warn = false;

@ -505,14 +505,12 @@ export function client_component(analysis, options) {
} }
if (dev) { if (dev) {
if (filename) {
// add `App[$.FILENAME] = 'App.svelte'` so that we can print useful messages later // add `App[$.FILENAME] = 'App.svelte'` so that we can print useful messages later
body.unshift( body.unshift(
b.stmt( b.stmt(
b.assignment('=', b.member(b.id(analysis.name), '$.FILENAME', true), b.literal(filename)) 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.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)))); 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); 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) { export function AwaitBlock(node, context) {
context.state.template.push('<!>'); 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 then_block;
let catch_block; let catch_block;
@ -45,7 +48,7 @@ export function AwaitBlock(node, context) {
b.call( b.call(
'$.await', '$.await',
context.state.node, context.state.node,
b.thunk(/** @type {Expression} */ (context.visit(node.expression))), expression,
node.pending node.pending
? b.arrow([b.id('$$anchor')], /** @type {BlockStatement} */ (context.visit(node.pending))) ? b.arrow([b.id('$$anchor')], /** @type {BlockStatement} */ (context.visit(node.pending)))
: b.literal(null), : b.literal(null),

@ -13,15 +13,6 @@ export function MemberExpression(node, context) {
if (field) { if (field) {
return context.state.in_constructor ? b.member(node, 'v') : b.call('$.get', node); 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(); context.next();

@ -42,11 +42,17 @@ export function Program(_, context) {
for (const [name, binding] of context.state.scope.declarations) { for (const [name, binding] of context.state.scope.declarations) {
if (binding.kind === 'store_sub') { 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] = { context.state.transform[name] = {
read: b.call, read: b.call,
assign: (_, value) => b.call('$.store_set', store, value), assign: (_, value) => b.call('$.store_set', get_store(), value),
mutate: (node, mutation) => { mutate: (node, mutation) => {
// We need to untrack the store read, for consistency with Svelte 4 // We need to untrack the store read, for consistency with Svelte 4
const untracked = b.call('$.untrack', node); const untracked = b.call('$.untrack', node);
@ -70,7 +76,7 @@ export function Program(_, context) {
return b.call( return b.call(
'$.store_mutate', '$.store_mutate',
store, get_store(),
b.assignment( b.assignment(
mutation.operator, mutation.operator,
/** @type {MemberExpression} */ ( /** @type {MemberExpression} */ (

@ -173,7 +173,7 @@ export function VariableDeclaration(node, context) {
let id; let id;
let rhs = value; let rhs = value;
if (init.arguments[0].type === 'Identifier') { if (rune === '$derived' && init.arguments[0].type === 'Identifier') {
id = init.arguments[0]; id = init.arguments[0];
} else { } else {
id = b.id(context.state.scope.generate('$$d')); 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 {VariableDeclarator} declarator
* @param {Scope} scope * @param {Scope} scope
* @param {Expression} value * @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.thunk(/** @type {Expression} */ (context.visit(node.expression))),
b.arrow( b.arrow(
[b.id('$$anchor'), b.id(component_name)], [b.id('$$anchor'), b.id(component_name)],
b.block([ b.block([...binding_initializers, b.stmt(prev(b.id('$$anchor')))])
...binding_initializers,
b.stmt(
dev
? b.call('$.validate_dynamic_component', b.thunk(prev(b.id('$$anchor'))))
: prev(b.id('$$anchor'))
)
])
) )
); );
}; };

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

@ -1,6 +1,6 @@
/** @import { Expression } from 'estree' */ /** @import { Expression } from 'estree' */
/** @import { ExpressionTag, SvelteNode, Text } from '#compiler' */ /** @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 { is_event_attribute, is_text_attribute } from '../../../../../utils/ast.js';
import * as b from '../../../../../utils/builders.js'; import * as b from '../../../../../utils/builders.js';
import { build_template_literal, build_update } from './utils.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 // traverse to the last (n - 1) one when hydrating
if (skipped > 1 && !needs_reset) { if (skipped > 1 && !needs_reset) {
skipped -= 1; 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) ? server_module(analysis, options)
: client_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) { if (program.body.length > 0) {
program.body[0].leadingComments = [ program.body[0].leadingComments = [
{ {

@ -356,7 +356,7 @@ export function server_component(analysis, options) {
body.push(b.export_default(component_function)); 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 // add `App[$.FILENAME] = 'App.svelte'` so that we can print useful messages later
body.unshift( body.unshift(
b.stmt( b.stmt(

@ -461,8 +461,20 @@ export function create_scopes(ast, root, allow_reactive_declarations, parent) {
ForStatement: create_block_scope, ForStatement: create_block_scope,
ForInStatement: create_block_scope, ForInStatement: create_block_scope,
ForOfStatement: create_block_scope, ForOfStatement: create_block_scope,
BlockStatement: create_block_scope,
SwitchStatement: 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 }) { ClassDeclaration(node, { state, next }) {
if (node.id) state.scope.declare(node.id, 'normal', 'let', node); if (node.id) state.scope.declare(node.id, 'normal', 'let', node);

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

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

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

@ -393,7 +393,7 @@ export function parse_attached_sourcemap(processed, tag_name) {
*/ */
export function merge_with_preprocessor_map(result, options, source_name) { export function merge_with_preprocessor_map(result, options, source_name) {
if (options.sourcemap) { 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 // 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 // map may contain a different file name. Patch our map beforehand to align sources so merging
// with the preprocessor map works correctly. // 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 | undefined} output_filename
* @param {string} fallback * @param {string} fallback
*/ */
export function get_source_name(filename, output_filename, 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); return output_filename ? get_relative_path(output_filename, filename) : get_basename(filename);
} }

@ -9,7 +9,7 @@ import * as w from './warnings.js';
*/ */
const common = { const common = {
filename: string(undefined), filename: string('(unknown)'),
// default to process.cwd() where it exists to replicate svelte4 behavior // 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 // 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", "element_invalid_self_closing_tag",
"event_directive_deprecated", "event_directive_deprecated",
"node_invalid_placement_ssr", "node_invalid_placement_ssr",
"script_context_deprecated",
"script_unknown_attribute",
"slot_element_deprecated", "slot_element_deprecated",
"svelte_component_deprecated", "svelte_component_deprecated",
"svelte_element_invalid_this" "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`); 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 * Using `<slot>` to render parent content is deprecated. Use `{@render ...}` tags instead
* @param {null | NodeLike} node * @param {null | NodeLike} node

@ -17,6 +17,8 @@ export interface ComponentConstructorOptions<
context?: Map<any, any>; context?: Map<any, any>;
hydrate?: boolean; hydrate?: boolean;
intro?: boolean; intro?: boolean;
recover?: boolean;
sync?: boolean;
$$inline?: 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 // rest parameter type, which is not supported. If rest parameters are added
// in the future, the condition can be removed. // in the future, the condition can be removed.
...args: number extends Parameters['length'] ? never : Parameters ...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 { 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 * @template V
* @param {Array<V>} array * @param {Array<V>} array
* @param {EachState} state * @param {EachState} state

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

@ -310,11 +310,7 @@ export function apply(
handler.apply(element, args); handler.apply(element, args);
} else if (has_side_effects || handler != null) { } else if (has_side_effects || handler != null) {
const filename = component?.[FILENAME]; const filename = component?.[FILENAME];
const location = filename const location = loc ? ` at ${filename}:${loc[0]}:${loc[1]}` : ` in ${filename}`;
? loc
? ` at ${filename}:${loc[0]}:${loc[1]}`
: ` in ${filename}`
: '';
const event_name = args[0].type; const event_name = args[0].type;
const description = `\`${event_name}\` handler${location}`; 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) { 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` * Updating state inside a derived is forbidden. If the value should not be reactive, declare it without `$state`
* @returns {never} * @returns {never}

@ -127,6 +127,7 @@ export {
export { set_text } from './render.js'; export { set_text } from './render.js';
export { export {
get, get,
safe_get,
invalidate_inner_signals, invalidate_inner_signals,
flush_sync, flush_sync,
tick, tick,
@ -143,12 +144,7 @@ export {
setContext, setContext,
hasContext hasContext
} from './runtime.js'; } from './runtime.js';
export { export { validate_binding, validate_each_keys, validate_prop_bindings } from './validate.js';
validate_binding,
validate_dynamic_component,
validate_each_keys,
validate_prop_bindings
} from './validate.js';
export { raf } from './timing.js'; export { raf } from './timing.js';
export { proxy } from './proxy.js'; export { proxy } from './proxy.js';
export { create_custom_element } from './dom/elements/custom-element.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 // 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)) { 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); sources.set(prop, s);
} }
@ -170,7 +170,7 @@ export function proxy(value, parent = null, prev) {
(current_effect !== null && (!has || get_descriptor(target, prop)?.writable)) (current_effect !== null && (!has || get_descriptor(target, prop)?.writable))
) { ) {
if (s === undefined) { if (s === undefined) {
s = source(has ? proxy(target[prop], metadata) : UNINITIALIZED); s = source(has ? proxy(target[prop], metadata) : UNINITIALIZED, null);
sources.set(prop, s); sources.set(prop, s);
} }

@ -130,7 +130,7 @@ const legacy_rest_props_handler = {
}, },
deleteProperty(target, key) { deleteProperty(target, key) {
// Svelte 4 allowed for deletions on $$restProps // 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); target.exclude.push(key);
update(target.version); update(target.version);
return true; 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 { DEV } from 'esm-env';
import { import {
current_component_context, current_component_context,
@ -13,7 +13,9 @@ import {
set_signal_status, set_signal_status,
untrack, untrack,
increment_version, increment_version,
update_effect update_effect,
derived_sources,
set_derived_sources
} from '../runtime.js'; } from '../runtime.js';
import { equals, safe_equals } from './equality.js'; import { equals, safe_equals } from './equality.js';
import { import {
@ -32,27 +34,39 @@ let inspect_effects = new Set();
/** /**
* @template V * @template V
* @param {V} v * @param {V} v
* @param {Reaction | null} [owner]
* @returns {Source<V>} * @returns {Source<V>}
*/ */
/*#__NO_SIDE_EFFECTS__*/ /*#__NO_SIDE_EFFECTS__*/
export function source(v) { export function source(v, owner = current_reaction) {
return { var source = {
f: 0, // TODO ideally we could skip this altogether, but it causes type errors f: 0, // TODO ideally we could skip this altogether, but it causes type errors
v, v,
reactions: null, reactions: null,
equals, equals,
version: 0 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 * @template V
* @param {V} initial_value * @param {V} initial_value
* @param {Reaction | null} [owner]
* @returns {Source<V>} * @returns {Source<V>}
*/ */
/*#__NO_SIDE_EFFECTS__*/ /*#__NO_SIDE_EFFECTS__*/
export function mutable_source(initial_value) { export function mutable_source(initial_value, owner) {
const s = source(initial_value); const s = source(initial_value, owner);
s.equals = safe_equals; s.equals = safe_equals;
// bind the signal to the component context, in case we need to // bind the signal to the component context, in case we need to
@ -84,7 +98,14 @@ export function mutate(source, value) {
* @returns {V} * @returns {V}
*/ */
export function set(source, value) { 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(); e.state_unsafe_mutation();
} }
@ -145,8 +166,8 @@ function mark_reactions(signal, status) {
var reaction = reactions[i]; var reaction = reactions[i];
var flags = reaction.f; var flags = reaction.f;
// If a reaction is already dirty, skip it (but always mark unowned deriveds) // Skip any effects that are already dirty
if ((flags & (CLEAN | UNOWNED)) === 0) continue; if ((flags & DIRTY) !== 0) continue;
// In legacy mode, skip the current effect to prevent infinite loops // In legacy mode, skip the current effect to prevent infinite loops
if (!runes && reaction === current_effect) continue; 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) { export function store_get(store, store_name, stores) {
const entry = (stores[store_name] ??= { const entry = (stores[store_name] ??= {
store: null, store: null,
source: mutable_source(undefined), source: mutable_source(undefined, null),
unsubscribe: noop unsubscribe: noop
}); });

@ -80,6 +80,20 @@ export function set_current_effect(effect) {
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 of the reaction that is currently being executed. In many cases,
* the dependencies are unchanged between runs, and so this will be `null` unless * the dependencies are unchanged between runs, and so this will be `null` unless
@ -230,6 +244,7 @@ function handle_error(error, effect, component_context) {
let current_context = component_context; let current_context = component_context;
while (current_context !== null) { while (current_context !== null) {
if (DEV) {
/** @type {string} */ /** @type {string} */
var filename = current_context.function?.[FILENAME]; var filename = current_context.function?.[FILENAME];
@ -237,6 +252,7 @@ function handle_error(error, effect, component_context) {
const file = filename.split('/').pop(); const file = filename.split('/').pop();
component_stack.push(file); component_stack.push(file);
} }
}
current_context = current_context.p; current_context = current_context.p;
} }
@ -279,12 +295,14 @@ export function update_reaction(reaction) {
var previous_untracked_writes = current_untracked_writes; var previous_untracked_writes = current_untracked_writes;
var previous_reaction = current_reaction; var previous_reaction = current_reaction;
var previous_skip_reaction = current_skip_reaction; var previous_skip_reaction = current_skip_reaction;
var prev_derived_sources = derived_sources;
new_deps = /** @type {null | Value[]} */ (null); new_deps = /** @type {null | Value[]} */ (null);
skipped_deps = 0; skipped_deps = 0;
current_untracked_writes = null; current_untracked_writes = null;
current_reaction = (reaction.f & (BRANCH_EFFECT | ROOT_EFFECT)) === 0 ? reaction : null; current_reaction = (reaction.f & (BRANCH_EFFECT | ROOT_EFFECT)) === 0 ? reaction : null;
current_skip_reaction = !is_flushing_effect && (reaction.f & UNOWNED) !== 0; current_skip_reaction = !is_flushing_effect && (reaction.f & UNOWNED) !== 0;
derived_sources = null;
try { try {
var result = /** @type {Function} */ (0, reaction.fn)(); var result = /** @type {Function} */ (0, reaction.fn)();
@ -321,6 +339,7 @@ export function update_reaction(reaction) {
current_untracked_writes = previous_untracked_writes; current_untracked_writes = previous_untracked_writes;
current_reaction = previous_reaction; current_reaction = previous_reaction;
current_skip_reaction = previous_skip_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. // Register the dependency on the current reaction signal.
if (current_reaction !== null) { if (current_reaction !== null) {
if (derived_sources !== null && derived_sources.includes(signal)) {
e.state_unsafe_local_read();
}
var deps = current_reaction.deps; var deps = current_reaction.deps;
// If the signal is accessing the same dependencies in the same // If the signal is accessing the same dependencies in the same
@ -734,6 +756,16 @@ export function get(signal) {
return signal.v; 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, * Invokes a function and captures all signals that are read during the invocation,
* then invalidates them. * then invalidates them.

@ -1,11 +1,11 @@
/** @import { Raf } from '#client' */ /** @import { Raf } from '#client' */
import { noop } from '../shared/utils.js'; 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} */ /** @type {Raf} */
export const raf = { export const raf = {

@ -14,31 +14,6 @@ function is_void(tag) {
return void_element_names.test(tag) || tag.toLowerCase() === '!doctype'; 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 {() => any} collection
* @param {(item: any, index: number) => string} key_fn * @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; ran = true;
if (effect.deps === null) { if (effect.deps === null) {
var location = filename && `${filename}:${line}:${column}`; var location = `${filename}:${line}:${column}`;
w.binding_property_non_reactive(binding, location); w.binding_property_non_reactive(binding, location);
warned = true; warned = true;

@ -56,6 +56,9 @@ function clone(value, cloned, path, paths) {
const unwrapped = cloned.get(value); const unwrapped = cloned.get(value);
if (unwrapped !== undefined) return unwrapped; 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)) { if (is_array(value)) {
const copy = /** @type {Snapshot<any>} */ ([]); const copy = /** @type {Snapshot<any>} */ ([]);
cloned.set(value, copy); cloned.set(value, copy);

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

@ -222,7 +222,7 @@ export function is_dom_property(name) {
return DOM_PROPERTIES.includes(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 * Returns `true` if `name` is a passive event

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

@ -5,6 +5,6 @@ export default test({
code: 'state_invalid_export', code: 'state_invalid_export',
message: 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", "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({ export const object = $state({
ok: true ok: true
}); });

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

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

@ -1,4 +1,4 @@
<script context="module"> <script module>
// this should be fine (state rune is not treated as a store) // this should be fine (state rune is not treated as a store)
const state = $state(0); const state = $state(0);
// this is not // 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} /> <svelte:options customElement="my-custom-element" runes={true} />
<script context="module" lang="ts"> <script module lang="ts">
</script> </script>
<script lang="ts" generics="T extends { foo: number }"> <script lang="ts" generics="T extends { foo: number }">

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

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

@ -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,11 +1,11 @@
<script context="module"> <script module>
let value = 'Blub'; let value = 'Blub';
let count = 0; let count = 0;
const subscribers = new Set(); const subscribers = new Set();
export const model = { export const model = {
subscribe(fn) { subscribe(fn) {
subscribers.add(fn); subscribers.add(fn);
count ++; count++;
fn(value); fn(value);
return () => { return () => {
count--; count--;
@ -14,7 +14,7 @@
}, },
set(v) { set(v) {
value = v; value = v;
subscribers.forEach(fn => fn(v)); subscribers.forEach((fn) => fn(v));
}, },
getCount() { getCount() {
return count; return count;

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

@ -1,4 +1,4 @@
<script context="module"> <script module>
let set = new Set(['x']); let set = new Set(['x']);
</script> </script>

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