svelte 5.49.0 → 5.49.2

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/elements.d.ts CHANGED
@@ -851,23 +851,6 @@ export interface HTMLAttributes<T extends EventTarget> extends AriaAttributes, D
851
851
  readonly 'bind:offsetWidth'?: number | undefined | null;
852
852
  readonly 'bind:offsetHeight'?: number | undefined | null;
853
853
 
854
- // SvelteKit
855
- 'data-sveltekit-keepfocus'?: true | '' | 'off' | undefined | null;
856
- 'data-sveltekit-noscroll'?: true | '' | 'off' | undefined | null;
857
- 'data-sveltekit-preload-code'?:
858
- | true
859
- | ''
860
- | 'eager'
861
- | 'viewport'
862
- | 'hover'
863
- | 'tap'
864
- | 'off'
865
- | undefined
866
- | null;
867
- 'data-sveltekit-preload-data'?: true | '' | 'hover' | 'tap' | 'off' | undefined | null;
868
- 'data-sveltekit-reload'?: true | '' | 'off' | undefined | null;
869
- 'data-sveltekit-replacestate'?: true | '' | 'off' | undefined | null;
870
-
871
854
  // allow any data- attribute
872
855
  [key: `data-${string}`]: any;
873
856
 
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.49.0",
5
+ "version": "5.49.2",
6
6
  "type": "module",
7
7
  "types": "./types/index.d.ts",
8
8
  "engines": {
@@ -139,7 +139,7 @@
139
139
  ],
