Skip to content

Commit 37a8b25

Browse files
committed
WIP
1 parent 1a8aab0 commit 37a8b25

File tree

14 files changed

+136
-12
lines changed

14 files changed

+136
-12
lines changed

packages/svelte/src/ambient.d.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -391,3 +391,5 @@ declare function $inspect<T extends any[]>(
391391
* https://svelte.dev/docs/svelte/$host
392392
*/
393393
declare function $host<El extends HTMLElement = HTMLElement>(): El;
394+
395+
declare function $link<T>(fn: () => T): T;

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

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,41 @@ export function CallExpression(node, context) {
142142
}
143143

144144
break;
145+
146+
case '$link': {
147+
if (node.arguments.length !== 1) {
148+
e.rune_invalid_arguments_length(node, rune, 'exactly one argument');
149+
}
150+
const mutation = node.arguments[0];
151+
152+
for (let i = context.path.length - 1; i >= 0; i--) {
153+
const node = context.path[i];
154+
const declarator = context.path.at(i - 1);
155+
156+
if (
157+
node.type === 'CallExpression' &&
158+
get_rune(node, context.state.scope) === '$state' &&
159+
declarator?.type === 'VariableDeclarator' &&
160+
declarator.id.type === 'Identifier' &&
161+
mutation.type === 'ArrowFunctionExpression'
162+
) {
163+
const binding = context.state.scope.get(declarator.id.name);
164+
if (binding) {
165+
binding.linked = true;
166+
}
167+
node.metadata ??= {
168+
links: []
169+
};
170+
node.metadata.links.push({ path: context.path.slice(i), mutation });
171+
break;
172+
}
173+
if (node.type !== 'Property' && node.type !== 'ObjectExpression') {
174+
throw new Error('Bad usage of $link');
175+
}
176+
}
177+
178+
break;
179+
}
145180
}
146181

147182
if (context.state.render_tag) {

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,10 @@ export function CallExpression(node, context) {
3333
case '$inspect':
3434
case '$inspect().with':
3535
return transform_inspect_rune(node, context);
36+
37+
case '$link': {
38+
return b.id('undefined');
39+
}
3640
}
3741

3842
if (

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

Lines changed: 30 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -125,16 +125,43 @@ export function VariableDeclaration(node, context) {
125125
if (rune === '$state' && should_proxy(value, context.state.scope)) {
126126
value = b.call('$.proxy', value);
127127
}
128-
if (is_state_source(binding, context.state.analysis)) {
128+
if (is_state_source(binding, context.state.analysis) || binding.linked) {
129129
value = b.call('$.state', value);
130130
}
131131
return value;
132132
};
133133

134134
if (declarator.id.type === 'Identifier') {
135-
declarations.push(
136-
b.declarator(declarator.id, create_state_declarator(declarator.id, value))
135+
const state_declaration = b.declarator(
136+
declarator.id,
137+
create_state_declarator(declarator.id, value)
137138
);
139+
140+
if (declarator.init?.type === 'CallExpression' && declarator.init.metadata?.links) {
141+
const links = declarator.init.metadata.links;
142+
143+
declarations.push(
144+
b.declarator(
145+
declarator.id,
146+
b.call(
147+
'$.derived_linked',
148+
b.thunk(
149+
b.block([
150+
{ ...node, declarations: [state_declaration] },
151+
...links.map((link) =>
152+
b.stmt(
153+
b.call('$.link', /** @type {Expression} */ (context.visit(link.mutation)))
154+
)
155+
),
156+
b.return(b.call('$.get', declarator.id))
157+
])
158+
)
159+
)
160+
)
161+
);
162+
} else {
163+
declarations.push(state_declaration);
164+
}
138165
} else {
139166
const tmp = context.state.scope.generate('tmp');
140167
const paths = extract_paths(declarator.id);

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,10 @@ export function add_state_transformers(context) {
4848
);
4949
}
5050
};
51+
} else if (binding.linked) {
52+
context.state.transform[name] = {
53+
read: get_value
54+
};
5155
}
5256
}
5357
}

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,4 +98,10 @@ declare module 'estree' {
9898
scope: Scope;
9999
};
100100
}
101+
102+
interface SimpleCallExpression {
103+
metadata?: {
104+
links: Array<{ path: SvelteNode[]; mutation: ArrowFunctionExpression }>;
105+
};
106+
}
101107
}

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -297,6 +297,7 @@ export interface Binding {
297297
references: { node: Identifier; path: SvelteNode[] }[];
298298
mutated: boolean;
299299
reassigned: boolean;
300+
linked: boolean;
300301
/** `true` if mutated _or_ reassigned */
301302
updated: boolean;
302303
scope: Scope;

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ export const LEGACY_DERIVED_PROP = 1 << 16;
1919
export const INSPECT_EFFECT = 1 << 17;
2020
export const HEAD_EFFECT = 1 << 18;
2121
export const EFFECT_HAS_DERIVED = 1 << 19;
22+
export const DERIVED_LINKED = 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: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ export {
9292
template_with_script,
9393
text
9494
} from './dom/template.js';
95-
export { derived, derived_safe_equal } from './reactivity/deriveds.js';
95+
export { derived, derived_linked, link, derived_safe_equal } from './reactivity/deriveds.js';
9696
export {
9797
effect_tracking,
9898
effect_root,

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

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { DEV } from 'esm-env';
33
import {
44
CLEAN,
55
DERIVED,
6+
DERIVED_LINKED,
67
DESTROYED,
78
DIRTY,
89
EFFECT_HAS_DERIVED,
@@ -18,7 +19,8 @@ import {
1819
update_reaction,
1920
increment_version,
2021
set_active_effect,
21-
component_context
22+
component_context,
23+
get
2224
} from '../runtime.js';
2325
import { equals, safe_equals } from './equality.js';
2426
import * as e from '../errors.js';
@@ -31,8 +33,8 @@ import { inspect_effects, set_inspect_effects } from './sources.js';
3133
* @returns {Derived<V>}
3234
*/
3335
/*#__NO_SIDE_EFFECTS__*/
34-
export function derived(fn) {
35-
var flags = DERIVED | DIRTY;
36+
export function derived(fn, _flags = 0) {
37+
var flags = DERIVED | DIRTY | _flags;
3638

3739
if (active_effect === null) {
3840
flags |= UNOWNED;
@@ -68,6 +70,22 @@ export function derived(fn) {
6870
return signal;
6971
}
7072

73+
/**
74+
* @template T
75+
* @param {() => T} fn
76+
*/
77+
export function derived_linked(fn) {
78+
return derived(fn, DERIVED_LINKED);
79+
}
80+
81+
/**
82+
* @param {() => void} fn
83+
*/
84+
export function link(fn) {
85+
const p = derived(fn);
86+
get(p);
87+
}
88+
7189
/**
7290
* @template V
7391
* @param {() => V} fn

0 commit comments

Comments
 (0)