Merge remote-tracking branch 'upstream/main' into pnpm-version-9

pull/11637/head
paoloricciuti 2 years ago
commit 76b6eb3576

@ -0,0 +1,5 @@
---
'svelte': patch
---
chore: remove `handle_compile_error`

@ -0,0 +1,5 @@
---
"svelte": patch
---
fix: allow runelike writable as prop

@ -0,0 +1,5 @@
---
"svelte": patch
---
fix: allow comments after last selector in css

@ -0,0 +1,5 @@
---
"svelte": patch
---
fix: support `array.lastIndexOf` without second argument

@ -0,0 +1,5 @@
---
"svelte": patch
---
fix: deduplicate children prop and default slot

@ -0,0 +1,5 @@
---
'svelte': patch
---
fix: make messages more consistent

@ -0,0 +1,5 @@
---
'svelte': patch
---
breaking: disallow string literal values in `<svelte:element this="...">`

@ -0,0 +1,5 @@
---
"svelte": patch
---
feat: error on imports to `svelte/internal/*`

@ -0,0 +1,5 @@
---
"svelte": patch
---
fix: use coarse-grained updates for derived expressions passed to props in legacy mode

@ -0,0 +1,5 @@
---
"svelte": patch
---
fix: throw on invalid attribute expressions

@ -0,0 +1,5 @@
---
"svelte": patch
---
fix: use svg methods for updating svg attributes too

@ -0,0 +1,5 @@
---
'svelte': patch
---
fix: don't add scoping modifier to nesting selectors

@ -0,0 +1,5 @@
---
"svelte": patch
---
feat: introduce `rootDir` compiler option, make `filename` relative to it

@ -0,0 +1,5 @@
---
"svelte": patch
---
fix: rename `__svelte_meta.filename` to `__svelte_meta.file` to align with svelte 4

@ -0,0 +1,5 @@
---
"svelte": patch
---
fix: migrate derivations without semicolons

@ -0,0 +1,5 @@
---
"svelte": patch
---
chore: speedup hydration around input and select values

@ -0,0 +1,5 @@
---
"svelte": patch
---
fix: check for invalid bindings on window and document

@ -0,0 +1,5 @@
---
"svelte": patch
---
breaking: event handlers + bindings now yield effect updates

@ -0,0 +1,5 @@
---
"svelte": patch
---
fix: avoid state_referenced_locally warning within type annotations

@ -0,0 +1,5 @@
---
"svelte": patch
---
fix: robustify `bind:scrollX/Y` binding

@ -15,6 +15,7 @@
"afraid-moose-matter",
"angry-books-jam",
"angry-plums-punch",
"angry-wasps-help",
"beige-cobras-smoke",
"beige-flies-wash",
"beige-mirrors-listen",
@ -38,6 +39,7 @@
"brave-walls-flow",
"breezy-carrots-flash",
"breezy-rules-beg",
"bright-falcons-float",
"bright-peas-juggle",
"bright-snakes-sing",
"brown-geckos-fry",
@ -58,9 +60,11 @@
"chilly-snakes-scream",
"clean-eels-beg",
"clever-chefs-relate",
"clever-maps-travel",
"clever-rockets-burn",
"clever-sloths-push",
"cold-birds-own",
"cold-cheetahs-judge",
"cold-masks-learn",
"cool-actors-tan",
"cool-ants-leave",
@ -78,6 +82,7 @@
"curvy-flies-exercise",
"curvy-ties-shout",
"cyan-colts-raise",
"cyan-ducks-teach",
"cyan-flowers-destroy",
"cyan-spies-grin",
"cyan-squids-drive",
@ -95,8 +100,11 @@
"dull-mangos-wave",
"dull-pots-add",
"dull-roses-relate",
"dull-worms-relax",
"early-ads-tie",
"early-months-whisper",
"eight-carrots-hunt",
"eight-cougars-watch",
"eight-hornets-punch",
"eight-pianos-raise",
"eight-steaks-shout",
@ -142,12 +150,14 @@
"four-flies-hammer",
"four-mice-hammer",
"four-pugs-listen",
"fresh-beds-wash",
"fresh-dots-destroy",
"fresh-impalas-bow",
"fresh-weeks-trade",
"friendly-candles-relate",
"friendly-lies-camp",
"funny-ties-jump",
"funny-trees-cry",
"funny-wombats-argue",
"fuzzy-bags-camp",
"fuzzy-donuts-provide",
@ -175,6 +185,7 @@
"green-eggs-approve",
"green-fishes-lie",
"green-hounds-play",
"green-snails-tickle",
"green-tigers-judge",
"grumpy-avocados-fetch",
"grumpy-jars-sparkle",
@ -200,6 +211,7 @@
"hungry-tips-unite",
"hungry-trees-travel",
"itchy-beans-melt",
"itchy-beds-kneel",
"itchy-bulldogs-tan",
"itchy-eels-marry",
"itchy-kings-deliver",
@ -209,13 +221,17 @@
"khaki-cooks-develop",
"khaki-ligers-sing",
"khaki-mails-draw",
"khaki-mails-scream",
"khaki-monkeys-cry",
"khaki-moose-arrive",
"khaki-tomatoes-rule",
"kind-baboons-approve",
"kind-deers-lay",
"kind-doors-grin",
"kind-dots-sort",
"kind-eagles-join",
"kind-rings-flash",
"kind-snakes-drive",
"kind-spoons-return",
"large-clouds-carry",
"large-turkeys-deny",
@ -234,6 +250,7 @@
"light-humans-hang",
"light-penguins-invent",
"light-pens-watch",
"little-ligers-exist",
"little-pans-jog",
"long-buckets-lay",
"long-crews-return",
@ -255,6 +272,7 @@
"lucky-toes-begin",
"many-rockets-give",
"many-trees-fix",
"mean-jokes-exist",
"metal-clouds-raise",
"metal-lobsters-burn",
"mighty-cooks-scream",
@ -306,6 +324,7 @@
"pink-bikes-agree",
"pink-goats-promise",
"pink-mayflies-tie",
"plenty-elephants-fry",
"plenty-starfishes-dress",
"plenty-zoos-fix",
"polite-dolphins-care",
@ -315,8 +334,11 @@
"poor-hats-design",
"poor-seahorses-flash",
"popular-apes-bathe",
"popular-cameras-tie",
"popular-games-hug",
"popular-ligers-perform",
"popular-mangos-rest",
"popular-roses-teach",
"popular-walls-hunt",
"pretty-ties-help",
"proud-pets-hang",
@ -324,7 +346,9 @@
"purple-dragons-peel",
"quiet-apricots-dream",
"quiet-berries-end",
"quiet-berries-explode",
"quiet-camels-mate",
"quiet-cobras-smile",
"quiet-crabs-nail",
"quiet-timers-speak",
"rare-mirrors-act",
@ -383,10 +407,13 @@
"shy-fishes-drive",
"silent-apes-report",
"silent-hats-stare",
"silent-rabbits-join",
"silly-ducks-mix",
"silly-laws-happen",
"silly-lies-film",
"silly-ways-wash",
"silver-points-approve",
"six-bears-trade",
"six-boats-shave",
"sixty-items-crash",
"sixty-numbers-hope",
@ -417,6 +444,7 @@
"soft-tigers-wink",
"sour-bags-fail",
"sour-forks-stare",
"sour-jeans-collect",
"sour-rules-march",
"sour-weeks-fix",
"spicy-jeans-deliver",
@ -451,6 +479,7 @@
"tall-shrimps-worry",
"tall-tigers-wait",
"tame-cycles-kneel",
"tame-dots-battle",
"tame-spies-drum",
"tasty-cheetahs-appear",
"tasty-numbers-perform",
@ -468,6 +497,7 @@
"thick-shirts-deliver",
"thick-swans-type",
"thin-foxes-lick",
"thin-years-rhyme",
"thirty-flowers-sit",
"thirty-ghosts-fix",
"thirty-impalas-repair",
@ -488,6 +518,7 @@
"tiny-meals-deliver",
"tiny-moose-kiss",
"tough-radios-punch",
"twelve-beans-drive",
"twelve-dragons-join",
"twelve-onions-juggle",
"twelve-worms-jog",
@ -499,8 +530,10 @@
"unlucky-boxes-obey",
"unlucky-steaks-warn",
"unlucky-trees-lick",
"violet-mails-trade",
"violet-mugs-behave",
"violet-pigs-jam",
"warm-cherries-shake",
"weak-drinks-speak",
"weak-frogs-bow",
"weak-terms-destroy",
@ -518,6 +551,7 @@
"wise-dodos-tell",
"wise-donkeys-marry",
"wise-jobs-admire",
"wise-kids-wash",
"wise-radios-exercise",
"wise-ties-clap",
"witty-camels-warn",
@ -527,6 +561,7 @@
"witty-years-crash",
"yellow-pugs-raise",
"yellow-taxis-double",
"yellow-trees-juggle",
"young-ads-roll",
"young-masks-refuse"
]

@ -0,0 +1,5 @@
---
'svelte': patch
---
fix: more efficient spread attributes in SSR output

@ -0,0 +1,5 @@
---
"svelte": patch
---
fix: inline pointer events now correctly work in Chrome

@ -0,0 +1,5 @@
---
"svelte": patch
---
fix: ensure we clear down each block opening anchors from document

@ -0,0 +1,5 @@
---
"svelte": patch
---
fix: don't warn on link without href if aria-disabled

@ -0,0 +1,5 @@
---
'svelte': patch
---
fix: don't use console.trace inside dev warnings

@ -0,0 +1,5 @@
---
'svelte': patch
---
fix: only inject push/pop in SSR components when necessary

@ -0,0 +1,5 @@
---
"svelte": patch
---
fix: update value like attributes in a separate template_effect

@ -0,0 +1,5 @@
---
"svelte": patch
---
fix: improve handling of unowned derived signal

@ -0,0 +1,5 @@
---
'svelte': patch
---
feat: migrate `<svelte:element this="div">`

@ -0,0 +1,5 @@
---
"svelte": patch
---
fix: don't require warning codes to be separated by commas in non-runes mode

@ -0,0 +1,5 @@
---
'svelte': patch
---
feat: more information when hydration fails

@ -0,0 +1,5 @@
---
"svelte": patch
---
fix: replay load and error events on load during hydration

@ -0,0 +1,5 @@
---
'svelte': patch
---
chore: use `new CustomEvent` instead of deprecated `initCustomEvent`

1
.gitignore vendored

@ -16,7 +16,6 @@ coverage
.env.test
# build output
dist
.vercel
# OS-specific

