Merge branch 'main' into svelte-custom-renderer

pull/18244/head
Paolo Ricciuti 2 months ago committed by GitHub
commit 8d5f108c31
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -0,0 +1,5 @@
---
'svelte': patch
---
fix: inline primitive constants in attribute values during SSR

@ -163,6 +163,8 @@ export const [getCounter, setCounter] = createContext<Counter>();
Svelte will warn you if you get it wrong.
Similarly, to pass primitive values through context, use functions as described in [Passing state into functions]($state#Passing-state-into-functions).
## Component testing
When writing [component tests](testing#Unit-and-component-tests-with-Vitest-Component-testing), it can be useful to create a wrapper component that sets the context in order to check the behaviour of a component that uses it. As of version 5.49, you can do this sort of thing:

@ -1,5 +1,19 @@
# svelte
## 5.55.8
### Patch Changes
- fix(print): handle `svelte:body` and fix keyframe percentage double-printing ([#18234](https://github.com/sveltejs/svelte/pull/18234))
- fix: execute uninitialized derived even if it's destroyed ([#18228](https://github.com/sveltejs/svelte/pull/18228))
- fix: use named symbols everywhere ([#18238](https://github.com/sveltejs/svelte/pull/18238))
- fix: don't run teardown effects when deriveds are unfreezed ([#18227](https://github.com/sveltejs/svelte/pull/18227))
- fix: unset context synchronously in `run` ([#18236](https://github.com/sveltejs/svelte/pull/18236))
## 5.55.7
### Patch Changes

@ -2,7 +2,7 @@
"name": "svelte",
"description": "Cybernetically enhanced web apps",
"license": "MIT",
"version": "5.55.7",
"version": "5.55.8",
"type": "module",
"types": "./types/index.d.ts",
"engines": {

@ -229,18 +229,25 @@ export function build_attribute_value(
? node.data.replace(regex_whitespaces_strict, ' ')
: node.data;
} else {
expressions.push(
b.call(
'$.stringify',
transform(
/** @type {Expression} */ (context.visit(node.expression)),
node.metadata.expression
)
)
);
const evaluated = context.state.scope.evaluate(node.expression);
quasi = b.quasi('', i + 1 === value.length);
quasis.push(quasi);
if (evaluated.is_known) {
quasi.value.cooked += (evaluated.value ?? '') + '';
} else {
const expression = transform(
/** @type {Expression} */ (context.visit(node.expression)),
node.metadata.expression
);
expressions.push(
evaluated.is_string && evaluated.is_defined
? expression
: b.call('$.stringify', expression)
);
quasi = b.quasi('', i + 1 === value.length);
quasis.push(quasi);
}
}
}
@ -248,7 +255,9 @@ export function build_attribute_value(
quasi.value.raw = sanitize_template_string(/** @type {string} */ (quasi.value.cooked));
}
return b.template(quasis, expressions);
return expressions.length > 0
? b.template(quasis, expressions)
: b.literal(/** @type {string} */ (quasi.value.cooked));
}
/**

@ -247,7 +247,7 @@ const css_visitors = {
},
Percentage(node, context) {
context.write(`${node.value}%`);
context.write(node.value);
},
PseudoClassSelector(node, context) {
@ -417,6 +417,7 @@ const svelte_visitors = (comments) => ({
const is_block_element =
child_node.type === 'RegularElement' ||
child_node.type === 'Component' ||
child_node.type === 'SvelteBody' ||
child_node.type === 'SvelteHead' ||
child_node.type === 'SvelteFragment' ||
child_node.type === 'SvelteBoundary' ||
@ -821,6 +822,10 @@ const svelte_visitors = (comments) => ({
context.write('</style>');
},
SvelteBody(node, context) {
base_element(node, context, comments);
},
SvelteBoundary(node, context) {
base_element(node, context, comments);
},

@ -32,7 +32,7 @@ export const ELEMENT_IS_NAMESPACED = 1;
export const ELEMENT_PRESERVE_ATTRIBUTE_CASE = 1 << 1;
export const ELEMENT_IS_INPUT = 1 << 2;
export const UNINITIALIZED = Symbol();
export const UNINITIALIZED = Symbol('uninitialized');
// Dev-time component properties
export const FILENAME = Symbol('filename');

@ -308,15 +308,21 @@ export function run(thunks) {
.then(() => {
restore();
if (errored) {
throw errored.error;
try {
if (errored) {
throw errored.error;
}
if (aborted(active)) {
throw STALE_REACTION;
}
return fn();
} finally {
// We gotta unset context directly in case the function returns a promise, in which case
// unset_context in .finally() would be too late ...
unset_context();
}
if (aborted(active)) {
throw STALE_REACTION;
}
return fn();
})
.catch(handle_error);
@ -325,6 +331,7 @@ export function run(thunks) {
promise.finally(() => {
blocker.settled = true;
// ... but we also need it after such a promise has resolved in case it restores our context
unset_context();
});
}

@ -338,7 +338,12 @@ export function execute_derived(derived) {
var prev_active_effect = active_effect;
var parent = derived.parent;
if (!is_destroying_effect && parent !== null && (parent.f & (DESTROYED | INERT)) !== 0) {
if (
!is_destroying_effect &&
parent !== null &&
derived.v !== UNINITIALIZED && // if it was never evaluated before, it's guaranteed to fail downstream, so we try to execute instead
(parent.f & (DESTROYED | INERT)) !== 0
) {
w.derived_inert();
return derived.v;
@ -447,8 +452,8 @@ export function freeze_derived_effects(derived) {
// make it a noop so it doesn't get called again if the derived
// is unfrozen. we don't set it to `null`, because the existence
// of a teardown function is what determines whether the
// effect runs again during unfreezing
e.teardown = noop;
// effect runs again during unfreezing (but not for teardown-only effects)
if (e.fn !== null) e.teardown = noop;
e.ac = null;
remove_reactions(e, 0);
@ -466,7 +471,7 @@ export function unfreeze_derived_effects(derived) {
for (const e of derived.effects) {
// if the effect was previously frozen — indicated by the presence
// of a teardown function — unfreeze it
if (e.teardown) {
if (e.teardown && e.fn !== null) {
update_effect(e);
}
}

@ -21,7 +21,7 @@ export let legacy_is_updating_store = false;
*/
let is_store_binding = false;
let IS_UNMOUNTED = Symbol();
let IS_UNMOUNTED = Symbol('unmounted');
/**
* Gets the current value of a store. If the store isn't subscribed to yet, it will create a proxy

@ -4,7 +4,7 @@ import { tag } from '../internal/client/dev/tracing.js';
import { get } from '../internal/client/runtime.js';
import { get_current_url } from './url.js';
export const REPLACE = Symbol();
export const REPLACE = Symbol('replace');
/**
* A reactive version of the built-in [`URLSearchParams`](https://developer.mozilla.org/en-US/docs/Web/API/URLSearchParams) object.

@ -4,5 +4,5 @@
* The current version, as set in package.json.
* @type {string}
*/
export const VERSION = '5.55.7';
export const VERSION = '5.55.8';
export const PUBLIC_VERSION = '5';

@ -0,0 +1,7 @@
<style>
@keyframes foo {
0% { left: 0px; }
50% { left: 50px; }
100% { left: 100px; }
}
</style>

@ -0,0 +1,13 @@
<style>
@keyframes foo {
0% {
left: 0px;
}
50% {
left: 50px;
}
100% {
left: 100px;
}
}
</style>

@ -19,7 +19,7 @@
from {
opacity: 0;
}
50%% {
50% {
opacity: 0.5;
}
to {

@ -0,0 +1 @@
<svelte:body onmousemove={handleMousemove} />

@ -0,0 +1 @@
<svelte:body onmousemove={handleMousemove}></svelte:body>

@ -0,0 +1,7 @@
<script>
import { push } from "./main.svelte";
const x = await push(1);
const y = await push(2);
</script>
{x} {y}

@ -0,0 +1,26 @@
import { tick } from 'svelte';
import { test } from '../../test';
export default test({
async test({ assert, target }) {
const [show, resolve, count] = target.querySelectorAll('button');
show.click();
await tick();
resolve.click();
await tick();
count.click();
await tick();
assert.htmlEqual(
target.innerHTML,
'<button>show</button> <button>resolve</button> <button>1</button>'
);
resolve.click();
await tick();
assert.htmlEqual(
target.innerHTML,
'1 2 <button>show</button> <button>resolve</button> <button>1</button>'
);
}
});

@ -0,0 +1,23 @@
<script module>
const queued = [];
export function push(v) {
return new Promise((fulfil) => {
queued.push(() => fulfil(v));
});
}
</script>
<script>
import Child from "./Child.svelte";
let show = $state(false);
let count = $state(0);
</script>
{#if show}
<Child />
{/if}
<button onclick={() => show = true}>show</button>
<button onclick={() => queued.shift()?.()}>resolve</button>
<button onclick={() => count++}>{count}</button>

@ -0,0 +1,21 @@
import { tick } from 'svelte';
import { test } from '../../test';
export default test({
compileOptions: { dev: true }, // for testing that teardown effect in eager $.get(loaded) doesn't lead to a crash (because it means REACTION_RAN is set, which means unfreeze_derived runs)
async test({ assert, target }) {
const [count, shift] = target.querySelectorAll('button');
shift.click();
await tick();
assert.htmlEqual(target.innerHTML, `<button>0</button><button>shift</button><p>0</p>`);
count.click();
await tick();
assert.htmlEqual(target.innerHTML, `<button>1</button><button>shift</button><p>0 (...)</p>`);
shift.click();
await tick();
assert.htmlEqual(target.innerHTML, `<button>1</button><button>shift</button><p>1</p>`);
}
});

@ -0,0 +1,21 @@
<script>
let count = $state(0);
let resolvers = [];
function push(value) {
const { promise, resolve } = Promise.withResolvers();
resolvers.push(() => resolve(value));
return promise;
}
</script>
<button onclick={() => count += 1}>{$state.eager(count)}</button>
<button onclick={() => resolvers.shift()?.()}>shift</button>
<svelte:boundary>
{@const loaded = $state.eager(count) === count}
<p>{await push(count)} {loaded ? '' : '(...)'}</p>
{#snippet pending()}{/snippet}
</svelte:boundary>

@ -0,0 +1,10 @@
<script>
import { fly } from 'svelte/transition';
// Not read before the outro, by which time the derived is destroyed
let duration = $derived(10);
</script>
<div in:fly={{ duration: 10 }} out:fly={{ duration }}>
hello
</div>

@ -0,0 +1,32 @@
import { flushSync } from 'svelte';
import { raf } from '../../../animation-helpers';
import { test } from '../../test';
export default test({
async test({ assert, target }) {
const [fly_in, fly_out] = target.querySelectorAll('button');
fly_in.click();
flushSync();
raf.tick(25);
assert.htmlEqual(
target.innerHTML,
`
<button>fly in</button>
<button>fly out</button>
<div style="">hello</div>
`
);
fly_out.click();
flushSync();
raf.tick(50);
assert.htmlEqual(
target.innerHTML,
`
<button>fly in</button>
<button>fly out</button>
`
);
}
});

@ -0,0 +1,11 @@
<script>
import Child from './Child.svelte';
let show = $state(false);
</script>
<button onclick={() => (show = true)}>fly in</button>
<button onclick={() => (show = false)}>fly out</button>
{#if show}
<Child />
{/if}

@ -2,9 +2,9 @@ import 'svelte/internal/init-operations';
import 'svelte/internal/disclose-version';
import * as $ from 'svelte/internal/client';
var root = $.from_html(`<h1></h1> <b></b> <button> </button> <h1></h1>`, 1);
var root = $.from_html(`<h1></h1> <b></b> <button> </button> <h1></h1> <div></div>`, 1);
export default function Nullish_coallescence_omittance($$anchor) {
export default function Nullish_coallescence_omittance($$anchor, $$props) {
let name = 'world';
let count = $.state(0);
var fragment = root();
@ -24,7 +24,14 @@ export default function Nullish_coallescence_omittance($$anchor) {
var h1_1 = $.sibling(button, 2);
h1_1.textContent = 'Hello, world';
$.template_effect(() => $.set_text(text, `Count is ${$.get(count) ?? ''}`));
var div = $.sibling(h1_1, 2);
$.template_effect(() => {
$.set_text(text, `Count is ${$.get(count) ?? ''}`);
$.set_attribute(div, 'title', `Hello, world ${$.get(count) ?? ''} 1 ${typeof $$props.value} ${$$props.value ?? ''}`);
});
$.delegated('click', button, () => $.update(count));
$.append($$anchor, fragment);
}

@ -1,8 +1,9 @@
import * as $ from 'svelte/internal/server';
export default function Nullish_coallescence_omittance($$renderer) {
export default function Nullish_coallescence_omittance($$renderer, $$props) {
let name = 'world';
let count = 0;
let { value } = $$props;
$$renderer.push(`<h1>Hello, world!</h1> <b>123</b> <button>Count is ${$.escape(count)}</button> <h1>Hello, world</h1>`);
$$renderer.push(`<h1>Hello, world!</h1> <b>123</b> <button>Count is ${$.escape(count)}</button> <h1>Hello, world</h1> <div${$.attr('title', `Hello, world ${$.stringify(count)} 1 ${typeof value} ${$.stringify(value)}`)}></div>`);
}

@ -1,8 +1,10 @@
<script>
let name = 'world';
let count = $state(0);
let { value } = $props();
</script>
<h1>Hello, {null}{name}!</h1>
<b>{1 ?? 'stuff'}{2 ?? 'more stuff'}{3 ?? 'even more stuff'}</b>
<button onclick={()=>count++}>Count is {count}</button>
<h1>Hello, {name ?? 'earth' ?? null}</h1>
<h1>Hello, {name ?? 'earth' ?? null}</h1>
<div title="Hello, {name} {count} {null} {1} {undefined} {typeof value} {value}"></div>

Loading…
Cancel
Save