Skip to content

Commit d92901e

Browse files
committed
init
1 parent 8067841 commit d92901e

File tree

12 files changed

+133
-27
lines changed

12 files changed

+133
-27
lines changed

packages/svelte/src/compiler/phases/2-analyze/index.js

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ import { ExportNamedDeclaration } from './visitors/ExportNamedDeclaration.js';
3737
import { ExportSpecifier } from './visitors/ExportSpecifier.js';
3838
import { ExpressionStatement } from './visitors/ExpressionStatement.js';
3939
import { ExpressionTag } from './visitors/ExpressionTag.js';
40+
import { Fragment } from './visitors/Fragment.js';
4041
import { FunctionDeclaration } from './visitors/FunctionDeclaration.js';
4142
import { FunctionExpression } from './visitors/FunctionExpression.js';
4243
import { HtmlTag } from './visitors/HtmlTag.js';
@@ -156,6 +157,7 @@ const visitors = {
156157
ExportSpecifier,
157158
ExpressionStatement,
158159
ExpressionTag,
160+
Fragment,
159161
FunctionDeclaration,
160162
FunctionExpression,
161163
HtmlTag,
@@ -300,6 +302,10 @@ export function analyze_module(source, options) {
300302
function_depth: 0,
301303
has_props_rune: false,
302304
options: /** @type {ValidatedCompileOptions} */ (options),
305+
fragment: {
306+
has_await: false,
307+
node: null
308+
},
303309
parent_element: null,
304310
reactive_statement: null
305311
},
@@ -688,6 +694,10 @@ export function analyze_component(root, source, options) {
688694
analysis,
689695
options,
690696
ast_type: ast === instance.ast ? 'instance' : ast === template.ast ? 'template' : 'module',
697+
fragment: {
698+
has_await: false,
699+
node: ast === template.ast ? template.ast : null
700+
},
691701
parent_element: null,
692702
has_props_rune: false,
693703
component_slots: new Set(),
@@ -753,6 +763,10 @@ export function analyze_component(root, source, options) {
753763
scopes,
754764
analysis,
755765
options,
766+
fragment: {
767+
has_await: false,
768+
node: ast === template.ast ? template.ast : null
769+
},
756770
parent_element: null,
757771
has_props_rune: false,
758772
ast_type: ast === instance.ast ? 'instance' : ast === template.ast ? 'template' : 'module',

packages/svelte/src/compiler/phases/2-analyze/types.d.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ export interface AnalysisState {
88
analysis: ComponentAnalysis;
99
options: ValidatedCompileOptions;
1010
ast_type: 'instance' | 'template' | 'module';
11+
fragment: FragmentAnalysis;
1112
/**
1213
* Tag name of the parent element. `null` if the parent is `svelte:element`, `#snippet`, a component or the root.
1314
* Parent doesn't necessarily mean direct path predecessor because there could be `#each`, `#if` etc in-between.
@@ -28,6 +29,11 @@ export interface AnalysisState {
2829
reactive_statement: null | ReactiveStatement;
2930
}
3031

32+
export interface FragmentAnalysis {
33+
has_await: boolean;
34+
node: AST.Fragment | null;
35+
}
36+
3137
export type Context<State extends AnalysisState = AnalysisState> = import('zimmerframe').Context<
3238
AST.SvelteNode,
3339
State

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,9 @@ export function AwaitExpression(node, context) {
1111

1212
if (context.state.expression) {
1313
context.state.expression.has_await = true;
14+
if (context.state.fragment.node) {
15+
context.state.fragment.has_await = true;
16+
}
1417
suspend = true;
1518
}
1619

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
/** @import { AST } from '#compiler' */
2+
/** @import { Context } from '../types.js' */
3+
4+
/**
5+
* @param {AST.Fragment} node
6+
* @param {Context} context
7+
*/
8+
export function Fragment(node, context) {
9+
const parent = /** @type {AST.TemplateNode} */ (context.path.at(-1));
10+
if (
11+
!parent ||
12+
parent.type === 'Component' ||
13+
parent.type === 'Root' ||
14+
parent.type === 'IfBlock' ||
15+
parent.type === 'KeyBlock' ||
16+
parent.type === 'EachBlock' ||
17+
parent.type === 'SnippetBlock' ||
18+
parent.type === 'AwaitBlock'
19+
) {
20+
const fragment_metadata = {
21+
has_await: false,
22+
node
23+
};
24+
context.next({ ...context.state, fragment: fragment_metadata });
25+
node.metadata.has_await = fragment_metadata.has_await;
26+
} else {
27+
context.next();
28+
}
29+
}

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,7 @@ export function client_component(analysis, options) {
170170

171171
// these are set inside the `Fragment` visitor, and cannot be used until then
172172
init: /** @type {any} */ (null),
173+
consts: /** @type {any} */ (null),
173174
update: /** @type {any} */ (null),
174175
after_update: /** @type {any} */ (null),
175176
template: /** @type {any} */ (null),

packages/svelte/src/compiler/phases/3-transform/client/types.d.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@ import type {
66
Expression,
77
AssignmentExpression,
88
UpdateExpression,
9-
VariableDeclaration
9+
VariableDeclaration,
10+
Declaration
1011
} from 'estree';
1112
import type { AST, Namespace, ValidatedCompileOptions } from '#compiler';
1213
import type { TransformState } from '../types.js';
@@ -57,6 +58,8 @@ export interface ComponentClientTransformState extends ClientTransformState {
5758
readonly update: Statement[];
5859
/** Stuff that happens after the render effect (control blocks, dynamic elements, bindings, actions, etc) */
5960
readonly after_update: Statement[];
61+
/** Transformed `{#const }` declarations */
62+
readonly consts: Statement[];
6063
/** Memoized expressions */
6164
readonly memoizer: Memoizer;
6265
/** The HTML template string */

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

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -290,7 +290,19 @@ export function should_proxy(node, scope) {
290290
* Svelte legacy mode should use safe equals in most places, runes mode shouldn't
291291
* @param {ComponentClientTransformState} state
292292
* @param {Expression} arg
293+
* @param {boolean} [async]
293294
*/
294-
export function create_derived(state, arg) {
295-
return b.call(state.analysis.runes ? '$.derived' : '$.derived_safe_equal', arg);
295+
export function create_derived(state, arg, async = false) {
296+
if (async) {
297+
return b.call(
298+
b.await(
299+
b.call(
300+
'$.save',
301+
b.call('$.async_derived', arg.type === 'ArrowFunctionExpression' ? b.async(arg) : arg)
302+
)
303+
)
304+
);
305+
} else {
306+
return b.call(state.analysis.runes ? '$.derived' : '$.derived_safe_equal', arg);
307+
}
296308
}

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

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,11 @@ export function AwaitExpression(node, context) {
1515
// preserve context for
1616
// a) top-level await and
1717
// b) awaits that precede other expressions in template or `$derived(...)`
18-
if (tla || (is_reactive_expression(context) && !is_last_evaluated_expression(context, node))) {
18+
if (
19+
tla ||
20+
(is_reactive_expression(context) &&
21+
(!is_last_evaluated_expression(context, node) || context.path.at(-1)?.type === 'ConstTag'))
22+
) {
1923
return b.call(b.await(b.call('$.save', argument)));
2024
}
2125

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

Lines changed: 20 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -16,21 +16,29 @@ export function ConstTag(node, context) {
1616
const declaration = node.declaration.declarations[0];
1717
// TODO we can almost certainly share some code with $derived(...)
1818
if (declaration.id.type === 'Identifier') {
19-
const init = build_expression(context, declaration.init, node.metadata.expression);
20-
let expression = create_derived(context.state, b.thunk(init));
19+
const init = build_expression(
20+
{ ...context, state: { ...context.state, in_derived: true } },
21+
declaration.init,
22+
node.metadata.expression
23+
);
24+
let expression = create_derived(
25+
context.state,
26+
b.thunk(init),
27+
node.metadata.expression.has_await
28+
);
2129

2230
if (dev) {
2331
expression = b.call('$.tag', expression, b.literal(declaration.id.name));
2432
}
2533

26-
context.state.init.push(b.const(declaration.id, expression));
34+
context.state.consts.push(b.const(declaration.id, expression));
2735

2836
context.state.transform[declaration.id.name] = { read: get_value };
2937

3038
// we need to eagerly evaluate the expression in order to hit any
3139
// 'Cannot access x before initialization' errors
3240
if (dev) {
33-
context.state.init.push(b.stmt(b.call('$.get', declaration.id)));
41+
context.state.consts.push(b.stmt(b.call('$.get', declaration.id)));
3442
}
3543
} else {
3644
const identifiers = extract_identifiers(declaration.id);
@@ -44,7 +52,11 @@ export function ConstTag(node, context) {
4452
delete transform[node.name];
4553
}
4654

47-
const child_state = { ...context.state, transform };
55+
const child_state = /** @type {ComponentContext['state']} */ ({
56+
...context.state,
57+
transform,
58+
in_derived: true
59+
});
4860

4961
// TODO optimise the simple `{ x } = y` case — we can just return `y`
5062
// instead of destructuring it only to return a new object
@@ -61,18 +73,18 @@ export function ConstTag(node, context) {
6173
])
6274
);
6375

64-
let expression = create_derived(context.state, fn);
76+
let expression = create_derived(context.state, fn, node.metadata.expression.has_await);
6577

6678
if (dev) {
6779
expression = b.call('$.tag', expression, b.literal('[@const]'));
6880
}
6981

70-
context.state.init.push(b.const(tmp, expression));
82+
context.state.consts.push(b.const(tmp, expression));
7183

7284
// we need to eagerly evaluate the expression in order to hit any
7385
// 'Cannot access x before initialization' errors
7486
if (dev) {
75-
context.state.init.push(b.stmt(b.call('$.get', tmp)));
87+
context.state.consts.push(b.stmt(b.call('$.get', tmp)));
7688
}
7789

7890
for (const node of identifiers) {

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

Lines changed: 23 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -48,8 +48,10 @@ export function Fragment(node, context) {
4848
const is_single_child_not_needing_template =
4949
trimmed.length === 1 &&
5050
(trimmed[0].type === 'SvelteFragment' || trimmed[0].type === 'TitleElement');
51+
const has_await = context.state.init !== null && (node.metadata.has_await || false);
5152

5253
const template_name = context.state.scope.root.unique('root'); // TODO infer name from parent
54+
const unsuspend = b.id('$$unsuspend');
5355

5456
/** @type {Statement[]} */
5557
const body = [];
@@ -61,6 +63,7 @@ export function Fragment(node, context) {
6163
const state = {
6264
...context.state,
6365
init: [],
66+
consts: [],
6467
update: [],
6568
after_update: [],
6669
memoizer: new Memoizer(),
@@ -78,7 +81,7 @@ export function Fragment(node, context) {
7881

7982
if (is_text_first) {
8083
// skip over inserted comment
81-
body.push(b.stmt(b.call('$.next')));
84+
state.init.unshift(b.stmt(b.call('$.next')));
8285
}
8386

8487
if (is_single_element) {
@@ -96,13 +99,13 @@ export function Fragment(node, context) {
9699
const template = transform_template(state, namespace, flags);
97100
state.hoisted.push(b.var(template_name, template));
98101

99-
body.push(b.var(id, b.call(template_name)));
102+
state.init.unshift(b.var(id, b.call(template_name)));
100103
close = b.stmt(b.call('$.append', b.id('$$anchor'), id));
101104
} else if (is_single_child_not_needing_template) {
102105
context.visit(trimmed[0], state);
103106
} else if (trimmed.length === 1 && trimmed[0].type === 'Text') {
104107
const id = b.id(context.state.scope.generate('text'));
105-
body.push(b.var(id, b.call('$.text', b.literal(trimmed[0].data))));
108+
state.init.unshift(b.var(id, b.call('$.text', b.literal(trimmed[0].data))));
106109
close = b.stmt(b.call('$.append', b.id('$$anchor'), id));
107110
} else if (trimmed.length > 0) {
108111
const id = b.id(context.state.scope.generate('fragment'));
@@ -120,7 +123,7 @@ export function Fragment(node, context) {
120123
state
121124
});
122125

123-
body.push(b.var(id, b.call('$.text')));
126+
state.init.unshift(b.var(id, b.call('$.text')));
124127
close = b.stmt(b.call('$.append', b.id('$$anchor'), id));
125128
} else {
126129
if (is_standalone) {
@@ -140,19 +143,29 @@ export function Fragment(node, context) {
140143

141144
if (state.template.nodes.length === 1 && state.template.nodes[0].type === 'comment') {
142145
// special case — we can use `$.comment` instead of creating a unique template
143-
body.push(b.var(id, b.call('$.comment')));
146+
state.init.unshift(b.var(id, b.call('$.comment')));
144147
} else {
145148
const template = transform_template(state, namespace, flags);
146149
state.hoisted.push(b.var(template_name, template));
147150

148-
body.push(b.var(id, b.call(template_name)));
151+
state.init.unshift(b.var(id, b.call(template_name)));
149152
}
150153

151154
close = b.stmt(b.call('$.append', b.id('$$anchor'), id));
152155
}
153156
}
154157
}
155158

159+
if (has_await) {
160+
body.push(b.var(unsuspend, b.call('$.suspend')));
161+
}
162+
163+
body.push(...state.consts);
164+
165+
if (has_await) {
166+
body.push(b.if(b.call('$.aborted'), b.return()));
167+
}
168+
156169
body.push(...state.init);
157170

158171
if (state.update.length > 0) {
@@ -168,5 +181,9 @@ export function Fragment(node, context) {
168181
body.push(close);
169182
}
170183

184+
if (has_await) {
185+
body.push(b.stmt(b.call(unsuspend)));
186+
}
187+
171188
return b.block(body);
172189
}

0 commit comments

Comments
 (0)