@ -1,5 +1,107 @@
# svelte
## 5.0.0-next.141
### Patch Changes
- fix: throw on invalid attribute expressions ([#11736](https://github.com/sveltejs/svelte/pull/11736))
- fix: use svg methods for updating svg attributes too ([#11755](https://github.com/sveltejs/svelte/pull/11755))
- fix: don't warn on link without href if aria-disabled ([#11737](https://github.com/sveltejs/svelte/pull/11737))
- fix: don't use console.trace inside dev warnings ([#11744](https://github.com/sveltejs/svelte/pull/11744))
## 5.0.0-next.140
### Patch Changes
- breaking: event handlers + bindings now yield effect updates ([#11706](https://github.com/sveltejs/svelte/pull/11706))
## 5.0.0-next.139
### Patch Changes
- fix: ensure we clear down each block opening anchors from document ([#11740](https://github.com/sveltejs/svelte/pull/11740))
## 5.0.0-next.138
### Patch Changes
- fix: allow comments after last selector in css ([#11723](https://github.com/sveltejs/svelte/pull/11723))
- fix: don't add scoping modifier to nesting selectors ([#11713](https://github.com/sveltejs/svelte/pull/11713))
- chore: speedup hydration around input and select values ([#11717](https://github.com/sveltejs/svelte/pull/11717))
- fix: update value like attributes in a separate template_effect ([#11720](https://github.com/sveltejs/svelte/pull/11720))
- fix: improve handling of unowned derived signal ([#11712](https://github.com/sveltejs/svelte/pull/11712))
## 5.0.0-next.137
### Patch Changes
- fix: migrate derivations without semicolons ([#11704](https://github.com/sveltejs/svelte/pull/11704))
- fix: check for invalid bindings on window and document ([#11676](https://github.com/sveltejs/svelte/pull/11676))
- fix: more efficient spread attributes in SSR output ([#11660](https://github.com/sveltejs/svelte/pull/11660))
- fix: inline pointer events now correctly work in Chrome ([#11695](https://github.com/sveltejs/svelte/pull/11695))
- fix: don't require warning codes to be separated by commas in non-runes mode ([#11669](https://github.com/sveltejs/svelte/pull/11669))
## 5.0.0-next.136
### Patch Changes
- chore: remove `handle_compile_error` ([#11639](https://github.com/sveltejs/svelte/pull/11639))
- breaking: disallow string literal values in `<svelte:element this="...">` ([#11454](https://github.com/sveltejs/svelte/pull/11454))
- fix: use coarse-grained updates for derived expressions passed to props in legacy mode ([#11652](https://github.com/sveltejs/svelte/pull/11652))
- fix: robustify `bind:scrollX/Y` binding ([#11655](https://github.com/sveltejs/svelte/pull/11655))
- feat: migrate `<svelte:element this="div">` ([#11659](https://github.com/sveltejs/svelte/pull/11659))
- feat: more information when hydration fails ([#11649](https://github.com/sveltejs/svelte/pull/11649))
- fix: replay load and error events on load during hydration ([#11642](https://github.com/sveltejs/svelte/pull/11642))
## 5.0.0-next.135
### Patch Changes
- fix: make messages more consistent ([#11643](https://github.com/sveltejs/svelte/pull/11643))
- feat: introduce `rootDir` compiler option, make `filename` relative to it ([#11627](https://github.com/sveltejs/svelte/pull/11627))
- fix: rename `__svelte_meta.filename` to `__svelte_meta.file` to align with svelte 4 ([#11627](https://github.com/sveltejs/svelte/pull/11627))
- fix: avoid state_referenced_locally warning within type annotations ([#11638](https://github.com/sveltejs/svelte/pull/11638))
## 5.0.0-next.134
### Patch Changes
- chore: improve SSR invalid element error message ([#11585](https://github.com/sveltejs/svelte/pull/11585))
- fix: deduplicate children prop and default slot ([#10800](https://github.com/sveltejs/svelte/pull/10800))
- feat: error on imports to `svelte/internal/*` ([#11632](https://github.com/sveltejs/svelte/pull/11632))
- fix: better handle img loading attribute ([#11635](https://github.com/sveltejs/svelte/pull/11635))
- feat: add $state.is rune ([#11613](https://github.com/sveltejs/svelte/pull/11613))
- feat: provide $state warnings for accidental equality ([#11610](https://github.com/sveltejs/svelte/pull/11610))
- feat: error when snippet shadow a prop ([#11631](https://github.com/sveltejs/svelte/pull/11631))
- chore: use `new CustomEvent` instead of deprecated `initCustomEvent` ([#11629](https://github.com/sveltejs/svelte/pull/11629))
## 5.0.0-next.133
### Patch Changes

@ -36,13 +36,9 @@
> Maximum update depth exceeded. This can happen when a reactive block or effect repeatedly sets a new value. Svelte limits the number of nested updates to prevent infinite loops
## hydration_missing_marker_close
## hydration_failed
> Missing hydration closing marker
## hydration_missing_marker_open
> Missing hydration opening marker
> Failed to hydrate the application
## lifecycle_legacy_only

@ -6,6 +6,8 @@
> Hydration failed because the initial UI does not match what was rendered on the server
> Hydration failed because the initial UI does not match what was rendered on the server. The error occurred near %location%
## lifecycle_double_unmount
> Tried to unmount a component that was not mounted

@ -46,6 +46,10 @@
> `$host()` can only be used inside custom element component instances
## import_svelte_internal_forbidden
> Imports of `svelte/internal/*` are forbidden. It contains private runtime code which is subject to change without notice. If you're importing from `svelte/internal/*` to work around a limitation of Svelte, please open an issue at https://github.com/sveltejs/svelte and explain your use case
## legacy_export_invalid
> Cannot use `export let` in runes mode — use `$props()` instead

@ -46,6 +46,10 @@
> 'type' attribute must be a static text value if input uses two-way binding
## attribute_unquoted_sequence
> Attribute values containing `{...}` must be enclosed in quote marks, unless the value only contains the expression
## bind_invalid_expression
> Can only bind to an Identifier or MemberExpression
@ -174,7 +178,7 @@
## mixed_event_handler_syntaxes
> Mixing old (on:%name%) and new syntaxes for event handling is not allowed. Use only the on%name% syntax.
> Mixing old (on:%name%) and new syntaxes for event handling is not allowed. Use only the on%name% syntax
## node_invalid_placement
@ -230,7 +234,7 @@
## slot_snippet_conflict
> Cannot use `<slot>` syntax and `{@render ...}` tags in the same component. Migrate towards `{@render ...}` tags completely.
> Cannot use `<slot>` syntax and `{@render ...}` tags in the same component. Migrate towards `{@render ...}` tags completely
## snippet_conflict
@ -260,13 +264,9 @@
> `<svelte:component>` must have a 'this' attribute
## svelte_element_invalid_this
> Invalid element definition — must be an `{expression}`
## svelte_element_missing_this
> `<svelte:element>` must have a 'this' attribute
> `<svelte:element>` must have a 'this' attribute with a value
## svelte_fragment_invalid_attribute

@ -20,7 +20,7 @@
## a11y_click_events_have_key_events
> Visible, non-interactive elements with a click event must be accompanied by a keyboard event handler. Consider whether an interactive element such as `<button type="button">` or `<a>` might be more appropriate. See https://svelte.dev/docs/accessibility-warnings#a11y-click-events-have-key-events for more details.
> Visible, non-interactive elements with a click event must be accompanied by a keyboard event handler. Consider whether an interactive element such as `<button type="button">` or `<a>` might be more appropriate. See https://svelte.dev/docs/accessibility-warnings#a11y-click-events-have-key-events for more details
## a11y_distracting_elements
@ -40,7 +40,7 @@
## a11y_img_redundant_alt
> Screenreaders already announce `<img>` elements as an image.
> Screenreaders already announce `<img>` elements as an image
## a11y_incorrect_aria_attribute_type
@ -76,7 +76,7 @@
## a11y_interactive_supports_focus
> Elements with the '%role%' interactive role must have a tabindex value.
> Elements with the '%role%' interactive role must have a tabindex value
## a11y_invalid_attribute
@ -84,7 +84,7 @@
## a11y_label_has_associated_control
> A form label must be associated with a control.
> A form label must be associated with a control
## a11y_media_has_caption
@ -120,7 +120,7 @@
## a11y_no_noninteractive_element_interactions
> Non-interactive element `<%element%>` should not be assigned mouse or keyboard event listeners.
> Non-interactive element `<%element%>` should not be assigned mouse or keyboard event listeners
## a11y_no_noninteractive_element_to_interactive_role

@ -32,8 +32,12 @@
## event_directive_deprecated
> Using `on:%name%` to listen to the %name% event is deprecated. Use the event attribute `on%name%` instead.
> Using `on:%name%` to listen to the %name% event is deprecated. Use the event attribute `on%name%` instead
## 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
## svelte_element_invalid_this
> `this` should be an `{expression}`. Using a string attribute value will cause an error in future versions of Svelte

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

@ -163,6 +163,7 @@ function transform(name, dest) {
type: 'Literal',
value: text
};
prev_vars = vars;
continue;
}

@ -1,3 +1,5 @@
import * as state from './state.js';
/** @typedef {{ start?: number, end?: number }} NodeLike */
// interface is duplicated between here (used internally) and ./interfaces.js
@ -5,8 +7,7 @@
export class CompileError extends Error {
name = 'CompileError';
/** @type {import('#compiler').CompileError['filename']} */
filename = undefined;
filename = state.filename;
/** @type {import('#compiler').CompileError['position']} */
position = undefined;
@ -25,8 +26,14 @@ export class CompileError extends Error {
*/
constructor(code, message, position) {
super(message);
this.code = code;
this.position = position;
if (position) {
this.start = state.locator(position[0]);
this.end = state.locator(position[1]);
}
}
toString() {

@ -5,13 +5,11 @@ var normal = 'font-weight: normal';
/**
* MESSAGE
* @param {boolean} trace
* @param {string} PARAMETER
*/
export function CODE(trace, PARAMETER) {
export function CODE(PARAMETER) {
if (DEV) {
console.warn(`%c[svelte] ${'CODE'}\n%c${MESSAGE}`, bold, normal);
if (trace) console.trace('stack trace');
} else {
// TODO print a link to the documentation
console.warn('CODE');

@ -1,12 +1,13 @@
/* This file is generated by scripts/process-messages/index.js. Do not edit! */
import * as state from './state.js';
/** @typedef {{ start?: number, end?: number }} NodeLike */
// interface is duplicated between here (used internally) and ./interfaces.js
// (exposed publicly), and I'm not sure how to avoid that
export class CompileError extends Error {
name = 'CompileError';
/** @type {import('#compiler').CompileError['filename']} */
filename = undefined;
filename = state.filename;
/** @type {import('#compiler').CompileError['position']} */
position = undefined;
/** @type {import('#compiler').CompileError['start']} */
@ -24,6 +25,11 @@ export class CompileError extends Error {
super(message);
this.code = code;
this.position = position;
if (position) {
this.start = state.locator(position[0]);
this.end = state.locator(position[1]);
}
}
toString() {
@ -198,6 +204,15 @@ export function host_invalid_placement(node) {
e(node, "host_invalid_placement", "`$host()` can only be used inside custom element component instances");
}
/**
* Imports of `svelte/internal/*` are forbidden. It contains private runtime code which is subject to change without notice. If you're importing from `svelte/internal/*` to work around a limitation of Svelte, please open an issue at https://github.com/sveltejs/svelte and explain your use case
* @param {null | number | NodeLike} node
* @returns {never}
*/
export function import_svelte_internal_forbidden(node) {
e(node, "import_svelte_internal_forbidden", "Imports of `svelte/internal/*` are forbidden. It contains private runtime code which is subject to change without notice. If you're importing from `svelte/internal/*` to work around a limitation of Svelte, please open an issue at https://github.com/sveltejs/svelte and explain your use case");
}
/**
* Cannot use `export let` in runes mode use `$props()` instead
* @param {null | number | NodeLike} node
@ -622,6 +637,15 @@ export function attribute_invalid_type(node) {
e(node, "attribute_invalid_type", "'type' attribute must be a static text value if input uses two-way binding");
}
/**
* Attribute values containing `{...}` must be enclosed in quote marks, unless the value only contains the expression
* @param {null | number | NodeLike} node
* @returns {never}
*/
export function attribute_unquoted_sequence(node) {
e(node, "attribute_unquoted_sequence", "Attribute values containing `{...}` must be enclosed in quote marks, unless the value only contains the expression");
}
/**
* Can only bind to an Identifier or MemberExpression
* @param {null | number | NodeLike} node
@ -919,13 +943,13 @@ export function let_directive_invalid_placement(node) {
}
/**
* Mixing old (on:%name%) and new syntaxes for event handling is not allowed. Use only the on%name% syntax.
* Mixing old (on:%name%) and new syntaxes for event handling is not allowed. Use only the on%name% syntax
* @param {null | number | NodeLike} node
* @param {string} name
* @returns {never}
*/
export function mixed_event_handler_syntaxes(node, name) {
e(node, "mixed_event_handler_syntaxes", `Mixing old (on:${name}) and new syntaxes for event handling is not allowed. Use only the on${name} syntax.`);
e(node, "mixed_event_handler_syntaxes", `Mixing old (on:${name}) and new syntaxes for event handling is not allowed. Use only the on${name} syntax`);
}
/**
@ -1050,12 +1074,12 @@ export function slot_element_invalid_name_default(node) {
}
/**
* Cannot use `<slot>` syntax and `{@render ...}` tags in the same component. Migrate towards `{@render ...}` tags completely.
* Cannot use `<slot>` syntax and `{@render ...}` tags in the same component. Migrate towards `{@render ...}` tags completely
* @param {null | number | NodeLike} node
* @returns {never}
*/
export function slot_snippet_conflict(node) {
e(node, "slot_snippet_conflict", "Cannot use `<slot>` syntax and `{@render ...}` tags in the same component. Migrate towards `{@render ...}` tags completely.");
e(node, "slot_snippet_conflict", "Cannot use `<slot>` syntax and `{@render ...}` tags in the same component. Migrate towards `{@render ...}` tags completely");
}
/**
@ -1123,21 +1147,12 @@ export function svelte_component_missing_this(node) {
}
/**
* Invalid element definition must be an `{expression}`
* @param {null | number | NodeLike} node
* @returns {never}
*/
export function svelte_element_invalid_this(node) {
e(node, "svelte_element_invalid_this", "Invalid element definition — must be an `{expression}`");
}
/**
* `<svelte:element>` must have a 'this' attribute
* `<svelte:element>` must have a 'this' attribute with a value
* @param {null | number | NodeLike} node
* @returns {never}
*/
export function svelte_element_missing_this(node) {
e(node, "svelte_element_missing_this", "`<svelte:element>` must have a 'this' attribute");
e(node, "svelte_element_missing_this", "`<svelte:element>` must have a 'this' attribute with a value");
}
/**

@ -1,5 +1,4 @@
import { walk as zimmerframe_walk } from 'zimmerframe';
import { CompileError } from './errors.js';
import { convert } from './legacy.js';
import { parse as parse_acorn } from './phases/1-parse/acorn.js';
import { parse as _parse } from './phases/1-parse/index.js';
@ -19,41 +18,33 @@ export { default as preprocess } from './preprocess/index.js';
* @returns {import('#compiler').CompileResult}
*/
export function compile(source, options) {
try {
state.reset({ source, filename: options.filename });
const validated = validate_component_options(options, '');
let parsed = _parse(source);
const { customElement: customElementOptions, ...parsed_options } = parsed.options || {};
/** @type {import('#compiler').ValidatedCompileOptions} */
const combined_options = {
...validated,
...parsed_options,
customElementOptions
const validated = validate_component_options(options, '');
state.reset(source, validated);
let parsed = _parse(source);
const { customElement: customElementOptions, ...parsed_options } = parsed.options || {};
/** @type {import('#compiler').ValidatedCompileOptions} */
const combined_options = {
...validated,
...parsed_options,
customElementOptions
};
if (parsed.metadata.ts) {
parsed = {
...parsed,
fragment: parsed.fragment && remove_typescript_nodes(parsed.fragment),
instance: parsed.instance && remove_typescript_nodes(parsed.instance),
module: parsed.module && remove_typescript_nodes(parsed.module)
};
if (parsed.metadata.ts) {
parsed = {
...parsed,
fragment: parsed.fragment && remove_typescript_nodes(parsed.fragment),
instance: parsed.instance && remove_typescript_nodes(parsed.instance),
module: parsed.module && remove_typescript_nodes(parsed.module)
};
}
const analysis = analyze_component(parsed, source, combined_options);
const result = transform_component(analysis, source, combined_options);
result.ast = to_public_ast(source, parsed, options.modernAst);
return result;
} catch (e) {
if (e instanceof CompileError) {
handle_compile_error(e, options.filename, source);
}
throw e;
}
const analysis = analyze_component(parsed, source, combined_options);
const result = transform_component(analysis, source, combined_options);
result.ast = to_public_ast(source, parsed, options.modernAst);
return result;
}
/**
@ -65,39 +56,11 @@ export function compile(source, options) {
* @returns {import('#compiler').CompileResult}
*/
export function compileModule(source, options) {
try {
state.reset({ source, filename: options.filename });
const validated = validate_module_options(options, '');
const analysis = analyze_module(parse_acorn(source, false), validated);
const result = transform_module(analysis, source, validated);
return result;
} catch (e) {
if (e instanceof CompileError) {
handle_compile_error(e, options.filename, source);
}
throw e;
}
}
const validated = validate_module_options(options, '');
state.reset(source, validated);
/**
* @param {import('#compiler').CompileError} error
* @param {string | undefined} filename
* @param {string} source
*/
function handle_compile_error(error, filename, source) {
error.filename = filename;
if (error.position) {
const start = state.locator(error.position[0]);
const end = state.locator(error.position[1]);
error.start = start;
error.end = end;
}
throw error;
const analysis = analyze_module(parse_acorn(source, false), validated);
return transform_module(analysis, source, validated);
}
/**
@ -134,25 +97,14 @@ function handle_compile_error(error, filename, source) {
*
* https://svelte.dev/docs/svelte-compiler#svelte-parse
* @param {string} source
* @param {{ filename?: string; modern?: boolean }} [options]
* @param {{ filename?: string; rootDir?: string; modern?: boolean }} [options]
* @returns {import('#compiler').Root | import('./types/legacy-nodes.js').LegacyRoot}
*/
export function parse(source, options = {}) {
state.reset({ source, filename: options.filename });
/** @type {import('#compiler').Root} */
let ast;
try {
ast = _parse(source);
} catch (e) {
if (e instanceof CompileError) {
handle_compile_error(e, options.filename, source);
}
throw e;
}
export function parse(source, { filename, rootDir, modern } = {}) {
state.reset(source, { filename, rootDir }); // TODO it's weird to require filename/rootDir here. reconsider the API
return to_public_ast(source, ast, options.modern);
const ast = _parse(source);
return to_public_ast(source, ast, modern);
}
/**

@ -18,7 +18,7 @@ import { migrate_svelte_ignore } from '../utils/extract_svelte_ignore.js';
*/
export function migrate(source) {
try {
reset({ source, filename: 'migrate.svelte' });
reset(source, { filename: 'migrate.svelte' });
let parsed = parse(source);
@ -369,11 +369,15 @@ const instance_script = {
/** @type {number} */ (node.body.expression.right.start),
'$derived('
);
state.str.update(
/** @type {number} */ (node.body.expression.right.end),
/** @type {number} */ (node.end),
');'
);
if (node.body.expression.right.end !== node.end) {
state.str.update(
/** @type {number} */ (node.body.expression.right.end),
/** @type {number} */ (node.end),
');'
);
} else {
state.str.appendRight(/** @type {number} */ (node.end), ');');
}
return;
} else {
for (const binding of reassigned_bindings) {
@ -427,6 +431,26 @@ const template = {
next();
},
SvelteElement(node, { state, next }) {
if (node.tag.type === 'Literal') {
let is_static = true;
let a = /** @type {number} */ (node.tag.start);
let b = /** @type {number} */ (node.tag.end);
let quote_mark = state.str.original[a - 1];
while (state.str.original[--a] !== '=') {
if (state.str.original[a] === '{') {
is_static = false;
break;
}
}
if (is_static && state.str.original[b] === quote_mark) {
state.str.prependLeft(a + 1, '{');
state.str.appendRight(/** @type {number} */ (node.tag.end) + 1, '}');
}
}
handle_events(node, state);
next();
},

@ -139,7 +139,7 @@ function read_selector_list(parser, inside_pseudo_class = false) {
const end = parser.index;
parser.allow_whitespace();
allow_comment_or_whitespace(parser);
if (inside_pseudo_class ? parser.match(')') : parser.match('{')) {
return {
@ -324,7 +324,7 @@ function read_selector(parser, inside_pseudo_class = false) {
}
const index = parser.index;
parser.allow_whitespace();
allow_comment_or_whitespace(parser);
if (parser.match(',') || (inside_pseudo_class ? parser.match(')') : parser.match('{'))) {
// rewind, so we know whether to continue building the selector list

@ -53,6 +53,24 @@ const visitors = {
},
TSTypeAliasDeclaration() {
return b.empty;
},
TSTypeParameterDeclaration() {
return b.empty;
},
TSTypeParameterInstantiation() {
return b.empty;
},
TSEnumDeclaration() {
return b.empty;
},
Identifier(node) {
if (node.typeAnnotation) {
return {
...node,
typeAnnotation: null
};
}
return node;
}
};

@ -4,6 +4,7 @@ import { read_script } from '../read/script.js';
import read_style from '../read/style.js';
import { closing_tag_omitted, decode_character_references } from '../utils/html.js';
import * as e from '../../../errors.js';
import * as w from '../../../warnings.js';
import { create_fragment } from '../utils/create.js';
import { create_attribute } from '../../nodes.js';
@ -216,7 +217,7 @@ export default function tag(parser) {
while ((attribute = read(parser))) {
if (attribute.type === 'Attribute' || attribute.type === 'BindDirective') {
if (unique_names.includes(attribute.name)) {
e.attribute_duplicate(attribute.start);
e.attribute_duplicate(attribute);
// <svelte:element bind:this this=..> is allowed
} else if (attribute.name !== 'this') {
unique_names.push(attribute.name);
@ -262,14 +263,34 @@ export default function tag(parser) {
const definition = /** @type {import('#compiler').Attribute} */ (
element.attributes.splice(index, 1)[0]
);
if (definition.value === true || definition.value.length !== 1) {
e.svelte_element_invalid_this(definition.start);
if (definition.value === true) {
e.svelte_element_missing_this(definition);
}
const chunk = definition.value[0];
element.tag =
chunk.type === 'Text'
? { type: 'Literal', value: chunk.data, raw: `'${chunk.raw}'` }
: chunk.expression;
if (definition.value.length !== 1 || chunk.type !== 'ExpressionTag') {
chunk.type;
w.svelte_element_invalid_this(definition);
// note that this is wrong, in the case of e.g. `this="h{n}"` — it will result in `<h>`.
// it would be much better to just error here, but we are preserving the existing buggy
// Svelte 4 behaviour out of an overabundance of caution regarding breaking changes.
// TODO in 6.0, error
element.tag =
chunk.type === 'Text'
? {
type: 'Literal',
value: chunk.data,
raw: `'${chunk.raw}'`,
start: chunk.start,
end: chunk.end
}
: chunk.expression;
} else {
element.tag = chunk.expression;
}
}
if (is_top_level_script_or_style) {

@ -793,7 +793,9 @@ function check_element(node, state) {
if (
current_role === get_implicit_role(node.name, attribute_map) &&
// <ul role="list"> is ok because CSS list-style:none removes the semantics and this is a way to bring them back
!['ul', 'ol', 'li'].includes(node.name)
!['ul', 'ol', 'li'].includes(node.name) &&
// <a role="link" /> is ok because without href the a tag doesn't have a role of link
!(node.name === 'a' && !attribute_map.has('href'))
) {
w.a11y_no_redundant_roles(attribute, current_role);
}
@ -1023,7 +1025,8 @@ function check_element(node, state) {
} else if (!has_spread) {
const id_attribute = get_static_value(attribute_map.get('id'));
const name_attribute = get_static_value(attribute_map.get('name'));
if (!id_attribute && !name_attribute) {
const aria_disabled_attribute = get_static_value(attribute_map.get('aria-disabled'));
if (!id_attribute && !name_attribute && aria_disabled_attribute !== 'true') {
warn_missing_attribute(node, ['href']);
}
}

@ -285,7 +285,9 @@ export function analyze_component(root, source, options) {
!Runes.includes(/** @type {any} */ (name)) ||
(declaration !== null &&
// const state = $state(0) is valid
get_rune(declaration.initial, instance.scope) === null &&
(get_rune(declaration.initial, instance.scope) === null ||
// rune-line names received as props are valid too (but we have to protect against $props as store)
(store_name !== 'props' && get_rune(declaration.initial, instance.scope) === '$props')) &&
// allow `import { derived } from 'svelte/store'` in the same file as `const x = $derived(..)` because one is not a subscription to the other
!(
name === '$derived' &&
@ -1449,24 +1451,6 @@ const common_visitors = {
SvelteElement(node, context) {
context.state.analysis.elements.push(node);
// TODO why are we handling the `<svelte:element this="x" />` case? there is no
// reason for someone to use a static value with `<svelte:element>`
if (
context.state.options.namespace !== 'foreign' &&
node.tag.type === 'Literal' &&
typeof node.tag.value === 'string'
) {
if (SVGElements.includes(node.tag.value)) {
node.metadata.svg = true;
return;
}
if (MathMLElements.includes(node.tag.value)) {
node.metadata.mathml = true;
return;
}
}
for (const attribute of node.attributes) {
if (attribute.type === 'Attribute') {
if (attribute.name === 'xmlns' && is_text_attribute(attribute)) {

@ -8,7 +8,6 @@ import * as e from '../../errors.js';
import {
extract_identifiers,
get_parent,
is_event_attribute,
is_expression_attribute,
is_text_attribute,
object,
@ -34,6 +33,17 @@ import { Scope, get_rune } from '../scope.js';
import { merge } from '../visitors.js';
import { a11y_validators } from './a11y.js';
/** @param {import('#compiler').Attribute} attribute */
function validate_attribute(attribute) {
if (attribute.value === true || attribute.value.length === 1) return;
const is_quoted = attribute.value.at(-1)?.end !== attribute.end;
if (!is_quoted) {
e.attribute_unquoted_sequence(attribute);
}
}
/**
* @param {import('#compiler').Component | import('#compiler').SvelteComponent | import('#compiler').SvelteSelf} node
* @param {import('zimmerframe').Context<import('#compiler').SvelteNode, import('./types.js').AnalysisState>} context
@ -58,14 +68,18 @@ function validate_component(node, context) {
}
if (attribute.type === 'Attribute') {
if (context.state.analysis.runes && is_expression_attribute(attribute)) {
const expression = attribute.value[0].expression;
if (expression.type === 'SequenceExpression') {
let i = /** @type {number} */ (expression.start);
while (--i > 0) {
const char = context.state.analysis.source[i];
if (char === '(') break; // parenthesized sequence expressions are ok
if (char === '{') e.attribute_invalid_sequence_expression(expression);
if (context.state.analysis.runes) {
validate_attribute(attribute);
if (is_expression_attribute(attribute)) {
const expression = attribute.value[0].expression;
if (expression.type === 'SequenceExpression') {
let i = /** @type {number} */ (expression.start);
while (--i > 0) {
const char = context.state.analysis.source[i];
if (char === '(') break; // parenthesized sequence expressions are ok
if (char === '{') e.attribute_invalid_sequence_expression(expression);
}
}
}
}
@ -107,14 +121,18 @@ function validate_element(node, context) {
if (attribute.type === 'Attribute') {
const is_expression = is_expression_attribute(attribute);
if (context.state.analysis.runes && is_expression) {
const expression = attribute.value[0].expression;
if (expression.type === 'SequenceExpression') {
let i = /** @type {number} */ (expression.start);
while (--i > 0) {
const char = context.state.analysis.source[i];
if (char === '(') break; // parenthesized sequence expressions are ok
if (char === '{') e.attribute_invalid_sequence_expression(expression);
if (context.state.analysis.runes) {
validate_attribute(attribute);
if (is_expression) {
const expression = attribute.value[0].expression;
if (expression.type === 'SequenceExpression') {
let i = /** @type {number} */ (expression.start);
while (--i > 0) {
const char = context.state.analysis.source[i];
if (char === '(') break; // parenthesized sequence expressions are ok
if (char === '{') e.attribute_invalid_sequence_expression(expression);
}
}
}
}
@ -393,6 +411,23 @@ const validation = {
);
}
if (property.invalid_elements && property.invalid_elements.includes(parent.name)) {
const valid_bindings = Object.entries(binding_properties)
.filter(([_, binding_property]) => {
return (
binding_property.valid_elements?.includes(parent.name) ||
(!binding_property.valid_elements &&
!binding_property.invalid_elements?.includes(parent.name))
);
})
.map(([property_name]) => property_name);
e.bind_invalid_name(
node,
node.name,
`Possible bindings for <${parent.name}> are ${valid_bindings.join(', ')}`
);
}
if (parent.name === 'input' && node.name !== 'this') {
const type = /** @type {import('#compiler').Attribute | undefined} */ (
parent.attributes.find((a) => a.type === 'Attribute' && a.name === 'type')
@ -586,22 +621,6 @@ const validation = {
) {
e.render_tag_invalid_call_expression(node);
}
const is_inside_textarea = context.path.find((n) => {
return (
n.type === 'SvelteElement' &&
n.name === 'svelte:element' &&
n.tag.type === 'Literal' &&
n.tag.value === 'textarea'
);
});
if (is_inside_textarea) {
e.tag_invalid_placement(
node,
'inside <textarea> or <svelte:element this="textarea">',
'render'
);
}
},
IfBlock(node, context) {
validate_block_not_empty(node.consequent, context);
@ -905,6 +924,11 @@ function ensure_no_module_import_conflict(node, state) {
* @type {import('zimmerframe').Visitors<import('#compiler').SvelteNode, import('./types.js').AnalysisState>}
*/
export const validation_runes_js = {
ImportDeclaration(node) {
if (typeof node.source.value === 'string' && node.source.value.startsWith('svelte/internal')) {
e.import_svelte_internal_forbidden(node);
}
},
ExportSpecifier(node, { state }) {
validate_export(node, state.scope, node.local.name);
},
@ -1077,6 +1101,11 @@ function validate_assignment(node, argument, state) {
}
export const validation_runes = merge(validation, a11y_validators, {
ImportDeclaration(node) {
if (typeof node.source.value === 'string' && node.source.value.startsWith('svelte/internal')) {
e.import_svelte_internal_forbidden(node);
}
},
Identifier(node, { path, state }) {
let i = path.length;
let parent = /** @type {import('estree').Expression} */ (path[--i]);

@ -8,6 +8,7 @@ import { javascript_visitors_runes } from './visitors/javascript-runes.js';
import { javascript_visitors_legacy } from './visitors/javascript-legacy.js';
import { serialize_get_binding } from './utils.js';
import { render_stylesheet } from '../css/index.js';
import { filename } from '../../../state.js';
/**
* This function ensures visitor sets don't accidentally clobber each other
@ -471,14 +472,7 @@ export function client_component(source, analysis, options) {
}
if (options.dev) {
if (options.filename) {
let filename = options.filename;
if (/(\/|\w:)/.test(options.filename)) {
// filename is absolute — truncate it
const parts = filename.split(/[/\\]/);
filename = parts.length > 3 ? ['...', ...parts.slice(-3)].join('/') : filename;
}
if (filename) {
// add `App.filename = 'App.svelte'` so that we can print useful messages later
body.unshift(
b.stmt(

@ -8,7 +8,12 @@ import {
} from '../../../../utils/ast.js';
import { binding_properties } from '../../../bindings.js';
import { clean_nodes, determine_namespace_for_children, infer_namespace } from '../../utils.js';
import { DOMProperties, PassiveEvents, VoidElements } from '../../../constants.js';
import {
DOMProperties,
LoadErrorElements,
PassiveEvents,
VoidElements
} from '../../../constants.js';
import { is_custom_element_node, is_element_node } from '../../../nodes.js';
import * as b from '../../../../utils/builders.js';
import {
@ -467,7 +472,7 @@ function serialize_dynamic_element_attributes(attributes, context, element_id) {
function serialize_element_attribute_update_assignment(element, node_id, attribute, context) {
const state = context.state;
const name = get_attribute_name(element, attribute, context);
const is_svg = context.state.metadata.namespace === 'svg';
const is_svg = context.state.metadata.namespace === 'svg' || element.name === 'svg';
const is_mathml = context.state.metadata.namespace === 'mathml';
let [contains_call_expression, value] = serialize_attribute_value(attribute.value, context);
@ -501,6 +506,10 @@ function serialize_element_attribute_update_assignment(element, node_id, attribu
value
)
);
} else if (name === 'value') {
update = b.stmt(b.call('$.set_value', node_id, value));
} else if (name === 'checked') {
update = b.stmt(b.call('$.set_checked', node_id, value));
} else if (DOMProperties.includes(name)) {
update = b.stmt(b.assignment('=', b.member(node_id, b.id(name)), value));
} else {
@ -627,6 +636,15 @@ function collect_parent_each_blocks(context) {
);
}
/**
* Svelte legacy mode should use safe equals in most places, runes mode shouldn't
* @param {import('../types.js').ComponentClientTransformState} state
* @param {import('estree').Expression} arg
*/
function create_derived(state, arg) {
return b.call(state.analysis.runes ? '$.derived' : '$.derived_safe_equal', arg);
}
/**
* @param {import('#compiler').Component | import('#compiler').SvelteComponent | import('#compiler').SvelteSelf} node
* @param {string} component_name
@ -660,6 +678,13 @@ function serialize_inline_component(node, component_name, context) {
*/
let slot_scope_applies_to_itself = false;
/**
* Components may have a children prop and also have child nodes. In this case, we assume
* that the child component isn't using render tags yet and pass the slot as $$slots.default.
* We're not doing it for spread attributes, as this would result in too many false positives.
*/
let has_children_prop = false;
/**
* @param {import('estree').Property} prop
*/
@ -709,6 +734,10 @@ function serialize_inline_component(node, component_name, context) {
slot_scope_applies_to_itself = true;
}
if (attribute.name === 'children') {
has_children_prop = true;
}
const [, value] = serialize_attribute_value(attribute.value, context);
if (attribute.metadata.dynamic) {
@ -729,7 +758,7 @@ function serialize_inline_component(node, component_name, context) {
if (should_wrap_in_derived) {
const id = b.id(context.state.scope.generate(attribute.name));
context.state.init.push(b.var(id, b.call('$.derived', b.thunk(value))));
context.state.init.push(b.var(id, create_derived(context.state, b.thunk(value))));
arg = b.call('$.get', id);
}
@ -825,10 +854,13 @@ function serialize_inline_component(node, component_name, context) {
b.block([...(slot_name === 'default' && !slot_scope_applies_to_itself ? lets : []), ...body])
);
if (slot_name === 'default') {
if (slot_name === 'default' && !has_children_prop) {
push_prop(
b.init('children', context.state.options.dev ? b.call('$.wrap_snippet', slot_fn) : slot_fn)
);
// We additionally add the default slot as a boolean, so that the slot render function on the other
// side knows it should get the content to render from $$props.children
serialized_slots.push(b.init(slot_name, b.true));
} else {
serialized_slots.push(b.init(slot_name, slot_fn));
}
@ -1169,7 +1201,7 @@ function get_template_function(namespace, state) {
return namespace === 'svg'
? contains_script_tag
? '$.svg_template_with_script'
: '$.svg_template'
: '$.ns_template'
: namespace === 'mathml'
? '$.mathml_template'
: contains_script_tag
@ -1630,9 +1662,8 @@ function serialize_template_literal(values, visit, state) {
state.init.push(
b.const(
id,
b.call(
// In runes mode, we want things to be fine-grained - but not in legacy mode
state.analysis.runes ? '$.derived' : '$.derived_safe_equal',
create_derived(
state,
b.thunk(/** @type {import('estree').Expression} */ (visit(node.expression)))
)
)
@ -1682,9 +1713,8 @@ export const template_visitors = {
state.init.push(
b.const(
declaration.id,
b.call(
// In runes mode, we want things to be fine-grained - but not in legacy mode
state.analysis.runes ? '$.derived' : '$.derived_safe_equal',
create_derived(
state,
b.thunk(/** @type {import('estree').Expression} */ (visit(declaration.init)))
)
)
@ -1719,10 +1749,7 @@ export const template_visitors = {
])
);
state.init.push(
// In runes mode, we want things to be fine-grained - but not in legacy mode
b.const(tmp, b.call(state.analysis.runes ? '$.derived' : '$.derived_safe_equal', fn))
);
state.init.push(b.const(tmp, create_derived(state, fn)));
// we need to eagerly evaluate the expression in order to hit any
// 'Cannot access x before initialization' errors
@ -1890,6 +1917,7 @@ export const template_visitors = {
let is_content_editable = false;
let has_content_editable_binding = false;
let img_might_be_lazy = false;
let might_need_event_replaying = false;
if (is_custom_element) {
// cloneNode is faster, but it does not instantiate the underlying class of the
@ -1922,6 +1950,9 @@ export const template_visitors = {
attributes.push(attribute);
needs_input_reset = true;
needs_content_reset = true;
if (LoadErrorElements.includes(node.name)) {
might_need_event_replaying = true;
}
} else if (attribute.type === 'ClassDirective') {
class_directives.push(attribute);
} else if (attribute.type === 'StyleDirective') {
@ -1944,6 +1975,8 @@ export const template_visitors = {
) {
has_content_editable_binding = true;
}
} else if (attribute.type === 'UseDirective' && LoadErrorElements.includes(node.name)) {
might_need_event_replaying = true;
}
context.visit(attribute);
}
@ -1961,7 +1994,7 @@ export const template_visitors = {
child_metadata.bound_contenteditable = true;
}
if (needs_input_reset && (node.name === 'input' || node.name === 'select')) {
if (needs_input_reset && node.name === 'input') {
context.state.init.push(b.stmt(b.call('$.remove_input_attr_defaults', context.state.node)));
}
@ -1996,6 +2029,12 @@ export const template_visitors = {
} else {
for (const attribute of /** @type {import('#compiler').Attribute[]} */ (attributes)) {
if (is_event_attribute(attribute)) {
if (
(attribute.name === 'onload' || attribute.name === 'onerror') &&
LoadErrorElements.includes(node.name)
) {
might_need_event_replaying = true;
}
serialize_event_attribute(attribute, context);
continue;
}
@ -2044,6 +2083,10 @@ export const template_visitors = {
serialize_class_directives(class_directives, node_id, context, is_attributes_reactive);
serialize_style_directives(style_directives, node_id, context, is_attributes_reactive);
if (might_need_event_replaying) {
context.state.after_update.push(b.stmt(b.call('$.replay_events', node_id)));
}
context.state.template.push('>');
/** @type {import('../types.js').SourceLocation[]} */
@ -2960,12 +3003,7 @@ export const template_visitors = {
const name = node.expression === null ? node.name : node.expression.name;
return b.const(
name,
b.call(
// in legacy mode, sources can be mutated but they're not fine-grained.
// Using the safe-equal derived version ensures the slot is still updated
state.analysis.runes ? '$.derived' : '$.derived_safe_equal',
b.thunk(b.member(b.id('$$slotProps'), b.id(node.name)))
)
create_derived(state, b.thunk(b.member(b.id('$$slotProps'), b.id(node.name))))
);
}
},
@ -3057,7 +3095,7 @@ export const template_visitors = {
);
const expression = is_default
? b.member(b.id('$$props'), b.id('children'))
? b.call('$.default_slot', b.id('$$props'))
: b.member(b.member(b.id('$$props'), b.id('$$slots')), name, true, true);
const slot = b.call('$.slot', context.state.node, expression, props_expression, fallback);

@ -238,7 +238,7 @@ const visitors = {
}
}
if (relative_selector.selectors.every((s) => s.type === 'NestingSelector')) {
if (relative_selector.selectors.some((s) => s.type === 'NestingSelector')) {
continue;
}

@ -11,6 +11,7 @@ import * as b from '../../../utils/builders.js';
import is_reference from 'is-reference';
import {
ContentEditableBindings,
LoadErrorElements,
VoidElements,
WhitespaceInsensitiveAttributes
} from '../../constants.js';
@ -23,11 +24,17 @@ import {
import { create_attribute, is_custom_element_node, is_element_node } from '../../nodes.js';
import { binding_properties } from '../../bindings.js';
import { regex_starts_with_newline, regex_whitespaces_strict } from '../../patterns.js';
import { DOMBooleanAttributes, HYDRATION_END, HYDRATION_START } from '../../../../constants.js';
import {
DOMBooleanAttributes,
ELEMENT_IS_NAMESPACED,
ELEMENT_PRESERVE_ATTRIBUTE_CASE,
HYDRATION_END,
HYDRATION_START
} from '../../../../constants.js';
import { escape_html } from '../../../../escaping.js';
import { sanitize_template_string } from '../../../utils/sanitize_template_string.js';
import { BLOCK_CLOSE, BLOCK_CLOSE_ELSE } from '../../../../internal/server/hydration.js';
import { locator } from '../../../state.js';
import { filename, locator } from '../../../state.js';
export const block_open = t_string(`<!--${HYDRATION_START}-->`);
export const block_close = t_string(`<!--${HYDRATION_END}-->`);
@ -888,42 +895,29 @@ function serialize_element_spread_attributes(
class_directives,
context
) {
/** @type {import('estree').Expression[]} */
const values = [];
for (const attribute of attributes) {
if (attribute.type === 'Attribute') {
const name = get_attribute_name(element, attribute, context);
const value = serialize_attribute_value(
attribute.value,
context,
WhitespaceInsensitiveAttributes.includes(name)
);
values.push(b.object([b.prop('init', b.literal(name), value)]));
} else {
values.push(/** @type {import('estree').Expression} */ (context.visit(attribute)));
}
}
let classes;
let styles;
let flags = 0;
const lowercase_attributes =
element.metadata.svg ||
element.metadata.mathml ||
(element.type === 'RegularElement' && is_custom_element_node(element))
? b.false
: b.true;
if (class_directives.length > 0 || context.state.analysis.css.hash) {
const properties = class_directives.map((directive) =>
b.init(
directive.name,
directive.expression.type === 'Identifier' && directive.expression.name === directive.name
? b.id(directive.name)
: /** @type {import('estree').Expression} */ (context.visit(directive.expression))
)
);
const is_html = element.metadata.svg || element.metadata.mathml ? b.false : b.true;
if (context.state.analysis.css.hash) {
properties.unshift(b.init(context.state.analysis.css.hash, b.literal(true)));
}
/** @type {import('estree').Expression[]} */
const args = [
b.array(values),
lowercase_attributes,
is_html,
b.literal(context.state.analysis.css.hash)
];
classes = b.object(properties);
}
if (style_directives.length > 0 || class_directives.length > 0) {
const styles = style_directives.map((directive) =>
if (style_directives.length > 0) {
const properties = style_directives.map((directive) =>
b.init(
directive.name,
directive.value === true
@ -931,25 +925,33 @@ function serialize_element_spread_attributes(
: serialize_attribute_value(directive.value, context, true)
)
);
const expressions = class_directives.map((directive) =>
b.conditional(directive.expression, b.literal(directive.name), b.literal(''))
);
const classes = expressions.length
? b.call(
b.member(
b.call(b.member(b.array(expressions), b.id('filter')), b.id('Boolean')),
b.id('join')
),
b.literal(' ')
)
: b.literal('');
args.push(
b.object([
b.init('styles', styles.length === 0 ? b.literal(null) : b.object(styles)),
b.init('classes', classes)
])
);
styles = b.object(properties);
}
if (element.metadata.svg || element.metadata.mathml) {
flags |= ELEMENT_IS_NAMESPACED | ELEMENT_PRESERVE_ATTRIBUTE_CASE;
} else if (is_custom_element_node(element)) {
flags |= ELEMENT_PRESERVE_ATTRIBUTE_CASE;
}
const object = b.object(
attributes.map((attribute) => {
if (attribute.type === 'Attribute') {
const name = get_attribute_name(element, attribute, context);
const value = serialize_attribute_value(
attribute.value,
context,
WhitespaceInsensitiveAttributes.includes(name)
);
return b.prop('init', b.key(name), value);
}
return b.spread(/** @type {import('estree').Expression} */ (context.visit(attribute)));
})
);
const args = [object, classes, styles, flags ? b.literal(flags) : undefined];
context.state.template.push(t_expression(b.call('$.spread_attributes', ...args)));
}
@ -978,6 +980,13 @@ function serialize_inline_component(node, component_name, context) {
*/
let slot_scope_applies_to_itself = false;
/**
* Components may have a children prop and also have child nodes. In this case, we assume
* that the child component isn't using render tags yet and pass the slot as $$slots.default.
* We're not doing it for spread attributes, as this would result in too many false positives.
*/
let has_children_prop = false;
/**
* @param {import('estree').Property} prop
*/
@ -1006,6 +1015,10 @@ function serialize_inline_component(node, component_name, context) {
slot_scope_applies_to_itself = true;
}
if (attribute.name === 'children') {
has_children_prop = true;
}
const value = serialize_attribute_value(attribute.value, context, false, true);
push_prop(b.prop('init', b.key(attribute.name), value));
} else if (attribute.type === 'BindDirective' && attribute.name !== 'this') {
@ -1080,7 +1093,7 @@ function serialize_inline_component(node, component_name, context) {
b.block([...(slot_name === 'default' && !slot_scope_applies_to_itself ? lets : []), ...body])
);
if (slot_name === 'default') {
if (slot_name === 'default' && !has_children_prop) {
push_prop(
b.prop(
'init',
@ -1088,6 +1101,9 @@ function serialize_inline_component(node, component_name, context) {
context.state.options.dev ? b.call('$.add_snippet_symbol', slot_fn) : slot_fn
)
);
// We additionally add the default slot as a boolean, so that the slot render function on the other
// side knows it should get the content to render from $$props.children
serialized_slots.push(b.init('default', b.true));
} else {
const slot = b.prop('init', b.literal(slot_name), slot_fn);
serialized_slots.push(slot);
@ -1755,7 +1771,7 @@ const template_visitors = {
const lets = [];
/** @type {import('estree').Expression} */
let expression = b.member_id('$$props.children');
let expression = b.call('$.default_slot', b.id('$$props'));
for (const attribute of node.attributes) {
if (attribute.type === 'SpreadAttribute') {
@ -1831,24 +1847,41 @@ function serialize_element_attributes(node, context) {
// Use the index to keep the attributes order which is important for spreading
let class_attribute_idx = -1;
let style_attribute_idx = -1;
let events_to_capture = new Set();
for (const attribute of node.attributes) {
if (attribute.type === 'Attribute') {
if (attribute.name === 'value' && node.name === 'textarea') {
if (attribute.name === 'value') {
if (node.name === 'textarea') {
if (
attribute.value !== true &&
attribute.value[0].type === 'Text' &&
regex_starts_with_newline.test(attribute.value[0].data)
) {
// Two or more leading newlines are required to restore the leading newline immediately after `<textarea>`.
// see https://html.spec.whatwg.org/multipage/syntax.html#element-restrictions
// also see related code in analysis phase
attribute.value[0].data = '\n' + attribute.value[0].data;
}
content = {
escape: true,
expression: serialize_attribute_value(attribute.value, context)
};
} else if (node.name !== 'select') {
// omit value attribute for select elements, it's irrelevant for the initially selected value and has no
// effect on the selected value after the user interacts with the select element (the value _property_ does, but not the attribute)
attributes.push(attribute);
}
// omit event handlers except for special cases
} else if (is_event_attribute(attribute)) {
if (
attribute.value !== true &&
attribute.value[0].type === 'Text' &&
regex_starts_with_newline.test(attribute.value[0].data)
(attribute.name === 'onload' || attribute.name === 'onerror') &&
LoadErrorElements.includes(node.name)
) {
// Two or more leading newlines are required to restore the leading newline immediately after `<textarea>`.
// see https://html.spec.whatwg.org/multipage/syntax.html#element-restrictions
// also see related code in analysis phase
attribute.value[0].data = '\n' + attribute.value[0].data;
events_to_capture.add(attribute.name);
}
content = { escape: true, expression: serialize_attribute_value(attribute.value, context) };
// omit event handlers
} else if (!is_event_attribute(attribute)) {
} else {
if (attribute.name === 'class') {
class_attribute_idx = attributes.length;
} else if (attribute.name === 'style') {
@ -1946,6 +1979,15 @@ function serialize_element_attributes(node, context) {
} else if (attribute.type === 'SpreadAttribute') {
attributes.push(attribute);
has_spread = true;
if (LoadErrorElements.includes(node.name)) {
events_to_capture.add('onload');
events_to_capture.add('onerror');
}
} else if (attribute.type === 'UseDirective') {
if (LoadErrorElements.includes(node.name)) {
events_to_capture.add('onload');
events_to_capture.add('onerror');
}
} else if (attribute.type === 'ClassDirective') {
class_directives.push(attribute);
} else if (attribute.type === 'StyleDirective') {
@ -2028,6 +2070,12 @@ function serialize_element_attributes(node, context) {
}
}
if (events_to_capture.size !== 0) {
for (const event of events_to_capture) {
context.state.template.push(t_string(` ${event}="this.__e=event"`));
}
}
return content;
}
@ -2303,12 +2351,17 @@ export function server_component(analysis, options) {
if (options.dev) push_args.push(b.id(analysis.name));
const component_block = b.block([
b.stmt(b.call('$.push', ...push_args)),
.../** @type {import('estree').Statement[]} */ (instance.body),
.../** @type {import('estree').Statement[]} */ (template.body),
b.stmt(b.call('$.pop'))
.../** @type {import('estree').Statement[]} */ (template.body)
]);
let should_inject_context = analysis.needs_context || options.dev;
if (should_inject_context) {
component_block.body.unshift(b.stmt(b.call('$.push', ...push_args)));
component_block.body.push(b.stmt(b.call('$.pop')));
}
if (analysis.uses_rest_props) {
/** @type {string[]} */
const named_props = analysis.exports.map(({ name, alias }) => alias ?? name);
@ -2340,9 +2393,18 @@ export function server_component(analysis, options) {
const body = [...state.hoisted, ...module.body];
let should_inject_props =
should_inject_context ||
props.length > 0 ||
analysis.needs_props ||
analysis.uses_props ||
analysis.uses_rest_props ||
analysis.uses_slots ||
analysis.slot_names.size > 0;
const component_function = b.function_declaration(
b.id(analysis.name),
[b.id('$$payload'), b.id('$$props')],
should_inject_props ? [b.id('$$payload'), b.id('$$props')] : [b.id('$$payload')],
component_block
);
if (options.legacy.componentApi) {
@ -2398,14 +2460,7 @@ export function server_component(analysis, options) {
body.push(b.export_default(component_function));
}
if (options.dev && options.filename) {
let filename = options.filename;
if (/(\/|\w:)/.test(options.filename)) {
// filename is absolute — truncate it
const parts = filename.split(/[/\\]/);
filename = parts.length > 3 ? ['...', ...parts.slice(-3)].join('/') : filename;
}
if (options.dev && filename) {
// add `App.filename = 'App.svelte'` so that we can print useful messages later
body.unshift(
b.stmt(

@ -5,6 +5,7 @@
* @property {string} [type] Set this to `set` if updates are written to the dom property
* @property {boolean} [omit_in_ssr] Set this to true if the binding should not be included in SSR
* @property {string[]} [valid_elements] If this is set, the binding is only valid on the given elements
* @property {string[]} [invalid_elements] If this is set, the binding is invalid on the given elements
*/
/**
@ -131,28 +132,36 @@ export const binding_properties = {
},
// dimensions
clientWidth: {
omit_in_ssr: true
omit_in_ssr: true,
invalid_elements: ['svelte:window', 'svelte:document']
},
clientHeight: {
omit_in_ssr: true
omit_in_ssr: true,
invalid_elements: ['svelte:window', 'svelte:document']
},
offsetWidth: {
omit_in_ssr: true
omit_in_ssr: true,
invalid_elements: ['svelte:window', 'svelte:document']
},
offsetHeight: {
omit_in_ssr: true
omit_in_ssr: true,
invalid_elements: ['svelte:window', 'svelte:document']
},
contentRect: {
omit_in_ssr: true
omit_in_ssr: true,
invalid_elements: ['svelte:window', 'svelte:document']
},
contentBoxSize: {
omit_in_ssr: true
omit_in_ssr: true,
invalid_elements: ['svelte:window', 'svelte:document']
},
borderBoxSize: {
omit_in_ssr: true
omit_in_ssr: true,
invalid_elements: ['svelte:window', 'svelte:document']
},
devicePixelContentBoxSize: {
omit_in_ssr: true
omit_in_ssr: true,
invalid_elements: ['svelte:window', 'svelte:document']
},
// checkbox/radio
indeterminate: {
@ -171,9 +180,15 @@ export const binding_properties = {
this: {
omit_in_ssr: true
},
innerText: {},
innerHTML: {},
textContent: {},
innerText: {
invalid_elements: ['svelte:window', 'svelte:document']
},
innerHTML: {
invalid_elements: ['svelte:window', 'svelte:document']
},
textContent: {
invalid_elements: ['svelte:window', 'svelte:document']
},
open: {
event: 'toggle',
type: 'set',

@ -61,6 +61,18 @@ export const WhitespaceInsensitiveAttributes = ['class', 'style'];
export const ContentEditableBindings = ['textContent', 'innerHTML', 'innerText'];
export const LoadErrorElements = [
'body',
'embed',
'iframe',
'img',
'link',
'object',
'script',
'style',
'track'
];
export const SVGElements = [
'altGlyph',
'altGlyphDef',

@ -21,11 +21,11 @@ export function is_element_node(node) {
}
/**
* @param {import('#compiler').RegularElement} node
* @param {import('#compiler').RegularElement | import('#compiler').SvelteElement} node
* @returns {boolean}
*/
export function is_custom_element_node(node) {
return node.name.includes('-');
return node.type === 'RegularElement' && node.name.includes('-');
}
/**

@ -676,14 +676,7 @@ export function create_scopes(ast, root, allow_reactive_declarations, parent) {
TransitionDirective: SvelteDirective,
AnimateDirective: SvelteDirective,
UseDirective: SvelteDirective,
// @ts-ignore
TSTypeAnnotation: skip,
TSInterfaceDeclaration: skip,
TSTypeAliasDeclaration: skip,
TSTypeParameterDeclaration: skip,
TSEnumDeclaration: skip
UseDirective: SvelteDirective
// TODO others
});

@ -5,7 +5,11 @@ import { getLocator } from 'locate-character';
/** @type {import('#compiler').Warning[]} */
export let warnings = [];
/** @type {string | undefined} */
/**
* The filename (if specified in the compiler options) relative to the rootDir (if specified).
* This should not be used in the compiler output except in dev mode
* @type {string | undefined}
*/
export let filename;
export let locator = getLocator('', { offsetLine: 1 });
@ -26,14 +30,23 @@ export function pop_ignore() {
}
/**
* @param {{
* source: string;
* filename: string | undefined;
* }} options
* @param {string} source
* @param {{ filename?: string, rootDir?: string }} options
*/
export function reset(options) {
filename = options.filename;
locator = getLocator(options.source, { offsetLine: 1 });
export function reset(source, options) {
const root_dir = options.rootDir?.replace(/\\/g, '/');
filename = options.filename?.replace(/\\/g, '/');
if (
typeof filename === 'string' &&
typeof root_dir === 'string' &&
filename.startsWith(root_dir)
) {
// make filename relative to rootDir
filename = filename.replace(root_dir, '').replace(/^[/\\]/, '');
}
locator = getLocator(source, { offsetLine: 1 });
warnings = [];
ignore_stack = [];
}

@ -216,12 +216,22 @@ export interface ModuleCompileOptions {
* Used for debugging hints and sourcemaps. Your bundler plugin will set it automatically.
*/
filename?: string;
/**
* Used for ensuring filenames don't leak filesystem information. Your bundler plugin will set it automatically.
* @default process.cwd() on node-like environments, undefined elsewhere
*/
rootDir?: string;
}
// 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'> & {
export type ValidatedModuleCompileOptions = Omit<
Required<ModuleCompileOptions>,
'filename' | 'rootDir'
> & {
filename: ModuleCompileOptions['filename'];
rootDir: ModuleCompileOptions['rootDir'];
};
export type ValidatedCompileOptions = ValidatedModuleCompileOptions &

@ -31,16 +31,16 @@ export function extract_svelte_ignore(offset, text, runes) {
/** @type {string[]} */
const ignores = [];
// Warnings have to be separated by commas, everything after is interpreted as prose
for (const match of text.slice(length).matchAll(/([\w$-]+)(,)?/gm)) {
const code = match[1];
if (runes) {
// Warnings have to be separated by commas, everything after is interpreted as prose
for (const match of text.slice(length).matchAll(/([\w$-]+)(,)?/gm)) {
const code = match[1];
ignores.push(code);
if (w.codes.includes(code)) {
ignores.push(code);
} else {
const replacement = replacements[code] ?? code.replace(/-/g, '_');
if (!w.codes.includes(code)) {
const replacement = replacements[code] ?? code.replace(/-/g, '_');
if (runes) {
// The type cast is for some reason necessary to pass the type check in CI
const start = offset + /** @type {number} */ (match.index);
const end = start + code.length;
@ -51,13 +51,26 @@ export function extract_svelte_ignore(offset, text, runes) {
const suggestion = fuzzymatch(code, w.codes);
w.unknown_code({ start, end }, code, suggestion);
}
} else if (w.codes.includes(replacement)) {
ignores.push(replacement);
}
if (!match[2]) {
break;
}
}
} else {
// Non-runes mode: lax parsing, backwards compat with old codes
for (const match of text.slice(length).matchAll(/[\w$-]+/gm)) {
const code = match[0];
ignores.push(code);
if (!w.codes.includes(code)) {
const replacement = replacements[code] ?? code.replace(/-/g, '_');
if (!match[2]) {
break;
if (w.codes.includes(replacement)) {
ignores.push(replacement);
}
}
}
}
@ -76,8 +89,12 @@ export function migrate_svelte_ignore(text) {
const length = match[0].length;
return (
text.substring(0, length) +
text
.substring(length)
.replace(/\w+-\w+(-\w+)*/g, (code) => replacements[code] ?? code.replace(/-/g, '_'))
text.substring(length).replace(/\w+-\w+(-\w+)*/g, (code, _, idx) => {
let replacement = replacements[code] ?? code.replace(/-/g, '_');
if (/\w+-\w+/.test(text.substring(length + idx + code.length))) {
replacement += ',';
}
return replacement;
})
);
}

@ -10,6 +10,10 @@ import * as w from './warnings.js';
const common = {
filename: string(undefined),
// 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
rootDir: string(typeof process !== 'undefined' ? process.cwd?.() : undefined),
dev: boolean(false),
generate: validator('client', (input, keypath) => {

@ -90,7 +90,8 @@ export const codes = [
"component_name_lowercase",
"element_invalid_self_closing_tag",
"event_directive_deprecated",
"slot_element_deprecated"
"slot_element_deprecated",
"svelte_element_invalid_this"
];
/**
@ -137,11 +138,11 @@ export function a11y_autofocus(node) {
}
/**
* Visible, non-interactive elements with a click event must be accompanied by a keyboard event handler. Consider whether an interactive element such as `<button type="button">` or `<a>` might be more appropriate. See https://svelte.dev/docs/accessibility-warnings#a11y-click-events-have-key-events for more details.
* Visible, non-interactive elements with a click event must be accompanied by a keyboard event handler. Consider whether an interactive element such as `<button type="button">` or `<a>` might be more appropriate. See https://svelte.dev/docs/accessibility-warnings#a11y-click-events-have-key-events for more details
* @param {null | NodeLike} node
*/
export function a11y_click_events_have_key_events(node) {
w(node, "a11y_click_events_have_key_events", "Visible, non-interactive elements with a click event must be accompanied by a keyboard event handler. Consider whether an interactive element such as `<button type=\"button\">` or `<a>` might be more appropriate. See https://svelte.dev/docs/accessibility-warnings#a11y-click-events-have-key-events for more details.");
w(node, "a11y_click_events_have_key_events", "Visible, non-interactive elements with a click event must be accompanied by a keyboard event handler. Consider whether an interactive element such as `<button type=\"button\">` or `<a>` might be more appropriate. See https://svelte.dev/docs/accessibility-warnings#a11y-click-events-have-key-events for more details");
}
/**
@ -179,11 +180,11 @@ export function a11y_hidden(node, name) {
}
/**
* Screenreaders already announce `<img>` elements as an image.
* Screenreaders already announce `<img>` elements as an image
* @param {null | NodeLike} node
*/
export function a11y_img_redundant_alt(node) {
w(node, "a11y_img_redundant_alt", "Screenreaders already announce `<img>` elements as an image.");
w(node, "a11y_img_redundant_alt", "Screenreaders already announce `<img>` elements as an image");
}
/**
@ -262,12 +263,12 @@ export function a11y_incorrect_aria_attribute_type_tristate(node, attribute) {
}
/**
* Elements with the '%role%' interactive role must have a tabindex value.
* Elements with the '%role%' interactive role must have a tabindex value
* @param {null | NodeLike} node
* @param {string} role
*/
export function a11y_interactive_supports_focus(node, role) {
w(node, "a11y_interactive_supports_focus", `Elements with the '${role}' interactive role must have a tabindex value.`);
w(node, "a11y_interactive_supports_focus", `Elements with the '${role}' interactive role must have a tabindex value`);
}
/**
@ -281,11 +282,11 @@ export function a11y_invalid_attribute(node, href_value, href_attribute) {
}
/**
* A form label must be associated with a control.
* A form label must be associated with a control
* @param {null | NodeLike} node
*/
export function a11y_label_has_associated_control(node) {
w(node, "a11y_label_has_associated_control", "A form label must be associated with a control.");
w(node, "a11y_label_has_associated_control", "A form label must be associated with a control");
}
/**
@ -363,12 +364,12 @@ export function a11y_no_interactive_element_to_noninteractive_role(node, element
}
/**
* Non-interactive element `<%element%>` should not be assigned mouse or keyboard event listeners.
* Non-interactive element `<%element%>` should not be assigned mouse or keyboard event listeners
* @param {null | NodeLike} node
* @param {string} element
*/
export function a11y_no_noninteractive_element_interactions(node, element) {
w(node, "a11y_no_noninteractive_element_interactions", `Non-interactive element \`<${element}>\` should not be assigned mouse or keyboard event listeners.`);
w(node, "a11y_no_noninteractive_element_interactions", `Non-interactive element \`<${element}>\` should not be assigned mouse or keyboard event listeners`);
}
/**
@ -698,18 +699,26 @@ export function element_invalid_self_closing_tag(node, name) {
}
/**
* Using `on:%name%` to listen to the %name% event is deprecated. Use the event attribute `on%name%` instead.
* Using `on:%name%` to listen to the %name% event is deprecated. Use the event attribute `on%name%` instead
* @param {null | NodeLike} node
* @param {string} name
*/
export function event_directive_deprecated(node, name) {
w(node, "event_directive_deprecated", `Using \`on:${name}\` to listen to the ${name} event is deprecated. Use the event attribute \`on${name}\` instead.`);
w(node, "event_directive_deprecated", `Using \`on:${name}\` to listen to the ${name} event is deprecated. Use the event attribute \`on${name}\` instead`);
}
/**
* 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
*/
export function slot_element_deprecated(node) {
w(node, "slot_element_deprecated", "Using `<slot>` to render parent content is deprecated. Use `{@render ...}` tags instead.");
w(node, "slot_element_deprecated", "Using `<slot>` to render parent content is deprecated. Use `{@render ...}` tags instead");
}
/**
* `this` should be an `{expression}`. Using a string attribute value will cause an error in future versions of Svelte
* @param {null | NodeLike} node
*/
export function svelte_element_invalid_this(node) {
w(node, "svelte_element_invalid_this", "`this` should be an `{expression}`. Using a string attribute value will cause an error in future versions of Svelte");
}

@ -22,6 +22,10 @@ export const TEMPLATE_USE_IMPORT_NODE = 1 << 1;
export const HYDRATION_START = '[';
export const HYDRATION_END = ']';
export const HYDRATION_END_ELSE = `${HYDRATION_END}!`; // used to indicate that an `{:else}...` block was rendered
export const HYDRATION_ERROR = {};
export const ELEMENT_IS_NAMESPACED = 1;
export const ELEMENT_PRESERVE_ATTRIBUTE_CASE = 1 << 1;
export const UNINITIALIZED = Symbol();
@ -32,11 +36,12 @@ export const RawTextElements = ['textarea', 'script', 'style', 'title'];
export const DelegatedEvents = [
'beforeinput',
'click',
'change',
'dblclick',
'contextmenu',
'focusin',
'focusout',
// 'input', This conflicts with bind:input
'input',
'keydown',
'keyup',
'mousedown',

@ -1,6 +1,5 @@
import { HYDRATION_END, HYDRATION_START } from '../../../constants.js';
import { hydrating } from '../dom/hydration.js';
import { is_array } from '../utils.js';
/**
* @param {any} fn
@ -12,15 +11,8 @@ export function add_locations(fn, filename, locations) {
return (/** @type {any[]} */ ...args) => {
const dom = fn(...args);
const nodes = hydrating
? is_array(dom)
? dom
: [dom]
: dom.nodeType === 11
? Array.from(dom.childNodes)
: [dom];
assign_locations(nodes, filename, locations);
var node = hydrating ? dom : dom.nodeType === 11 ? dom.firstChild : dom;
assign_locations(node, filename, locations);
return dom;
};
@ -34,38 +26,34 @@ export function add_locations(fn, filename, locations) {
function assign_location(element, filename, location) {
// @ts-expect-error
element.__svelte_meta = {
loc: { filename, line: location[0], column: location[1] }
loc: { file: filename, line: location[0], column: location[1] }
};
if (location[2]) {
assign_locations(
/** @type {import('#client').TemplateNode[]} */ (Array.from(element.childNodes)),
filename,
location[2]
);
assign_locations(element.firstChild, filename, location[2]);
}
}
/**
* @param {import('#client').TemplateNode[]} nodes
* @param {Node | null} node
* @param {string} filename
* @param {import('../../../compiler/phases/3-transform/client/types.js').SourceLocation[]} locations
*/
function assign_locations(nodes, filename, locations) {
var j = 0;
function assign_locations(node, filename, locations) {
var i = 0;
var depth = 0;
for (var i = 0; i < nodes.length; i += 1) {
var node = nodes[i];
while (node && i < locations.length) {
if (hydrating && node.nodeType === 8) {
var comment = /** @type {Comment} */ (node);
if (comment.data === HYDRATION_START) depth += 1;
if (comment.data.startsWith(HYDRATION_END)) depth -= 1;
else if (comment.data[0] === HYDRATION_END) depth -= 1;
}
if (depth === 0 && node.nodeType === 1) {
assign_location(/** @type {Element} */ (node), filename, locations[j++]);
assign_location(/** @type {Element} */ (node), filename, locations[i++]);
}
node = node.nextSibling;
}
}

@ -3,6 +3,14 @@ import { get_proxied_value } from '../proxy.js';
export function init_array_prototype_warnings() {
const array_prototype = Array.prototype;
// The REPL ends up here over and over, and this prevents it from adding more and more patches
// of the same kind to the prototype, which would slow down everything over time.
// @ts-expect-error
const cleanup = Array.__svelte_cleanup;
if (cleanup) {
cleanup();
}
const { indexOf, lastIndexOf, includes } = array_prototype;
array_prototype.indexOf = function (item, from_index) {
@ -13,9 +21,6 @@ export function init_array_prototype_warnings() {
if (test !== -1) {
w.state_proxy_equality_mismatch('array.indexOf(...)');
// eslint-disable-next-line no-console
console.trace();
}
}
@ -23,16 +28,21 @@ export function init_array_prototype_warnings() {
};
array_prototype.lastIndexOf = function (item, from_index) {
const index = lastIndexOf.call(this, item, from_index);
// we need to specify this.length - 1 because it's probably using something like
// `arguments` inside so passing undefined is different from not passing anything
const index = lastIndexOf.call(this, item, from_index ?? this.length - 1);
if (index === -1) {
const test = lastIndexOf.call(get_proxied_value(this), get_proxied_value(item), from_index);
// we need to specify this.length - 1 because it's probably using something like
// `arguments` inside so passing undefined is different from not passing anything
const test = lastIndexOf.call(
get_proxied_value(this),
get_proxied_value(item),
from_index ?? this.length - 1
);
if (test !== -1) {
w.state_proxy_equality_mismatch('array.lastIndexOf(...)');
// eslint-disable-next-line no-console
console.trace();
}
}
@ -47,14 +57,18 @@ export function init_array_prototype_warnings() {
if (test) {
w.state_proxy_equality_mismatch('array.includes(...)');
// eslint-disable-next-line no-console
console.trace();
}
}
return has;
};
// @ts-expect-error
Array.__svelte_cleanup = () => {
array_prototype.indexOf = indexOf;
array_prototype.lastIndexOf = lastIndexOf;
array_prototype.includes = includes;
};
}
/**
@ -66,9 +80,6 @@ export function init_array_prototype_warnings() {
export function strict_equals(a, b, equal = true) {
if ((a === b) !== (get_proxied_value(a) === get_proxied_value(b))) {
w.state_proxy_equality_mismatch(equal ? '===' : '!==');
// eslint-disable-next-line no-console
console.trace();
}
return (a === b) === equal;
@ -83,9 +94,6 @@ export function strict_equals(a, b, equal = true) {
export function equals(a, b, equal = true) {
if ((a == b) !== (get_proxied_value(a) == get_proxied_value(b))) {
w.state_proxy_equality_mismatch(equal ? '==' : '!=');
// eslint-disable-next-line no-console
console.trace();
}
return (a == b) === equal;

@ -241,8 +241,5 @@ export function check_ownership(metadata) {
} else {
w.ownership_invalid_mutation();
}
// eslint-disable-next-line no-console
console.trace();
}
}

@ -1,8 +1,7 @@
import { namespace_svg } from '../../../../constants.js';
import { hydrate_anchor, hydrate_nodes, hydrating } from '../hydration.js';
import { hydrate_anchor, hydrate_start, hydrating } from '../hydration.js';
import { empty } from '../operations.js';
import { render_effect } from '../../reactivity/effects.js';
import { remove } from '../reconciler.js';
/**
* @param {Element | Text | Comment} anchor
@ -20,7 +19,7 @@ export function css_props(anchor, is_html, props, component) {
if (hydrating) {
// Hydration: css props element is surrounded by a ssr comment ...
element = /** @type {HTMLElement | SVGElement} */ (hydrate_nodes[0]);
element = /** @type {HTMLElement | SVGElement} */ (hydrate_start);
// ... and the child(ren) of the css props element is also surround by a ssr comment
component_anchor = /** @type {Comment} */ (
hydrate_anchor(/** @type {Comment} */ (element.firstChild))
@ -60,7 +59,7 @@ export function css_props(anchor, is_html, props, component) {
});
return () => {
remove(element);
element.remove();
};
});
}

@ -8,7 +8,13 @@ import {
HYDRATION_END_ELSE,
HYDRATION_START
} from '../../../../constants.js';
import { hydrate_anchor, hydrate_nodes, hydrating, set_hydrating } from '../hydration.js';
import {
hydrate_anchor,
hydrate_nodes,
hydrate_start,
hydrating,
set_hydrating
} from '../hydration.js';
import { clear_text_content, empty } from '../operations.js';
import { remove } from '../reconciler.js';
import { untrack } from '../../runtime.js';
@ -25,7 +31,6 @@ import {
import { source, mutable_source, set } from '../../reactivity/sources.js';
import { is_array, is_frozen } from '../../utils.js';
import { INERT, STATE_SYMBOL } from '../../constants.js';
import { push_template_node } from '../template.js';
/**
* The row of a keyed each block that is currently updating. We track this
@ -149,7 +154,7 @@ export function each(anchor, flags, get_collection, get_key, render_fn, fallback
// this is separate to the previous block because `hydrating` might change
if (hydrating) {
/** @type {Node} */
var child_anchor = hydrate_nodes[0];
var child_anchor = hydrate_start;
/** @type {import('#client').EachItem | import('#client').EachState} */
var prev = state;
@ -280,7 +285,7 @@ function reconcile(array, state, anchor, render_fn, flags, get_key) {
item = items.get(key);
if (item === undefined) {
var child_open = push_template_node(empty());
var child_open = empty();
var child_anchor = current ? current.o : anchor;
child_anchor.before(child_open);
@ -407,6 +412,7 @@ function reconcile(array, state, anchor, render_fn, flags, get_key) {
for (var i = 0; i < to_destroy.length; i += 1) {
var item = to_destroy[i];
items.delete(item.k);
item.o.remove();
link(item.prev, item.next);
}
});

@ -4,7 +4,6 @@ import { current_effect, get } from '../../runtime.js';
import { is_array } from '../../utils.js';
import { hydrate_nodes, hydrating } from '../hydration.js';
import { create_fragment_from_html, remove } from '../reconciler.js';
import { push_template_node } from '../template.js';
/**
* @param {import('#client').Effect} effect
@ -38,7 +37,7 @@ export function html(anchor, get_value, svg, mathml) {
let value = derived(get_value);
render_effect(() => {
var dom = html_to_dom(anchor, parent_effect, get(value), svg, mathml);
var dom = html_to_dom(anchor, get(value), svg, mathml);
if (dom) {
return () => {
@ -56,13 +55,12 @@ export function html(anchor, get_value, svg, mathml) {
* inserts it before the target anchor and returns the new nodes.
* @template V
* @param {Element | Text | Comment} target
* @param {import('#client').Effect | null} effect
* @param {V} value
* @param {boolean} svg
* @param {boolean} mathml
* @returns {Element | Comment | (Element | Comment | Text)[]}
*/
function html_to_dom(target, effect, value, svg, mathml) {
function html_to_dom(target, value, svg, mathml) {
if (hydrating) return hydrate_nodes;
var html = value + '';
@ -81,9 +79,6 @@ function html_to_dom(target, effect, value, svg, mathml) {
if (node.childNodes.length === 1) {
var child = /** @type {Text | Element | Comment} */ (node.firstChild);
target.before(child);
if (effect !== null) {
push_template_node(child, effect);
}
return child;
}
@ -97,9 +92,5 @@ function html_to_dom(target, effect, value, svg, mathml) {
target.before(node);
}
if (effect !== null) {
push_template_node(nodes, effect);
}
return nodes;
}

@ -1,42 +1,18 @@
import { namespace_svg } from '../../../../constants.js';
import { hydrate_anchor, hydrate_nodes, hydrating, set_hydrate_nodes } from '../hydration.js';
import { hydrate_anchor, hydrate_start, hydrating } from '../hydration.js';
import { empty } from '../operations.js';
import {
block,
branch,
destroy_effect,
pause_effect,
render_effect,
resume_effect
} from '../../reactivity/effects.js';
import { is_array } from '../../utils.js';
import { set_should_intro } from '../../render.js';
import { current_each_item, set_current_each_item } from './each.js';
import { current_component_context, current_effect } from '../../runtime.js';
import { push_template_node } from '../template.js';
import { current_component_context } from '../../runtime.js';
import { DEV } from 'esm-env';
/**
* @param {import('#client').Effect} effect
* @param {Element} from
* @param {Element} to
* @returns {void}
*/
function swap_block_dom(effect, from, to) {
const dom = effect.dom;
if (is_array(dom)) {
for (let i = 0; i < dom.length; i++) {
if (dom[i] === from) {
dom[i] = to;
break;
}
}
} else if (dom === from) {
effect.dom = to;
}
}
/**
* @param {Comment} anchor
* @param {() => string} get_tag
@ -47,122 +23,104 @@ function swap_block_dom(effect, from, to) {
* @returns {void}
*/
export function element(anchor, get_tag, is_svg, render_fn, get_namespace, location) {
const parent_effect = /** @type {import('#client').Effect} */ (current_effect);
const filename = DEV && location && current_component_context?.function.filename;
render_effect(() => {
/** @type {string | null} */
let tag;
/** @type {string | null} */
let current_tag;
/** @type {null | Element} */
let element = null;
/** @type {import('#client').Effect | null} */
let effect;
/**
* The keyed `{#each ...}` item block, if any, that this element is inside.
* We track this so we can set it when changing the element, allowing any
* `animate:` directive to bind itself to the correct block
*/
let each_item_block = current_each_item;
block(() => {
const next_tag = get_tag() || null;
const ns = get_namespace
? get_namespace()
: is_svg || next_tag === 'svg'
? namespace_svg
: null;
// Assumption: Noone changes the namespace but not the tag (what would that even mean?)
if (next_tag === tag) return;
// See explanation of `each_item_block` above
var previous_each_item = current_each_item;
set_current_each_item(each_item_block);
if (effect) {
if (next_tag === null) {
// start outro
pause_effect(effect, () => {
effect = null;
current_tag = null;
element?.remove();
});
} else if (next_tag === current_tag) {
// same tag as is currently rendered — abort outro
resume_effect(effect);
} else {
// tag is changing — destroy immediately, render contents without intro transitions
destroy_effect(effect);
set_should_intro(false);
}
/** @type {string | null} */
let tag;
/** @type {string | null} */
let current_tag;
/** @type {null | Element} */
let element = null;
/** @type {import('#client').Effect | null} */
let effect;
/**
* The keyed `{#each ...}` item block, if any, that this element is inside.
* We track this so we can set it when changing the element, allowing any
* `animate:` directive to bind itself to the correct block
*/
let each_item_block = current_each_item;
block(() => {
const next_tag = get_tag() || null;
const ns = get_namespace
? get_namespace()
: is_svg || next_tag === 'svg'
? namespace_svg
: null;
// Assumption: Noone changes the namespace but not the tag (what would that even mean?)
if (next_tag === tag) return;
// See explanation of `each_item_block` above
var previous_each_item = current_each_item;
set_current_each_item(each_item_block);
if (effect) {
if (next_tag === null) {
// start outro
pause_effect(effect, () => {
effect = null;
current_tag = null;
element?.remove();
});
} else if (next_tag === current_tag) {
// same tag as is currently rendered — abort outro
resume_effect(effect);
} else {
// tag is changing — destroy immediately, render contents without intro transitions
destroy_effect(effect);
set_should_intro(false);
}
}
if (next_tag && next_tag !== current_tag) {
effect = branch(() => {
const prev_element = element;
element = hydrating
? /** @type {Element} */ (hydrate_nodes[0])
: ns
? document.createElementNS(ns, next_tag)
: document.createElement(next_tag);
if (DEV && location) {
// @ts-expect-error
element.__svelte_meta = {
loc: {
filename,
line: location[0],
column: location[1]
}
};
}
if (render_fn) {
// If hydrating, use the existing ssr comment as the anchor so that the
// inner open and close methods can pick up the existing nodes correctly
var child_anchor = hydrating
? element.firstChild && hydrate_anchor(/** @type {Comment} */ (element.firstChild))
: element.appendChild(empty());
if (hydrating && !element.firstChild) {
// if the element is a void element with content, add an empty
// node to avoid breaking assumptions elsewhere
set_hydrate_nodes([empty()]);
if (next_tag && next_tag !== current_tag) {
effect = branch(() => {
element = hydrating
? /** @type {Element} */ (hydrate_start)
: ns
? document.createElementNS(ns, next_tag)
: document.createElement(next_tag);
if (DEV && location) {
// @ts-expect-error
element.__svelte_meta = {
loc: {
file: filename,
line: location[0],
column: location[1]
}
};
}
// `child_anchor` is undefined if this is a void element, but we still
// need to call `render_fn` in order to run actions etc. If the element
// contains children, it's a user error (which is warned on elsewhere)
// and the DOM will be silently discarded
render_fn(element, child_anchor);
}
anchor.before(element);
if (prev_element) {
swap_block_dom(parent_effect, prev_element, element);
prev_element.remove();
} else if (!hydrating) {
push_template_node(element, parent_effect);
}
});
}
if (render_fn) {
// If hydrating, use the existing ssr comment as the anchor so that the
// inner open and close methods can pick up the existing nodes correctly
var child_anchor = hydrating
? element.firstChild && hydrate_anchor(/** @type {Comment} */ (element.firstChild))
: element.appendChild(empty());
// `child_anchor` is undefined if this is a void element, but we still
// need to call `render_fn` in order to run actions etc. If the element
// contains children, it's a user error (which is warned on elsewhere)
// and the DOM will be silently discarded
render_fn(element, child_anchor);
}
tag = next_tag;
if (tag) current_tag = tag;
set_should_intro(true);
anchor.before(element);
return () => {
element?.remove();
};
});
}
set_current_each_item(previous_each_item);
});
tag = next_tag;
if (tag) current_tag = tag;
set_should_intro(true);
return () => {
element?.remove();
};
set_current_each_item(previous_each_item);
});
}

@ -2,29 +2,67 @@ import { DEV } from 'esm-env';
import { hydrating } from '../hydration.js';
import { get_descriptors, get_prototype_of, map_get, map_set } from '../../utils.js';
import { AttributeAliases, DelegatedEvents, namespace_svg } from '../../../../constants.js';
import { delegate } from './events.js';
import { autofocus } from './misc.js';
import { create_event, delegate } from './events.js';
import { add_form_reset_listener, autofocus } from './misc.js';
import { effect, effect_root } from '../../reactivity/effects.js';
import * as w from '../../warnings.js';
import { LOADING_ATTR_SYMBOL } from '../../constants.js';
import { queue_idle_task } from '../task.js';
/**
* The value/checked attribute in the template actually corresponds to the defaultValue property, so we need
* to remove it upon hydration to avoid a bug when someone resets the form value.
* @param {HTMLInputElement | HTMLSelectElement} dom
* @param {HTMLInputElement} dom
* @returns {void}
*/
export function remove_input_attr_defaults(dom) {
if (hydrating) {
// using getAttribute instead of dom.value allows us to have
// null instead of "on" if the user didn't set a value
const value = dom.getAttribute('value');
set_attribute(dom, 'value', null);
set_attribute(dom, 'checked', null);
if (value) dom.value = value;
let already_removed = false;
// We try and remove the default attributes later, rather than sync during hydration.
// Doing it sync during hydration has a negative impact on performance, but deferring the
// work in an idle task alleviates this greatly. If a form reset event comes in before
// the idle callback, then we ensure the input defaults are cleared just before.
const remove_defaults = () => {
if (already_removed) return;
already_removed = true;
const value = dom.getAttribute('value');
set_attribute(dom, 'value', null);
set_attribute(dom, 'checked', null);
if (value) dom.value = value;
};
// @ts-expect-error
dom.__on_r = remove_defaults;
queue_idle_task(remove_defaults);
add_form_reset_listener();
}
}
/**
* @param {Element} element
* @param {any} value
*/
export function set_value(element, value) {
// @ts-expect-error
var attributes = (element.__attributes ??= {});
if (attributes.value === (attributes.value = value)) return;
// @ts-expect-error
element.value = value;
}
/**
* @param {Element} element
* @param {boolean} checked
*/
export function set_checked(element, checked) {
// @ts-expect-error
var attributes = (element.__attributes ??= {});
if (attributes.checked === (attributes.checked = checked)) return;
// @ts-expect-error
element.checked = checked;
}
/**
* @param {Element} element
* @param {string} attribute
@ -149,10 +187,15 @@ export function set_attributes(element, prev, next, lowercase_attributes, css_ha
if (value != null) {
if (!delegated) {
// we use `addEventListener` here because these events are not delegated
if (!prev) {
events.push([key, value, () => element.addEventListener(event_name, value, opts)]);
events.push([
key,
value,
() => (next[key] = create_event(event_name, element, value, opts))
]);
} else {
element.addEventListener(event_name, value, opts);
next[key] = create_event(event_name, element, value, opts);
}
} else {
// @ts-ignore

@ -4,6 +4,7 @@ import { stringify } from '../../../render.js';
import { listen_to_event_and_reset_event } from './shared.js';
import * as e from '../../../errors.js';
import { get_proxied_value, is } from '../../../proxy.js';
import { yield_updates } from '../../../runtime.js';
/**
* @param {HTMLInputElement} input
@ -18,7 +19,7 @@ export function bind_value(input, get_value, update) {
e.bind_invalid_checkbox_value();
}
update(is_numberlike_input(input) ? to_number(input.value) : input.value);
yield_updates(() => update(is_numberlike_input(input) ? to_number(input.value) : input.value));
});
render_effect(() => {
@ -84,10 +85,10 @@ export function bind_group(inputs, group_index, input, get_value, update) {
value = get_binding_group_value(binding_group, value, input.checked);
}
update(value);
yield_updates(() => update(value));
},
// TODO better default value handling
() => update(is_checkbox ? [] : null)
() => yield_updates(() => update(is_checkbox ? [] : null))
);
render_effect(() => {
@ -128,7 +129,7 @@ export function bind_group(inputs, group_index, input, get_value, update) {
export function bind_checked(input, get_value, update) {
listen_to_event_and_reset_event(input, 'change', () => {
var value = input.checked;
update(value);
yield_updates(() => update(value));
});
if (get_value() == undefined) {
@ -187,7 +188,7 @@ function to_number(value) {
*/
export function bind_files(input, get_value, update) {
listen_to_event_and_reset_event(input, 'change', () => {
update(input.files);
yield_updates(() => update(input.files));
});
render_effect(() => {
input.files = get_value();

@ -1,6 +1,7 @@
import { hydrating } from '../../hydration.js';
import { render_effect, effect } from '../../../reactivity/effects.js';
import { listen } from './shared.js';
import { yield_updates } from '../../../runtime.js';
/** @param {TimeRanges} ranges */
function time_ranges_to_array(ranges) {
@ -35,7 +36,7 @@ export function bind_current_time(media, get_value, update) {
}
updating = true;
update(media.currentTime);
yield_updates(() => update(media.currentTime));
};
raf_id = requestAnimationFrame(callback);
@ -60,7 +61,9 @@ export function bind_current_time(media, get_value, update) {
* @param {(array: Array<{ start: number; end: number }>) => void} update
*/
export function bind_buffered(media, update) {
listen(media, ['loadedmetadata', 'progress'], () => update(time_ranges_to_array(media.buffered)));
listen(media, ['loadedmetadata', 'progress'], () =>
yield_updates(() => update(time_ranges_to_array(media.buffered)))
);
}
/**
@ -76,7 +79,9 @@ export function bind_seekable(media, update) {
* @param {(array: Array<{ start: number; end: number }>) => void} update
*/
export function bind_played(media, update) {
listen(media, ['timeupdate'], () => update(time_ranges_to_array(media.played)));
listen(media, ['timeupdate'], () =>
yield_updates(() => update(time_ranges_to_array(media.played)))
);
}
/**
@ -84,7 +89,7 @@ export function bind_played(media, update) {
* @param {(seeking: boolean) => void} update
*/
export function bind_seeking(media, update) {
listen(media, ['seeking', 'seeked'], () => update(media.seeking));
listen(media, ['seeking', 'seeked'], () => yield_updates(() => update(media.seeking)));
}
/**
@ -92,7 +97,7 @@ export function bind_seeking(media, update) {
* @param {(seeking: boolean) => void} update
*/
export function bind_ended(media, update) {
listen(media, ['timeupdate', 'ended'], () => update(media.ended));
listen(media, ['timeupdate', 'ended'], () => yield_updates(() => update(media.ended)));
}
/**
@ -103,7 +108,7 @@ export function bind_ready_state(media, update) {
listen(
media,
['loadedmetadata', 'loadeddata', 'canplay', 'canplaythrough', 'playing', 'waiting', 'emptied'],
() => update(media.readyState)
() => yield_updates(() => update(media.readyState))
);
}
@ -127,7 +132,7 @@ export function bind_playback_rate(media, get_value, update) {
}
listen(media, ['ratechange'], () => {
if (!updating) update(media.playbackRate);
if (!updating) yield_updates(() => update(media.playbackRate));
updating = false;
});
});
@ -145,7 +150,7 @@ export function bind_paused(media, get_value, update) {
var callback = () => {
if (paused !== media.paused) {
paused = media.paused;
update((paused = media.paused));
yield_updates(() => update((paused = media.paused)));
}
};
@ -170,7 +175,7 @@ export function bind_paused(media, get_value, update) {
media.pause();
} else {
media.play().catch(() => {
update((paused = true));
yield_updates(() => update((paused = true)));
});
}
};
@ -234,7 +239,7 @@ export function bind_muted(media, get_value, update) {
var callback = () => {
updating = true;
update(media.muted);
yield_updates(() => update(media.muted));
};
if (get_value() == null) {

@ -1,3 +1,4 @@
import { yield_updates } from '../../../runtime.js';
import { listen } from './shared.js';
/**
@ -6,6 +7,6 @@ import { listen } from './shared.js';
*/
export function bind_online(update) {
listen(window, ['online', 'offline'], () => {
update(navigator.onLine);
yield_updates(() => update(navigator.onLine));
});
}

@ -1,6 +1,6 @@
import { effect } from '../../../reactivity/effects.js';
import { listen_to_event_and_reset_event } from './shared.js';
import { untrack } from '../../../runtime.js';
import { untrack, yield_updates } from '../../../runtime.js';
import { is } from '../../../proxy.js';
/**
@ -90,7 +90,7 @@ export function bind_select_value(select, get_value, update) {
value = selected_option && get_option_value(selected_option);
}
update(value);
yield_updates(() => update(value));
});
// Needs to be an effect, not a render_effect, so that in case of each loops the logic runs after the each block has updated

@ -1,4 +1,5 @@
import { render_effect } from '../../../reactivity/effects.js';
import { add_form_reset_listener } from '../misc.js';
/**
* Fires the handler once immediately (unless corresponding arg is set to `false`),
@ -26,8 +27,6 @@ export function listen(target, events, handler, call_handler_immediately = true)
});
}
let listening_to_form_reset = false;
/**
* Listen to the given event, and then instantiate a global form reset listener if not already done,
* to notify all bindings when the form is reset
@ -52,24 +51,5 @@ export function listen_to_event_and_reset_event(element, event, handler, on_rese
element.__on_r = on_reset;
}
if (!listening_to_form_reset) {
listening_to_form_reset = true;
document.addEventListener(
'reset',
(evt) => {
// Needs to happen one tick later or else the dom properties of the form
// elements have not updated to their reset values yet
Promise.resolve().then(() => {
if (!evt.defaultPrevented) {
for (const e of /**@type {HTMLFormElement} */ (evt.target).elements) {
// @ts-expect-error
e.__on_r?.();
}
}
});
},
// In the capture phase to guarantee we get noticed of it (no possiblity of stopPropagation)
{ capture: true }
);
}
add_form_reset_listener();
}

@ -1,5 +1,5 @@
import { effect, render_effect } from '../../../reactivity/effects.js';
import { untrack } from '../../../runtime.js';
import { untrack, yield_updates } from '../../../runtime.js';
/**
* Resize observer singleton.
@ -88,7 +88,10 @@ export function bind_resize_observer(element, type, update) {
? resize_observer_border_box
: resize_observer_device_pixel_content_box;
var unsub = observer.observe(element, /** @param {any} entry */ (entry) => update(entry[type]));
var unsub = observer.observe(
element,
/** @param {any} entry */ (entry) => yield_updates(() => update(entry[type]))
);
render_effect(() => unsub);
}
@ -101,7 +104,7 @@ export function bind_element_size(element, type, update) {
var unsub = resize_observer_border_box.observe(element, () => update(element[type]));
effect(() => {
untrack(() => update(element[type]));
yield_updates(() => untrack(() => update(element[type])));
return unsub;
});
}

@ -1,7 +1,7 @@
import { STATE_SYMBOL } from '../../../constants.js';
import { effect, render_effect } from '../../../reactivity/effects.js';
import { untrack } from '../../../runtime.js';
import { queue_task } from '../../task.js';
import { untrack, yield_updates } from '../../../runtime.js';
import { queue_micro_task } from '../../task.js';
/**
* @param {any} bound_value
@ -37,19 +37,21 @@ export function bind_this(element_or_component, update, get_value, get_parts) {
untrack(() => {
if (element_or_component !== get_value(...parts)) {
update(element_or_component, ...parts);
// If this is an effect rerun (cause: each block context changes), then nullfiy the binding at
// the previous position if it isn't already taken over by a different effect.
if (old_parts && is_bound_this(get_value(...old_parts), element_or_component)) {
update(null, ...old_parts);
}
yield_updates(() => {
update(element_or_component, ...parts);
// If this is an effect rerun (cause: each block context changes), then nullfiy the binding at
// the previous position if it isn't already taken over by a different effect.
if (old_parts && is_bound_this(get_value(...old_parts), element_or_component)) {
update(null, ...old_parts);
}
});
}
});
});
return () => {
// We cannot use effects in the teardown phase, we we use a microtask instead.
queue_task(() => {
queue_micro_task(() => {
if (parts && is_bound_this(get_value(...parts), element_or_component)) {
update(null, ...parts);
}

@ -1,4 +1,5 @@
import { render_effect } from '../../../reactivity/effects.js';
import { effect, render_effect } from '../../../reactivity/effects.js';
import { yield_updates } from '../../../runtime.js';
import { listen } from './shared.js';
/**
@ -15,14 +16,13 @@ export function bind_window_scroll(type, get_value, update) {
clearTimeout(timeout);
timeout = setTimeout(clear, 100); // TODO use scrollend event if supported (or when supported everywhere?)
update(window[is_scrolling_x ? 'scrollX' : 'scrollY']);
yield_updates(() => update(window[is_scrolling_x ? 'scrollX' : 'scrollY']));
};
addEventListener('scroll', target_handler, {
passive: true
});
var latest_value = 0;
var scrolling = false;
/** @type {ReturnType<typeof setTimeout>} */
@ -30,10 +30,14 @@ export function bind_window_scroll(type, get_value, update) {
var clear = () => {
scrolling = false;
};
var first = true;
render_effect(() => {
latest_value = get_value();
if (!scrolling && latest_value != null) {
var latest_value = get_value();
// Don't scroll to the initial value for accessibility reasons
if (first) {
first = false;
} else if (!scrolling && latest_value != null) {
scrolling = true;
clearTimeout(timeout);
if (is_scrolling_x) {
@ -45,6 +49,15 @@ export function bind_window_scroll(type, get_value, update) {
}
});
// Browsers fire the scroll event only if the scroll position is not 0.
// This effect is (almost) guaranteed to run after the scroll event would've fired.
effect(() => {
var value = window[is_scrolling_x ? 'scrollX' : 'scrollY'];
if (value === 0) {
yield_updates(() => update(value));
}
});
render_effect(() => {
return () => {
removeEventListener('scroll', target_handler);
@ -57,5 +70,5 @@ export function bind_window_scroll(type, get_value, update) {
* @param {(size: number) => void} update
*/
export function bind_window_size(type, update) {
listen(window, ['resize'], () => update(window[type]));
listen(window, ['resize'], () => yield_updates(() => update(window[type])));
}

@ -1,5 +1,4 @@
import { hydrating } from '../hydration.js';
import { set_class_name } from '../operations.js';
/**
* @param {SVGElement} dom
@ -83,7 +82,7 @@ export function set_class(dom, value) {
if (value == null) {
dom.removeAttribute('class');
} else {
set_class_name(dom, next_class_name);
dom.className = next_class_name;
}
// @ts-expect-error need to add __className to patched prototype

@ -109,8 +109,9 @@ if (typeof HTMLElement === 'function') {
const existing_slots = get_custom_elements_slots(this);
for (const name of this.$$s) {
if (name in existing_slots) {
if (name === 'default') {
if (name === 'default' && !this.$$d.children) {
this.$$d.children = create_slot(name);
$$slots.default = true;
} else {
$$slots[name] = create_slot(name);
}

@ -1,32 +1,83 @@
import { render_effect } from '../../reactivity/effects.js';
import { all_registered_events, root_event_handles } from '../../render.js';
import { yield_updates } from '../../runtime.js';
import { define_property, is_array } from '../../utils.js';
import { hydrating } from '../hydration.js';
import { queue_micro_task } from '../task.js';
/**
* SSR adds onload and onerror attributes to catch those events before the hydration.
* This function detects those cases, removes the attributes and replays the events.
* @param {HTMLElement} dom
*/
export function replay_events(dom) {
if (!hydrating) return;
if (dom.onload) {
dom.removeAttribute('onload');
}
if (dom.onerror) {
dom.removeAttribute('onerror');
}
// @ts-expect-error
const event = dom.__e;
if (event !== undefined) {
// @ts-expect-error
dom.__e = undefined;
queueMicrotask(() => {
if (dom.isConnected) {
dom.dispatchEvent(event);
}
});
}
}
/**
* @param {string} event_name
* @param {Element} dom
* @param {EventListener} handler
* @param {boolean} capture
* @param {boolean} [passive]
* @returns {void}
* @param {AddEventListenerOptions} options
*/
export function event(event_name, dom, handler, capture, passive) {
var options = { capture, passive };
export function create_event(event_name, dom, handler, options) {
/**
* @this {EventTarget}
*/
function target_handler(/** @type {Event} */ event) {
if (!capture) {
if (!options.capture) {
// Only call in the bubble phase, else delegated events would be called before the capturing events
handle_event_propagation(dom, event);
}
if (!event.cancelBubble) {
return handler.call(this, event);
return yield_updates(() => handler.call(this, event));
}
}
dom.addEventListener(event_name, target_handler, options);
// Chrome has a bug where pointer events don't work when attached to a DOM element that has been cloned
// with cloneNode() and the DOM element is disconnected from the document. To ensure the event works, we
// defer the attachment till after it's been appended to the document. TODO: remove this once Chrome fixes
// this bug.
if (event_name.startsWith('pointer')) {
queue_micro_task(() => {
dom.addEventListener(event_name, target_handler, options);
});
} else {
dom.addEventListener(event_name, target_handler, options);
}
return target_handler;
}
/**
* @param {string} event_name
* @param {Element} dom
* @param {EventListener} handler
* @param {boolean} capture
* @param {boolean} [passive]
* @returns {void}
*/
export function event(event_name, dom, handler, capture, passive) {
var options = { capture, passive };
var target_handler = create_event(event_name, dom, handler, options);
// @ts-ignore
if (dom === document.body || dom === window || dom === document) {
@ -153,7 +204,7 @@ export function handle_event_propagation(handler_element, event) {
}
try {
next(current_target);
yield_updates(() => next(/** @type {Element} */ (current_target)));
} finally {
// @ts-expect-error is used above
event.__root = handler_element;

@ -31,3 +31,28 @@ export function remove_textarea_child(dom) {
clear_text_content(dom);
}
}
let listening_to_form_reset = false;
export function add_form_reset_listener() {
if (!listening_to_form_reset) {
listening_to_form_reset = true;
document.addEventListener(
'reset',
(evt) => {
// Needs to happen one tick later or else the dom properties of the form
// elements have not updated to their reset values yet
Promise.resolve().then(() => {
if (!evt.defaultPrevented) {
for (const e of /**@type {HTMLFormElement} */ (evt.target).elements) {
// @ts-expect-error
e.__on_r?.();
}
}
});
},
// In the capture phase to guarantee we get noticed of it (no possiblity of stopPropagation)
{ capture: true }
);
}
}

@ -10,25 +10,12 @@ import { TRANSITION_GLOBAL, TRANSITION_IN, TRANSITION_OUT } from '../../../../co
import { BLOCK_EFFECT, EFFECT_RAN, EFFECT_TRANSPARENT } from '../../constants.js';
/**
* @template T
* @param {string} type
* @param {T} [detail]
* @param {any}params_0
* @returns {Event}
*/
function custom_event(type, detail, { bubbles = false, cancelable = false } = {}) {
const e = document.createEvent('CustomEvent');
e.initCustomEvent(type, bubbles, cancelable, detail);
return e;
}
/**
* @param {Element} dom
* @param {Element} element
* @param {'introstart' | 'introend' | 'outrostart' | 'outroend'} type
* @returns {void}
*/
function dispatch_event(dom, type) {
dom.dispatchEvent(custom_event(type));
function dispatch_event(element, type) {
element.dispatchEvent(new CustomEvent(type));
}
/**

@ -1,5 +1,6 @@
import { HYDRATION_END, HYDRATION_START } from '../../../constants.js';
import * as e from '../errors.js';
import { DEV } from 'esm-env';
import { HYDRATION_END, HYDRATION_START, HYDRATION_ERROR } from '../../../constants.js';
import * as w from '../warnings.js';
/**
* Use this variable to guard everything related to hydration code so it can be treeshaken out
@ -20,9 +21,13 @@ export function set_hydrating(value) {
*/
export let hydrate_nodes = /** @type {any} */ (null);
/** @type {import('#client').TemplateNode} */
export let hydrate_start;
/** @param {import('#client').TemplateNode[]} nodes */
export function set_hydrate_nodes(nodes) {
hydrate_nodes = nodes;
hydrate_start = nodes && nodes[0];
}
/**
@ -57,6 +62,7 @@ export function hydrate_anchor(node) {
} else if (data[0] === HYDRATION_END) {
if (depth === 0) {
hydrate_nodes = /** @type {import('#client').TemplateNode[]} */ (nodes);
hydrate_start = /** @type {import('#client').TemplateNode} */ (nodes[0]);
return current;
}
@ -67,5 +73,16 @@ export function hydrate_anchor(node) {
nodes.push(current);
}
e.hydration_missing_marker_close();
let location;
if (DEV) {
// @ts-expect-error
const loc = node.parentNode?.__svelte_meta?.loc;
if (loc) {
location = `${loc.file}:${loc.line}:${loc.column}`;
}
}
w.hydration_mismatch(location);
throw HYDRATION_ERROR;
}

@ -66,3 +66,15 @@ export function update_legacy_props($$new_props) {
}
}
}
/**
* @param {Record<string, any>} $$props
*/
export function default_slot($$props) {
var children = $$props.$$slots?.default;
if (children === true) {
return $$props.children;
} else {
return children;
}
}

@ -1,46 +1,13 @@
import { hydrate_anchor, hydrate_nodes, hydrating } from './hydration.js';
import { get_descriptor } from '../utils.js';
import { hydrate_anchor, hydrate_start, hydrating } from './hydration.js';
import { DEV } from 'esm-env';
import { init_array_prototype_warnings } from '../dev/equality.js';
// We cache the Node and Element prototype methods, so that we can avoid doing
// expensive prototype chain lookups.
/** @type {Node} */
var node_prototype;
/** @type {Element} */
var element_prototype;
/** @type {Text} */
var text_prototype;
/** @type {typeof Node.prototype.appendChild} */
var append_child_method;
/** @type {typeof Node.prototype.cloneNode} */
var clone_node_method;
/** @type {(this: Node) => ChildNode | null} */
var first_child_get;
/** @type {(this: Node) => ChildNode | null} */
var next_sibling_get;
/** @type {(this: Node, text: string ) => void} */
var text_content_set;
/** @type {(this: Element, class_name: string) => void} */
var class_name_set;
import { current_effect } from '../runtime.js';
// export these for reference in the compiled code, making global name deduplication unnecessary
/**
* @type {Window}
*/
/** @type {Window} */
export var $window;
/**
* @type {Document}
*/
/** @type {Document} */
export var $document;
/**
@ -48,29 +15,27 @@ export var $document;
* where these globals are not available while avoiding a separate server entry point
*/
export function init_operations() {
if (node_prototype !== undefined) {
if ($window !== undefined) {
return;
}
node_prototype = Node.prototype;
element_prototype = Element.prototype;
text_prototype = Text.prototype;
append_child_method = node_prototype.appendChild;
clone_node_method = node_prototype.cloneNode;
$window = window;
$document = document;
var element_prototype = Element.prototype;
// the following assignments improve perf of lookups on DOM nodes
// @ts-expect-error
element_prototype.__click = undefined;
// @ts-expect-error
text_prototype.__nodeValue = ' ';
// @ts-expect-error
element_prototype.__className = '';
// @ts-expect-error
element_prototype.__attributes = null;
// @ts-expect-error
element_prototype.__e = undefined;
// @ts-expect-error
Text.prototype.__nodeValue = ' ';
if (DEV) {
// @ts-expect-error
@ -78,47 +43,6 @@ export function init_operations() {
init_array_prototype_warnings();
}
first_child_get = /** @type {(this: Node) => ChildNode | null} */ (
// @ts-ignore
get_descriptor(node_prototype, 'firstChild').get
);
next_sibling_get = /** @type {(this: Node) => ChildNode | null} */ (
// @ts-ignore
get_descriptor(node_prototype, 'nextSibling').get
);
text_content_set = /** @type {(this: Node, text: string ) => void} */ (
// @ts-ignore
get_descriptor(node_prototype, 'textContent').set
);
class_name_set = /** @type {(this: Element, class_name: string) => void} */ (
// @ts-ignore
get_descriptor(element_prototype, 'className').set
);
}
/**
* @template {Element} E
* @template {Node} T
* @param {E} element
* @param {T} child
*/
export function append_child(element, child) {
append_child_method.call(element, child);
}
/**
* @template {Node} N
* @param {N} node
* @param {boolean} deep
* @returns {N}
*/
/*#__NO_SIDE_EFFECTS__*/
export function clone_node(node, deep) {
return /** @type {N} */ (clone_node_method.call(node, deep));
}
/** @returns {Text} */
@ -133,7 +57,7 @@ export function empty() {
*/
/*#__NO_SIDE_EFFECTS__*/
export function child(node) {
const child = first_child_get.call(node);
const child = node.firstChild;
if (!hydrating) return child;
// Child can be null if we have an element with a single child, like `<p>{text}</p>`, where `text` is empty
@ -153,22 +77,24 @@ export function child(node) {
export function first_child(fragment, is_text) {
if (!hydrating) {
// when not hydrating, `fragment` is a `DocumentFragment` (the result of calling `open_frag`)
return first_child_get.call(/** @type {DocumentFragment} */ (fragment));
return /** @type {DocumentFragment} */ (fragment).firstChild;
}
// when we _are_ hydrating, `fragment` is an array of nodes
const first_node = /** @type {import('#client').TemplateNode[]} */ (fragment)[0];
// if an {expression} is empty during SSR, there might be no
// text node to hydrate — we must therefore create one
if (is_text && first_node?.nodeType !== 3) {
const text = empty();
hydrate_nodes.unshift(text);
first_node?.before(text);
if (is_text && hydrate_start?.nodeType !== 3) {
var text = empty();
var dom = /** @type {import('#client').TemplateNode[]} */ (
/** @type {import('#client').Effect} */ (current_effect).dom
);
dom.unshift(text);
hydrate_start?.before(text);
return text;
}
return hydrate_anchor(first_node);
return hydrate_anchor(hydrate_start);
}
/**
@ -179,7 +105,7 @@ export function first_child(fragment, is_text) {
*/
/*#__NO_SIDE_EFFECTS__*/
export function sibling(node, is_text = false) {
const next_sibling = next_sibling_get.call(node);
const next_sibling = node.nextSibling;
if (!hydrating) {
return next_sibling;
@ -188,14 +114,13 @@ export function sibling(node, is_text = false) {
// if a sibling {expression} is empty during SSR, there might be no
// text node to hydrate — we must therefore create one
if (is_text && next_sibling?.nodeType !== 3) {
const text = empty();
if (next_sibling) {
const index = hydrate_nodes.indexOf(/** @type {Text | Comment | Element} */ (next_sibling));
hydrate_nodes.splice(index, 0, text);
next_sibling.before(text);
} else {
hydrate_nodes.push(text);
}
var text = empty();
var dom = /** @type {import('#client').TemplateNode[]} */ (
/** @type {import('#client').Effect} */ (current_effect).dom
);
dom.unshift(text);
next_sibling?.before(text);
return text;
}
@ -203,23 +128,13 @@ export function sibling(node, is_text = false) {
return hydrate_anchor(/** @type {Node} */ (next_sibling));
}
/**
* @template {Element} N
* @param {N} node
* @param {string} class_name
* @returns {void}
*/
export function set_class_name(node, class_name) {
class_name_set.call(node, class_name);
}
/**
* @template {Node} N
* @param {N} node
* @returns {void}
*/
export function clear_text_content(node) {
text_content_set.call(node, '');
node.textContent = '';
}
/** @param {string} name */

@ -1,33 +1,63 @@
import { run_all } from '../../shared/utils.js';
let is_task_queued = false;
// Fallback for when requestIdleCallback is not available
const request_idle_callback =
typeof requestIdleCallback === 'undefined'
? (/** @type {() => void} */ cb) => setTimeout(cb, 1)
: requestIdleCallback;
let is_micro_task_queued = false;
let is_idle_task_queued = false;
/** @type {Array<() => void>} */
let current_queued_miro_tasks = [];
/** @type {Array<() => void>} */
let current_queued_tasks = [];
let current_queued_idle_tasks = [];
function process_micro_tasks() {
is_micro_task_queued = false;
const tasks = current_queued_miro_tasks.slice();
current_queued_miro_tasks = [];
run_all(tasks);
}
function process_task() {
is_task_queued = false;
const tasks = current_queued_tasks.slice();
current_queued_tasks = [];
function process_idle_tasks() {
is_idle_task_queued = false;
const tasks = current_queued_idle_tasks.slice();
current_queued_idle_tasks = [];
run_all(tasks);
}
/**
* @param {() => void} fn
*/
export function queue_task(fn) {
if (!is_task_queued) {
is_task_queued = true;
queueMicrotask(process_task);
export function queue_micro_task(fn) {
if (!is_micro_task_queued) {
is_micro_task_queued = true;
queueMicrotask(process_micro_tasks);
}
current_queued_miro_tasks.push(fn);
}
/**
* @param {() => void} fn
*/
export function queue_idle_task(fn) {
if (!is_idle_task_queued) {
is_idle_task_queued = true;
request_idle_callback(process_idle_tasks);
}
current_queued_tasks.push(fn);
current_queued_idle_tasks.push(fn);
}
/**
* Synchronously run any queued tasks.
*/
export function flush_tasks() {
if (is_task_queued) {
process_task();
if (is_micro_task_queued) {
process_micro_tasks();
}
if (is_idle_task_queued) {
process_idle_tasks();
}
}

@ -1,36 +1,20 @@
import { hydrate_nodes, hydrating } from './hydration.js';
import { clone_node, empty } from './operations.js';
import { hydrate_nodes, hydrate_start, hydrating } from './hydration.js';
import { empty } from './operations.js';
import { create_fragment_from_html } from './reconciler.js';
import { current_effect } from '../runtime.js';
import { TEMPLATE_FRAGMENT, TEMPLATE_USE_IMPORT_NODE } from '../../../constants.js';
import { effect } from '../reactivity/effects.js';
import { is_array } from '../utils.js';
/**
* @template {import("#client").TemplateNode | import("#client").TemplateNode[]} T
* @param {T} dom
* @param {import("#client").Effect} effect
* @returns {T}
*/
export function push_template_node(
dom,
effect = /** @type {import('#client').Effect} */ (current_effect)
) {
var current_dom = effect.dom;
if (current_dom === null) {
effect.dom = dom;
} else {
if (!is_array(current_dom)) {
current_dom = effect.dom = [current_dom];
}
function push_template_node(dom) {
var effect = /** @type {import('#client').Effect} */ (current_effect);
if (is_array(dom)) {
current_dom.push(...dom);
} else {
current_dom.push(dom);
}
if (effect.dom === null) {
effect.dom = dom;
}
return dom;
}
/**
@ -48,22 +32,16 @@ export function template(content, flags) {
return () => {
if (hydrating) {
return push_template_node(is_fragment ? hydrate_nodes : hydrate_nodes[0]);
push_template_node(is_fragment ? hydrate_nodes : hydrate_start);
return hydrate_start;
}
if (!node) {
node = create_fragment_from_html(content);
if (!is_fragment) node = /** @type {Node} */ (node.firstChild);
}
var clone = use_import_node ? document.importNode(node, true) : clone_node(node, true);
push_template_node(
is_fragment
? /** @type {import('#client').TemplateNode[]} */ ([...clone.childNodes])
: /** @type {import('#client').TemplateNode} */ (clone)
);
return clone;
return use_import_node ? document.importNode(node, true) : node.cloneNode(true);
};
}
@ -94,43 +72,37 @@ export function template_with_script(content, flags) {
/**
* @param {string} content
* @param {number} flags
* @param {'svg' | 'math'} ns
* @returns {() => Node | Node[]}
*/
/*#__NO_SIDE_EFFECTS__*/
export function svg_template(content, flags) {
export function ns_template(content, flags, ns = 'svg') {
var is_fragment = (flags & TEMPLATE_FRAGMENT) !== 0;
var fn = template(`<svg>${content}</svg>`, 0); // we don't need to worry about using importNode for SVGs
var fn = template(`<${ns}>${content}</${ns}>`, 0); // we don't need to worry about using importNode for namespaced elements
/** @type {Element | DocumentFragment} */
var node;
return () => {
if (hydrating) {
return push_template_node(is_fragment ? hydrate_nodes : hydrate_nodes[0]);
push_template_node(is_fragment ? hydrate_nodes : hydrate_start);
return hydrate_start;
}
if (!node) {
var svg = /** @type {Element} */ (fn());
var wrapper = /** @type {Element} */ (fn());
if ((flags & TEMPLATE_FRAGMENT) === 0) {
node = /** @type {Element} */ (svg.firstChild);
node = /** @type {Element} */ (wrapper.firstChild);
} else {
node = document.createDocumentFragment();
while (svg.firstChild) {
node.appendChild(svg.firstChild);
while (wrapper.firstChild) {
node.appendChild(wrapper.firstChild);
}
}
}
var clone = clone_node(node, true);
push_template_node(
is_fragment
? /** @type {import('#client').TemplateNode[]} */ ([...clone.childNodes])
: /** @type {import('#client').TemplateNode} */ (clone)
);
return clone;
return node.cloneNode(true);
};
}
@ -142,7 +114,7 @@ export function svg_template(content, flags) {
/*#__NO_SIDE_EFFECTS__*/
export function svg_template_with_script(content, flags) {
var first = true;
var fn = svg_template(content, flags);
var fn = ns_template(content, flags);
return () => {
if (hydrating) return fn();
@ -165,40 +137,7 @@ export function svg_template_with_script(content, flags) {
*/
/*#__NO_SIDE_EFFECTS__*/
export function mathml_template(content, flags) {
var is_fragment = (flags & TEMPLATE_FRAGMENT) !== 0;
var fn = template(`<math>${content}</math>`, 0); // we don't need to worry about using importNode for MathML
/** @type {Element | DocumentFragment} */
var node;
return () => {
if (hydrating) {
return push_template_node(is_fragment ? hydrate_nodes : hydrate_nodes[0]);
}
if (!node) {
var math = /** @type {Element} */ (fn());
if ((flags & TEMPLATE_FRAGMENT) === 0) {
node = /** @type {Element} */ (math.firstChild);
} else {
node = document.createDocumentFragment();
while (math.firstChild) {
node.appendChild(math.firstChild);
}
}
}
var clone = clone_node(node, true);
push_template_node(
is_fragment
? /** @type {import('#client').TemplateNode[]} */ ([...clone.childNodes])
: /** @type {import('#client').TemplateNode} */ (clone)
);
return clone;
};
return ns_template(content, flags, 'math');
}
/**
@ -238,9 +177,9 @@ function run_scripts(node) {
*/
/*#__NO_SIDE_EFFECTS__*/
export function text(anchor) {
if (!hydrating) return push_template_node(empty());
if (!hydrating) return empty();
var node = hydrate_nodes[0];
var node = hydrate_start;
if (!node) {
// if an {expression} is empty during SSR, `hydrate_nodes` will be empty.
@ -248,18 +187,20 @@ export function text(anchor) {
anchor.before((node = empty()));
}
return push_template_node(node);
push_template_node(node);
return node;
}
export function comment() {
// we're not delegating to `template` here for performance reasons
if (hydrating) {
return push_template_node(hydrate_nodes);
push_template_node(hydrate_nodes);
return hydrate_start;
}
var frag = document.createDocumentFragment();
var anchor = empty();
frag.append(anchor);
push_template_node([anchor]);
return frag;
}
@ -268,10 +209,17 @@ export function comment() {
* Assign the created (or in hydration mode, traversed) dom elements to the current block
* and insert the elements into the dom (in client mode).
* @param {Text | Comment | Element} anchor
* @param {import('#client').Dom} dom
* @param {DocumentFragment | Element} dom
*/
export function append(anchor, dom) {
if (!hydrating) {
anchor.before(/** @type {Node} */ (dom));
}
if (hydrating) return;
var effect = /** @type {import('#client').Effect} */ (current_effect);
effect.dom =
dom.nodeType === 11
? /** @type {import('#client').TemplateNode[]} */ ([...dom.childNodes])
: /** @type {Element | Comment} */ (dom);
anchor.before(/** @type {Node} */ (dom));
}

@ -161,34 +161,18 @@ export function effect_update_depth_exceeded() {
}
/**
* Missing hydration closing marker
* Failed to hydrate the application
* @returns {never}
*/
export function hydration_missing_marker_close() {
export function hydration_failed() {
if (DEV) {
const error = new Error(`${"hydration_missing_marker_close"}\n${"Missing hydration closing marker"}`);
const error = new Error(`${"hydration_failed"}\n${"Failed to hydrate the application"}`);
error.name = 'Svelte error';
throw error;
} else {
// TODO print a link to the documentation
throw new Error("hydration_missing_marker_close");
}
}
/**
* Missing hydration opening marker
* @returns {never}
*/
export function hydration_missing_marker_open() {
if (DEV) {
const error = new Error(`${"hydration_missing_marker_open"}\n${"Missing hydration opening marker"}`);
error.name = 'Svelte error';
throw error;
} else {
// TODO print a link to the documentation
throw new Error("hydration_missing_marker_open");
throw new Error("hydration_failed");
}
}

@ -27,10 +27,12 @@ export {
set_custom_element_data,
set_dynamic_element_attributes,
set_xlink_attribute,
handle_lazy_img
handle_lazy_img,
set_value,
set_checked
} from './dom/elements/attributes.js';
export { set_class, set_svg_class, set_mathml_class, toggle_class } from './dom/elements/class.js';
export { event, delegate } from './dom/elements/events.js';
export { event, delegate, replay_events } from './dom/elements/events.js';
export { autofocus, remove_textarea_child } from './dom/elements/misc.js';
export { set_style } from './dom/elements/style.js';
export { animation, transition } from './dom/elements/transitions.js';
@ -72,12 +74,13 @@ export {
add_legacy_event_listener,
bubble_event,
reactive_import,
update_legacy_props
update_legacy_props,
default_slot
} from './dom/legacy/misc.js';
export {
append,
comment,
svg_template,
ns_template,
svg_template_with_script,
mathml_template,
template,
@ -154,7 +157,6 @@ export {
} from './dom/operations.js';
export { noop } from '../shared/utils.js';
export {
add_snippet_symbol,
validate_component,
validate_dynamic_element_tag,
validate_snippet,

@ -1,12 +1,6 @@
import { DEV } from 'esm-env';
import {
append_child,
clear_text_content,
create_element,
empty,
init_operations
} from './dom/operations.js';
import { HYDRATION_START, PassiveDelegatedEvents } from '../../constants.js';
import { clear_text_content, create_element, empty, init_operations } from './dom/operations.js';
import { HYDRATION_ERROR, HYDRATION_START, PassiveDelegatedEvents } from '../../constants.js';
import { flush_sync, push, pop, current_component_context } from './runtime.js';
import { effect_root, branch } from './reactivity/effects.js';
import {
@ -137,8 +131,6 @@ export function hydrate(component, options) {
const target = options.target;
const previous_hydrate_nodes = hydrate_nodes;
let hydrated = false;
try {
// Don't flush previous effects to ensure order of outer effects stays consistent
return flush_sync(() => {
@ -153,7 +145,7 @@ export function hydrate(component, options) {
}
if (!node) {
e.hydration_missing_marker_open();
throw HYDRATION_ERROR;
}
const anchor = hydrate_anchor(node);
@ -162,17 +154,14 @@ export function hydrate(component, options) {
// flush_sync will run this callback and then synchronously run any pending effects,
// which don't belong to the hydration phase anymore - therefore reset it here
set_hydrating(false);
hydrated = true;
return instance;
}, false);
} catch (error) {
if (
!hydrated &&
options.recover !== false &&
/** @type {Error} */ (error).message.includes('hydration_missing_marker_close')
) {
w.hydration_mismatch();
if (error === HYDRATION_ERROR) {
if (options.recover === false) {
e.hydration_failed();
}
// If an error occured above, the operations might not yet have been initialised.
init_operations();
@ -180,9 +169,9 @@ export function hydrate(component, options) {
set_hydrating(false);
return mount(component, options);
} else {
throw error;
}
throw error;
} finally {
set_hydrating(!!previous_hydrate_nodes);
set_hydrate_nodes(previous_hydrate_nodes);
@ -332,7 +321,8 @@ export async function append_styles(target, style_sheet_id, styles) {
const style = create_element('style');
style.id = style_sheet_id;
style.textContent = styles;
append_child(/** @type {Document} */ (append_styles_to).head || append_styles_to, style);
const target = /** @type {Document} */ (append_styles_to).head || append_styles_to;
target.appendChild(style);
}
}

@ -28,6 +28,7 @@ import { lifecycle_outside_component } from '../shared/errors.js';
const FLUSH_MICROTASK = 0;
const FLUSH_SYNC = 1;
export const FLUSH_YIELD = 2;
// Used for DEV time error handling
/** @param {WeakSet<Error>} value */
@ -36,6 +37,8 @@ const handled_errors = new WeakSet();
let current_scheduler_mode = FLUSH_MICROTASK;
// Used for handling scheduling
let is_micro_task_queued = false;
let is_yield_task_queued = false;
export let is_flushing_effect = false;
export let is_destroying_effect = false;
@ -420,8 +423,8 @@ function remove_reaction(signal, dependency) {
}
}
if (reactions_length === 0 && (dependency.f & UNOWNED) !== 0) {
// If the signal is unowned then we need to make sure to change it to dirty.
set_signal_status(dependency, DIRTY);
// If the signal is unowned then we need to make sure to change it to maybe dirty.
set_signal_status(dependency, MAYBE_DIRTY);
remove_reactions(/** @type {import('#client').Derived} **/ (dependency), 0);
}
}
@ -521,13 +524,17 @@ function infinite_loop_guard() {
* @returns {void}
*/
function flush_queued_root_effects(root_effects) {
const length = root_effects.length;
if (length === 0) {
return;
}
infinite_loop_guard();
var previously_flushing_effect = is_flushing_effect;
is_flushing_effect = true;
try {
for (var i = 0; i < root_effects.length; i++) {
for (var i = 0; i < length; i++) {
var effect = root_effects[i];
// When working with custom elements, the root effects might not have a root
@ -563,19 +570,31 @@ function flush_queued_effects(effects) {
}
}
function process_microtask() {
function process_deferred() {
is_micro_task_queued = false;
is_yield_task_queued = false;
if (flush_count > 101) {
return;
}
const previous_queued_root_effects = current_queued_root_effects;
current_queued_root_effects = [];
flush_queued_root_effects(previous_queued_root_effects);
if (!is_micro_task_queued) {
if (!is_micro_task_queued && !is_yield_task_queued) {
flush_count = 0;
}
}
async function yield_tick() {
// TODO: replace this with scheduler.yield when it becomes standard
await new Promise((fulfil) => {
requestAnimationFrame(() => {
setTimeout(fulfil, 0);
});
// In case of being within background tab, the rAF won't fire
setTimeout(fulfil, 100);
});
}
/**
* @param {import('#client').Effect} signal
* @returns {void}
@ -584,7 +603,12 @@ export function schedule_effect(signal) {
if (current_scheduler_mode === FLUSH_MICROTASK) {
if (!is_micro_task_queued) {
is_micro_task_queued = true;
queueMicrotask(process_microtask);
queueMicrotask(process_deferred);
}
} else if (current_scheduler_mode === FLUSH_YIELD) {
if (!is_yield_task_queued) {
is_yield_task_queued = true;
yield_tick().then(process_deferred);
}
}
@ -684,6 +708,19 @@ function process_effects(effect, collected_effects) {
}
}
/**
* @param {{ (): void; (): any; }} fn
*/
export function yield_updates(fn) {
const previous_scheduler_mode = current_scheduler_mode;
try {
current_scheduler_mode = FLUSH_YIELD;
return fn();
} finally {
current_scheduler_mode = previous_scheduler_mode;
}
}
/**
* Internal version of `flushSync` with the option to not flush previous effects.
* Returns the result of the passed function, if given.
@ -729,7 +766,7 @@ export function flush_sync(fn, flush_previous = true) {
* @returns {Promise<void>}
*/
export async function tick() {
await Promise.resolve();
await yield_tick();
// By calling flush_sync we guarantee that any pending state changes are applied after one tick.
// TODO look into whether we can make flushing subsequent updates synchronously in the future.
flush_sync();

@ -21,11 +21,12 @@ export function hydration_attribute_changed(attribute, html, value) {
}
/**
* Hydration failed because the initial UI does not match what was rendered on the server
* Hydration failed because the initial UI does not match what was rendered on the server. The error occurred near %location%
* @param {string | undefined | null} [location]
*/
export function hydration_mismatch() {
export function hydration_mismatch(location) {
if (DEV) {
console.warn(`%c[svelte] ${"hydration_mismatch"}\n%c${"Hydration failed because the initial UI does not match what was rendered on the server"}`, bold, normal);
console.warn(`%c[svelte] ${"hydration_mismatch"}\n%c${location ? `Hydration failed because the initial UI does not match what was rendered on the server. The error occurred near ${location}` : "Hydration failed because the initial UI does not match what was rendered on the server"}`, bold, normal);
} else {
// TODO print a link to the documentation
console.warn("hydration_mismatch");
@ -66,7 +67,7 @@ export function ownership_invalid_binding(parent, child, owner) {
*/
export function ownership_invalid_mutation(component, owner) {
if (DEV) {
console.warn(`%c[svelte] ${"ownership_invalid_mutation"}\n%c${`${component} mutated a value owned by ${owner}. This is strongly discouraged. Consider passing values to child components with \`bind:\`, or use a callback instead`}`, bold, normal);
console.warn(`%c[svelte] ${"ownership_invalid_mutation"}\n%c${component ? `${component} mutated a value owned by ${owner}. This is strongly discouraged. Consider passing values to child components with \`bind:\`, or use a callback instead` : "Mutating a value outside the component that created it is strongly discouraged. Consider passing values to child components with `bind:`, or use a callback instead"}`, bold, normal);
} else {
// TODO print a link to the documentation
console.warn("ownership_invalid_mutation");

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

Loading…
Cancel
Save