fix: make deriveds on the server lazy again

Fixes a regression introduced in #15820: deriveds need to be lazily called on the server, too, since they can close over variables only later defined

Fixes #15960
pull/15964/head
Simon Holthausen 4 months ago
parent 7183886a73
commit e6c4e8c5c5

@ -0,0 +1,5 @@
---
'svelte': patch
---
fix: make deriveds on the server lazy again

@ -23,6 +23,7 @@ import { Identifier } from './visitors/Identifier.js';
import { IfBlock } from './visitors/IfBlock.js'; import { IfBlock } from './visitors/IfBlock.js';
import { KeyBlock } from './visitors/KeyBlock.js'; import { KeyBlock } from './visitors/KeyBlock.js';
import { LabeledStatement } from './visitors/LabeledStatement.js'; import { LabeledStatement } from './visitors/LabeledStatement.js';
import { MemberExpression } from './visitors/MemberExpression.js';
import { PropertyDefinition } from './visitors/PropertyDefinition.js'; import { PropertyDefinition } from './visitors/PropertyDefinition.js';
import { RegularElement } from './visitors/RegularElement.js'; import { RegularElement } from './visitors/RegularElement.js';
import { RenderTag } from './visitors/RenderTag.js'; import { RenderTag } from './visitors/RenderTag.js';
@ -48,6 +49,7 @@ const global_visitors = {
ExpressionStatement, ExpressionStatement,
Identifier, Identifier,
LabeledStatement, LabeledStatement,
MemberExpression,
PropertyDefinition, PropertyDefinition,
UpdateExpression, UpdateExpression,
VariableDeclaration VariableDeclaration

@ -22,10 +22,6 @@ export function ClassBody(node, context) {
const child_state = { ...context.state, state_fields }; const child_state = { ...context.state, state_fields };
for (const [name, field] of state_fields) { for (const [name, field] of state_fields) {
if (name[0] === '#') {
continue;
}
// insert backing fields for stuff declared in the constructor // insert backing fields for stuff declared in the constructor
if ( if (
field && field &&

@ -0,0 +1,23 @@
/** @import { ClassBody, MemberExpression } from 'estree' */
/** @import { Context } from '../types.js' */
import * as b from '#compiler/builders';
/**
* @param {MemberExpression} node
* @param {Context} context
*/
export function MemberExpression(node, context) {
if (
context.state.analysis.runes &&
node.object.type === 'ThisExpression' &&
node.property.type === 'PrivateIdentifier'
) {
const field = context.state.state_fields?.get(`#${node.property.name}`);
if (field?.type === '$derived' || field?.type === '$derived.by') {
return b.call(node);
}
}
context.next();
}

@ -11,7 +11,7 @@ export function PropertyDefinition(node, context) {
if (context.state.analysis.runes && node.value != null && node.value.type === 'CallExpression') { if (context.state.analysis.runes && node.value != null && node.value.type === 'CallExpression') {
const rune = get_rune(node.value, context.state.scope); const rune = get_rune(node.value, context.state.scope);
if (rune === '$state' || rune === '$state.raw' || rune === '$derived') { if (rune === '$state' || rune === '$state.raw') {
return { return {
...node, ...node,
value: value:
@ -21,13 +21,14 @@ export function PropertyDefinition(node, context) {
}; };
} }
if (rune === '$derived.by') { if (rune === '$derived.by' || rune === '$derived') {
const fn = /** @type {Expression} */ (context.visit(node.value.arguments[0]));
return { return {
...node, ...node,
value: value:
node.value.arguments.length === 0 node.value.arguments.length === 0
? null ? null
: b.call(/** @type {Expression} */ (context.visit(node.value.arguments[0]))) : b.call('$.once', rune === '$derived' ? b.thunk(fn) : fn)
}; };
} }
} }

@ -5,6 +5,7 @@ export default test({
html: ` html: `
<button>0</button> <button>0</button>
<p>doubled: 0</p> <p>doubled: 0</p>
<p>trippled: 0</p>
`, `,
test({ assert, target }) { test({ assert, target }) {
@ -17,6 +18,7 @@ export default test({
` `
<button>1</button> <button>1</button>
<p>doubled: 2</p> <p>doubled: 2</p>
<p>trippled: 3</p>
` `
); );
@ -27,6 +29,7 @@ export default test({
` `
<button>2</button> <button>2</button>
<p>doubled: 4</p> <p>doubled: 4</p>
<p>trippled: 9</p>
` `
); );
} }

@ -2,14 +2,24 @@
class Counter { class Counter {
count = $state(0); count = $state(0);
#doubled = $derived(this.count * 2); #doubled = $derived(this.count * 2);
#trippled = $derived.by(() => this.count * this.by);
get embiggened() { constructor(by) {
this.by = by;
}
get embiggened1() {
return this.#doubled; return this.#doubled;
} }
get embiggened2() {
return this.#trippled;
}
} }
const counter = new Counter(); const counter = new Counter(3);
</script> </script>
<button on:click={() => counter.count++}>{counter.count}</button> <button on:click={() => counter.count++}>{counter.count}</button>
<p>doubled: {counter.embiggened}</p> <p>doubled: {counter.embiggened1}</p>
<p>trippled: {counter.embiggened2}</p>

Loading…
Cancel
Save