pull/11773/head
Rich Harris 2 years ago
commit f35874295e

@ -0,0 +1,5 @@
---
"svelte": patch
---
fix: address derived memory leak on disconnection from reactive graph

@ -0,0 +1,5 @@
---
"svelte": patch
---
fix: `$state.is` missing second argument on the server

@ -0,0 +1,5 @@
---
"svelte": patch
---
fix: set correct scope for `@const` tags within slots

@ -0,0 +1,5 @@
---
"svelte": patch
---
fix: handle `this` parameter in TypeScript-annotated functions

@ -0,0 +1,5 @@
---
'svelte': patch
---
fix: allow classes to be reassigned

@ -0,0 +1,5 @@
---
"svelte": patch
---
fix: capture the correct event names when spreading attributes

@ -0,0 +1,5 @@
---
"svelte": patch
---
feat: provide `Component` type that represents the new shape of Svelte components

@ -0,0 +1,5 @@
---
"svelte": patch
---
fix: better support for onwheel events in chrome

@ -0,0 +1,5 @@
---
"svelte": patch
---
fix: prevent buggy ownership warning when reassigning state

@ -13,6 +13,7 @@
"afraid-geckos-dance",
"afraid-kids-hide",
"afraid-moose-matter",
"afraid-worms-drum",
"angry-books-jam",
"angry-plums-punch",
"angry-wasps-help",
@ -56,6 +57,7 @@
"chilled-pumas-invite",
"chilled-seas-jog",
"chilly-dolphins-lick",
"chilly-pans-raise",
"chilly-rocks-hug",
"chilly-snakes-scream",
"clean-eels-beg",
@ -63,6 +65,7 @@
"clever-maps-travel",
"clever-rockets-burn",
"clever-sloths-push",
"clever-stingrays-shout",
"cold-birds-own",
"cold-cheetahs-judge",
"cold-masks-learn",
@ -97,6 +100,7 @@
"dry-fans-march",
"dry-pillows-exist",
"dull-coins-vanish",
"dull-donkeys-smell",
"dull-mangos-wave",
"dull-pots-add",
"dull-roses-relate",
@ -138,6 +142,7 @@
"fifty-steaks-float",
"five-tigers-search",
"flat-melons-protect",
"flat-olives-live",
"fluffy-dolls-share",
"fluffy-humans-worry",
"fluffy-ravens-juggle",
@ -153,6 +158,7 @@
"fresh-beds-wash",
"fresh-dots-destroy",
"fresh-impalas-bow",
"fresh-walls-bathe",
"fresh-weeks-trade",
"friendly-candles-relate",
"friendly-lies-camp",
@ -166,6 +172,7 @@
"gentle-spies-happen",
"gentle-toys-chew",
"gentle-trees-exercise",
"gentle-wasps-pull",
"giant-bananas-turn",
"giant-moons-own",
"giant-planets-shake",
@ -190,10 +197,12 @@
"grumpy-avocados-fetch",
"grumpy-jars-sparkle",
"happy-beds-scream",
"happy-dogs-jump",
"happy-suits-film",
"healthy-ants-film",
"healthy-planes-vanish",
"heavy-comics-move",
"heavy-doors-applaud",
"heavy-ducks-leave",
"heavy-ears-rule",
"hip-balloons-begin",
@ -275,9 +284,11 @@
"mean-jokes-exist",
"metal-clouds-raise",
"metal-lobsters-burn",
"metal-pans-trade",
"mighty-cooks-scream",
"mighty-files-hammer",
"mighty-frogs-obey",
"modern-apricots-promise",
"moody-carrots-lay",
"moody-frogs-exist",
"moody-ghosts-buy",
@ -375,6 +386,7 @@
"rotten-rules-invite",
"rude-ghosts-tickle",
"selfish-dragons-knock",
"selfish-panthers-add",
"selfish-socks-smile",
"selfish-spies-help",
"selfish-tools-hide",
@ -418,7 +430,9 @@
"sixty-items-crash",
"sixty-numbers-hope",
"sixty-pandas-rush",
"sleepy-cats-eat",
"slimy-clouds-talk",
"slimy-hairs-impress",
"slimy-laws-explode",
"slimy-onions-approve",
"slimy-walls-draw",
@ -437,6 +451,7 @@
"smart-turkeys-tell",
"smart-zebras-pay",
"smooth-kids-protect",
"smooth-pens-protect",
"smooth-rings-rush",
"soft-clocks-remember",
"soft-geese-learn",
@ -444,6 +459,7 @@
"soft-tigers-wink",
"sour-bags-fail",
"sour-forks-stare",
"sour-geese-listen",
"sour-jeans-collect",
"sour-rules-march",
"sour-weeks-fix",
@ -459,6 +475,7 @@
"stale-fans-rest",
"stale-jeans-refuse",
"strange-apricots-happen",
"strange-roses-brake",
"strong-apricots-destroy",
"strong-gifts-smoke",
"strong-lemons-provide",
@ -518,6 +535,7 @@
"tiny-meals-deliver",
"tiny-moose-kiss",
"tough-radios-punch",
"tough-tomatoes-explain",
"twelve-beans-drive",
"twelve-dragons-join",
"twelve-onions-juggle",

@ -0,0 +1,5 @@
---
"svelte": patch
---
fix: allow global next to `&` for nesting

@ -0,0 +1,5 @@
---
'svelte': patch
---
fix: parse ongotpointercapture and onlostpointercapture events correctly

@ -0,0 +1,5 @@
---
"svelte": patch
---
fix: coherent infinite loop guard

@ -0,0 +1,5 @@
---
"svelte": patch
---
fix: address regressed memory leak

@ -0,0 +1,5 @@
---
"svelte": patch
---
fix: make prop fallback values deeply reactive if needed

@ -0,0 +1,5 @@
---
"svelte": patch
---
fix: robustify initial scroll value detection when scroll is smooth

@ -25,12 +25,14 @@ jobs:
os: ubuntu-latest
- node-version: 20
os: ubuntu-latest
- node-version: 21
- node-version: 22
os: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: pnpm/action-setup@v2.2.4
with:
version: 9
- uses: actions/setup-node@v3
with:
node-version: ${{ matrix.node-version }}
@ -46,6 +48,8 @@ jobs:
steps:
- uses: actions/checkout@v3
- uses: pnpm/action-setup@v2.2.4
with:
version: 9
- uses: actions/setup-node@v3
with:
node-version: 18

@ -22,6 +22,8 @@ jobs:
# This makes Actions fetch all Git history so that Changesets can generate changelogs with the correct commits
fetch-depth: 0
- uses: pnpm/action-setup@v2.2.4
with:
version: 9
- name: Setup Node.js
uses: actions/setup-node@v3
with:

@ -1,46 +1,3 @@
packages/**/dist/*.js
packages/**/build/*.js
packages/**/npm/**/*
packages/**/config/*.js
packages/svelte/messages/**/*.md
packages/svelte/src/compiler/errors.js
packages/svelte/src/compiler/warnings.js
packages/svelte/src/internal/client/errors.js
packages/svelte/src/internal/client/warnings.js
packages/svelte/src/internal/shared/errors.js
packages/svelte/src/internal/shared/warnings.js
packages/svelte/src/internal/server/errors.js
packages/svelte/tests/**/*.svelte
packages/svelte/tests/**/_expected*
packages/svelte/tests/**/_actual*
packages/svelte/tests/**/expected*
packages/svelte/tests/**/_output
packages/svelte/tests/**/shards/*.test.js
packages/svelte/tests/hydration/samples/*/_expected.html
packages/svelte/tests/hydration/samples/*/_override.html
packages/svelte/types
packages/svelte/compiler/index.js
playgrounds/demo/src
playgrounds/sandbox/input/**.svelte
playgrounds/sandbox/output
sites/svelte.dev/static/svelte-app.json
sites/svelte.dev/scripts/svelte-app/
sites/svelte.dev/src/routes/_components/Supporters/contributors.jpg
sites/svelte.dev/src/routes/_components/Supporters/contributors.js
sites/svelte.dev/src/routes/_components/Supporters/donors.jpg
sites/svelte.dev/src/routes/_components/Supporters/donors.js
sites/svelte.dev/src/lib/generated
**/node_modules
**/.svelte-kit
**/.vercel
.github/CODEOWNERS
.prettierignore
.changeset
pnpm-lock.yaml
pnpm-workspace.yaml
# Temporarily ignore this file to avoid merge conflicts.
# see: https://github.com/sveltejs/svelte/pull/9609
documentation/docs/05-misc/03-typescript.md

