Merge branch 'main' into fix-unmount-memory-leak

pull/12101/head
Rich Harris 2 years ago committed by GitHub
commit 4f84a078bd
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -0,0 +1,5 @@
---
'svelte': patch
---
feat: more accurate `render`/`mount`/`hydrate` options

@ -0,0 +1,5 @@
---
"svelte": patch
---
fix: wait a microtask for await blocks to reduce UI churn

@ -0,0 +1,5 @@
---
"svelte": patch
---
fix: ensure state update expressions are serialised correctly

@ -0,0 +1,5 @@
---
"svelte": patch
---
fix: repair each block length even without an else

@ -0,0 +1,5 @@
---
"svelte": patch
---
breaking: bump dts-buddy

@ -0,0 +1,5 @@
---
"svelte": patch
---
fix: better binding interop between runes/non-runes components

@ -0,0 +1,5 @@
---
"svelte": patch
---
fix: throw compilation error for malformed snippets

@ -141,6 +141,7 @@
"fair-crabs-check", "fair-crabs-check",
"fair-pianos-talk", "fair-pianos-talk",
"fair-spies-repeat", "fair-spies-repeat",
"famous-chairs-notice",
"famous-falcons-melt", "famous-falcons-melt",
"famous-kiwis-thank", "famous-kiwis-thank",
"famous-knives-sneeze", "famous-knives-sneeze",
@ -187,6 +188,7 @@
"fuzzy-bags-camp", "fuzzy-bags-camp",
"fuzzy-donuts-provide", "fuzzy-donuts-provide",
"gentle-dolls-juggle", "gentle-dolls-juggle",
"gentle-eagles-walk",
"gentle-sheep-hug", "gentle-sheep-hug",
"gentle-spies-happen", "gentle-spies-happen",
"gentle-ties-fetch", "gentle-ties-fetch",
@ -205,6 +207,7 @@
"good-plums-type", "good-plums-type",
"good-rivers-yawn", "good-rivers-yawn",
"good-roses-argue", "good-roses-argue",
"gorgeous-boxes-design",
"gorgeous-hats-wonder", "gorgeous-hats-wonder",
"gorgeous-monkeys-carry", "gorgeous-monkeys-carry",
"gorgeous-singers-rest", "gorgeous-singers-rest",
@ -219,6 +222,7 @@
"grumpy-jars-sparkle", "grumpy-jars-sparkle",
"happy-beds-scream", "happy-beds-scream",
"happy-dogs-jump", "happy-dogs-jump",
"happy-lobsters-lick",
"happy-suits-film", "happy-suits-film",
"healthy-ants-film", "healthy-ants-film",
"healthy-planes-vanish", "healthy-planes-vanish",
@ -322,6 +326,7 @@
"mighty-files-hammer", "mighty-files-hammer",
"mighty-frogs-obey", "mighty-frogs-obey",
"modern-apricots-promise", "modern-apricots-promise",
"modern-fishes-double",
"moody-carrots-lay", "moody-carrots-lay",
"moody-frogs-exist", "moody-frogs-exist",
"moody-ghosts-buy", "moody-ghosts-buy",
@ -337,6 +342,7 @@
"neat-files-rescue", "neat-files-rescue",
"neat-jokes-beam", "neat-jokes-beam",
"nervous-berries-boil", "nervous-berries-boil",
"nervous-ducks-repeat",
"nervous-spoons-relax", "nervous-spoons-relax",
"nervous-turkeys-end", "nervous-turkeys-end",
"new-boats-wait", "new-boats-wait",
@ -380,6 +386,7 @@
"polite-dolphins-care", "polite-dolphins-care",
"polite-pumpkins-guess", "polite-pumpkins-guess",
"polite-ravens-study", "polite-ravens-study",
"polite-ways-serve",
"poor-eggs-enjoy", "poor-eggs-enjoy",
"poor-hats-design", "poor-hats-design",
"poor-seahorses-flash", "poor-seahorses-flash",
@ -522,6 +529,7 @@
"stale-comics-look", "stale-comics-look",
"stale-fans-rest", "stale-fans-rest",
"stale-jeans-refuse", "stale-jeans-refuse",
"stale-nails-listen",
"strange-apricots-happen", "strange-apricots-happen",
"strange-roses-brake", "strange-roses-brake",
"strong-apricots-destroy", "strong-apricots-destroy",
@ -537,6 +545,7 @@
"swift-feet-juggle", "swift-feet-juggle",
"swift-knives-tie", "swift-knives-tie",
"swift-poets-carry", "swift-poets-carry",
"swift-rats-sing",
"swift-ravens-hunt", "swift-ravens-hunt",
"swift-seahorses-deliver", "swift-seahorses-deliver",
"tall-books-grin", "tall-books-grin",
@ -553,6 +562,7 @@
"tasty-steaks-smile", "tasty-steaks-smile",
"ten-eels-move", "ten-eels-move",
"ten-foxes-repeat", "ten-foxes-repeat",
"ten-geese-share",
"ten-jokes-divide", "ten-jokes-divide",
"ten-peaches-sleep", "ten-peaches-sleep",
"ten-singers-cough", "ten-singers-cough",
@ -597,6 +607,7 @@
"twelve-dragons-join", "twelve-dragons-join",
"twelve-onions-juggle", "twelve-onions-juggle",
"twelve-worms-jog", "twelve-worms-jog",
"twenty-gifts-develop",
"two-brooms-fail", "two-brooms-fail",
"two-candles-move", "two-candles-move",
"two-dogs-accept", "two-dogs-accept",

@ -0,0 +1,5 @@
---
"svelte": patch
---
chore: remove anchor node from each block items

@ -0,0 +1,5 @@
---
"svelte": patch
---
fix: prevent `a11y_label_has_associated_control` false positive for component or render tag in `<label>`

@ -0,0 +1,5 @@
---
"svelte": patch
---
fix: allow multiple optional parameters with defaults in snippets

@ -0,0 +1,5 @@
---
"svelte": patch
---
fix: remove document event listeners on unmount

2
.gitignore vendored

@ -22,3 +22,5 @@ coverage
.DS_Store .DS_Store
tmp tmp
benchmarking/compare/.results

@ -0,0 +1,58 @@
import {
kairo_avoidable_owned,
kairo_avoidable_unowned
} from './benchmarks/kairo/kairo_avoidable.js';
import { kairo_broad_owned, kairo_broad_unowned } from './benchmarks/kairo/kairo_broad.js';
import { kairo_deep_owned, kairo_deep_unowned } from './benchmarks/kairo/kairo_deep.js';
import { kairo_diamond_owned, kairo_diamond_unowned } from './benchmarks/kairo/kairo_diamond.js';
import { kairo_mux_unowned, kairo_mux_owned } from './benchmarks/kairo/kairo_mux.js';
import { kairo_repeated_unowned, kairo_repeated_owned } from './benchmarks/kairo/kairo_repeated.js';
import { kairo_triangle_owned, kairo_triangle_unowned } from './benchmarks/kairo/kairo_triangle.js';
import { kairo_unstable_owned, kairo_unstable_unowned } from './benchmarks/kairo/kairo_unstable.js';
import { mol_bench_owned, mol_bench_unowned } from './benchmarks/mol_bench.js';
import {
sbench_create_0to1,
sbench_create_1000to1,
sbench_create_1to1,
sbench_create_1to1000,
sbench_create_1to2,
sbench_create_1to4,
sbench_create_1to8,
sbench_create_2to1,
sbench_create_4to1,
sbench_create_signals
} from './benchmarks/sbench.js';
// This benchmark has been adapted from the js-reactivity-benchmark (https://github.com/milomg/js-reactivity-benchmark)
// Not all tests are the same, and many parts have been tweaked to capture different data.
export const benchmarks = [
sbench_create_signals,
sbench_create_0to1,
sbench_create_1to1,
sbench_create_2to1,
sbench_create_4to1,
sbench_create_1000to1,
sbench_create_1to2,
sbench_create_1to4,
sbench_create_1to8,
sbench_create_1to1000,
kairo_avoidable_owned,
kairo_avoidable_unowned,
kairo_broad_owned,
kairo_broad_unowned,
kairo_deep_owned,
kairo_deep_unowned,
kairo_diamond_owned,
kairo_diamond_unowned,
kairo_triangle_owned,
kairo_triangle_unowned,
kairo_mux_owned,
kairo_mux_unowned,
kairo_repeated_owned,
kairo_repeated_unowned,
kairo_unstable_owned,
kairo_unstable_unowned,
mol_bench_owned,
mol_bench_unowned
];

@ -34,7 +34,7 @@ function setup() {
}; };
} }
export async function kairo_avoidable() { export async function kairo_avoidable_unowned() {
// Do 10 loops to warm up JIT // Do 10 loops to warm up JIT
for (let i = 0; i < 10; i++) { for (let i = 0; i < 10; i++) {
const { run, destroy } = setup(); const { run, destroy } = setup();
@ -53,7 +53,38 @@ export async function kairo_avoidable() {
destroy(); destroy();
return { return {
benchmark: 'kairo_avoidable', benchmark: 'kairo_avoidable_unowned',
time: timing.time.toFixed(2),
gc_time: timing.gc_time.toFixed(2)
};
}
export async function kairo_avoidable_owned() {
let run, destroy;
const destroy_owned = $.effect_root(() => {
// Do 10 loops to warm up JIT
for (let i = 0; i < 10; i++) {
const { run, destroy } = setup();
run();
destroy();
}
({ run, destroy } = setup());
});
const { timing } = await fastest_test(10, () => {
for (let i = 0; i < 100; i++) {
run();
}
});
// @ts-ignore
destroy();
destroy_owned();
return {
benchmark: 'kairo_avoidable_owned',
time: timing.time.toFixed(2), time: timing.time.toFixed(2),
gc_time: timing.gc_time.toFixed(2) gc_time: timing.gc_time.toFixed(2)
}; };

@ -28,7 +28,7 @@ function setup() {
$.flush_sync(() => { $.flush_sync(() => {
$.set(head, 1); $.set(head, 1);
}); });
counter = 0 counter = 0;
for (let i = 0; i < 50; i++) { for (let i = 0; i < 50; i++) {
$.flush_sync(() => { $.flush_sync(() => {
$.set(head, i); $.set(head, i);
@ -40,7 +40,7 @@ function setup() {
}; };
} }
export async function kairo_broad() { export async function kairo_broad_unowned() {
// Do 10 loops to warm up JIT // Do 10 loops to warm up JIT
for (let i = 0; i < 10; i++) { for (let i = 0; i < 10; i++) {
const { run, destroy } = setup(); const { run, destroy } = setup();
@ -59,7 +59,38 @@ export async function kairo_broad() {
destroy(); destroy();
return { return {
benchmark: 'kairo_broad', benchmark: 'kairo_broad_unowned',
time: timing.time.toFixed(2),
gc_time: timing.gc_time.toFixed(2)
};
}
export async function kairo_broad_owned() {
let run, destroy;
const destroy_owned = $.effect_root(() => {
// Do 10 loops to warm up JIT
for (let i = 0; i < 10; i++) {
const { run, destroy } = setup();
run();
destroy();
}
({ run, destroy } = setup());
});
const { timing } = await fastest_test(10, () => {
for (let i = 0; i < 100; i++) {
run();
}
});
// @ts-ignore
destroy();
destroy_owned();
return {
benchmark: 'kairo_broad_owned',
time: timing.time.toFixed(2), time: timing.time.toFixed(2),
gc_time: timing.gc_time.toFixed(2) gc_time: timing.gc_time.toFixed(2)
}; };

@ -8,12 +8,12 @@ function setup() {
let head = $.source(0); let head = $.source(0);
let current = head; let current = head;
for (let i = 0; i < len; i++) { for (let i = 0; i < len; i++) {
let c = current; let c = current;
current = $.derived(() => { current = $.derived(() => {
return $.get(c) + 1; return $.get(c) + 1;
}); });
} }
let counter = 0; let counter = 0;
const destroy = $.effect_root(() => { const destroy = $.effect_root(() => {
$.render_effect(() => { $.render_effect(() => {
@ -28,7 +28,7 @@ function setup() {
$.flush_sync(() => { $.flush_sync(() => {
$.set(head, 1); $.set(head, 1);
}); });
counter = 0 counter = 0;
for (let i = 0; i < iter; i++) { for (let i = 0; i < iter; i++) {
$.flush_sync(() => { $.flush_sync(() => {
$.set(head, i); $.set(head, i);
@ -40,7 +40,7 @@ function setup() {
}; };
} }
export async function kairo_deep() { export async function kairo_deep_unowned() {
// Do 10 loops to warm up JIT // Do 10 loops to warm up JIT
for (let i = 0; i < 10; i++) { for (let i = 0; i < 10; i++) {
const { run, destroy } = setup(); const { run, destroy } = setup();
@ -59,7 +59,38 @@ export async function kairo_deep() {
destroy(); destroy();
return { return {
benchmark: 'kairo_deep', benchmark: 'kairo_deep_unowned',
time: timing.time.toFixed(2),
gc_time: timing.gc_time.toFixed(2)
};
}
export async function kairo_deep_owned() {
let run, destroy;
const destroy_owned = $.effect_root(() => {
// Do 10 loops to warm up JIT
for (let i = 0; i < 10; i++) {
const { run, destroy } = setup();
run();
destroy();
}
({ run, destroy } = setup());
});
const { timing } = await fastest_test(10, () => {
for (let i = 0; i < 100; i++) {
run();
}
});
// @ts-ignore
destroy();
destroy_owned();
return {
benchmark: 'kairo_deep_owned',
time: timing.time.toFixed(2), time: timing.time.toFixed(2),
gc_time: timing.gc_time.toFixed(2) gc_time: timing.gc_time.toFixed(2)
}; };

@ -5,17 +5,17 @@ let width = 5;
function setup() { function setup() {
let head = $.source(0); let head = $.source(0);
let current = []; let current = [];
for (let i = 0; i < width; i++) { for (let i = 0; i < width; i++) {
current.push( current.push(
$.derived(() => { $.derived(() => {
return $.get(head) + 1; return $.get(head) + 1;
}) })
); );
} }
let sum = $.derived(() => { let sum = $.derived(() => {
return current.map((x) => $.get(x)).reduce((a, b) => a + b, 0); return current.map((x) => $.get(x)).reduce((a, b) => a + b, 0);
}); });
let counter = 0; let counter = 0;
const destroy = $.effect_root(() => { const destroy = $.effect_root(() => {
@ -44,7 +44,7 @@ function setup() {
}; };
} }
export async function kairo_diamond() { export async function kairo_diamond_unowned() {
// Do 10 loops to warm up JIT // Do 10 loops to warm up JIT
for (let i = 0; i < 10; i++) { for (let i = 0; i < 10; i++) {
const { run, destroy } = setup(); const { run, destroy } = setup();
@ -63,7 +63,38 @@ export async function kairo_diamond() {
destroy(); destroy();
return { return {
benchmark: 'kairo_diamond', benchmark: 'kairo_diamond_unowned',
time: timing.time.toFixed(2),
gc_time: timing.gc_time.toFixed(2)
};
}
export async function kairo_diamond_owned() {
let run, destroy;
const destroy_owned = $.effect_root(() => {
// Do 10 loops to warm up JIT
for (let i = 0; i < 10; i++) {
const { run, destroy } = setup();
run();
destroy();
}
({ run, destroy } = setup());
});
const { timing } = await fastest_test(10, () => {
for (let i = 0; i < 100; i++) {
run();
}
});
// @ts-ignore
destroy();
destroy_owned();
return {
benchmark: 'kairo_diamond_owned',
time: timing.time.toFixed(2), time: timing.time.toFixed(2),
gc_time: timing.gc_time.toFixed(2) gc_time: timing.gc_time.toFixed(2)
}; };

@ -3,12 +3,12 @@ import * as $ from '../../../packages/svelte/src/internal/client/index.js';
function setup() { function setup() {
let heads = new Array(100).fill(null).map((_) => $.source(0)); let heads = new Array(100).fill(null).map((_) => $.source(0));
const mux = $.derived(() => { const mux = $.derived(() => {
return Object.fromEntries(heads.map((h) => $.get(h)).entries()); return Object.fromEntries(heads.map((h) => $.get(h)).entries());
}); });
const splited = heads const splited = heads
.map((_, index) => $.derived(() => $.get(mux)[index])) .map((_, index) => $.derived(() => $.get(mux)[index]))
.map((x) => $.derived(() => $.get(x) + 1)); .map((x) => $.derived(() => $.get(x) + 1));
const destroy = $.effect_root(() => { const destroy = $.effect_root(() => {
splited.forEach((x) => { splited.forEach((x) => {
@ -37,7 +37,7 @@ function setup() {
}; };
} }
export async function kairo_mux() { export async function kairo_mux_unowned() {
// Do 10 loops to warm up JIT // Do 10 loops to warm up JIT
for (let i = 0; i < 10; i++) { for (let i = 0; i < 10; i++) {
const { run, destroy } = setup(); const { run, destroy } = setup();
@ -56,7 +56,38 @@ export async function kairo_mux() {
destroy(); destroy();
return { return {
benchmark: 'kairo_mux', benchmark: 'kairo_mux_unowned',
time: timing.time.toFixed(2),
gc_time: timing.gc_time.toFixed(2)
};
}
export async function kairo_mux_owned() {
let run, destroy;
const destroy_owned = $.effect_root(() => {
// Do 10 loops to warm up JIT
for (let i = 0; i < 10; i++) {
const { run, destroy } = setup();
run();
destroy();
}
({ run, destroy } = setup());
});
const { timing } = await fastest_test(10, () => {
for (let i = 0; i < 100; i++) {
run();
}
});
// @ts-ignore
destroy();
destroy_owned();
return {
benchmark: 'kairo_mux_owned',
time: timing.time.toFixed(2), time: timing.time.toFixed(2),
gc_time: timing.gc_time.toFixed(2) gc_time: timing.gc_time.toFixed(2)
}; };

@ -4,14 +4,14 @@ import * as $ from '../../../packages/svelte/src/internal/client/index.js';
let size = 30; let size = 30;
function setup() { function setup() {
let head = $.source(0); let head = $.source(0);
let current = $.derived(() => { let current = $.derived(() => {
let result = 0; let result = 0;
for (let i = 0; i < size; i++) { for (let i = 0; i < size; i++) {
result += $.get(head); result += $.get(head);
} }
return result; return result;
}); });
let counter = 0; let counter = 0;
@ -41,7 +41,7 @@ function setup() {
}; };
} }
export async function kairo_repeated() { export async function kairo_repeated_unowned() {
// Do 10 loops to warm up JIT // Do 10 loops to warm up JIT
for (let i = 0; i < 10; i++) { for (let i = 0; i < 10; i++) {
const { run, destroy } = setup(); const { run, destroy } = setup();
@ -60,7 +60,38 @@ export async function kairo_repeated() {
destroy(); destroy();
return { return {
benchmark: 'kairo_repeated', benchmark: 'kairo_repeated_unowned',
time: timing.time.toFixed(2),
gc_time: timing.gc_time.toFixed(2)
};
}
export async function kairo_repeated_owned() {
let run, destroy;
const destroy_owned = $.effect_root(() => {
// Do 10 loops to warm up JIT
for (let i = 0; i < 10; i++) {
const { run, destroy } = setup();
run();
destroy();
}
({ run, destroy } = setup());
});
const { timing } = await fastest_test(10, () => {
for (let i = 0; i < 100; i++) {
run();
}
});
// @ts-ignore
destroy();
destroy_owned();
return {
benchmark: 'kairo_repeated_owned',
time: timing.time.toFixed(2), time: timing.time.toFixed(2),
gc_time: timing.gc_time.toFixed(2) gc_time: timing.gc_time.toFixed(2)
}; };

@ -4,26 +4,26 @@ import * as $ from '../../../packages/svelte/src/internal/client/index.js';
let width = 10; let width = 10;
function count(number) { function count(number) {
return new Array(number) return new Array(number)
.fill(0) .fill(0)
.map((_, i) => i + 1) .map((_, i) => i + 1)
.reduce((x, y) => x + y, 0); .reduce((x, y) => x + y, 0);
} }
function setup() { function setup() {
let head = $.source(0); let head = $.source(0);
let current = head; let current = head;
let list = []; let list = [];
for (let i = 0; i < width; i++) { for (let i = 0; i < width; i++) {
let c = current; let c = current;
list.push(current); list.push(current);
current = $.derived(() => { current = $.derived(() => {
return $.get(c) + 1; return $.get(c) + 1;
}); });
} }
let sum = $.derived(() => { let sum = $.derived(() => {
return list.map((x) => $.get(x)).reduce((a, b) => a + b, 0); return list.map((x) => $.get(x)).reduce((a, b) => a + b, 0);
}); });
let counter = 0; let counter = 0;
@ -54,7 +54,7 @@ function setup() {
}; };
} }
export async function kairo_triangle() { export async function kairo_triangle_unowned() {
// Do 10 loops to warm up JIT // Do 10 loops to warm up JIT
for (let i = 0; i < 10; i++) { for (let i = 0; i < 10; i++) {
const { run, destroy } = setup(); const { run, destroy } = setup();
@ -73,7 +73,38 @@ export async function kairo_triangle() {
destroy(); destroy();
return { return {
benchmark: 'kairo_triangle', benchmark: 'kairo_triangle_unowned',
time: timing.time.toFixed(2),
gc_time: timing.gc_time.toFixed(2)
};
}
export async function kairo_triangle_owned() {
let run, destroy;
const destroy_owned = $.effect_root(() => {
// Do 10 loops to warm up JIT
for (let i = 0; i < 10; i++) {
const { run, destroy } = setup();
run();
destroy();
}
({ run, destroy } = setup());
});
const { timing } = await fastest_test(10, () => {
for (let i = 0; i < 100; i++) {
run();
}
});
// @ts-ignore
destroy();
destroy_owned();
return {
benchmark: 'kairo_triangle_owned',
time: timing.time.toFixed(2), time: timing.time.toFixed(2),
gc_time: timing.gc_time.toFixed(2) gc_time: timing.gc_time.toFixed(2)
}; };

@ -2,16 +2,16 @@ import { assert, fastest_test } from '../../utils.js';
import * as $ from '../../../packages/svelte/src/internal/client/index.js'; import * as $ from '../../../packages/svelte/src/internal/client/index.js';
function setup() { function setup() {
let head = $.source(0); let head = $.source(0);
const double = $.derived(() => $.get(head) * 2); const double = $.derived(() => $.get(head) * 2);
const inverse = $.derived(() => -$.get(head)); const inverse = $.derived(() => -$.get(head));
let current = $.derived(() => { let current = $.derived(() => {
let result = 0; let result = 0;
for (let i = 0; i < 20; i++) { for (let i = 0; i < 20; i++) {
result += $.get(head) % 2 ? $.get(double) : $.get(inverse); result += $.get(head) % 2 ? $.get(double) : $.get(inverse);
} }
return result; return result;
}); });
let counter = 0; let counter = 0;
@ -40,7 +40,7 @@ function setup() {
}; };
} }
export async function kairo_unstable() { export async function kairo_unstable_unowned() {
// Do 10 loops to warm up JIT // Do 10 loops to warm up JIT
for (let i = 0; i < 10; i++) { for (let i = 0; i < 10; i++) {
const { run, destroy } = setup(); const { run, destroy } = setup();
@ -59,7 +59,38 @@ export async function kairo_unstable() {
destroy(); destroy();
return { return {
benchmark: 'kairo_unstable', benchmark: 'kairo_unstable_unowned',
time: timing.time.toFixed(2),
gc_time: timing.gc_time.toFixed(2)
};
}
export async function kairo_unstable_owned() {
let run, destroy;
const destroy_owned = $.effect_root(() => {
// Do 10 loops to warm up JIT
for (let i = 0; i < 10; i++) {
const { run, destroy } = setup();
run();
destroy();
}
({ run, destroy } = setup());
});
const { timing } = await fastest_test(10, () => {
for (let i = 0; i < 100; i++) {
run();
}
});
// @ts-ignore
destroy();
destroy_owned();
return {
benchmark: 'kairo_unstable_owned',
time: timing.time.toFixed(2), time: timing.time.toFixed(2),
gc_time: timing.gc_time.toFixed(2) gc_time: timing.gc_time.toFixed(2)
}; };

@ -64,7 +64,38 @@ function setup() {
}; };
} }
export async function mol_bench() { export async function mol_bench_owned() {
let run, destroy;
const destroy_owned = $.effect_root(() => {
// Do 10 loops to warm up JIT
for (let i = 0; i < 10; i++) {
const { run, destroy } = setup();
run(0);
destroy();
}
({ run, destroy } = setup());
});
const { timing } = await fastest_test(10, () => {
for (let i = 0; i < 1e4; i++) {
run(i);
}
});
// @ts-ignore
destroy();
destroy_owned();
return {
benchmark: 'mol_bench_owned',
time: timing.time.toFixed(2),
gc_time: timing.gc_time.toFixed(2)
};
}
export async function mol_bench_unowned() {
// Do 10 loops to warm up JIT // Do 10 loops to warm up JIT
for (let i = 0; i < 10; i++) { for (let i = 0; i < 10; i++) {
const { run, destroy } = setup(); const { run, destroy } = setup();
@ -83,7 +114,7 @@ export async function mol_bench() {
destroy(); destroy();
return { return {
benchmark: 'mol_bench', benchmark: 'mol_bench_unowned',
time: timing.time.toFixed(2), time: timing.time.toFixed(2),
gc_time: timing.gc_time.toFixed(2) gc_time: timing.gc_time.toFixed(2)
}; };

@ -0,0 +1,339 @@
import { fastest_test } from '../utils.js';
import * as $ from '../../packages/svelte/src/internal/client/index.js';
const COUNT = 1e5;
/**
* @param {number} n
* @param {any[]} sources
*/
function create_data_signals(n, sources) {
for (let i = 0; i < n; i++) {
sources[i] = $.source(i);
}
return sources;
}
/**
* @param {number} i
*/
function create_computation_0(i) {
$.derived(() => i);
}
/**
* @param {any} s1
*/
function create_computation_1(s1) {
$.derived(() => $.get(s1));
}
/**
* @param {any} s1
* @param {any} s2
*/
function create_computation_2(s1, s2) {
$.derived(() => $.get(s1) + $.get(s2));
}
function create_computation_1000(ss, offset) {
$.derived(() => {
let sum = 0;
for (let i = 0; i < 1000; i++) {
sum += $.get(ss[offset + i]);
}
return sum;
});
}
/**
* @param {number} n
*/
function create_computations_0to1(n) {
for (let i = 0; i < n; i++) {
create_computation_0(i);
}
}
/**
* @param {number} n
* @param {any[]} sources
*/
function create_computations_1to1(n, sources) {
for (let i = 0; i < n; i++) {
const source = sources[i];
create_computation_1(source);
}
}
/**
* @param {number} n
* @param {any[]} sources
*/
function create_computations_2to1(n, sources) {
for (let i = 0; i < n; i++) {
create_computation_2(sources[i * 2], sources[i * 2 + 1]);
}
}
function create_computation_4(s1, s2, s3, s4) {
$.derived(() => $.get(s1) + $.get(s2) + $.get(s3) + $.get(s4));
}
function create_computations_1000to1(n, sources) {
for (let i = 0; i < n; i++) {
create_computation_1000(sources, i * 1000);
}
}
function create_computations_1to2(n, sources) {
for (let i = 0; i < n / 2; i++) {
const source = sources[i];
create_computation_1(source);
create_computation_1(source);
}
}
function create_computations_1to4(n, sources) {
for (let i = 0; i < n / 4; i++) {
const source = sources[i];
create_computation_1(source);
create_computation_1(source);
create_computation_1(source);
create_computation_1(source);
}
}
function create_computations_1to8(n, sources) {
for (let i = 0; i < n / 8; i++) {
const source = sources[i];
create_computation_1(source);
create_computation_1(source);
create_computation_1(source);
create_computation_1(source);
create_computation_1(source);
create_computation_1(source);
create_computation_1(source);
create_computation_1(source);
}
}
function create_computations_1to1000(n, sources) {
for (let i = 0; i < n / 1000; i++) {
const source = sources[i];
for (let j = 0; j < 1000; j++) {
create_computation_1(source);
}
}
}
function create_computations_4to1(n, sources) {
for (let i = 0; i < n; i++) {
create_computation_4(
sources[i * 4],
sources[i * 4 + 1],
sources[i * 4 + 2],
sources[i * 4 + 3]
);
}
}
/**
* @param {any} fn
* @param {number} count
* @param {number} scount
*/
function bench(fn, count, scount) {
let sources = create_data_signals(scount, []);
fn(count, sources);
}
export async function sbench_create_signals() {
// Do 3 loops to warm up JIT
for (let i = 0; i < 3; i++) {
bench(create_data_signals, COUNT, COUNT);
}
const { timing } = await fastest_test(10, () => {
for (let i = 0; i < 100; i++) {
bench(create_data_signals, COUNT, COUNT);
}
});
return {
benchmark: 'sbench_create_signals',
time: timing.time.toFixed(2),
gc_time: timing.gc_time.toFixed(2)
};
}
export async function sbench_create_0to1() {
// Do 3 loops to warm up JIT
for (let i = 0; i < 3; i++) {
bench(create_computations_0to1, COUNT, 0);
}
const { timing } = await fastest_test(10, () => {
for (let i = 0; i < 100; i++) {
bench(create_computations_0to1, COUNT, 0);
}
});
return {
benchmark: 'sbench_create_0to1',
time: timing.time.toFixed(2),
gc_time: timing.gc_time.toFixed(2)
};
}
export async function sbench_create_1to1() {
// Do 3 loops to warm up JIT
for (let i = 0; i < 3; i++) {
bench(create_computations_1to1, COUNT, COUNT);
}
const { timing } = await fastest_test(10, () => {
for (let i = 0; i < 100; i++) {
bench(create_computations_1to1, COUNT, COUNT);
}
});
return {
benchmark: 'sbench_create_1to1',
time: timing.time.toFixed(2),
gc_time: timing.gc_time.toFixed(2)
};
}
export async function sbench_create_2to1() {
// Do 3 loops to warm up JIT
for (let i = 0; i < 3; i++) {
bench(create_computations_2to1, COUNT / 2, COUNT);
}
const { timing } = await fastest_test(10, () => {
for (let i = 0; i < 100; i++) {
bench(create_computations_2to1, COUNT / 2, COUNT);
}
});
return {
benchmark: 'sbench_create_2to1',
time: timing.time.toFixed(2),
gc_time: timing.gc_time.toFixed(2)
};
}
export async function sbench_create_4to1() {
// Do 3 loops to warm up JIT
for (let i = 0; i < 3; i++) {
bench(create_computations_4to1, COUNT / 4, COUNT);
}
const { timing } = await fastest_test(10, () => {
for (let i = 0; i < 100; i++) {
bench(create_computations_4to1, COUNT / 4, COUNT);
}
});
return {
benchmark: 'sbench_create_4to1',
time: timing.time.toFixed(2),
gc_time: timing.gc_time.toFixed(2)
};
}
export async function sbench_create_1000to1() {
// Do 3 loops to warm up JIT
for (let i = 0; i < 3; i++) {
bench(create_computations_1000to1, COUNT / 1000, COUNT);
}
const { timing } = await fastest_test(10, () => {
for (let i = 0; i < 100; i++) {
bench(create_computations_1000to1, COUNT / 1000, COUNT);
}
});
return {
benchmark: 'sbench_create_1000to1',
time: timing.time.toFixed(2),
gc_time: timing.gc_time.toFixed(2)
};
}
export async function sbench_create_1to2() {
// Do 3 loops to warm up JIT
for (let i = 0; i < 3; i++) {
bench(create_computations_1to2, COUNT, COUNT / 2);
}
const { timing } = await fastest_test(10, () => {
for (let i = 0; i < 100; i++) {
bench(create_computations_1to2, COUNT, COUNT / 2);
}
});
return {
benchmark: 'sbench_create_1to2',
time: timing.time.toFixed(2),
gc_time: timing.gc_time.toFixed(2)
};
}
export async function sbench_create_1to4() {
// Do 3 loops to warm up JIT
for (let i = 0; i < 3; i++) {
bench(create_computations_1to4, COUNT, COUNT / 4);
}
const { timing } = await fastest_test(10, () => {
for (let i = 0; i < 100; i++) {
bench(create_computations_1to4, COUNT, COUNT / 4);
}
});
return {
benchmark: 'sbench_create_1to4',
time: timing.time.toFixed(2),
gc_time: timing.gc_time.toFixed(2)
};
}
export async function sbench_create_1to8() {
// Do 3 loops to warm up JIT
for (let i = 0; i < 3; i++) {
bench(create_computations_1to8, COUNT, COUNT / 8);
}
const { timing } = await fastest_test(10, () => {
for (let i = 0; i < 100; i++) {
bench(create_computations_1to8, COUNT, COUNT / 8);
}
});
return {
benchmark: 'sbench_create_1to8',
time: timing.time.toFixed(2),
gc_time: timing.gc_time.toFixed(2)
};
}
export async function sbench_create_1to1000() {
// Do 3 loops to warm up JIT
for (let i = 0; i < 3; i++) {
bench(create_computations_1to1000, COUNT, COUNT / 1000);
}
const { timing } = await fastest_test(10, () => {
for (let i = 0; i < 100; i++) {
bench(create_computations_1to1000, COUNT, COUNT / 1000);
}
});
return {
benchmark: 'sbench_create_1to1000',
time: timing.time.toFixed(2),
gc_time: timing.gc_time.toFixed(2)
};
}

@ -0,0 +1,90 @@
import fs from 'node:fs';
import path from 'node:path';
import { execSync, fork } from 'node:child_process';
import { fileURLToPath } from 'node:url';
import { benchmarks } from '../benchmarks.js';
// if (execSync('git status --porcelain').toString().trim()) {
// console.error('Working directory is not clean');
// process.exit(1);
// }
const filename = fileURLToPath(import.meta.url);
const runner = path.resolve(filename, '../runner.js');
const outdir = path.resolve(filename, '../.results');
if (fs.existsSync(outdir)) fs.rmSync(outdir, { recursive: true });
fs.mkdirSync(outdir);
const branches = [];
for (const arg of process.argv.slice(2)) {
if (arg.startsWith('--')) continue;
if (arg === filename) continue;
branches.push(arg);
}
if (branches.length === 0) {
branches.push(
execSync('git symbolic-ref --short -q HEAD || git rev-parse --short HEAD').toString().trim()
);
}
if (branches.length === 1) {
branches.push('main');
}
process.on('exit', () => {
execSync(`git checkout ${branches[0]}`);
});
for (const branch of branches) {
console.group(`Benchmarking ${branch}`);
execSync(`git checkout ${branch}`);
await new Promise((fulfil, reject) => {
const child = fork(runner);
child.on('message', (results) => {
fs.writeFileSync(`${outdir}/${branch}.json`, JSON.stringify(results, null, ' '));
fulfil();
});
child.on('error', reject);
});
console.groupEnd();
}
const results = branches.map((branch) => {
return JSON.parse(fs.readFileSync(`${outdir}/${branch}.json`, 'utf-8'));
});
for (let i = 0; i < results[0].length; i += 1) {
console.group(`${results[0][i].benchmark}`);
for (const metric of ['time', 'gc_time']) {
const times = results.map((result) => +result[i][metric]);
let min = Infinity;
let min_index = -1;
for (let b = 0; b < times.length; b += 1) {
if (times[b] < min) {
min = times[b];
min_index = b;
}
}
if (min !== 0) {
console.group(`${metric}: fastest is ${branches[min_index]}`);
times.forEach((time, b) => {
console.log(`${branches[b]}: ${time.toFixed(2)}ms (${((time / min) * 100).toFixed(2)}%)`);
});
console.groupEnd();
}
}
console.groupEnd();
}

@ -0,0 +1,10 @@
import { benchmarks } from '../benchmarks.js';
const results = [];
for (const benchmark of benchmarks) {
const result = await benchmark();
console.error(result.benchmark);
results.push(result);
}
process.send(results);

@ -1,48 +1,32 @@
import * as $ from '../packages/svelte/src/internal/client/index.js'; import * as $ from '../packages/svelte/src/internal/client/index.js';
import { kairo_avoidable } from './benchmarks/kairo/kairo_avoidable.js'; import { benchmarks } from './benchmarks.js';
import { kairo_broad } from './benchmarks/kairo/kairo_broad.js';
import { kairo_deep } from './benchmarks/kairo/kairo_deep.js';
import { kairo_diamond } from './benchmarks/kairo/kairo_diamond.js';
import { kairo_mux } from './benchmarks/kairo/kairo_mux.js';
import { kairo_repeated } from './benchmarks/kairo/kairo_repeated.js';
import { kairo_triangle } from './benchmarks/kairo/kairo_triangle.js';
import { kairo_unstable } from './benchmarks/kairo/kairo_unstable.js';
import { mol_bench } from './benchmarks/mol_bench.js';
// This benchmark has been adapted from the js-reactivity-benchmark (https://github.com/milomg/js-reactivity-benchmark) let total_time = 0;
// Not all tests are the same, and many parts have been tweaked to capture different data. let total_gc_time = 0;
const benchmarks = [ // eslint-disable-next-line no-console
kairo_avoidable, console.log('-- Benchmarking Started --');
kairo_broad, $.push({}, true);
kairo_deep, try {
kairo_diamond, for (const benchmark of benchmarks) {
kairo_triangle, const results = await benchmark();
kairo_mux,
kairo_repeated,
kairo_unstable,
mol_bench
];
async function run_benchmarks() {
// eslint-disable-next-line no-console
console.log('-- Benchmarking Started --');
$.push({}, true);
try {
for (const benchmark of benchmarks) {
// eslint-disable-next-line no-console
console.log(await benchmark());
}
} catch (e) {
// eslint-disable-next-line no-console
console.error('-- Benchmarking Failed --');
// eslint-disable-next-line no-console // eslint-disable-next-line no-console
console.error(e); console.log(results);
process.exit(1); total_time += Number(results.time);
total_gc_time += Number(results.gc_time);
} }
$.pop(); } catch (e) {
// eslint-disable-next-line no-console // eslint-disable-next-line no-console
console.log('-- Benchmarking Complete --'); console.error('-- Benchmarking Failed --');
// eslint-disable-next-line no-console
console.error(e);
process.exit(1);
} }
$.pop();
run_benchmarks(); // eslint-disable-next-line no-console
console.log('-- Benchmarking Complete --');
// eslint-disable-next-line no-console
console.log({
total_time: total_time.toFixed(2),
total_gc_time: total_gc_time.toFixed(2)
});

@ -25,23 +25,22 @@
"changeset:version": "changeset version && pnpm -r generate:version && git add --all", "changeset:version": "changeset version && pnpm -r generate:version && git add --all",
"changeset:publish": "changeset publish", "changeset:publish": "changeset publish",
"bench": "node --allow-natives-syntax ./benchmarking/run.js", "bench": "node --allow-natives-syntax ./benchmarking/run.js",
"bench:compare": "node --allow-natives-syntax ./benchmarking/compare/index.js",
"bench:debug": "node --allow-natives-syntax --inspect-brk ./benchmarking/run.js" "bench:debug": "node --allow-natives-syntax --inspect-brk ./benchmarking/run.js"
}, },
"devDependencies": { "devDependencies": {
"@changesets/cli": "^2.27.1", "@changesets/cli": "^2.27.6",
"@sveltejs/eslint-config": "^7.0.1", "@sveltejs/eslint-config": "^7.0.1",
"@svitejs/changesets-changelog-github-compact": "^1.1.0", "@svitejs/changesets-changelog-github-compact": "^1.1.0",
"@types/node": "^20.11.5", "@types/node": "^20.11.5",
"@vitest/coverage-v8": "^1.2.1", "@vitest/coverage-v8": "^1.2.1",
"concurrently": "^8.2.2",
"cross-env": "^7.0.3",
"eslint": "^9.0.0", "eslint": "^9.0.0",
"eslint-plugin-lube": "^0.4.3", "eslint-plugin-lube": "^0.4.3",
"jsdom": "22.0.0", "jsdom": "22.0.0",
"playwright": "^1.41.1", "playwright": "^1.41.1",
"prettier": "^3.2.4", "prettier": "^3.2.4",
"prettier-plugin-svelte": "^3.1.2", "prettier-plugin-svelte": "^3.1.2",
"typescript": "^5.3.3", "typescript": "^5.5.2",
"typescript-eslint": "^8.0.0-alpha.20", "typescript-eslint": "^8.0.0-alpha.20",
"v8-natives": "^1.2.5", "v8-natives": "^1.2.5",
"vitest": "^1.2.1" "vitest": "^1.2.1"

@ -1,5 +1,47 @@
# svelte # svelte
## 5.0.0-next.165
### Patch Changes
- breaking: bump dts-buddy ([#12134](https://github.com/sveltejs/svelte/pull/12134))
- fix: throw compilation error for malformed snippets ([#12144](https://github.com/sveltejs/svelte/pull/12144))
## 5.0.0-next.164
### Patch Changes
- fix: prevent `a11y_label_has_associated_control` false positive for component or render tag in `<label>` ([#12119](https://github.com/sveltejs/svelte/pull/12119))
- fix: allow multiple optional parameters with defaults in snippets ([#12070](https://github.com/sveltejs/svelte/pull/12070))
## 5.0.0-next.163
### Patch Changes
- feat: more accurate `render`/`mount`/`hydrate` options ([#12111](https://github.com/sveltejs/svelte/pull/12111))
- fix: better binding interop between runes/non-runes components ([#12123](https://github.com/sveltejs/svelte/pull/12123))
## 5.0.0-next.162
### Patch Changes
- chore: remove anchor node from each block items ([#11836](https://github.com/sveltejs/svelte/pull/11836))
## 5.0.0-next.161
### Patch Changes
- fix: wait a microtask for await blocks to reduce UI churn ([#11989](https://github.com/sveltejs/svelte/pull/11989))
- fix: ensure state update expressions are serialised correctly ([#12109](https://github.com/sveltejs/svelte/pull/12109))
- fix: repair each block length even without an else ([#12098](https://github.com/sveltejs/svelte/pull/12098))
- fix: remove document event listeners on unmount ([#12105](https://github.com/sveltejs/svelte/pull/12105))
## 5.0.0-next.160 ## 5.0.0-next.160
### Patch Changes ### Patch Changes

@ -2,7 +2,7 @@
"name": "svelte", "name": "svelte",
"description": "Cybernetically enhanced web apps", "description": "Cybernetically enhanced web apps",
"license": "MIT", "license": "MIT",
"version": "5.0.0-next.160", "version": "5.0.0-next.165",
"type": "module", "type": "module",
"types": "./types/index.d.ts", "types": "./types/index.d.ts",
"engines": { "engines": {
@ -112,7 +112,7 @@
"prepublishOnly": "pnpm build", "prepublishOnly": "pnpm build",
"format": "prettier --check --write .", "format": "prettier --check --write .",
"lint": "prettier --check . && eslint", "lint": "prettier --check . && eslint",
"knip": "knip" "knip": "pnpm dlx knip"
}, },
"devDependencies": { "devDependencies": {
"@jridgewell/trace-mapping": "^0.3.22", "@jridgewell/trace-mapping": "^0.3.22",
@ -122,9 +122,8 @@
"@rollup/plugin-terser": "^0.4.4", "@rollup/plugin-terser": "^0.4.4",
"@rollup/plugin-virtual": "^3.0.2", "@rollup/plugin-virtual": "^3.0.2",
"@types/aria-query": "^5.0.4", "@types/aria-query": "^5.0.4",
"dts-buddy": "^0.4.7", "dts-buddy": "^0.5.0",
"esbuild": "^0.19.11", "esbuild": "^0.19.11",
"knip": "^4.2.1",
"rollup": "^4.9.5", "rollup": "^4.9.5",
"source-map": "^0.7.4", "source-map": "^0.7.4",
"tiny-glob": "^0.2.9" "tiny-glob": "^0.2.9"

@ -30,7 +30,7 @@ await createBundle({
[`${pkg.name}/legacy`]: `${dir}/src/legacy/legacy-client.js`, [`${pkg.name}/legacy`]: `${dir}/src/legacy/legacy-client.js`,
[`${pkg.name}/motion`]: `${dir}/src/motion/public.d.ts`, [`${pkg.name}/motion`]: `${dir}/src/motion/public.d.ts`,
[`${pkg.name}/reactivity`]: `${dir}/src/reactivity/index-client.js`, [`${pkg.name}/reactivity`]: `${dir}/src/reactivity/index-client.js`,
[`${pkg.name}/server`]: `${dir}/src/server/index.js`, [`${pkg.name}/server`]: `${dir}/src/server/index.d.ts`,
[`${pkg.name}/store`]: `${dir}/src/store/public.d.ts`, [`${pkg.name}/store`]: `${dir}/src/store/public.d.ts`,
[`${pkg.name}/transition`]: `${dir}/src/transition/public.d.ts`, [`${pkg.name}/transition`]: `${dir}/src/transition/public.d.ts`,
[`${pkg.name}/events`]: `${dir}/src/events/index.js`, [`${pkg.name}/events`]: `${dir}/src/events/index.js`,

@ -1,6 +1,7 @@
import * as acorn from 'acorn'; import * as acorn from 'acorn';
import { walk } from 'zimmerframe'; import { walk } from 'zimmerframe';
import { tsPlugin } from 'acorn-typescript'; import { tsPlugin } from 'acorn-typescript';
import { locator } from '../../state.js';
const ParserWithTS = acorn.Parser.extend(tsPlugin({ allowSatisfies: true })); const ParserWithTS = acorn.Parser.extend(tsPlugin({ allowSatisfies: true }));
@ -127,6 +128,16 @@ function amend(source, node) {
// @ts-expect-error // @ts-expect-error
delete node.loc.end.index; delete node.loc.end.index;
if (typeof node.loc?.end === 'number') {
const loc = locator(node.loc.end);
if (loc) {
node.loc.end = {
line: loc.line,
column: loc.column
};
}
}
if (/** @type {any} */ (node).typeAnnotation && node.end === undefined) { if (/** @type {any} */ (node).typeAnnotation && node.end === undefined) {
// i think there might be a bug in acorn-typescript that prevents // i think there might be a bug in acorn-typescript that prevents
// `end` from being assigned when there's a type annotation // `end` from being assigned when there's a type annotation

@ -14,17 +14,16 @@ import { locator } from '../../../state.js';
/** /**
* @param {import('../index.js').Parser} parser * @param {import('../index.js').Parser} parser
* @param {boolean} [optional_allowed]
* @returns {import('estree').Pattern} * @returns {import('estree').Pattern}
*/ */
export default function read_pattern(parser, optional_allowed = false) { export default function read_pattern(parser) {
const start = parser.index; const start = parser.index;
let i = parser.index; let i = parser.index;
const code = full_char_code_at(parser.template, i); const code = full_char_code_at(parser.template, i);
if (isIdentifierStart(code, true)) { if (isIdentifierStart(code, true)) {
const name = /** @type {string} */ (parser.read_identifier()); const name = /** @type {string} */ (parser.read_identifier());
const annotation = read_type_annotation(parser, optional_allowed); const annotation = read_type_annotation(parser);
return { return {
type: 'Identifier', type: 'Identifier',
@ -84,7 +83,7 @@ export default function read_pattern(parser, optional_allowed = false) {
parse_expression_at(`${space_with_newline}(${pattern_string} = 1)`, parser.ts, start - 1) parse_expression_at(`${space_with_newline}(${pattern_string} = 1)`, parser.ts, start - 1)
).left; ).left;
expression.typeAnnotation = read_type_annotation(parser, optional_allowed); expression.typeAnnotation = read_type_annotation(parser);
if (expression.typeAnnotation) { if (expression.typeAnnotation) {
expression.end = expression.typeAnnotation.end; expression.end = expression.typeAnnotation.end;
} }
@ -97,19 +96,12 @@ export default function read_pattern(parser, optional_allowed = false) {
/** /**
* @param {import('../index.js').Parser} parser * @param {import('../index.js').Parser} parser
* @param {boolean} [optional_allowed]
* @returns {any} * @returns {any}
*/ */
function read_type_annotation(parser, optional_allowed = false) { function read_type_annotation(parser) {
const start = parser.index; const start = parser.index;
parser.allow_whitespace(); parser.allow_whitespace();
if (optional_allowed && parser.eat('?')) {
// Acorn-TS puts the optional info as a property on the surrounding node.
// We spare the work here because it doesn't matter for us anywhere else.
parser.allow_whitespace();
}
if (!parser.eat(':')) { if (!parser.eat(':')) {
parser.index = start; parser.index = start;
return undefined; return undefined;

@ -3,6 +3,7 @@ import read_expression from '../read/expression.js';
import * as e from '../../../errors.js'; import * as e from '../../../errors.js';
import { create_fragment } from '../utils/create.js'; import { create_fragment } from '../utils/create.js';
import { walk } from 'zimmerframe'; import { walk } from 'zimmerframe';
import { parse_expression_at } from '../acorn.js';
const regex_whitespace_with_closing_curly_brace = /^\s*}/; const regex_whitespace_with_closing_curly_brace = /^\s*}/;
@ -268,37 +269,28 @@ function open(parser) {
e.expected_identifier(parser.index); e.expected_identifier(parser.index);
} }
parser.eat('(', true);
parser.allow_whitespace(); parser.allow_whitespace();
/** @type {import('estree').Pattern[]} */ const params_start = parser.index;
const parameters = [];
while (!parser.match(')')) {
let pattern = read_pattern(parser, true);
parser.allow_whitespace();
if (parser.eat('=')) {
parser.allow_whitespace();
const right = read_expression(parser);
pattern = {
type: 'AssignmentPattern',
left: pattern,
right: right,
start: pattern.start,
end: right.end
};
}
parameters.push(pattern); parser.eat('(', true);
let parentheses = 1;
if (!parser.eat(',')) break; while (parser.index < parser.template.length && (!parser.match(')') || parentheses !== 1)) {
parser.allow_whitespace(); if (parser.match('(')) parentheses++;
if (parser.match(')')) parentheses--;
parser.index += 1;
} }
parser.eat(')', true); parser.eat(')', true);
const prelude = parser.template.slice(0, params_start).replace(/\S/g, ' ');
const params = parser.template.slice(params_start, parser.index);
let function_expression = /** @type {import('estree').ArrowFunctionExpression} */ (
parse_expression_at(prelude + `${params} => {}`, parser.ts, params_start)
);
parser.allow_whitespace(); parser.allow_whitespace();
parser.eat('}', true); parser.eat('}', true);
@ -313,10 +305,9 @@ function open(parser) {
end: name_end, end: name_end,
name name
}, },
parameters, parameters: function_expression.params,
body: create_fragment() body: create_fragment()
}); });
parser.stack.push(block); parser.stack.push(block);
parser.fragments.push(block.body); parser.fragments.push(block.body);

@ -1086,6 +1086,8 @@ function check_element(node, state) {
if ( if (
node.type === 'SvelteElement' || node.type === 'SvelteElement' ||
node.type === 'SlotElement' || node.type === 'SlotElement' ||
node.type === 'Component' ||
node.type === 'RenderTag' ||
(node.type === 'RegularElement' && (node.type === 'RegularElement' &&
(a11y_labelable.includes(node.name) || node.name === 'slot')) (a11y_labelable.includes(node.name) || node.name === 'slot'))
) { ) {

@ -363,7 +363,12 @@ export function client_component(source, analysis, options) {
} }
if (analysis.uses_props || analysis.uses_rest_props) { if (analysis.uses_props || analysis.uses_rest_props) {
const to_remove = [b.literal('children'), b.literal('$$slots'), b.literal('$$events')]; const to_remove = [
b.literal('children'),
b.literal('$$slots'),
b.literal('$$events'),
b.literal('$$legacy')
];
if (analysis.custom_element) { if (analysis.custom_element) {
to_remove.push(b.literal('$$host')); to_remove.push(b.literal('$$host'));
} }

@ -1,5 +1,6 @@
import * as b from '../../../utils/builders.js'; import * as b from '../../../utils/builders.js';
import { import {
extract_identifiers,
extract_paths, extract_paths,
is_expression_async, is_expression_async,
is_simple_expression, is_simple_expression,
@ -88,7 +89,7 @@ export function serialize_get_binding(node, state) {
} }
if (binding.kind === 'prop' || binding.kind === 'bindable_prop') { if (binding.kind === 'prop' || binding.kind === 'bindable_prop') {
if (!state.analysis.runes || binding.reassigned || binding.initial) { if (is_prop_source(binding, state)) {
return b.call(node); return b.call(node);
} }
@ -119,10 +120,11 @@ export function serialize_get_binding(node, state) {
* @param {import('estree').AssignmentExpression} node * @param {import('estree').AssignmentExpression} node
* @param {import('zimmerframe').Context<import('#compiler').SvelteNode, State>} context * @param {import('zimmerframe').Context<import('#compiler').SvelteNode, State>} context
* @param {() => any} fallback * @param {() => any} fallback
* @param {boolean} prefix
* @param {{skip_proxy_and_freeze?: boolean}} [options] * @param {{skip_proxy_and_freeze?: boolean}} [options]
* @returns {import('estree').Expression} * @returns {import('estree').Expression}
*/ */
export function serialize_set_binding(node, context, fallback, options) { export function serialize_set_binding(node, context, fallback, prefix, options) {
const { state, visit } = context; const { state, visit } = context;
const assignee = node.left; const assignee = node.left;
@ -146,7 +148,9 @@ export function serialize_set_binding(node, context, fallback, options) {
const value = path.expression?.(b.id(tmp_id)); const value = path.expression?.(b.id(tmp_id));
const assignment = b.assignment('=', path.node, value); const assignment = b.assignment('=', path.node, value);
original_assignments.push(assignment); original_assignments.push(assignment);
assignments.push(serialize_set_binding(assignment, context, () => assignment, options)); assignments.push(
serialize_set_binding(assignment, context, () => assignment, prefix, options)
);
} }
if (assignments.every((assignment, i) => assignment === original_assignments[i])) { if (assignments.every((assignment, i) => assignment === original_assignments[i])) {
@ -387,18 +391,20 @@ export function serialize_set_binding(node, context, fallback, options) {
), ),
b.call('$.untrack', b.id('$' + left_name)) b.call('$.untrack', b.id('$' + left_name))
); );
} else if (!state.analysis.runes) { } else if (
!state.analysis.runes ||
// this condition can go away once legacy mode is gone; only necessary for interop with legacy parent bindings
(binding.mutated && binding.kind === 'bindable_prop')
) {
if (binding.kind === 'bindable_prop') { if (binding.kind === 'bindable_prop') {
return b.call( return b.call(
left, left,
b.sequence([ b.assignment(
b.assignment( node.operator,
node.operator, /** @type {import('estree').Pattern} */ (visit(node.left)),
/** @type {import('estree').Pattern} */ (visit(node.left)), value
value ),
), b.true
b.call(left)
])
); );
} else { } else {
return b.call( return b.call(
@ -411,6 +417,15 @@ export function serialize_set_binding(node, context, fallback, options) {
) )
); );
} }
} else if (
node.right.type === 'Literal' &&
(node.operator === '+=' || node.operator === '-=')
) {
return b.update(
node.operator === '+=' ? '++' : '--',
/** @type {import('estree').Expression} */ (visit(node.left)),
prefix
);
} else { } else {
return b.assignment( return b.assignment(
node.operator, node.operator,
@ -525,9 +540,7 @@ function get_hoistable_params(node, context) {
} else if ( } else if (
// If we are referencing a simple $$props value, then we need to reference the object property instead // If we are referencing a simple $$props value, then we need to reference the object property instead
(binding.kind === 'prop' || binding.kind === 'bindable_prop') && (binding.kind === 'prop' || binding.kind === 'bindable_prop') &&
!binding.reassigned && !is_prop_source(binding, context.state)
binding.initial === null &&
!context.state.analysis.accessors
) { ) {
push_unique(b.id('$$props')); push_unique(b.id('$$props'));
} else { } else {
@ -589,7 +602,9 @@ export function get_prop_source(binding, state, name, initial) {
if ( if (
state.analysis.accessors || state.analysis.accessors ||
(state.analysis.immutable ? binding.reassigned : binding.mutated) (state.analysis.immutable
? binding.reassigned || (state.analysis.runes && binding.mutated)
: binding.mutated)
) { ) {
flags |= PROPS_IS_UPDATED; flags |= PROPS_IS_UPDATED;
} }
@ -624,6 +639,25 @@ export function get_prop_source(binding, state, name, initial) {
return b.call('$.prop', ...args); return b.call('$.prop', ...args);
} }
/**
*
* @param {import('#compiler').Binding} binding
* @param {import('./types').ClientTransformState} state
* @returns
*/
export function is_prop_source(binding, state) {
return (
(binding.kind === 'prop' || binding.kind === 'bindable_prop') &&
(!state.analysis.runes ||
state.analysis.accessors ||
binding.reassigned ||
binding.initial ||
// Until legacy mode is gone, we also need to use the prop source when only mutated is true,
// because the parent could be a legacy component which needs coarse-grained reactivity
binding.mutated)
);
}
/** /**
* @param {import('estree').Expression} node * @param {import('estree').Expression} node
* @param {import("../../scope.js").Scope | null} scope * @param {import("../../scope.js").Scope | null} scope
@ -672,3 +706,44 @@ export function with_loc(target, source) {
} }
return target; return target;
} }
/**
* @param {import("estree").Pattern} node
* @param {import("zimmerframe").Context<import("#compiler").SvelteNode, import("./types").ComponentClientTransformState>} context
* @returns {{ id: import("estree").Pattern, declarations: null | import("estree").Statement[] }}
*/
export function create_derived_block_argument(node, context) {
if (node.type === 'Identifier') {
return { id: node, declarations: null };
}
const pattern = /** @type {import('estree').Pattern} */ (context.visit(node));
const identifiers = extract_identifiers(node);
const id = b.id('$$source');
const value = b.id('$$value');
const block = b.block([
b.var(pattern, b.call('$.get', id)),
b.return(b.object(identifiers.map((identifier) => b.prop('init', identifier, identifier))))
]);
const declarations = [b.var(value, create_derived(context.state, b.thunk(block)))];
for (const id of identifiers) {
declarations.push(
b.var(id, create_derived(context.state, b.thunk(b.member(b.call('$.get', value), id))))
);
}
return { id, declarations };
}
/**
* Svelte legacy mode should use safe equals in most places, runes mode shouldn't
* @param {import('./types.js').ComponentClientTransformState} state
* @param {import('estree').Expression} arg
*/
export function create_derived(state, arg) {
return b.call(state.analysis.runes ? '$.derived' : '$.derived_safe_equal', arg);
}

@ -32,7 +32,7 @@ export const global_visitors = {
next(); next();
}, },
AssignmentExpression(node, context) { AssignmentExpression(node, context) {
return serialize_set_binding(node, context, context.next); return serialize_set_binding(node, context, context.next, false);
}, },
UpdateExpression(node, context) { UpdateExpression(node, context) {
const { state, next, visit } = context; const { state, next, visit } = context;
@ -98,7 +98,12 @@ export const global_visitors = {
/** @type {import('estree').Pattern} */ (argument), /** @type {import('estree').Pattern} */ (argument),
b.literal(1) b.literal(1)
); );
const serialized_assignment = serialize_set_binding(assignment, context, () => assignment); const serialized_assignment = serialize_set_binding(
assignment,
context,
() => assignment,
node.prefix
);
const value = /** @type {import('estree').Expression} */ (visit(argument)); const value = /** @type {import('estree').Expression} */ (visit(argument));
if (serialized_assignment === assignment) { if (serialized_assignment === assignment) {
// No change to output -> nothing to transform -> we can keep the original update expression // No change to output -> nothing to transform -> we can keep the original update expression

@ -4,6 +4,7 @@ import * as b from '../../../../utils/builders.js';
import * as assert from '../../../../utils/assert.js'; import * as assert from '../../../../utils/assert.js';
import { import {
get_prop_source, get_prop_source,
is_prop_source,
is_state_source, is_state_source,
serialize_proxy_reassignment, serialize_proxy_reassignment,
should_proxy_or_freeze should_proxy_or_freeze
@ -240,7 +241,11 @@ export const javascript_visitors_runes = {
assert.equal(declarator.id.type, 'ObjectPattern'); assert.equal(declarator.id.type, 'ObjectPattern');
/** @type {string[]} */ /** @type {string[]} */
const seen = []; const seen = ['$$slots', '$$events', '$$legacy'];
if (state.analysis.custom_element) {
seen.push('$$host');
}
for (const property of declarator.id.properties) { for (const property of declarator.id.properties) {
if (property.type === 'Property') { if (property.type === 'Property') {
@ -264,7 +269,7 @@ export const javascript_visitors_runes = {
initial = b.call('$.proxy', initial); initial = b.call('$.proxy', initial);
} }
if (binding.reassigned || state.analysis.accessors || initial) { if (is_prop_source(binding, state)) {
declarations.push(b.declarator(id, get_prop_source(binding, state, name, initial))); declarations.push(b.declarator(id, get_prop_source(binding, state, name, initial)));
} }
} else { } else {

@ -21,7 +21,9 @@ import {
function_visitor, function_visitor,
get_assignment_value, get_assignment_value,
serialize_get_binding, serialize_get_binding,
serialize_set_binding serialize_set_binding,
create_derived,
create_derived_block_argument
} from '../utils.js'; } from '../utils.js';
import { import {
AttributeAliases, AttributeAliases,
@ -646,15 +648,6 @@ function collect_parent_each_blocks(context) {
); );
} }
/**
* Svelte legacy mode should use safe equals in most places, runes mode shouldn't
* @param {import('../types.js').ComponentClientTransformState} state
* @param {import('estree').Expression} arg
*/
function create_derived(state, arg) {
return b.call(state.analysis.runes ? '$.derived' : '$.derived_safe_equal', arg);
}
/** /**
* @param {import('#compiler').Component | import('#compiler').SvelteComponent | import('#compiler').SvelteSelf} node * @param {import('#compiler').Component | import('#compiler').SvelteComponent | import('#compiler').SvelteSelf} node
* @param {string} component_name * @param {string} component_name
@ -798,7 +791,9 @@ function serialize_inline_component(node, component_name, context) {
const assignment = b.assignment('=', attribute.expression, b.id('$$value')); const assignment = b.assignment('=', attribute.expression, b.id('$$value'));
push_prop( push_prop(
b.set(attribute.name, [ b.set(attribute.name, [
b.stmt(serialize_set_binding(assignment, context, () => context.visit(assignment))) b.stmt(
serialize_set_binding(assignment, context, () => context.visit(assignment), false)
)
]) ])
); );
} }
@ -905,6 +900,10 @@ function serialize_inline_component(node, component_name, context) {
push_prop(b.init('$$slots', b.object(serialized_slots))); push_prop(b.init('$$slots', b.object(serialized_slots)));
} }
if (!context.state.analysis.runes) {
push_prop(b.init('$$legacy', b.true));
}
const props_expression = const props_expression =
props_and_spreads.length === 0 || props_and_spreads.length === 0 ||
(props_and_spreads.length === 1 && Array.isArray(props_and_spreads[0])) (props_and_spreads.length === 1 && Array.isArray(props_and_spreads[0]))
@ -1026,7 +1025,7 @@ function serialize_bind_this(bind_this, context, node) {
const bind_this_id = /** @type {import('estree').Expression} */ (context.visit(bind_this)); const bind_this_id = /** @type {import('estree').Expression} */ (context.visit(bind_this));
const ids = Array.from(each_ids.values()).map((id) => b.id('$$value_' + id[0])); const ids = Array.from(each_ids.values()).map((id) => b.id('$$value_' + id[0]));
const assignment = b.assignment('=', bind_this, b.id('$$value')); const assignment = b.assignment('=', bind_this, b.id('$$value'));
const update = serialize_set_binding(assignment, context, () => context.visit(assignment)); const update = serialize_set_binding(assignment, context, () => context.visit(assignment), false);
for (const [binding, [, , expression]] of each_ids) { for (const [binding, [, , expression]] of each_ids) {
// reset expressions to what they were before // reset expressions to what they were before
@ -2400,7 +2399,7 @@ export const template_visitors = {
if (assignment.left.type !== 'Identifier' && assignment.left.type !== 'MemberExpression') { if (assignment.left.type !== 'Identifier' && assignment.left.type !== 'MemberExpression') {
// serialize_set_binding turns other patterns into IIFEs and separates the assignments // serialize_set_binding turns other patterns into IIFEs and separates the assignments
// into separate expressions, at which point this is called again with an identifier or member expression // into separate expressions, at which point this is called again with an identifier or member expression
return serialize_set_binding(assignment, context, () => assignment); return serialize_set_binding(assignment, context, () => assignment, false);
} }
const left = object(assignment.left); const left = object(assignment.left);
const value = get_assignment_value(assignment, context); const value = get_assignment_value(assignment, context);
@ -2438,7 +2437,7 @@ export const template_visitors = {
: b.id(node.index); : b.id(node.index);
const item = each_node_meta.item; const item = each_node_meta.item;
const binding = /** @type {import('#compiler').Binding} */ (context.state.scope.get(item.name)); const binding = /** @type {import('#compiler').Binding} */ (context.state.scope.get(item.name));
binding.expression = (id) => { binding.expression = (/** @type {import("estree").Identifier} */ id) => {
const item_with_loc = with_loc(item, id); const item_with_loc = with_loc(item, id);
return b.call('$.unwrap', item_with_loc); return b.call('$.unwrap', item_with_loc);
}; };
@ -2592,6 +2591,45 @@ export const template_visitors = {
AwaitBlock(node, context) { AwaitBlock(node, context) {
context.state.template.push('<!>'); context.state.template.push('<!>');
let then_block;
let catch_block;
if (node.then) {
/** @type {import('estree').Pattern[]} */
const args = [b.id('$$anchor')];
const block = /** @type {import('estree').BlockStatement} */ (context.visit(node.then));
if (node.value) {
const argument = create_derived_block_argument(node.value, context);
args.push(argument.id);
if (argument.declarations !== null) {
block.body.unshift(...argument.declarations);
}
}
then_block = b.arrow(args, block);
}
if (node.catch) {
/** @type {import('estree').Pattern[]} */
const args = [b.id('$$anchor')];
const block = /** @type {import('estree').BlockStatement} */ (context.visit(node.catch));
if (node.error) {
const argument = create_derived_block_argument(node.error, context);
args.push(argument.id);
if (argument.declarations !== null) {
block.body.unshift(...argument.declarations);
}
}
catch_block = b.arrow(args, block);
}
context.state.init.push( context.state.init.push(
b.stmt( b.stmt(
b.call( b.call(
@ -2604,28 +2642,8 @@ export const template_visitors = {
/** @type {import('estree').BlockStatement} */ (context.visit(node.pending)) /** @type {import('estree').BlockStatement} */ (context.visit(node.pending))
) )
: b.literal(null), : b.literal(null),
node.then then_block,
? b.arrow( catch_block
node.value
? [
b.id('$$anchor'),
/** @type {import('estree').Pattern} */ (context.visit(node.value))
]
: [b.id('$$anchor')],
/** @type {import('estree').BlockStatement} */ (context.visit(node.then))
)
: b.literal(null),
node.catch
? b.arrow(
node.error
? [
b.id('$$anchor'),
/** @type {import('estree').Pattern} */ (context.visit(node.error))
]
: [b.id('$$anchor')],
/** @type {import('estree').BlockStatement} */ (context.visit(node.catch))
)
: b.literal(null)
) )
) )
); );
@ -2762,6 +2780,7 @@ export const template_visitors = {
assignment, assignment,
context, context,
() => /** @type {import('estree').Expression} */ (visit(assignment)), () => /** @type {import('estree').Expression} */ (visit(assignment)),
false,
{ {
skip_proxy_and_freeze: true skip_proxy_and_freeze: true
} }

@ -613,7 +613,7 @@ export function create_scopes(ast, root, allow_reactive_declarations, parent) {
scopes.set(node.value, value_scope); scopes.set(node.value, value_scope);
context.visit(node.value, { scope: value_scope }); context.visit(node.value, { scope: value_scope });
for (const id of extract_identifiers(node.value)) { for (const id of extract_identifiers(node.value)) {
then_scope.declare(id, 'normal', 'const'); then_scope.declare(id, 'derived', 'const');
value_scope.declare(id, 'normal', 'const'); value_scope.declare(id, 'normal', 'const');
} }
} }
@ -627,7 +627,7 @@ export function create_scopes(ast, root, allow_reactive_declarations, parent) {
scopes.set(node.error, error_scope); scopes.set(node.error, error_scope);
context.visit(node.error, { scope: error_scope }); context.visit(node.error, { scope: error_scope });
for (const id of extract_identifiers(node.error)) { for (const id of extract_identifiers(node.error)) {
catch_scope.declare(id, 'normal', 'const'); catch_scope.declare(id, 'derived', 'const');
error_scope.declare(id, 'normal', 'const'); error_scope.declare(id, 'normal', 'const');
} }
} }

@ -189,14 +189,31 @@ export type ComponentEvents<Comp extends SvelteComponent> =
Comp extends SvelteComponent<any, infer Events> ? Events : never; Comp extends SvelteComponent<any, infer Events> ? Events : never;
/** /**
* Convenience type to get the props the given component expects. Example: * Convenience type to get the props the given component expects.
* ```html
* <script lang="ts">
* import type { ComponentProps } from 'svelte';
* import Component from './Component.svelte';
* *
* const props: ComponentProps<Component> = { foo: 'bar' }; // Errors if these aren't the correct props * Example: Ensure a variable contains the props expected by `MyComponent`:
* </script> *
* ```ts
* import type { ComponentProps } from 'svelte';
* import MyComponent from './MyComponent.svelte';
*
* // Errors if these aren't the correct props expected by MyComponent.
* const props: ComponentProps<MyComponent> = { foo: 'bar' };
* ```
*
* Example: A generic function that accepts some component and infers the type of its props:
*
* ```ts
* import type { Component, ComponentProps } from 'svelte';
* import MyComponent from './MyComponent.svelte';
*
* function withProps<TComponent extends Component<any>>(
* component: TComponent,
* props: ComponentProps<TComponent>
* ) {};
*
* // Errors if the second argument is not the correct props expected by the component in the first argument.
* withProps(MyComponent, { foo: 'bar' });
* ``` * ```
*/ */
export type ComponentProps<Comp extends SvelteComponent | Component<any>> = export type ComponentProps<Comp extends SvelteComponent | Component<any>> =

@ -7,111 +7,135 @@ import {
set_current_reaction, set_current_reaction,
set_dev_current_component_function set_dev_current_component_function
} from '../../runtime.js'; } from '../../runtime.js';
import { block, branch, destroy_effect, pause_effect } from '../../reactivity/effects.js'; import { block, branch, pause_effect, resume_effect } from '../../reactivity/effects.js';
import { INERT } from '../../constants.js';
import { DEV } from 'esm-env'; import { DEV } from 'esm-env';
import { queue_micro_task } from '../task.js';
import { hydrating } from '../hydration.js';
import { set, source } from '../../reactivity/sources.js';
const PENDING = 0;
const THEN = 1;
const CATCH = 2;
/** /**
* @template V * @template V
* @param {Comment} anchor * @param {Comment} anchor
* @param {(() => Promise<V>)} get_input * @param {(() => Promise<V>)} get_input
* @param {null | ((anchor: Node) => void)} pending_fn * @param {null | ((anchor: Node) => void)} pending_fn
* @param {null | ((anchor: Node, value: V) => void)} then_fn * @param {null | ((anchor: Node, value: import('#client').Source<V>) => void)} then_fn
* @param {null | ((anchor: Node, error: unknown) => void)} catch_fn * @param {null | ((anchor: Node, error: unknown) => void)} catch_fn
* @returns {void} * @returns {void}
*/ */
export function await_block(anchor, get_input, pending_fn, then_fn, catch_fn) { export function await_block(anchor, get_input, pending_fn, then_fn, catch_fn) {
const component_context = current_component_context; var component_context = current_component_context;
/** @type {any} */
let component_function;
if (DEV) {
component_function = component_context?.function ?? null;
}
/** @type {any} */ /** @type {any} */
let input; var component_function = DEV ? component_context?.function : null;
/** @type {V | Promise<V>} */
var input;
/** @type {import('#client').Effect | null} */ /** @type {import('#client').Effect | null} */
let pending_effect; var pending_effect;
/** @type {import('#client').Effect | null} */ /** @type {import('#client').Effect | null} */
let then_effect; var then_effect;
/** @type {import('#client').Effect | null} */ /** @type {import('#client').Effect | null} */
let catch_effect; var catch_effect;
var input_source = source(/** @type {V} */ (undefined));
var error_source = source(undefined);
var resolved = false;
/** /**
* @param {(anchor: Comment, value: any) => void} fn * @param {PENDING | THEN | CATCH} state
* @param {any} value * @param {boolean} restore
*/ */
function create_effect(fn, value) { function update(state, restore) {
set_current_effect(effect); resolved = true;
set_current_reaction(effect); // TODO do we need both?
set_current_component_context(component_context); if (restore) {
if (DEV) { set_current_effect(effect);
set_dev_current_component_function(component_function); set_current_reaction(effect); // TODO do we need both?
set_current_component_context(component_context);
if (DEV) set_dev_current_component_function(component_function);
}
if (state === PENDING && pending_fn) {
if (pending_effect) resume_effect(pending_effect);
else pending_effect = branch(() => pending_fn(anchor));
}
if (state === THEN && then_fn) {
if (then_effect) resume_effect(then_effect);
else then_effect = branch(() => then_fn(anchor, input_source));
}
if (state === CATCH && catch_fn) {
if (catch_effect) resume_effect(catch_effect);
else catch_effect = branch(() => catch_fn(anchor, error_source));
}
if (state !== PENDING && pending_effect) {
pause_effect(pending_effect, () => (pending_effect = null));
} }
var e = branch(() => fn(anchor, value));
if (DEV) { if (state !== THEN && then_effect) {
set_dev_current_component_function(null); pause_effect(then_effect, () => (then_effect = null));
}
if (state !== CATCH && catch_effect) {
pause_effect(catch_effect, () => (catch_effect = null));
} }
set_current_component_context(null);
set_current_reaction(null);
set_current_effect(null);
// without this, the DOM does not update until two ticks after the promise, if (restore) {
// resolves which is unexpected behaviour (and somewhat irksome to test) if (DEV) set_dev_current_component_function(null);
flush_sync(); set_current_component_context(null);
set_current_reaction(null);
set_current_effect(null);
return e; // without this, the DOM does not update until two ticks after the promise
// resolves, which is unexpected behaviour (and somewhat irksome to test)
flush_sync();
}
} }
const effect = block(() => { var effect = block(() => {
if (input === (input = get_input())) return; if (input === (input = get_input())) return;
if (is_promise(input)) { if (is_promise(input)) {
const promise = /** @type {Promise<any>} */ (input); var promise = input;
if (pending_fn) { resolved = false;
if (pending_effect && (pending_effect.f & INERT) === 0) {
destroy_effect(pending_effect);
}
pending_effect = branch(() => pending_fn(anchor));
}
if (then_effect) pause_effect(then_effect);
if (catch_effect) pause_effect(catch_effect);
promise.then( promise.then(
(value) => { (value) => {
if (promise !== input) return; if (promise !== input) return;
if (pending_effect) pause_effect(pending_effect); set(input_source, value);
update(THEN, true);
if (then_fn) {
then_effect = create_effect(then_fn, value);
}
}, },
(error) => { (error) => {
if (promise !== input) return; if (promise !== input) return;
if (pending_effect) pause_effect(pending_effect); set(error_source, error);
update(CATCH, true);
if (catch_fn) {
catch_effect = create_effect(catch_fn, error);
}
} }
); );
} else {
if (pending_effect) pause_effect(pending_effect);
if (catch_effect) pause_effect(catch_effect);
if (then_fn) { if (hydrating) {
if (then_effect) { if (pending_fn) {
destroy_effect(then_effect); pending_effect = branch(() => pending_fn(anchor));
} }
} else {
then_effect = branch(() => then_fn(anchor, input)); // Wait a microtask before checking if we should show the pending state as
// the promise might have resolved by the next microtask.
queue_micro_task(() => {
if (!resolved) update(PENDING, true);
});
} }
} else {
set(input_source, input);
update(THEN, false);
} }
// Inert effects are proactively detached from the effect tree. Returning a noop // Inert effects are proactively detached from the effect tree. Returning a noop

@ -85,7 +85,6 @@ function pause_effects(items, controlled_anchor, items_map) {
var item = items[i]; var item = items[i];
if (!is_controlled) { if (!is_controlled) {
items_map.delete(item.k); items_map.delete(item.k);
item.o.remove();
link(item.prev, item.next); link(item.prev, item.next);
} }
destroy_effect(item.e, !is_controlled); destroy_effect(item.e, !is_controlled);
@ -151,7 +150,7 @@ export function each(anchor, flags, get_collection, get_key, render_fn, fallback
if (hydrating) { if (hydrating) {
var is_else = /** @type {Comment} */ (anchor).data === HYDRATION_END_ELSE; var is_else = /** @type {Comment} */ (anchor).data === HYDRATION_END_ELSE;
if (is_else !== (length === 0)) { if (is_else !== (length === 0) || hydrate_start === undefined) {
// hydration mismatch — remove the server-rendered DOM and start over // hydration mismatch — remove the server-rendered DOM and start over
remove(hydrate_nodes); remove(hydrate_nodes);
set_hydrating(false); set_hydrating(false);
@ -182,11 +181,10 @@ export function each(anchor, flags, get_collection, get_key, render_fn, fallback
break; break;
} }
var child_open = /** @type {Comment} */ (child_anchor);
child_anchor = hydrate_anchor(child_anchor); child_anchor = hydrate_anchor(child_anchor);
var value = array[i]; var value = array[i];
var key = get_key(value, i); var key = get_key(value, i);
item = create_item(child_open, child_anchor, prev, null, value, key, i, render_fn, flags); item = create_item(child_anchor, prev, null, value, key, i, render_fn, flags);
state.items.set(key, item); state.items.set(key, item);
child_anchor = /** @type {Comment} */ (child_anchor.nextSibling); child_anchor = /** @type {Comment} */ (child_anchor.nextSibling);
@ -293,22 +291,9 @@ function reconcile(array, state, anchor, render_fn, flags, get_key) {
item = items.get(key); item = items.get(key);
if (item === undefined) { if (item === undefined) {
var child_open = empty(); var child_anchor = current ? get_first_node(current.e) : anchor;
var child_anchor = current ? current.o : anchor;
prev = create_item(child_anchor, prev, prev.next, value, key, i, render_fn, flags);
child_anchor.before(child_open);
prev = create_item(
child_open,
child_anchor,
prev,
prev.next,
value,
key,
i,
render_fn,
flags
);
items.set(key, prev); items.set(key, prev);
@ -451,7 +436,6 @@ function update_item(item, value, index, type) {
/** /**
* @template V * @template V
* @param {Comment | Text} open
* @param {Node} anchor * @param {Node} anchor
* @param {import('#client').EachItem | import('#client').EachState} prev * @param {import('#client').EachItem | import('#client').EachState} prev
* @param {import('#client').EachItem | null} next * @param {import('#client').EachItem | null} next
@ -462,7 +446,7 @@ function update_item(item, value, index, type) {
* @param {number} flags * @param {number} flags
* @returns {import('#client').EachItem} * @returns {import('#client').EachItem}
*/ */
function create_item(open, anchor, prev, next, value, key, index, render_fn, flags) { function create_item(anchor, prev, next, value, key, index, render_fn, flags) {
var previous_each_item = current_each_item; var previous_each_item = current_each_item;
try { try {
@ -480,7 +464,6 @@ function create_item(open, anchor, prev, next, value, key, index, render_fn, fla
a: null, a: null,
// @ts-expect-error // @ts-expect-error
e: null, e: null,
o: open,
prev, prev,
next next
}; };
@ -497,16 +480,52 @@ function create_item(open, anchor, prev, next, value, key, index, render_fn, fla
} }
} }
/**
* @param {import('#client').TemplateNode} dom
* @param {import("#client").Effect} effect
* @returns {import('#client').TemplateNode}
*/
function get_adjusted_first_node(dom, effect) {
if ((dom.nodeType === 3 && /** @type {Text} */ (dom).data === '') || dom.nodeType === 8) {
var adjusted = effect.first;
var next;
while (adjusted !== null) {
next = adjusted.first;
if (adjusted.dom !== null) {
break;
} else if (next === null) {
return /** @type {import('#client').TemplateNode} */ (dom.previousSibling);
}
adjusted = next;
}
return get_first_node(/** @type {import("#client").Effect} */ (adjusted));
}
return dom;
}
/**
*
* @param {import('#client').Effect} effect
* @returns {import('#client').TemplateNode}
*/
function get_first_node(effect) {
var dom = effect.dom;
if (is_array(dom)) {
return get_adjusted_first_node(dom[0], effect);
}
return get_adjusted_first_node(/** @type {import('#client').TemplateNode} **/ (dom), effect);
}
/** /**
* @param {import('#client').EachItem} item * @param {import('#client').EachItem} item
* @param {import('#client').EachItem | null} next * @param {import('#client').EachItem | null} next
* @param {Text | Element | Comment} anchor * @param {Text | Element | Comment} anchor
*/ */
function move(item, next, anchor) { function move(item, next, anchor) {
var end = item.next ? item.next.o : anchor; var end = item.next ? get_first_node(item.next.e) : anchor;
var dest = next ? next.o : anchor; var dest = next ? get_first_node(next.e) : anchor;
var node = /** @type {import('#client').TemplateNode} */ (item.o); var node = get_first_node(item.e);
while (node !== end) { while (node !== end) {
var next_node = /** @type {import('#client').TemplateNode} */ (node.nextSibling); var next_node = /** @type {import('#client').TemplateNode} */ (node.nextSibling);

@ -47,7 +47,6 @@ function swap_block_dom(effect, from, to) {
* @returns {void} * @returns {void}
*/ */
export function element(node, get_tag, is_svg, render_fn, get_namespace, location) { 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; const filename = DEV && location && current_component_context?.function.filename;
/** @type {string | null} */ /** @type {string | null} */
@ -64,6 +63,18 @@ export function element(node, get_tag, is_svg, render_fn, get_namespace, locatio
/** @type {import('#client').Effect | null} */ /** @type {import('#client').Effect | null} */
let effect; let effect;
const parent_effect = /** @type {import('#client').Effect} */ (current_effect);
// Remove the the hydrated effect dom entry for our dynamic element
if (hydrating && is_array(parent_effect.dom)) {
var remove_index = parent_effect.dom.indexOf(
/** @type {import('#client').TemplateNode} */ (element)
);
if (remove_index !== -1) {
parent_effect.dom.splice(remove_index, 1);
}
}
/** /**
* The keyed `{#each ...}` item block, if any, that this element is inside. * The keyed `{#each ...}` item block, if any, that this element is inside.
* We track this so we can set it when changing the element, allowing any * We track this so we can set it when changing the element, allowing any
@ -72,6 +83,7 @@ export function element(node, get_tag, is_svg, render_fn, get_namespace, locatio
let each_item_block = current_each_item; let each_item_block = current_each_item;
block(() => { block(() => {
const element_effect = /** @type {import('#client').Effect} */ (current_effect);
const next_tag = get_tag() || null; const next_tag = get_tag() || null;
const ns = get_namespace const ns = get_namespace
? get_namespace() ? get_namespace()
@ -124,6 +136,13 @@ export function element(node, get_tag, is_svg, render_fn, get_namespace, locatio
}; };
} }
if (prev_element && !hydrating) {
swap_block_dom(element_effect, prev_element, element);
prev_element.remove();
} else {
push_template_node(element, element_effect);
}
if (render_fn) { if (render_fn) {
// If hydrating, use the existing ssr comment as the anchor so that the // If hydrating, use the existing ssr comment as the anchor so that the
// inner open and close methods can pick up the existing nodes correctly // inner open and close methods can pick up the existing nodes correctly
@ -144,15 +163,6 @@ export function element(node, get_tag, is_svg, render_fn, get_namespace, locatio
anchor.before(element); anchor.before(element);
if (!hydrating) {
if (prev_element) {
swap_block_dom(parent_effect, prev_element, element);
prev_element.remove();
} else {
push_template_node(element, parent_effect);
}
}
// See below // See below
return noop; return noop;
}); });

@ -267,9 +267,16 @@ export function prop(props, key, flags, fallback) {
// intermediate mode — prop is written to, but the parent component had // intermediate mode — prop is written to, but the parent component had
// `bind:foo` which means we can just call `$$props.foo = value` directly // `bind:foo` which means we can just call `$$props.foo = value` directly
if (setter) { if (setter) {
return function (/** @type {V} */ value) { var legacy_parent = props.$$legacy;
if (arguments.length === 1) { return function (/** @type {any} */ value, /** @type {boolean} */ mutation) {
/** @type {Function} */ (setter)(value); if (arguments.length > 0) {
// We don't want to notify if the value was mutated and the parent is in runes mode.
// In that case the state proxy (if it exists) should take care of the notification.
// If the parent is not in runes mode, we need to notify on mutation, too, that the prop
// has changed because the parent will not be able to detect the change otherwise.
if (!runes || !mutation || legacy_parent) {
/** @type {Function} */ (setter)(mutation ? getter() : value);
}
return value; return value;
} else { } else {
return getter(); return getter();
@ -302,7 +309,7 @@ export function prop(props, key, flags, fallback) {
if (!immutable) current_value.equals = safe_equals; if (!immutable) current_value.equals = safe_equals;
return function (/** @type {V} */ value) { return function (/** @type {any} */ value, /** @type {boolean} */ mutation) {
var current = get(current_value); var current = get(current_value);
// legacy nonsense — need to ensure the source is invalidated when necessary // legacy nonsense — need to ensure the source is invalidated when necessary
@ -318,9 +325,11 @@ export function prop(props, key, flags, fallback) {
} }
if (arguments.length > 0) { if (arguments.length > 0) {
if (!current_value.equals(value)) { const new_value = mutation ? get(current_value) : value;
if (!current_value.equals(new_value)) {
from_child = true; from_child = true;
set(inner_current_value, value); set(inner_current_value, new_value);
get(current_value); // force a synchronisation immediately get(current_value); // force a synchronisation immediately
} }

@ -72,13 +72,20 @@ export function slot(anchor, slot_fn, slot_props, fallback_fn) {
* @template {Record<string, any>} Props * @template {Record<string, any>} Props
* @template {Record<string, any>} Exports * @template {Record<string, any>} Exports
* @param {import('../../index.js').ComponentType<import('../../index.js').SvelteComponent<Props>> | import('../../index.js').Component<Props, Exports, any>} component * @param {import('../../index.js').ComponentType<import('../../index.js').SvelteComponent<Props>> | import('../../index.js').Component<Props, Exports, any>} component
* @param {{ * @param {{} extends Props ? {
* target: Document | Element | ShadowRoot; * target: Document | Element | ShadowRoot;
* anchor?: Node; * anchor?: Node;
* props?: Props; * props?: Props;
* events?: Record<string, (e: any) => any>; * events?: Record<string, (e: any) => any>;
* context?: Map<any, any>; * context?: Map<any, any>;
* intro?: boolean; * intro?: boolean;
* }: {
* target: Document | Element | ShadowRoot;
* props: Props;
* anchor?: Node;
* events?: Record<string, (e: any) => any>;
* context?: Map<any, any>;
* intro?: boolean;
* }} options * }} options
* @returns {Exports} * @returns {Exports}
*/ */
@ -98,13 +105,20 @@ export function mount(component, options) {
* @template {Record<string, any>} Props * @template {Record<string, any>} Props
* @template {Record<string, any>} Exports * @template {Record<string, any>} Exports
* @param {import('../../index.js').ComponentType<import('../../index.js').SvelteComponent<Props>> | import('../../index.js').Component<Props, Exports, any>} component * @param {import('../../index.js').ComponentType<import('../../index.js').SvelteComponent<Props>> | import('../../index.js').Component<Props, Exports, any>} component
* @param {{ * @param {{} extends Props ? {
* target: Document | Element | ShadowRoot; * target: Document | Element | ShadowRoot;
* props?: Props; * props?: Props;
* events?: Record<string, (e: any) => any>; * events?: Record<string, (e: any) => any>;
* context?: Map<any, any>; * context?: Map<any, any>;
* intro?: boolean; * intro?: boolean;
* recover?: boolean; * recover?: boolean;
* } : {
* target: Document | Element | ShadowRoot;
* props: Props;
* events?: Record<string, (e: any) => any>;
* context?: Map<any, any>;
* intro?: boolean;
* recover?: boolean;
* }} options * }} options
* @returns {Exports} * @returns {Exports}
*/ */

@ -85,8 +85,6 @@ export type EachItem = {
i: number | Source<number>; i: number | Source<number>;
/** key */ /** key */
k: unknown; k: unknown;
/** anchor for items inserted before this */
o: Comment | Text;
prev: EachItem | EachState; prev: EachItem | EachState;
next: EachItem | null; next: EachItem | null;
}; };

@ -97,10 +97,10 @@ export let on_destroy = [];
* Takes a component and returns an object with `body` and `head` properties on it, which you can use to populate the HTML when server-rendering your app. * Takes a component and returns an object with `body` and `head` properties on it, which you can use to populate the HTML when server-rendering your app.
* @template {Record<string, any>} Props * @template {Record<string, any>} Props
* @param {import('svelte').Component<Props> | import('svelte').ComponentType<import('svelte').SvelteComponent<Props>>} component * @param {import('svelte').Component<Props> | import('svelte').ComponentType<import('svelte').SvelteComponent<Props>>} component
* @param {{ props: Omit<Props, '$$slots' | '$$events'>; context?: Map<any, any> }} options * @param {{ props?: Omit<Props, '$$slots' | '$$events'>; context?: Map<any, any> }} [options]
* @returns {import('#server').RenderOutput} * @returns {import('#server').RenderOutput}
*/ */
export function render(component, options) { export function render(component, options = {}) {
const payload = create_payload(); const payload = create_payload();
const prev_on_destroy = on_destroy; const prev_on_destroy = on_destroy;

@ -0,0 +1,21 @@
import type { RenderOutput } from '#server';
import type { ComponentProps, Component, SvelteComponent, ComponentType } from 'svelte';
/**
* Only available on the server and when compiling with the `server` option.
* Takes a component and returns an object with `body` and `head` properties on it, which you can use to populate the HTML when server-rendering your app.
*/
export function render<
Comp extends SvelteComponent<any> | Component<any>,
Props extends ComponentProps<Comp> = ComponentProps<Comp>
>(
...args: {} extends Props
? [
component: Comp extends SvelteComponent<any> ? ComponentType<Comp> : Comp,
options?: { props?: Omit<Props, '$$slots' | '$$events'>; context?: Map<any, any> }
]
: [
component: Comp extends SvelteComponent<any> ? ComponentType<Comp> : Comp,
options: { props: Omit<Props, '$$slots' | '$$events'>; context?: Map<any, any> }
]
): RenderOutput;

@ -1,4 +1,5 @@
import { noop, run_all } from '../internal/shared/utils.js'; import { noop, run_all } from '../internal/shared/utils.js';
import { safe_not_equal } from '../internal/client/reactivity/equality.js';
import { subscribe_to_store } from './utils.js'; import { subscribe_to_store } from './utils.js';
/** /**
@ -21,15 +22,6 @@ export function readable(value, start) {
}; };
} }
/**
* @param {any} a
* @param {any} b
* @returns {boolean}
*/
export function safe_not_equal(a, b) {
return a != a ? b == b : a !== b || (a && typeof a === 'object') || typeof a === 'function';
}
/** /**
* Create a `Writable` store that allows both updating and reading by subscription. * Create a `Writable` store that allows both updating and reading by subscription.
* *
@ -213,11 +205,9 @@ export function readonly(store) {
* @param {import('../store/public').Readable<T>} store * @param {import('../store/public').Readable<T>} store
* @returns {T} * @returns {T}
*/ */
export function get_store_value(store) { export function get(store) {
let value; let value;
subscribe_to_store(store, (_) => (value = _))(); subscribe_to_store(store, (_) => (value = _))();
// @ts-expect-error // @ts-expect-error
return value; return value;
} }
export { get_store_value as get };

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

@ -0,0 +1,9 @@
import { test } from '../../test';
export default test({
error: {
code: 'expected_token',
message: 'Expected token )',
position: [31, 31]
}
});

@ -0,0 +1,9 @@
import { test } from '../../test';
export default test({
error: {
code: 'expected_token',
message: 'Expected token }',
position: [20, 20]
}
});

@ -0,0 +1,20 @@
import { assert_ok, test } from '../../test';
export default test({
server_props: {
items: []
},
props: {
items: [{ name: 'a' }]
},
snapshot(target) {
const ul = target.querySelector('ul');
assert_ok(ul);
return {
ul
};
}
});

@ -0,0 +1,9 @@
<!--ssr:0--><ul><!--ssr:1--><!--ssr:7--><li>a</li><!--ssr:7--><!--ssr:1--></ul>
<ul><!--ssr:2--><!--ssr:9--><li>a</li><!--ssr:9--><!--ssr:2--></ul>
<ul><!--ssr:3--><!--ssr:11--><li>a</li><!--ssr:11--><!--ssr:3--></ul>
<!--ssr:4--><!--ssr:13--><li>a</li>
<li>a</li><!--ssr:13--><!--ssr:4-->
<!--ssr:5--><!--ssr:15--><li>a</li>
<li>a</li><!--ssr:15--><!--ssr:5-->
<!--ssr:6--><!--ssr:17--><li>a</li>
<li>a</li><!--ssr:17--><!--ssr:6--><!--ssr:0--></div>

@ -0,0 +1,32 @@
<script>
let { items } = $props();
</script>
<ul>
{#each items as item}
<li>{item.name}</li>
{/each}
</ul>
<ul>
{#each items as item (item)}
<li>{item.name}</li>
{/each}
</ul>
<ul>
{#each items as item (item.name)}
<li>{item.name}</li>
{/each}
</ul>
{#each items as item}
<li>{item.name}</li>
<li>{item.name}</li>
{/each}
{#each items as item (item)}
<li>{item.name}</li>
<li>{item.name}</li>
{/each}
{#each items as item (item.name)}
<li>{item.name}</li>
<li>{item.name}</li>
{/each}

@ -32,20 +32,28 @@
"loc": { "loc": {
"start": { "start": {
"line": 3, "line": 3,
"column": 14, "column": 14
"character": 43
}, },
"end": { "end": {
"line": 3, "line": 3,
"column": 25, "column": 25
"character": 54
} }
}, },
"end": 54, "end": 46,
"typeAnnotation": { "typeAnnotation": {
"type": "TSTypeAnnotation", "type": "TSTypeAnnotation",
"start": 46, "start": 46,
"end": 54, "end": 54,
"loc": {
"start": {
"line": 3,
"column": 17
},
"end": {
"line": 3,
"column": 25
}
},
"typeAnnotation": { "typeAnnotation": {
"type": "TSStringKeyword", "type": "TSStringKeyword",
"start": 48, "start": 48,

@ -54,7 +54,10 @@
"line": 6, "line": 6,
"column": 12 "column": 12
}, },
"end": 87 "end": {
"line": 6,
"column": 25
}
}, },
"name": "e", "name": "e",
"typeAnnotation": { "typeAnnotation": {

@ -22,6 +22,7 @@ export default test({
prop3: { prop7: 'seven' }, prop3: { prop7: 'seven' },
prop4: { prop10: 'ten' } prop4: { prop10: 'ten' }
})); }));
await Promise.resolve();
assert.htmlEqual( assert.htmlEqual(
target.innerHTML, target.innerHTML,
` `

@ -20,4 +20,4 @@
<p>the promise is pending</p> <p>the promise is pending</p>
{:then} {:then}
<p>the promise is resolved</p> <p>the promise is resolved</p>
{/await} {/await}

@ -0,0 +1,8 @@
import { test } from '../../test';
export default test({
mode: ['client'],
test({ assert, logs }) {
assert.deepEqual(logs, [true]);
}
});

@ -0,0 +1,4 @@
<script>
export let a = {};
console.log((a.b = true));
</script>

@ -118,9 +118,8 @@ export default test({
assert.htmlEqual( assert.htmlEqual(
target.innerHTML, target.innerHTML,
` `
<p class="then" foo="0.5">43</p> <p class="then" foo="0.5">44</p>
<p class="pending" foo="0.5">loading...</p> <p class="pending" foo="0.5">loading...</p>
<p class="then" foo="0.0">44</p>
` `
); );
@ -159,9 +158,8 @@ export default test({
assert.htmlEqual( assert.htmlEqual(
target.innerHTML, target.innerHTML,
` `
<p class="then" foo="0.6">44</p> <p class="then" foo="0.6">45</p>
<p class="pending" foo="0.4">loading...</p> <p class="pending" foo="0.4">loading...</p>
<p class="then" foo="0.0">45</p>
` `
); );
@ -169,9 +167,8 @@ export default test({
assert.htmlEqual( assert.htmlEqual(
target.innerHTML, target.innerHTML,
` `
<p class="then" foo="0.4">44</p> <p class="then" foo="0.8">45</p>
<p class="pending" foo="0.2">loading...</p> <p class="pending" foo="0.2">loading...</p>
<p class="then" foo="0.2">45</p>
` `
); );
@ -183,10 +180,8 @@ export default test({
assert.htmlEqual( assert.htmlEqual(
target.innerHTML, target.innerHTML,
` `
<p class="then" foo="0.4">44</p> <p class="then" foo="0.8">45</p>
<p class="pending" foo="0.2">loading...</p> <p class="pending" foo="0.2">loading...</p>
<p class="then" foo="0.2">45</p>
<p class="pending" foo="0.0">loading...</p>
` `
); );
@ -195,10 +190,8 @@ export default test({
assert.htmlEqual( assert.htmlEqual(
target.innerHTML, target.innerHTML,
` `
<p class="then" foo="0.3">44</p> <p class="then" foo="0.7">45</p>
<p class="pending" foo="0.1">loading...</p> <p class="pending" foo="0.3">loading...</p>
<p class="then" foo="0.1">45</p>
<p class="pending" foo="0.1">loading...</p>
` `
); );
@ -207,11 +200,8 @@ export default test({
assert.htmlEqual( assert.htmlEqual(
target.innerHTML, target.innerHTML,
` `
<p class="then" foo="0.3">44</p> <p class="then" foo="0.7">46</p>
<p class="pending" foo="0.1">loading...</p> <p class="pending" foo="0.3">loading...</p>
<p class="then" foo="0.1">45</p>
<p class="pending" foo="0.1">loading...</p>
<p class="then" foo="0.0">46</p>
` `
); );
@ -219,20 +209,12 @@ export default test({
assert.htmlEqual( assert.htmlEqual(
target.innerHTML, target.innerHTML,
` `
<p class="then" foo="0.2">44</p> <p class="then" foo="0.8">46</p>
<p class="then" foo="0.1">46</p> <p class="pending" foo="0.2">loading...</p>
` `
); );
raf.tick((time += 20)); raf.tick((time += 20));
assert.htmlEqual(
target.innerHTML,
`
<p class="then" foo="0.3">46</p>
`
);
raf.tick((time += 70));
assert.htmlEqual( assert.htmlEqual(
target.innerHTML, target.innerHTML,
` `

@ -183,7 +183,9 @@ async function run_test_variant(
if (str.slice(0, i).includes('logs')) { if (str.slice(0, i).includes('logs')) {
// eslint-disable-next-line no-console // eslint-disable-next-line no-console
console.log = (...args) => logs.push(...args); console.log = (...args) => {
logs.push(...args);
};
} }
if (str.slice(0, i).includes('hydrate')) { if (str.slice(0, i).includes('hydrate')) {

@ -0,0 +1,24 @@
import { test } from '../../test';
export default test({
async test({ assert, target, logs }) {
const [b1, b2] = target.querySelectorAll('button');
b1.click();
await Promise.resolve();
assert.htmlEqual(
target.innerHTML,
`<p>pending</p><button>Show Promise A</button><button>Show Promise B</button>`
);
b2.click();
await Promise.resolve();
await Promise.resolve();
assert.htmlEqual(
target.innerHTML,
`<p>pending</p><button>Show Promise A</button><button>Show Promise B</button>`
);
assert.deepEqual(logs, ['rendering pending block']);
}
});

@ -0,0 +1,17 @@
<script>
const a = new Promise(() => {});
const b = new Promise(() => {});
let promise = $state(a);
</script>
{#await promise}
{console.log('rendering pending block')}
<p>pending</p>
{:then value}
{console.log('rendering then block')}
<p>then {value}</p>
{/await}
<button onclick={() => (promise = a)}>Show Promise A</button>
<button onclick={() => (promise = b)}>Show Promise B</button>

@ -0,0 +1,21 @@
import { test } from '../../test';
export default test({
async test({ assert, target, logs }) {
const [b1, b2, b3, b4] = target.querySelectorAll('button');
b1.click();
await Promise.resolve();
b2.click();
await Promise.resolve();
await Promise.resolve();
await Promise.resolve();
b3.click();
await Promise.resolve();
await Promise.resolve();
await Promise.resolve();
b4.click();
await Promise.resolve();
await Promise.resolve();
assert.deepEqual(logs, ['pending', 'a', 'b', 'c', 'pending']);
}
});

@ -0,0 +1,21 @@
<script>
const promise_a = Promise.resolve('a');
const promise_b = Promise.resolve('b');
const promise_c = Promise.resolve('c');
const promise_d = new Promise(() => {});
let current_promise = $state(promise_a);
</script>
{#await current_promise}
{console.log('pending')}
{:then value}
{console.log(value)}
{:catch}
{console.log('error')}
{/await}
<button onclick={()=>{current_promise = promise_a}}>Show Promise A</button>
<button onclick={()=>{current_promise = promise_b}}>Show Promise B</button>
<button onclick={()=>{current_promise = promise_c}}>Show Promise C</button>
<button onclick={()=>{current_promise = promise_d}}>Show Promise D</button>

@ -0,0 +1,27 @@
import { test } from '../../test';
export default test({
async test({ assert, target, logs }) {
const [b1, b2] = target.querySelectorAll('button');
b1.click();
await Promise.resolve();
assert.htmlEqual(
target.innerHTML,
`<p>then a</p><button>Show Promise A</button><button>Show Promise B</button>`
);
b2.click();
await Promise.resolve();
assert.htmlEqual(
target.innerHTML,
`<p>then a</p><button>Show Promise A</button><button>Show Promise B</button>`
);
await Promise.resolve();
await Promise.resolve();
assert.htmlEqual(
target.innerHTML,
`<p>then b</p><button>Show Promise A</button><button>Show Promise B</button>`
);
assert.deepEqual(logs, ['rendering pending block', 'rendering then block']);
}
});

@ -0,0 +1,17 @@
<script>
const a = Promise.resolve('a');
const b = Promise.resolve('b');
let promise = $state(a);
</script>
{#await promise}
{console.log('rendering pending block')}
<p>pending</p>
{:then value}
{console.log('rendering then block')}
<p>then {value}</p>
{/await}
<button onclick={() => (promise = a)}>Show Promise A</button>
<button onclick={() => (promise = b)}>Show Promise B</button>

@ -0,0 +1,9 @@
<script>
let { object = $bindable(), primitive = $bindable() } = $props();
</script>
{#if primitive}
<button onclick={() => (primitive = 'bar')}>{primitive}</button>
{:else}
<button onclick={() => (object.value = 'bar')}>{object.value}</button>
{/if}

@ -0,0 +1,9 @@
<script>
let { object = $bindable({}), primitive = $bindable('') } = $props();
</script>
{#if primitive}
<button onclick={() => (primitive = 'bar')}>{primitive}</button>
{:else}
<button onclick={() => (object.value = 'bar')}>{object.value}</button>
{/if}

@ -0,0 +1,24 @@
<svelte:options runes={false} />
<script>
import Component1 from './Component1.svelte';
import Component2 from './Component2.svelte';
let object1 = { value: 'foo' };
let object2 = { value: 'foo' };
let primitive1 = 'foo';
let primitive2 = 'foo';
</script>
{object1.value}
<Component1 bind:object={object1} />
{object2.value}
<Component2 bind:object={object2} />
{primitive1}
<Component1 bind:primitive={primitive1} />
{primitive2}
<Component2 bind:primitive={primitive2} />

@ -0,0 +1,39 @@
<script>
import Component1 from './Component1.svelte';
import Component2 from './Component2.svelte';
let object1 = $state({ value: 'foo' });
let object2 = $state({ value: 'foo' });
class Frozen {
constructor(value) {
this.value = value;
}
}
let object3 = $state(new Frozen('foo'));
let object4 = $state(new Frozen('foo'));
let primitive1 = $state('foo');
let primitive2 = $state('foo');
</script>
{object1.value}
<Component1 bind:object={object1} />
{object2.value}
<Component2 bind:object={object2} />
<!-- force them into a different render effect so they don't coincidently update with the others -->
{#if true}
{object3.value}
<Component1 bind:object={object3} />
{object4.value}
<Component2 bind:object={object4} />
{/if}
{primitive1}
<Component1 bind:primitive={primitive1} />
{primitive2}
<Component2 bind:primitive={primitive2} />

@ -0,0 +1,24 @@
import { flushSync } from 'svelte';
import { test } from '../../test';
export default test({
mode: ['client'],
async test({ assert, target }) {
const buttons = target.querySelectorAll('button');
for (const button of buttons) {
await button.click();
flushSync();
}
flushSync();
assert.htmlEqual(
target.innerHTML,
`
bar <button>bar</button> bar <button>bar</button> bar <button>bar</button> bar <button>bar</button>
<hr>
bar <button>bar</button> bar <button>bar</button> foo <button>foo</button> foo <button>foo</button> bar <button>bar</button> bar <button>bar</button>
`
);
}
});

@ -0,0 +1,10 @@
<script>
import Legacy from './Legacy.svelte';
import Runes from './Runes.svelte';
</script>
<Legacy />
<hr />
<Runes />

@ -0,0 +1,5 @@
import { test } from '../../test';
export default test({
html: '013/023'
});

@ -0,0 +1,9 @@
{#snippet one(a, b = 1, c = (2, 3))}
{a}{b}{c}
{/snippet}
{#snippet two(a, b = (1, 2), c = 3)}
{a}{b}{c}
{/snippet}
{@render one(0)}/{@render two(0)}

@ -0,0 +1,7 @@
import { test } from '../../test';
export default test({
test({ assert, logs }) {
assert.deepEqual(logs, [1, 1, 1, 1]);
}
});

@ -0,0 +1,9 @@
<script>
let x = $state(0);
let o = $state({ x: 0 });
console.log(++x);
console.log(x++);
console.log(++o.x);
console.log(o.x++);
</script>

@ -5,6 +5,6 @@ export default function Bind_this($$anchor) {
var fragment = $.comment(); var fragment = $.comment();
var node = $.first_child(fragment); var node = $.first_child(fragment);
$.bind_this(Foo(node, {}), ($$value) => foo = $$value, () => foo); $.bind_this(Foo(node, { $$legacy: true }), ($$value) => foo = $$value, () => foo);
$.append($$anchor, fragment); $.append($$anchor, fragment);
} }

@ -131,6 +131,13 @@ mount(NewComponent, {
intro: false, intro: false,
recover: false recover: false
}); });
mount(
NewComponent,
// @ts-expect-error props missing
{ target: null as any }
);
// if component receives no args, props can be omitted
mount(null as any as typeof SvelteComponent<{}>, { target: null as any });
hydrate(NewComponent, { hydrate(NewComponent, {
target: null as any as Document | Element | ShadowRoot, target: null as any as Document | Element | ShadowRoot,
@ -148,6 +155,13 @@ hydrate(NewComponent, {
intro: false, intro: false,
recover: false recover: false
}); });
hydrate(
NewComponent,
// @ts-expect-error props missing
{ target: null as any }
);
// if component receives no args, props can be omitted
hydrate(null as any as typeof SvelteComponent<{}>, { target: null as any });
render(NewComponent, { render(NewComponent, {
props: { props: {
@ -156,6 +170,14 @@ render(NewComponent, {
x: '' x: ''
} }
}); });
// @ts-expect-error
render(NewComponent);
render(NewComponent, {
props: {
// @ts-expect-error
prop: 1
}
});
// --------------------------------------------------------------------------- interop // --------------------------------------------------------------------------- interop
@ -255,6 +277,13 @@ mount(functionComponent, {
readonly: 1 readonly: 1
} }
}); });
mount(
functionComponent,
// @ts-expect-error props missing
{ target: null as any }
);
// if component receives no args, props can be omitted
mount(null as any as Component<{}>, { target: null as any });
hydrate(functionComponent, { hydrate(functionComponent, {
target: null as any as Document | Element | ShadowRoot, target: null as any as Document | Element | ShadowRoot,
@ -272,13 +301,33 @@ hydrate(functionComponent, {
binding: true binding: true
} }
}); });
hydrate(
functionComponent,
// @ts-expect-error props missing
{ target: null as any }
);
// if component receives no args, props can be omitted
hydrate(null as any as Component<{}>, { target: null as any });
render(functionComponent, { render(functionComponent, {
props: { props: {
binding: true, binding: true,
readonly: 'foo', readonly: 'foo'
}
});
// @ts-expect-error
render(functionComponent);
render(functionComponent, {
// @ts-expect-error
props: {
binding: true
}
});
render(functionComponent, {
props: {
binding: true,
// @ts-expect-error // @ts-expect-error
x: '' readonly: 1
} }
}); });

@ -1,3 +1,11 @@
<script>
import Component from './component.svelte'
</script>
<label> <label>
<slot /> <slot />
</label> </label>
<label>
<Component />
</label>

@ -0,0 +1,7 @@
<script>
import Component from './component.svelte'
</script>
<label>
{@render x()}
</label>

@ -0,0 +1,4 @@
<!-- prettier-ignore -->
{#snippet ok () }
asd
{/snippet}

@ -23,7 +23,7 @@
"svelte/internal/client": ["./src/internal/client/index.js"], "svelte/internal/client": ["./src/internal/client/index.js"],
"svelte/legacy": ["./src/legacy/legacy-client.js"], "svelte/legacy": ["./src/legacy/legacy-client.js"],
"svelte/motion": ["./src/motion/public.d.ts"], "svelte/motion": ["./src/motion/public.d.ts"],
"svelte/server": ["./src/server/index.js"], "svelte/server": ["./src/server/index.d.ts"],
"svelte/store": ["./src/store/public.d.ts"], "svelte/store": ["./src/store/public.d.ts"],
"#compiler": ["./src/compiler/types/index.d.ts"], "#compiler": ["./src/compiler/types/index.d.ts"],
"#client": ["./src/internal/client/types.d.ts"], "#client": ["./src/internal/client/types.d.ts"],

@ -5,7 +5,7 @@ declare module 'svelte' {
* See [breaking changes](https://svelte-5-preview.vercel.app/docs/breaking-changes#components-are-no-longer-classes) * See [breaking changes](https://svelte-5-preview.vercel.app/docs/breaking-changes#components-are-no-longer-classes)
* for more info. * for more info.
*/ */
export interface ComponentConstructorOptions< interface ComponentConstructorOptions<
Props extends Record<string, any> = Record<string, any> Props extends Record<string, any> = Record<string, any>
> { > {
target: Element | Document | ShadowRoot; target: Element | Document | ShadowRoot;
@ -36,7 +36,7 @@ declare module 'svelte' {
* To instantiate components, use `mount` instead`. * To instantiate components, use `mount` instead`.
* See [breaking changes documentation](https://svelte-5-preview.vercel.app/docs/breaking-changes#components-are-no-longer-classes) for more info. * See [breaking changes documentation](https://svelte-5-preview.vercel.app/docs/breaking-changes#components-are-no-longer-classes) for more info.
*/ */
export class SvelteComponent< class SvelteComponent<
Props extends Record<string, any> = Record<string, any>, Props extends Record<string, any> = Record<string, any>,
Events extends Record<string, any> = any, Events extends Record<string, any> = any,
Slots extends Record<string, any> = any Slots extends Record<string, any> = any
@ -123,7 +123,7 @@ declare module 'svelte' {
* <MyComponent foo={'bar'} /> * <MyComponent foo={'bar'} />
* ``` * ```
*/ */
export interface Component< interface Component<
Props extends Record<string, any> = {}, Props extends Record<string, any> = {},
Exports extends Record<string, any> = {}, Exports extends Record<string, any> = {},
Bindings extends keyof Props | '' = string Bindings extends keyof Props | '' = string
@ -158,7 +158,7 @@ declare module 'svelte' {
/** /**
* @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. * @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< class SvelteComponentTyped<
Props extends Record<string, any> = Record<string, any>, Props extends Record<string, any> = Record<string, any>,
Events extends Record<string, any> = any, Events extends Record<string, any> = any,
Slots extends Record<string, any> = any Slots extends Record<string, any> = any
@ -182,21 +182,38 @@ declare module 'svelte' {
* <Component on:close={handleCloseEvent} /> * <Component on:close={handleCloseEvent} />
* ``` * ```
*/ */
export type ComponentEvents<Comp extends SvelteComponent> = type ComponentEvents<Comp extends SvelteComponent> =
Comp extends SvelteComponent<any, infer Events> ? Events : never; Comp extends SvelteComponent<any, infer Events> ? Events : never;
/** /**
* Convenience type to get the props the given component expects. Example: * Convenience type to get the props the given component expects.
* ```html
* <script lang="ts">
* import type { ComponentProps } from 'svelte';
* import Component from './Component.svelte';
* *
* const props: ComponentProps<Component> = { foo: 'bar' }; // Errors if these aren't the correct props * Example: Ensure a variable contains the props expected by `MyComponent`:
* </script> *
* ```ts
* import type { ComponentProps } from 'svelte';
* import MyComponent from './MyComponent.svelte';
*
* // Errors if these aren't the correct props expected by MyComponent.
* const props: ComponentProps<MyComponent> = { foo: 'bar' };
* ```
*
* Example: A generic function that accepts some component and infers the type of its props:
*
* ```ts
* import type { Component, ComponentProps } from 'svelte';
* import MyComponent from './MyComponent.svelte';
*
* function withProps<TComponent extends Component<any>>(
* component: TComponent,
* props: ComponentProps<TComponent>
* ) {};
*
* // Errors if the second argument is not the correct props expected by the component in the first argument.
* withProps(MyComponent, { foo: 'bar' });
* ``` * ```
*/ */
export type ComponentProps<Comp extends SvelteComponent | Component<any>> = type ComponentProps<Comp extends SvelteComponent | Component<any>> =
Comp extends SvelteComponent<infer Props> Comp extends SvelteComponent<infer Props>
? Props ? Props
: Comp extends Component<infer Props> : Comp extends Component<infer Props>
@ -225,7 +242,7 @@ declare module 'svelte' {
* <svelte:component this={componentOfCertainSubType} needsThisProp="hello" /> * <svelte:component this={componentOfCertainSubType} needsThisProp="hello" />
* ``` * ```
*/ */
export type ComponentType<Comp extends SvelteComponent = SvelteComponent> = (new ( type ComponentType<Comp extends SvelteComponent = SvelteComponent> = (new (
options: ComponentConstructorOptions< options: ComponentConstructorOptions<
Comp extends SvelteComponent<infer Props> ? Props : Record<string, any> Comp extends SvelteComponent<infer Props> ? Props : Record<string, any>
> >
@ -243,7 +260,7 @@ declare module 'svelte' {
* ``` * ```
* You can only call a snippet through the `{@render ...}` tag. * You can only call a snippet through the `{@render ...}` tag.
*/ */
export type Snippet<T extends unknown[] = []> = type Snippet<T extends unknown[] = []> =
// this conditional allows tuples but not arrays. Arrays would indicate a // this conditional allows tuples but not arrays. Arrays would indicate a
// rest parameter type, which is not supported. If rest parameters are added // rest parameter type, which is not supported. If rest parameters are added
// in the future, the condition can be removed. // in the future, the condition can be removed.
@ -262,7 +279,7 @@ declare module 'svelte' {
cancelable?: boolean; cancelable?: boolean;
} }
export interface EventDispatcher<EventMap extends Record<string, any>> { interface EventDispatcher<EventMap extends Record<string, any>> {
// Implementation notes: // Implementation notes:
// - undefined extends X instead of X extends undefined makes this work better with both strict and nonstrict mode // - undefined extends X instead of X extends undefined makes this work better with both strict and nonstrict mode
// - | null | undefined is added for convenience, as they are equivalent for the custom event constructor (both result in a null detail) // - | null | undefined is added for convenience, as they are equivalent for the custom event constructor (both result in a null detail)
@ -285,7 +302,7 @@ declare module 'svelte' {
* *
* https://svelte.dev/docs/svelte#onmount * https://svelte.dev/docs/svelte#onmount
* */ * */
export function onMount<T>(fn: () => NotFunction<T> | Promise<NotFunction<T>> | (() => any)): void; function onMount<T>(fn: () => NotFunction<T> | Promise<NotFunction<T>> | (() => any)): void;
/** /**
* Schedules a callback to run immediately before the component is unmounted. * Schedules a callback to run immediately before the component is unmounted.
* *
@ -294,7 +311,7 @@ declare module 'svelte' {
* *
* https://svelte.dev/docs/svelte#ondestroy * https://svelte.dev/docs/svelte#ondestroy
* */ * */
export function onDestroy(fn: () => any): void; function onDestroy(fn: () => any): void;
/** /**
* Creates an event dispatcher that can be used to dispatch [component events](https://svelte.dev/docs#template-syntax-component-directives-on-eventname). * Creates an event dispatcher that can be used to dispatch [component events](https://svelte.dev/docs#template-syntax-component-directives-on-eventname).
* Event dispatchers are functions that can take two arguments: `name` and `detail`. * Event dispatchers are functions that can take two arguments: `name` and `detail`.
@ -317,7 +334,7 @@ declare module 'svelte' {
* https://svelte.dev/docs/svelte#createeventdispatcher * https://svelte.dev/docs/svelte#createeventdispatcher
* @deprecated Use callback props and/or the `$host()` rune instead see https://svelte-5-preview.vercel.app/docs/deprecations#createeventdispatcher * @deprecated Use callback props and/or the `$host()` rune instead see https://svelte-5-preview.vercel.app/docs/deprecations#createeventdispatcher
* */ * */
export function createEventDispatcher<EventMap extends Record<string, any> = any>(): EventDispatcher<EventMap>; function createEventDispatcher<EventMap extends Record<string, any> = any>(): EventDispatcher<EventMap>;
/** /**
* Schedules a callback to run immediately before the component is updated after any state change. * Schedules a callback to run immediately before the component is updated after any state change.
* *
@ -328,7 +345,7 @@ declare module 'svelte' {
* https://svelte.dev/docs/svelte#beforeupdate * https://svelte.dev/docs/svelte#beforeupdate
* @deprecated Use `$effect.pre` instead see https://svelte-5-preview.vercel.app/docs/deprecations#beforeupdate-and-afterupdate * @deprecated Use `$effect.pre` instead see https://svelte-5-preview.vercel.app/docs/deprecations#beforeupdate-and-afterupdate
* */ * */
export function beforeUpdate(fn: () => void): void; function beforeUpdate(fn: () => void): void;
/** /**
* Schedules a callback to run immediately after the component has been updated. * Schedules a callback to run immediately after the component has been updated.
* *
@ -339,58 +356,72 @@ declare module 'svelte' {
* https://svelte.dev/docs/svelte#afterupdate * https://svelte.dev/docs/svelte#afterupdate
* @deprecated Use `$effect` instead see https://svelte-5-preview.vercel.app/docs/deprecations#beforeupdate-and-afterupdate * @deprecated Use `$effect` instead see https://svelte-5-preview.vercel.app/docs/deprecations#beforeupdate-and-afterupdate
* */ * */
export function afterUpdate(fn: () => void): void; function afterUpdate(fn: () => void): void;
/** /**
* Synchronously flushes any pending state changes and those that result from it. * Synchronously flushes any pending state changes and those that result from it.
* */ * */
export function flushSync(fn?: (() => void) | undefined): void; function flushSync(fn?: (() => void) | undefined): void;
/** Anything except a function */ /** Anything except a function */
type NotFunction<T> = T extends Function ? never : T; type NotFunction<T> = T extends Function ? never : T;
/** /**
* Mounts a component to the given target and returns the exports and potentially the props (if compiled with `accessors: true`) of the component * 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>>(component: ComponentType<SvelteComponent<Props, any, any>> | Component<Props, Exports, any>, options: { function mount<Props extends Record<string, any>, Exports extends Record<string, any>>(component: ComponentType<SvelteComponent<Props>> | Component<Props, Exports, any>, options: {} extends Props ? {
target: Document | Element | ShadowRoot; target: Document | Element | ShadowRoot;
anchor?: Node | undefined; anchor?: Node;
props?: Props | undefined; props?: Props;
events?: Record<string, (e: any) => any> | undefined; events?: Record<string, (e: any) => any>;
context?: Map<any, any> | undefined; context?: Map<any, any>;
intro?: boolean | undefined; intro?: boolean;
} : {
target: Document | Element | ShadowRoot;
props: Props;
anchor?: Node;
events?: Record<string, (e: any) => any>;
context?: Map<any, any>;
intro?: boolean;
}): Exports; }): Exports;
/** /**
* Hydrates a component on the given target and returns the exports and potentially the props (if compiled with `accessors: true`) of the component * 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>>(component: ComponentType<SvelteComponent<Props, any, any>> | Component<Props, Exports, any>, options: { function hydrate<Props extends Record<string, any>, Exports extends Record<string, any>>(component: ComponentType<SvelteComponent<Props>> | Component<Props, Exports, any>, options: {} extends Props ? {
target: Document | Element | ShadowRoot; target: Document | Element | ShadowRoot;
props?: Props | undefined; props?: Props;
events?: Record<string, (e: any) => any> | undefined; events?: Record<string, (e: any) => any>;
context?: Map<any, any> | undefined; context?: Map<any, any>;
intro?: boolean | undefined; intro?: boolean;
recover?: boolean | undefined; recover?: boolean;
} : {
target: Document | Element | ShadowRoot;
props: Props;
events?: Record<string, (e: any) => any>;
context?: Map<any, any>;
intro?: boolean;
recover?: boolean;
}): Exports; }): Exports;
/** /**
* Unmounts a component that was previously mounted using `mount` or `hydrate`. * Unmounts a component that was previously mounted using `mount` or `hydrate`.
* */ * */
export function unmount(component: Record<string, any>): void; function unmount(component: Record<string, any>): void;
/** /**
* Returns a promise that resolves once any pending state changes have been applied. * Returns a promise that resolves once any pending state changes have been applied.
* */ * */
export function tick(): Promise<void>; function tick(): Promise<void>;
/** /**
* Use `untrack` to prevent something from being treated as an `$effect`/`$derived` dependency. * Use `untrack` to prevent something from being treated as an `$effect`/`$derived` dependency.
* *
* https://svelte-5-preview.vercel.app/docs/functions#untrack * https://svelte-5-preview.vercel.app/docs/functions#untrack
* */ * */
export function untrack<T>(fn: () => T): T; function untrack<T>(fn: () => T): T;
/** /**
* Retrieves the context that belongs to the closest parent component with the specified `key`. * Retrieves the context that belongs to the closest parent component with the specified `key`.
* Must be called during component initialisation. * Must be called during component initialisation.
* *
* https://svelte.dev/docs/svelte#getcontext * https://svelte.dev/docs/svelte#getcontext
* */ * */
export function getContext<T>(key: any): T; function getContext<T>(key: any): T;
/** /**
* Associates an arbitrary `context` object with the current component and the specified `key` * Associates an arbitrary `context` object with the current component and the specified `key`
* and returns that object. The context is then available to children of the component * and returns that object. The context is then available to children of the component
@ -400,14 +431,14 @@ declare module 'svelte' {
* *
* https://svelte.dev/docs/svelte#setcontext * https://svelte.dev/docs/svelte#setcontext
* */ * */
export function setContext<T>(key: any, context: T): T; function setContext<T>(key: any, context: T): T;
/** /**
* Checks whether a given `key` has been set in the context of a parent component. * Checks whether a given `key` has been set in the context of a parent component.
* Must be called during component initialisation. * Must be called during component initialisation.
* *
* https://svelte.dev/docs/svelte#hascontext * https://svelte.dev/docs/svelte#hascontext
* */ * */
export function hasContext(key: any): boolean; function hasContext(key: any): boolean;
/** /**
* Retrieves the whole context map that belongs to the closest parent component. * Retrieves the whole context map that belongs to the closest parent component.
* Must be called during component initialisation. Useful, for example, if you * Must be called during component initialisation. Useful, for example, if you
@ -415,7 +446,9 @@ declare module 'svelte' {
* *
* https://svelte.dev/docs/svelte#getallcontexts * https://svelte.dev/docs/svelte#getallcontexts
* */ * */
export function getAllContexts<T extends Map<any, any> = Map<any, any>>(): T; function getAllContexts<T extends Map<any, any> = Map<any, any>>(): T;
export { ComponentConstructorOptions, SvelteComponent, Component, SvelteComponentTyped, ComponentEvents, ComponentProps, ComponentType, Snippet, EventDispatcher, onMount, onDestroy, createEventDispatcher, beforeUpdate, afterUpdate, flushSync, hydrate, mount, unmount, getContext, getAllContexts, hasContext, setContext, tick, untrack };
} }
declare module 'svelte/action' { declare module 'svelte/action' {
@ -447,7 +480,7 @@ declare module 'svelte/action' {
* *
* Docs: https://svelte.dev/docs/svelte-action * Docs: https://svelte.dev/docs/svelte-action
*/ */
export interface ActionReturn< interface ActionReturn<
Parameter = undefined, Parameter = undefined,
Attributes extends Record<string, any> = Record<never, any> Attributes extends Record<string, any> = Record<never, any>
> { > {
@ -478,7 +511,7 @@ declare module 'svelte/action' {
* *
* Docs: https://svelte.dev/docs/svelte-action * Docs: https://svelte.dev/docs/svelte-action
*/ */
export interface Action< interface Action<
Element = HTMLElement, Element = HTMLElement,
Parameter = undefined, Parameter = undefined,
Attributes extends Record<string, any> = Record<never, any> Attributes extends Record<string, any> = Record<never, any>
@ -492,11 +525,13 @@ declare module 'svelte/action' {
// Implementation notes: // Implementation notes:
// - undefined extends X instead of X extends undefined makes this work better with both strict and nonstrict mode // - undefined extends X instead of X extends undefined makes this work better with both strict and nonstrict mode
export { ActionReturn, Action };
} }
declare module 'svelte/animate' { declare module 'svelte/animate' {
// todo: same as Transition, should it be shared? // todo: same as Transition, should it be shared?
export interface AnimationConfig { interface AnimationConfig {
delay?: number; delay?: number;
duration?: number; duration?: number;
easing?: (t: number) => number; easing?: (t: number) => number;
@ -504,7 +539,7 @@ declare module 'svelte/animate' {
tick?: (t: number, u: number) => void; tick?: (t: number, u: number) => void;
} }
export interface FlipParams { interface FlipParams {
delay?: number; delay?: number;
duration?: number | ((len: number) => number); duration?: number | ((len: number) => number);
easing?: (t: number) => number; easing?: (t: number) => number;
@ -515,10 +550,12 @@ declare module 'svelte/animate' {
* *
* https://svelte.dev/docs/svelte-animate#flip * https://svelte.dev/docs/svelte-animate#flip
* */ * */
export function flip(node: Element, { from, to }: { function flip(node: Element, { from, to }: {
from: DOMRect; from: DOMRect;
to: DOMRect; to: DOMRect;
}, params?: FlipParams): AnimationConfig; }, params?: FlipParams): AnimationConfig;
export { AnimationConfig, FlipParams, flip };
} }
declare module 'svelte/compiler' { declare module 'svelte/compiler' {
@ -533,14 +570,14 @@ declare module 'svelte/compiler' {
* @param source The component source code * @param source The component source code
* @param options The compiler options * @param options The compiler options
* */ * */
export function compile(source: string, options: CompileOptions): CompileResult; function compile(source: string, options: CompileOptions): CompileResult;
/** /**
* `compileModule` takes your JavaScript source code containing runes, and turns it into a JavaScript module. * `compileModule` takes your JavaScript source code containing runes, and turns it into a JavaScript module.
* *
* https://svelte.dev/docs/svelte-compiler#svelte-compile * https://svelte.dev/docs/svelte-compiler#svelte-compile
* @param source The component source code * @param source The component source code
* */ * */
export function compileModule(source: string, options: ModuleCompileOptions): CompileResult; function compileModule(source: string, options: ModuleCompileOptions): CompileResult;
/** /**
* The parse function parses a component, returning only its abstract syntax tree. * The parse function parses a component, returning only its abstract syntax tree.
* *
@ -549,7 +586,7 @@ declare module 'svelte/compiler' {
* *
* https://svelte.dev/docs/svelte-compiler#svelte-parse * https://svelte.dev/docs/svelte-compiler#svelte-parse
* */ * */
export function parse(source: string, options: { function parse(source: string, options: {
filename?: string; filename?: string;
modern: true; modern: true;
}): Root; }): Root;
@ -561,14 +598,14 @@ declare module 'svelte/compiler' {
* *
* https://svelte.dev/docs/svelte-compiler#svelte-parse * https://svelte.dev/docs/svelte-compiler#svelte-parse
* */ * */
export function parse(source: string, options?: { function parse(source: string, options?: {
filename?: string | undefined; filename?: string;
modern?: false | undefined; modern?: false;
} | undefined): LegacyRoot; } | undefined): LegacyRoot;
/** /**
* @deprecated Replace this with `import { walk } from 'estree-walker'` * @deprecated Replace this with `import { walk } from 'estree-walker'`
* */ * */
export function walk(): never; function walk(): never;
/** The return value of `compile` from `svelte/compiler` */ /** The return value of `compile` from `svelte/compiler` */
interface CompileResult { interface CompileResult {
/** The compiled JavaScript */ /** The compiled JavaScript */
@ -1093,19 +1130,19 @@ declare module 'svelte/compiler' {
* *
* https://svelte.dev/docs/svelte-compiler#svelte-preprocess * https://svelte.dev/docs/svelte-compiler#svelte-preprocess
* */ * */
export function preprocess(source: string, preprocessor: PreprocessorGroup | PreprocessorGroup[], options?: { function preprocess(source: string, preprocessor: PreprocessorGroup | PreprocessorGroup[], options?: {
filename?: string | undefined; filename?: string;
} | undefined): Promise<Processed>; } | undefined): Promise<Processed>;
export class CompileError extends Error { class CompileError extends Error {
constructor(code: string, message: string, position: [number, number] | undefined); constructor(code: string, message: string, position: [number, number] | undefined);
filename: string | undefined; filename: string | undefined;
position: CompileError_1['position']; position: CompileError_1["position"];
start: CompileError_1['start']; start: CompileError_1["start"];
end: CompileError_1['end']; end: CompileError_1["end"];
code: string; code: string;
} }
/** /**
@ -1113,13 +1150,13 @@ declare module 'svelte/compiler' {
* *
* https://svelte.dev/docs/svelte-compiler#svelte-version * https://svelte.dev/docs/svelte-compiler#svelte-version
* */ * */
export const VERSION: string; const VERSION: string;
/** /**
* Does a best-effort migration of Svelte code towards using runes, event attributes and render tags. * Does a best-effort migration of Svelte code towards using runes, event attributes and render tags.
* May throw an error if the code is too complex to migrate automatically. * May throw an error if the code is too complex to migrate automatically.
* *
* */ * */
export function migrate(source: string): { function migrate(source: string): {
code: string; code: string;
}; };
class Scope { class Scope {
@ -1139,13 +1176,13 @@ declare module 'svelte/compiler' {
/** /**
* A map of declarators to the bindings they declare * A map of declarators to the bindings they declare
* */ * */
declarators: Map<import('estree').VariableDeclarator | LetDirective, Binding[]>; declarators: Map<import("estree").VariableDeclarator | LetDirective, Binding[]>;
/** /**
* A set of all the names referenced with this scope * A set of all the names referenced with this scope
* useful for generating unique names * useful for generating unique names
* */ * */
references: Map<string, { references: Map<string, {
node: import('estree').Identifier; node: import("estree").Identifier;
path: SvelteNode[]; path: SvelteNode[];
}[]>; }[]>;
/** /**
@ -1154,18 +1191,18 @@ declare module 'svelte/compiler' {
*/ */
function_depth: number; function_depth: number;
declare(node: import('estree').Identifier, kind: Binding['kind'], declaration_kind: DeclarationKind, initial?: null | import('estree').Expression | import('estree').FunctionDeclaration | import('estree').ClassDeclaration | import('estree').ImportDeclaration | EachBlock): Binding; declare(node: import("estree").Identifier, kind: Binding["kind"], declaration_kind: DeclarationKind, initial?: null | import("estree").Expression | import("estree").FunctionDeclaration | import("estree").ClassDeclaration | import("estree").ImportDeclaration | EachBlock): Binding;
child(porous?: boolean): Scope; child(porous?: boolean): Scope;
generate(preferred_name: string): string; generate(preferred_name: string): string;
get(name: string): Binding | null; get(name: string): Binding | null;
get_bindings(node: import('estree').VariableDeclarator | LetDirective): Binding[]; get_bindings(node: import("estree").VariableDeclarator | LetDirective): Binding[];
owner(name: string): Scope | null; owner(name: string): Scope | null;
reference(node: import('estree').Identifier, path: SvelteNode[]): void; reference(node: import("estree").Identifier, path: SvelteNode[]): void;
#private; #private;
} }
class ScopeRoot { class ScopeRoot {
@ -1855,133 +1892,137 @@ declare module 'svelte/compiler' {
style?: Preprocessor; style?: Preprocessor;
script?: Preprocessor; script?: Preprocessor;
} }
export { compile, compileModule, parse, walk, preprocess, CompileError, VERSION, migrate };
} }
declare module 'svelte/easing' { declare module 'svelte/easing' {
/** /**
* https://svelte.dev/docs/svelte-easing * https://svelte.dev/docs/svelte-easing
* */ * */
export function linear(t: number): number; function linear(t: number): number;
/** /**
* https://svelte.dev/docs/svelte-easing * https://svelte.dev/docs/svelte-easing
* */ * */
export function backInOut(t: number): number; function backInOut(t: number): number;
/** /**
* https://svelte.dev/docs/svelte-easing * https://svelte.dev/docs/svelte-easing
* */ * */
export function backIn(t: number): number; function backIn(t: number): number;
/** /**
* https://svelte.dev/docs/svelte-easing * https://svelte.dev/docs/svelte-easing
* */ * */
export function backOut(t: number): number; function backOut(t: number): number;
/** /**
* https://svelte.dev/docs/svelte-easing * https://svelte.dev/docs/svelte-easing
* */ * */
export function bounceOut(t: number): number; function bounceOut(t: number): number;
/** /**
* https://svelte.dev/docs/svelte-easing * https://svelte.dev/docs/svelte-easing
* */ * */
export function bounceInOut(t: number): number; function bounceInOut(t: number): number;
/** /**
* https://svelte.dev/docs/svelte-easing * https://svelte.dev/docs/svelte-easing
* */ * */
export function bounceIn(t: number): number; function bounceIn(t: number): number;
/** /**
* https://svelte.dev/docs/svelte-easing * https://svelte.dev/docs/svelte-easing
* */ * */
export function circInOut(t: number): number; function circInOut(t: number): number;
/** /**
* https://svelte.dev/docs/svelte-easing * https://svelte.dev/docs/svelte-easing
* */ * */
export function circIn(t: number): number; function circIn(t: number): number;
/** /**
* https://svelte.dev/docs/svelte-easing * https://svelte.dev/docs/svelte-easing
* */ * */
export function circOut(t: number): number; function circOut(t: number): number;
/** /**
* https://svelte.dev/docs/svelte-easing * https://svelte.dev/docs/svelte-easing
* */ * */
export function cubicInOut(t: number): number; function cubicInOut(t: number): number;
/** /**
* https://svelte.dev/docs/svelte-easing * https://svelte.dev/docs/svelte-easing
* */ * */
export function cubicIn(t: number): number; function cubicIn(t: number): number;
/** /**
* https://svelte.dev/docs/svelte-easing * https://svelte.dev/docs/svelte-easing
* */ * */
export function cubicOut(t: number): number; function cubicOut(t: number): number;
/** /**
* https://svelte.dev/docs/svelte-easing * https://svelte.dev/docs/svelte-easing
* */ * */
export function elasticInOut(t: number): number; function elasticInOut(t: number): number;
/** /**
* https://svelte.dev/docs/svelte-easing * https://svelte.dev/docs/svelte-easing
* */ * */
export function elasticIn(t: number): number; function elasticIn(t: number): number;
/** /**
* https://svelte.dev/docs/svelte-easing * https://svelte.dev/docs/svelte-easing
* */ * */
export function elasticOut(t: number): number; function elasticOut(t: number): number;
/** /**
* https://svelte.dev/docs/svelte-easing * https://svelte.dev/docs/svelte-easing
* */ * */
export function expoInOut(t: number): number; function expoInOut(t: number): number;
/** /**
* https://svelte.dev/docs/svelte-easing * https://svelte.dev/docs/svelte-easing
* */ * */
export function expoIn(t: number): number; function expoIn(t: number): number;
/** /**
* https://svelte.dev/docs/svelte-easing * https://svelte.dev/docs/svelte-easing
* */ * */
export function expoOut(t: number): number; function expoOut(t: number): number;
/** /**
* https://svelte.dev/docs/svelte-easing * https://svelte.dev/docs/svelte-easing
* */ * */
export function quadInOut(t: number): number; function quadInOut(t: number): number;
/** /**
* https://svelte.dev/docs/svelte-easing * https://svelte.dev/docs/svelte-easing
* */ * */
export function quadIn(t: number): number; function quadIn(t: number): number;
/** /**
* https://svelte.dev/docs/svelte-easing * https://svelte.dev/docs/svelte-easing
* */ * */
export function quadOut(t: number): number; function quadOut(t: number): number;
/** /**
* https://svelte.dev/docs/svelte-easing * https://svelte.dev/docs/svelte-easing
* */ * */
export function quartInOut(t: number): number; function quartInOut(t: number): number;
/** /**
* https://svelte.dev/docs/svelte-easing * https://svelte.dev/docs/svelte-easing
* */ * */
export function quartIn(t: number): number; function quartIn(t: number): number;
/** /**
* https://svelte.dev/docs/svelte-easing * https://svelte.dev/docs/svelte-easing
* */ * */
export function quartOut(t: number): number; function quartOut(t: number): number;
/** /**
* https://svelte.dev/docs/svelte-easing * https://svelte.dev/docs/svelte-easing
* */ * */
export function quintInOut(t: number): number; function quintInOut(t: number): number;
/** /**
* https://svelte.dev/docs/svelte-easing * https://svelte.dev/docs/svelte-easing
* */ * */
export function quintIn(t: number): number; function quintIn(t: number): number;
/** /**
* https://svelte.dev/docs/svelte-easing * https://svelte.dev/docs/svelte-easing
* */ * */
export function quintOut(t: number): number; function quintOut(t: number): number;
/** /**
* https://svelte.dev/docs/svelte-easing * https://svelte.dev/docs/svelte-easing
* */ * */
export function sineInOut(t: number): number; function sineInOut(t: number): number;
/** /**
* https://svelte.dev/docs/svelte-easing * https://svelte.dev/docs/svelte-easing
* */ * */
export function sineIn(t: number): number; function sineIn(t: number): number;
/** /**
* https://svelte.dev/docs/svelte-easing * https://svelte.dev/docs/svelte-easing
* */ * */
export function sineOut(t: number): number; function sineOut(t: number): number;
export { linear, backInOut, backIn, backOut, bounceOut, bounceInOut, bounceIn, circInOut, circIn, circOut, cubicInOut, cubicIn, cubicOut, elasticInOut, elasticIn, elasticOut, expoInOut, expoIn, expoOut, quadInOut, quadIn, quadOut, quartInOut, quartIn, quartOut, quintInOut, quintIn, quintOut, sineInOut, sineIn, sineOut };
} }
declare module 'svelte/legacy' { declare module 'svelte/legacy' {
@ -1991,11 +2032,11 @@ declare module 'svelte/legacy' {
* @deprecated Use this only as a temporary solution to migrate your imperative component code to Svelte 5. * @deprecated Use this only as a temporary solution to migrate your imperative component code to Svelte 5.
* *
* */ * */
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> & { 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>> | import("svelte").Component<Props, any, string>; component: import("svelte").ComponentType<import("svelte").SvelteComponent<Props, Events, Slots>> | import("svelte").Component<Props>;
immutable?: boolean | undefined; immutable?: boolean;
hydrate?: boolean | undefined; hydrate?: boolean;
recover?: boolean | undefined; recover?: boolean;
}): import("svelte").SvelteComponent<Props, Events, Slots> & Exports; }): import("svelte").SvelteComponent<Props, Events, Slots> & Exports;
/** /**
* Takes the component function and returns a Svelte 4 compatible component constructor. * Takes the component function and returns a Svelte 4 compatible component constructor.
@ -2003,17 +2044,19 @@ declare module 'svelte/legacy' {
* @deprecated Use this only as a temporary solution to migrate your imperative component code to Svelte 5. * @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").Component<Props, any, string>): import("svelte").ComponentType<import("svelte").SvelteComponent<Props, Events, Slots> & Exports>; 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>): 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. * Runs the given function once immediately on the server, and works like `$effect.pre` on the client.
* *
* @deprecated Use this only as a temporary solution to migrate your component code to Svelte 5. * @deprecated Use this only as a temporary solution to migrate your component code to Svelte 5.
* */ * */
export function run(fn: () => void | (() => void)): void; function run(fn: () => void | (() => void)): void;
export { createClassComponent, asClassComponent, run };
} }
declare module 'svelte/motion' { declare module 'svelte/motion' {
export interface Spring<T> extends Readable<T> { interface Spring<T> extends Readable<T> {
set: (new_value: T, opts?: SpringUpdateOpts) => Promise<void>; set: (new_value: T, opts?: SpringUpdateOpts) => Promise<void>;
update: (fn: Updater<T>, opts?: SpringUpdateOpts) => Promise<void>; update: (fn: Updater<T>, opts?: SpringUpdateOpts) => Promise<void>;
precision: number; precision: number;
@ -2021,7 +2064,7 @@ declare module 'svelte/motion' {
stiffness: number; stiffness: number;
} }
export interface Tweened<T> extends Readable<T> { interface Tweened<T> extends Readable<T> {
set(value: T, opts?: TweenedOptions<T>): Promise<void>; set(value: T, opts?: TweenedOptions<T>): Promise<void>;
update(updater: Updater<T>, opts?: TweenedOptions<T>): Promise<void>; update(updater: Updater<T>, opts?: TweenedOptions<T>): Promise<void>;
} }
@ -2066,13 +2109,15 @@ declare module 'svelte/motion' {
* *
* https://svelte.dev/docs/svelte-motion#spring * https://svelte.dev/docs/svelte-motion#spring
* */ * */
export function spring<T = any>(value?: T | undefined, opts?: SpringOpts | undefined): Spring<T>; function spring<T = any>(value?: T | undefined, opts?: SpringOpts | undefined): Spring<T>;
/** /**
* A tweened store in Svelte is a special type of store that provides smooth transitions between state values over time. * A tweened store in Svelte is a special type of store that provides smooth transitions between state values over time.
* *
* https://svelte.dev/docs/svelte-motion#tweened * https://svelte.dev/docs/svelte-motion#tweened
* */ * */
export function tweened<T>(value?: T | undefined, defaults?: TweenedOptions<T> | undefined): Tweened<T>; function tweened<T>(value?: T | undefined, defaults?: TweenedOptions<T> | undefined): Tweened<T>;
export { Spring, Tweened, spring, tweened };
} }
declare module 'svelte/reactivity' { declare module 'svelte/reactivity' {
@ -2110,14 +2155,25 @@ declare module 'svelte/reactivity' {
} }
declare module 'svelte/server' { declare module 'svelte/server' {
import type { ComponentProps, Component, SvelteComponent, ComponentType } from 'svelte';
/** /**
* Only available on the server and when compiling with the `server` option. * Only available on the server and when compiling with the `server` option.
* Takes a component and returns an object with `body` and `head` properties on it, which you can use to populate the HTML when server-rendering your app. * Takes a component and returns an object with `body` and `head` properties on it, which you can use to populate the HTML when server-rendering your app.
* */ */
export function render<Props extends Record<string, any>>(component: import("svelte").Component<Props, any, string> | import("svelte").ComponentType<import("svelte").SvelteComponent<Props, any, any>>, options: { function render<
props: Omit<Props, "$$slots" | "$$events">; Comp extends SvelteComponent<any> | Component<any>,
context?: Map<any, any> | undefined; Props extends ComponentProps<Comp> = ComponentProps<Comp>
}): RenderOutput; >(
...args: {} extends Props
? [
component: Comp extends SvelteComponent<any> ? ComponentType<Comp> : Comp,
options?: { props?: Omit<Props, '$$slots' | '$$events'>; context?: Map<any, any> }
]
: [
component: Comp extends SvelteComponent<any> ? ComponentType<Comp> : Comp,
options: { props: Omit<Props, '$$slots' | '$$events'>; context?: Map<any, any> }
]
): RenderOutput;
interface RenderOutput { interface RenderOutput {
/** HTML that goes into the `<head>` */ /** HTML that goes into the `<head>` */
head: string; head: string;
@ -2126,17 +2182,19 @@ declare module 'svelte/server' {
/** HTML that goes somewhere into the `<body>` */ /** HTML that goes somewhere into the `<body>` */
body: string; body: string;
} }
export { render };
} }
declare module 'svelte/store' { declare module 'svelte/store' {
/** Callback to inform of a value updates. */ /** Callback to inform of a value updates. */
export type Subscriber<T> = (value: T) => void; type Subscriber<T> = (value: T) => void;
/** Unsubscribes from value updates. */ /** Unsubscribes from value updates. */
export type Unsubscriber = () => void; type Unsubscriber = () => void;
/** Callback to update a value. */ /** Callback to update a value. */
export type Updater<T> = (value: T) => T; type Updater<T> = (value: T) => T;
/** /**
* Start and stop notification callbacks. * Start and stop notification callbacks.
@ -2147,13 +2205,13 @@ declare module 'svelte/store' {
* @returns Optionally, a cleanup function that is called when the last remaining * @returns Optionally, a cleanup function that is called when the last remaining
* subscriber unsubscribes. * subscriber unsubscribes.
*/ */
export type StartStopNotifier<T> = ( type StartStopNotifier<T> = (
set: (value: T) => void, set: (value: T) => void,
update: (fn: Updater<T>) => void update: (fn: Updater<T>) => void
) => void | (() => void); ) => void | (() => void);
/** Readable interface for subscribing. */ /** Readable interface for subscribing. */
export interface Readable<T> { interface Readable<T> {
/** /**
* Subscribe on value changes. * Subscribe on value changes.
* @param run subscription callback * @param run subscription callback
@ -2163,7 +2221,7 @@ declare module 'svelte/store' {
} }
/** Writable interface for both updating and subscribing. */ /** Writable interface for both updating and subscribing. */
export interface Writable<T> extends Readable<T> { interface Writable<T> extends Readable<T> {
/** /**
* Set value and inform subscribers. * Set value and inform subscribers.
* @param value to set * @param value to set
@ -2194,49 +2252,49 @@ declare module 'svelte/store' {
* https://svelte.dev/docs/svelte-store#readable * https://svelte.dev/docs/svelte-store#readable
* @param value initial value * @param value initial value
* */ * */
export function readable<T>(value?: T | undefined, start?: StartStopNotifier<T> | undefined): Readable<T>; function readable<T>(value?: T | undefined, start?: StartStopNotifier<T> | undefined): Readable<T>;
export function safe_not_equal(a: any, b: any): boolean;
/** /**
* Create a `Writable` store that allows both updating and reading by subscription. * Create a `Writable` store that allows both updating and reading by subscription.
* *
* https://svelte.dev/docs/svelte-store#writable * https://svelte.dev/docs/svelte-store#writable
* @param value initial value * @param value initial value
* */ * */
export function writable<T>(value?: T | undefined, start?: StartStopNotifier<T> | undefined): Writable<T>; function writable<T>(value?: T | undefined, start?: StartStopNotifier<T> | undefined): Writable<T>;
/** /**
* Derived value store by synchronizing one or more readable stores and * Derived value store by synchronizing one or more readable stores and
* applying an aggregation function over its input values. * applying an aggregation function over its input values.
* *
* https://svelte.dev/docs/svelte-store#derived * https://svelte.dev/docs/svelte-store#derived
* */ * */
export function derived<S extends Stores, T>(stores: S, fn: (values: StoresValues<S>, set: (value: T) => void, update: (fn: Updater<T>) => void) => Unsubscriber | void, initial_value?: T | undefined): Readable<T>; function derived<S extends Stores, T>(stores: S, fn: (values: StoresValues<S>, set: (value: T) => void, update: (fn: Updater<T>) => void) => Unsubscriber | void, initial_value?: T | undefined): Readable<T>;
/** /**
* Derived value store by synchronizing one or more readable stores and * Derived value store by synchronizing one or more readable stores and
* applying an aggregation function over its input values. * applying an aggregation function over its input values.
* *
* https://svelte.dev/docs/svelte-store#derived * https://svelte.dev/docs/svelte-store#derived
* */ * */
export function derived<S extends Stores, T>(stores: S, fn: (values: StoresValues<S>) => T, initial_value?: T | undefined): Readable<T>; function derived<S extends Stores, T>(stores: S, fn: (values: StoresValues<S>) => T, initial_value?: T | undefined): Readable<T>;
/** /**
* Takes a store and returns a new one derived from the old one that is readable. * Takes a store and returns a new one derived from the old one that is readable.
* *
* https://svelte.dev/docs/svelte-store#readonly * https://svelte.dev/docs/svelte-store#readonly
* @param store - store to make readonly * @param store - store to make readonly
* */ * */
export function readonly<T>(store: Readable<T>): Readable<T>; function readonly<T>(store: Readable<T>): Readable<T>;
/** /**
* Get the current value from a store by subscribing and immediately unsubscribing. * Get the current value from a store by subscribing and immediately unsubscribing.
* *
* https://svelte.dev/docs/svelte-store#get * https://svelte.dev/docs/svelte-store#get
* */ * */
export function get<T>(store: Readable<T>): T; function get<T>(store: Readable<T>): T;
export { Subscriber, Unsubscriber, Updater, StartStopNotifier, Readable, Writable, readable, writable, derived, readonly, get };
} }
declare module 'svelte/transition' { declare module 'svelte/transition' {
export type EasingFunction = (t: number) => number; type EasingFunction = (t: number) => number;
export interface TransitionConfig { interface TransitionConfig {
delay?: number; delay?: number;
duration?: number; duration?: number;
easing?: EasingFunction; easing?: EasingFunction;
@ -2244,7 +2302,7 @@ declare module 'svelte/transition' {
tick?: (t: number, u: number) => void; tick?: (t: number, u: number) => void;
} }
export interface BlurParams { interface BlurParams {
delay?: number; delay?: number;
duration?: number; duration?: number;
easing?: EasingFunction; easing?: EasingFunction;
@ -2252,13 +2310,13 @@ declare module 'svelte/transition' {
opacity?: number; opacity?: number;
} }
export interface FadeParams { interface FadeParams {
delay?: number; delay?: number;
duration?: number; duration?: number;
easing?: EasingFunction; easing?: EasingFunction;
} }
export interface FlyParams { interface FlyParams {
delay?: number; delay?: number;
duration?: number; duration?: number;
easing?: EasingFunction; easing?: EasingFunction;
@ -2267,14 +2325,14 @@ declare module 'svelte/transition' {
opacity?: number; opacity?: number;
} }
export interface SlideParams { interface SlideParams {
delay?: number; delay?: number;
duration?: number; duration?: number;
easing?: EasingFunction; easing?: EasingFunction;
axis?: 'x' | 'y'; axis?: 'x' | 'y';
} }
export interface ScaleParams { interface ScaleParams {
delay?: number; delay?: number;
duration?: number; duration?: number;
easing?: EasingFunction; easing?: EasingFunction;
@ -2282,14 +2340,14 @@ declare module 'svelte/transition' {
opacity?: number; opacity?: number;
} }
export interface DrawParams { interface DrawParams {
delay?: number; delay?: number;
speed?: number; speed?: number;
duration?: number | ((len: number) => number); duration?: number | ((len: number) => number);
easing?: EasingFunction; easing?: EasingFunction;
} }
export interface CrossfadeParams { interface CrossfadeParams {
delay?: number; delay?: number;
duration?: number | ((len: number) => number); duration?: number | ((len: number) => number);
easing?: EasingFunction; easing?: EasingFunction;
@ -2299,37 +2357,37 @@ declare module 'svelte/transition' {
* *
* https://svelte.dev/docs/svelte-transition#blur * https://svelte.dev/docs/svelte-transition#blur
* */ * */
export function blur(node: Element, { delay, duration, easing, amount, opacity }?: BlurParams | undefined): TransitionConfig; function blur(node: Element, { delay, duration, easing, amount, opacity }?: BlurParams | undefined): TransitionConfig;
/** /**
* Animates the opacity of an element from 0 to the current opacity for `in` transitions and from the current opacity to 0 for `out` transitions. * Animates the opacity of an element from 0 to the current opacity for `in` transitions and from the current opacity to 0 for `out` transitions.
* *
* https://svelte.dev/docs/svelte-transition#fade * https://svelte.dev/docs/svelte-transition#fade
* */ * */
export function fade(node: Element, { delay, duration, easing }?: FadeParams | undefined): TransitionConfig; function fade(node: Element, { delay, duration, easing }?: FadeParams | undefined): TransitionConfig;
/** /**
* Animates the x and y positions and the opacity of an element. `in` transitions animate from the provided values, passed as parameters to the element's default values. `out` transitions animate from the element's default values to the provided values. * Animates the x and y positions and the opacity of an element. `in` transitions animate from the provided values, passed as parameters to the element's default values. `out` transitions animate from the element's default values to the provided values.
* *
* https://svelte.dev/docs/svelte-transition#fly * https://svelte.dev/docs/svelte-transition#fly
* */ * */
export function fly(node: Element, { delay, duration, easing, x, y, opacity }?: FlyParams | undefined): TransitionConfig; function fly(node: Element, { delay, duration, easing, x, y, opacity }?: FlyParams | undefined): TransitionConfig;
/** /**
* Slides an element in and out. * Slides an element in and out.
* *
* https://svelte.dev/docs/svelte-transition#slide * https://svelte.dev/docs/svelte-transition#slide
* */ * */
export function slide(node: Element, { delay, duration, easing, axis }?: SlideParams | undefined): TransitionConfig; function slide(node: Element, { delay, duration, easing, axis }?: SlideParams | undefined): TransitionConfig;
/** /**
* Animates the opacity and scale of an element. `in` transitions animate from an element's current (default) values to the provided values, passed as parameters. `out` transitions animate from the provided values to an element's default values. * Animates the opacity and scale of an element. `in` transitions animate from an element's current (default) values to the provided values, passed as parameters. `out` transitions animate from the provided values to an element's default values.
* *
* https://svelte.dev/docs/svelte-transition#scale * https://svelte.dev/docs/svelte-transition#scale
* */ * */
export function scale(node: Element, { delay, duration, easing, start, opacity }?: ScaleParams | undefined): TransitionConfig; function scale(node: Element, { delay, duration, easing, start, opacity }?: ScaleParams | undefined): TransitionConfig;
/** /**
* Animates the stroke of an SVG element, like a snake in a tube. `in` transitions begin with the path invisible and draw the path to the screen over time. `out` transitions start in a visible state and gradually erase the path. `draw` only works with elements that have a `getTotalLength` method, like `<path>` and `<polyline>`. * Animates the stroke of an SVG element, like a snake in a tube. `in` transitions begin with the path invisible and draw the path to the screen over time. `out` transitions start in a visible state and gradually erase the path. `draw` only works with elements that have a `getTotalLength` method, like `<path>` and `<polyline>`.
* *
* https://svelte.dev/docs/svelte-transition#draw * https://svelte.dev/docs/svelte-transition#draw
* */ * */
export function draw(node: SVGElement & { function draw(node: SVGElement & {
getTotalLength(): number; getTotalLength(): number;
}, { delay, speed, duration, easing }?: DrawParams | undefined): TransitionConfig; }, { delay, speed, duration, easing }?: DrawParams | undefined): TransitionConfig;
/** /**
@ -2337,13 +2395,15 @@ declare module 'svelte/transition' {
* *
* https://svelte.dev/docs/svelte-transition#crossfade * https://svelte.dev/docs/svelte-transition#crossfade
* */ * */
export function crossfade({ fallback, ...defaults }: CrossfadeParams & { function crossfade({ fallback, ...defaults }: CrossfadeParams & {
fallback?: (node: Element, params: CrossfadeParams, intro: boolean) => TransitionConfig; fallback?: (node: Element, params: CrossfadeParams, intro: boolean) => TransitionConfig;
}): [(node: any, params: CrossfadeParams & { }): [(node: any, params: CrossfadeParams & {
key: any; key: any;
}) => () => TransitionConfig, (node: any, params: CrossfadeParams & { }) => () => TransitionConfig, (node: any, params: CrossfadeParams & {
key: any; key: any;
}) => () => TransitionConfig]; }) => () => TransitionConfig];
export { EasingFunction, TransitionConfig, BlurParams, FadeParams, FlyParams, SlideParams, ScaleParams, DrawParams, CrossfadeParams, blur, fade, fly, slide, scale, draw, crossfade };
} }
declare module 'svelte/events' { declare module 'svelte/events' {
@ -2353,27 +2413,29 @@ declare module 'svelte/events' {
* (with attributes like `onclick`), which use event delegation for performance reasons * (with attributes like `onclick`), which use event delegation for performance reasons
* *
* */ * */
export function on<Element extends HTMLElement, Type extends keyof HTMLElementEventMap>(element: Element, type: Type, handler: (this: Element, event: HTMLElementEventMap[Type]) => any, options?: AddEventListenerOptions | undefined): () => void; function on<Element extends HTMLElement, Type extends keyof HTMLElementEventMap>(element: Element, type: Type, handler: (this: Element, event: HTMLElementEventMap[Type]) => any, options?: AddEventListenerOptions | undefined): () => void;
/** /**
* Attaches an event handler to an element and returns a function that removes the handler. Using this * Attaches an event handler to an element and returns a function that removes the handler. Using this
* rather than `addEventListener` will preserve the correct order relative to handlers added declaratively * rather than `addEventListener` will preserve the correct order relative to handlers added declaratively
* (with attributes like `onclick`), which use event delegation for performance reasons * (with attributes like `onclick`), which use event delegation for performance reasons
* *
* */ * */
export function on(element: EventTarget, type: string, handler: EventListener, options?: AddEventListenerOptions | undefined): () => void; function on(element: EventTarget, type: string, handler: EventListener, options?: AddEventListenerOptions | undefined): () => void;
export { on };
} }
declare module 'svelte/types/compiler/preprocess' { declare module 'svelte/types/compiler/preprocess' {
/** @deprecated import this from 'svelte/preprocess' instead */ /** @deprecated import this from 'svelte/preprocess' instead */
export type MarkupPreprocessor = MarkupPreprocessor_1; type MarkupPreprocessor = MarkupPreprocessor_1;
/** @deprecated import this from 'svelte/preprocess' instead */ /** @deprecated import this from 'svelte/preprocess' instead */
export type Preprocessor = Preprocessor_1; type Preprocessor = Preprocessor_1;
/** @deprecated import this from 'svelte/preprocess' instead */ /** @deprecated import this from 'svelte/preprocess' instead */
export type PreprocessorGroup = PreprocessorGroup_1; type PreprocessorGroup = PreprocessorGroup_1;
/** @deprecated import this from 'svelte/preprocess' instead */ /** @deprecated import this from 'svelte/preprocess' instead */
export type Processed = Processed_1; type Processed = Processed_1;
/** @deprecated import this from 'svelte/preprocess' instead */ /** @deprecated import this from 'svelte/preprocess' instead */
export type SveltePreprocessor<PreprocessorType extends keyof PreprocessorGroup_1, Options = any> = SveltePreprocessor_1< type SveltePreprocessor<PreprocessorType extends keyof PreprocessorGroup_1, Options = any> = SveltePreprocessor_1<
PreprocessorType, PreprocessorType,
Options Options
>; >;
@ -2457,14 +2519,16 @@ declare module 'svelte/types/compiler/preprocess' {
> { > {
(options?: Options): Required<Pick<PreprocessorGroup_1, PreprocessorType>>; (options?: Options): Required<Pick<PreprocessorGroup_1, PreprocessorType>>;
} }
export { MarkupPreprocessor, Preprocessor, PreprocessorGroup, Processed, SveltePreprocessor };
} }
declare module 'svelte/types/compiler/interfaces' { declare module 'svelte/types/compiler/interfaces' {
import type { Location } from 'locate-character'; import type { Location } from 'locate-character';
/** @deprecated import this from 'svelte' instead */ /** @deprecated import this from 'svelte' instead */
export type CompileOptions = CompileOptions_1; type CompileOptions = CompileOptions_1;
/** @deprecated import this from 'svelte' instead */ /** @deprecated import this from 'svelte' instead */
export type Warning = Warning_1; type Warning = Warning_1;
interface Warning_1 { interface Warning_1 {
start?: Location; start?: Location;
end?: Location; end?: Location;
@ -2635,6 +2699,8 @@ declare module 'svelte/types/compiler/interfaces' {
* (also see https://github.com/sveltejs/svelte/pull/5652) * (also see https://github.com/sveltejs/svelte/pull/5652)
*/ */
type Namespace = 'html' | 'svg' | 'mathml' | 'foreign'; type Namespace = 'html' | 'svg' | 'mathml' | 'foreign';
export { CompileOptions, Warning };
}declare module '*.svelte' { }declare module '*.svelte' {
// use prettier-ignore for a while because of https://github.com/sveltejs/language-tools/commit/026111228b5814a9109cc4d779d37fb02955fb8b // use prettier-ignore for a while because of https://github.com/sveltejs/language-tools/commit/026111228b5814a9109cc4d779d37fb02955fb8b
// prettier-ignore // prettier-ignore

@ -13,8 +13,8 @@
}, },
"devDependencies": { "devDependencies": {
"@sveltejs/vite-plugin-svelte": "^3.1.0", "@sveltejs/vite-plugin-svelte": "^3.1.0",
"express": "^4.19.2",
"nodemon": "^3.0.3", "nodemon": "^3.0.3",
"polka": "^1.0.0-next.25",
"svelte": "workspace:*", "svelte": "workspace:*",
"vite": "^5.0.13", "vite": "^5.0.13",
"vite-plugin-inspect": "^0.8.4" "vite-plugin-inspect": "^0.8.4"

@ -1,7 +1,7 @@
import fs from 'node:fs'; import fs from 'node:fs';
import path from 'node:path'; import path from 'node:path';
import { fileURLToPath } from 'node:url'; import { fileURLToPath } from 'node:url';
import express from 'express'; import polka from 'polka';
import { createServer as createViteServer } from 'vite'; import { createServer as createViteServer } from 'vite';
const PORT = process.env.PORT || '5173'; const PORT = process.env.PORT || '5173';
@ -11,7 +11,7 @@ const __dirname = path.dirname(fileURLToPath(import.meta.url));
process.env.NODE_ENV = 'development'; process.env.NODE_ENV = 'development';
async function createServer() { async function createServer() {
const app = express(); const app = polka();
const vite = await createViteServer({ const vite = await createViteServer({
server: { middlewareMode: true }, server: { middlewareMode: true },

@ -1,7 +1,5 @@
// @ts-ignore
import { render } from 'svelte/server'; import { render } from 'svelte/server';
// @ts-ignore you need to create this file // @ts-ignore you need to create this file
import App from './App.svelte'; import App from './App.svelte';
// @ts-ignore export const { head, body } = render(App);
export const { head, body, css } = render(App, { props: { initialCount: 0 } });

File diff suppressed because it is too large Load Diff

@ -21,7 +21,6 @@
"@sveltejs/kit": "^2.5.0", "@sveltejs/kit": "^2.5.0",
"@sveltejs/site-kit": "6.0.0-next.64", "@sveltejs/site-kit": "6.0.0-next.64",
"@sveltejs/vite-plugin-svelte": "^3.1.0", "@sveltejs/vite-plugin-svelte": "^3.1.0",
"@types/marked": "^6.0.0",
"@vercel/speed-insights": "^1.0.0", "@vercel/speed-insights": "^1.0.0",
"esrap": "^1.2.2", "esrap": "^1.2.2",
"marked": "^9.0.0", "marked": "^9.0.0",
@ -31,7 +30,7 @@
"svelte": "workspace:^", "svelte": "workspace:^",
"svelte-check": "^3.6.3", "svelte-check": "^3.6.3",
"tslib": "^2.6.2", "tslib": "^2.6.2",
"typescript": "^5.3.3", "typescript": "^5.5.2",
"vite": "^5.0.13" "vite": "^5.0.13"
}, },
"dependencies": { "dependencies": {

@ -54,7 +54,7 @@
"svelte-check": "^3.6.3", "svelte-check": "^3.6.3",
"svelte-preprocess": "^5.1.3", "svelte-preprocess": "^5.1.3",
"tiny-glob": "^0.2.9", "tiny-glob": "^0.2.9",
"typescript": "^5.3.3", "typescript": "^5.5.2",
"vite": "^5.0.13", "vite": "^5.0.13",
"vite-imagetools": "^6.2.9" "vite-imagetools": "^6.2.9"
} }

@ -3,21 +3,24 @@ import { browserslistToTargets } from 'lightningcss';
import { readFile } from 'node:fs/promises'; import { readFile } from 'node:fs/promises';
import browserslist from 'browserslist'; import browserslist from 'browserslist';
/** @type {import('vite').PluginOption[]} */
const plugins = [raw(['.ttf']), sveltekit()]; const plugins = [raw(['.ttf']), sveltekit()];
// Only enable sharp if we're not in a webcontainer env // Only enable sharp if we're not in a webcontainer env
if (!process.versions.webcontainer) { if (!process.versions.webcontainer) {
plugins.push( const { imagetools } = await import('vite-imagetools');
(await import('vite-imagetools')).imagetools({
defaultDirectives: (url) => { const plugin = imagetools({
if (url.searchParams.has('big-image')) { defaultDirectives: (url) => {
return new URLSearchParams('w=640;1280;2560;3840&format=avif;webp;png&as=picture'); if (url.searchParams.has('big-image')) {
} return new URLSearchParams('w=640;1280;2560;3840&format=avif;webp;png&as=picture');
return new URLSearchParams();
} }
})
); return new URLSearchParams();
}
});
plugins.push(/** @type {import('vite').PluginOption} */ (/** @type {unknown} */ (plugin)));
} }
/** /**

Loading…
Cancel
Save