8000 fix: make deriveds on the server lazy again (#15964) · sveltejs/svelte@ac046df · GitHub
[go: up one dir, main page]

Skip to content

Commit ac046df

Browse files
fix: make deriveds on the server lazy again (#15964)
* 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 * fix: handle basic assignment of deriveds on the server * fix: use `build_assignment_value` for deriveds assignments * use once * allow writing to public deriveds on server --------- Co-authored-by: paoloricciuti <ricciutipaolo@gmail.com>
1 parent 9250c9c commit ac046df

File tree

12 files changed

+116
-9
lines changed

12 files changed

+116
-9
lines changed

.changeset/khaki-carrots-destroy.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'svelte': patch
3+
---
4+
5+
fix: make deriveds on the server lazy again

packages/svelte/src/compiler/phases/3-transform/server/transform-server.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import { Identifier } from './visitors/Identifier.js';
2323
import { IfBlock } from './visitors/IfBlock.js';
2424
import { KeyBlock } from './visitors/KeyBlock.js';
2525
import { LabeledStatement } from './visitors/LabeledStatement.js';
26+
import { MemberExpression } from './visitors/MemberExpression.js';
2627
import { PropertyDefinition } from './visitors/PropertyDefinition.js';
2728
import { RegularElement } from './visitors/RegularElement.js';
2829
import { RenderTag } from './visitors/RenderTag.js';
@@ -48,6 +49,7 @@ const global_visitors = {
4849
ExpressionStatement,
4950
Identifier,
5051
LabeledStatement,
52+
MemberExpression,
5153
PropertyDefinition,
5254
UpdateExpression,
5355
VariableDeclaration

packages/svelte/src/compiler/phases/3-transform/server/visitors/AssignmentExpression.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,11 @@ function build_assignment(operator, left, right, context) {
4444
/** @type {Expression} */ (context.visit(right))
4545
);
4646
}
47+
} else if (field && (field.type === '$derived' || field.type === '$derived.by')) {
48+
let value = /** @type {Expression} */ (
49+
context.visit(build_assignment_value(operator, left, right))
50+
);
51+
return b.call(b.member(b.this, name), value);
4752
}
4853
}
4954

