diff --git a/.changeset/gorgeous-hats-wonder.md b/.changeset/gorgeous-hats-wonder.md new file mode 100644 index 0000000000..e1ce7bdaed --- /dev/null +++ b/.changeset/gorgeous-hats-wonder.md @@ -0,0 +1,5 @@ +--- +"svelte": patch +--- + +fix: ensure element size bindings don't unsubscribe multiple times from the resize observer diff --git a/.changeset/hot-rivers-punch.md b/.changeset/hot-rivers-punch.md new file mode 100644 index 0000000000..4669e6d3c9 --- /dev/null +++ b/.changeset/hot-rivers-punch.md @@ -0,0 +1,5 @@ +--- +"svelte": patch +--- + +fix: prevent misidentification of bindings as delegatable event handlers if used outside event attribute diff --git a/.changeset/moody-toys-relax.md b/.changeset/moody-toys-relax.md new file mode 100644 index 0000000000..236fa089c4 --- /dev/null +++ b/.changeset/moody-toys-relax.md @@ -0,0 +1,5 @@ +--- +"svelte": patch +--- + +fix: preserve current input values when removing defaults diff --git a/.changeset/pre.json b/.changeset/pre.json index 269df98439..faff6a413b 100644 --- a/.changeset/pre.json +++ b/.changeset/pre.json @@ -205,6 +205,7 @@ "good-plums-type", "good-rivers-yawn", "good-roses-argue", + "gorgeous-hats-wonder", "gorgeous-monkeys-carry", "gorgeous-singers-rest", "great-fans-unite", @@ -234,6 +235,7 @@ "honest-pans-kick", "hot-cooks-repair", "hot-jobs-tap", + "hot-rivers-punch", "hot-sloths-clap", "hungry-boxes-relate", "hungry-dots-fry", @@ -326,6 +328,7 @@ "moody-houses-argue", "moody-owls-cry", "moody-sheep-type", + "moody-toys-relax", "nasty-glasses-begin", "nasty-lions-double", "nasty-yaks-peel", @@ -526,6 +529,7 @@ "strong-lemons-provide", "strong-pans-doubt", "stupid-parents-crash", + "sweet-bottles-check", "sweet-mangos-beg", "sweet-pens-sniff", "swift-donkeys-perform", @@ -608,6 +612,7 @@ "weak-drinks-speak", "weak-frogs-bow", "weak-terms-destroy", + "wet-bats-exercise", "wet-games-fly", "wet-pears-remain", "wet-wombats-repeat", diff --git a/.changeset/sweet-bottles-check.md b/.changeset/sweet-bottles-check.md new file mode 100644 index 0000000000..fafab0f70f --- /dev/null +++ b/.changeset/sweet-bottles-check.md @@ -0,0 +1,5 @@ +--- +"svelte": patch +--- + +chore: improve runtime performance of capturing reactive signals diff --git a/.changeset/twenty-gifts-develop.md b/.changeset/twenty-gifts-develop.md new file mode 100644 index 0000000000..f610d9647f --- /dev/null +++ b/.changeset/twenty-gifts-develop.md @@ -0,0 +1,5 @@ +--- +"svelte": patch +--- + +fix: remove document event listeners on unmount diff --git a/.changeset/wet-bats-exercise.md b/.changeset/wet-bats-exercise.md new file mode 100644 index 0000000000..550ad83375 --- /dev/null +++ b/.changeset/wet-bats-exercise.md @@ -0,0 +1,5 @@ +--- +"svelte": patch +--- + +fix: preserve component function context for nested components diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 572de29b85..d3c8b3955c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -60,3 +60,17 @@ jobs: - name: build and check generated types if: (${{ success() }} || ${{ failure() }}) # ensures this step runs even if previous steps fail run: pnpm build && { [ "`git status --porcelain=v1`" == "" ] || (echo "Generated types have changed — please regenerate types locally and commit the changes after you have reviewed them"; git diff; exit 1); } + Benchmarks: + runs-on: ubuntu-latest + timeout-minutes: 15 + steps: + - uses: actions/checkout@v3 + - uses: pnpm/action-setup@v4 + - uses: actions/setup-node@v3 + with: + node-version: 18 + cache: pnpm + - run: pnpm install --frozen-lockfile + - run: pnpm bench + env: + CI: true diff --git a/.gitignore b/.gitignore index 4ce5c243c7..d503437664 100644 --- a/.gitignore +++ b/.gitignore @@ -22,3 +22,5 @@ coverage .DS_Store tmp + +benchmarking/compare/.results diff --git a/benchmarking/benchmarks.js b/benchmarking/benchmarks.js new file mode 100644 index 0000000000..283112dbe3 --- /dev/null +++ b/benchmarking/benchmarks.js @@ -0,0 +1,24 @@ +import { kairo_avoidable } from './benchmarks/kairo/kairo_avoidable.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) +// Not all tests are the same, and many parts have been tweaked to capture different data. + +export const benchmarks = [ + kairo_avoidable, + kairo_broad, + kairo_deep, + kairo_diamond, + kairo_triangle, + kairo_mux, + kairo_repeated, + kairo_unstable, + mol_bench +]; diff --git a/benchmarking/benchmarks/kairo/kairo_avoidable.js b/benchmarking/benchmarks/kairo/kairo_avoidable.js new file mode 100644 index 0000000000..636e96ccce --- /dev/null +++ b/benchmarking/benchmarks/kairo/kairo_avoidable.js @@ -0,0 +1,60 @@ +import { assert, fastest_test } from '../../utils.js'; +import * as $ from '../../../packages/svelte/src/internal/client/index.js'; +import { busy } from './util.js'; + +function setup() { + let head = $.source(0); + let computed1 = $.derived(() => $.get(head)); + let computed2 = $.derived(() => ($.get(computed1), 0)); + let computed3 = $.derived(() => (busy(), $.get(computed2) + 1)); // heavy computation + let computed4 = $.derived(() => $.get(computed3) + 2); + let computed5 = $.derived(() => $.get(computed4) + 3); + + const destroy = $.effect_root(() => { + $.render_effect(() => { + $.get(computed5); + busy(); // heavy side effect + }); + }); + + return { + destroy, + run() { + $.flush_sync(() => { + $.set(head, 1); + }); + assert($.get(computed5) === 6); + for (let i = 0; i < 1000; i++) { + $.flush_sync(() => { + $.set(head, i); + }); + assert($.get(computed5) === 6); + } + } + }; +} + +export async function kairo_avoidable() { + // Do 10 loops to warm up JIT + for (let i = 0; i < 10; i++) { + const { run, destroy } = setup(); + run(); + destroy(); + } + + const { run, destroy } = setup(); + + const { timing } = await fastest_test(10, () => { + for (let i = 0; i < 100; i++) { + run(); + } + }); + + destroy(); + + return { + benchmark: 'kairo_avoidable', + time: timing.time.toFixed(2), + gc_time: timing.gc_time.toFixed(2) + }; +} diff --git a/benchmarking/benchmarks/kairo/kairo_broad.js b/benchmarking/benchmarks/kairo/kairo_broad.js new file mode 100644 index 0000000000..682154f0e3 --- /dev/null +++ b/benchmarking/benchmarks/kairo/kairo_broad.js @@ -0,0 +1,66 @@ +import { assert, fastest_test } from '../../utils.js'; +import * as $ from '../../../packages/svelte/src/internal/client/index.js'; + +function setup() { + let head = $.source(0); + let last = head; + let counter = 0; + + const destroy = $.effect_root(() => { + for (let i = 0; i < 50; i++) { + let current = $.derived(() => { + return $.get(head) + i; + }); + let current2 = $.derived(() => { + return $.get(current) + 1; + }); + $.render_effect(() => { + $.get(current2); + counter++; + }); + last = current2; + } + }); + + return { + destroy, + run() { + $.flush_sync(() => { + $.set(head, 1); + }); + counter = 0 + for (let i = 0; i < 50; i++) { + $.flush_sync(() => { + $.set(head, i); + }); + assert($.get(last) === i + 50); + } + assert(counter === 50 * 50); + } + }; +} + +export async function kairo_broad() { + // Do 10 loops to warm up JIT + for (let i = 0; i < 10; i++) { + const { run, destroy } = setup(); + run(); + destroy(); + } + + const { run, destroy } = setup(); + + const { timing } = await fastest_test(10, () => { + for (let i = 0; i < 100; i++) { + run(); + } + }); + + destroy(); + + return { + benchmark: 'kairo_broad', + time: timing.time.toFixed(2), + gc_time: timing.gc_time.toFixed(2) + }; +} diff --git a/benchmarking/benchmarks/kairo/kairo_deep.js b/benchmarking/benchmarks/kairo/kairo_deep.js new file mode 100644 index 0000000000..af985c3e43 --- /dev/null +++ b/benchmarking/benchmarks/kairo/kairo_deep.js @@ -0,0 +1,66 @@ +import { assert, fastest_test } from '../../utils.js'; +import * as $ from '../../../packages/svelte/src/internal/client/index.js'; + +let len = 50; +const iter = 50; + +function setup() { + let head = $.source(0); + let current = head; + for (let i = 0; i < len; i++) { + let c = current; + current = $.derived(() => { + return $.get(c) + 1; + }); + } + let counter = 0; + + const destroy = $.effect_root(() => { + $.render_effect(() => { + $.get(current); + counter++; + }); + }); + + return { + destroy, + run() { + $.flush_sync(() => { + $.set(head, 1); + }); + counter = 0 + for (let i = 0; i < iter; i++) { + $.flush_sync(() => { + $.set(head, i); + }); + assert($.get(current) === len + i); + } + assert(counter === iter); + } + }; +} + +export async function kairo_deep() { + // Do 10 loops to warm up JIT + for (let i = 0; i < 10; i++) { + const { run, destroy } = setup(); + run(); + destroy(); + } + + const { run, destroy } = setup(); + + const { timing } = await fastest_test(10, () => { + for (let i = 0; i < 100; i++) { + run(); + } + }); + + destroy(); + + return { + benchmark: 'kairo_deep', + time: timing.time.toFixed(2), + gc_time: timing.gc_time.toFixed(2) + }; +} diff --git a/benchmarking/benchmarks/kairo/kairo_diamond.js b/benchmarking/benchmarks/kairo/kairo_diamond.js new file mode 100644 index 0000000000..879727c99e --- /dev/null +++ b/benchmarking/benchmarks/kairo/kairo_diamond.js @@ -0,0 +1,70 @@ +import { assert, fastest_test } from '../../utils.js'; +import * as $ from '../../../packages/svelte/src/internal/client/index.js'; + +let width = 5; + +function setup() { + let head = $.source(0); + let current = []; + for (let i = 0; i < width; i++) { + current.push( + $.derived(() => { + return $.get(head) + 1; + }) + ); + } + let sum = $.derived(() => { + return current.map((x) => $.get(x)).reduce((a, b) => a + b, 0); + }); + let counter = 0; + + const destroy = $.effect_root(() => { + $.render_effect(() => { + $.get(sum); + counter++; + }); + }); + + return { + destroy, + run() { + $.flush_sync(() => { + $.set(head, 1); + }); + assert($.get(sum) === 2 * width); + counter = 0; + for (let i = 0; i < 500; i++) { + $.flush_sync(() => { + $.set(head, i); + }); + assert($.get(sum) === (i + 1) * width); + } + assert(counter === 500); + } + }; +} + +export async function kairo_diamond() { + // Do 10 loops to warm up JIT + for (let i = 0; i < 10; i++) { + const { run, destroy } = setup(); + run(); + destroy(); + } + + const { run, destroy } = setup(); + + const { timing } = await fastest_test(10, () => { + for (let i = 0; i < 100; i++) { + run(); + } + }); + + destroy(); + + return { + benchmark: 'kairo_diamond', + time: timing.time.toFixed(2), + gc_time: timing.gc_time.toFixed(2) + }; +} diff --git a/benchmarking/benchmarks/kairo/kairo_mux.js b/benchmarking/benchmarks/kairo/kairo_mux.js new file mode 100644 index 0000000000..d2867f499b --- /dev/null +++ b/benchmarking/benchmarks/kairo/kairo_mux.js @@ -0,0 +1,63 @@ +import { assert, fastest_test } from '../../utils.js'; +import * as $ from '../../../packages/svelte/src/internal/client/index.js'; + +function setup() { + let heads = new Array(100).fill(null).map((_) => $.source(0)); + const mux = $.derived(() => { + return Object.fromEntries(heads.map((h) => $.get(h)).entries()); + }); + const splited = heads + .map((_, index) => $.derived(() => $.get(mux)[index])) + .map((x) => $.derived(() => $.get(x) + 1)); + + const destroy = $.effect_root(() => { + splited.forEach((x) => { + $.render_effect(() => { + $.get(x); + }); + }); + }); + + return { + destroy, + run() { + for (let i = 0; i < 10; i++) { + $.flush_sync(() => { + $.set(heads[i], i); + }); + assert($.get(splited[i]) === i + 1); + } + for (let i = 0; i < 10; i++) { + $.flush_sync(() => { + $.set(heads[i], i * 2); + }); + assert($.get(splited[i]) === i * 2 + 1); + } + } + }; +} + +export async function kairo_mux() { + // Do 10 loops to warm up JIT + for (let i = 0; i < 10; i++) { + const { run, destroy } = setup(); + run(); + destroy(); + } + + const { run, destroy } = setup(); + + const { timing } = await fastest_test(10, () => { + for (let i = 0; i < 100; i++) { + run(); + } + }); + + destroy(); + + return { + benchmark: 'kairo_mux', + time: timing.time.toFixed(2), + gc_time: timing.gc_time.toFixed(2) + }; +} diff --git a/benchmarking/benchmarks/kairo/kairo_repeated.js b/benchmarking/benchmarks/kairo/kairo_repeated.js new file mode 100644 index 0000000000..fd22c1e563 --- /dev/null +++ b/benchmarking/benchmarks/kairo/kairo_repeated.js @@ -0,0 +1,67 @@ +import { assert, fastest_test } from '../../utils.js'; +import * as $ from '../../../packages/svelte/src/internal/client/index.js'; + +let size = 30; + +function setup() { + let head = $.source(0); + let current = $.derived(() => { + let result = 0; + for (let i = 0; i < size; i++) { + result += $.get(head); + } + return result; + }); + + let counter = 0; + + const destroy = $.effect_root(() => { + $.render_effect(() => { + $.get(current); + counter++; + }); + }); + + return { + destroy, + run() { + $.flush_sync(() => { + $.set(head, 1); + }); + assert($.get(current) === size); + counter = 0; + for (let i = 0; i < 100; i++) { + $.flush_sync(() => { + $.set(head, i); + }); + assert($.get(current) === i * size); + } + assert(counter === 100); + } + }; +} + +export async function kairo_repeated() { + // Do 10 loops to warm up JIT + for (let i = 0; i < 10; i++) { + const { run, destroy } = setup(); + run(); + destroy(); + } + + const { run, destroy } = setup(); + + const { timing } = await fastest_test(10, () => { + for (let i = 0; i < 100; i++) { + run(); + } + }); + + destroy(); + + return { + benchmark: 'kairo_repeated', + time: timing.time.toFixed(2), + gc_time: timing.gc_time.toFixed(2) + }; +} diff --git a/benchmarking/benchmarks/kairo/kairo_triangle.js b/benchmarking/benchmarks/kairo/kairo_triangle.js new file mode 100644 index 0000000000..4e8afe1b82 --- /dev/null +++ b/benchmarking/benchmarks/kairo/kairo_triangle.js @@ -0,0 +1,80 @@ +import { assert, fastest_test } from '../../utils.js'; +import * as $ from '../../../packages/svelte/src/internal/client/index.js'; + +let width = 10; + +function count(number) { + return new Array(number) + .fill(0) + .map((_, i) => i + 1) + .reduce((x, y) => x + y, 0); +} + +function setup() { + let head = $.source(0); + let current = head; + let list = []; + for (let i = 0; i < width; i++) { + let c = current; + list.push(current); + current = $.derived(() => { + return $.get(c) + 1; + }); + } + let sum = $.derived(() => { + return list.map((x) => $.get(x)).reduce((a, b) => a + b, 0); + }); + + let counter = 0; + + const destroy = $.effect_root(() => { + $.render_effect(() => { + $.get(sum); + counter++; + }); + }); + + return { + destroy, + run() { + const constant = count(width); + $.flush_sync(() => { + $.set(head, 1); + }); + assert($.get(sum) === constant); + counter = 0; + for (let i = 0; i < 100; i++) { + $.flush_sync(() => { + $.set(head, i); + }); + assert($.get(sum) === constant - width + i * width); + } + assert(counter === 100); + } + }; +} + +export async function kairo_triangle() { + // Do 10 loops to warm up JIT + for (let i = 0; i < 10; i++) { + const { run, destroy } = setup(); + run(); + destroy(); + } + + const { run, destroy } = setup(); + + const { timing } = await fastest_test(10, () => { + for (let i = 0; i < 100; i++) { + run(); + } + }); + + destroy(); + + return { + benchmark: 'kairo_triangle', + time: timing.time.toFixed(2), + gc_time: timing.gc_time.toFixed(2) + }; +} diff --git a/benchmarking/benchmarks/kairo/kairo_unstable.js b/benchmarking/benchmarks/kairo/kairo_unstable.js new file mode 100644 index 0000000000..0185d12868 --- /dev/null +++ b/benchmarking/benchmarks/kairo/kairo_unstable.js @@ -0,0 +1,66 @@ +import { assert, fastest_test } from '../../utils.js'; +import * as $ from '../../../packages/svelte/src/internal/client/index.js'; + +function setup() { + let head = $.source(0); + const double = $.derived(() => $.get(head) * 2); + const inverse = $.derived(() => -$.get(head)); + let current = $.derived(() => { + let result = 0; + for (let i = 0; i < 20; i++) { + result += $.get(head) % 2 ? $.get(double) : $.get(inverse); + } + return result; + }); + + let counter = 0; + + const destroy = $.effect_root(() => { + $.render_effect(() => { + $.get(current); + counter++; + }); + }); + + return { + destroy, + run() { + $.flush_sync(() => { + $.set(head, 1); + }); + assert($.get(current) === 40); + counter = 0; + for (let i = 0; i < 100; i++) { + $.flush_sync(() => { + $.set(head, i); + }); + } + assert(counter === 100); + } + }; +} + +export async function kairo_unstable() { + // Do 10 loops to warm up JIT + for (let i = 0; i < 10; i++) { + const { run, destroy } = setup(); + run(); + destroy(); + } + + const { run, destroy } = setup(); + + const { timing } = await fastest_test(10, () => { + for (let i = 0; i < 100; i++) { + run(); + } + }); + + destroy(); + + return { + benchmark: 'kairo_unstable', + time: timing.time.toFixed(2), + gc_time: timing.gc_time.toFixed(2) + }; +} diff --git a/benchmarking/benchmarks/kairo/util.js b/benchmarking/benchmarks/kairo/util.js new file mode 100644 index 0000000000..5c73e99eec --- /dev/null +++ b/benchmarking/benchmarks/kairo/util.js @@ -0,0 +1,7 @@ + +export function busy() { + let a = 0; + for (let i = 0; i < 1_00; i++) { + a++; + } +} diff --git a/benchmarking/benchmarks/mol_bench.js b/benchmarking/benchmarks/mol_bench.js new file mode 100644 index 0000000000..7fa49d7df4 --- /dev/null +++ b/benchmarking/benchmarks/mol_bench.js @@ -0,0 +1,90 @@ +import { assert, fastest_test } from '../utils.js'; +import * as $ from '../../packages/svelte/src/internal/client/index.js'; + +/** + * @param {number} n + */ +function fib(n) { + if (n < 2) return 1; + return fib(n - 1) + fib(n - 2); +} + +/** + * @param {number} n + */ +function hard(n) { + return n + fib(16); +} + +const numbers = Array.from({ length: 5 }, (_, i) => i); + +function setup() { + let res = []; + const A = $.source(0); + const B = $.source(0); + const C = $.derived(() => ($.get(A) % 2) + ($.get(B) % 2)); + const D = $.derived(() => numbers.map((i) => i + ($.get(A) % 2) - ($.get(B) % 2))); + D.equals = function (/** @type {number[]} */ l) { + var r = this.v; + return r !== null && l.length === r.length && l.every((v, i) => v === r[i]); + }; + const E = $.derived(() => hard($.get(C) + $.get(A) + $.get(D)[0])); + const F = $.derived(() => hard($.get(D)[0] && $.get(B))); + const G = $.derived(() => $.get(C) + ($.get(C) || $.get(E) % 2) + $.get(D)[0] + $.get(F)); + + const destroy = $.effect_root(() => { + $.render_effect(() => { + res.push(hard($.get(G))); + }); + $.render_effect(() => { + res.push($.get(G)); + }); + $.render_effect(() => { + res.push(hard($.get(F))); + }); + }); + + return { + destroy, + /** + * @param {number} i + */ + run(i) { + res.length = 0; + $.flush_sync(() => { + $.set(B, 1); + $.set(A, 1 + i * 2); + }); + $.flush_sync(() => { + $.set(A, 2 + i * 2); + $.set(B, 2); + }); + assert(res[0] === 3198 && res[1] === 1601 && res[2] === 3195 && res[3] === 1598); + } + }; +} + +export async function mol_bench() { + // Do 10 loops to warm up JIT + for (let i = 0; i < 10; i++) { + const { run, destroy } = setup(); + run(0); + destroy(); + } + + const { run, destroy } = setup(); + + const { timing } = await fastest_test(10, () => { + for (let i = 0; i < 1e4; i++) { + run(i); + } + }); + + destroy(); + + return { + benchmark: 'mol_bench', + time: timing.time.toFixed(2), + gc_time: timing.gc_time.toFixed(2) + }; +} diff --git a/benchmarking/compare/index.js b/benchmarking/compare/index.js new file mode 100644 index 0000000000..a5fc6d10a9 --- /dev/null +++ b/benchmarking/compare/index.js @@ -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(); +} diff --git a/benchmarking/compare/runner.js b/benchmarking/compare/runner.js new file mode 100644 index 0000000000..6fa58e2bac --- /dev/null +++ b/benchmarking/compare/runner.js @@ -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); diff --git a/benchmarking/run.js b/benchmarking/run.js new file mode 100644 index 0000000000..35a6ac307f --- /dev/null +++ b/benchmarking/run.js @@ -0,0 +1,32 @@ +import * as $ from '../packages/svelte/src/internal/client/index.js'; +import { benchmarks } from './benchmarks.js'; + +let total_time = 0; +let total_gc_time = 0; + +// eslint-disable-next-line no-console +console.log('-- Benchmarking Started --'); +$.push({}, true); +try { + for (const benchmark of benchmarks) { + const results = await benchmark(); + // eslint-disable-next-line no-console + console.log(results); + total_time += Number(results.time); + total_gc_time += Number(results.gc_time); + } +} catch (e) { + // eslint-disable-next-line no-console + console.error('-- Benchmarking Failed --'); + // eslint-disable-next-line no-console + console.error(e); + process.exit(1); +} +$.pop(); +// 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) +}); diff --git a/benchmarking/tsconfig.json b/benchmarking/tsconfig.json new file mode 100644 index 0000000000..81fe19744a --- /dev/null +++ b/benchmarking/tsconfig.json @@ -0,0 +1,17 @@ +{ + "compilerOptions": { + "moduleResolution": "Bundler", + "target": "ESNext", + "module": "ESNext", + "verbatimModuleSyntax": true, + "isolatedModules": true, + "resolveJsonModule": true, + "sourceMap": true, + "esModuleInterop": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "allowJs": true, + "checkJs": true + }, + "include": ["./run.js", "./utils.js", "./benchmarks"] +} diff --git a/benchmarking/utils.js b/benchmarking/utils.js new file mode 100644 index 0000000000..db2fa753ee --- /dev/null +++ b/benchmarking/utils.js @@ -0,0 +1,98 @@ +import { performance, PerformanceObserver } from 'node:perf_hooks'; +import v8 from 'v8-natives'; + +// Credit to https://github.com/milomg/js-reactivity-benchmark for the logic for timing + GC tracking. + +class GarbageTrack { + track_id = 0; + observer = new PerformanceObserver((list) => this.perf_entries.push(...list.getEntries())); + perf_entries = []; + periods = []; + + watch(fn) { + this.track_id++; + const start = performance.now(); + const result = fn(); + const end = performance.now(); + this.periods.push({ track_id: this.track_id, start, end }); + + return { result, track_id: this.track_id }; + } + + /** + * @param {number} track_id + */ + async gcDuration(track_id) { + await promise_delay(10); + + const period = this.periods.find((period) => period.track_id === track_id); + if (!period) { + // eslint-disable-next-line @typescript-eslint/prefer-promise-reject-errors + return Promise.reject('no period found'); + } + + const entries = this.perf_entries.filter( + (e) => e.startTime >= period.start && e.startTime < period.end + ); + return entries.reduce((t, e) => e.duration + t, 0); + } + + destroy() { + this.observer.disconnect(); + } + + constructor() { + this.observer.observe({ entryTypes: ['gc'] }); + } +} + +function promise_delay(timeout = 0) { + return new Promise((resolve) => setTimeout(resolve, timeout)); +} + +/** + * @param {{ (): void; (): any; }} fn + */ +function run_timed(fn) { + const start = performance.now(); + const result = fn(); + const time = performance.now() - start; + return { result, time }; +} + +/** + * @param {() => void} fn + */ +async function run_tracked(fn) { + v8.collectGarbage(); + const gc_track = new GarbageTrack(); + const { result: wrappedResult, track_id } = gc_track.watch(() => run_timed(fn)); + const gc_time = await gc_track.gcDuration(track_id); + const { result, time } = wrappedResult; + gc_track.destroy(); + return { result, timing: { time, gc_time } }; +} + +/** + * @param {number} times + * @param {() => void} fn + */ +export async function fastest_test(times, fn) { + const results = []; + for (let i = 0; i < times; i++) { + const run = await run_tracked(fn); + results.push(run); + } + const fastest = results.reduce((a, b) => (a.timing.time < b.timing.time ? a : b)); + + return fastest; +} + +/** + * @param {boolean} a + */ +export function assert(a) { + if (!a) { + throw new Error('Assertion failed'); + } +} diff --git a/package.json b/package.json index bb50cef2d5..ee8dc4f836 100644 --- a/package.json +++ b/package.json @@ -23,7 +23,10 @@ "test": "vitest run", "test-output": "vitest run --coverage --reporter=json --outputFile=sites/svelte-5-preview/src/routes/status/results.json", "changeset:version": "changeset version && pnpm -r generate:version && git add --all", - "changeset:publish": "changeset publish" + "changeset:publish": "changeset publish", + "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" }, "devDependencies": { "@changesets/cli": "^2.27.1", @@ -41,6 +44,7 @@ "prettier-plugin-svelte": "^3.1.2", "typescript": "^5.3.3", "typescript-eslint": "^8.0.0-alpha.20", + "v8-natives": "^1.2.5", "vitest": "^1.2.1" }, "pnpm": { diff --git a/packages/svelte/CHANGELOG.md b/packages/svelte/CHANGELOG.md index b132e414a6..f6cb14fa8a 100644 --- a/packages/svelte/CHANGELOG.md +++ b/packages/svelte/CHANGELOG.md @@ -1,5 +1,23 @@ # svelte +## 5.0.0-next.160 + +### Patch Changes + +- chore: improve runtime performance of capturing reactive signals ([#12093](https://github.com/sveltejs/svelte/pull/12093)) + +## 5.0.0-next.159 + +### Patch Changes + +- fix: ensure element size bindings don't unsubscribe multiple times from the resize observer ([#12091](https://github.com/sveltejs/svelte/pull/12091)) + +- fix: prevent misidentification of bindings as delegatable event handlers if used outside event attribute ([#12081](https://github.com/sveltejs/svelte/pull/12081)) + +- fix: preserve current input values when removing defaults ([#12083](https://github.com/sveltejs/svelte/pull/12083)) + +- fix: preserve component function context for nested components ([#12089](https://github.com/sveltejs/svelte/pull/12089)) + ## 5.0.0-next.158 ### Patch Changes diff --git a/packages/svelte/elements.d.ts b/packages/svelte/elements.d.ts index 191308bbf2..05c113358e 100644 --- a/packages/svelte/elements.d.ts +++ b/packages/svelte/elements.d.ts @@ -113,6 +113,7 @@ export interface DOMAttributes { 'on:beforeinput'?: EventHandler | undefined | null; onbeforeinput?: EventHandler | undefined | null; onbeforeinputcapture?: EventHandler | undefined | null; + // oninput can be either an InputEvent or an Event, depending on the target element (input, textarea etc). 'on:input'?: FormEventHandler | undefined | null; oninput?: FormEventHandler | undefined | null; oninputcapture?: FormEventHandler | undefined | null; diff --git a/packages/svelte/package.json b/packages/svelte/package.json index 09c70a1522..ff6c49cb83 100644 --- a/packages/svelte/package.json +++ b/packages/svelte/package.json @@ -2,7 +2,7 @@ "name": "svelte", "description": "Cybernetically enhanced web apps", "license": "MIT", - "version": "5.0.0-next.158", + "version": "5.0.0-next.160", "type": "module", "types": "./types/index.d.ts", "engines": { diff --git a/packages/svelte/src/compiler/phases/2-analyze/index.js b/packages/svelte/src/compiler/phases/2-analyze/index.js index 16c0e8f834..ad39beb4eb 100644 --- a/packages/svelte/src/compiler/phases/2-analyze/index.js +++ b/packages/svelte/src/compiler/phases/2-analyze/index.js @@ -111,23 +111,24 @@ function get_delegated_event(event_name, handler, context) { if (binding != null) { for (const { path } of binding.references) { const parent = path.at(-1); - if (parent == null) { - return non_hoistable; - } + if (parent == null) return non_hoistable; + + const grandparent = path.at(-2); /** @type {import('#compiler').RegularElement | null} */ let element = null; /** @type {string | null} */ let event_name = null; if (parent.type === 'OnDirective') { - element = /** @type {import('#compiler').RegularElement} */ (path.at(-2)); + element = /** @type {import('#compiler').RegularElement} */ (grandparent); event_name = parent.name; } else if ( parent.type === 'ExpressionTag' && - is_event_attribute(/** @type {import('#compiler').Attribute} */ (path.at(-2))) + grandparent?.type === 'Attribute' && + is_event_attribute(grandparent) ) { element = /** @type {import('#compiler').RegularElement} */ (path.at(-3)); - const attribute = /** @type {import('#compiler').Attribute} */ (path.at(-2)); + const attribute = /** @type {import('#compiler').Attribute} */ (grandparent); event_name = get_attribute_event_name(attribute.name); } diff --git a/packages/svelte/src/compiler/phases/3-transform/client/visitors/template.js b/packages/svelte/src/compiler/phases/3-transform/client/visitors/template.js index b72b61cf7d..d8ae6749f4 100644 --- a/packages/svelte/src/compiler/phases/3-transform/client/visitors/template.js +++ b/packages/svelte/src/compiler/phases/3-transform/client/visitors/template.js @@ -886,7 +886,12 @@ function serialize_inline_component(node, component_name, context) { if (slot_name === 'default' && !has_children_prop) { push_prop( - b.init('children', context.state.options.dev ? b.call('$.wrap_snippet', slot_fn) : slot_fn) + b.init( + 'children', + context.state.options.dev + ? b.call('$.wrap_snippet', slot_fn, b.id(context.state.analysis.name)) + : slot_fn + ) ); // We additionally add the default slot as a boolean, so that the slot render function on the other // side knows it should get the content to render from $$props.children @@ -2030,7 +2035,7 @@ export const template_visitors = { } if (needs_input_reset && node.name === 'input') { - context.state.init.push(b.stmt(b.call('$.remove_input_attr_defaults', context.state.node))); + context.state.init.push(b.stmt(b.call('$.remove_input_defaults', context.state.node))); } if (needs_content_reset && node.name === 'textarea') { @@ -2699,7 +2704,7 @@ export const template_visitors = { let snippet = b.arrow(args, body); if (context.state.options.dev) { - snippet = b.call('$.wrap_snippet', snippet); + snippet = b.call('$.wrap_snippet', snippet, b.id(context.state.analysis.name)); } const declaration = b.var(node.expression, snippet); diff --git a/packages/svelte/src/internal/client/dom/blocks/snippet.js b/packages/svelte/src/internal/client/dom/blocks/snippet.js index 5bf13a45d8..611733feb1 100644 --- a/packages/svelte/src/internal/client/dom/blocks/snippet.js +++ b/packages/svelte/src/internal/client/dom/blocks/snippet.js @@ -39,15 +39,13 @@ export function snippet(get_snippet, node, ...args) { * In development, wrap the snippet function so that it passes validation, and so that the * correct component context is set for ownership checks * @param {(node: import('#client').TemplateNode, ...args: any[]) => import('#client').Dom} fn - * @returns + * @param {any} component */ -export function wrap_snippet(fn) { - let component = /** @type {import('#client').ComponentContext} */ (current_component_context); - +export function wrap_snippet(fn, component) { return add_snippet_symbol( (/** @type {import('#client').TemplateNode} */ node, /** @type {any[]} */ ...args) => { var previous_component_function = dev_current_component_function; - set_dev_current_component_function(component.function); + set_dev_current_component_function(component); try { return fn(node, ...args); diff --git a/packages/svelte/src/internal/client/dom/elements/attributes.js b/packages/svelte/src/internal/client/dom/elements/attributes.js index e6d6e78c1f..d09667dd90 100644 --- a/packages/svelte/src/internal/client/dom/elements/attributes.js +++ b/packages/svelte/src/internal/client/dom/elements/attributes.js @@ -16,29 +16,40 @@ import { queue_idle_task, queue_micro_task } from '../task.js'; /** * The value/checked attribute in the template actually corresponds to the defaultValue property, so we need * to remove it upon hydration to avoid a bug when someone resets the form value. - * @param {HTMLInputElement} dom + * @param {HTMLInputElement} input * @returns {void} */ -export function remove_input_attr_defaults(dom) { - if (hydrating) { - let already_removed = false; - // We try and remove the default attributes later, rather than sync during hydration. - // Doing it sync during hydration has a negative impact on performance, but deferring the - // work in an idle task alleviates this greatly. If a form reset event comes in before - // the idle callback, then we ensure the input defaults are cleared just before. - const remove_defaults = () => { - if (already_removed) return; - already_removed = true; - const value = dom.getAttribute('value'); - set_attribute(dom, 'value', null); - set_attribute(dom, 'checked', null); - if (value) dom.value = value; - }; - // @ts-expect-error - dom.__on_r = remove_defaults; - queue_idle_task(remove_defaults); - add_form_reset_listener(); - } +export function remove_input_defaults(input) { + if (!hydrating) return; + + var already_removed = false; + + // We try and remove the default attributes later, rather than sync during hydration. + // Doing it sync during hydration has a negative impact on performance, but deferring the + // work in an idle task alleviates this greatly. If a form reset event comes in before + // the idle callback, then we ensure the input defaults are cleared just before. + var remove_defaults = () => { + if (already_removed) return; + already_removed = true; + + // Remove the attributes but preserve the values + if (input.hasAttribute('value')) { + var value = input.value; + set_attribute(input, 'value', null); + input.value = value; + } + + if (input.hasAttribute('checked')) { + var checked = input.checked; + set_attribute(input, 'checked', null); + input.checked = checked; + } + }; + + // @ts-expect-error + input.__on_r = remove_defaults; + queue_idle_task(remove_defaults); + add_form_reset_listener(); } /** diff --git a/packages/svelte/src/internal/client/dom/elements/bindings/size.js b/packages/svelte/src/internal/client/dom/elements/bindings/size.js index e0ad045d80..c817787497 100644 --- a/packages/svelte/src/internal/client/dom/elements/bindings/size.js +++ b/packages/svelte/src/internal/client/dom/elements/bindings/size.js @@ -1,4 +1,5 @@ import { effect, teardown } from '../../../reactivity/effects.js'; +import { untrack } from '../../../runtime.js'; /** * Resize observer singleton. @@ -100,7 +101,8 @@ export function bind_element_size(element, type, update) { var unsub = resize_observer_border_box.observe(element, () => update(element[type])); effect(() => { - update(element[type]); + // The update could contain reads which should be ignored + untrack(() => update(element[type])); return unsub; }); } diff --git a/packages/svelte/src/internal/client/index.js b/packages/svelte/src/internal/client/index.js index 148c64bb69..36df01ce18 100644 --- a/packages/svelte/src/internal/client/index.js +++ b/packages/svelte/src/internal/client/index.js @@ -21,7 +21,7 @@ export { element } from './dom/blocks/svelte-element.js'; export { head } from './dom/blocks/svelte-head.js'; export { action } from './dom/elements/actions.js'; export { - remove_input_attr_defaults, + remove_input_defaults, set_attribute, set_attributes, set_custom_element_data, diff --git a/packages/svelte/src/internal/client/render.js b/packages/svelte/src/internal/client/render.js index 82d2c6bd57..f380f3729f 100644 --- a/packages/svelte/src/internal/client/render.js +++ b/packages/svelte/src/internal/client/render.js @@ -251,6 +251,7 @@ function _mount(Component, { target, anchor, props = {}, events, context, intro return () => { for (const event_name of registered_events) { target.removeEventListener(event_name, bound_event_listener); + document.removeEventListener(event_name, bound_event_listener); } root_event_handles.delete(event_handle); mounted_components.delete(component); diff --git a/packages/svelte/src/version.js b/packages/svelte/src/version.js index 6b9acb45e3..1b9efa6021 100644 --- a/packages/svelte/src/version.js +++ b/packages/svelte/src/version.js @@ -6,5 +6,5 @@ * https://svelte.dev/docs/svelte-compiler#svelte-version * @type {string} */ -export const VERSION = '5.0.0-next.158'; +export const VERSION = '5.0.0-next.160'; export const PUBLIC_VERSION = '5'; diff --git a/packages/svelte/tests/hydration/samples/input-value-changed/_config.js b/packages/svelte/tests/hydration/samples/input-value-changed/_config.js new file mode 100644 index 0000000000..f8c0dea072 --- /dev/null +++ b/packages/svelte/tests/hydration/samples/input-value-changed/_config.js @@ -0,0 +1,16 @@ +import { test } from '../../test'; + +export default test({ + server_props: { + name: 'server' + }, + + props: { + name: 'browser' + }, + + test(assert, target) { + const input = target.querySelector('input'); + assert.equal(input?.value, 'browser'); + } +}); diff --git a/packages/svelte/tests/hydration/samples/input-value-changed/_expected.html b/packages/svelte/tests/hydration/samples/input-value-changed/_expected.html new file mode 100644 index 0000000000..c5384e717e --- /dev/null +++ b/packages/svelte/tests/hydration/samples/input-value-changed/_expected.html @@ -0,0 +1 @@ + diff --git a/packages/svelte/tests/hydration/samples/input-value-changed/main.svelte b/packages/svelte/tests/hydration/samples/input-value-changed/main.svelte new file mode 100644 index 0000000000..c07692dc02 --- /dev/null +++ b/packages/svelte/tests/hydration/samples/input-value-changed/main.svelte @@ -0,0 +1,5 @@ + + + diff --git a/packages/svelte/tests/runtime-runes/samples/event-on-3/_config.js b/packages/svelte/tests/runtime-runes/samples/event-on-3/_config.js new file mode 100644 index 0000000000..5460c761ac --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/event-on-3/_config.js @@ -0,0 +1,7 @@ +import { test } from '../../test'; + +export default test({ + test() { + // Compiler shouldn't error + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/event-on-3/main.svelte b/packages/svelte/tests/runtime-runes/samples/event-on-3/main.svelte new file mode 100644 index 0000000000..4fa2257227 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/event-on-3/main.svelte @@ -0,0 +1,5 @@ + + +{f} diff --git a/packages/svelte/tests/runtime-runes/samples/non-local-mutation-with-binding-6/Component1.svelte b/packages/svelte/tests/runtime-runes/samples/non-local-mutation-with-binding-6/Component1.svelte new file mode 100644 index 0000000000..17d34e7cf6 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/non-local-mutation-with-binding-6/Component1.svelte @@ -0,0 +1,5 @@ + + +{@render children()} diff --git a/packages/svelte/tests/runtime-runes/samples/non-local-mutation-with-binding-6/Component2.svelte b/packages/svelte/tests/runtime-runes/samples/non-local-mutation-with-binding-6/Component2.svelte new file mode 100644 index 0000000000..17d34e7cf6 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/non-local-mutation-with-binding-6/Component2.svelte @@ -0,0 +1,5 @@ + + +{@render children()} diff --git a/packages/svelte/tests/runtime-runes/samples/non-local-mutation-with-binding-6/Component3.svelte b/packages/svelte/tests/runtime-runes/samples/non-local-mutation-with-binding-6/Component3.svelte new file mode 100644 index 0000000000..57ef96fed5 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/non-local-mutation-with-binding-6/Component3.svelte @@ -0,0 +1,5 @@ + + + diff --git a/packages/svelte/tests/runtime-runes/samples/non-local-mutation-with-binding-6/_config.js b/packages/svelte/tests/runtime-runes/samples/non-local-mutation-with-binding-6/_config.js new file mode 100644 index 0000000000..216f4c138a --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/non-local-mutation-with-binding-6/_config.js @@ -0,0 +1,23 @@ +import { flushSync } from 'svelte'; +import { test } from '../../test'; + +// Tests that nested snippets preserve correct component function context so we don't get false positive warnings +export default test({ + html: ``, + + compileOptions: { + dev: true + }, + + test({ assert, target, warnings }) { + const button = target.querySelector('button'); + + button?.click(); + flushSync(); + + assert.htmlEqual(target.innerHTML, ``); + assert.deepEqual(warnings, []); + }, + + warnings: [] +}); diff --git a/packages/svelte/tests/runtime-runes/samples/non-local-mutation-with-binding-6/main.svelte b/packages/svelte/tests/runtime-runes/samples/non-local-mutation-with-binding-6/main.svelte new file mode 100644 index 0000000000..d2f53f5363 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/non-local-mutation-with-binding-6/main.svelte @@ -0,0 +1,13 @@ + + + + + + + diff --git a/packages/svelte/tests/snapshot/samples/state-proxy-literal/_expected/client/index.svelte.js b/packages/svelte/tests/snapshot/samples/state-proxy-literal/_expected/client/index.svelte.js index 455f227e09..7cb2415bf5 100644 --- a/packages/svelte/tests/snapshot/samples/state-proxy-literal/_expected/client/index.svelte.js +++ b/packages/svelte/tests/snapshot/samples/state-proxy-literal/_expected/client/index.svelte.js @@ -16,11 +16,11 @@ export default function State_proxy_literal($$anchor) { var fragment = root(); var input = $.first_child(fragment); - $.remove_input_attr_defaults(input); + $.remove_input_defaults(input); var input_1 = $.sibling($.sibling(input, true)); - $.remove_input_attr_defaults(input_1); + $.remove_input_defaults(input_1); var button = $.sibling($.sibling(input_1, true)); @@ -30,4 +30,4 @@ export default function State_proxy_literal($$anchor) { $.append($$anchor, fragment); } -$.delegate(["click"]); \ No newline at end of file +$.delegate(["click"]); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 8e63c570df..7c61c77e45 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -16,7 +16,7 @@ importers: version: 2.27.1 '@sveltejs/eslint-config': specifier: ^7.0.1 - version: 7.0.1(@stylistic/eslint-plugin-js@1.8.0(eslint@9.0.0))(eslint-config-prettier@9.1.0(eslint@9.0.0))(eslint-plugin-svelte@2.38.0(eslint@9.0.0)(svelte@5.0.0-next.144))(eslint-plugin-unicorn@52.0.0(eslint@9.0.0))(eslint@9.0.0)(typescript-eslint@8.0.0-alpha.20(eslint@9.0.0)(typescript@5.3.3))(typescript@5.3.3) + version: 7.0.1(@stylistic/eslint-plugin-js@1.8.0(eslint@9.0.0))(eslint-config-prettier@9.1.0(eslint@9.0.0))(eslint-plugin-svelte@2.38.0(eslint@9.0.0)(svelte@5.0.0-next.158))(eslint-plugin-unicorn@52.0.0(eslint@9.0.0))(eslint@9.0.0)(typescript-eslint@8.0.0-alpha.20(eslint@9.0.0)(typescript@5.3.3))(typescript@5.3.3) '@svitejs/changesets-changelog-github-compact': specifier: ^1.1.0 version: 1.1.0 @@ -49,13 +49,16 @@ importers: version: 3.2.4 prettier-plugin-svelte: specifier: ^3.1.2 - version: 3.1.2(prettier@3.2.4)(svelte@5.0.0-next.144) + version: 3.1.2(prettier@3.2.4)(svelte@5.0.0-next.158) typescript: specifier: ^5.3.3 version: 5.3.3 typescript-eslint: specifier: ^8.0.0-alpha.20 version: 8.0.0-alpha.20(eslint@9.0.0)(typescript@5.3.3) + v8-natives: + specifier: ^1.2.5 + version: 1.2.5 vitest: specifier: ^1.2.1 version: 1.2.1(@types/node@20.11.5)(jsdom@22.0.0)(lightningcss@1.23.0)(sass@1.70.0)(terser@5.27.0) @@ -4575,8 +4578,8 @@ packages: resolution: {integrity: sha512-hsoB/WZGEPFXeRRLPhPrbRz67PhP6sqYgvwcAs+gWdSQSvNDw+/lTeUJSWe5h2xC97Fz/8QxAOqItwBzNJPU8w==} engines: {node: '>=16'} - svelte@5.0.0-next.144: - resolution: {integrity: sha512-akjtRBHzaLa1XdMv9tBGkXE5N2JaRc3gL+ZIctjc9Gew9DF7NxGTlxXq+HR9yUV7Lsg4o9ltMfkxz8H3K7piNQ==} + svelte@5.0.0-next.158: + resolution: {integrity: sha512-QRmXxHByWntyWqLtzjNsBbNT89F2yA7aWPp9M9l9a6/PAE3gmQh6+qoVPgrxMR7iiFgpwh5ZU9Bm25j3IhGicQ==} engines: {node: '>=18'} symbol-tree@3.2.4: @@ -4829,6 +4832,10 @@ packages: resolution: {integrity: sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==} engines: {node: '>= 0.4.0'} + v8-natives@1.2.5: + resolution: {integrity: sha512-CVNliz6KF2yet3HBIkbFJKZmjlt95C8dsNZDnwoS6X98+QJRpsSz9uxo3TziBqdyJQkWwfD3VG9lRzsQNvF24Q==} + engines: {node: '>= 0.6.0'} + v8-to-istanbul@9.2.0: resolution: {integrity: sha512-/EH/sDgxU2eGxajKdwLCDmQ4FWq+kpi3uCmBGpw1xJtnAxEjlD8j8PEiGWpCIMIs3ciNAgH0d3TTJiUkYzyZjA==} engines: {node: '>=10.12.0'} @@ -6453,12 +6460,12 @@ snapshots: - encoding - supports-color - '@sveltejs/eslint-config@7.0.1(@stylistic/eslint-plugin-js@1.8.0(eslint@9.0.0))(eslint-config-prettier@9.1.0(eslint@9.0.0))(eslint-plugin-svelte@2.38.0(eslint@9.0.0)(svelte@5.0.0-next.144))(eslint-plugin-unicorn@52.0.0(eslint@9.0.0))(eslint@9.0.0)(typescript-eslint@8.0.0-alpha.20(eslint@9.0.0)(typescript@5.3.3))(typescript@5.3.3)': + '@sveltejs/eslint-config@7.0.1(@stylistic/eslint-plugin-js@1.8.0(eslint@9.0.0))(eslint-config-prettier@9.1.0(eslint@9.0.0))(eslint-plugin-svelte@2.38.0(eslint@9.0.0)(svelte@5.0.0-next.158))(eslint-plugin-unicorn@52.0.0(eslint@9.0.0))(eslint@9.0.0)(typescript-eslint@8.0.0-alpha.20(eslint@9.0.0)(typescript@5.3.3))(typescript@5.3.3)': dependencies: '@stylistic/eslint-plugin-js': 1.8.0(eslint@9.0.0) eslint: 9.0.0 eslint-config-prettier: 9.1.0(eslint@9.0.0) - eslint-plugin-svelte: 2.38.0(eslint@9.0.0)(svelte@5.0.0-next.144) + eslint-plugin-svelte: 2.38.0(eslint@9.0.0)(svelte@5.0.0-next.158) eslint-plugin-unicorn: 52.0.0(eslint@9.0.0) globals: 15.0.0 typescript: 5.3.3 @@ -7570,7 +7577,7 @@ snapshots: eslint-plugin-lube@0.4.3: {} - eslint-plugin-svelte@2.38.0(eslint@9.0.0)(svelte@5.0.0-next.144): + eslint-plugin-svelte@2.38.0(eslint@9.0.0)(svelte@5.0.0-next.158): dependencies: '@eslint-community/eslint-utils': 4.4.0(eslint@9.0.0) '@jridgewell/sourcemap-codec': 1.4.15 @@ -7584,9 +7591,9 @@ snapshots: postcss-safe-parser: 6.0.0(postcss@8.4.38) postcss-selector-parser: 6.0.16 semver: 7.6.0 - svelte-eslint-parser: 0.35.0(svelte@5.0.0-next.144) + svelte-eslint-parser: 0.35.0(svelte@5.0.0-next.158) optionalDependencies: - svelte: 5.0.0-next.144 + svelte: 5.0.0-next.158 transitivePeerDependencies: - supports-color - ts-node @@ -9094,10 +9101,10 @@ snapshots: prettier: 3.2.4 svelte: 4.2.9 - prettier-plugin-svelte@3.1.2(prettier@3.2.4)(svelte@5.0.0-next.144): + prettier-plugin-svelte@3.1.2(prettier@3.2.4)(svelte@5.0.0-next.158): dependencies: prettier: 3.2.4 - svelte: 5.0.0-next.144 + svelte: 5.0.0-next.158 prettier@2.8.8: {} @@ -9726,7 +9733,7 @@ snapshots: - stylus - sugarss - svelte-eslint-parser@0.35.0(svelte@5.0.0-next.144): + svelte-eslint-parser@0.35.0(svelte@5.0.0-next.158): dependencies: eslint-scope: 7.2.2 eslint-visitor-keys: 3.4.3 @@ -9734,7 +9741,7 @@ snapshots: postcss: 8.4.38 postcss-scss: 4.0.9(postcss@8.4.38) optionalDependencies: - svelte: 5.0.0-next.144 + svelte: 5.0.0-next.158 svelte-hmr@0.16.0(svelte@4.2.9): dependencies: @@ -9809,7 +9816,7 @@ snapshots: magic-string: 0.30.5 periscopic: 3.1.0 - svelte@5.0.0-next.144: + svelte@5.0.0-next.158: dependencies: '@ampproject/remapping': 2.2.1 '@jridgewell/sourcemap-codec': 1.4.15 @@ -10070,6 +10077,8 @@ snapshots: utils-merge@1.0.1: {} + v8-natives@1.2.5: {} + v8-to-istanbul@9.2.0: dependencies: '@jridgewell/trace-mapping': 0.3.22