Merge branch 'main' into svelte-custom-renderer

svelte-custom-renderer
Paolo Ricciuti 7 hours ago committed by GitHub
commit c92989ad10
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -0,0 +1,5 @@
---
'svelte': patch
---
fix: rethrow error of failed iterable after calling `return()`

@ -0,0 +1,5 @@
---
'svelte': patch
---
fix: wrap `Promise.all` in `save` during SSR

@ -12,7 +12,7 @@ import {
import * as b from '#compiler/builders';
import { sanitize_template_string } from '../../../../../utils/sanitize_template_string.js';
import { regex_whitespaces_strict } from '../../../../patterns.js';
import { has_await_expression } from '../../../../../utils/ast.js';
import { has_await_expression, save } from '../../../../../utils/ast.js';
import { ExpressionMetadata } from '../../../../nodes.js';
/** Opens an if/each block, so that we can remove nodes in the case of a mismatch */
@ -360,7 +360,7 @@ export class PromiseOptimiser {
return b.const(
b.array_pattern(this.expressions.map((_, i) => b.id(`$$${i}`))),
b.await(b.call('Promise.all', promises))
save(b.call('Promise.all', promises))
);
}

@ -217,22 +217,35 @@ export async function* for_await_track_reactivity_loss(iterable) {
throw new TypeError('value is not async iterable');
}
/** Whether the completion of the iterator was "normal", meaning it wasn't ended via `break` or a similar method */
let normal_completion = false;
// eslint-disable-next-line no-useless-assignment
let invoke_return = true;
try {
while (true) {
const { done, value } = (await track_reactivity_loss(iterator.next()))();
if (done) {
normal_completion = true;
invoke_return = false;
break;
}
var prev = reactivity_loss_tracker;
yield value;
try {
yield value;
} catch (e) {
set_reactivity_loss_tracker(prev);
// If the yield throws, we need to call `return` but not return its value, instead rethrow
if (iterator.return !== undefined) {
(await track_reactivity_loss(iterator.return()))();
}
throw e;
}
set_reactivity_loss_tracker(prev);
}
} catch (error) {
invoke_return = false;
throw error;
} finally {
// If the iterator had an abrupt completion and `return` is defined on the iterator, call it and return the value
if (!normal_completion && iterator.return !== undefined) {
// If the iterator had an abrupt completion (break) and `return` is defined on the iterator, call it and return the value
if (invoke_return && iterator.return !== undefined) {
// eslint-disable-next-line no-unsafe-finally
return /** @type {TReturn} */ ((await track_reactivity_loss(iterator.return()))().value);
}

@ -0,0 +1,24 @@
import { tick } from 'svelte';
import { test } from '../../test';
import { normalise_trace_logs } from '../../../helpers.js';
export default test({
compileOptions: {
dev: true
},
html: '<p>pending</p>',
async test({ assert, target, warnings }) {
await tick();
assert.htmlEqual(
target.innerHTML,
'<h1>number -> number -> number -> return -> body failed -> ended</h1>'
);
assert.deepEqual(normalise_trace_logs(warnings), [
{
log: 'Detected reactivity loss when reading `values[1]`. This happens when state is read in an async function after an earlier `await`'
}
]);
}
});

@ -0,0 +1,45 @@
<script>
let values = $state([0, 1, 2]);
async function get_result() {
const logs = [];
const iterator = {
index: 0,
async next() {
if (this.index > 2) { done: true }
return { done: false, value: values[this.index++] };
},
async return() {
logs.push('return');
},
[Symbol.asyncIterator]() {
return this;
}
};
try {
for await (const value of iterator) {
logs.push('number');
// Read reactive state after async iterator await.
if (values.length === 3 && value === 2) {
throw new Error('body failed');
}
}
logs.push('done');
} catch (error) {
logs.push(error.message);
}
logs.push('ended');
return logs.join(' -> ');
}
</script>
<svelte:boundary>
<h1>{await get_result()}</h1>
{#snippet pending()}
<p>pending</p>
{/snippet}
</svelte:boundary>

@ -0,0 +1,21 @@
import { tick } from 'svelte';
import { test } from '../../test';
import { normalise_trace_logs } from '../../../helpers.js';
export default test({
compileOptions: {
dev: true
},
html: '<p>pending</p>',
async test({ assert, target, warnings }) {
await tick();
assert.htmlEqual(target.innerHTML, '<h1>number -> number -> next failed -> ended</h1>');
assert.deepEqual(normalise_trace_logs(warnings), [
{
log: 'Detected reactivity loss when reading `values[1]`. This happens when state is read in an async function after an earlier `await`'
}
]);
}
});

@ -0,0 +1,43 @@
<script>
let values = $state([0, 1, 2]);
async function get_result() {
const logs = [];
const iterator = {
index: 0,
async next() {
if (this.index > 1) throw new Error('next failed');
return { done: false, value: values[this.index++] };
},
async return() {
logs.push('return');
},
[Symbol.asyncIterator]() {
return this;
}
};
try {
for await (const value of iterator) {
logs.push('number');
// Read reactive state after async iterator await.
values.length === value;
}
logs.push('done');
} catch (error) {
logs.push(error.message);
}
logs.push('ended');
return logs.join(' -> ');
}
</script>
<svelte:boundary>
<h1>{await get_result()}</h1>
{#snippet pending()}
<p>pending</p>
{/snippet}
</svelte:boundary>

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

@ -0,0 +1 @@
<!--1410iyz--><!----><title>Async multiple attributes</title>

@ -0,0 +1,15 @@
<script>
const user = $derived(Promise.resolve({
name: 'test',
image: '',
}))
</script>
<svelte:head>
<title>Async multiple attributes</title>
</svelte:head>
<img
alt={(await user).name}
src={(await user).image}
/>
Loading…
Cancel
Save