svelte 5.47.1 → 5.48.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 +1 -1
- package/src/compiler/errors.js +2 -2
- package/src/compiler/index.js +25 -1
- package/src/compiler/phases/1-parse/index.js +14 -0
- package/src/compiler/phases/1-parse/read/style.js +17 -13
- package/src/compiler/phases/3-transform/client/transform-client.js +1 -0
- package/src/compiler/phases/3-transform/client/visitors/ConstTag.js +11 -5
- package/src/compiler/phases/3-transform/client/visitors/Fragment.js +4 -3
- package/src/compiler/phases/3-transform/client/visitors/RegularElement.js +2 -1
- package/src/compiler/phases/3-transform/client/visitors/SnippetBlock.js +1 -1
- package/src/compiler/phases/3-transform/client/visitors/SvelteBoundary.js +1 -1
- package/src/compiler/phases/3-transform/client/visitors/SvelteElement.js +1 -1
- package/src/compiler/phases/3-transform/client/visitors/shared/component.js +1 -1
- package/src/compiler/phases/3-transform/server/visitors/ConstTag.js +4 -2
- package/src/compiler/phases/3-transform/server/visitors/HtmlTag.js +11 -1
- package/src/compiler/phases/3-transform/server/visitors/SvelteElement.js +1 -1
- package/src/compiler/phases/3-transform/shared/transform-async.js +15 -12
- package/src/internal/client/dom/blocks/async.js +7 -2
- package/src/internal/client/dom/blocks/boundary.js +13 -7
- package/src/internal/client/dom/elements/attributes.js +2 -2
- package/src/internal/client/reactivity/async.js +65 -40
- package/src/internal/client/reactivity/batch.js +27 -30
- package/src/internal/client/reactivity/effects.js +3 -3
- package/src/internal/client/reactivity/sources.js +9 -19
- package/src/internal/client/runtime.js +17 -14
- package/src/internal/client/validate.js +2 -1
- package/src/internal/server/dev.js +6 -1
- package/src/version.js +1 -1
- package/types/index.d.ts +6 -0
- package/types/index.d.ts.map +2 -1
package/package.json
CHANGED
package/src/compiler/errors.js
CHANGED
|
@@ -977,12 +977,12 @@ export function const_tag_invalid_expression(node) {
|
|
|
977
977
|
}
|
|
978
978
|
|
|
979
979
|
/**
|
|
980
|
-
* `{@const}` must be the immediate child of `{#snippet}`, `{#if}`, `{:else if}`, `{:else}`, `{#each}`, `{:then}`, `{:catch}`, `<svelte:fragment>`, `<svelte:boundary
|
|
980
|
+
* `{@const}` must be the immediate child of `{#snippet}`, `{#if}`, `{:else if}`, `{:else}`, `{#each}`, `{:then}`, `{:catch}`, `<svelte:fragment>`, `<svelte:boundary>` or `<Component>`
|
|
981
981
|
* @param {null | number | NodeLike} node
|
|
982
982
|
* @returns {never}
|
|
983
983
|
*/
|
|
984
984
|
export function const_tag_invalid_placement(node) {
|
|
985
|
-
e(node, 'const_tag_invalid_placement', `\`{@const}\` must be the immediate child of \`{#snippet}\`, \`{#if}\`, \`{:else if}\`, \`{:else}\`, \`{#each}\`, \`{:then}\`, \`{:catch}\`, \`<svelte:fragment>\`, \`<svelte:boundary
|
|
985
|
+
e(node, 'const_tag_invalid_placement', `\`{@const}\` must be the immediate child of \`{#snippet}\`, \`{#if}\`, \`{:else if}\`, \`{:else}\`, \`{#each}\`, \`{:then}\`, \`{:catch}\`, \`<svelte:fragment>\`, \`<svelte:boundary>\` or \`<Component>\`\nhttps://svelte.dev/e/const_tag_invalid_placement`);
|
|
986
986
|
}
|
|
987
987
|
|
|
988
988
|
/**
|
package/src/compiler/index.js
CHANGED
|
@@ -3,8 +3,9 @@
|
|
|
3
3
|
/** @import { AST } from './public.js' */
|
|
4
4
|
import { walk as zimmerframe_walk } from 'zimmerframe';
|
|
5
5
|
import { convert } from './legacy.js';
|
|
6
|
-
import { parse as _parse } from './phases/1-parse/index.js';
|
|
6
|
+
import { parse as _parse, Parser } from './phases/1-parse/index.js';
|
|
7
7
|
import { remove_typescript_nodes } from './phases/1-parse/remove_typescript_nodes.js';
|
|
8
|
+
import { parse_stylesheet } from './phases/1-parse/read/style.js';
|
|
8
9
|
import { analyze_component, analyze_module } from './phases/2-analyze/index.js';
|
|
9
10
|
import { transform_component, transform_module } from './phases/3-transform/index.js';
|
|
10
11
|
import { validate_component_options, validate_module_options } from './validate-options.js';
|
|
@@ -118,6 +119,29 @@ export function parse(source, { modern, loose } = {}) {
|
|
|
118
119
|
return to_public_ast(source, ast, modern);
|
|
119
120
|
}
|
|
120
121
|
|
|
122
|
+
/**
|
|
123
|
+
* The parseCss function parses a CSS stylesheet, returning its abstract syntax tree.
|
|
124
|
+
*
|
|
125
|
+
* @param {string} source The CSS source code
|
|
126
|
+
* @returns {Omit<AST.CSS.StyleSheet, 'attributes' | 'content'>}
|
|
127
|
+
*/
|
|
128
|
+
export function parseCss(source) {
|
|
129
|
+
source = remove_bom(source);
|
|
130
|
+
state.reset({ warning: () => false, filename: undefined });
|
|
131
|
+
|
|
132
|
+
state.set_source(source);
|
|
133
|
+
|
|
134
|
+
const parser = Parser.forCss(source);
|
|
135
|
+
const children = parse_stylesheet(parser);
|
|
136
|
+
|
|
137
|
+
return {
|
|
138
|
+
type: 'StyleSheet',
|
|
139
|
+
start: 0,
|
|
140
|
+
end: source.length,
|
|
141
|
+
children
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
|
|
121
145
|
/**
|
|
122
146
|
* @param {string} source
|
|
123
147
|
* @param {AST.Root} ast
|
|
@@ -34,6 +34,20 @@ export class Parser {
|
|
|
34
34
|
/** */
|
|
35
35
|
index = 0;
|
|
36
36
|
|
|
37
|
+
/**
|
|
38
|
+
* Creates a minimal parser instance for CSS-only parsing.
|
|
39
|
+
* Skips Svelte component parsing setup.
|
|
40
|
+
* @param {string} source
|
|
41
|
+
* @returns {Parser}
|
|
42
|
+
*/
|
|
43
|
+
static forCss(source) {
|
|
44
|
+
const parser = Object.create(Parser.prototype);
|
|
45
|
+
parser.template = source;
|
|
46
|
+
parser.index = 0;
|
|
47
|
+
parser.loose = false;
|
|
48
|
+
return parser;
|
|
49
|
+
}
|
|
50
|
+
|
|
37
51
|
/** Whether we're parsing in TypeScript mode */
|
|
38
52
|
ts = false;
|
|
39
53
|
|
|
@@ -24,10 +24,11 @@ const REGEX_HTML_COMMENT_CLOSE = /-->/;
|
|
|
24
24
|
*/
|
|
25
25
|
export default function read_style(parser, start, attributes) {
|
|
26
26
|
const content_start = parser.index;
|
|
27
|
-
const children = read_body(parser, '</style');
|
|
27
|
+
const children = read_body(parser, (p) => p.match('</style') || p.index >= p.template.length);
|
|
28
28
|
const content_end = parser.index;
|
|
29
29
|
|
|
30
|
-
parser.
|
|
30
|
+
parser.eat('</style', true);
|
|
31
|
+
parser.read(/^\s*>/);
|
|
31
32
|
|
|
32
33
|
return {
|
|
33
34
|
type: 'StyleSheet',
|
|
@@ -46,20 +47,14 @@ export default function read_style(parser, start, attributes) {
|
|
|
46
47
|
|
|
47
48
|
/**
|
|
48
49
|
* @param {Parser} parser
|
|
49
|
-
* @param {
|
|
50
|
-
* @returns {
|
|
50
|
+
* @param {(parser: Parser) => boolean} finished
|
|
51
|
+
* @returns {Array<AST.CSS.Rule | AST.CSS.Atrule>}
|
|
51
52
|
*/
|
|
52
|
-
function read_body(parser,
|
|
53
|
+
function read_body(parser, finished) {
|
|
53
54
|
/** @type {Array<AST.CSS.Rule | AST.CSS.Atrule>} */
|
|
54
55
|
const children = [];
|
|
55
56
|
|
|
56
|
-
while (parser
|
|
57
|
-
allow_comment_or_whitespace(parser);
|
|
58
|
-
|
|
59
|
-
if (parser.match(close)) {
|
|
60
|
-
return children;
|
|
61
|
-
}
|
|
62
|
-
|
|
57
|
+
while ((allow_comment_or_whitespace(parser), !finished(parser))) {
|
|
63
58
|
if (parser.match('@')) {
|
|
64
59
|
children.push(read_at_rule(parser));
|
|
65
60
|
} else {
|
|
@@ -67,7 +62,7 @@ function read_body(parser, close) {
|
|
|
67
62
|
}
|
|
68
63
|
}
|
|
69
64
|
|
|
70
|
-
|
|
65
|
+
return children;
|
|
71
66
|
}
|
|
72
67
|
|
|
73
68
|
/**
|
|
@@ -627,3 +622,12 @@ function allow_comment_or_whitespace(parser) {
|
|
|
627
622
|
parser.allow_whitespace();
|
|
628
623
|
}
|
|
629
624
|
}
|
|
625
|
+
|
|
626
|
+
/**
|
|
627
|
+
* Parse standalone CSS content (not wrapped in `<style>`).
|
|
628
|
+
* @param {Parser} parser
|
|
629
|
+
* @returns {Array<AST.CSS.Rule | AST.CSS.Atrule>}
|
|
630
|
+
*/
|
|
631
|
+
export function parse_stylesheet(parser) {
|
|
632
|
+
return read_body(parser, (p) => p.index >= p.template.length);
|
|
633
|
+
}
|
|
@@ -170,6 +170,7 @@ export function client_component(analysis, options) {
|
|
|
170
170
|
// these are set inside the `Fragment` visitor, and cannot be used until then
|
|
171
171
|
init: /** @type {any} */ (null),
|
|
172
172
|
consts: /** @type {any} */ (null),
|
|
173
|
+
snippets: /** @type {any} */ (null),
|
|
173
174
|
let_directives: /** @type {any} */ (null),
|
|
174
175
|
update: /** @type {any} */ (null),
|
|
175
176
|
after_update: /** @type {any} */ (null),
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/** @import { Pattern } from 'estree' */
|
|
1
|
+
/** @import { Expression, Identifier, Pattern } from 'estree' */
|
|
2
2
|
/** @import { AST } from '#compiler' */
|
|
3
3
|
/** @import { ComponentContext } from '../types' */
|
|
4
4
|
/** @import { ExpressionMetadata } from '../../../nodes.js' */
|
|
@@ -88,8 +88,8 @@ export function ConstTag(node, context) {
|
|
|
88
88
|
|
|
89
89
|
/**
|
|
90
90
|
* @param {ComponentContext['state']} state
|
|
91
|
-
* @param {
|
|
92
|
-
* @param {
|
|
91
|
+
* @param {Identifier} id
|
|
92
|
+
* @param {Expression} expression
|
|
93
93
|
* @param {ExpressionMetadata} metadata
|
|
94
94
|
* @param {import('#compiler').Binding[]} bindings
|
|
95
95
|
*/
|
|
@@ -99,7 +99,9 @@ function add_const_declaration(state, id, expression, metadata, bindings) {
|
|
|
99
99
|
const after = dev ? [b.stmt(b.call('$.get', id))] : [];
|
|
100
100
|
|
|
101
101
|
const has_await = metadata.has_await;
|
|
102
|
-
const blockers = [...metadata.dependencies]
|
|
102
|
+
const blockers = [...metadata.dependencies]
|
|
103
|
+
.map((dep) => dep.blocker)
|
|
104
|
+
.filter((b) => b !== null && b.object !== state.async_consts?.id);
|
|
103
105
|
|
|
104
106
|
if (has_await || state.async_consts || blockers.length > 0) {
|
|
105
107
|
const run = (state.async_consts ??= {
|
|
@@ -112,7 +114,11 @@ function add_const_declaration(state, id, expression, metadata, bindings) {
|
|
|
112
114
|
const assignment = b.assignment('=', id, expression);
|
|
113
115
|
const body = after.length === 0 ? assignment : b.block([b.stmt(assignment), ...after]);
|
|
114
116
|
|
|
115
|
-
if (blockers.length
|
|
117
|
+
if (blockers.length === 1) {
|
|
118
|
+
run.thunks.push(b.thunk(b.member(/** @type {Expression} */ (blockers[0]), 'promise')));
|
|
119
|
+
} else if (blockers.length > 0) {
|
|
120
|
+
run.thunks.push(b.thunk(b.call('$.wait', b.array(blockers))));
|
|
121
|
+
}
|
|
116
122
|
|
|
117
123
|
run.thunks.push(b.thunk(body, has_await));
|
|
118
124
|
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
/** @import { Expression, Statement } from 'estree' */
|
|
2
2
|
/** @import { AST } from '#compiler' */
|
|
3
3
|
/** @import { ComponentClientTransformState, ComponentContext } from '../types' */
|
|
4
|
-
import { TEMPLATE_FRAGMENT, TEMPLATE_USE_IMPORT_NODE } from '../../../../../constants.js';
|
|
5
4
|
import * as b from '#compiler/builders';
|
|
5
|
+
import { TEMPLATE_FRAGMENT, TEMPLATE_USE_IMPORT_NODE } from '../../../../../constants.js';
|
|
6
6
|
import { clean_nodes, infer_namespace } from '../../utils.js';
|
|
7
7
|
import { transform_template } from '../transform-template/index.js';
|
|
8
|
+
import { Template } from '../transform-template/template.js';
|
|
8
9
|
import { process_children } from './shared/fragment.js';
|
|
9
10
|
import { build_render_statement, Memoizer } from './shared/utils.js';
|
|
10
|
-
import { Template } from '../transform-template/template.js';
|
|
11
11
|
|
|
12
12
|
/**
|
|
13
13
|
* @param {AST.Fragment} node
|
|
@@ -60,6 +60,7 @@ export function Fragment(node, context) {
|
|
|
60
60
|
const state = {
|
|
61
61
|
...context.state,
|
|
62
62
|
init: [],
|
|
63
|
+
snippets: [],
|
|
63
64
|
consts: [],
|
|
64
65
|
let_directives: [],
|
|
65
66
|
update: [],
|
|
@@ -150,7 +151,7 @@ export function Fragment(node, context) {
|
|
|
150
151
|
}
|
|
151
152
|
}
|
|
152
153
|
|
|
153
|
-
body.push(...state.let_directives, ...state.consts);
|
|
154
|
+
body.push(...state.snippets, ...state.let_directives, ...state.consts);
|
|
154
155
|
|
|
155
156
|
if (state.async_consts && state.async_consts.thunks.length > 0) {
|
|
156
157
|
body.push(b.var(state.async_consts.id, b.call('$.run', b.array(state.async_consts.thunks))));
|
|
@@ -329,7 +329,7 @@ export function RegularElement(node, context) {
|
|
|
329
329
|
);
|
|
330
330
|
|
|
331
331
|
/** @type {typeof state} */
|
|
332
|
-
const child_state = { ...state, init: [], update: [], after_update: [] };
|
|
332
|
+
const child_state = { ...state, init: [], update: [], after_update: [], snippets: [] };
|
|
333
333
|
|
|
334
334
|
for (const node of hoisted) {
|
|
335
335
|
context.visit(node, child_state);
|
|
@@ -441,6 +441,7 @@ export function RegularElement(node, context) {
|
|
|
441
441
|
// Wrap children in `{...}` to avoid declaration conflicts
|
|
442
442
|
context.state.init.push(
|
|
443
443
|
b.block([
|
|
444
|
+
...child_state.snippets,
|
|
444
445
|
...child_state.init,
|
|
445
446
|
...element_state.init,
|
|
446
447
|
child_state.update.length > 0 ? build_render_statement(child_state) : b.empty,
|
|
@@ -77,7 +77,7 @@ export function SvelteBoundary(node, context) {
|
|
|
77
77
|
/** @type {Statement[]} */
|
|
78
78
|
const statements = [];
|
|
79
79
|
|
|
80
|
-
context.visit(child, { ...context.state,
|
|
80
|
+
context.visit(child, { ...context.state, snippets: statements });
|
|
81
81
|
|
|
82
82
|
const snippet = /** @type {VariableDeclaration} */ (statements[0]);
|
|
83
83
|
|
|
@@ -117,10 +117,10 @@ export function SvelteElement(node, context) {
|
|
|
117
117
|
);
|
|
118
118
|
|
|
119
119
|
if (dev) {
|
|
120
|
+
statements.push(b.stmt(b.call('$.validate_dynamic_element_tag', get_tag)));
|
|
120
121
|
if (node.fragment.nodes.length > 0) {
|
|
121
122
|
statements.push(b.stmt(b.call('$.validate_void_dynamic_element', get_tag)));
|
|
122
123
|
}
|
|
123
|
-
statements.push(b.stmt(b.call('$.validate_dynamic_element_tag', get_tag)));
|
|
124
124
|
}
|
|
125
125
|
|
|
126
126
|
const location = dev && locator(node.start);
|
|
@@ -333,7 +333,7 @@ export function build_component(node, component_name, loc, context) {
|
|
|
333
333
|
// can be used as props without creating conflicts
|
|
334
334
|
context.visit(child, {
|
|
335
335
|
...context.state,
|
|
336
|
-
|
|
336
|
+
snippets: snippet_declarations
|
|
337
337
|
});
|
|
338
338
|
|
|
339
339
|
push_prop(b.prop('init', child.expression, child.expression));
|
|
@@ -15,7 +15,7 @@ export function ConstTag(node, context) {
|
|
|
15
15
|
const has_await = node.metadata.expression.has_await;
|
|
16
16
|
const blockers = [...node.metadata.expression.dependencies]
|
|
17
17
|
.map((dep) => dep.blocker)
|
|
18
|
-
.filter((b) => b !== null);
|
|
18
|
+
.filter((b) => b !== null && b.object !== context.state.async_consts?.id);
|
|
19
19
|
|
|
20
20
|
if (has_await || context.state.async_consts || blockers.length > 0) {
|
|
21
21
|
const run = (context.state.async_consts ??= {
|
|
@@ -30,7 +30,9 @@ export function ConstTag(node, context) {
|
|
|
30
30
|
context.state.init.push(b.let(identifier.name));
|
|
31
31
|
}
|
|
32
32
|
|
|
33
|
-
if (blockers.length
|
|
33
|
+
if (blockers.length === 1) {
|
|
34
|
+
run.thunks.push(b.thunk(/** @type {Expression} */ (blockers[0])));
|
|
35
|
+
} else if (blockers.length > 0) {
|
|
34
36
|
run.thunks.push(b.thunk(b.call('Promise.all', b.array(blockers))));
|
|
35
37
|
}
|
|
36
38
|
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
/** @import { AST } from '#compiler' */
|
|
3
3
|
/** @import { ComponentContext } from '../types.js' */
|
|
4
4
|
import * as b from '#compiler/builders';
|
|
5
|
-
import { create_push } from './shared/utils.js';
|
|
5
|
+
import { block_close, block_open, create_push } from './shared/utils.js';
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
8
|
* @param {AST.HtmlTag} node
|
|
@@ -12,5 +12,15 @@ export function HtmlTag(node, context) {
|
|
|
12
12
|
const expression = /** @type {Expression} */ (context.visit(node.expression));
|
|
13
13
|
const call = b.call('$.html', expression);
|
|
14
14
|
|
|
15
|
+
const has_await = node.metadata.expression.has_await;
|
|
16
|
+
|
|
17
|
+
if (has_await) {
|
|
18
|
+
context.state.template.push(block_open);
|
|
19
|
+
}
|
|
20
|
+
|
|
15
21
|
context.state.template.push(create_push(call, node.metadata.expression, true));
|
|
22
|
+
|
|
23
|
+
if (has_await) {
|
|
24
|
+
context.state.template.push(block_close);
|
|
25
|
+
}
|
|
16
26
|
}
|
|
@@ -29,10 +29,10 @@ export function SvelteElement(node, context) {
|
|
|
29
29
|
tag = b.id(tag_id);
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
+
context.state.init.push(b.stmt(b.call('$.validate_dynamic_element_tag', b.thunk(tag))));
|
|
32
33
|
if (node.fragment.nodes.length > 0) {
|
|
33
34
|
context.state.init.push(b.stmt(b.call('$.validate_void_dynamic_element', b.thunk(tag))));
|
|
34
35
|
}
|
|
35
|
-
context.state.init.push(b.stmt(b.call('$.validate_dynamic_element_tag', b.thunk(tag))));
|
|
36
36
|
}
|
|
37
37
|
|
|
38
38
|
const state = {
|
|
@@ -49,22 +49,25 @@ export function transform_body(instance_body, runner, transform) {
|
|
|
49
49
|
if (instance_body.async.length > 0) {
|
|
50
50
|
const thunks = instance_body.async.map((s) => {
|
|
51
51
|
if (s.node.type === 'VariableDeclarator') {
|
|
52
|
-
const visited = /** @type {ESTree.VariableDeclaration} */ (
|
|
52
|
+
const visited = /** @type {ESTree.VariableDeclaration | ESTree.EmptyStatement} */ (
|
|
53
53
|
transform(b.var(s.node.id, s.node.init))
|
|
54
54
|
);
|
|
55
55
|
|
|
56
|
-
const statements =
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
56
|
+
const statements =
|
|
57
|
+
visited.type === 'VariableDeclaration'
|
|
58
|
+
? visited.declarations.map((node) => {
|
|
59
|
+
if (
|
|
60
|
+
node.id.type === 'Identifier' &&
|
|
61
|
+
(node.id.name.startsWith('$$d') || node.id.name.startsWith('$$array'))
|
|
62
|
+
) {
|
|
63
|
+
// this is an intermediate declaration created in VariableDeclaration.js;
|
|
64
|
+
// subsequent statements depend on it
|
|
65
|
+
return b.var(node.id, node.init);
|
|
66
|
+
}
|
|
65
67
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
+
return b.stmt(b.assignment('=', node.id, node.init ?? b.void0));
|
|
69
|
+
})
|
|
70
|
+
: [];
|
|
68
71
|
|
|
69
72
|
if (statements.length === 1) {
|
|
70
73
|
const statement = /** @type {ESTree.ExpressionStatement} */ (statements[0]);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/** @import { TemplateNode, Value } from '#client' */
|
|
1
|
+
/** @import { Blocker, TemplateNode, Value } from '#client' */
|
|
2
2
|
import { flatten } from '../../reactivity/async.js';
|
|
3
3
|
import { Batch, current_batch } from '../../reactivity/batch.js';
|
|
4
4
|
import { get } from '../../runtime.js';
|
|
@@ -14,11 +14,16 @@ import { get_boundary } from './boundary.js';
|
|
|
14
14
|
|
|
15
15
|
/**
|
|
16
16
|
* @param {TemplateNode} node
|
|
17
|
-
* @param {
|
|
17
|
+
* @param {Blocker[]} blockers
|
|
18
18
|
* @param {Array<() => Promise<any>>} expressions
|
|
19
19
|
* @param {(anchor: TemplateNode, ...deriveds: Value[]) => void} fn
|
|
20
20
|
*/
|
|
21
21
|
export function async(node, blockers = [], expressions = [], fn) {
|
|
22
|
+
if (expressions.length === 0 && blockers.every((b) => b.settled)) {
|
|
23
|
+
fn(node);
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
|
|
22
27
|
var boundary = get_boundary();
|
|
23
28
|
var batch = /** @type {Batch} */ (current_batch);
|
|
24
29
|
var blocking = boundary.is_rendered();
|
|
@@ -102,6 +102,7 @@ export class Boundary {
|
|
|
102
102
|
|
|
103
103
|
#local_pending_count = 0;
|
|
104
104
|
#pending_count = 0;
|
|
105
|
+
#pending_count_update_queued = false;
|
|
105
106
|
|
|
106
107
|
#is_creating_fallback = false;
|
|
107
108
|
|
|
@@ -202,12 +203,11 @@ export class Boundary {
|
|
|
202
203
|
|
|
203
204
|
#hydrate_pending_content() {
|
|
204
205
|
const pending = this.#props.pending;
|
|
205
|
-
if (!pending)
|
|
206
|
-
|
|
207
|
-
}
|
|
206
|
+
if (!pending) return;
|
|
207
|
+
|
|
208
208
|
this.#pending_effect = branch(() => pending(this.#anchor));
|
|
209
209
|
|
|
210
|
-
|
|
210
|
+
queue_micro_task(() => {
|
|
211
211
|
var anchor = this.#get_anchor();
|
|
212
212
|
|
|
213
213
|
this.#main_effect = this.#run(() => {
|
|
@@ -359,9 +359,15 @@ export class Boundary {
|
|
|
359
359
|
|
|
360
360
|
this.#local_pending_count += d;
|
|
361
361
|
|
|
362
|
-
if (this.#effect_pending)
|
|
363
|
-
|
|
364
|
-
|
|
362
|
+
if (!this.#effect_pending || this.#pending_count_update_queued) return;
|
|
363
|
+
this.#pending_count_update_queued = true;
|
|
364
|
+
|
|
365
|
+
queue_micro_task(() => {
|
|
366
|
+
this.#pending_count_update_queued = false;
|
|
367
|
+
if (this.#effect_pending) {
|
|
368
|
+
internal_set(this.#effect_pending, this.#local_pending_count);
|
|
369
|
+
}
|
|
370
|
+
});
|
|
365
371
|
}
|
|
366
372
|
|
|
367
373
|
get_effect_pending() {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/** @import { Effect } from '#client' */
|
|
1
|
+
/** @import { Blocker, Effect } from '#client' */
|
|
2
2
|
import { DEV } from 'esm-env';
|
|
3
3
|
import { hydrating, set_hydrating } from '../hydration.js';
|
|
4
4
|
import { get_descriptors, get_prototype_of } from '../../../shared/utils.js';
|
|
@@ -483,7 +483,7 @@ function set_attributes(
|
|
|
483
483
|
* @param {(...expressions: any) => Record<string | symbol, any>} fn
|
|
484
484
|
* @param {Array<() => any>} sync
|
|
485
485
|
* @param {Array<() => Promise<any>>} async
|
|
486
|
-
* @param {
|
|
486
|
+
* @param {Blocker[]} blockers
|
|
487
487
|
* @param {string} [css_hash]
|
|
488
488
|
* @param {boolean} [should_remove_defaults]
|
|
489
489
|
* @param {boolean} [skip_warning]
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/** @import {
|
|
1
|
+
/** @import { Blocker, Effect, Value } from '#client' */
|
|
2
2
|
import { DESTROYED, STALE_REACTION } from '#client/constants';
|
|
3
3
|
import { DEV } from 'esm-env';
|
|
4
4
|
import {
|
|
@@ -27,7 +27,7 @@ import {
|
|
|
27
27
|
import { aborted } from './effects.js';
|
|
28
28
|
|
|
29
29
|
/**
|
|
30
|
-
* @param {
|
|
30
|
+
* @param {Blocker[]} blockers
|
|
31
31
|
* @param {Array<() => any>} sync
|
|
32
32
|
* @param {Array<() => Promise<any>>} async
|
|
33
33
|
* @param {(values: Value[]) => any} fn
|
|
@@ -35,7 +35,10 @@ import { aborted } from './effects.js';
|
|
|
35
35
|
export function flatten(blockers, sync, async, fn) {
|
|
36
36
|
const d = is_runes() ? derived : derived_safe_equal;
|
|
37
37
|
|
|
38
|
-
|
|
38
|
+
// Filter out already-settled blockers - no need to wait for them
|
|
39
|
+
var pending = blockers.filter((b) => !b.settled);
|
|
40
|
+
|
|
41
|
+
if (async.length === 0 && pending.length === 0) {
|
|
39
42
|
fn(sync.map(d));
|
|
40
43
|
return;
|
|
41
44
|
}
|
|
@@ -44,47 +47,52 @@ export function flatten(blockers, sync, async, fn) {
|
|
|
44
47
|
var parent = /** @type {Effect} */ (active_effect);
|
|
45
48
|
|
|
46
49
|
var restore = capture();
|
|
50
|
+
var blocker_promise =
|
|
51
|
+
pending.length === 1
|
|
52
|
+
? pending[0].promise
|
|
53
|
+
: pending.length > 1
|
|
54
|
+
? Promise.all(pending.map((b) => b.promise))
|
|
55
|
+
: null;
|
|
56
|
+
|
|
57
|
+
/** @param {Value[]} values */
|
|
58
|
+
function finish(values) {
|
|
59
|
+
restore();
|
|
47
60
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
61
|
+
try {
|
|
62
|
+
fn(values);
|
|
63
|
+
} catch (error) {
|
|
64
|
+
if ((parent.f & DESTROYED) === 0) {
|
|
65
|
+
invoke_error_boundary(error, parent);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
52
68
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
// ignore errors in blocks that have already been destroyed
|
|
57
|
-
if ((parent.f & DESTROYED) === 0) {
|
|
58
|
-
invoke_error_boundary(error, parent);
|
|
59
|
-
}
|
|
60
|
-
}
|
|
69
|
+
batch?.deactivate();
|
|
70
|
+
unset_context();
|
|
71
|
+
}
|
|
61
72
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
invoke_error_boundary(error, parent);
|
|
67
|
-
});
|
|
73
|
+
// Fast path: blockers but no async expressions
|
|
74
|
+
if (async.length === 0) {
|
|
75
|
+
/** @type {Promise<any>} */ (blocker_promise).then(() => finish(sync.map(d)));
|
|
76
|
+
return;
|
|
68
77
|
}
|
|
69
78
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
79
|
+
// Full path: has async expressions
|
|
80
|
+
function run() {
|
|
81
|
+
restore();
|
|
82
|
+
Promise.all(async.map((expression) => async_derived(expression)))
|
|
83
|
+
.then((result) => finish([...sync.map(d), ...result]))
|
|
84
|
+
.catch((error) => invoke_error_boundary(error, parent));
|
|
85
|
+
}
|
|
73
86
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
} finally {
|
|
77
|
-
batch?.deactivate();
|
|
78
|
-
unset_context();
|
|
79
|
-
}
|
|
80
|
-
});
|
|
87
|
+
if (blocker_promise) {
|
|
88
|
+
blocker_promise.then(run);
|
|
81
89
|
} else {
|
|
82
90
|
run();
|
|
83
91
|
}
|
|
84
92
|
}
|
|
85
93
|
|
|
86
94
|
/**
|
|
87
|
-
* @param {
|
|
95
|
+
* @param {Blocker[]} blockers
|
|
88
96
|
* @param {(values: Value[]) => any} fn
|
|
89
97
|
*/
|
|
90
98
|
export function run_after_blockers(blockers, fn) {
|
|
@@ -239,7 +247,13 @@ export function run(thunks) {
|
|
|
239
247
|
|
|
240
248
|
var promise = Promise.resolve(thunks[0]()).catch(handle_error);
|
|
241
249
|
|
|
242
|
-
|
|
250
|
+
/** @type {Blocker} */
|
|
251
|
+
var blocker = { promise, settled: false };
|
|
252
|
+
var blockers = [blocker];
|
|
253
|
+
|
|
254
|
+
promise.finally(() => {
|
|
255
|
+
blocker.settled = true;
|
|
256
|
+
});
|
|
243
257
|
|
|
244
258
|
for (const fn of thunks.slice(1)) {
|
|
245
259
|
promise = promise
|
|
@@ -255,13 +269,17 @@ export function run(thunks) {
|
|
|
255
269
|
restore();
|
|
256
270
|
return fn();
|
|
257
271
|
})
|
|
258
|
-
.catch(handle_error)
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
});
|
|
272
|
+
.catch(handle_error);
|
|
273
|
+
|
|
274
|
+
const blocker = { promise, settled: false };
|
|
275
|
+
blockers.push(blocker);
|
|
263
276
|
|
|
264
|
-
|
|
277
|
+
promise.finally(() => {
|
|
278
|
+
blocker.settled = true;
|
|
279
|
+
|
|
280
|
+
unset_context();
|
|
281
|
+
current_batch?.deactivate();
|
|
282
|
+
});
|
|
265
283
|
}
|
|
266
284
|
|
|
267
285
|
promise
|
|
@@ -273,5 +291,12 @@ export function run(thunks) {
|
|
|
273
291
|
batch.decrement(blocking);
|
|
274
292
|
});
|
|
275
293
|
|
|
276
|
-
return
|
|
294
|
+
return blockers;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
/**
|
|
298
|
+
* @param {Blocker[]} blockers
|
|
299
|
+
*/
|
|
300
|
+
export function wait(blockers) {
|
|
301
|
+
return Promise.all(blockers.map((b) => b.promise));
|
|
277
302
|
}
|