140
140
  "devDependencies": {
141
141
  "@jridgewell/trace-mapping": "^0.3.25",
142
- "@playwright/test": "^1.46.1",
142
+ "@playwright/test": "^1.58.0",
143
143
  "@rollup/plugin-commonjs": "^28.0.1",
144
144
  "@rollup/plugin-node-resolve": "^15.3.0",
145
145
  "@rollup/plugin-terser": "^0.4.4",
@@ -165,7 +165,7 @@
165
165
  "clsx": "^2.1.1",
166
166
  "devalue": "^5.6.2",
167
167
  "esm-env": "^1.2.1",
168
- "esrap": "^2.2.1",
168
+ "esrap": "^2.2.2",
169
169
  "is-reference": "^3.0.3",
170
170
  "locate-character": "^3.0.0",
171
171
  "magic-string": "^0.30.11",
@@ -312,10 +312,10 @@ export function EachBlock(node, context) {
312
312
  declarations.push(b.let(node.index, index));
313
313
  }
314
314
 
315
- const is_async = node.metadata.expression.is_async();
315
+ const has_await = node.metadata.expression.has_await;
316
316
 
317
- const get_collection = b.thunk(collection, node.metadata.expression.has_await);
318
- const thunk = is_async ? b.thunk(b.call('$.get', b.id('$$collection'))) : get_collection;
317
+ const get_collection = b.thunk(collection, has_await);
318
+ const thunk = has_await ? b.thunk(b.call('$.get', b.id('$$collection'))) : get_collection;
319
319
 
320
320
  const render_args = [b.id('$$anchor'), item];
321
321
  if (uses_index || collection_id) render_args.push(index);
@@ -342,15 +342,18 @@ export function EachBlock(node, context) {
342
342
  statements.unshift(b.stmt(b.call('$.validate_each_keys', thunk, key_function)));
343
343
  }
344
344
 
345
- if (is_async) {
345
+ if (node.metadata.expression.is_async()) {
346
346
  context.state.init.push(
347
347
  b.stmt(
348
348
  b.call(
349
349
  '$.async',
350
350
  context.state.node,
351
351
  node.metadata.expression.blockers(),
352
- b.array([get_collection]),
353
- b.arrow([context.state.node, b.id('$$collection')], b.block(statements))
352
+ has_await ? b.array([get_collection]) : b.void0,
353
+ b.arrow(
354
+ has_await ? [context.state.node, b.id('$$collection')] : [context.state.node],
355
+ b.block(statements)
356
+ )
354
357
  )
355
358
  )
356
359
  );
@@ -11,10 +11,11 @@ import { build_expression } from './shared/utils.js';
11
11
  export function HtmlTag(node, context) {
12
12
  context.state.template.push_comment();
13
13
 
14
- const is_async = node.metadata.expression.is_async();
14
+ const has_await = node.metadata.expression.has_await;
15
+ const has_blockers = node.metadata.expression.has_blockers();
15
16
 
16
17
  const expression = build_expression(context, node.expression, node.metadata.expression);
17
- const html = is_async ? b.call('$.get', b.id('$$html')) : expression;
18
+ const html = has_await ? b.call('$.get', b.id('$$html')) : expression;
18
19
 
19
20
  const is_svg = context.state.metadata.namespace === 'svg';
20
21
  const is_mathml = context.state.metadata.namespace === 'mathml';
@@ -31,15 +32,18 @@ export function HtmlTag(node, context) {
31
32
  );
32
33
 
33
34
  // push into init, so that bindings run afterwards, which might trigger another run and override hydration
34
- if (is_async) {
35
+ if (has_await || has_blockers) {
35
36
  context.state.init.push(
36
37
  b.stmt(
37
38
  b.call(
38
39
  '$.async',
39
40
  context.state.node,
40
41
  node.metadata.expression.blockers(),
41
- b.array([b.thunk(expression, node.metadata.expression.has_await)]),
42
- b.arrow([context.state.node, b.id('$$html')], b.block([statement]))
42
+ has_await ? b.array([b.thunk(expression, true)]) : b.void0,
43
+ b.arrow(
44
+ has_await ? [context.state.node, b.id('$$html')] : [context.state.node],
45
+ b.block([statement])
46
+ )
43
47
  )
44
48
  )
45
49
  );
@@ -25,10 +25,11 @@ export function IfBlock(node, context) {
25
25
  statements.push(b.var(alternate_id, b.arrow([b.id('$$anchor')], alternate)));
26
26
  }
27
27
 
28
- const is_async = node.metadata.expression.is_async();
28
+ const has_await = node.metadata.expression.has_await;
29
+ const has_blockers = node.metadata.expression.has_blockers();
29
30
 
30
31
  const expression = build_expression(context, node.test, node.metadata.expression);
31
- const test = is_async ? b.call('$.get', b.id('$$condition')) : expression;
32
+ const test = has_await ? b.call('$.get', b.id('$$condition')) : expression;
32
33
 
33
34
  /** @type {Expression[]} */
34
35
  const args = [
@@ -72,15 +73,18 @@ export function IfBlock(node, context) {
72
73
 
73
74
  statements.push(add_svelte_meta(b.call('$.if', ...args), node, 'if'));
74
75
 
75
- if (is_async) {
76
+ if (has_await || has_blockers) {
76
77
  context.state.init.push(
77
78
  b.stmt(
78
79
  b.call(
79
80
  '$.async',
80
81
  context.state.node,
81
82
  node.metadata.expression.blockers(),
82
- b.array([b.thunk(expression, node.metadata.expression.has_await)]),
83
- b.arrow([context.state.node, b.id('$$condition')], b.block(statements))
83
+ has_await ? b.array([b.thunk(expression, true)]) : b.void0,
84
+ b.arrow(
85
+ has_await ? [context.state.node, b.id('$$condition')] : [context.state.node],
86
+ b.block(statements)
87
+ )
84
88
  )
85
89
  )
86
90
  );
@@ -11,29 +11,35 @@ import { build_expression, add_svelte_meta } from './shared/utils.js';
11
11
  export function KeyBlock(node, context) {
12
12
  context.state.template.push_comment();
13
13
 
14
- const is_async = node.metadata.expression.is_async();
14
+ const has_await = node.metadata.expression.has_await;
15
+ const has_blockers = node.metadata.expression.has_blockers();
15
16
 
16
17
  const expression = build_expression(context, node.expression, node.metadata.expression);
17
- const key = b.thunk(is_async ? b.call('$.get', b.id('$$key')) : expression);
18
+ const key = b.thunk(has_await ? b.call('$.get', b.id('$$key')) : expression);
18
19
  const body = /** @type {Expression} */ (context.visit(node.fragment));
19
20
 
20
- let statement = add_svelte_meta(
21
+ const statement = add_svelte_meta(
21
22
  b.call('$.key', context.state.node, key, b.arrow([b.id('$$anchor')], body)),
22
23
  node,
23
24
  'key'
24
25
  );
25
26
 
26
- if (is_async) {
27
- statement = b.stmt(
28
- b.call(
29
- '$.async',
30
- context.state.node,
31
- node.metadata.expression.blockers(),
32
- b.array([b.thunk(expression, node.metadata.expression.has_await)]),
33
- b.arrow([context.state.node, b.id('$$key')], b.block([statement]))
27
+ if (has_await || has_blockers) {
28
+ context.state.init.push(
29
+ b.stmt(
30
+ b.call(
31
+ '$.async',
32
+ context.state.node,
33
+ node.metadata.expression.blockers(),
34
+ has_await ? b.array([b.thunk(expression, true)]) : b.void0,
35
+ b.arrow(
36
+ has_await ? [context.state.node, b.id('$$key')] : [context.state.node],
37
+ b.block([statement])
38
+ )
39
+ )
34
40
  )
35
41
  );
42
+ } else {
43
+ context.state.init.push(statement);
36
44
  }
37
-
38
- context.state.init.push(statement);
39
45
  }
@@ -93,10 +93,11 @@ export function SvelteElement(node, context) {
93
93
  );
94
94
  }
95
95
 
96
- const is_async = node.metadata.expression.is_async();
96
+ const has_await = node.metadata.expression.has_await;
97
+ const has_blockers = node.metadata.expression.has_blockers();
97
98
 
98
99
  const expression = /** @type {Expression} */ (context.visit(node.tag));
99
- const get_tag = b.thunk(is_async ? b.call('$.get', b.id('$$tag')) : expression);
100
+ const get_tag = b.thunk(has_await ? b.call('$.get', b.id('$$tag')) : expression);
100
101
 
101
102
  /** @type {Statement[]} */
102
103
  const inner = inner_context.state.init;
@@ -139,15 +140,18 @@ export function SvelteElement(node, context) {
139
140
  )
140
141
  );
141
142
 
142
- if (is_async) {
143
+ if (has_await || has_blockers) {
143
144
  context.state.init.push(
144
145
  b.stmt(
145
146
  b.call(
146
147
  '$.async',
147
148
  context.state.node,
148
149
  node.metadata.expression.blockers(),
149
- b.array([b.thunk(expression, node.metadata.expression.has_await)]),
150
- b.arrow([context.state.node, b.id('$$tag')], b.block(statements))
150
+ has_await ? b.array([b.thunk(expression, true)]) : b.void0,
151
+ b.arrow(
152
+ has_await ? [context.state.node, b.id('$$tag')] : [context.state.node],
153
+ b.block(statements)
154
+ )
151
155
  )
152
156
  )
153
157
  );
@@ -70,8 +70,7 @@ export function RegularElement(node, context) {
70
70
  if (optimiser.expressions.length > 0) {
71
71
  context.state.template.push(
72
72
  create_child_block(
73
- b.block([optimiser.apply(), ...state.init, ...build_template(state.template)]),
74
- true
73
+ b.block([optimiser.apply(), ...state.init, ...build_template(state.template)])
75
74
  )
76
75
  );
77
76
  } else {
@@ -133,7 +132,7 @@ export function RegularElement(node, context) {
133
132
 
134
133
  if (optimiser.expressions.length > 0) {
135
134
  context.state.template.push(
136
- create_child_block(b.block([optimiser.apply(), ...state.init, statement]), true)
135
+ create_child_block(b.block([optimiser.apply(), ...state.init, statement]))
137
136
  );
138
137
  } else {
139
138
  context.state.template.push(...state.init, statement);
@@ -186,7 +185,7 @@ export function RegularElement(node, context) {
186
185
 
187
186
  if (optimiser.expressions.length > 0) {
188
187
  context.state.template.push(
189
- create_child_block(b.block([optimiser.apply(), ...state.init, statement]), true)
188
+ create_child_block(b.block([optimiser.apply(), ...state.init, statement]))
190
189
  );
191
190
  } else {
192
191
  context.state.template.push(...state.init, statement);
@@ -236,18 +235,19 @@ export function RegularElement(node, context) {
236
235
  }
237
236
 
238
237
  if (optimiser.is_async()) {
239
- let statement = create_child_block(
240
- b.block([optimiser.apply(), ...state.init, ...build_template(state.template)]),
241
- true
242
- );
238
+ let statements = [...state.init, ...build_template(state.template)];
239
+
240
+ if (optimiser.has_await) {
241
+ statements = [create_child_block(b.block([optimiser.apply(), ...statements]))];
242
+ }
243
243
 
244
244
  const blockers = optimiser.blockers();
245
245
 
246
246
  if (blockers.elements.length > 0) {
247
- statement = create_async_block(b.block([statement]), blockers, false, false);
247
+ statements = [create_async_block(b.block(statements), blockers, false, false)];
248
248
  }
249
249
 
250
- context.state.template.push(statement);
250
+ context.state.template.push(...statements);
251
251
  } else {
252
252
  context.state.init.push(...state.init);
253
253
  context.state.template.push(...state.template);
@@ -79,7 +79,7 @@ export function SvelteElement(node, context) {
79
79
  );
80
80
 
81
81
  if (optimiser.expressions.length > 0) {
82
- statement = create_child_block(b.block([optimiser.apply(), statement]), true);
82
+ statement = create_child_block(b.block([optimiser.apply(), statement]));
83
83
  }
84
84
 
85
85
  statements.push(statement);
@@ -264,11 +264,10 @@ export function build_getter(node, state) {
264
264
  /**
265
265
  * Creates a `$$renderer.child(...)` expression statement
266
266
  * @param {BlockStatement | Expression} body
267
- * @param {boolean} async
268
267
  * @returns {Statement}
269
268
  */
270
- export function create_child_block(body, async) {
271
- return b.stmt(b.call('$$renderer.child', b.arrow([b.id('$$renderer')], body, async)));
269
+ export function create_child_block(body) {
270
+ return b.stmt(b.call('$$renderer.child', b.arrow([b.id('$$renderer')], body, true)));
272
271
  }
273
272
 
274
273
  /**
@@ -115,57 +115,17 @@ function base_element(node, context) {
115
115
  const is_doctype_node = node.name.toLowerCase() === '!doctype';
116
116
  const is_self_closing =
117
117
  is_void(node.name) || (node.type === 'Component' && node.fragment.nodes.length === 0);
118
- let multiline_content = false;
119
118
 
120
119
  if (is_doctype_node) child_context.write(`>`);
121
120
  else if (is_self_closing) {
122
121
  child_context.write(`${multiline_attributes ? '' : ' '}/>`);
123
122
  } else {
124
123
  child_context.write('>');
125
-
126
- // Process the element's content in a separate context for measurement
127
- const content_context = child_context.new();
128
- const allow_inline_content = child_context.measure() < LINE_BREAK_THRESHOLD;
129
- block(content_context, node.fragment, allow_inline_content);
130
-
131
- // Determine if content should be formatted on multiple lines
132
- multiline_content = content_context.measure() > LINE_BREAK_THRESHOLD;
133
-
134
- if (multiline_content) {
135
- child_context.newline();
136
-
137
- // Only indent if attributes are inline and content itself isn't already multiline
138
- const should_indent = !multiline_attributes && !content_context.multiline;
139
- if (should_indent) {
140
- child_context.indent();
141
- }
142
-
143
- child_context.append(content_context);
144
-
145
- if (should_indent) {
146
- child_context.dedent();
147
- }
148
-
149
- child_context.newline();
150
- } else {
151
- child_context.append(content_context);
152
- }
153
-
124
+ block(child_context, node.fragment, true);
154
125
  child_context.write(`</${node.name}>`);
155
126
  }
156
127
 
157
- const break_line_after = child_context.measure() > LINE_BREAK_THRESHOLD;
158
-
159
- if ((multiline_content || multiline_attributes) && !context.empty()) {
160
- context.newline();
161
- }
162
-
163
128
  context.append(child_context);
164
-
165
- if (is_self_closing) return;
166
- if (multiline_content || multiline_attributes || break_line_after) {
167
- context.newline();
168
- }
169
129
  }
170
130
 
171
131
  /** @type {Visitors<AST.SvelteNode>} */
@@ -412,6 +372,8 @@ const svelte_visitors = {
412
372
  }
413
373
  } else {
414
374
  sequence.push(child_node);
375
+
376
+ if (child_node.type === 'RegularElement') flush();
415
377
  }
416
378
  }
417
379
 
@@ -420,18 +382,20 @@ const svelte_visitors = {
420
382
  let multiline = false;
421
383
  let width = 0;
422
384
 
423
- const child_contexts = items.map((sequence) => {
424
- const child_context = context.new();
385
+ const child_contexts = items
386
+ .filter((x) => x.length > 0)
387
+ .map((sequence) => {
388
+ const child_context = context.new();
425
389
 
426
- for (const node of sequence) {
427
- child_context.visit(node);
428
- multiline ||= child_context.multiline;
429
- }
390
+ for (const node of sequence) {
391
+ child_context.visit(node);
392
+ multiline ||= child_context.multiline;
393
+ }
430
394
 
431
- width += child_context.measure();
395
+ width += child_context.measure();
432
396
 
433
- return child_context;
434
- });
397
+ return child_context;
398
+ });
435
399
 
436
400
  multiline ||= width > LINE_BREAK_THRESHOLD;
437
401
 
@@ -200,17 +200,17 @@ export class BranchManager {
200
200
  if (defer) {
201
201
  for (const [k, effect] of this.#onscreen) {
202
202
  if (k === key) {
203
- batch.skipped_effects.delete(effect);
203
+ batch.unskip_effect(effect);
204
204
  } else {
205
- batch.skipped_effects.add(effect);
205
+ batch.skip_effect(effect);
206
206
  }
207
207
  }
208
208
 
209
209
  for (const [k, branch] of this.#offscreen) {
210
210
  if (k === key) {
211
- batch.skipped_effects.delete(branch.effect);
211
+ batch.unskip_effect(branch.effect);
212
212
  } else {
213
- batch.skipped_effects.add(branch.effect);
213
+ batch.skip_effect(branch.effect);
214
214
  }
215
215
  }
216
216
 
@@ -257,7 +257,7 @@ export function each(node, flags, get_collection, get_key, render_fn, fallback_f
257
257
  if (item.i) internal_set(item.i, index);
258
258
 
259
259
  if (defer) {
260
- batch.skipped_effects.delete(item.e);
260
+ batch.unskip_effect(item.e);
261
261
  }
262
262
  } else {
263
263
  item = create_item(
@@ -299,7 +299,7 @@ export function each(node, flags, get_collection, get_key, render_fn, fallback_f
299
299
  if (defer) {
300
300
  for (const [key, item] of items) {
301
301
  if (!keys.has(key)) {
302
- batch.skipped_effects.add(item.e);
302
+ batch.skip_effect(item.e);
303
303
  }
304
304
  }
305
305
 
@@ -122,6 +122,10 @@ export function child(node, is_text) {
122
122
  return text;
123
123
  }
124
124
 
125
+ if (is_text) {
126
+ merge_text_nodes(/** @type {Text} */ (child));
127
+ }
128
+
125
129
  set_hydrate_node(child);
126
130
  return child;
127
131
  }
@@ -142,14 +146,18 @@ export function first_child(node, is_text = false) {
142
146
  return first;
143
147
  }
144
148
 
145
- // if an {expression} is empty during SSR, there might be no
146
- // text node to hydrate we must therefore create one
147
- if (is_text && hydrate_node?.nodeType !== TEXT_NODE) {
148
- var text = create_text();
149
+ if (is_text) {
150
+ // if an {expression} is empty during SSR, there might be no
151
+ // text node to hydrate we must therefore create one
152
+ if (hydrate_node?.nodeType !== TEXT_NODE) {
153
+ var text = create_text();
149
154
 
150
- hydrate_node?.before(text);
151
- set_hydrate_node(text);
152
- return text;
155
+ hydrate_node?.before(text);
156
+ set_hydrate_node(text);
157
+ return text;
158
+ }
159
+
160
+ merge_text_nodes(/** @type {Text} */ (hydrate_node));
153
161
  }
154
162
 
155
163
  return hydrate_node;
@@ -175,20 +183,24 @@ export function sibling(node, count = 1, is_text = false) {
175
183
  return next_sibling;
176
184
  }
177
185
 
178
- // if a sibling {expression} is empty during SSR, there might be no
179
- // text node to hydrate we must therefore create one
180
- if (is_text && next_sibling?.nodeType !== TEXT_NODE) {
181
- var text = create_text();
182
- // If the next sibling is `null` and we're handling text then it's because
183
- // the SSR content was empty for the text, so we need to generate a new text
184
- // node and insert it after the last sibling
185
- if (next_sibling === null) {
186
- last_sibling?.after(text);
187
- } else {
188
- next_sibling.before(text);
186
+ if (is_text) {
187
+ // if a sibling {expression} is empty during SSR, there might be no
188
+ // text node to hydrate we must therefore create one
189
+ if (next_sibling?.nodeType !== TEXT_NODE) {
190
+ var text = create_text();
191
+ // If the next sibling is `null` and we're handling text then it's because
192
+ // the SSR content was empty for the text, so we need to generate a new text
193
+ // node and insert it after the last sibling
194
+ if (next_sibling === null) {
195
+ last_sibling?.after(text);
196
+ } else {
197
+ next_sibling.before(text);
198
+ }
199
+ set_hydrate_node(text);
200
+ return text;
189
201
  }
190
- set_hydrate_node(text);
191
- return text;
202
+
203
+ merge_text_nodes(/** @type {Text} */ (next_sibling));
192
204
  }
193
205
 
194
206
  set_hydrate_node(next_sibling);
@@ -258,3 +270,24 @@ export function set_attribute(element, key, value = '') {
258
270
  }
259
271
  return element.setAttribute(key, value);
260
272
  }
273
+
274
+ /**
275
+ * Browsers split text nodes larger than 65536 bytes when parsing.
276
+ * For hydration to succeed, we need to stitch them back together
277
+ * @param {Text} text
278
+ */
279
+ export function merge_text_nodes(text) {
280
+ if (/** @type {string} */ (text.nodeValue).length < 65536) {
281
+ return;
282
+ }
283
+
284
+ let next = text.nextSibling;
285
+
286
+ while (next !== null && next.nodeType === TEXT_NODE) {
287
+ next.remove();
288
+
289
+ /** @type {string} */ (text.nodeValue) += /** @type {string} */ (next.nodeValue);
290
+
291
+ next = text.nextSibling;
292
+ }
293
+ }
@@ -4,11 +4,13 @@ import { hydrate_next, hydrate_node, hydrating, set_hydrate_node } from './hydra
4
4
  import {
5
5
  create_text,
6
6
  get_first_child,
7
+ get_next_sibling,
7
8
  is_firefox,
8
9
  create_element,
9
10
  create_fragment,
10
11
  create_comment,
11
- set_attribute
12
+ set_attribute,
13
+ merge_text_nodes
12
14
  } from './operations.js';
13
15
  import { create_fragment_from_html } from './reconciler.js';
14
16
  import { active_effect } from '../runtime.js';
@@ -310,6 +312,8 @@ export function text(value = '') {
310
312
  // if an {expression} is empty during SSR, we need to insert an empty text node
311
313
  node.before((node = create_text()));
312
314
  set_hydrate_node(node);
315
+ } else {
316
+ merge_text_nodes(/** @type {Text} */ (node));
313
317
  }
314
318
 
315
319
  assign_nodes(node, node);