packages/svelte/src/compiler/phases/3-transform/server/visitors/CallExpression.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ export function CallExpression(node, context) {
3131

3232
if (rune === '$derived' || rune === '$derived.by') {
3333
const fn = /** @type {Expression} */ (context.visit(node.arguments[0]));
34-
return b.call('$.once', rune === '$derived' ? b.thunk(fn) : fn);
34+
return b.call('$.derived', rune === '$derived' ? b.thunk(fn) : fn);
3535
}
3636

3737
if (rune === '$state.snapshot') {

packages/svelte/src/compiler/phases/3-transform/server/visitors/ClassBody.js

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,8 @@ export function ClassBody(node, context) {
3636

3737
body.push(
3838
b.prop_def(field.key, null),
39-
b.method('get', b.key(name), [], [b.return(b.call(member))])
39+
b.method('get', b.key(name), [], [b.return(b.call(member))]),
40+
b.method('set', b.key(name), [b.id('$$value')], [b.return(b.call(member, b.id('$$value')))])
4041
);
4142
}
4243
}
@@ -61,6 +62,7 @@ export function ClassBody(node, context) {
6162
if (name[0] === '#' || field.type === '$state' || field.type === '$state.raw') {
6263
body.push(/** @type {PropertyDefinition} */ (context.visit(definition, child_state)));
6364
} else if (field.node === definition) {
65+
// $derived / $derived.by
6466
const member = b.member(b.this, field.key);
6567

6668
body.push(
@@ -69,7 +71,8 @@ export function ClassBody(node, context) {
6971
/** @type {CallExpression} */ (context.visit(field.value, child_state))
7072
),
7173

72-
b.method('get', definition.key, [], [b.return(b.call(member))])
74+
b.method('get', definition.key, [], [b.return(b.call(member))]),
75+
b.method('set', b.key(name), [b.id('$$value')], [b.return(b.call(member, b.id('$$value')))])
7376
);
7477
}
7578
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
/** @import { ClassBody, MemberExpression } from 'estree' */
2+
/** @import { Context } from '../types.js' */
3+
import * as b from '#compiler/builders';
4+
5+
/**
6+
* @param {MemberExpression} node
7+
* @param {Context} context
8+
*/
9+
export function MemberExpression(node, context) {
10+
if (
11+
context.state.analysis.runes &&
12+
node.object.type === 'ThisExpression' &&
13+
node.property.type === 'PrivateIdentifier'
14+
) {
15+
const field = context.state.state_fields?.get(`#${node.property.name}`);
16+
17+
if (field?.type === '$derived' || field?.type === '$derived.by') {
18+
return b.call(node);
19+
}
20+
}
21+
22+
context.next();
23+
}

packages/svelte/src/compiler/phases/3-transform/server/visitors/PropertyDefinition.js

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ export function PropertyDefinition(node, context) {
1111
if (context.state.analysis.runes && node.value != null && node.value.type === 'CallExpression') {
1212
const rune = get_rune(node.value, context.state.scope);
1313

14-
if (rune === '$state' || rune === '$state.raw' || rune === '$derived') {
14+
if (rune === '$state' || rune === '$state.raw') {
1515
return {
1616
...node,
1717
value:
@@ -21,13 +21,14 @@ export function PropertyDefinition(node, context) {
2121
};
2222
}
2323

24-
if (rune === '$derived.by') {
24+
if (rune === '$derived.by' || rune === '$derived') {
25+
const fn = /** @type {Expression} */ (context.visit(node.value.arguments[0]));
2526
return {
2627
...node,
2728
value:
2829
node.value.arguments.length === 0
2930
? null
30-
: b.call(/** @type {Expression} */ (context.visit(node.value.arguments[0])))
31+
: b.call('$.derived', rune === '$derived' ? b.thunk(fn) : fn)
3132
};
3233
}
3334
}

packages/svelte/src/internal/server/index.js

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -514,3 +514,24 @@ export {
514514
} from '../shared/validate.js';
515515

516516
export { escape_html as escape };
517+
518+
/**
519+
* @template T
520+
* @param {()=>T} fn
521+
* @returns {(new_value?: T) => (T | void)}
522+
*/
523+
export function derived(fn) {
524+
const get_value = once(fn);
525+
/**
526+
* @type {T | undefined}
527+
*/
528+
let updated_value;
529+
530+
return function (new_value) {
531+
if (arguments.length === 0) {
532+
return updated_value ?? get_value();
533+
}
534+
updated_value = new_value;
535+
return updated_value;
536+
};
537+
}

packages/svelte/tests/runtime-runes/samples/class-state-derived-private/_config.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ export default test({
55
html: `
66
<button>0</button>
77
<p>doubled: 0</p>
8+
<p>tripled: 0</p>
89
`,
910

1011
test({ assert, target }) {
@@ -17,6 +18,7 @@ export default test({
1718
`
1819
<button>1</button>
1920
<p>doubled: 2</p>
21+
<p>tripled: 3</p>
2022
`
2123
);
2224

@@ -27,6 +29,7 @@ export default test({
2729
`
2830
<button>2</button>
2931
<p>doubled: 4</p>
32+
<p>tripled: 6</p>
3033
`
3134
);
3235
}

packages/svelte/tests/runtime-runes/samples/class-state-derived-private/main.svelte

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,24 @@
22
class Counter {
33
count = $state(0);
44
#doubled = $derived(this.count * 2);
5+
#tripled = $derived.by(() => this.count * this.by);
56
6-
get embiggened() {
7+
constructor(by) {
8+
this.by = by;
9+
}
10+
11+
get embiggened1() {
712
return this.#doubled;
813
}
14+
15+
get embiggened2() {
16+
return this.#tripled;
17+
}
918
}
1019
11-
const counter = new Counter();
20+
const counter = new Counter(3);
1221
</script>
1322

1423
<button on:click={() => counter.count++}>{counter.count}</button>
15-
<p>doubled: {counter.embiggened}</p>
24+
<p>doubled: {counter.embiggened1}</p>
25+
<p>tripled: {counter.embiggened2}</p>
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import { test } from '../../test';
2+
3+
export default test({
4+
html: `3 3 3 3`
5+
});
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
<script>
2+
class X {
3+
x = $state(1);
4+
on_class = $derived(this.x * 2);
5+
#on_class_private = $derived(this.x * 2);
6+
#in_constructor_private
7+
8+
constructor() {
9+
this.#in_constructor_private = $derived(this.x * 2);
10+
this.in_constructor = $derived(this.x * 2);
11+
this.#on_class_private = 3;
12+
this.#in_constructor_private = 3;
13+
}
14+
15+
get on_class_private() {
16+
return this.#on_class_private;
17+
}
18+
19+
get in_constructor_private() {
20+
return this.#in_constructor_private;
21+
}
22+
}
23+
24+
const x = new X();
25+
x.on_class = 3;
26+
x.in_constructor = 3;
27+
</script>
28+
29+
{x.on_class} {x.in_constructor} {x.on_class_private} {x.in_constructor_private}

0 commit comments

Comments
 (0)
0