@ -5,9 +5,8 @@
"private": true,
"type": "module",
"license": "MIT",
"packageManager": "pnpm@8.6.12",
"engines": {
"pnpm": "^8.0.0"
"pnpm": "^9.0.0"
},
"repository": {
"type": "git",
@ -18,8 +17,8 @@
"build:sites": "pnpm -r --filter=./sites/* build",
"preview-site": "npm run build --prefix sites/svelte-5-preview",
"check": "cd packages/svelte && pnpm build && cd ../../ && pnpm -r check",
"format": "prettier --write .",
"lint": "prettier --check . && eslint",
"lint": "pnpm -r lint && prettier --check documentation",
"format": "pnpm -r format && prettier --check --write documentation",
"test": "vitest run",
"test-output": "vitest run --coverage --reporter=json --outputFile=sites/svelte-5-preview/src/routes/status/results.json",
"changeset:version": "changeset version && pnpm -r generate:version && git add --all",
@ -40,7 +39,7 @@
"prettier": "^3.2.4",
"prettier-plugin-svelte": "^3.1.2",
"typescript": "^5.3.3",
"typescript-eslint": "^7.6.0",
"typescript-eslint": "^8.0.0-alpha.20",
"vitest": "^1.2.1"
},
"pnpm": {

@ -0,0 +1,22 @@
dist/*.js
build/*.js
npm/**/*
config/*.js
messages/**/*.md
src/compiler/errors.js
src/compiler/warnings.js
src/internal/client/errors.js
src/internal/client/warnings.js
src/internal/shared/errors.js
src/internal/shared/warnings.js
src/internal/server/errors.js
tests/**/*.svelte
tests/**/_expected*
tests/**/_actual*
tests/**/expected*
tests/**/_output
tests/**/shards/*.test.js
tests/hydration/samples/*/_expected.html
tests/hydration/samples/*/_override.html
types
compiler/index.js

@ -1,5 +1,57 @@
# svelte
## 5.0.0-next.145
### Patch Changes
- fix: `$state.is` missing second argument on the server ([#11835](https://github.com/sveltejs/svelte/pull/11835))
- fix: prevent buggy ownership warning when reassigning state ([#11812](https://github.com/sveltejs/svelte/pull/11812))
- fix: address regressed memory leak ([#11832](https://github.com/sveltejs/svelte/pull/11832))
## 5.0.0-next.144
### Patch Changes
- fix: address derived memory leak on disconnection from reactive graph ([#11819](https://github.com/sveltejs/svelte/pull/11819))
- fix: set correct scope for `@const` tags within slots ([#11798](https://github.com/sveltejs/svelte/pull/11798))
- fix: better support for onwheel events in chrome ([#11808](https://github.com/sveltejs/svelte/pull/11808))
- fix: coherent infinite loop guard ([#11815](https://github.com/sveltejs/svelte/pull/11815))
- fix: make prop fallback values deeply reactive if needed ([#11804](https://github.com/sveltejs/svelte/pull/11804))
- fix: robustify initial scroll value detection when scroll is smooth ([#11802](https://github.com/sveltejs/svelte/pull/11802))
## 5.0.0-next.143
### Patch Changes
- feat: provide `Component` type that represents the new shape of Svelte components ([#11775](https://github.com/sveltejs/svelte/pull/11775))
## 5.0.0-next.142
### Patch Changes
- fix: allow runelike writable as prop ([#11768](https://github.com/sveltejs/svelte/pull/11768))
- fix: support `array.lastIndexOf` without second argument ([#11766](https://github.com/sveltejs/svelte/pull/11766))
- fix: handle `this` parameter in TypeScript-annotated functions ([#11795](https://github.com/sveltejs/svelte/pull/11795))
- fix: allow classes to be reassigned ([#11794](https://github.com/sveltejs/svelte/pull/11794))
- fix: capture the correct event names when spreading attributes ([#11783](https://github.com/sveltejs/svelte/pull/11783))
- fix: allow global next to `&` for nesting ([#11784](https://github.com/sveltejs/svelte/pull/11784))
- fix: parse ongotpointercapture and onlostpointercapture events correctly ([#11790](https://github.com/sveltejs/svelte/pull/11790))
- fix: only inject push/pop in SSR components when necessary ([#11771](https://github.com/sveltejs/svelte/pull/11771))
## 5.0.0-next.141
### Patch Changes

@ -2,7 +2,7 @@
"name": "svelte",
"description": "Cybernetically enhanced web apps",
"license": "MIT",
"version": "5.0.0-next.141",
"version": "5.0.0-next.145",
"type": "module",
"types": "./types/index.d.ts",
"engines": {
@ -106,6 +106,8 @@
"generate:version": "node ./scripts/generate-version.js",
"generate:types": "node ./scripts/generate-types.js && tsc -p tsconfig.generated.json",
"prepublishOnly": "pnpm build",
"format": "prettier --check --write .",
"lint": "prettier --check . && eslint",
"knip": "knip"
},
"devDependencies": {

@ -1,6 +1,17 @@
import { walk } from 'zimmerframe';
import * as b from '../../utils/builders.js';
/**
* @param {import('estree').FunctionExpression | import('estree').FunctionDeclaration} node
* @param {import('zimmerframe').Context<any, any>} context
*/
function remove_this_param(node, context) {
if (node.params[0]?.type === 'Identifier' && node.params[0].name === 'this') {
node.params.shift();
}
return context.next();
}
/** @type {import('zimmerframe').Visitors<any, null>} */
const visitors = {
ImportDeclaration(node) {
@ -71,7 +82,9 @@ const visitors = {
};
}
return node;
}
},
FunctionExpression: remove_this_param,
FunctionDeclaration: remove_this_param
};
/**

@ -271,7 +271,6 @@ export default function tag(parser) {
const chunk = definition.value[0];
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>`.

@ -18,7 +18,12 @@ import { validation_legacy, validation_runes, validation_runes_js } from './vali
import check_graph_for_cycles from './utils/check_graph_for_cycles.js';
import { regex_starts_with_newline } from '../patterns.js';
import { create_attribute, is_element_node } from '../nodes.js';
import { DelegatedEvents, namespace_mathml, namespace_svg } from '../../../constants.js';
import {
DelegatedEvents,
is_capture_event,
namespace_mathml,
namespace_svg
} from '../../../constants.js';
import { should_proxy_or_freeze } from '../3-transform/client/utils.js';
import { analyze_css } from './css/css-analyze.js';
import { prune } from './css/css-prune.js';
@ -1508,23 +1513,13 @@ function determine_element_spread(node) {
* @param {string} event_name
*/
function get_attribute_event_name(event_name) {
if (is_capture_event(event_name)) {
if (is_capture_event(event_name, 'include-on')) {
event_name = event_name.slice(0, -7);
}
event_name = event_name.slice(2);
return event_name;
}
/**
* @param {string} name
* @returns boolean
*/
function is_capture_event(name) {
return (
name.endsWith('capture') && name !== 'ongotpointercapture' && name !== 'onlostpointercapture'
);
}
/**
* @param {Map<import('estree').LabeledStatement, import('../types.js').ReactiveStatement>} unsorted_reactive_declarations
*/

@ -203,7 +203,7 @@ export function serialize_set_binding(node, context, fallback, options) {
assignment.right =
private_state.kind === 'frozen_state'
? b.call('$.freeze', value)
: b.call('$.proxy', value);
: serialize_proxy_reassignment(value, private_state.id, state);
return assignment;
}
}
@ -216,7 +216,7 @@ export function serialize_set_binding(node, context, fallback, options) {
should_proxy_or_freeze(value, context.state.scope)
? private_state.kind === 'frozen_state'
? b.call('$.freeze', value)
: b.call('$.proxy', value)
: serialize_proxy_reassignment(value, private_state.id, state)
: value
);
}
@ -240,7 +240,7 @@ export function serialize_set_binding(node, context, fallback, options) {
assignment.right =
public_state.kind === 'frozen_state'
? b.call('$.freeze', value)
: b.call('$.proxy', value);
: serialize_proxy_reassignment(value, public_state.id, state);
return assignment;
}
}
@ -305,7 +305,7 @@ export function serialize_set_binding(node, context, fallback, options) {
context.state.analysis.runes &&
!options?.skip_proxy_and_freeze &&
should_proxy_or_freeze(value, context.state.scope)
? b.call('$.proxy', value)
? serialize_proxy_reassignment(value, left_name, state)
: value
);
} else if (binding.kind === 'frozen_state') {
@ -410,6 +410,25 @@ export function serialize_set_binding(node, context, fallback, options) {
return serialize();
}
/**
* @param {import('estree').Expression} value
* @param {import('estree').PrivateIdentifier | string} proxy_reference
* @param {import('./types').ClientTransformState} state
*/
export function serialize_proxy_reassignment(value, proxy_reference, state) {
return state.options.dev
? b.call(
'$.proxy',
value,
b.true,
b.null,
typeof proxy_reference === 'string'
? b.id(proxy_reference)
: b.member(b.this, proxy_reference)
)
: b.call('$.proxy', value);
}
/**
* @param {import('estree').ArrowFunctionExpression | import('estree').FunctionExpression} node
* @param {import('./types').ComponentContext} context

@ -2,7 +2,12 @@ import { get_rune } from '../../../scope.js';
import { is_hoistable_function, transform_inspect_rune } from '../../utils.js';
import * as b from '../../../../utils/builders.js';
import * as assert from '../../../../utils/assert.js';
import { get_prop_source, is_state_source, should_proxy_or_freeze } from '../utils.js';
import {
get_prop_source,
is_state_source,
serialize_proxy_reassignment,
should_proxy_or_freeze
} from '../utils.js';
import { extract_paths } from '../../../../utils/ast.js';
import { regex_invalid_identifier_chars } from '../../../patterns.js';
@ -139,7 +144,11 @@ export const javascript_visitors_runes = {
'set',
definition.key,
[value],
[b.stmt(b.call('$.set', member, b.call('$.proxy', value)))]
[
b.stmt(
b.call('$.set', member, serialize_proxy_reassignment(value, field.id, state))
)
]
)
);
}
@ -246,9 +255,14 @@ export const javascript_visitors_runes = {
property.value.type === 'AssignmentPattern' ? property.value.left : property.value;
assert.equal(id.type, 'Identifier');
const binding = /** @type {import('#compiler').Binding} */ (state.scope.get(id.name));
const initial =
let initial =
binding.initial &&
/** @type {import('estree').Expression} */ (visit(binding.initial));
// We're adding proxy here on demand and not within the prop runtime function so that
// people not using proxied state anywhere in their code don't have to pay the additional bundle size cost
if (initial && binding.mutated && should_proxy_or_freeze(initial, state.scope)) {
initial = b.call('$.proxy', initial);
}
if (binding.reassigned || state.analysis.accessors || initial) {
declarations.push(b.declarator(id, get_prop_source(binding, state, name, initial)));

@ -32,6 +32,7 @@ import {
EACH_IS_STRICT_EQUALS,
EACH_ITEM_REACTIVE,
EACH_KEYED,
is_capture_event,
TEMPLATE_FRAGMENT,
TEMPLATE_USE_IMPORT_NODE,
TRANSITION_GLOBAL,
@ -846,7 +847,13 @@ function serialize_inline_component(node, component_name, context) {
/** @type {import('estree').Property[]} */
const serialized_slots = [];
for (const slot_name of Object.keys(children)) {
const body = create_block(node, `${node.name}_${slot_name}`, children[slot_name], context);
const body = create_block(
node,
node.fragment,
`${node.name}_${slot_name}`,
children[slot_name],
context
);
if (body.length === 0) continue;
const slot_fn = b.arrow(
@ -1022,13 +1029,14 @@ function serialize_locations(locations) {
* ```
* Adds the hoisted parts to `context.state.hoisted` and returns the statements of the main block.
* @param {import('#compiler').SvelteNode} parent
* @param {import('#compiler').Fragment} fragment
* @param {string} name
* @param {import('#compiler').SvelteNode[]} nodes
* @param {import('../types.js').ComponentContext} context
* @returns {import('estree').Statement[]}
*/
function create_block(parent, name, nodes, context) {
const namespace = infer_namespace(context.state.metadata.namespace, parent, nodes, context.path);
function create_block(parent, fragment, name, nodes, context) {
const namespace = infer_namespace(context.state.metadata.namespace, parent, nodes);
const { hoisted, trimmed } = clean_nodes(
parent,
@ -1059,6 +1067,7 @@ function create_block(parent, name, nodes, context) {
/** @type {import('../types').ComponentClientTransformState} */
const state = {
...context.state,
scope: context.state.scopes.get(fragment) ?? context.state.scope,
before_init: [],
init: [],
update: [],
@ -1410,11 +1419,7 @@ function serialize_event_attribute(node, context) {
const modifiers = [];
let event_name = node.name.slice(2);
if (
event_name.endsWith('capture') &&
event_name !== 'ongotpointercapture' &&
event_name !== 'onlostpointercapture'
) {
if (is_capture_event(event_name)) {
event_name = event_name.slice(0, -7);
modifiers.push('capture');
}
@ -1619,7 +1624,7 @@ function serialize_attribute_value(attribute_value, context) {
/**
* @param {Array<import('#compiler').Text | import('#compiler').ExpressionTag>} values
* @param {(node: import('#compiler').SvelteNode) => any} visit
* @param {(node: import('#compiler').SvelteNode, state: any) => any} visit
* @param {import("../types.js").ComponentClientTransformState} state
* @returns {[boolean, import('estree').TemplateLiteral]}
*/
@ -1664,13 +1669,13 @@ function serialize_template_literal(values, visit, state) {
id,
create_derived(
state,
b.thunk(/** @type {import('estree').Expression} */ (visit(node.expression)))
b.thunk(/** @type {import('estree').Expression} */ (visit(node.expression, state)))
)
)
);
expressions.push(b.call('$.get', id));
} else {
expressions.push(b.call('$.stringify', visit(node.expression)));
expressions.push(b.call('$.stringify', visit(node.expression, state)));
}
quasis.push(b.quasi('', i + 1 === values.length));
}
@ -1683,7 +1688,7 @@ function serialize_template_literal(values, visit, state) {
/** @type {import('../types').ComponentVisitors} */
export const template_visitors = {
Fragment(node, context) {
const body = create_block(node, 'root', node.nodes, context);
const body = create_block(context.path.at(-1) ?? node, node, 'root', node.nodes, context);
return b.block(body);
},
Comment(node, context) {
@ -2224,7 +2229,7 @@ export const template_visitors = {
}
inner.push(...inner_context.state.after_update);
inner.push(
...create_block(node, 'dynamic_element', node.fragment.nodes, {
...create_block(node, node.fragment, 'dynamic_element', node.fragment.nodes, {
...context,
state: {
...context.state,
@ -2452,7 +2457,7 @@ export const template_visitors = {
}
// TODO should use context.visit?
const children = create_block(node, 'each_block', node.body.nodes, context);
const children = create_block(node, node.body, 'each_block', node.body.nodes, context);
const key_function = node.key
? b.arrow(
@ -3020,22 +3025,14 @@ export const template_visitors = {
}
}
const state = {
...context.state,
// TODO this logic eventually belongs in create_block, when fragments are used everywhere
scope: /** @type {import('../../../scope').Scope} */ (context.state.scopes.get(node.fragment))
};
context.state.init.push(...lets);
context.state.init.push(
...create_block(
node,
node.fragment,
'slot_template',
/** @type {import('#compiler').SvelteNode[]} */ (node.fragment.nodes),
{
...context,
state
}
context
)
);
},
@ -3091,7 +3088,7 @@ export const template_visitors = {
? b.literal(null)
: b.arrow(
[b.id('$$anchor')],
b.block(create_block(node, 'fallback', node.fragment.nodes, context))
b.block(create_block(node, node.fragment, 'fallback', node.fragment.nodes, context))
);
const expression = is_default
@ -3109,7 +3106,7 @@ export const template_visitors = {
'$.head',
b.arrow(
[b.id('$$anchor')],
b.block(create_block(node, 'head', node.fragment.nodes, context))
b.block(create_block(node, node.fragment, 'head', node.fragment.nodes, context))
)
)
)

@ -238,6 +238,13 @@ const visitors = {
}
}
// for any :global() at the middle of compound selector
for (const selector of relative_selector.selectors) {
if (selector.type === 'PseudoClassSelector' && selector.name === 'global') {
remove_global_pseudo_class(selector);
}
}
if (relative_selector.selectors.some((s) => s.type === 'NestingSelector')) {
continue;
}
@ -250,13 +257,6 @@ const visitors = {
context.state.specificity.bumped = true;
// for any :global() at the middle of compound selector
for (const selector of relative_selector.selectors) {
if (selector.type === 'PseudoClassSelector' && selector.name === 'global') {
remove_global_pseudo_class(selector);
}
}
let i = relative_selector.selectors.length;
while (i--) {
const selector = relative_selector.selectors[i];

@ -242,13 +242,14 @@ function process_children(nodes, parent, { visit, state }) {
/**
* @param {import('#compiler').SvelteNode} parent
* @param {import('#compiler').Fragment} fragment
* @param {import('#compiler').SvelteNode[]} nodes
* @param {import('./types').ComponentContext} context
* @param {import('./types').Anchor} [anchor]
* @returns {import('estree').Statement[]}
*/
function create_block(parent, nodes, context, anchor) {
const namespace = infer_namespace(context.state.metadata.namespace, parent, nodes, context.path);
function create_block(parent, fragment, nodes, context, anchor) {
const namespace = infer_namespace(context.state.metadata.namespace, parent, nodes);
const { hoisted, trimmed } = clean_nodes(
parent,
@ -266,6 +267,7 @@ function create_block(parent, nodes, context, anchor) {
/** @type {import('./types').ComponentServerTransformState} */
const state = {
...context.state,
scope: context.state.scopes.get(fragment) ?? context.state.scope,
init: [],
template: [],
metadata: {
@ -791,7 +793,8 @@ const javascript_visitors_runes = {
if (rune === '$state.is') {
return b.call(
'Object.is',
/** @type {import('estree').Expression} */ (context.visit(node.arguments[0]))
/** @type {import('estree').Expression} */ (context.visit(node.arguments[0])),
/** @type {import('estree').Expression} */ (context.visit(node.arguments[1]))
);
}
@ -1087,7 +1090,7 @@ function serialize_inline_component(node, component_name, context) {
const serialized_slots = [];
for (const slot_name of Object.keys(children)) {
const body = create_block(node, children[slot_name], context);
const body = create_block(node, node.fragment, children[slot_name], context);
if (body.length === 0) continue;
const slot_fn = b.arrow(
@ -1270,7 +1273,7 @@ const javascript_visitors_legacy = {
/** @type {import('./types').ComponentVisitors} */
const template_visitors = {
Fragment(node, context) {
const body = create_block(node, node.nodes, context);
const body = create_block(context.path.at(-1) ?? node, node, node.nodes, context);
return b.block(body);
},
HtmlTag(node, context) {
@ -1472,7 +1475,7 @@ const template_visitors = {
}
};
const main = create_block(node, node.fragment.nodes, {
const main = create_block(node, node.fragment, node.fragment.nodes, {
...context,
state: { ...context.state, metadata }
});
@ -1541,7 +1544,9 @@ const template_visitors = {
each.push(b.stmt(b.assignment('+=', b.id('$$payload.out'), b.literal(block_open.value))));
each.push(
.../** @type {import('estree').Statement[]} */ (create_block(node, children, context))
.../** @type {import('estree').Statement[]} */ (
create_block(node, node.body, children, context)
)
);
each.push(b.stmt(b.assignment('+=', b.id('$$payload.out'), b.literal(block_close.value))));
@ -1556,7 +1561,7 @@ const template_visitors = {
const close = b.stmt(b.assignment('+=', b.id('$$payload.out'), b.literal(BLOCK_CLOSE)));
if (node.fallback) {
const fallback = create_block(node, node.fallback.nodes, context);
const fallback = create_block(node, node.fallback, node.fallback.nodes, context);
fallback.push(b.stmt(b.assignment('+=', b.id('$$payload.out'), b.literal(BLOCK_CLOSE_ELSE))));
@ -1577,8 +1582,10 @@ const template_visitors = {
const state = context.state;
state.template.push(block_open);
const consequent = create_block(node, node.consequent.nodes, context);
const alternate = node.alternate ? create_block(node, node.alternate.nodes, context) : [];
const consequent = create_block(node, node.consequent, node.consequent.nodes, context);
const alternate = node.alternate
? create_block(node, node.alternate, node.alternate.nodes, context)
: [];
consequent.push(b.stmt(b.assignment('+=', b.id('$$payload.out'), b.literal(BLOCK_CLOSE))));
alternate.push(b.stmt(b.assignment('+=', b.id('$$payload.out'), b.literal(BLOCK_CLOSE_ELSE))));
@ -1634,7 +1641,7 @@ const template_visitors = {
KeyBlock(node, context) {
const state = context.state;
state.template.push(block_open);
const body = create_block(node, node.fragment.nodes, context);
const body = create_block(node, node.fragment, node.fragment.nodes, context);
state.template.push(t_statement(b.block(body)));
state.template.push(block_close);
},
@ -1724,15 +1731,7 @@ const template_visitors = {
}
}
const state = {
...context.state,
// TODO this logic eventually belongs in create_block, when fragments are used everywhere
scope: /** @type {import('../../scope').Scope} */ (context.state.scopes.get(node.fragment))
};
const body = create_block(node, node.fragment.nodes, {
...context,
state
});
const body = create_block(node, node.fragment, node.fragment.nodes, context);
context.state.template.push(t_statement(b.block(body)));
},
@ -1802,7 +1801,7 @@ const template_visitors = {
const fallback =
node.fragment.nodes.length === 0
? b.literal(null)
: b.thunk(b.block(create_block(node, node.fragment.nodes, context)));
: b.thunk(b.block(create_block(node, node.fragment, node.fragment.nodes, context)));
const slot = b.call('$.slot', b.id('$$payload'), expression, props_expression, fallback);
state.template.push(t_statement(b.stmt(slot)));
@ -1810,7 +1809,7 @@ const template_visitors = {
},
SvelteHead(node, context) {
const state = context.state;
const body = create_block(node, node.fragment.nodes, context);
const body = create_block(node, node.fragment, node.fragment.nodes, context);
state.template.push(
t_statement(
b.stmt(b.call('$.head', b.id('$$payload'), b.arrow([b.id('$$payload')], b.block(body))))

@ -155,35 +155,28 @@ export function clean_nodes(
* @param {import('#compiler').Namespace} namespace
* @param {import('#compiler').SvelteNode} parent
* @param {import('#compiler').SvelteNode[]} nodes
* @param {import('#compiler').SvelteNode[]} path
*/
export function infer_namespace(namespace, parent, nodes, path) {
const parent_node =
parent.type === 'Fragment'
? // Messy: We know that Fragment calls create_block directly, so we can do this here
path.at(-1)
: parent;
export function infer_namespace(namespace, parent, nodes) {
if (namespace !== 'foreign') {
if (parent_node?.type === 'RegularElement' && parent_node.name === 'foreignObject') {
if (parent.type === 'RegularElement' && parent.name === 'foreignObject') {
return 'html';
}
if (parent_node?.type === 'RegularElement' || parent_node?.type === 'SvelteElement') {
if (parent_node.metadata.svg) {
if (parent.type === 'RegularElement' || parent.type === 'SvelteElement') {
if (parent.metadata.svg) {
return 'svg';
}
return parent_node.metadata.mathml ? 'mathml' : 'html';
return parent.metadata.mathml ? 'mathml' : 'html';
}
// Re-evaluate the namespace inside slot nodes that reset the namespace
if (
parent_node === undefined ||
parent_node.type === 'Root' ||
parent_node.type === 'Component' ||
parent_node.type === 'SvelteComponent' ||
parent_node.type === 'SvelteFragment' ||
parent_node.type === 'SnippetBlock'
parent.type === 'Fragment' ||
parent.type === 'Root' ||
parent.type === 'Component' ||
parent.type === 'SvelteComponent' ||
parent.type === 'SvelteFragment' ||
parent.type === 'SnippetBlock'
) {
const new_namespace = check_nodes_for_namespace(nodes, 'keep');
if (new_namespace !== 'keep' && new_namespace !== 'maybe_html') {

@ -280,8 +280,6 @@ export function create_scopes(ast, root, allow_reactive_declarations, parent) {
next({ scope });
};
const skip = () => {};
/**
* @type {import('zimmerframe').Visitor<import('#compiler').ElementLike, State, import('#compiler').SvelteNode>}
*/
@ -476,7 +474,7 @@ export function create_scopes(ast, root, allow_reactive_declarations, parent) {
SwitchStatement: create_block_scope,
ClassDeclaration(node, { state, next }) {
if (node.id) state.scope.declare(node.id, 'normal', 'const', node);
if (node.id) state.scope.declare(node.id, 'normal', 'let', node);
next();
},

@ -469,6 +469,7 @@ export function do_while(test, body) {
const true_instance = literal(true);
const false_instance = literal(false);
const null_instane = literal(null);
/** @type {import('estree').DebuggerStatement} */
const debugger_builder = {
@ -630,6 +631,7 @@ export {
return_builder as return,
if_builder as if,
this_instance as this,
null_instane as null,
debugger_builder as debugger
};

@ -280,3 +280,16 @@ export function is_tag_valid_with_parent(tag, parent_tag) {
return true;
}
/**
* @param {string} name
* @param {"include-on" | "exclude-on"} [mode] - wether if name starts with `on` or `on` is excluded at this point
*/
export function is_capture_event(name, mode = 'exclude-on') {
if (!name.endsWith('capture')) {
return false;
}
return mode == 'exclude-on'
? name !== 'gotpointercapture' && name !== 'lostpointercapture'
: name !== 'ongotpointercapture' && name !== 'onlostpointercapture';
}

@ -3,7 +3,7 @@
import './ambient.js';
/**
* @deprecated Svelte components were classes in Svelte 4. In Svelte 5, thy are not anymore.
* @deprecated In Svelte 4, components are classes. In Svelte 5, they are functions.
* Use `mount` or `createRoot` instead to instantiate components.
* See [breaking changes](https://svelte-5-preview.vercel.app/docs/breaking-changes#components-are-no-longer-classes)
* for more info.
@ -34,32 +34,10 @@ type Properties<Props, Slots> = Props &
: {});
/**
* Can be used to create strongly typed Svelte components.
*
* #### Example:
*
* You have component library on npm called `component-library`, from which
* you export a component called `MyComponent`. For Svelte+TypeScript users,
* you want to provide typings. Therefore you create a `index.d.ts`:
* ```ts
* import { SvelteComponent } from "svelte";
* export class MyComponent extends SvelteComponent<{foo: string}> {}
* ```
* Typing this makes it possible for IDEs like VS Code with the Svelte extension
* to provide intellisense and to use the component like this in a Svelte file
* with TypeScript:
* ```svelte
* <script lang="ts">
* import { MyComponent } from "component-library";
* </script>
* <MyComponent foo={'bar'} />
* ```
*
* This was the base class for Svelte components in Svelte 4. Svelte 5+ components
* are completely different under the hood. You should only use this type for typing,
* not actually instantiate components with `new` - use `mount` or `createRoot` instead.
* See [breaking changes](https://svelte-5-preview.vercel.app/docs/breaking-changes#components-are-no-longer-classes)
* for more info.
* are completely different under the hood. For typing, use `Component` instead.
* To instantiate components, use `mount` or `createRoot`.
* See [breaking changes documentation](https://svelte-5-preview.vercel.app/docs/breaking-changes#components-are-no-longer-classes) for more info.
*/
export class SvelteComponent<
Props extends Record<string, any> = Record<string, any>,
@ -80,27 +58,25 @@ export class SvelteComponent<
* For type checking capabilities only.
* Does not exist at runtime.
* ### DO NOT USE!
* */
*/
$$prop_def: Props; // Without Properties: unnecessary, causes type bugs
/**
* For type checking capabilities only.
* Does not exist at runtime.
* ### DO NOT USE!
*
* */
*/
$$events_def: Events;
/**
* For type checking capabilities only.
* Does not exist at runtime.
* ### DO NOT USE!
*
* */
*/
$$slot_def: Slots;
/**
* For type checking capabilities only.
* Does not exist at runtime.
* ### DO NOT USE!
* */
*/
$$bindings?: string;
/**
@ -129,7 +105,61 @@ export class SvelteComponent<
}
/**
* @deprecated Use `SvelteComponent` instead. See TODO for more information.
* Can be used to create strongly typed Svelte components.
*
* #### Example:
*
* You have component library on npm called `component-library`, from which
* you export a component called `MyComponent`. For Svelte+TypeScript users,
* you want to provide typings. Therefore you create a `index.d.ts`:
* ```ts
* import { Component } from "svelte";
* export declare const MyComponent: Component<{ foo: string }> {}
* ```
* Typing this makes it possible for IDEs like VS Code with the Svelte extension
* to provide intellisense and to use the component like this in a Svelte file
* with TypeScript:
* ```svelte
* <script lang="ts">
* import { MyComponent } from "component-library";
* </script>
* <MyComponent foo={'bar'} />
* ```
*/
export interface Component<
Props extends Record<string, any> = {},
Exports extends Record<string, any> = {},
Bindings extends keyof Props | '' = ''
> {
/**
* @param internal An internal object used by Svelte. Do not use or modify.
* @param props The props passed to the component.
*/
(
internal: unknown,
props: Props
): {
/**
* @deprecated This method only exists when using one of the legacy compatibility helpers, which
* is a stop-gap solution. See https://svelte-5-preview.vercel.app/docs/breaking-changes#components-are-no-longer-classes
* for more info.
*/
$on?(type: string, callback: (e: any) => void): () => void;
/**
* @deprecated This method only exists when using one of the legacy compatibility helpers, which
* is a stop-gap solution. See https://svelte-5-preview.vercel.app/docs/breaking-changes#components-are-no-longer-classes
* for more info.
*/
$set?(props: Partial<Props>): void;
} & Exports;
/** The custom element version of the component. Only present if compiled with the `customElement` compiler option */
element?: typeof HTMLElement;
/** Does not exist at runtime, for typing capabilities only. DO NOT USE */
z_$$bindings?: Bindings;
}
/**
* @deprecated Use `Component` instead. See [breaking changes documentation](https://svelte-5-preview.vercel.app/docs/breaking-changes#components-are-no-longer-classes) for more information.
*/
export class SvelteComponentTyped<
Props extends Record<string, any> = Record<string, any>,
@ -138,6 +168,8 @@ export class SvelteComponentTyped<
> extends SvelteComponent<Props, Events, Slots> {}
/**
* @deprecated The new `Component` type does not have a dedicated Events type. Use `ComponentProps` instead.
*
* Convenience type to get the events the given component expects. Example:
* ```html
* <script lang="ts">
@ -166,10 +198,16 @@ export type ComponentEvents<Comp extends SvelteComponent> =
* </script>
* ```
*/
export type ComponentProps<Comp extends SvelteComponent> =
Comp extends SvelteComponent<infer Props> ? Props : never;
export type ComponentProps<Comp extends SvelteComponent | Component> =
Comp extends SvelteComponent<infer Props>
? Props
: Comp extends Component<infer Props>
? Props
: never;
/**
* @deprecated This type is obsolete when working with the new `Component` type.
*
* Convenience type to get the type of a Svelte component. Useful for example in combination with
* dynamic components using `<svelte:component>`.
*

@ -5,16 +5,17 @@ export const BLOCK_EFFECT = 1 << 4;
export const BRANCH_EFFECT = 1 << 5;
export const ROOT_EFFECT = 1 << 6;
export const UNOWNED = 1 << 7;
export const CLEAN = 1 << 8;
export const DIRTY = 1 << 9;
export const MAYBE_DIRTY = 1 << 10;
export const INERT = 1 << 11;
export const DESTROYED = 1 << 12;
export const EFFECT_RAN = 1 << 13;
export const DISCONNECTED = 1 << 8;
export const CLEAN = 1 << 9;
export const DIRTY = 1 << 10;
export const MAYBE_DIRTY = 1 << 11;
export const INERT = 1 << 12;
export const DESTROYED = 1 << 13;
export const EFFECT_RAN = 1 << 14;
/** 'Transparent' effects do not create a transition boundary */
export const EFFECT_TRANSPARENT = 1 << 14;
export const EFFECT_TRANSPARENT = 1 << 15;
/** Svelte 4 legacy mode props need to be handled with deriveds and be recognized elsewhere, hence the dedicated flag */
export const LEGACY_DERIVED_PROP = 1 << 15;
export const LEGACY_DERIVED_PROP = 1 << 16;
export const STATE_SYMBOL = Symbol('$state');
export const LOADING_ATTR_SYMBOL = Symbol('');

@ -4,6 +4,7 @@ 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
@ -37,7 +38,7 @@ export function html(anchor, get_value, svg, mathml) {
let value = derived(get_value);
render_effect(() => {
var dom = html_to_dom(anchor, get(value), svg, mathml);
var dom = html_to_dom(anchor, parent_effect, get(value), svg, mathml);
if (dom) {
return () => {
@ -55,12 +56,13 @@ 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, value, svg, mathml) {
function html_to_dom(target, effect, value, svg, mathml) {
if (hydrating) return hydrate_nodes;
var html = value + '';
@ -79,6 +81,9 @@ function html_to_dom(target, 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;
}
@ -92,5 +97,9 @@ function html_to_dom(target, value, svg, mathml) {
target.before(node);
}
if (effect !== null) {
push_template_node(nodes, effect);
}
return nodes;
}

@ -10,8 +10,31 @@ import {
} from '../../reactivity/effects.js';
import { set_should_intro } from '../../render.js';
import { current_each_item, set_current_each_item } from './each.js';
import { current_component_context } from '../../runtime.js';
import { current_component_context, current_effect } from '../../runtime.js';
import { DEV } from 'esm-env';
import { is_array } from '../../utils.js';
import { push_template_node } from '../template.js';
/**
* @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 | Element} node
@ -23,6 +46,7 @@ import { DEV } from 'esm-env';
* @returns {void}
*/
export function element(node, 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;
/** @type {string | null} */
@ -81,6 +105,7 @@ export function element(node, get_tag, is_svg, render_fn, get_namespace, locatio
if (next_tag && next_tag !== current_tag) {
effect = branch(() => {
const prev_element = element;
element = hydrating
? /** @type {Element} */ (element)
: ns
@ -118,9 +143,12 @@ export function element(node, get_tag, is_svg, render_fn, get_namespace, locatio
anchor.before(element);
return () => {
element?.remove();
};
if (prev_element) {
swap_block_dom(parent_effect, prev_element, element);
prev_element.remove();
} else if (!hydrating) {
push_template_node(element, parent_effect);
}
});
}

@ -1,7 +1,12 @@
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 {
AttributeAliases,
DelegatedEvents,
is_capture_event,
namespace_svg
} from '../../../../constants.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';
@ -158,7 +163,8 @@ export function set_attributes(element, prev, next, lowercase_attributes, css_ha
/** @type {Array<[string, any, () => void]>} */
var events = [];
for (key in next) {
// since key is captured we use const
for (const key in next) {
// let instead of var because referenced in a closure
let value = next[key];
if (value === prev?.[key]) continue;
@ -172,11 +178,7 @@ export function set_attributes(element, prev, next, lowercase_attributes, css_ha
let event_name = key.slice(2);
var delegated = DelegatedEvents.includes(event_name);
if (
event_name.endsWith('capture') &&
event_name !== 'ongotpointercapture' &&
event_name !== 'onlostpointercapture'
) {
if (is_capture_event(event_name)) {
event_name = event_name.slice(0, -7);
opts.capture = true;
}

@ -4,7 +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';
import { yield_event_updates } from '../../../runtime.js';
/**
* @param {HTMLInputElement} input
@ -19,7 +19,9 @@ export function bind_value(input, get_value, update) {
e.bind_invalid_checkbox_value();
}
yield_updates(() => update(is_numberlike_input(input) ? to_number(input.value) : input.value));
yield_event_updates(() =>
update(is_numberlike_input(input) ? to_number(input.value) : input.value)
);
});
render_effect(() => {
@ -85,10 +87,10 @@ export function bind_group(inputs, group_index, input, get_value, update) {
value = get_binding_group_value(binding_group, value, input.checked);
}
yield_updates(() => update(value));
yield_event_updates(() => update(value));
},
// TODO better default value handling
() => yield_updates(() => update(is_checkbox ? [] : null))
() => yield_event_updates(() => update(is_checkbox ? [] : null))
);
render_effect(() => {
@ -129,7 +131,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;
yield_updates(() => update(value));
yield_event_updates(() => update(value));
});
if (get_value() == undefined) {
@ -188,7 +190,7 @@ function to_number(value) {
*/
export function bind_files(input, get_value, update) {
listen_to_event_and_reset_event(input, 'change', () => {
yield_updates(() => update(input.files));
yield_event_updates(() => update(input.files));
});
render_effect(() => {
input.files = get_value();

@ -1,7 +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';
import { yield_event_updates } from '../../../runtime.js';
/** @param {TimeRanges} ranges */
function time_ranges_to_array(ranges) {
@ -36,7 +36,7 @@ export function bind_current_time(media, get_value, update) {
}
updating = true;
yield_updates(() => update(media.currentTime));
yield_event_updates(() => update(media.currentTime));
};
raf_id = requestAnimationFrame(callback);
@ -62,7 +62,7 @@ export function bind_current_time(media, get_value, update) {
*/
export function bind_buffered(media, update) {
listen(media, ['loadedmetadata', 'progress'], () =>
yield_updates(() => update(time_ranges_to_array(media.buffered)))
yield_event_updates(() => update(time_ranges_to_array(media.buffered)))
);
}
@ -80,7 +80,7 @@ export function bind_seekable(media, update) {
*/
export function bind_played(media, update) {
listen(media, ['timeupdate'], () =>
yield_updates(() => update(time_ranges_to_array(media.played)))
yield_event_updates(() => update(time_ranges_to_array(media.played)))
);
}
@ -89,7 +89,7 @@ export function bind_played(media, update) {
* @param {(seeking: boolean) => void} update
*/
export function bind_seeking(media, update) {
listen(media, ['seeking', 'seeked'], () => yield_updates(() => update(media.seeking)));
listen(media, ['seeking', 'seeked'], () => yield_event_updates(() => update(media.seeking)));
}
/**
@ -97,7 +97,7 @@ export function bind_seeking(media, update) {
* @param {(seeking: boolean) => void} update
*/
export function bind_ended(media, update) {
listen(media, ['timeupdate', 'ended'], () => yield_updates(() => update(media.ended)));
listen(media, ['timeupdate', 'ended'], () => yield_event_updates(() => update(media.ended)));
}
/**
@ -108,7 +108,7 @@ export function bind_ready_state(media, update) {
listen(
media,
['loadedmetadata', 'loadeddata', 'canplay', 'canplaythrough', 'playing', 'waiting', 'emptied'],
() => yield_updates(() => update(media.readyState))
() => yield_event_updates(() => update(media.readyState))
);
}
@ -132,7 +132,7 @@ export function bind_playback_rate(media, get_value, update) {
}
listen(media, ['ratechange'], () => {
if (!updating) yield_updates(() => update(media.playbackRate));
if (!updating) yield_event_updates(() => update(media.playbackRate));
updating = false;
});
});
@ -150,7 +150,7 @@ export function bind_paused(media, get_value, update) {
var callback = () => {
if (paused !== media.paused) {
paused = media.paused;
yield_updates(() => update((paused = media.paused)));
yield_event_updates(() => update((paused = media.paused)));
}
};
@ -175,7 +175,7 @@ export function bind_paused(media, get_value, update) {
media.pause();
} else {
media.play().catch(() => {
yield_updates(() => update((paused = true)));
yield_event_updates(() => update((paused = true)));
});
}
};
@ -239,7 +239,7 @@ export function bind_muted(media, get_value, update) {
var callback = () => {
updating = true;
yield_updates(() => update(media.muted));
yield_event_updates(() => update(media.muted));
};
if (get_value() == null) {

@ -1,4 +1,4 @@
import { yield_updates } from '../../../runtime.js';
import { yield_event_updates } from '../../../runtime.js';
import { listen } from './shared.js';
/**
@ -7,6 +7,6 @@ import { listen } from './shared.js';
*/
export function bind_online(update) {
listen(window, ['online', 'offline'], () => {
yield_updates(() => update(navigator.onLine));
yield_event_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, yield_updates } from '../../../runtime.js';
import { untrack, yield_event_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);
}
yield_updates(() => update(value));
yield_event_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,5 +1,5 @@
import { effect, render_effect } from '../../../reactivity/effects.js';
import { untrack, yield_updates } from '../../../runtime.js';
import { untrack, yield_event_updates } from '../../../runtime.js';
/**
* Resize observer singleton.
@ -90,7 +90,7 @@ export function bind_resize_observer(element, type, update) {
var unsub = observer.observe(
element,
/** @param {any} entry */ (entry) => yield_updates(() => update(entry[type]))
/** @param {any} entry */ (entry) => yield_event_updates(() => update(entry[type]))
);
render_effect(() => unsub);
}
@ -104,7 +104,7 @@ export function bind_element_size(element, type, update) {
var unsub = resize_observer_border_box.observe(element, () => update(element[type]));
effect(() => {
yield_updates(() => untrack(() => update(element[type])));
yield_event_updates(() => untrack(() => update(element[type])));
return unsub;
});
}

@ -1,6 +1,6 @@
import { STATE_SYMBOL } from '../../../constants.js';
import { effect, render_effect } from '../../../reactivity/effects.js';
import { untrack, yield_updates } from '../../../runtime.js';
import { untrack, yield_event_updates } from '../../../runtime.js';
import { queue_micro_task } from '../../task.js';
/**
@ -37,7 +37,7 @@ export function bind_this(element_or_component, update, get_value, get_parts) {
untrack(() => {
if (element_or_component !== get_value(...parts)) {
yield_updates(() => {
yield_event_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.

@ -1,5 +1,5 @@
import { effect, render_effect } from '../../../reactivity/effects.js';
import { yield_updates } from '../../../runtime.js';
import { yield_event_updates } from '../../../runtime.js';
import { listen } from './shared.js';
/**
@ -16,7 +16,7 @@ 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?)
yield_updates(() => update(window[is_scrolling_x ? 'scrollX' : 'scrollY']));
yield_event_updates(() => update(window[is_scrolling_x ? 'scrollX' : 'scrollY']));
};
addEventListener('scroll', target_handler, {
@ -49,14 +49,8 @@ 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));
}
});
// Browsers don't fire the scroll event for the initial scroll position when scroll style isn't set to smooth
effect(target_handler);
render_effect(() => {
return () => {
@ -70,5 +64,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'], () => yield_updates(() => update(window[type])));
listen(window, ['resize'], () => yield_event_updates(() => update(window[type])));
}

@ -1,6 +1,6 @@
import { render_effect } from '../../reactivity/effects.js';
import { all_registered_events, root_event_handles } from '../../render.js';
import { yield_updates } from '../../runtime.js';
import { yield_event_updates } from '../../runtime.js';
import { define_property, is_array } from '../../utils.js';
import { hydrating } from '../hydration.js';
import { queue_micro_task } from '../task.js';
@ -48,15 +48,15 @@ export function create_event(event_name, dom, handler, options) {
handle_event_propagation(dom, event);
}
if (!event.cancelBubble) {
return yield_updates(() => handler.call(this, event));
return yield_event_updates(() => handler.call(this, event));
}
}
// 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')) {
// this bug. The same applies to wheel events.
if (event_name.startsWith('pointer') || event_name === 'wheel') {
queue_micro_task(() => {
dom.addEventListener(event_name, target_handler, options);
});
@ -204,7 +204,7 @@ export function handle_event_propagation(handler_element, event) {
}
try {
yield_updates(() => next(/** @type {Element} */ (current_target)));
yield_event_updates(() => next(/** @type {Element} */ (current_target)));
} finally {
// @ts-expect-error is used above
event.__root = handler_element;

@ -4,17 +4,32 @@ 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
*/
function push_template_node(dom) {
var effect = /** @type {import('#client').Effect} */ (current_effect);
if (effect.dom === null) {
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];
}
if (is_array(dom)) {
current_dom.push(...dom);
} else {
current_dom.push(dom);
}
}
return dom;
}
/**
@ -41,7 +56,15 @@ export function template(content, flags) {
if (!is_fragment) node = /** @type {Node} */ (node.firstChild);
}
return use_import_node ? document.importNode(node, true) : node.cloneNode(true);
var clone = use_import_node ? document.importNode(node, true) : node.cloneNode(true);
push_template_node(
is_fragment
? /** @type {import('#client').TemplateNode[]} */ ([...clone.childNodes])
: /** @type {import('#client').TemplateNode} */ (clone)
);
return clone;
};
}
@ -102,7 +125,15 @@ export function ns_template(content, flags, ns = 'svg') {
}
}
return node.cloneNode(true);
var clone = node.cloneNode(true);
push_template_node(
is_fragment
? /** @type {import('#client').TemplateNode[]} */ ([...clone.childNodes])
: /** @type {import('#client').TemplateNode} */ (clone)
);
return clone;
};
}
@ -177,7 +208,7 @@ function run_scripts(node) {
*/
/*#__NO_SIDE_EFFECTS__*/
export function text(anchor) {
if (!hydrating) return empty();
if (!hydrating) return push_template_node(empty());
var node = hydrate_start;
@ -201,6 +232,7 @@ export function comment() {
var frag = document.createDocumentFragment();
var anchor = empty();
frag.append(anchor);
push_template_node([anchor]);
return frag;
}
@ -213,13 +245,9 @@ export function comment() {
*/
export function append(anchor, 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);
// We intentionally do not assign the `dom` property of the effect here because it's far too
// late. If we try, we will capture additional DOM elements that we cannot control the lifecycle
// for and will inevitably cause memory leaks. See https://github.com/sveltejs/svelte/pull/11832
anchor.before(/** @type {Node} */ (dom));
}

@ -27,9 +27,10 @@ import * as e from './errors.js';
* @param {T} value
* @param {boolean} [immutable]
* @param {import('#client').ProxyMetadata | null} [parent]
* @param {import('#client').Source<T>} [prev] dev mode only
* @returns {import('#client').ProxyStateObject<T> | T}
*/
export function proxy(value, immutable = true, parent = null) {
export function proxy(value, immutable = true, parent = null, prev) {
if (typeof value === 'object' && value != null && !is_frozen(value)) {
// If we have an existing proxy, return it...
if (STATE_SYMBOL in value) {
@ -71,13 +72,22 @@ export function proxy(value, immutable = true, parent = null) {
// @ts-expect-error
value[STATE_SYMBOL].parent = parent;
// @ts-expect-error
value[STATE_SYMBOL].owners =
parent === null
? current_component_context !== null
? new Set([current_component_context.function])
: null
: new Set();
if (prev) {
// Reuse owners from previous state; necessary because reassignment is not guaranteed to have correct component context.
// If no previous proxy exists we play it safe and assume ownerless state
// @ts-expect-error
const prev_owners = prev?.v?.[STATE_SYMBOL]?.owners;
// @ts-expect-error
value[STATE_SYMBOL].owners = prev_owners ? new Set(prev_owners) : null;
} else {
// @ts-expect-error
value[STATE_SYMBOL].owners =
parent === null
? current_component_context !== null
? new Set([current_component_context.function])
: null
: new Set();
}
}
return proxy;

@ -323,6 +323,8 @@ export function prop(props, key, flags, fallback) {
from_child = true;
set(inner_current_value, value);
get(current_value); // force a synchronisation immediately
// Increment the value so that unowned/disconnected nodes can validate dirtiness again.
current_value.version++;
}
return value;

@ -84,13 +84,12 @@ export function stringify(value) {
*
* @template {Record<string, any>} Props
* @template {Record<string, any>} Exports
* @template {Record<string, any>} Events
* @param {import('../../index.js').ComponentType<import('../../index.js').SvelteComponent<Props, Events>>} component
* @param {import('../../index.js').ComponentType<import('../../index.js').SvelteComponent<Props>> | import('../../index.js').Component<Props, Exports, any>} component
* @param {{
* target: Document | Element | ShadowRoot;
* anchor?: Node;
* props?: Props;
* events?: { [Property in keyof Events]: (e: Events[Property]) => any };
* events?: Record<string, (e: any) => any>;
* context?: Map<any, any>;
* intro?: boolean;
* }} options
@ -111,12 +110,11 @@ export function mount(component, options) {
*
* @template {Record<string, any>} Props
* @template {Record<string, any>} Exports
* @template {Record<string, any>} Events
* @param {import('../../index.js').ComponentType<import('../../index.js').SvelteComponent<Props, Events>>} component
* @param {import('../../index.js').ComponentType<import('../../index.js').SvelteComponent<Props>> | import('../../index.js').Component<Props, Exports, any>} component
* @param {{
* target: Document | Element | ShadowRoot;
* props?: Props;
* events?: { [Property in keyof Events]: (e: Events[Property]) => any };
* events?: Record<string, (e: any) => any>;
* context?: Map<any, any>;
* intro?: boolean;
* recover?: boolean;
@ -181,7 +179,7 @@ export function hydrate(component, options) {
/**
* @template {Record<string, any>} Exports
* @param {import('../../index.js').ComponentType<import('../../index.js').SvelteComponent<any>>} Component
* @param {import('../../index.js').ComponentType<import('../../index.js').SvelteComponent<any>> | import('../../index.js').Component<any>} Component
* @param {{
* target: Document | Element | ShadowRoot;
* anchor: Node;

@ -16,7 +16,8 @@ import {
STATE_SYMBOL,
BLOCK_EFFECT,
ROOT_EFFECT,
LEGACY_DERIVED_PROP
LEGACY_DERIVED_PROP,
DISCONNECTED
} from './constants.js';
import { flush_tasks } from './dom/task.js';
import { add_owner } from './dev/ownership.js';
@ -198,12 +199,15 @@ export function check_dirtiness(reaction) {
return true;
}
var is_disconnected = (flags & DISCONNECTED) !== 0;
if ((flags & MAYBE_DIRTY) !== 0 || (is_dirty && is_unowned)) {
var dependencies = reaction.deps;
if (dependencies !== null) {
var length = dependencies.length;
var is_equal;
var reactions;
for (var i = 0; i < length; i++) {
var dependency = dependencies[i];
@ -211,13 +215,13 @@ export function check_dirtiness(reaction) {
if (!is_dirty && check_dirtiness(/** @type {import('#client').Derived} */ (dependency))) {
is_equal = update_derived(/** @type {import('#client').Derived} **/ (dependency), true);
}
var version = dependency.version;
if (is_unowned) {
// If we're working with an unowned derived signal, then we need to check
// if our dependency write version is higher. If it is then we can assume
// that state has changed to a newer version and thus this unowned signal
// is also dirty.
var version = dependency.version;
if (version > /** @type {import('#client').Derived} */ (reaction).version) {
/** @type {import('#client').Derived} */ (reaction).version = version;
@ -228,7 +232,7 @@ export function check_dirtiness(reaction) {
// If we are working with an unowned signal as part of an effect (due to !current_skip_reaction)
// and the version hasn't changed, we still need to check that this reaction
// if linked to the dependency source otherwise future updates will not be caught.
var reactions = dependency.reactions;
reactions = dependency.reactions;
if (reactions === null) {
dependency.reactions = [reaction];
} else {
@ -238,6 +242,20 @@ export function check_dirtiness(reaction) {
} else if ((reaction.f & DIRTY) !== 0) {
// `signal` might now be dirty, as a result of calling `check_dirtiness` and/or `update_derived`
return true;
} else if (is_disconnected) {
// It might be that the derived was was dereferenced from its dependencies but has now come alive again.
// In thise case, we need to re-attach it to the graph and mark it dirty if any of its dependencies have
// changed since.
if (version > /** @type {import('#client').Derived} */ (reaction).version) {
/** @type {import('#client').Derived} */ (reaction).version = version;
is_dirty = true;
}
reactions = dependency.reactions;
if (reactions === null) {
dependency.reactions = [reaction];
} else if (!reactions.includes(reaction)) {
reactions.push(reaction);
}
}
}
}
@ -246,6 +264,9 @@ export function check_dirtiness(reaction) {
if (!is_unowned) {
set_signal_status(reaction, CLEAN);
}
if (is_disconnected) {
reaction.f ^= DISCONNECTED;
}
}
return is_dirty;
@ -422,9 +443,15 @@ 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 maybe dirty.
// If the derived has no reactions, then we can disconnect it from the graph,
// allowing it to either reconnect in the future, or be GC'd by the VM.
if (reactions_length === 0 && (dependency.f & DERIVED) !== 0) {
set_signal_status(dependency, MAYBE_DIRTY);
// If we are working with a derived that is owned by an effect, then mark it as being
// disconnected.
if ((dependency.f & (UNOWNED | DISCONNECTED)) === 0) {
dependency.f ^= DISCONNECTED;
}
remove_reactions(/** @type {import('#client').Derived} **/ (dependency), 0);
}
}
@ -573,7 +600,7 @@ function flush_queued_effects(effects) {
function process_deferred() {
is_micro_task_queued = false;
is_yield_task_queued = false;
if (flush_count > 101) {
if (flush_count > 1001) {
return;
}
const previous_queued_root_effects = current_queued_root_effects;
@ -711,7 +738,7 @@ function process_effects(effect, collected_effects) {
/**
* @param {{ (): void; (): any; }} fn
*/
export function yield_updates(fn) {
export function yield_event_updates(fn) {
const previous_scheduler_mode = current_scheduler_mode;
try {
current_scheduler_mode = FLUSH_YIELD;
@ -740,6 +767,8 @@ export function flush_sync(fn, flush_previous = true) {
current_scheduler_mode = FLUSH_SYNC;
current_queued_root_effects = root_effects;
is_yield_task_queued = false;
is_micro_task_queued = false;
if (flush_previous) {
flush_queued_root_effects(previous_queued_root_effects);
@ -815,7 +844,7 @@ export function get(signal) {
) {
if (current_dependencies === null) {
current_dependencies = [signal];
} else {
} else if (current_dependencies[current_dependencies.length - 1] !== signal) {
current_dependencies.push(signal);
}
}

@ -14,7 +14,7 @@ import { define_property } from '../internal/client/utils.js';
* @template {Record<string, any>} Slots
*
* @param {import('svelte').ComponentConstructorOptions<Props> & {
* component: import('svelte').ComponentType<import('svelte').SvelteComponent<Props, Events, Slots>>;
* component: import('svelte').ComponentType<import('svelte').SvelteComponent<Props, Events, Slots>> | import('svelte').Component<Props>;
* immutable?: boolean;
* hydrate?: boolean;
* recover?: boolean;
@ -36,7 +36,7 @@ export function createClassComponent(options) {
* @template {Record<string, any>} Events
* @template {Record<string, any>} Slots
*
* @param {import('svelte').SvelteComponent<Props, Events, Slots>} component
* @param {import('svelte').SvelteComponent<Props, Events, Slots> | import('svelte').Component<Props>} component
* @returns {import('svelte').ComponentType<import('svelte').SvelteComponent<Props, Events, Slots> & Exports>}
*/
export function asClassComponent(component) {

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

@ -0,0 +1,5 @@
import { test } from '../../test';
export default test({
warnings: []
});

@ -0,0 +1,5 @@
div.svelte-xyz {
&.class{
color: red;
}
}

@ -0,0 +1,6 @@
<div class="svelte-xyz">
</div>
<div class="class svelte-xyz">
foo
</div>

@ -0,0 +1,14 @@
<style>
div {
&:global(.class){
color: red;
}
}
</style>
<div>
</div>
<div class="class">
foo
</div>

@ -0,0 +1,16 @@
import { tick } from 'svelte';
import { test } from '../../assert';
export default test({
async test({ assert, window }) {
const div = window.document.querySelector('div');
div?.dispatchEvent(new PointerEvent('gotpointercapture'));
div?.dispatchEvent(new PointerEvent('lostpointercapture'));
await tick();
assert.equal(div?.dataset.lostCaptured, 'true');
assert.equal(div?.dataset.gotCaptured, 'true');
}
});

@ -0,0 +1,13 @@
<script>
</script>
<div
ongotpointercapture={(e) => {
e.target.dataset.gotCaptured = "true";
}}
onlostpointercapture={(e) => {
e.target.dataset.lostCaptured = "true";
}}
>
</div>

@ -1,3 +1,4 @@
import { flushSync } from 'svelte';
import { test } from '../../test';
export default test({
@ -22,15 +23,15 @@ export default test({
<input type="checkbox" value="c" data-index="z-2">
`,
async test({ assert, target, window }) {
test({ assert, target, window }) {
const inputs = target.querySelectorAll('input');
const checked = new Set();
/** @param {number} i */
const checkInbox = async (i) => {
const checkInbox = (i) => {
checked.add(i);
inputs[i].checked = true;
await inputs[i].dispatchEvent(event);
inputs[i].dispatchEvent(event);
};
for (let i = 0; i < 18; i++) {
@ -39,17 +40,20 @@ export default test({
const event = new window.Event('change');
await checkInbox(2);
checkInbox(2);
flushSync();
for (let i = 0; i < 18; i++) {
assert.equal(inputs[i].checked, checked.has(i));
}
await checkInbox(12);
checkInbox(12);
flushSync();
for (let i = 0; i < 18; i++) {
assert.equal(inputs[i].checked, checked.has(i));
}
await checkInbox(8);
checkInbox(8);
flushSync();
for (let i = 0; i < 18; i++) {
assert.equal(inputs[i].checked, checked.has(i));
}

@ -1,3 +1,4 @@
import { flushSync } from 'svelte';
import { ok, test } from '../../test';
export default test({
@ -16,6 +17,7 @@ export default test({
input.value = '5.';
input.dispatchEvent(inputEvent);
flushSync();
// input type number has value === "" if ends with dot/comma
assert.equal(component.value, undefined);
@ -23,12 +25,14 @@ export default test({
input.value = '5.5';
input.dispatchEvent(inputEvent);
flushSync();
assert.equal(component.value, 5.5);
assert.equal(input.value, '5.5');
input.value = '5.50';
input.dispatchEvent(inputEvent);
flushSync();
assert.equal(component.value, 5.5);
assert.equal(input.value, '5.50');

@ -0,0 +1,7 @@
import { test } from '../../test';
export default test({
html: `
<div slot="footer">hello hello</div>
`
});

@ -0,0 +1,13 @@
<script>
import Nested from "./Nested.svelte"
import Nested2 from "./Nested2.svelte"
</script>
<Nested>
<Nested2 slot="inner" let:text>
<div slot="footer">
{@const text2 = text}
{text} {text2}
</div>
</Nested2>
</Nested>

@ -0,0 +1,16 @@
import { flushSync } from 'svelte';
import { test } from '../../test';
export default test({
test({ assert, target }) {
const div = target.querySelector('div');
div?.dispatchEvent(new Event('b'));
flushSync();
assert.htmlEqual(target.innerHTML, '<div>b</div>');
div?.dispatchEvent(new Event('a'));
flushSync();
assert.htmlEqual(target.innerHTML, '<div>a</div>');
}
});

@ -0,0 +1,8 @@
<script lang="ts">
const props = {};
let changed = $state('');
</script>
<div {...props} ona={() => (changed = 'a')} onb={() => (changed = 'b')}>
{changed}
</div>

@ -1,8 +1,8 @@
<script>
/** @type {{ object: { count: number }}} */
let { object = $bindable() } = $props();
let { object = $bindable(), reset } = $props();
</script>
<button onclick={() => object.count += 1}>
clicks: {object.count}
</button>
<button onclick={reset}>reset</button>

@ -1,36 +1,30 @@
import { flushSync } from 'svelte';
import { test } from '../../test';
/** @type {typeof console.trace} */
let trace;
export default test({
html: `<button>clicks: 0</button>`,
html: `<button>clicks: 0</button> <button>reset</button>`,
compileOptions: {
dev: true
},
before_test: () => {
trace = console.trace;
console.trace = () => {};
},
after_test: () => {
console.trace = trace;
},
test({ assert, target, warnings }) {
const btn = target.querySelector('button');
flushSync(() => {
btn?.click();
});
assert.htmlEqual(target.innerHTML, `<button>clicks: 1</button>`);
assert.deepEqual(warnings, [
'Counter.svelte mutated a value owned by main.svelte. This is strongly discouraged. Consider passing values to child components with `bind:`, or use a callback instead'
]);
const warning =
'Counter.svelte mutated a value owned by main.svelte. This is strongly discouraged. Consider passing values to child components with `bind:`, or use a callback instead';
const [btn1, btn2] = target.querySelectorAll('button');
btn1.click();
flushSync();
assert.htmlEqual(target.innerHTML, `<button>clicks: 1</button> <button>reset</button>`);
assert.deepEqual(warnings, [warning]);
btn2.click();
flushSync();
assert.htmlEqual(target.innerHTML, `<button>clicks: 0</button> <button>reset</button>`);
btn1.click();
flushSync();
assert.htmlEqual(target.innerHTML, `<button>clicks: 1</button> <button>reset</button>`);
assert.deepEqual(warnings, [warning, warning]);
}
});

@ -1,7 +1,7 @@
<script>
import Counter from './Counter.svelte';
const object = $state({ count: 0 });
let object = $state({ count: 0 });
</script>
<Counter {object} />
<Counter {object} reset={() => object = {count: 0}} />

@ -0,0 +1,11 @@
import { test } from '../../test';
export default test({
compileOptions: {
dev: true
},
async test({ assert, warnings }) {
assert.deepEqual(warnings, []);
}
});

@ -0,0 +1,18 @@
<script context="module">
let toast1 = $state();
let toast2 = $state({});
export async function show_toast() {
toast1 = {
message: 'foo',
show: true
};
toast1.show = false;
toast2 = {
message: 'foo',
show: true
};
toast2.show = false;
}
</script>

@ -0,0 +1,5 @@
<script>
import { show_toast } from "./child.svelte";
show_toast();
</script>

@ -17,8 +17,8 @@ export default test({
assert.htmlEqual(
target.innerHTML,
`
<button>mutate: 0</button>
<button>reassign: 0</button>
<button>mutate: 1</button>
<button>reassign: 1</button>
`
);

@ -2,6 +2,10 @@
interface Hello { message: 'hello' }
type Goodbye = { message: 'goodbye' };
function this_fn(this: any) {
console.log(this);
}
export type { Hello };
</script>

@ -0,0 +1,4 @@
<p>true</p>
<p>true</p>
<p>true</p>
<p>true</p>

@ -0,0 +1,10 @@
<script>
const obj = {};
const a = $state(obj)
const b= $state(obj)
</script>
<p>{a === obj}</p>
<p>{$state.is(a, obj)}</p>
<p>{a === b}</p>
<p>{$state.is(a, b)}</p>

@ -2,13 +2,14 @@ import { describe, assert, it } from 'vitest';
import { flushSync } from '../../src/index-client';
import * as $ from '../../src/internal/client/runtime';
import {
destroy_effect,
effect,
effect_root,
render_effect,
user_effect
} from '../../src/internal/client/reactivity/effects';
import { source, set } from '../../src/internal/client/reactivity/sources';
import type { Derived, Value } from '../../src/internal/client/types';
import type { Derived, Effect, Value } from '../../src/internal/client/types';
import { proxy } from '../../src/internal/client/proxy';
import { derived } from '../../src/internal/client/reactivity/deriveds';
@ -423,4 +424,27 @@ describe('signals', () => {
destroy();
};
});
test('owned deriveds correctly cleanup when no longer connected to graph', () => {
let a: Derived<unknown>;
let state = source(0);
const destroy = effect_root(() => {
render_effect(() => {
a = derived(() => {
$.get(state);
});
$.get(a);
});
});
return () => {
flushSync();
assert.equal(a?.deps?.length, 1);
assert.equal(state?.reactions?.length, 1);
destroy();
assert.equal(a?.deps?.length, 1);
assert.equal(state?.reactions, null);
};
});
});

@ -5,7 +5,8 @@ import {
type ComponentProps,
type ComponentType,
mount,
hydrate
hydrate,
type Component
} from 'svelte';
SvelteComponent.element === HTMLElement;
@ -49,6 +50,15 @@ const legacyComponentEvents2: ComponentEvents<LegacyComponent> = {
event: new KeyboardEvent('click')
};
const legacyComponentInstance: SvelteComponent<{ prop: string }> = new LegacyComponent({
target: null as any as Document | Element | ShadowRoot,
props: {
prop: 'foo'
}
});
const legacyComponentClass: typeof SvelteComponent<{ prop: string }> = LegacyComponent;
// --------------------------------------------------------------------------- new: functions
class NewComponent extends SvelteComponent<
@ -130,7 +140,7 @@ hydrate(NewComponent, {
},
events: {
event: (e) =>
// @ts-expect-error
// we're not type checking this as it's an edge case and removing the generic later would be an annoying mini breaking change
e.doesNotExist
},
immutable: true,
@ -174,3 +184,73 @@ const x: typeof asLegacyComponent = createClassComponent({
hydrate: true,
component: NewComponent
});
// --------------------------------------------------------------------------- function component
const functionComponent: Component<
{ binding: boolean; readonly: string },
{ foo: 'bar' },
'binding'
> = (a, props) => {
props.binding === true;
props.readonly === 'foo';
// @ts-expect-error
props.readonly = true;
// @ts-expect-error
props.binding = '';
return {
foo: 'bar'
};
};
functionComponent.element === HTMLElement;
functionComponent(null as any, {
binding: true,
// @ts-expect-error
readonly: true
});
const functionComponentInstance = functionComponent(null as any, {
binding: true,
readonly: 'foo',
// @ts-expect-error
x: ''
});
functionComponentInstance.foo === 'bar';
// @ts-expect-error
functionComponentInstance.foo = 'foo';
mount(functionComponent, {
target: null as any as Document | Element | ShadowRoot,
props: {
binding: true,
readonly: 'foo',
// would be nice to error here, probably needs NoInfer type helper in upcoming TS 5.5
x: ''
}
});
mount(functionComponent, {
target: null as any as Document | Element | ShadowRoot,
props: {
binding: true,
// @ts-expect-error wrong type
readonly: 1
}
});
hydrate(functionComponent, {
target: null as any as Document | Element | ShadowRoot,
props: {
binding: true,
readonly: 'foo',
// would be nice to error here, probably needs NoInfer type helper in upcoming TS 5.5
x: ''
}
});
hydrate(functionComponent, {
target: null as any as Document | Element | ShadowRoot,
// @ts-expect-error missing prop
props: {
binding: true
}
});

@ -1,6 +1,6 @@
declare module 'svelte' {
/**
* @deprecated Svelte components were classes in Svelte 4. In Svelte 5, thy are not anymore.
* @deprecated In Svelte 4, components are classes. In Svelte 5, they are functions.
* Use `mount` or `createRoot` instead to instantiate components.
* See [breaking changes](https://svelte-5-preview.vercel.app/docs/breaking-changes#components-are-no-longer-classes)
* for more info.
@ -31,32 +31,10 @@ declare module 'svelte' {
: {});
/**
* Can be used to create strongly typed Svelte components.
*
* #### Example:
*
* You have component library on npm called `component-library`, from which
* you export a component called `MyComponent`. For Svelte+TypeScript users,
* you want to provide typings. Therefore you create a `index.d.ts`:
* ```ts
* import { SvelteComponent } from "svelte";
* export class MyComponent extends SvelteComponent<{foo: string}> {}
* ```
* Typing this makes it possible for IDEs like VS Code with the Svelte extension
* to provide intellisense and to use the component like this in a Svelte file
* with TypeScript:
* ```svelte
* <script lang="ts">
* import { MyComponent } from "component-library";
* </script>
* <MyComponent foo={'bar'} />
* ```
*
* This was the base class for Svelte components in Svelte 4. Svelte 5+ components
* are completely different under the hood. You should only use this type for typing,
* not actually instantiate components with `new` - use `mount` or `createRoot` instead.
* See [breaking changes](https://svelte-5-preview.vercel.app/docs/breaking-changes#components-are-no-longer-classes)
* for more info.
* are completely different under the hood. For typing, use `Component` instead.
* To instantiate components, use `mount` or `createRoot`.
* See [breaking changes documentation](https://svelte-5-preview.vercel.app/docs/breaking-changes#components-are-no-longer-classes) for more info.
*/
export class SvelteComponent<
Props extends Record<string, any> = Record<string, any>,
@ -77,27 +55,25 @@ declare module 'svelte' {
* For type checking capabilities only.
* Does not exist at runtime.
* ### DO NOT USE!
* */
*/
$$prop_def: Props; // Without Properties: unnecessary, causes type bugs
/**
* For type checking capabilities only.
* Does not exist at runtime.
* ### DO NOT USE!
*
* */
*/
$$events_def: Events;
/**
* For type checking capabilities only.
* Does not exist at runtime.
* ### DO NOT USE!
*
* */
*/
$$slot_def: Slots;
/**
* For type checking capabilities only.
* Does not exist at runtime.
* ### DO NOT USE!
* */
*/
$$bindings?: string;
/**
@ -126,7 +102,61 @@ declare module 'svelte' {
}
/**
* @deprecated Use `SvelteComponent` instead. See TODO for more information.
* Can be used to create strongly typed Svelte components.
*
* #### Example:
*
* You have component library on npm called `component-library`, from which
* you export a component called `MyComponent`. For Svelte+TypeScript users,
* you want to provide typings. Therefore you create a `index.d.ts`:
* ```ts
* import { Component } from "svelte";
* export declare const MyComponent: Component<{ foo: string }> {}
* ```
* Typing this makes it possible for IDEs like VS Code with the Svelte extension
* to provide intellisense and to use the component like this in a Svelte file
* with TypeScript:
* ```svelte
* <script lang="ts">
* import { MyComponent } from "component-library";
* </script>
* <MyComponent foo={'bar'} />
* ```
*/
export interface Component<
Props extends Record<string, any> = {},
Exports extends Record<string, any> = {},
Bindings extends keyof Props | '' = ''
> {
/**
* @param internal An internal object used by Svelte. Do not use or modify.
* @param props The props passed to the component.
*/
(
internal: unknown,
props: Props
): {
/**
* @deprecated This method only exists when using one of the legacy compatibility helpers, which
* is a stop-gap solution. See https://svelte-5-preview.vercel.app/docs/breaking-changes#components-are-no-longer-classes
* for more info.
*/
$on?(type: string, callback: (e: any) => void): () => void;
/**
* @deprecated This method only exists when using one of the legacy compatibility helpers, which
* is a stop-gap solution. See https://svelte-5-preview.vercel.app/docs/breaking-changes#components-are-no-longer-classes
* for more info.
*/
$set?(props: Partial<Props>): void;
} & Exports;
/** The custom element version of the component. Only present if compiled with the `customElement` compiler option */
element?: typeof HTMLElement;
/** Does not exist at runtime, for typing capabilities only. DO NOT USE */
z_$$bindings?: Bindings;
}
/**
* @deprecated Use `Component` instead. See [breaking changes documentation](https://svelte-5-preview.vercel.app/docs/breaking-changes#components-are-no-longer-classes) for more information.
*/
export class SvelteComponentTyped<
Props extends Record<string, any> = Record<string, any>,
@ -135,6 +165,8 @@ declare module 'svelte' {
> extends SvelteComponent<Props, Events, Slots> {}
/**
* @deprecated The new `Component` type does not have a dedicated Events type. Use `ComponentProps` instead.
*
* Convenience type to get the events the given component expects. Example:
* ```html
* <script lang="ts">
@ -163,10 +195,16 @@ declare module 'svelte' {
* </script>
* ```
*/
export type ComponentProps<Comp extends SvelteComponent> =
Comp extends SvelteComponent<infer Props> ? Props : never;
export type ComponentProps<Comp extends SvelteComponent | Component> =
Comp extends SvelteComponent<infer Props>
? Props
: Comp extends Component<infer Props>
? Props
: never;
/**
* @deprecated This type is obsolete when working with the new `Component` type.
*
* Convenience type to get the type of a Svelte component. Useful for example in combination with
* dynamic components using `<svelte:component>`.
*
@ -310,11 +348,11 @@ declare module 'svelte' {
* Mounts a component to the given target and returns the exports and potentially the props (if compiled with `accessors: true`) of the component
*
* */
export function mount<Props extends Record<string, any>, Exports extends Record<string, any>, Events extends Record<string, any>>(component: ComponentType<SvelteComponent<Props, Events, any>>, options: {
export function mount<Props extends Record<string, any>, Exports extends Record<string, any>>(component: ComponentType<SvelteComponent<Props, any, any>> | Component<Props, Exports, any>, options: {
target: Document | Element | ShadowRoot;
anchor?: Node | undefined;
props?: Props | undefined;
events?: { [Property in keyof Events]: (e: Events[Property]) => any; } | undefined;
events?: Record<string, (e: any) => any> | undefined;
context?: Map<any, any> | undefined;
intro?: boolean | undefined;
}): Exports;
@ -322,10 +360,10 @@ declare module 'svelte' {
* Hydrates a component on the given target and returns the exports and potentially the props (if compiled with `accessors: true`) of the component
*
* */
export function hydrate<Props extends Record<string, any>, Exports extends Record<string, any>, Events extends Record<string, any>>(component: ComponentType<SvelteComponent<Props, Events, any>>, options: {
export function hydrate<Props extends Record<string, any>, Exports extends Record<string, any>>(component: ComponentType<SvelteComponent<Props, any, any>> | Component<Props, Exports, any>, options: {
target: Document | Element | ShadowRoot;
props?: Props | undefined;
events?: { [Property in keyof Events]: (e: Events[Property]) => any; } | undefined;
events?: Record<string, (e: any) => any> | undefined;
context?: Map<any, any> | undefined;
intro?: boolean | undefined;
recover?: boolean | undefined;
@ -1952,7 +1990,7 @@ declare module 'svelte/legacy' {
*
* */
export function createClassComponent<Props extends Record<string, any>, Exports extends Record<string, any>, Events extends Record<string, any>, Slots extends Record<string, any>>(options: import("svelte").ComponentConstructorOptions<Props> & {
component: import("svelte").ComponentType<import("svelte").SvelteComponent<Props, Events, Slots>>;
component: import("svelte").ComponentType<import("svelte").SvelteComponent<Props, Events, Slots>> | import("svelte").Component<Props, any, "">;
immutable?: boolean | undefined;
hydrate?: boolean | undefined;
recover?: boolean | undefined;
@ -1963,7 +2001,7 @@ declare module 'svelte/legacy' {
* @deprecated Use this only as a temporary solution to migrate your imperative component code to Svelte 5.
*
* */
export function asClassComponent<Props extends Record<string, any>, Exports extends Record<string, any>, Events extends Record<string, any>, Slots extends Record<string, any>>(component: import("svelte").SvelteComponent<Props, Events, Slots>): import("svelte").ComponentType<import("svelte").SvelteComponent<Props, Events, Slots> & Exports>;
export function asClassComponent<Props extends Record<string, any>, Exports extends Record<string, any>, Events extends Record<string, any>, Slots extends Record<string, any>>(component: import("svelte").SvelteComponent<Props, Events, Slots> | import("svelte").Component<Props, any, "">): import("svelte").ComponentType<import("svelte").SvelteComponent<Props, Events, Slots> & Exports>;
/**
* Runs the given function once immediately on the server, and works like `$effect.pre` on the client.
*

@ -9,7 +9,9 @@
"ssr": "node ./server.js",
"build": "vite build --outDir dist/client && vite build --outDir dist/server --ssr src/entry-server.ts",
"prod": "npm run build && node dist",
"preview": "vite preview"
"preview": "vite preview",
"format": "prettier --check --write .",
"lint": "prettier --check . && eslint"
},
"devDependencies": {
"@sveltejs/vite-plugin-svelte": "^3.1.0",

@ -0,0 +1,2 @@
input/**.svelte
output

@ -4,6 +4,10 @@
"type": "module",
"license": "MIT",
"version": "0.0.1",
"scripts": {
"format": "prettier --check --write .",
"lint": "prettier --check . && eslint"
},
"dependencies": {
"@types/minimist": "^1.2.5",
"minimist": "^1.2.8",

File diff suppressed because it is too large Load Diff

@ -9,7 +9,9 @@
"build": "vite build",
"preview": "vite preview",
"check-disable-tmp": "svelte-kit sync && svelte-check --tsconfig ./jsconfig.json",
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./jsconfig.json --watch"
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./jsconfig.json --watch",
"format": "prettier --check --write .",
"lint": "prettier --check ."
},
"devDependencies": {
"@fontsource/fira-mono": "^5.0.8",
@ -20,6 +22,7 @@
"@sveltejs/site-kit": "6.0.0-next.64",
"@sveltejs/vite-plugin-svelte": "^3.1.0",
"@types/marked": "^6.0.0",
"@vercel/speed-insights": "^1.0.0",
"esrap": "^1.2.2",
"marked": "^9.0.0",
"publint": "^0.2.7",

@ -1,9 +1,12 @@
<script>
import { injectSpeedInsights } from '@vercel/speed-insights/sveltekit';
import { page } from '$app/stores';
import { Icon, Shell } from '@sveltejs/site-kit/components';
import { Nav, Separator } from '@sveltejs/site-kit/nav';
import '@sveltejs/site-kit/styles/index.css';
injectSpeedInsights();
export let data;
</script>

@ -1,6 +1,7 @@
<script>
import { theme } from '@sveltejs/site-kit/stores';
import '@sveltejs/site-kit/styles/index.css';
import { replaceState } from '$app/navigation';
import Repl from '$lib/Repl.svelte';
import { default_files } from './defaults.js';
@ -69,10 +70,9 @@
setting_hash = true;
history.replaceState(
history.state,
'',
`${location.pathname}${location.search}#${await compress_and_encode_text(json)}`
replaceState(
`${location.pathname}${location.search}#${await compress_and_encode_text(json)}`,
{}
);
}
</script>

@ -39,7 +39,7 @@ Traditional `on:` event handlers will continue to work, but are deprecated in Sv
In Svelte 4, components could emit events by creating a dispatcher with [`createEventDispatcher`](https://svelte.dev/docs/svelte#createeventdispatcher).
This function is deprecated in Svelte 5. Instead, components should accept _callback props_ ([demo](/#H4sIAAAAAAAAE41TS27bMBC9ykBtELu1ZTmAG0C2hPYG3dddyPIwJkKRAjmy4wrad9VFL5BV75cjlKQof5osutCHb968-XCmjRgXaKL0WxvJosIojb7UdTSJ6Fi7g9mjILRnoxpdOmRlSs1rytdyTbyqlSb42lQ1MK0quI1n7hD3brdLR3KPQALDfyBk8N5QQTiaL8bLwbJptKGziRXCoLdaO2tkSVxJ0GiQRmNovSYFtfmij0GDhnf2WLeWq9k5WblymfmsJRM2TtZatSy_EvyYwSDIGYw8lsP9YnzKkXQT5Dv33uJbWhe-ybgvfDooO7-ZT6h9Z3le10utNg2RLVTJUvDyMWt9xV0u8QCbQgilbD09xzd_ZepCQikKY7J1tFGqWkf5y_PvP7Zqa7GcNkXbjO4Nci-3jsDQUaBFTFkITKFN4mQOH3zKnZXry3l5_vXTi5yEZ5x1vqfe39N8gFB_rQx3l5YC40-4DR0VyCiFJJxI1efDgW9pl8I8SW4CskP-sKMriClJU5eZR_eHQQifaFoI_mDDlSgJ9RCPS5yedJZDatxRpri3VJOCVPI0Lu4Th94MpZAu5FCMbxIk8Z259rCtH-iF5FXRsz2cxAsDTOlDobdXXp8f8ci03TgDl_7JDbQQLiOJP0HXw3eLK_x-MRhcey4sPdxPfrgZu7uV2nLGcRulbnq7yWnV3Ub87667RW0h7M4EwuBD5_a21qo2I7ey1xv370QH7y4PPxfz_IobAnR5-DlxXxf0vfsLb_4Z08cEAAA=)):
This function is deprecated in Svelte 5. Instead, components should accept _callback props_ - which means you then pass functions as properties to these components ([demo](/#H4sIAAAAAAAACo1US27bMBC9yoBtELu2ZDmAG0CRhPYG3VddyPIwIUKRgjiOkwrcd9VFL5BV75cjFKQo2e5_IQnzeW-GM3zqGRcSDUs_9kxVDbKUvW9btmT01DrDPKAkZEtm9L6rnSczdSdaKkpVkmha3RF82Dct8E43cBmvnBEPsMsbl-QeiQRGfEbI4bWhinC23sxvxsh23xk6hnglDfqoKonvVU1CK-jQIM3m0HtOCmzrzVCDRg4P9j5bqmx1bFZlrjPfteKyIsz7WasP2M0hL85YFzn4QGAWHGbeX8D1Zj41S90-1LHuvcM_kp4QJPNhDNFpCUew8i32rwQfCnjObLsn0gq0qqWo7_Pez8AWCg-wraTUWmWrIcevIzNtpaCWlTF5ybZaNyUrXp6_fc9WLlKUqk9RGrS_SR7oSgaGniTmJTN1JTGFPomTNbzxbduSFcORXp6_fvEkE_FKcOun7PE-zRcIM2i1EW6NKXDxiLswWomcUkiCRbo9Ggexo7sU1klyETx3KG7v6MzFtaLIdea9D4eRCB8pqqS4VSnUqGhapRQKo4nnZmxNuJQIH1CRSUFpNV0g94nDbMajUFep8TB-SJDEV-YcoXUzpldKNNWQ7d1JvDHAdXeout0Z6t09PvGuatDAKT65gB7CMpL4LdjBfbU5819vxoAbz0lkcA9aCJthS9boneACdyx119guJ_E7jfyv-p10ewhqWkJQAFin5LbTrZkdJe5v-1HiXvzn6vz5rs-8hAJ7EJUtgn1y7f8ADN1MwGD_G-gBUWSLaModfnA-kELvvxb-Bl8sbLGY4L_O-5P9ATwVcA54BQAA)):
```svelte
<script>
@ -55,12 +55,12 @@ This function is deprecated in Svelte 5. Instead, components should accept _call
</script>
<Pump
inflate={() => {
size += 5;
inflate={(power) => {
size += power;
if (size > 75) burst = true;
}}
deflate={() => {
if (size > 0) size -= 5;
deflate={(power) => {
if (size > 0) size -= power;
}}
/>
@ -77,10 +77,14 @@ This function is deprecated in Svelte 5. Instead, components should accept _call
```svelte
<script>
let { inflate, deflate } = $props();
let power = $state(5);
</script>
<button onclick={inflate}>inflate</button>
<button onclick={deflate}>deflate</button>
<button onclick={() => inflate(power)}>inflate</button>
<button onclick={() => deflate(power)}>deflate</button>
<button onclick={() => power--}>-</button>
Pump power: {power}
<button onclick={() => power++}>+</button>
```
## Bubbling events

@ -86,6 +86,37 @@ import App from './App.svelte';
`render` also no longer returns CSS; it should be served separately from a CSS file.
### Component typing changes
The change from classes towards functions is also reflected in the typings: `SvelteComponent`, the base class from Svelte 4, is deprecated in favor of the new `Component` type which defines the function shape of a Svelte component. To manually define a component shape in a `d.ts` file:
```ts
import type { Component } from 'svelte';
export declare const MyComponent: Component<{
foo: string;
}>;
```
To declare that a component of a certain type is required:
```svelte
<script lang="ts">
import type { Component } from 'svelte';
import {
ComponentA,
ComponentB
} from 'component-library';
let component: Component<{ foo: string }> = $state(
Math.random() ? ComponentA : ComponentB
);
</script>
<svelte:component this={component} foo="bar" />
```
The two utility types `ComponentEvents` and `ComponentType` are also deprecated. `ComponentEvents` is obsolete because events are defined as callback props now, and `ComponentType` is obsolete because the new `Component` type is the component type already (e.g. `ComponentType<SvelteComponent<{ prop: string }>>` == `Component<{ prop: string }>`).
### bind:this changes
Because components are no longer classes, using `bind:this` no longer returns a class instance with `$set`, `$on` and `$destroy` methods on it. It only returns the instance exports (`export function/const`) and, if you're using the `accessors` option, a getter/setter-pair for each property.

@ -0,0 +1,7 @@
static/svelte-app.json
scripts/svelte-app/
src/routes/_components/Supporters/contributors.jpg
src/routes/_components/Supporters/contributors.js
src/routes/_components/Supporters/donors.jpg
src/routes/_components/Supporters/donors.js
src/lib/generated

@ -13,8 +13,9 @@
"start": "node build",
"check": "node scripts/update.js && pnpm generate && svelte-kit sync && svelte-check",
"check:watch": "svelte-kit sync && svelte-check --watch",
"format": "prettier --check . --write",
"check:format": "prettier --check ."
"check:format": "prettier --check .",
"format": "prettier --check --write .",
"lint": "prettier --check ."
},
"dependencies": {
"@jridgewell/sourcemap-codec": "^1.4.15",

Loading…
Cancel
Save