mirror of https://github.com/sveltejs/svelte
commit
c7472a6168
@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
'svelte': minor
|
||||||
|
---
|
||||||
|
|
||||||
|
feat: allow passing `ShadowRootInit` object to custom element `shadow` option
|
||||||
@ -1,91 +0,0 @@
|
|||||||
import { assert, fastest_test } from '../../../utils.js';
|
|
||||||
import * as $ from 'svelte/internal/client';
|
|
||||||
import { busy } from './util.js';
|
|
||||||
|
|
||||||
function setup() {
|
|
||||||
let head = $.state(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(() => {
|
|
||||||
$.set(head, 1);
|
|
||||||
});
|
|
||||||
assert($.get(computed5) === 6);
|
|
||||||
for (let i = 0; i < 1000; i++) {
|
|
||||||
$.flush(() => {
|
|
||||||
$.set(head, i);
|
|
||||||
});
|
|
||||||
assert($.get(computed5) === 6);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function kairo_avoidable_unowned() {
|
|
||||||
// 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 < 1000; i++) {
|
|
||||||
run();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
destroy();
|
|
||||||
|
|
||||||
return {
|
|
||||||
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 < 1000; i++) {
|
|
||||||
run();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// @ts-ignore
|
|
||||||
destroy();
|
|
||||||
destroy_owned();
|
|
||||||
|
|
||||||
return {
|
|
||||||
benchmark: 'kairo_avoidable_owned',
|
|
||||||
time: timing.time.toFixed(2),
|
|
||||||
gc_time: timing.gc_time.toFixed(2)
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@ -1,97 +0,0 @@
|
|||||||
import { assert, fastest_test } from '../../../utils.js';
|
|
||||||
import * as $ from 'svelte/internal/client';
|
|
||||||
|
|
||||||
function setup() {
|
|
||||||
let head = $.state(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(() => {
|
|
||||||
$.set(head, 1);
|
|
||||||
});
|
|
||||||
counter = 0;
|
|
||||||
for (let i = 0; i < 50; i++) {
|
|
||||||
$.flush(() => {
|
|
||||||
$.set(head, i);
|
|
||||||
});
|
|
||||||
assert($.get(last) === i + 50);
|
|
||||||
}
|
|
||||||
assert(counter === 50 * 50);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function kairo_broad_unowned() {
|
|
||||||
// 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 < 1000; i++) {
|
|
||||||
run();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
destroy();
|
|
||||||
|
|
||||||
return {
|
|
||||||
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 < 1000; i++) {
|
|
||||||
run();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// @ts-ignore
|
|
||||||
destroy();
|
|
||||||
destroy_owned();
|
|
||||||
|
|
||||||
return {
|
|
||||||
benchmark: 'kairo_broad_owned',
|
|
||||||
time: timing.time.toFixed(2),
|
|
||||||
gc_time: timing.gc_time.toFixed(2)
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@ -1,97 +0,0 @@
|
|||||||
import { assert, fastest_test } from '../../../utils.js';
|
|
||||||
import * as $ from 'svelte/internal/client';
|
|
||||||
|
|
||||||
let len = 50;
|
|
||||||
const iter = 50;
|
|
||||||
|
|
||||||
function setup() {
|
|
||||||
let head = $.state(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(() => {
|
|
||||||
$.set(head, 1);
|
|
||||||
});
|
|
||||||
counter = 0;
|
|
||||||
for (let i = 0; i < iter; i++) {
|
|
||||||
$.flush(() => {
|
|
||||||
$.set(head, i);
|
|
||||||
});
|
|
||||||
assert($.get(current) === len + i);
|
|
||||||
}
|
|
||||||
assert(counter === iter);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function kairo_deep_unowned() {
|
|
||||||
// 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 < 1000; i++) {
|
|
||||||
run();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
destroy();
|
|
||||||
|
|
||||||
return {
|
|
||||||
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 < 1000; i++) {
|
|
||||||
run();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// @ts-ignore
|
|
||||||
destroy();
|
|
||||||
destroy_owned();
|
|
||||||
|
|
||||||
return {
|
|
||||||
benchmark: 'kairo_deep_owned',
|
|
||||||
time: timing.time.toFixed(2),
|
|
||||||
gc_time: timing.gc_time.toFixed(2)
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@ -1,101 +0,0 @@
|
|||||||
import { assert, fastest_test } from '../../../utils.js';
|
|
||||||
import * as $ from 'svelte/internal/client';
|
|
||||||
|
|
||||||
let width = 5;
|
|
||||||
|
|
||||||
function setup() {
|
|
||||||
let head = $.state(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(() => {
|
|
||||||
$.set(head, 1);
|
|
||||||
});
|
|
||||||
assert($.get(sum) === 2 * width);
|
|
||||||
counter = 0;
|
|
||||||
for (let i = 0; i < 500; i++) {
|
|
||||||
$.flush(() => {
|
|
||||||
$.set(head, i);
|
|
||||||
});
|
|
||||||
assert($.get(sum) === (i + 1) * width);
|
|
||||||
}
|
|
||||||
assert(counter === 500);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function kairo_diamond_unowned() {
|
|
||||||
// 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 < 1000; i++) {
|
|
||||||
run();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
destroy();
|
|
||||||
|
|
||||||
return {
|
|
||||||
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 < 1000; i++) {
|
|
||||||
run();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// @ts-ignore
|
|
||||||
destroy();
|
|
||||||
destroy_owned();
|
|
||||||
|
|
||||||
return {
|
|
||||||
benchmark: 'kairo_diamond_owned',
|
|
||||||
time: timing.time.toFixed(2),
|
|
||||||
gc_time: timing.gc_time.toFixed(2)
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@ -1,94 +0,0 @@
|
|||||||
import { assert, fastest_test } from '../../../utils.js';
|
|
||||||
import * as $ from 'svelte/internal/client';
|
|
||||||
|
|
||||||
function setup() {
|
|
||||||
let heads = new Array(100).fill(null).map((_) => $.state(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(() => {
|
|
||||||
$.set(heads[i], i);
|
|
||||||
});
|
|
||||||
assert($.get(splited[i]) === i + 1);
|
|
||||||
}
|
|
||||||
for (let i = 0; i < 10; i++) {
|
|
||||||
$.flush(() => {
|
|
||||||
$.set(heads[i], i * 2);
|
|
||||||
});
|
|
||||||
assert($.get(splited[i]) === i * 2 + 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function kairo_mux_unowned() {
|
|
||||||
// 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 < 1000; i++) {
|
|
||||||
run();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
destroy();
|
|
||||||
|
|
||||||
return {
|
|
||||||
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 < 1000; i++) {
|
|
||||||
run();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// @ts-ignore
|
|
||||||
destroy();
|
|
||||||
destroy_owned();
|
|
||||||
|
|
||||||
return {
|
|
||||||
benchmark: 'kairo_mux_owned',
|
|
||||||
time: timing.time.toFixed(2),
|
|
||||||
gc_time: timing.gc_time.toFixed(2)
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@ -1,98 +0,0 @@
|
|||||||
import { assert, fastest_test } from '../../../utils.js';
|
|
||||||
import * as $ from 'svelte/internal/client';
|
|
||||||
|
|
||||||
let size = 30;
|
|
||||||
|
|
||||||
function setup() {
|
|
||||||
let head = $.state(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(() => {
|
|
||||||
$.set(head, 1);
|
|
||||||
});
|
|
||||||
assert($.get(current) === size);
|
|
||||||
counter = 0;
|
|
||||||
for (let i = 0; i < 100; i++) {
|
|
||||||
$.flush(() => {
|
|
||||||
$.set(head, i);
|
|
||||||
});
|
|
||||||
assert($.get(current) === i * size);
|
|
||||||
}
|
|
||||||
assert(counter === 100);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function kairo_repeated_unowned() {
|
|
||||||
// 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 < 1000; i++) {
|
|
||||||
run();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
destroy();
|
|
||||||
|
|
||||||
return {
|
|
||||||
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 < 1000; i++) {
|
|
||||||
run();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// @ts-ignore
|
|
||||||
destroy();
|
|
||||||
destroy_owned();
|
|
||||||
|
|
||||||
return {
|
|
||||||
benchmark: 'kairo_repeated_owned',
|
|
||||||
time: timing.time.toFixed(2),
|
|
||||||
gc_time: timing.gc_time.toFixed(2)
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@ -1,111 +0,0 @@
|
|||||||
import { assert, fastest_test } from '../../../utils.js';
|
|
||||||
import * as $ from 'svelte/internal/client';
|
|
||||||
|
|
||||||
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 = $.state(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(() => {
|
|
||||||
$.set(head, 1);
|
|
||||||
});
|
|
||||||
assert($.get(sum) === constant);
|
|
||||||
counter = 0;
|
|
||||||
for (let i = 0; i < 100; i++) {
|
|
||||||
$.flush(() => {
|
|
||||||
$.set(head, i);
|
|
||||||
});
|
|
||||||
assert($.get(sum) === constant - width + i * width);
|
|
||||||
}
|
|
||||||
assert(counter === 100);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function kairo_triangle_unowned() {
|
|
||||||
// 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 < 1000; i++) {
|
|
||||||
run();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
destroy();
|
|
||||||
|
|
||||||
return {
|
|
||||||
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 < 1000; i++) {
|
|
||||||
run();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// @ts-ignore
|
|
||||||
destroy();
|
|
||||||
destroy_owned();
|
|
||||||
|
|
||||||
return {
|
|
||||||
benchmark: 'kairo_triangle_owned',
|
|
||||||
time: timing.time.toFixed(2),
|
|
||||||
gc_time: timing.gc_time.toFixed(2)
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@ -1,97 +0,0 @@
|
|||||||
import { assert, fastest_test } from '../../../utils.js';
|
|
||||||
import * as $ from 'svelte/internal/client';
|
|
||||||
|
|
||||||
function setup() {
|
|
||||||
let head = $.state(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(() => {
|
|
||||||
$.set(head, 1);
|
|
||||||
});
|
|
||||||
assert($.get(current) === 40);
|
|
||||||
counter = 0;
|
|
||||||
for (let i = 0; i < 100; i++) {
|
|
||||||
$.flush(() => {
|
|
||||||
$.set(head, i);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
assert(counter === 100);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function kairo_unstable_unowned() {
|
|
||||||
// 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 < 1000; i++) {
|
|
||||||
run();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
destroy();
|
|
||||||
|
|
||||||
return {
|
|
||||||
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 < 1000; i++) {
|
|
||||||
run();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// @ts-ignore
|
|
||||||
destroy();
|
|
||||||
destroy_owned();
|
|
||||||
|
|
||||||
return {
|
|
||||||
benchmark: 'kairo_unstable_owned',
|
|
||||||
time: timing.time.toFixed(2),
|
|
||||||
gc_time: timing.gc_time.toFixed(2)
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@ -1,6 +0,0 @@
|
|||||||
export function busy() {
|
|
||||||
let a = 0;
|
|
||||||
for (let i = 0; i < 1_00; i++) {
|
|
||||||
a++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -0,0 +1,35 @@
|
|||||||
|
import assert from 'node:assert';
|
||||||
|
import * as $ from 'svelte/internal/client';
|
||||||
|
import { busy } from '../util.js';
|
||||||
|
|
||||||
|
export default () => {
|
||||||
|
let head = $.state(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(() => {
|
||||||
|
$.set(head, 1);
|
||||||
|
});
|
||||||
|
assert.equal($.get(computed5), 6);
|
||||||
|
for (let i = 0; i < 1000; i++) {
|
||||||
|
$.flush(() => {
|
||||||
|
$.set(head, i);
|
||||||
|
});
|
||||||
|
assert.equal($.get(computed5), 6);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
||||||
@ -0,0 +1,41 @@
|
|||||||
|
import assert from 'node:assert';
|
||||||
|
import * as $ from 'svelte/internal/client';
|
||||||
|
|
||||||
|
export default () => {
|
||||||
|
let head = $.state(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(() => {
|
||||||
|
$.set(head, 1);
|
||||||
|
});
|
||||||
|
counter = 0;
|
||||||
|
for (let i = 0; i < 50; i++) {
|
||||||
|
$.flush(() => {
|
||||||
|
$.set(head, i);
|
||||||
|
});
|
||||||
|
assert.equal($.get(last), i + 50);
|
||||||
|
}
|
||||||
|
assert.equal(counter, 50 * 50);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
||||||
@ -0,0 +1,41 @@
|
|||||||
|
import assert from 'node:assert';
|
||||||
|
import * as $ from 'svelte/internal/client';
|
||||||
|
|
||||||
|
let len = 50;
|
||||||
|
const iter = 50;
|
||||||
|
|
||||||
|
export default () => {
|
||||||
|
let head = $.state(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(() => {
|
||||||
|
$.set(head, 1);
|
||||||
|
});
|
||||||
|
counter = 0;
|
||||||
|
for (let i = 0; i < iter; i++) {
|
||||||
|
$.flush(() => {
|
||||||
|
$.set(head, i);
|
||||||
|
});
|
||||||
|
assert.equal($.get(current), len + i);
|
||||||
|
}
|
||||||
|
assert.equal(counter, iter);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
||||||
@ -0,0 +1,45 @@
|
|||||||
|
import assert from 'node:assert';
|
||||||
|
import * as $ from 'svelte/internal/client';
|
||||||
|
|
||||||
|
let width = 5;
|
||||||
|
|
||||||
|
export default () => {
|
||||||
|
let head = $.state(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(() => {
|
||||||
|
$.set(head, 1);
|
||||||
|
});
|
||||||
|
assert.equal($.get(sum), 2 * width);
|
||||||
|
counter = 0;
|
||||||
|
for (let i = 0; i < 500; i++) {
|
||||||
|
$.flush(() => {
|
||||||
|
$.set(head, i);
|
||||||
|
});
|
||||||
|
assert.equal($.get(sum), (i + 1) * width);
|
||||||
|
}
|
||||||
|
assert.equal(counter, 500);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
||||||
@ -0,0 +1,38 @@
|
|||||||
|
import assert from 'node:assert';
|
||||||
|
import * as $ from 'svelte/internal/client';
|
||||||
|
|
||||||
|
export default () => {
|
||||||
|
let heads = new Array(100).fill(null).map((_) => $.state(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(() => {
|
||||||
|
$.set(heads[i], i);
|
||||||
|
});
|
||||||
|
assert.equal($.get(splited[i]), i + 1);
|
||||||
|
}
|
||||||
|
for (let i = 0; i < 10; i++) {
|
||||||
|
$.flush(() => {
|
||||||
|
$.set(heads[i], i * 2);
|
||||||
|
});
|
||||||
|
assert.equal($.get(splited[i]), i * 2 + 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
||||||
@ -0,0 +1,42 @@
|
|||||||
|
import assert from 'node:assert';
|
||||||
|
import * as $ from 'svelte/internal/client';
|
||||||
|
|
||||||
|
let size = 30;
|
||||||
|
|
||||||
|
export default () => {
|
||||||
|
let head = $.state(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(() => {
|
||||||
|
$.set(head, 1);
|
||||||
|
});
|
||||||
|
assert.equal($.get(current), size);
|
||||||
|
counter = 0;
|
||||||
|
for (let i = 0; i < 100; i++) {
|
||||||
|
$.flush(() => {
|
||||||
|
$.set(head, i);
|
||||||
|
});
|
||||||
|
assert.equal($.get(current), i * size);
|
||||||
|
}
|
||||||
|
assert.equal(counter, 100);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
||||||
@ -0,0 +1,55 @@
|
|||||||
|
import assert from 'node:assert';
|
||||||
|
import * as $ from 'svelte/internal/client';
|
||||||
|
|
||||||
|
let width = 10;
|
||||||
|
|
||||||
|
function count(number) {
|
||||||
|
return new Array(number)
|
||||||
|
.fill(0)
|
||||||
|
.map((_, i) => i + 1)
|
||||||
|
.reduce((x, y) => x + y, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default () => {
|
||||||
|
let head = $.state(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(() => {
|
||||||
|
$.set(head, 1);
|
||||||
|
});
|
||||||
|
assert.equal($.get(sum), constant);
|
||||||
|
counter = 0;
|
||||||
|
for (let i = 0; i < 100; i++) {
|
||||||
|
$.flush(() => {
|
||||||
|
$.set(head, i);
|
||||||
|
});
|
||||||
|
assert.equal($.get(sum), constant - width + i * width);
|
||||||
|
}
|
||||||
|
assert.equal(counter, 100);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
||||||
@ -0,0 +1,41 @@
|
|||||||
|
import assert from 'node:assert';
|
||||||
|
import * as $ from 'svelte/internal/client';
|
||||||
|
|
||||||
|
export default () => {
|
||||||
|
let head = $.state(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(() => {
|
||||||
|
$.set(head, 1);
|
||||||
|
});
|
||||||
|
assert.equal($.get(current), 40);
|
||||||
|
counter = 0;
|
||||||
|
for (let i = 0; i < 100; i++) {
|
||||||
|
$.flush(() => {
|
||||||
|
$.set(head, i);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
assert.equal(counter, 100);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
||||||
@ -0,0 +1,35 @@
|
|||||||
|
import assert from 'node:assert';
|
||||||
|
import * as $ from 'svelte/internal/client';
|
||||||
|
|
||||||
|
const ARRAY_SIZE = 1000;
|
||||||
|
|
||||||
|
export default () => {
|
||||||
|
const signals = Array.from({ length: ARRAY_SIZE }, (_, i) => $.state(i));
|
||||||
|
const order = $.state(0);
|
||||||
|
|
||||||
|
// break skipped_deps fast path by changing order of reads
|
||||||
|
const total = $.derived(() => {
|
||||||
|
const ord = $.get(order);
|
||||||
|
let sum = 0;
|
||||||
|
for (let i = 0; i < ARRAY_SIZE; i++) {
|
||||||
|
sum += /** @type {number} */ ($.get(signals[(i + ord) % ARRAY_SIZE]));
|
||||||
|
}
|
||||||
|
return sum;
|
||||||
|
});
|
||||||
|
|
||||||
|
const destroy = $.effect_root(() => {
|
||||||
|
$.render_effect(() => {
|
||||||
|
$.get(total);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
destroy,
|
||||||
|
run() {
|
||||||
|
for (let i = 0; i < 5; i++) {
|
||||||
|
$.flush(() => $.set(order, i));
|
||||||
|
assert.equal($.get(total), (ARRAY_SIZE * (ARRAY_SIZE - 1)) / 2); // sum of 0..999
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
||||||
@ -0,0 +1,71 @@
|
|||||||
|
import * as $ from 'svelte/internal/client';
|
||||||
|
import { fastest_test } from '../../utils.js';
|
||||||
|
|
||||||
|
export function busy() {
|
||||||
|
let a = 0;
|
||||||
|
for (let i = 0; i < 1_00; i++) {
|
||||||
|
a++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {string} label
|
||||||
|
* @param {() => { run: (i?: number) => void, destroy: () => void }} setup
|
||||||
|
*/
|
||||||
|
export function create_test(label, setup) {
|
||||||
|
return {
|
||||||
|
unowned: {
|
||||||
|
label: `${label}_unowned`,
|
||||||
|
fn: async () => {
|
||||||
|
// 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 result = await fastest_test(10, () => {
|
||||||
|
for (let i = 0; i < 1000; i++) {
|
||||||
|
run(i);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
destroy();
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
owned: {
|
||||||
|
label: `${label}_owned`,
|
||||||
|
fn: async () => {
|
||||||
|
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 result = await fastest_test(10, () => {
|
||||||
|
for (let i = 0; i < 1000; i++) {
|
||||||
|
run(i);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// @ts-ignore
|
||||||
|
destroy();
|
||||||
|
destroy_owned();
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
@ -1,10 +1,13 @@
|
|||||||
import { reactivity_benchmarks } from '../benchmarks/reactivity/index.js';
|
import { reactivity_benchmarks } from '../benchmarks/reactivity/index.js';
|
||||||
|
|
||||||
const results = [];
|
const results = [];
|
||||||
for (const benchmark of reactivity_benchmarks) {
|
|
||||||
const result = await benchmark();
|
for (let i = 0; i < reactivity_benchmarks.length; i += 1) {
|
||||||
console.error(result.benchmark);
|
const benchmark = reactivity_benchmarks[i];
|
||||||
results.push(result);
|
|
||||||
|
process.stderr.write(`Running ${i + 1}/${reactivity_benchmarks.length} ${benchmark.label} `);
|
||||||
|
results.push({ benchmark: benchmark.label, ...(await benchmark.fn()) });
|
||||||
|
process.stderr.write('\x1b[2K\r');
|
||||||
}
|
}
|
||||||
|
|
||||||
process.send(results);
|
process.send(results);
|
||||||
|
|||||||
@ -0,0 +1,123 @@
|
|||||||
|
---
|
||||||
|
title: Hydratable data
|
||||||
|
---
|
||||||
|
|
||||||
|
In Svelte, when you want to render asynchronous content data on the server, you can simply `await` it. This is great! However, it comes with a pitfall: when hydrating that content on the client, Svelte has to redo the asynchronous work, which blocks hydration for however long it takes:
|
||||||
|
|
||||||
|
```svelte
|
||||||
|
<script>
|
||||||
|
import { getUser } from 'my-database-library';
|
||||||
|
|
||||||
|
// This will get the user on the server, render the user's name into the h1,
|
||||||
|
// and then, during hydration on the client, it will get the user _again_,
|
||||||
|
// blocking hydration until it's done.
|
||||||
|
const user = await getUser();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<h1>{user.name}</h1>
|
||||||
|
```
|
||||||
|
|
||||||
|
That's silly, though. If we've already done the hard work of getting the data on the server, we don't want to get it again during hydration on the client. `hydratable` is a low-level API built to solve this problem. You probably won't need this very often — it will be used behind the scenes by whatever datafetching library you use. For example, it powers [remote functions in SvelteKit](/docs/kit/remote-functions).
|
||||||
|
|
||||||
|
To fix the example above:
|
||||||
|
|
||||||
|
```svelte
|
||||||
|
<script>
|
||||||
|
import { hydratable } from 'svelte';
|
||||||
|
import { getUser } from 'my-database-library';
|
||||||
|
|
||||||
|
// During server rendering, this will serialize and stash the result of `getUser`, associating
|
||||||
|
// it with the provided key and baking it into the `head` content. During hydration, it will
|
||||||
|
// look for the serialized version, returning it instead of running `getUser`. After hydration
|
||||||
|
// is done, if it's called again, it'll simply invoke `getUser`.
|
||||||
|
const user = await hydratable('user', () => getUser());
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<h1>{user.name}</h1>
|
||||||
|
```
|
||||||
|
|
||||||
|
This API can also be used to provide access to random or time-based values that are stable between server rendering and hydration. For example, to get a random number that doesn't update on hydration:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import { hydratable } from 'svelte';
|
||||||
|
const rand = hydratable('random', () => Math.random());
|
||||||
|
```
|
||||||
|
|
||||||
|
If you're a library author, be sure to prefix the keys of your `hydratable` values with the name of your library so that your keys don't conflict with other libraries.
|
||||||
|
|
||||||
|
## Serialization
|
||||||
|
|
||||||
|
All data returned from a `hydratable` function must be serializable. But this doesn't mean you're limited to JSON — Svelte uses [`devalue`](https://npmjs.com/package/devalue), which can serialize all sorts of things including `Map`, `Set`, `URL`, and `BigInt`. Check the documentation page for a full list. In addition to these, thanks to some Svelte magic, you can also fearlessly use promises:
|
||||||
|
|
||||||
|
```svelte
|
||||||
|
<script>
|
||||||
|
import { hydratable } from 'svelte';
|
||||||
|
const promises = hydratable('random', () => {
|
||||||
|
return {
|
||||||
|
one: Promise.resolve(1),
|
||||||
|
two: Promise.resolve(2)
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{await promises.one}
|
||||||
|
{await promises.two}
|
||||||
|
```
|
||||||
|
|
||||||
|
## CSP
|
||||||
|
|
||||||
|
`hydratable` adds an inline `<script>` block to the `head` returned from `render`. If you're using [Content Security Policy](https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/CSP) (CSP), this script will likely fail to run. You can provide a `nonce` to `render`:
|
||||||
|
|
||||||
|
```js
|
||||||
|
/// file: server.js
|
||||||
|
import { render } from 'svelte/server';
|
||||||
|
import App from './App.svelte';
|
||||||
|
// ---cut---
|
||||||
|
const nonce = crypto.randomUUID();
|
||||||
|
|
||||||
|
const { head, body } = await render(App, {
|
||||||
|
csp: { nonce }
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
This will add the `nonce` to the script block, on the assumption that you will later add the same nonce to the CSP header of the document that contains it:
|
||||||
|
|
||||||
|
```js
|
||||||
|
/// file: server.js
|
||||||
|
let response = new Response();
|
||||||
|
let nonce = 'xyz123';
|
||||||
|
// ---cut---
|
||||||
|
response.headers.set(
|
||||||
|
'Content-Security-Policy',
|
||||||
|
`script-src 'nonce-${nonce}'`
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
It's essential that a `nonce` — which, British slang definition aside, means 'number used once' — is only used when dynamically server rendering an individual response.
|
||||||
|
|
||||||
|
If instead you are generating static HTML ahead of time, you must use hashes instead:
|
||||||
|
|
||||||
|
```js
|
||||||
|
/// file: server.js
|
||||||
|
import { render } from 'svelte/server';
|
||||||
|
import App from './App.svelte';
|
||||||
|
// ---cut---
|
||||||
|
const { head, body, hashes } = await render(App, {
|
||||||
|
csp: { hash: true }
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
`hashes.script` will be an array of strings like `["sha256-abcd123"]`. As with `nonce`, the hashes should be used in your CSP header:
|
||||||
|
|
||||||
|
```js
|
||||||
|
/// file: server.js
|
||||||
|
let response = new Response();
|
||||||
|
let hashes = { script: ['sha256-xyz123'] };
|
||||||
|
// ---cut---
|
||||||
|
response.headers.set(
|
||||||
|
'Content-Security-Policy',
|
||||||
|
`script-src ${hashes.script.map((hash) => `'${hash}'`).join(' ')}`
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
We recommend using `nonce` over hash if you can, as `hash` will interfere with streaming SSR in the future.
|
||||||
@ -0,0 +1,34 @@
|
|||||||
|
<!-- This file is generated by scripts/process-messages/index.js. Do not edit! -->
|
||||||
|
|
||||||
|
### unresolved_hydratable
|
||||||
|
|
||||||
|
```
|
||||||
|
A `hydratable` value with key `%key%` was created, but at least part of it was not used during the render.
|
||||||
|
|
||||||
|
The `hydratable` was initialized in:
|
||||||
|
%stack%
|
||||||
|
```
|
||||||
|
|
||||||
|
The most likely cause of this is creating a `hydratable` in the `script` block of your component and then `await`ing
|
||||||
|
the result inside a `svelte:boundary` with a `pending` snippet:
|
||||||
|
|
||||||
|
```svelte
|
||||||
|
<script>
|
||||||
|
import { hydratable } from 'svelte';
|
||||||
|
import { getUser } from '$lib/get-user.js';
|
||||||
|
|
||||||
|
const user = hydratable('user', getUser);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<svelte:boundary>
|
||||||
|
<h1>{(await user).name}</h1>
|
||||||
|
|
||||||
|
{#snippet pending()}
|
||||||
|
<div>Loading...</div>
|
||||||
|
{/snippet}
|
||||||
|
</svelte:boundary>
|
||||||
|
```
|
||||||
|
|
||||||
|
Consider inlining the `hydratable` call inside the boundary so that it's not called on the server.
|
||||||
|
|
||||||
|
Note that this can also happen when a `hydratable` contains multiple promises and some but not all of them have been used.
|
||||||
@ -0,0 +1,30 @@
|
|||||||
|
## unresolved_hydratable
|
||||||
|
|
||||||
|
> A `hydratable` value with key `%key%` was created, but at least part of it was not used during the render.
|
||||||
|
>
|
||||||
|
> The `hydratable` was initialized in:
|
||||||
|
> %stack%
|
||||||
|
|
||||||
|
The most likely cause of this is creating a `hydratable` in the `script` block of your component and then `await`ing
|
||||||
|
the result inside a `svelte:boundary` with a `pending` snippet:
|
||||||
|
|
||||||
|
```svelte
|
||||||
|
<script>
|
||||||
|
import { hydratable } from 'svelte';
|
||||||
|
import { getUser } from '$lib/get-user.js';
|
||||||
|
|
||||||
|
const user = hydratable('user', getUser);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<svelte:boundary>
|
||||||
|
<h1>{(await user).name}</h1>
|
||||||
|
|
||||||
|
{#snippet pending()}
|
||||||
|
<div>Loading...</div>
|
||||||
|
{/snippet}
|
||||||
|
</svelte:boundary>
|
||||||
|
```
|
||||||
|
|
||||||
|
Consider inlining the `hydratable` call inside the boundary so that it's not called on the server.
|
||||||
|
|
||||||
|
Note that this can also happen when a `hydratable` contains multiple promises and some but not all of them have been used.
|
||||||
@ -0,0 +1,15 @@
|
|||||||
|
/** @import { Context } from '../types' */
|
||||||
|
/** @import { AST } from '#compiler'; */
|
||||||
|
import * as e from '../../../errors.js';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {AST.AnimateDirective} node
|
||||||
|
* @param {Context} context
|
||||||
|
*/
|
||||||
|
export function AnimateDirective(node, context) {
|
||||||
|
context.next({ ...context.state, expression: node.metadata.expression });
|
||||||
|
|
||||||
|
if (node.metadata.expression.has_await) {
|
||||||
|
e.illegal_await_expression(node);
|
||||||
|
}
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue