Skip to content

Commit f1f2d68

Browse files
committed
WIP
1 parent 830de56 commit f1f2d68

File tree

11 files changed

+98
-103
lines changed

11 files changed

+98
-103
lines changed

packages/svelte/src/ambient.d.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -253,7 +253,9 @@ declare namespace $effect {
253253
* https://svelte.dev/docs/svelte/$effect#$effect.pre
254254
* @param fn The function to execute
255255
*/
256-
export function pre(fn: () => void | (() => void), options?: { bind: any }): void;
256+
export function pre(fn: () => void | (() => void)): void;
257+
258+
export function lazy(state: any, fn: () => void | (() => void)): void;
257259

258260
/**
259261
* The `$effect.tracking` rune is an advanced feature that tells you whether or not the code is running inside a tracking context, such as an effect or inside your template.

packages/svelte/src/compiler/phases/2-analyze/visitors/CallExpression.js

Lines changed: 20 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -91,50 +91,32 @@ export function CallExpression(node, context) {
9191

9292
break;
9393
case '$effect':
94-
case '$effect.pre': {
95-
const grand_parent = /** @type {SvelteNode} */ (get_parent(context.path, -2));
96-
94+
case '$effect.pre':
95+
case '$effect.lazy': {
9796
if (parent.type !== 'ExpressionStatement') {
9897
e.effect_invalid_placement(node);
9998
}
10099

101-
if (rune === '$effect.pre') {
102-
if (node.arguments.length !== 1 && node.arguments.length !== 2) {
103-
e.rune_invalid_arguments_length(node, rune, 'exactly one or two arguments');
100+
if (rune === '$effect.lazy') {
101+
if (node.arguments.length !== 2) {
102+
e.rune_invalid_arguments_length(node, rune, 'exactly two arguments');
103+
}
104+
const state_ref = node.arguments[0];
105+
106+
if (state_ref.type !== 'Identifier') {
107+
throw new Error('Bad $effect.lazy, first argument must be an identifier');
104108
}
109+
const scope = context.state.scope;
110+
const binding = /** @type {Binding} */ (scope.get(state_ref.name));
105111

106-
if (node.arguments.length === 2) {
107-
const linked = node.arguments[1];
108-
109-
if (linked.type !== 'ObjectExpression') {
110-
throw new Error('Bad $effect.pre, second argument must be an object');
111-
}
112-
const scope = context.state.scope;
113-
114-
for (const property of linked.properties) {
115-
if (
116-
property.type !== 'Property' ||
117-
property.computed ||
118-
property.key.type !== 'Identifier' ||
119-
property.key.name !== 'bind' ||
120-
property.value.type !== 'Identifier'
121-
) {
122-
throw new Error(
123-
'Bad $effect.pre, second argument must be an object simply properties'
124-
);
125-
}
126-
const binding = /** @type {Binding} */ (scope.get(property.value.name));
127-
128-
if (binding === null || binding.kind !== 'state') {
129-
throw new Error('Bad $effect.pre link, must be a local reference to $state');
130-
}
131-
const link = scope.generate('effect');
132-
(binding.linked_effects ??= []).push(link);
133-
/** @type {SimpleCallExpression} */ (node).metadata = {
134-
link
135-
};
136-
}
112+
if (binding === null || binding.kind !== 'state') {
113+
throw new Error('Bad $effect.lazy link, must be a local reference to $state');
137114
}
115+
const id = scope.generate('effect');
116+
(binding.linked_effects ??= []).push(id);
117+
/** @type {SimpleCallExpression} */ (node).metadata = {
118+
id
119+
};
138120
} else if (node.arguments.length !== 1) {
139121
e.rune_invalid_arguments_length(node, rune, 'exactly one argument');
140122
}
@@ -203,7 +185,7 @@ export function CallExpression(node, context) {
203185
}
204186

205187
// `$inspect(foo)` or `$derived(foo) should not trigger the `static-state-reference` warning
206-
if (rune === '$inspect' || rune === '$derived' || rune === '$effect.pre') {
188+
if (rune === '$inspect' || rune === '$derived' || rune === '$effect.lazy') {
207189
context.next({ ...context.state, function_depth: context.state.function_depth + 1 });
208190
} else {
209191
context.next();

packages/svelte/src/compiler/phases/3-transform/client/visitors/ExpressionStatement.js

Lines changed: 14 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -10,34 +10,28 @@ import { get_rune } from '../../../scope.js';
1010
export function ExpressionStatement(node, context) {
1111
if (node.expression.type === 'CallExpression') {
1212
const rune = get_rune(node.expression, context.state.scope);
13-
const callee = rune === '$effect' ? '$.user_effect' : '$.user_pre_effect';
1413

1514
if (rune === '$effect' || rune === '$effect.pre') {
15+
const callee = rune === '$effect' ? '$.user_effect' : '$.user_pre_effect';
1616
const func = /** @type {Expression} */ (context.visit(node.expression.arguments[0]));
17-
const link = node.expression.metadata?.link;
18-
19-
if (rune === '$effect.pre' && link) {
20-
const bind_option = /** @type {Property} */ (
21-
/** @type {ObjectExpression} */ (node.expression.arguments[1]).properties.find(
22-
(p) => p.type === 'Property' && p.key.type === 'Identifier' && p.key.name === 'bind'
23-
)
24-
);
25-
26-
const expr = b.call(
27-
callee,
28-
/** @type {Expression} */ (func),
29-
/** @type {Identifier} */ (bind_option.value)
30-
);
31-
expr.callee.loc = node.expression.callee.loc; // ensure correct mapping
32-
33-
return b.var(b.id(link), expr);
34-
}
35-
3617
const expr = b.call(callee, /** @type {Expression} */ (func));
3718
expr.callee.loc = node.expression.callee.loc; // ensure correct mapping
3819

3920
return b.stmt(expr);
4021
}
22+
if (rune === '$effect.lazy') {
23+
const func = /** @type {Expression} */ (context.visit(node.expression.arguments[1]));
24+
const id = /** @type {string} */ (node.expression.metadata?.id);
25+
26+
const expr = b.call(
27+
'$.lazy_effect',
28+
/** @type {Expression} */ (func),
29+
/** @type {Identifier} */ (node.expression.arguments[0])
30+
);
31+
expr.callee.loc = node.expression.callee.loc; // ensure correct mapping
32+
33+
return b.var(b.id(id), expr);
34+
}
4135
}
4236

4337
context.next();

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

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -12,17 +12,14 @@ export function ExpressionStatement(node, context) {
1212

1313
if (node.expression.type === 'CallExpression') {
1414
if (rune === '$effect' || rune === '$effect.pre' || rune === '$effect.root') {
15-
const link = node.expression.metadata?.link;
16-
15+
return b.empty;
16+
}
17+
if (rune === '$effect.lazy') {
1718
// TODO we should probably only output the effect in SSR if it's link is referenced
1819
// outside of the effect?
19-
if (rune === '$effect.pre' && link) {
20-
const func = /** @type {FunctionExpression} */ (node.expression.arguments[0]);
20+
const func = /** @type {FunctionExpression} */ (node.expression.arguments[1]);
2121

22-
return b.stmt(b.call(/** @type {FunctionExpression} */ (context.visit(func))));
23-
}
24-
25-
return b.empty;
22+
return b.stmt(b.call(/** @type {FunctionExpression} */ (context.visit(func))));
2623
}
2724
}
2825

packages/svelte/src/compiler/phases/types.d.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@ declare module 'estree' {
101101

102102
interface SimpleCallExpression {
103103
metadata?: {
104-
link: string;
104+
id: string;
105105
};
106106
}
107107
}

packages/svelte/src/internal/client/constants.js

Lines changed: 14 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -4,21 +4,22 @@ export const RENDER_EFFECT = 1 << 3;
44
export const BLOCK_EFFECT = 1 << 4;
55
export const BRANCH_EFFECT = 1 << 5;
66
export const ROOT_EFFECT = 1 << 6;
7-
export const UNOWNED = 1 << 7;
8-
export const DISCONNECTED = 1 << 8;
9-
export const CLEAN = 1 << 9;
10-
export const DIRTY = 1 << 10;
11-
export const MAYBE_DIRTY = 1 << 11;
12-
export const INERT = 1 << 12;
13-
export const DESTROYED = 1 << 13;
14-
export const EFFECT_RAN = 1 << 14;
7+
export const LAZY_EFFECT = 1 << 7;
8+
export const UNOWNED = 1 << 8;
9+
export const DISCONNECTED = 1 << 9;
10+
export const CLEAN = 1 << 10;
11+
export const DIRTY = 1 << 11;
12+
export const MAYBE_DIRTY = 1 << 12;
13+
export const INERT = 1 << 13;
14+
export const DESTROYED = 1 << 14;
15+
export const EFFECT_RAN = 1 << 15;
1516
/** 'Transparent' effects do not create a transition boundary */
16-
export const EFFECT_TRANSPARENT = 1 << 15;
17+
export const EFFECT_TRANSPARENT = 1 << 16;
1718
/** Svelte 4 legacy mode props need to be handled with deriveds and be recognized elsewhere, hence the dedicated flag */
18-
export const LEGACY_DERIVED_PROP = 1 << 16;
19-
export const INSPECT_EFFECT = 1 << 17;
20-
export const HEAD_EFFECT = 1 << 18;
21-
export const EFFECT_HAS_DERIVED = 1 << 19;
19+
export const LEGACY_DERIVED_PROP = 1 << 17;
20+
export const INSPECT_EFFECT = 1 << 18;
21+
export const HEAD_EFFECT = 1 << 19;
22+
export const EFFECT_HAS_DERIVED = 1 << 20;
2223

2324
export const STATE_SYMBOL = Symbol('$state');
2425
export const STATE_SYMBOL_METADATA = Symbol('$state metadata');

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,8 @@ export {
102102
template_effect,
103103
effect,
104104
user_effect,
105-
user_pre_effect
105+
user_pre_effect,
106+
lazy_effect
106107
} from './reactivity/effects.js';
107108
export { mutable_state, mutate, set, state, state_linked } from './reactivity/sources.js';
108109
export {

packages/svelte/src/internal/client/reactivity/effects.js

Lines changed: 32 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,8 @@ import {
3535
INSPECT_EFFECT,
3636
HEAD_EFFECT,
3737
MAYBE_DIRTY,
38-
EFFECT_HAS_DERIVED
38+
EFFECT_HAS_DERIVED,
39+
LAZY_EFFECT
3940
} from '../constants.js';
4041
import { set } from './sources.js';
4142
import * as e from '../errors.js';
@@ -45,7 +46,7 @@ import { get_next_sibling } from '../dom/operations.js';
4546
import { destroy_derived } from './deriveds.js';
4647

4748
/**
48-
* @param {'$effect' | '$effect.pre' | '$inspect'} rune
49+
* @param {'$effect' | '$effect.pre' | '$effect.lazy' | '$inspect'} rune
4950
*/
5051
export function validate_effect(rune) {
5152
if (active_effect === null && active_reaction === null) {
@@ -217,30 +218,47 @@ export function user_effect(fn) {
217218
}
218219
}
219220

221+
/**
222+
* Internal representation of `$effect.lazy(...)`
223+
* @param {() => void | (() => void)} fn
224+
* @param {Derived} state
225+
* @returns {Effect}
226+
*/
227+
export function lazy_effect(fn, state) {
228+
validate_effect('$effect.lazy');
229+
if (DEV) {
230+
define_property(fn, 'name', {
231+
value: '$effect.lazy'
232+
});
233+
}
234+
235+
return create_effect(
236+
LAZY_EFFECT,
237+
() => {
238+
var teardown = fn();
239+
var current_effect = /** @type {Effect} */ (active_reaction);
240+
(current_effect.deriveds ??= []).push(state);
241+
242+
return teardown;
243+
},
244+
false
245+
);
246+
}
247+
220248
/**
221249
* Internal representation of `$effect.pre(...)`
222250
* @param {() => void | (() => void)} fn
223-
* @param {Derived} [bound_state]
224251
* @returns {Effect}
225252
*/
226-
export function user_pre_effect(fn, bound_state) {
253+
export function user_pre_effect(fn) {
227254
validate_effect('$effect.pre');
228255
if (DEV) {
229256
define_property(fn, 'name', {
230257
value: '$effect.pre'
231258
});
232259
}
233260

234-
return render_effect(() => {
235-
var teardown = fn();
236-
237-
if (bound_state !== undefined) {
238-
var current_effect = /** @type {Effect} */ (active_reaction);
239-
(current_effect.deriveds ??= []).push(bound_state);
240-
}
241-
242-
return teardown;
243-
});
261+
return render_effect(fn);
244262
}
245263

246264
/** @param {() => void | (() => void)} fn */

packages/svelte/src/internal/client/reactivity/sources.js

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,6 @@ export function state(v) {
7474
* @param {() => Effect[]} fn
7575
*/
7676
export function state_linked(state, fn) {
77-
var init = false;
7877
/** @type {Derived} */
7978
var derived_state = derived(() => {
8079
var effects = fn();
@@ -87,13 +86,11 @@ export function state_linked(state, fn) {
8786
flush_effect(effects[i]);
8887
}
8988
}
89+
var current_derived = /** @type {Derived} */ (active_reaction);
9090

91-
if (!init) {
92-
init = true;
93-
return state;
94-
}
95-
return derived_state.v;
91+
return current_derived.v;
9692
});
93+
derived_state.v = state;
9794

9895
return derived_state;
9996
}

packages/svelte/src/utils.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -423,6 +423,7 @@ const RUNES = /** @type {const} */ ([
423423
'$derived.by',
424424
'$effect',
425425
'$effect.pre',
426+
'$effect.lazy',
426427
'$effect.tracking',
427428
'$effect.root',
428429
'$inspect',

0 commit comments

Comments
 (0)