Skip to content

Commit 193f23f

Browse files
feat: async fragments (#16542)
* init * fix * fix * maybe this works? * fix, minor cleanup * maybe this'll fix hydration issues? * apparently not, maybe this'll do it * add test, changeset * minor tweak * tabs * avoid timeouts in tests, they add up quickly * tweak * fix * some is more 'correct' than find * chore: remove `b.async` * simplify * apply suggestion from review Co-authored-by: Rich Harris <[email protected]> * Update .changeset/brave-baboons-teach.md --------- Co-authored-by: Rich Harris <[email protected]>
1 parent 41a20aa commit 193f23f

File tree

19 files changed

+142
-48
lines changed

19 files changed

+142
-48
lines changed

.changeset/brave-baboons-teach.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'svelte': minor
3+
---
4+
5+
feat: allow `await` inside `@const` declarations

packages/svelte/src/compiler/phases/1-parse/utils/create.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,8 @@ export function create_fragment(transparent = false) {
1010
nodes: [],
1111
metadata: {
1212
transparent,
13-
dynamic: false
13+
dynamic: false,
14+
has_await: false
1415
}
1516
};
1617
}

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

Lines changed: 5 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,7 @@ export function analyze_module(source, options) {
300302
function_depth: 0,
301303
has_props_rune: false,
302304
options: /** @type {ValidatedCompileOptions} */ (options),
305+
fragment: null,
303306
parent_element: null,
304307
reactive_statement: null
305308
},
@@ -687,6 +690,7 @@ export function analyze_component(root, source, options) {
687690
analysis,
688691
options,
689692
ast_type: ast === instance.ast ? 'instance' : ast === template.ast ? 'template' : 'module',
693+
fragment: ast === template.ast ? ast : null,
690694
parent_element: null,
691695
has_props_rune: false,
692696
component_slots: new Set(),
@@ -752,6 +756,7 @@ export function analyze_component(root, source, options) {
752756
scopes,
753757
analysis,
754758
options,
759+
fragment: ast === template.ast ? ast : null,
755760
parent_element: null,
756761
has_props_rune: false,
757762
ast_type: ast === instance.ast ? 'instance' : ast === template.ast ? 'template' : 'module',

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

Lines changed: 1 addition & 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: AST.Fragment | null;
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.

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

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

1212
if (context.state.expression) {
1313
context.state.expression.has_await = true;
14+
15+
if (
16+
context.state.fragment &&
17+
// TODO there's probably a better way to do this
18+
context.path.some((node) => node.type === 'ConstTag')
19+
) {
20+
context.state.fragment.metadata.has_await = true;
21+
}
22+
1423
suspend = true;
1524
}
1625

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
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+
context.next({ ...context.state, fragment: node });
10+
}

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
@@ -172,6 +172,7 @@ export function client_component(analysis, options) {
172172

173173
// these are set inside the `Fragment` visitor, and cannot be used until then
174174
init: /** @type {any} */ (null),
175+
consts: /** @type {any} */ (null),
175176
update: /** @type {any} */ (null),
176177
after_update: /** @type {any} */ (null),
177178
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: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
/** @import { ArrowFunctionExpression, AssignmentExpression, Expression, FunctionDeclaration, FunctionExpression, Identifier, Node, Pattern, UpdateExpression } from 'estree' */
1+
/** @import { ArrowFunctionExpression, AssignmentExpression, BlockStatement, Expression, FunctionDeclaration, FunctionExpression, Identifier, Node, Pattern, UpdateExpression } from 'estree' */
22
/** @import { Binding } from '#compiler' */
33
/** @import { ClientTransformState, ComponentClientTransformState, ComponentContext } from './types.js' */
44
/** @import { Analysis } from '../../types.js' */
@@ -289,8 +289,15 @@ export function should_proxy(node, scope) {
289289
/**
290290
* Svelte legacy mode should use safe equals in most places, runes mode shouldn't
291291
* @param {ComponentClientTransformState} state
292-
* @param {Expression} arg
292+
* @param {Expression | BlockStatement} expression
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, expression, async = false) {
296+
const thunk = b.thunk(expression, async);
297+
298+
if (async) {
299+
return b.call(b.await(b.call('$.save', b.call('$.async_derived', thunk))));
300+
} else {
301+
return b.call(state.analysis.runes ? '$.derived' : '$.derived_safe_equal', thunk);
302+
}
296303
}

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -96,13 +96,13 @@ function create_derived_block_argument(node, context) {
9696
b.return(b.object(identifiers.map((identifier) => b.prop('init', identifier, identifier))))
9797
]);
9898

99-
const declarations = [b.var(value, create_derived(context.state, b.thunk(block)))];
99+
const declarations = [b.var(value, create_derived(context.state, block))];
100100

101101
for (const id of identifiers) {
102102
context.state.transform[id.name] = { read: get_value };
103103

104104
declarations.push(
105-
b.var(id, create_derived(context.state, b.thunk(b.member(b.call('$.get', value), id))))
105+
b.var(id, create_derived(context.state, b.member(b.call('$.get', value), id)))
106106
);
107107
}
108108

0 commit comments

Comments
 (0)