svelte 5.55.1 → 5.55.3

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.
Files changed (31) hide show
  1. package/compiler/index.js +1 -1
  2. package/elements.d.ts +3 -3
  3. package/package.json +1 -1
  4. package/src/compiler/phases/1-parse/acorn.js +58 -31
  5. package/src/compiler/phases/1-parse/index.js +0 -10
  6. package/src/compiler/phases/1-parse/read/context.js +28 -34
  7. package/src/compiler/phases/1-parse/read/expression.js +4 -38
  8. package/src/compiler/phases/1-parse/read/script.js +1 -8
  9. package/src/compiler/phases/1-parse/state/tag.js +1 -6
  10. package/src/compiler/phases/2-analyze/visitors/ConstTag.js +25 -0
  11. package/src/compiler/phases/2-analyze/visitors/Fragment.js +1 -1
  12. package/src/compiler/phases/2-analyze/visitors/shared/function.js +1 -1
  13. package/src/compiler/phases/3-transform/client/transform-client.js +1 -1
  14. package/src/compiler/phases/3-transform/client/visitors/ConstTag.js +18 -33
  15. package/src/compiler/phases/3-transform/server/visitors/ConstTag.js +13 -12
  16. package/src/internal/client/constants.js +2 -0
  17. package/src/internal/client/dev/hmr.js +6 -3
  18. package/src/internal/client/dom/blocks/boundary.js +21 -5
  19. package/src/internal/client/dom/blocks/branches.js +8 -0
  20. package/src/internal/client/dom/blocks/each.js +5 -0
  21. package/src/internal/client/reactivity/async.js +20 -1
  22. package/src/internal/client/reactivity/batch.js +60 -15
  23. package/src/internal/client/reactivity/deriveds.js +43 -37
  24. package/src/internal/client/reactivity/sources.js +2 -10
  25. package/src/internal/client/runtime.js +12 -4
  26. package/src/internal/client/warnings.js +11 -0
  27. package/src/internal/server/errors.js +12 -0
  28. package/src/internal/server/renderer.js +4 -0
  29. package/src/reactivity/date.js +4 -0
  30. package/src/version.js +1 -1
  31. package/types/index.d.ts.map +1 -1
package/elements.d.ts CHANGED
@@ -952,9 +952,9 @@ export interface HTMLDetailsAttributes extends HTMLAttributes<HTMLDetailsElement
952
952
 
953
953
  'bind:open'?: boolean | undefined | null;
954
954
 
955
- 'on:toggle'?: EventHandler<Event, HTMLDetailsElement> | undefined | null;
956
- ontoggle?: EventHandler<Event, HTMLDetailsElement> | undefined | null;
957
- ontogglecapture?: EventHandler<Event, HTMLDetailsElement> | undefined | null;
955
+ 'on:toggle'?: ToggleEventHandler<HTMLDetailsElement> | undefined | null;
956
+ ontoggle?: ToggleEventHandler<HTMLDetailsElement> | undefined | null;
957
+ ontogglecapture?: ToggleEventHandler<HTMLDetailsElement> | undefined | null;
958
958
  }
959
959
 
