svelte 5.55.10 → 5.56.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/compiler/index.js +1 -1
- package/package.json +7 -4
- package/src/compiler/errors.js +18 -0
- package/src/compiler/legacy.js +4 -0
- package/src/compiler/phases/1-parse/acorn.js +31 -1
- package/src/compiler/phases/1-parse/index.js +4 -1
- package/src/compiler/phases/1-parse/state/tag.js +104 -3
- package/src/compiler/phases/1-parse/utils/bracket.js +14 -3
- package/src/compiler/phases/2-analyze/index.js +5 -0
- package/src/compiler/phases/2-analyze/visitors/CallExpression.js +3 -0
- package/src/compiler/phases/2-analyze/visitors/ConstTag.js +2 -25
- package/src/compiler/phases/2-analyze/visitors/DeclarationTag.js +71 -0
- package/src/compiler/phases/2-analyze/visitors/Identifier.js +1 -1
- package/src/compiler/phases/2-analyze/visitors/SnippetBlock.js +15 -10
- package/src/compiler/phases/3-transform/client/transform-client.js +5 -15
- package/src/compiler/phases/3-transform/client/transform-template/index.js +40 -3
- package/src/compiler/phases/3-transform/client/utils.js +21 -0
- package/src/compiler/phases/3-transform/client/visitors/ConstTag.js +13 -24
- package/src/compiler/phases/3-transform/client/visitors/DeclarationTag.js +89 -0
- package/src/compiler/phases/3-transform/client/visitors/Fragment.js +2 -5
- package/src/compiler/phases/3-transform/client/visitors/RegularElement.js +30 -8
- package/src/compiler/phases/3-transform/client/visitors/SvelteBoundary.js +7 -2
- package/src/compiler/phases/3-transform/client/visitors/VariableDeclaration.js +12 -2
- package/src/compiler/phases/3-transform/server/transform-server.js +2 -0
- package/src/compiler/phases/3-transform/server/visitors/ConstTag.js +9 -24
- package/src/compiler/phases/3-transform/server/visitors/DeclarationTag.js +85 -0
- package/src/compiler/phases/3-transform/server/visitors/RegularElement.js +24 -7
- package/src/compiler/phases/3-transform/server/visitors/shared/utils.js +1 -1
- package/src/compiler/phases/3-transform/utils.js +1 -0
- package/src/compiler/phases/nodes.js +1 -0
- package/src/compiler/print/index.js +42 -0
- package/src/compiler/utils/builders.js +2 -1
- package/src/internal/client/dom/blocks/boundary.js +1 -1
- package/src/internal/client/dom/blocks/each.js +3 -1
- package/src/internal/client/dom/elements/attributes.js +9 -0
- package/src/internal/client/dom/elements/bindings/input.js +0 -1
- package/src/internal/client/dom/operations.js +12 -2
- package/src/internal/client/reactivity/async.js +23 -10
- package/src/internal/client/reactivity/batch.js +0 -21
- package/src/internal/client/reactivity/effects.js +4 -2
- package/src/internal/client/reactivity/props.js +6 -6
- package/src/internal/client/reactivity/sources.js +1 -2
- package/src/internal/client/runtime.js +4 -8
- package/src/reactivity/url-search-params.js +3 -2
- package/src/utils.js +1 -1
- package/src/version.js +1 -1
- package/types/index.d.ts +7 -0
- package/types/index.d.ts.map +1 -1
|
@@ -7,6 +7,7 @@ import * as b from '#compiler/builders';
|
|
|
7
7
|
import { create_derived } from '../utils.js';
|
|
8
8
|
import { get_value } from './shared/declarations.js';
|
|
9
9
|
import { build_expression } from './shared/utils.js';
|
|
10
|
+
import { add_async_declaration } from './DeclarationTag.js';
|
|
10
11
|
|
|
11
12
|
/**
|
|
12
13
|
* @param {AST.ConstTag} node
|
|
@@ -26,7 +27,7 @@ export function ConstTag(node, context) {
|
|
|
26
27
|
|
|
27
28
|
context.state.transform[declaration.id.name] = { read: get_value };
|
|
28
29
|
|
|
29
|
-
add_const_declaration(context
|
|
30
|
+
add_const_declaration(context, declaration.id, expression, node.metadata);
|
|
30
31
|
} else {
|
|
31
32
|
const identifiers = extract_identifiers(declaration.id);
|
|
32
33
|
const tmp = b.id(context.state.scope.generate('computed_const'));
|
|
@@ -63,7 +64,7 @@ export function ConstTag(node, context) {
|
|
|
63
64
|
expression = b.call('$.tag', expression, b.literal('[@const]'));
|
|
64
65
|
}
|
|
65
66
|
|
|
66
|
-
add_const_declaration(context
|
|
67
|
+
add_const_declaration(context, tmp, expression, node.metadata);
|
|
67
68
|
|
|
68
69
|
for (const node of identifiers) {
|
|
69
70
|
context.state.transform[node.name] = {
|
|
@@ -74,38 +75,26 @@ export function ConstTag(node, context) {
|
|
|
74
75
|
}
|
|
75
76
|
|
|
76
77
|
/**
|
|
77
|
-
* @param {ComponentContext
|
|
78
|
+
* @param {ComponentContext} context
|
|
78
79
|
* @param {Identifier} id
|
|
79
80
|
* @param {Expression} expression
|
|
80
81
|
* @param {AST.ConstTag['metadata']} metadata
|
|
81
82
|
*/
|
|
82
|
-
function add_const_declaration(
|
|
83
|
+
function add_const_declaration(context, id, expression, metadata) {
|
|
83
84
|
// we need to eagerly evaluate the expression in order to hit any
|
|
84
85
|
// 'Cannot access x before initialization' errors
|
|
85
86
|
const after = dev ? [b.stmt(b.call('$.get', id))] : [];
|
|
86
87
|
|
|
87
|
-
const blockers = [...metadata.expression.dependencies]
|
|
88
|
-
.map((dep) => dep.blocker)
|
|
89
|
-
.filter((b) => b !== null && b.object !== state.async_consts?.id);
|
|
90
|
-
|
|
91
88
|
if (metadata.promises_id) {
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
if (blockers.length === 1) {
|
|
100
|
-
run.thunks.push(b.thunk(b.member(/** @type {Expression} */ (blockers[0]), 'promise')));
|
|
101
|
-
} else if (blockers.length > 0) {
|
|
102
|
-
run.thunks.push(b.thunk(b.call('$.wait', b.array(blockers))));
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
// keep the number of thunks pushed in sync with ConstTag in analysis phase
|
|
106
|
-
const assignment = b.assignment('=', id, expression);
|
|
107
|
-
run.thunks.push(b.thunk(assignment, metadata.expression.has_await));
|
|
89
|
+
add_async_declaration(
|
|
90
|
+
context,
|
|
91
|
+
metadata,
|
|
92
|
+
[id],
|
|
93
|
+
[b.stmt(b.assignment('=', id, expression))],
|
|
94
|
+
'let'
|
|
95
|
+
);
|
|
108
96
|
} else {
|
|
97
|
+
const { state } = context;
|
|
109
98
|
state.consts.push(b.const(id, expression));
|
|
110
99
|
state.consts.push(...after);
|
|
111
100
|
}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/** @import { Expression, Identifier, Pattern, Statement, ExpressionStatement, VariableDeclaration } from 'estree' */
|
|
2
|
+
/** @import { AST } from '#compiler' */
|
|
3
|
+
/** @import { ComponentContext } from '../types' */
|
|
4
|
+
import { extract_identifiers, has_await_expression } from '../../../../utils/ast.js';
|
|
5
|
+
import * as b from '#compiler/builders';
|
|
6
|
+
import { add_state_transformers } from './shared/declarations.js';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* @param {AST.DeclarationTag} node
|
|
10
|
+
* @param {ComponentContext} context
|
|
11
|
+
*/
|
|
12
|
+
export function DeclarationTag(node, context) {
|
|
13
|
+
// register the transformers _before_ visiting the declaration, so that
|
|
14
|
+
// later declarators can reference earlier ones (e.g. `{let a = $state(0), b = $derived(a * 2)}`)
|
|
15
|
+
add_state_transformers(context);
|
|
16
|
+
const declaration = /** @type {Statement | undefined} */ (context.visit(node.declaration));
|
|
17
|
+
|
|
18
|
+
if (
|
|
19
|
+
node.metadata.promises_id &&
|
|
20
|
+
node.declaration.type === 'VariableDeclaration' &&
|
|
21
|
+
declaration?.type === 'VariableDeclaration'
|
|
22
|
+
) {
|
|
23
|
+
const { ids, assignments } = build_async_declaration_parts(declaration);
|
|
24
|
+
add_async_declaration(context, node.metadata, ids, assignments, declaration.kind);
|
|
25
|
+
} else {
|
|
26
|
+
context.state.consts.push(declaration ?? node.declaration);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* @param {VariableDeclaration} declaration
|
|
32
|
+
*/
|
|
33
|
+
export function build_async_declaration_parts(declaration) {
|
|
34
|
+
const ids = new Map();
|
|
35
|
+
for (const declarator of declaration.declarations) {
|
|
36
|
+
for (const id of extract_identifiers(declarator.id)) {
|
|
37
|
+
ids.set(id.name, id);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const assignments = declaration.declarations
|
|
42
|
+
.filter((declarator) => declarator.init !== null)
|
|
43
|
+
.map((declarator) =>
|
|
44
|
+
b.stmt(
|
|
45
|
+
b.assignment(
|
|
46
|
+
'=',
|
|
47
|
+
/** @type {Pattern} */ (declarator.id),
|
|
48
|
+
/** @type {Expression} */ (declarator.init)
|
|
49
|
+
)
|
|
50
|
+
)
|
|
51
|
+
);
|
|
52
|
+
|
|
53
|
+
return { ids: [...ids.values()], assignments };
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* @param {ComponentContext} context
|
|
58
|
+
* @param {AST.ConstTag['metadata'] | AST.DeclarationTag['metadata']} metadata
|
|
59
|
+
* @param {Identifier[]} ids
|
|
60
|
+
* @param {ExpressionStatement[]} assignments
|
|
61
|
+
* @param {VariableDeclaration['kind']} [kind]
|
|
62
|
+
*/
|
|
63
|
+
export function add_async_declaration(context, metadata, ids, assignments, kind = 'let') {
|
|
64
|
+
const run = (context.state.async_consts ??= {
|
|
65
|
+
id: /** @type {Identifier} */ (metadata.promises_id),
|
|
66
|
+
thunks: []
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
for (const id of ids) {
|
|
70
|
+
context.state.consts.push(kind === 'var' ? b.var(id.name) : b.let(id.name));
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const blockers = [...metadata.expression.dependencies]
|
|
74
|
+
.map((dep) => dep.blocker)
|
|
75
|
+
.filter((b) => b !== null && b.object !== context.state.async_consts?.id);
|
|
76
|
+
|
|
77
|
+
if (blockers.length === 1) {
|
|
78
|
+
run.thunks.push(b.thunk(b.member(/** @type {Expression} */ (blockers[0]), 'promise')));
|
|
79
|
+
} else if (blockers.length > 0) {
|
|
80
|
+
run.thunks.push(b.thunk(b.call('$.wait', b.array(blockers))));
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// keep the number of thunks pushed in sync with analysis phase
|
|
84
|
+
const has_await =
|
|
85
|
+
metadata.expression.has_await ||
|
|
86
|
+
assignments.some((assignment) => has_await_expression(assignment));
|
|
87
|
+
const body = assignments.length === 1 ? assignments[0].expression : b.block(assignments);
|
|
88
|
+
run.thunks.push(b.thunk(body, has_await));
|
|
89
|
+
}
|
|
@@ -52,7 +52,6 @@ export function Fragment(node, context) {
|
|
|
52
52
|
(trimmed[0].type === 'IfBlock' &&
|
|
53
53
|
trimmed[0].elseif &&
|
|
54
54
|
/** @type {AST.IfBlock} */ (parent).metadata.flattened?.includes(trimmed[0])));
|
|
55
|
-
const template_name = context.state.scope.root.unique('root'); // TODO infer name from parent
|
|
56
55
|
|
|
57
56
|
/** @type {Statement[]} */
|
|
58
57
|
const body = [];
|
|
@@ -96,8 +95,7 @@ export function Fragment(node, context) {
|
|
|
96
95
|
|
|
97
96
|
let flags = state.template.needs_import_node ? TEMPLATE_USE_IMPORT_NODE : undefined;
|
|
98
97
|
|
|
99
|
-
const
|
|
100
|
-
state.hoisted.push(b.var(template_name, template));
|
|
98
|
+
const template_name = transform_template(state, 'root', flags);
|
|
101
99
|
|
|
102
100
|
state.init.unshift(b.var(id, b.call(template_name)));
|
|
103
101
|
close = b.stmt(b.call('$.append', b.id('$$anchor'), id));
|
|
@@ -147,8 +145,7 @@ export function Fragment(node, context) {
|
|
|
147
145
|
// special case — we can use `$.comment` instead of creating a unique template
|
|
148
146
|
state.init.unshift(b.var(id, b.call('$.comment')));
|
|
149
147
|
} else {
|
|
150
|
-
const
|
|
151
|
-
state.hoisted.push(b.var(template_name, template));
|
|
148
|
+
const template_name = transform_template(state, 'root', flags);
|
|
152
149
|
|
|
153
150
|
state.init.unshift(b.var(id, b.call(template_name)));
|
|
154
151
|
}
|
|
@@ -18,7 +18,7 @@ import {
|
|
|
18
18
|
is_customizable_select_element
|
|
19
19
|
} from '../../../nodes.js';
|
|
20
20
|
import { clean_nodes, determine_namespace_for_children } from '../../utils.js';
|
|
21
|
-
import { build_getter } from '../utils.js';
|
|
21
|
+
import { build_getter, get_transform } from '../utils.js';
|
|
22
22
|
import {
|
|
23
23
|
get_attribute_name,
|
|
24
24
|
build_attribute_value,
|
|
@@ -300,11 +300,14 @@ export function RegularElement(node, context) {
|
|
|
300
300
|
}
|
|
301
301
|
}
|
|
302
302
|
|
|
303
|
+
const scope = /** @type {Scope} */ (context.state.scopes.get(node.fragment));
|
|
304
|
+
|
|
303
305
|
/** @type {ComponentClientTransformState} */
|
|
304
306
|
const state = {
|
|
305
307
|
...context.state,
|
|
306
308
|
metadata,
|
|
307
|
-
scope
|
|
309
|
+
scope,
|
|
310
|
+
transform: get_transform(scope, context.state),
|
|
308
311
|
preserve_whitespace: context.state.preserve_whitespace || name === 'pre' || name === 'textarea'
|
|
309
312
|
};
|
|
310
313
|
|
|
@@ -318,8 +321,19 @@ export function RegularElement(node, context) {
|
|
|
318
321
|
state.options.preserveComments
|
|
319
322
|
);
|
|
320
323
|
|
|
324
|
+
const has_declarations = !node.fragment.metadata.transparent;
|
|
325
|
+
|
|
321
326
|
/** @type {typeof state} */
|
|
322
|
-
const child_state = {
|
|
327
|
+
const child_state = {
|
|
328
|
+
...state,
|
|
329
|
+
init: [],
|
|
330
|
+
update: [],
|
|
331
|
+
after_update: [],
|
|
332
|
+
snippets: [],
|
|
333
|
+
consts: has_declarations ? [] : state.consts,
|
|
334
|
+
async_consts: has_declarations ? undefined : state.async_consts,
|
|
335
|
+
memoizer: has_declarations ? new Memoizer() : state.memoizer
|
|
336
|
+
};
|
|
323
337
|
|
|
324
338
|
for (const node of hoisted) {
|
|
325
339
|
context.visit(node, child_state);
|
|
@@ -360,7 +374,6 @@ export function RegularElement(node, context) {
|
|
|
360
374
|
context.state.template.push_comment();
|
|
361
375
|
|
|
362
376
|
// Create a separate template for the rich content
|
|
363
|
-
const template_name = context.state.scope.root.unique(`${name}_content`);
|
|
364
377
|
const fragment_id = b.id(context.state.scope.generate('fragment'));
|
|
365
378
|
const anchor_id = b.id(context.state.scope.generate('anchor'));
|
|
366
379
|
|
|
@@ -384,9 +397,8 @@ export function RegularElement(node, context) {
|
|
|
384
397
|
}
|
|
385
398
|
);
|
|
386
399
|
|
|
387
|
-
// Transform the template to $.from_html(...) and hoist it
|
|
388
|
-
const
|
|
389
|
-
context.state.hoisted.push(b.var(template_name, template));
|
|
400
|
+
// Transform the template to $.from_html(...) and hoist it (deduplicating identical templates)
|
|
401
|
+
const template_name = transform_template(select_state, `${name}_content`, TEMPLATE_FRAGMENT);
|
|
390
402
|
|
|
391
403
|
// Build the rich content function body
|
|
392
404
|
// The anchor is the child of the element (a hydration marker during hydration)
|
|
@@ -427,11 +439,21 @@ export function RegularElement(node, context) {
|
|
|
427
439
|
}
|
|
428
440
|
}
|
|
429
441
|
|
|
430
|
-
if (node.fragment.nodes.some((node) => node.type === 'SnippetBlock')) {
|
|
442
|
+
if (node.fragment.nodes.some((node) => node.type === 'SnippetBlock') || has_declarations) {
|
|
443
|
+
if (child_state.async_consts && child_state.async_consts.thunks.length > 0) {
|
|
444
|
+
child_state.consts.push(
|
|
445
|
+
b.var(
|
|
446
|
+
child_state.async_consts.id,
|
|
447
|
+
b.call('$.run', b.array(child_state.async_consts.thunks))
|
|
448
|
+
)
|
|
449
|
+
);
|
|
450
|
+
}
|
|
451
|
+
|
|
431
452
|
// Wrap children in `{...}` to avoid declaration conflicts
|
|
432
453
|
context.state.init.push(
|
|
433
454
|
b.block([
|
|
434
455
|
...child_state.snippets,
|
|
456
|
+
...child_state.consts,
|
|
435
457
|
...child_state.init,
|
|
436
458
|
...element_state.init,
|
|
437
459
|
child_state.update.length > 0 ? build_render_statement(child_state) : b.empty,
|
|
@@ -40,6 +40,7 @@ export function SvelteBoundary(node, context) {
|
|
|
40
40
|
const hoisted = [];
|
|
41
41
|
|
|
42
42
|
let has_const = false;
|
|
43
|
+
let has_declaration = false;
|
|
43
44
|
|
|
44
45
|
// const tags need to live inside the boundary, but might also be referenced in hoisted snippets.
|
|
45
46
|
// to resolve this we cheat: we duplicate const tags inside snippets
|
|
@@ -55,6 +56,10 @@ export function SvelteBoundary(node, context) {
|
|
|
55
56
|
});
|
|
56
57
|
}
|
|
57
58
|
}
|
|
59
|
+
|
|
60
|
+
if (child.type === 'DeclarationTag') {
|
|
61
|
+
has_declaration = true;
|
|
62
|
+
}
|
|
58
63
|
}
|
|
59
64
|
|
|
60
65
|
for (const child of node.fragment.nodes) {
|
|
@@ -68,10 +73,10 @@ export function SvelteBoundary(node, context) {
|
|
|
68
73
|
if (child.type === 'SnippetBlock') {
|
|
69
74
|
if (
|
|
70
75
|
context.state.options.experimental.async &&
|
|
71
|
-
has_const &&
|
|
76
|
+
(has_const || has_declaration) &&
|
|
72
77
|
!['failed', 'pending'].includes(child.expression.name)
|
|
73
78
|
) {
|
|
74
|
-
// we can't hoist snippets as they may reference const tags, so we just keep them in the fragment
|
|
79
|
+
// we can't hoist snippets as they may reference const/declaration tags, so we just keep them in the fragment
|
|
75
80
|
nodes.push(child);
|
|
76
81
|
} else {
|
|
77
82
|
/** @type {Statement[]} */
|
|
@@ -49,8 +49,13 @@ export function VariableDeclaration(node, context) {
|
|
|
49
49
|
}
|
|
50
50
|
|
|
51
51
|
if (declarator.id.type === 'Identifier') {
|
|
52
|
+
const exclude_id = context.state.scope.root.unique('rest_excludes');
|
|
53
|
+
context.state.hoisted.push(
|
|
54
|
+
b.var(exclude_id, b.new('Set', b.array(seen.map((name) => b.literal(name)))))
|
|
55
|
+
);
|
|
56
|
+
|
|
52
57
|
/** @type {Expression[]} */
|
|
53
|
-
const args = [b.id('$$props'),
|
|
58
|
+
const args = [b.id('$$props'), exclude_id];
|
|
54
59
|
|
|
55
60
|
if (dev) {
|
|
56
61
|
// include rest name, so we can provide informative error messages
|
|
@@ -95,8 +100,13 @@ export function VariableDeclaration(node, context) {
|
|
|
95
100
|
}
|
|
96
101
|
} else {
|
|
97
102
|
// RestElement
|
|
103
|
+
const exclude_id = context.state.scope.root.unique('rest_excludes');
|
|
104
|
+
context.state.hoisted.push(
|
|
105
|
+
b.var(exclude_id, b.new('Set', b.array(seen.map((name) => b.literal(name)))))
|
|
106
|
+
);
|
|
107
|
+
|
|
98
108
|
/** @type {Expression[]} */
|
|
99
|
-
const args = [b.id('$$props'),
|
|
109
|
+
const args = [b.id('$$props'), exclude_id];
|
|
100
110
|
|
|
101
111
|
if (dev) {
|
|
102
112
|
// include rest name, so we can provide informative error messages
|
|
@@ -15,6 +15,7 @@ import { CallExpression } from './visitors/CallExpression.js';
|
|
|
15
15
|
import { ClassBody } from './visitors/ClassBody.js';
|
|
16
16
|
import { Component } from './visitors/Component.js';
|
|
17
17
|
import { ConstTag } from './visitors/ConstTag.js';
|
|
18
|
+
import { DeclarationTag } from './visitors/DeclarationTag.js';
|
|
18
19
|
import { DebugTag } from './visitors/DebugTag.js';
|
|
19
20
|
import { EachBlock } from './visitors/EachBlock.js';
|
|
20
21
|
import { ExpressionStatement } from './visitors/ExpressionStatement.js';
|
|
@@ -64,6 +65,7 @@ const template_visitors = {
|
|
|
64
65
|
AwaitBlock,
|
|
65
66
|
Component,
|
|
66
67
|
ConstTag,
|
|
68
|
+
DeclarationTag,
|
|
67
69
|
DebugTag,
|
|
68
70
|
EachBlock,
|
|
69
71
|
Fragment,
|
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
/** @import { Expression, Pattern
|
|
1
|
+
/** @import { Expression, Pattern } from 'estree' */
|
|
2
2
|
/** @import { AST } from '#compiler' */
|
|
3
3
|
/** @import { ComponentContext } from '../types.js' */
|
|
4
4
|
import * as b from '#compiler/builders';
|
|
5
5
|
import { extract_identifiers } from '../../../../utils/ast.js';
|
|
6
|
+
import { add_async_declaration } from './DeclarationTag.js';
|
|
6
7
|
|
|
7
8
|
/**
|
|
8
9
|
* @param {AST.ConstTag} node
|
|
@@ -12,31 +13,15 @@ export function ConstTag(node, context) {
|
|
|
12
13
|
const declaration = node.declaration.declarations[0];
|
|
13
14
|
const id = /** @type {Pattern} */ (context.visit(declaration.id));
|
|
14
15
|
const init = /** @type {Expression} */ (context.visit(declaration.init));
|
|
15
|
-
const blockers = [...node.metadata.expression.dependencies]
|
|
16
|
-
.map((dep) => dep.blocker)
|
|
17
|
-
.filter((b) => b !== null && b.object !== context.state.async_consts?.id);
|
|
18
16
|
|
|
19
17
|
if (node.metadata.promises_id) {
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
for (const identifier of identifiers) {
|
|
28
|
-
context.state.init.push(b.let(identifier.name));
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
if (blockers.length === 1) {
|
|
32
|
-
run.thunks.push(b.thunk(/** @type {Expression} */ (blockers[0])));
|
|
33
|
-
} else if (blockers.length > 0) {
|
|
34
|
-
run.thunks.push(b.thunk(b.call('Promise.all', b.array(blockers))));
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
// keep the number of thunks pushed in sync with ConstTag in analysis phase
|
|
38
|
-
const assignment = b.assignment('=', id, init);
|
|
39
|
-
run.thunks.push(b.thunk(assignment, node.metadata.expression.has_await));
|
|
18
|
+
add_async_declaration(
|
|
19
|
+
context,
|
|
20
|
+
node.metadata,
|
|
21
|
+
extract_identifiers(id),
|
|
22
|
+
[b.stmt(b.assignment('=', id, init))],
|
|
23
|
+
'let'
|
|
24
|
+
);
|
|
40
25
|
} else {
|
|
41
26
|
context.state.init.push(b.const(id, init));
|
|
42
27
|
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/** @import { Expression, Identifier, Pattern, Statement, ExpressionStatement, VariableDeclaration } from 'estree' */
|
|
2
|
+
/** @import { AST } from '#compiler' */
|
|
3
|
+
/** @import { ComponentContext } from '../types.js' */
|
|
4
|
+
import { extract_identifiers, has_await_expression } from '../../../../utils/ast.js';
|
|
5
|
+
import * as b from '#compiler/builders';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* @param {AST.DeclarationTag} node
|
|
9
|
+
* @param {ComponentContext} context
|
|
10
|
+
*/
|
|
11
|
+
export function DeclarationTag(node, context) {
|
|
12
|
+
const declaration = /** @type {Statement} */ (context.visit(node.declaration));
|
|
13
|
+
|
|
14
|
+
if (
|
|
15
|
+
node.metadata.promises_id &&
|
|
16
|
+
node.declaration.type === 'VariableDeclaration' &&
|
|
17
|
+
declaration.type === 'VariableDeclaration'
|
|
18
|
+
) {
|
|
19
|
+
const { ids, assignments } = build_async_declaration_parts(declaration);
|
|
20
|
+
add_async_declaration(context, node.metadata, ids, assignments, declaration.kind);
|
|
21
|
+
} else {
|
|
22
|
+
context.state.init.push(declaration);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* @param {VariableDeclaration} declaration
|
|
28
|
+
*/
|
|
29
|
+
export function build_async_declaration_parts(declaration) {
|
|
30
|
+
const ids = new Map();
|
|
31
|
+
for (const declarator of declaration.declarations) {
|
|
32
|
+
for (const id of extract_identifiers(declarator.id)) {
|
|
33
|
+
ids.set(id.name, id);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const assignments = declaration.declarations
|
|
38
|
+
.filter((declarator) => declarator.init !== null)
|
|
39
|
+
.map((declarator) =>
|
|
40
|
+
b.stmt(
|
|
41
|
+
b.assignment(
|
|
42
|
+
'=',
|
|
43
|
+
/** @type {Pattern} */ (declarator.id),
|
|
44
|
+
/** @type {Expression} */ (declarator.init)
|
|
45
|
+
)
|
|
46
|
+
)
|
|
47
|
+
);
|
|
48
|
+
|
|
49
|
+
return { ids: [...ids.values()], assignments };
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* @param {ComponentContext} context
|
|
54
|
+
* @param {AST.ConstTag['metadata'] | AST.DeclarationTag['metadata']} metadata
|
|
55
|
+
* @param {Identifier[]} ids
|
|
56
|
+
* @param {ExpressionStatement[]} assignments
|
|
57
|
+
* @param {VariableDeclaration['kind']} [kind]
|
|
58
|
+
*/
|
|
59
|
+
export function add_async_declaration(context, metadata, ids, assignments, kind = 'let') {
|
|
60
|
+
const run = (context.state.async_consts ??= {
|
|
61
|
+
id: /** @type {Identifier} */ (metadata.promises_id),
|
|
62
|
+
thunks: []
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
for (const id of ids) {
|
|
66
|
+
context.state.init.push(kind === 'var' ? b.var(id.name) : b.let(id.name));
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const blockers = [...metadata.expression.dependencies]
|
|
70
|
+
.map((dep) => dep.blocker)
|
|
71
|
+
.filter((b) => b !== null && b.object !== context.state.async_consts?.id);
|
|
72
|
+
|
|
73
|
+
if (blockers.length === 1) {
|
|
74
|
+
run.thunks.push(b.thunk(/** @type {Expression} */ (blockers[0])));
|
|
75
|
+
} else if (blockers.length > 0) {
|
|
76
|
+
run.thunks.push(b.thunk(b.call('Promise.all', b.array(blockers))));
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// keep the number of thunks pushed in sync with analysis phase
|
|
80
|
+
const has_await =
|
|
81
|
+
metadata.expression.has_await ||
|
|
82
|
+
assignments.some((assignment) => has_await_expression(assignment));
|
|
83
|
+
const body = assignments.length === 1 ? assignments[0].expression : b.block(assignments);
|
|
84
|
+
run.thunks.push(b.thunk(body, has_await));
|
|
85
|
+
}
|
|
@@ -17,17 +17,23 @@ import { is_customizable_select_element } from '../../../nodes.js';
|
|
|
17
17
|
export function RegularElement(node, context) {
|
|
18
18
|
const name = context.state.namespace === 'html' ? node.name.toLowerCase() : node.name;
|
|
19
19
|
const namespace = determine_namespace_for_children(node, context.state.namespace);
|
|
20
|
+
const has_child_declarations = !node.fragment.metadata.transparent;
|
|
20
21
|
|
|
21
22
|
/** @type {ComponentServerTransformState} */
|
|
22
23
|
const state = {
|
|
23
24
|
...context.state,
|
|
24
25
|
namespace,
|
|
26
|
+
scope: /** @type {Scope} */ (context.state.scopes.get(node.fragment)),
|
|
25
27
|
preserve_whitespace:
|
|
26
28
|
context.state.preserve_whitespace || node.name === 'pre' || node.name === 'textarea',
|
|
27
29
|
init: [],
|
|
28
|
-
template: []
|
|
30
|
+
template: [],
|
|
31
|
+
async_consts: undefined
|
|
29
32
|
};
|
|
30
33
|
|
|
34
|
+
/** @type {ComponentServerTransformState} */
|
|
35
|
+
const attribute_state = { ...state, scope: context.state.scope };
|
|
36
|
+
|
|
31
37
|
const node_is_void = is_void(name);
|
|
32
38
|
|
|
33
39
|
const optimiser = new PromiseOptimiser();
|
|
@@ -50,7 +56,11 @@ export function RegularElement(node, context) {
|
|
|
50
56
|
if (!is_special) {
|
|
51
57
|
// only open the tag in the non-special path
|
|
52
58
|
state.template.push(b.literal(`<${name}`));
|
|
53
|
-
body = build_element_attributes(
|
|
59
|
+
body = build_element_attributes(
|
|
60
|
+
node,
|
|
61
|
+
{ ...context, state: attribute_state },
|
|
62
|
+
optimiser.transform
|
|
63
|
+
);
|
|
54
64
|
state.template.push(b.literal(node_is_void ? '/>' : '>')); // add `/>` for XHTML compliance
|
|
55
65
|
}
|
|
56
66
|
|
|
@@ -72,10 +82,7 @@ export function RegularElement(node, context) {
|
|
|
72
82
|
node.fragment.nodes,
|
|
73
83
|
context.path,
|
|
74
84
|
namespace,
|
|
75
|
-
|
|
76
|
-
...state,
|
|
77
|
-
scope: /** @type {Scope} */ (state.scopes.get(node.fragment))
|
|
78
|
-
},
|
|
85
|
+
state,
|
|
79
86
|
state.preserve_whitespace,
|
|
80
87
|
state.options.preserveComments
|
|
81
88
|
);
|
|
@@ -205,7 +212,17 @@ export function RegularElement(node, context) {
|
|
|
205
212
|
state.template.push(b.stmt(b.call('$.pop_element')));
|
|
206
213
|
}
|
|
207
214
|
|
|
208
|
-
if (
|
|
215
|
+
if (has_child_declarations && state.async_consts && state.async_consts.thunks.length > 0) {
|
|
216
|
+
state.init.push(
|
|
217
|
+
b.var(state.async_consts.id, b.call('$$renderer.run', b.array(state.async_consts.thunks)))
|
|
218
|
+
);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
if (has_child_declarations) {
|
|
222
|
+
context.state.template.push(
|
|
223
|
+
...optimiser.render([b.block([...state.init, ...build_template(state.template)])])
|
|
224
|
+
);
|
|
225
|
+
} else if (optimiser.is_async()) {
|
|
209
226
|
context.state.template.push(
|
|
210
227
|
...optimiser.render([...state.init, ...build_template(state.template)])
|
|
211
228
|
);
|
|
@@ -343,7 +343,7 @@ export class PromiseOptimiser {
|
|
|
343
343
|
* @param {ExpressionMetadata} metadata
|
|
344
344
|
*/
|
|
345
345
|
check_blockers(metadata) {
|
|
346
|
-
for (const binding of metadata.
|
|
346
|
+
for (const binding of metadata.references) {
|
|
347
347
|
if (binding.blocker) {
|
|
348
348
|
this.#blockers.add(binding.blocker);
|
|
349
349
|
}
|
|
@@ -603,6 +603,48 @@ const svelte_visitors = (comments) => ({
|
|
|
603
603
|
context.write('}');
|
|
604
604
|
},
|
|
605
605
|
|
|
606
|
+
DeclarationTag(node, context) {
|
|
607
|
+
context.write('{');
|
|
608
|
+
|
|
609
|
+
// This is duplicated from esrap's handling of VariableDeclaration,
|
|
610
|
+
// which we need to do in order to omit the trailing semicolon that esrap would add.
|
|
611
|
+
const open = context.new();
|
|
612
|
+
const join = context.new();
|
|
613
|
+
const child_context = context.new();
|
|
614
|
+
|
|
615
|
+
context.append(child_context);
|
|
616
|
+
|
|
617
|
+
child_context.write(`${node.declaration.kind} `);
|
|
618
|
+
child_context.append(open);
|
|
619
|
+
|
|
620
|
+
const declarations = node.declaration.declarations;
|
|
621
|
+
let first = true;
|
|
622
|
+
|
|
623
|
+
for (const d of declarations) {
|
|
624
|
+
if (!first) child_context.append(join);
|
|
625
|
+
first = false;
|
|
626
|
+
|
|
627
|
+
child_context.visit(d);
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
const length = child_context.measure() + 2 * (declarations.length - 1);
|
|
631
|
+
|
|
632
|
+
const multiline = child_context.multiline || (declarations.length > 1 && length > 50);
|
|
633
|
+
|
|
634
|
+
if (multiline) {
|
|
635
|
+
context.multiline = true;
|
|
636
|
+
|
|
637
|
+
if (declarations.length > 1) open.indent();
|
|
638
|
+
join.write(',');
|
|
639
|
+
join.newline();
|
|
640
|
+
if (declarations.length > 1) context.dedent();
|
|
641
|
+
} else {
|
|
642
|
+
join.write(', ');
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
context.write('}');
|
|
646
|
+
},
|
|
647
|
+
|
|
606
648
|
DebugTag(node, context) {
|
|
607
649
|
context.write('{@debug ');
|
|
608
650
|
let started = false;
|
|
@@ -396,7 +396,7 @@ export class Boundary {
|
|
|
396
396
|
if (this.#pending_effect) current_batch.skip_effect(this.#pending_effect);
|
|
397
397
|
if (this.#failed_effect) current_batch.skip_effect(this.#failed_effect);
|
|
398
398
|
|
|
399
|
-
current_batch.
|
|
399
|
+
current_batch.oncommit(() => {
|
|
400
400
|
this.#handle_error(error);
|
|
401
401
|
});
|
|
402
402
|
} else {
|
|
@@ -203,7 +203,9 @@ export function each(node, flags, get_collection, get_key, render_fn, fallback_f
|
|
|
203
203
|
var each_array = derived_safe_equal(() => {
|
|
204
204
|
var collection = get_collection();
|
|
205
205
|
|
|
206
|
-
return
|
|
206
|
+
return /** @type {V[]} */ (
|
|
207
|
+
is_array(collection) ? collection : collection == null ? [] : array_from(collection)
|
|
208
|
+
);
|
|
207
209
|
});
|
|
208
210
|
|
|
209
211
|
if (DEV) {
|