960
960
  export interface HTMLDelAttributes extends HTMLAttributes<HTMLModElement> {
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "svelte",
3
3
  "description": "Cybernetically enhanced web apps",
4
4
  "license": "MIT",
5
- "version": "5.55.1",
5
+ "version": "5.55.3",
6
6
  "type": "module",
7
7
  "types": "./types/index.d.ts",
8
8
  "engines": {
@@ -1,10 +1,13 @@
1
1
  /** @import { Comment, Program } from 'estree' */
2
2
  /** @import { AST } from '#compiler' */
3
+ /** @import { Parser } from './index.js' */
3
4
  import * as acorn from 'acorn';
4
5
  import { walk } from 'zimmerframe';
5
6
  import { tsPlugin } from '@sveltejs/acorn-typescript';
7
+ import * as e from '../../errors.js';
6
8
 
7
- const ParserWithTS = acorn.Parser.extend(tsPlugin());
9
+ const JSParser = acorn.Parser;
10
+ const TSParser = JSParser.extend(tsPlugin());
8
11
 
9
12
  /**
10
13
  * @typedef {Comment & {
@@ -20,15 +23,15 @@ const ParserWithTS = acorn.Parser.extend(tsPlugin());
20
23
  * @param {boolean} [is_script]
21
24
  */
22
25
  export function parse(source, comments, typescript, is_script) {
23
- const parser = typescript ? ParserWithTS : acorn.Parser;
26
+ const acorn = typescript ? TSParser : JSParser;
24
27
 
25
28
  const { onComment, add_comments } = get_comment_handlers(
26
29
  source,
27
30
  /** @type {CommentWithLocation[]} */ (comments)
28
31
  );
29
32
 
30
- // @ts-ignore
31
- const parse_statement = parser.prototype.parseStatement;
33
+ // @ts-expect-error
34
+ const parse_statement = acorn.prototype.parseStatement;
32
35
 
33
36
  // If we're dealing with a <script> then it might contain an export
34
37
  // for something that doesn't exist directly inside but is inside the
@@ -36,7 +39,7 @@ export function parse(source, comments, typescript, is_script) {
36
39
  // an error in these cases
37
40
  if (is_script) {
38
41
  // @ts-ignore
39
- parser.prototype.parseStatement = function (...args) {
42
+ acorn.prototype.parseStatement = function (...args) {
40
43
  const v = parse_statement.call(this, ...args);
41
44
  // @ts-ignore
42
45
  this.undefinedExports = {};
@@ -44,53 +47,77 @@ export function parse(source, comments, typescript, is_script) {
44
47
  };
45
48
  }
46
49
 
47
- let ast;
48
-
49
50
  try {
50
- ast = parser.parse(source, {
51
+ const ast = acorn.parse(source, {
51
52
  onComment,
52
53
  sourceType: 'module',
53
54
  ecmaVersion: 16,
54
55
  locations: true
55
56
  });
57
+
58
+ add_comments(ast);
59
+
60
+ return /** @type {Program} */ (ast);
61
+ } catch (err) {
62
+ // TODO the `return` in necessary for TS<7 due to a bug; otherwise
63
+ // the `finally` block is regarded as unreachable
64
+ return handle_parse_error(err);
56
65
  } finally {
57
66
  if (is_script) {
58
- // @ts-ignore
59
- parser.prototype.parseStatement = parse_statement;
67
+ // @ts-expect-error
68
+ acorn.prototype.parseStatement = parse_statement;
60
69
  }
61
70
  }
62
-
63
- add_comments(ast);
64
-
65
- return /** @type {Program} */ (ast);
66
71
  }
67
72
 
68
73
  /**
74
+ * @param {Parser} parser
69
75
  * @param {string} source
70
- * @param {Comment[]} comments
71
- * @param {boolean} typescript
72
76
  * @param {number} index
73
77
  * @returns {acorn.Expression & { leadingComments?: CommentWithLocation[]; trailingComments?: CommentWithLocation[]; }}
74
78
  */
75
- export function parse_expression_at(source, comments, typescript, index) {
76
- const parser = typescript ? ParserWithTS : acorn.Parser;
79
+ export function parse_expression_at(parser, source, index) {
80
+ const acorn = parser.ts ? TSParser : JSParser;
77
81
 
78
- const { onComment, add_comments } = get_comment_handlers(
79
- source,
80
- /** @type {CommentWithLocation[]} */ (comments),
81
- index
82
- );
82
+ const { onComment, add_comments } = get_comment_handlers(source, parser.root.comments, index);
83
83
 
84
- const ast = parser.parseExpressionAt(source, index, {
85
- onComment,
86
- sourceType: 'module',
87
- ecmaVersion: 16,
88
- locations: true
89
- });
84
+ try {
85
+ const ast = acorn.parseExpressionAt(source, index, {
86
+ onComment,
87
+ sourceType: 'module',
88
+ ecmaVersion: 16,
89
+ locations: true,
90
+ preserveParens: true
91
+ });
92
+
93
+ add_comments(ast);
94
+
95
+ return ast;
96
+ } catch (e) {
97
+ handle_parse_error(e);
98
+ }
99
+ }
100
+
101
+ const regex_position_indicator = / \(\d+:\d+\)$/;
90
102
 
91
- add_comments(ast);
103
+ /**
104
+ * @param {any} err
105
+ * @returns {never}
106
+ */
107
+ function handle_parse_error(err) {
108
+ e.js_parse_error(err.pos, err.message.replace(regex_position_indicator, ''));
109
+ }
92
110
 
93
- return ast;
111
+ /**
112
+ * @param {acorn.Expression} node
113
+ * @returns {acorn.Expression}
114
+ */
115
+ export function remove_parens(node) {
116
+ return walk(node, null, {
117
+ ParenthesizedExpression(node, context) {
118
+ return context.visit(node.expression);
119
+ }
120
+ });
94
121
  }
95
122
 
96
123
  /**
@@ -11,8 +11,6 @@ import { is_reserved } from '../../../utils.js';
11
11
  import { disallow_children } from '../2-analyze/visitors/shared/special-element.js';
12
12
  import * as state from '../../state.js';
13
13
 
14
- const regex_position_indicator = / \(\d+:\d+\)$/;
15
-
16
14
  /** @param {number} cc */
17
15
  function is_whitespace(cc) {
18
16
  // fast path for common whitespace
@@ -175,14 +173,6 @@ export class Parser {
175
173
  return this.stack[this.stack.length - 1];
176
174
  }
177
175
 
178
- /**
179
- * @param {any} err
180
- * @returns {never}
181
- */
182
- acorn_error(err) {
183
- e.js_parse_error(err.pos, err.message.replace(regex_position_indicator, ''));
184
- }
185
-
186
176
  /**
187
177
  * @param {string} str
188
178
  * @param {boolean} required
@@ -1,7 +1,7 @@
1
1
  /** @import { Pattern } from 'estree' */
2
2
  /** @import { Parser } from '../index.js' */
3
3
  import { match_bracket } from '../utils/bracket.js';
4
- import { parse_expression_at } from '../acorn.js';
4
+ import { parse_expression_at, remove_parens } from '../acorn.js';
5
5
  import { regex_not_newline_characters } from '../../patterns.js';
6
6
  import * as e from '../../../errors.js';
7
7
 
@@ -35,38 +35,32 @@ export default function read_pattern(parser) {
35
35
 
36
36
  const pattern_string = parser.template.slice(start, i);
37
37
 
38
- try {
39
- // the length of the `space_with_newline` has to be start - 1
40
- // because we added a `(` in front of the pattern_string,
41
- // which shifted the entire string to right by 1
42
- // so we offset it by removing 1 character in the `space_with_newline`
43
- // to achieve that, we remove the 1st space encountered,
44
- // so it will not affect the `column` of the node
45
- let space_with_newline = parser.template
46
- .slice(0, start)
47
- .replace(regex_not_newline_characters, ' ');
48
- const first_space = space_with_newline.indexOf(' ');
49
- space_with_newline =
50
- space_with_newline.slice(0, first_space) + space_with_newline.slice(first_space + 1);
51
-
52
- const expression = /** @type {any} */ (
53
- parse_expression_at(
54
- `${space_with_newline}(${pattern_string} = 1)`,
55
- parser.root.comments,
56
- parser.ts,
57
- start - 1
58
- )
59
- ).left;
60
-
61
- expression.typeAnnotation = read_type_annotation(parser);
62
- if (expression.typeAnnotation) {
63
- expression.end = expression.typeAnnotation.end;
64
- }
65
-
66
- return expression;
67
- } catch (error) {
68
- parser.acorn_error(error);
38
+ // the length of the `space_with_newline` has to be start - 1
39
+ // because we added a `(` in front of the pattern_string,
40
+ // which shifted the entire string to right by 1
41
+ // so we offset it by removing 1 character in the `space_with_newline`
42
+ // to achieve that, we remove the 1st space encountered,
43
+ // so it will not affect the `column` of the node
44
+ let space_with_newline = parser.template
45
+ .slice(0, start)
46
+ .replace(regex_not_newline_characters, ' ');
47
+ const first_space = space_with_newline.indexOf(' ');
48
+ space_with_newline =
49
+ space_with_newline.slice(0, first_space) + space_with_newline.slice(first_space + 1);
50
+
51
+ /** @type {any} */
52
+ let expression = remove_parens(
53
+ parse_expression_at(parser, `${space_with_newline}(${pattern_string} = 1)`, start - 1)
54
+ );
55
+
56
+ expression = expression.left;
57
+
58
+ expression.typeAnnotation = read_type_annotation(parser);
59
+ if (expression.typeAnnotation) {
60
+ expression.end = expression.typeAnnotation.end;
69
61
  }
62
+
63
+ return expression;
70
64
  }
71
65
 
72
66
  /**
@@ -92,13 +86,13 @@ function read_type_annotation(parser) {
92
86
  // parameters as part of a sequence expression instead, and will then error on optional
93
87
  // parameters (`?:`). Therefore replace that sequence with something that will not error.
94
88
  parser.template.slice(parser.index).replace(/\?\s*:/g, ':');
95
- let expression = parse_expression_at(template, parser.root.comments, parser.ts, a);
89
+ let expression = remove_parens(parse_expression_at(parser, template, a));
96
90
 
97
91
  // `foo: bar = baz` gets mangled — fix it
98
92
  if (expression.type === 'AssignmentExpression') {
99
93
  let b = expression.right.start;
100
94
  while (template[b] !== '=') b -= 1;
101
- expression = parse_expression_at(template.slice(0, b), parser.root.comments, parser.ts, a);
95
+ expression = remove_parens(parse_expression_at(parser, template.slice(0, b), a));
102
96
  }
103
97
 
104
98
  // `array as item: string, index` becomes `string, index`, which is mistaken as a sequence expression - fix that
@@ -1,6 +1,6 @@
1
1
  /** @import { Expression } from 'estree' */
2
2
  /** @import { Parser } from '../index.js' */
3
- import { parse_expression_at } from '../acorn.js';
3
+ import { parse_expression_at, remove_parens } from '../acorn.js';
4
4
  import { regex_whitespace } from '../../patterns.js';
5
5
  import * as e from '../../../errors.js';
6
6
  import { find_matching_bracket } from '../utils/bracket.js';
@@ -34,50 +34,16 @@ export function get_loose_identifier(parser, opening_token) {
34
34
  */
35
35
  export default function read_expression(parser, opening_token, disallow_loose) {
36
36
  try {
37
- let comment_index = parser.root.comments.length;
38
-
39
- const node = parse_expression_at(
40
- parser.template,
41
- parser.root.comments,
42
- parser.ts,
43
- parser.index
44
- );
45
-
46
- let num_parens = 0;
47
-
48
- let i = parser.root.comments.length;
49
- while (i-- > comment_index) {
50
- const comment = parser.root.comments[i];
51
- if (comment.end < node.start) {
52
- parser.index = comment.end;
53
- break;
54
- }
55
- }
56
-
57
- for (let i = parser.index; i < /** @type {number} */ (node.start); i += 1) {
58
- if (parser.template[i] === '(') num_parens += 1;
59
- }
37
+ const node = parse_expression_at(parser, parser.template, parser.index);
60
38
 
61
39
  let index = /** @type {number} */ (node.end);
62
40
 
63
41
  const last_comment = parser.root.comments.at(-1);
64
42
  if (last_comment && last_comment.end > index) index = last_comment.end;
65
43
 
66
- while (num_parens > 0) {
67
- const char = parser.template[index];
68
-
69
- if (char === ')') {
70
- num_parens -= 1;
71
- } else if (!regex_whitespace.test(char)) {
72
- e.expected_token(index, ')');
73
- }
74
-
75
- index += 1;
76
- }
77
-
78
44
  parser.index = index;
79
45
 
80
- return /** @type {Expression} */ (node);
46
+ return /** @type {Expression} */ (remove_parens(node));
81
47
  } catch (err) {
82
48
  // If we are in an each loop we need the error to be thrown in cases like
83
49
  // `as { y = z }` so we still throw and handle the error there
@@ -88,6 +54,6 @@ export default function read_expression(parser, opening_token, disallow_loose) {
88
54
  }
89
55
  }
90
56
 
91
- parser.acorn_error(err);
57
+ throw err;
92
58
  }
93
59
  }
@@ -31,14 +31,7 @@ export function read_script(parser, start, attributes) {
31
31
  parser.template.slice(0, script_start).replace(regex_not_newline_characters, ' ') + data;
32
32
  parser.read(regex_starts_with_closing_script_tag);
33
33
 
34
- /** @type {Program} */
35
- let ast;
36
-
37
- try {
38
- ast = acorn.parse(source, parser.root.comments, parser.ts, true);
39
- } catch (err) {
40
- parser.acorn_error(err);
41
- }
34
+ const ast = acorn.parse(source, parser.root.comments, parser.ts, true);
42
35
 
43
36
  ast.start = script_start;
44
37
 
@@ -392,12 +392,7 @@ function open(parser) {
392
392
 
393
393
  let function_expression = matched
394
394
  ? /** @type {ArrowFunctionExpression} */ (
395
- parse_expression_at(
396
- prelude + `${params} => {}`,
397
- parser.root.comments,
398
- parser.ts,
399
- params_start
400
- )
395
+ parse_expression_at(parser, prelude + `${params} => {}`, params_start)
401
396
  )
402
397
  : { params: [] };
403
398
 
@@ -1,6 +1,7 @@
1
1
  /** @import { AST } from '#compiler' */
2
2
  /** @import { Context } from '../types' */
3
3
  import * as e from '../../../errors.js';
4
+ import * as b from '#compiler/builders';
4
5
  import { validate_opening_tag } from './shared/utils.js';
5
6
 
6
7
  /**
@@ -42,4 +43,28 @@ export function ConstTag(node, context) {
42
43
  function_depth: context.state.function_depth + 1,
43
44
  derived_function_depth: context.state.function_depth + 1
44
45
  });
46
+
47
+ const has_await = node.metadata.expression.has_await;
48
+ const blockers = [...node.metadata.expression.dependencies]
49
+ .map((dep) => dep.blocker)
50
+ .filter((b) => b !== null && b.object !== context.state.async_consts?.id);
51
+
52
+ if (has_await || context.state.async_consts || blockers.length > 0) {
53
+ const run = (context.state.async_consts ??= {
54
+ id: context.state.analysis.root.unique('promises'),
55
+ declaration_count: 0
56
+ });
57
+ node.metadata.promises_id = run.id;
58
+
59
+ const bindings = context.state.scope.get_bindings(declaration);
60
+
61
+ // keep the counter in sync with the number of thunks pushed in ConstTag in transform
62
+ // TODO 6.0 once non-async and non-runes mode is gone investigate making this more robust
63
+ // via something like the approach in https://github.com/sveltejs/svelte/pull/18032
64
+ const length = run.declaration_count++;
65
+ const blocker = b.member(run.id, b.literal(length), true);
66
+ for (const binding of bindings) {
67
+ binding.blocker = blocker;
68
+ }
69
+ }
45
70
  }
@@ -6,5 +6,5 @@
6
6
  * @param {Context} context
7
7
  */
8
8
  export function Fragment(node, context) {
9
- context.next({ ...context.state, fragment: node });
9
+ context.next({ ...context.state, fragment: node, async_consts: undefined });
10
10
  }
@@ -10,7 +10,7 @@ export function visit_function(node, context) {
10
10
  for (const [name] of context.state.scope.references) {
11
11
  const binding = context.state.scope.get(name);
12
12
 
13
- if (binding && binding.scope.function_depth < context.state.scope.function_depth) {
13
+ if (binding && binding.scope !== context.state.scope) {
14
14
  context.state.expression.references.add(binding);
15
15
  }
16
16
  }
@@ -352,7 +352,7 @@ export function client_component(analysis, options) {
352
352
  )
353
353
  );
354
354
  } else if (dev) {
355
- component_returned_object.push(b.spread(b.call(b.id('$.legacy_api'))));
355
+ component_returned_object.unshift(b.spread(b.call(b.id('$.legacy_api'))));
356
356
  }
357
357
 
358
358
  const push_args = [b.id('$$props'), b.literal(analysis.runes)];
@@ -1,7 +1,6 @@
1
- /** @import { Expression, Identifier, Pattern } from 'estree' */
1
+ /** @import { Expression, Identifier, Pattern, Statement } from 'estree' */
2
2
  /** @import { AST } from '#compiler' */
3
3
  /** @import { ComponentContext } from '../types' */
4
- /** @import { ExpressionMetadata } from '../../../nodes.js' */
5
4
  import { dev } from '../../../../state.js';
6
5
  import { extract_identifiers } from '../../../../utils/ast.js';
7
6
  import * as b from '#compiler/builders';
@@ -27,13 +26,7 @@ export function ConstTag(node, context) {
27
26
 
28
27
  context.state.transform[declaration.id.name] = { read: get_value };
29
28
 
30
- add_const_declaration(
31
- context.state,
32
- declaration.id,
33
- expression,
34
- node.metadata.expression,
35
- context.state.scope.get_bindings(declaration)
36
- );
29
+ add_const_declaration(context.state, declaration.id, expression, node.metadata);
37
30
  } else {
38
31
  const identifiers = extract_identifiers(declaration.id);
39
32
  const tmp = b.id(context.state.scope.generate('computed_const'));
@@ -70,13 +63,7 @@ export function ConstTag(node, context) {
70
63
  expression = b.call('$.tag', expression, b.literal('[@const]'));
71
64
  }
72
65
 
73
- add_const_declaration(
74
- context.state,
75
- tmp,
76
- expression,
77
- node.metadata.expression,
78
- context.state.scope.get_bindings(declaration)
79
- );
66
+ add_const_declaration(context.state, tmp, expression, node.metadata);
80
67
 
81
68
  for (const node of identifiers) {
82
69
  context.state.transform[node.name] = {
@@ -90,42 +77,40 @@ export function ConstTag(node, context) {
90
77
  * @param {ComponentContext['state']} state
91
78
  * @param {Identifier} id
92
79
  * @param {Expression} expression
93
- * @param {ExpressionMetadata} metadata
94
- * @param {import('#compiler').Binding[]} bindings
80
+ * @param {AST.ConstTag['metadata']} metadata
95
81
  */
96
- function add_const_declaration(state, id, expression, metadata, bindings) {
82
+ function add_const_declaration(state, id, expression, metadata) {
97
83
  // we need to eagerly evaluate the expression in order to hit any
98
84
  // 'Cannot access x before initialization' errors
99
85
  const after = dev ? [b.stmt(b.call('$.get', id))] : [];
100
86
 
101
- const has_await = metadata.has_await;
102
- const blockers = [...metadata.dependencies]
87
+ const blockers = [...metadata.expression.dependencies]
103
88
  .map((dep) => dep.blocker)
104
89
  .filter((b) => b !== null && b.object !== state.async_consts?.id);
105
90
 
106
- if (has_await || state.async_consts || blockers.length > 0) {
91
+ if (metadata.promises_id) {
107
92
  const run = (state.async_consts ??= {
108
- id: b.id(state.scope.generate('promises')),
93
+ id: metadata.promises_id,
109
94
  thunks: []
110
95
  });
111
96
 
112
97
  state.consts.push(b.let(id));
113
98
 
114
- const assignment = b.assignment('=', id, expression);
115
- const body = after.length === 0 ? assignment : b.block([b.stmt(assignment), ...after]);
99
+ /** @type {Statement | undefined} */
100
+ let promise_stmt;
116
101
 
117
102
  if (blockers.length === 1) {
118
- run.thunks.push(b.thunk(b.member(/** @type {Expression} */ (blockers[0]), 'promise')));
103
+ promise_stmt = b.stmt(b.await(b.member(/** @type {Expression} */ (blockers[0]), 'promise')));
119
104
  } else if (blockers.length > 0) {
120
- run.thunks.push(b.thunk(b.call('$.wait', b.array(blockers))));
105
+ promise_stmt = b.stmt(b.await(b.call('$.wait', b.array(blockers))));
121
106
  }
122
107
 
123
- run.thunks.push(b.thunk(body, has_await));
124
-
125
- const blocker = b.member(run.id, b.literal(run.thunks.length - 1), true);
126
-
127
- for (const binding of bindings) {
128
- binding.blocker = blocker;
108
+ // keep the number of thunks pushed in sync with ConstTag in analysis phase
109
+ const assignment = b.assignment('=', id, expression);
110
+ if (promise_stmt) {
111
+ run.thunks.push(b.thunk(b.block([promise_stmt, b.stmt(assignment)]), true));
112
+ } else {
113
+ run.thunks.push(b.thunk(assignment, metadata.expression.has_await));
129
114
  }
130
115
  } else {
131
116
  state.consts.push(b.const(id, expression));
@@ -1,4 +1,4 @@
1
- /** @import { Expression, Pattern } from 'estree' */
1
+ /** @import { Expression, Pattern, Statement } from 'estree' */
2
2
  /** @import { AST } from '#compiler' */
3
3
  /** @import { ComponentContext } from '../types.js' */
4
4
  import * as b from '#compiler/builders';
@@ -12,36 +12,37 @@ export function ConstTag(node, context) {
12
12
  const declaration = node.declaration.declarations[0];
13
13
  const id = /** @type {Pattern} */ (context.visit(declaration.id));
14
14
  const init = /** @type {Expression} */ (context.visit(declaration.init));
15
- const has_await = node.metadata.expression.has_await;
16
15
  const blockers = [...node.metadata.expression.dependencies]
17
16
  .map((dep) => dep.blocker)
18
17
  .filter((b) => b !== null && b.object !== context.state.async_consts?.id);
19
18
 
20
- if (has_await || context.state.async_consts || blockers.length > 0) {
19
+ if (node.metadata.promises_id) {
21
20
  const run = (context.state.async_consts ??= {
22
- id: b.id(context.state.scope.generate('promises')),
21
+ id: node.metadata.promises_id,
23
22
  thunks: []
24
23
  });
25
24
 
26
25
  const identifiers = extract_identifiers(declaration.id);
27
- const bindings = context.state.scope.get_bindings(declaration);
28
26
 
29
27
  for (const identifier of identifiers) {
30
28
  context.state.init.push(b.let(identifier.name));
31
29
  }
32
30
 
31
+ /** @type {Statement | undefined} */
32
+ let promise_stmt;
33
+
33
34
  if (blockers.length === 1) {
34
- run.thunks.push(b.thunk(/** @type {Expression} */ (blockers[0])));
35
+ promise_stmt = b.stmt(b.await(/** @type {Expression} */ (blockers[0])));
35
36
  } else if (blockers.length > 0) {
36
- run.thunks.push(b.thunk(b.call('Promise.all', b.array(blockers))));
37
+ promise_stmt = b.stmt(b.await(b.call('Promise.all', b.array(blockers))));
37
38
  }
38
39
 
40
+ // keep the number of thunks pushed in sync with ConstTag in analysis phase
39
41
  const assignment = b.assignment('=', id, init);
40
- run.thunks.push(b.thunk(b.block([b.stmt(assignment)]), has_await));
41
-
42
- const blocker = b.member(run.id, b.literal(run.thunks.length - 1), true);
43
- for (const binding of bindings) {
44
- binding.blocker = blocker;
42
+ if (promise_stmt) {
43
+ run.thunks.push(b.thunk(b.block([promise_stmt, b.stmt(assignment)]), true));
44
+ } else {
45
+ run.thunks.push(b.thunk(assignment, node.metadata.expression.has_await));
45
46
  }
46
47
  } else {
47
48
  context.state.init.push(b.const(id, init));
@@ -62,6 +62,8 @@ export const STATE_SYMBOL = Symbol('$state');
62
62
  export const LEGACY_PROPS = Symbol('legacy props');
63
63
  export const LOADING_ATTR_SYMBOL = Symbol('');
64
64
  export const PROXY_PATH_SYMBOL = Symbol('proxy path');
65
+ /** An anchor might change, via this symbol on the original anchor we can tell HMR about the updated anchor */
66
+ export const HMR_ANCHOR = Symbol('hmr anchor');
65
67
 
66
68
  /** allow users to ignore aborted signal errors if `reason.name === 'StaleReactionError` */
67
69
  export const STALE_REACTION = new (class StaleReactionError extends Error {
@@ -1,6 +1,6 @@
1
1
  /** @import { Effect, TemplateNode } from '#client' */
2
2
  import { FILENAME, HMR } from '../../../constants.js';
3
- import { EFFECT_TRANSPARENT } from '#client/constants';
3
+ import { EFFECT_TRANSPARENT, HMR_ANCHOR } from '#client/constants';
4
4
  import { hydrate_node, hydrating } from '../dom/hydration.js';
5
5
  import { block, branch, destroy_effect } from '../reactivity/effects.js';
6
6
  import { set, source } from '../reactivity/sources.js';
@@ -15,10 +15,10 @@ export function hmr(fn) {
15
15
  const current = source(fn);
16
16
 
17
17
  /**
18
- * @param {TemplateNode} anchor
18
+ * @param {TemplateNode} initial_anchor
19
19
  * @param {any} props
20
20
  */
21
- function wrapper(anchor, props) {
21
+ function wrapper(initial_anchor, props) {
22
22
  let component = {};
23
23
  let instance = {};
24
24
 
@@ -26,6 +26,7 @@ export function hmr(fn) {
26
26
  let effect;
27
27
 
28
28
  let ran = false;
29
+ let anchor = initial_anchor;
29
30
 
30
31
  block(() => {
31
32
  if (component === (component = get(current))) {
@@ -39,6 +40,8 @@ export function hmr(fn) {
39
40
  }
40
41
 
41
42
  effect = branch(() => {
43
+ anchor = /** @type {any} */ (anchor)[HMR_ANCHOR] ?? anchor;
44
+
42
45
  // when the component is invalidated, replace it without transitions
43
46
  if (ran) set_should_intro(false);
44
47