svelte 5.56.0 → 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 +1 -1
- package/src/compiler/phases/1-parse/acorn.js +16 -29
- package/src/compiler/phases/1-parse/state/tag.js +21 -8
- package/src/compiler/phases/1-parse/utils/bracket.js +14 -3
- package/src/compiler/phases/2-analyze/visitors/DeclarationTag.js +15 -2
- package/src/compiler/phases/2-analyze/visitors/SnippetBlock.js +15 -10
- package/src/compiler/phases/3-transform/client/visitors/DeclarationTag.js +3 -1
- package/src/compiler/phases/3-transform/server/visitors/shared/utils.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/reactivity/async.js +23 -10
- package/src/internal/client/reactivity/effects.js +4 -2
- package/src/reactivity/url-search-params.js +3 -2
- package/src/version.js +1 -1
package/package.json
CHANGED
|
@@ -5,7 +5,6 @@ import * as acorn from 'acorn';
|
|
|
5
5
|
import { walk } from 'zimmerframe';
|
|
6
6
|
import { tsPlugin } from '@sveltejs/acorn-typescript';
|
|
7
7
|
import * as e from '../../errors.js';
|
|
8
|
-
import { find_matching_bracket } from './utils/bracket.js';
|
|
9
8
|
|
|
10
9
|
const JSParser = acorn.Parser;
|
|
11
10
|
const TSParser = JSParser.extend(tsPlugin());
|
|
@@ -106,38 +105,26 @@ export function parse_expression_at(parser, source, index) {
|
|
|
106
105
|
* @returns {Statement}
|
|
107
106
|
*/
|
|
108
107
|
export function parse_statement_at(parser, source, index) {
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
while (source[end - 1] === ';') {
|
|
114
|
-
end -= 1;
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
const padded_source = `${' '.repeat(index)}${source.slice(index, end)}`;
|
|
118
|
-
const { onComment, add_comments } = get_comment_handlers(
|
|
119
|
-
padded_source,
|
|
120
|
-
parser.root.comments,
|
|
121
|
-
index
|
|
122
|
-
);
|
|
108
|
+
// cast to `any`: acorn's Parser constructor and parseStatement/nextToken aren't in its public types
|
|
109
|
+
const acorn = /** @type {any} */ (parser.ts ? TSParser : JSParser);
|
|
110
|
+
const { onComment, add_comments } = get_comment_handlers(source, parser.root.comments, index);
|
|
123
111
|
|
|
124
112
|
try {
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
sourceType: 'module',
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
});
|
|
131
|
-
|
|
132
|
-
add_comments(ast);
|
|
133
|
-
|
|
134
|
-
const statement = /** @type {Statement} */ (
|
|
135
|
-
/** @type {unknown} */ (/** @type {Program} */ (ast).body[0])
|
|
113
|
+
// This is like parseExpressionAt but for statements
|
|
114
|
+
const p = new acorn(
|
|
115
|
+
{ onComment, sourceType: 'module', ecmaVersion: 16, locations: true },
|
|
116
|
+
source,
|
|
117
|
+
index
|
|
136
118
|
);
|
|
137
|
-
|
|
119
|
+
p.nextToken();
|
|
120
|
+
const statement = /** @type {Statement} */ (p.parseStatement(null, true, Object.create(null)));
|
|
121
|
+
add_comments(/** @type {acorn.Node} */ (statement));
|
|
138
122
|
return statement;
|
|
139
|
-
} catch (
|
|
140
|
-
|
|
123
|
+
} catch (err) {
|
|
124
|
+
// A statement that runs to the end of the source (e.g. an unterminated declaration tag)
|
|
125
|
+
// is an EOF, not a stray token; preserve the friendlier `unexpected_eof` diagnostic.
|
|
126
|
+
if (/** @type {any} */ (err).pos === source.length) e.unexpected_eof(source.length);
|
|
127
|
+
handle_parse_error(err);
|
|
141
128
|
}
|
|
142
129
|
}
|
|
143
130
|
|
|
@@ -12,9 +12,9 @@ import { find_matching_bracket, match_bracket } from '../utils/bracket.js';
|
|
|
12
12
|
|
|
13
13
|
const regex_whitespace_with_closing_curly_brace = /\s*}/y;
|
|
14
14
|
const regex_supported_declaration = /(?:let|const)\b/y;
|
|
15
|
-
|
|
16
|
-
//
|
|
17
|
-
const
|
|
15
|
+
const regex_unsupported_declaration = /(?:var|interface|enum)\b/y;
|
|
16
|
+
// `type` is a contextual keyword; this is just a shape hint, confirmed by parsing.
|
|
17
|
+
const regex_maybe_type_declaration = /type\b/y;
|
|
18
18
|
|
|
19
19
|
const pointy_bois = { '<': '>' };
|
|
20
20
|
|
|
@@ -77,10 +77,17 @@ function read_declaration(parser) {
|
|
|
77
77
|
e.declaration_tag_invalid_type({ start, end: start + unsupported.length });
|
|
78
78
|
}
|
|
79
79
|
|
|
80
|
-
if (
|
|
80
|
+
if (
|
|
81
|
+
!parser.match_regex(regex_supported_declaration) &&
|
|
82
|
+
// `type` is special, since it is not a reserved keyword and can be used
|
|
83
|
+
// as part of a valid expression. We gotta parse first and then see what it is.
|
|
84
|
+
!parser.match_regex(regex_maybe_type_declaration)
|
|
85
|
+
) {
|
|
81
86
|
return null;
|
|
82
87
|
}
|
|
83
88
|
|
|
89
|
+
const initial_comment_count = parser.root.comments.length;
|
|
90
|
+
|
|
84
91
|
/** @type {import('estree').Statement | import('estree').VariableDeclaration} */
|
|
85
92
|
let declaration;
|
|
86
93
|
try {
|
|
@@ -117,10 +124,16 @@ function read_declaration(parser) {
|
|
|
117
124
|
}
|
|
118
125
|
|
|
119
126
|
if (declaration.type !== 'VariableDeclaration') {
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
}
|
|
127
|
+
if (declaration.type === 'ExpressionStatement') {
|
|
128
|
+
parser.root.comments.length = initial_comment_count; // Else they show up duplicated
|
|
129
|
+
return null;
|
|
130
|
+
} else {
|
|
131
|
+
// This is a TSTypeAliasDeclaration
|
|
132
|
+
e.declaration_tag_invalid_type({
|
|
133
|
+
start: declaration.start ?? start,
|
|
134
|
+
end: declaration.end ?? parser.index
|
|
135
|
+
});
|
|
136
|
+
}
|
|
124
137
|
}
|
|
125
138
|
|
|
126
139
|
// TODO support using
|
|
@@ -39,7 +39,9 @@ function find_string_end(string, search_start_index, string_start_char) {
|
|
|
39
39
|
* @returns {number} The index of the end of this regex expression, or `Infinity` if not found.
|
|
40
40
|
*/
|
|
41
41
|
function find_regex_end(string, search_start_index) {
|
|
42
|
-
|
|
42
|
+
const slash = find_unescaped_char(string, search_start_index, '/');
|
|
43
|
+
const eol = find_unescaped_char(string, search_start_index, '\n');
|
|
44
|
+
return slash < eol ? slash : Infinity;
|
|
43
45
|
}
|
|
44
46
|
|
|
45
47
|
/**
|
|
@@ -105,7 +107,11 @@ export function find_matching_bracket(template, index, open) {
|
|
|
105
107
|
continue;
|
|
106
108
|
case '/': {
|
|
107
109
|
const next_char = template[i + 1];
|
|
108
|
-
if (!next_char)
|
|
110
|
+
if (!next_char) {
|
|
111
|
+
// `/` is the last character; advance past it so we don't loop forever
|
|
112
|
+
i++;
|
|
113
|
+
continue;
|
|
114
|
+
}
|
|
109
115
|
if (next_char === '/') {
|
|
110
116
|
i = infinity_if_negative(template.indexOf('\n', i + 1)) + '\n'.length;
|
|
111
117
|
continue;
|
|
@@ -114,7 +120,12 @@ export function find_matching_bracket(template, index, open) {
|
|
|
114
120
|
i = infinity_if_negative(template.indexOf('*/', i + 1)) + '*/'.length;
|
|
115
121
|
continue;
|
|
116
122
|
}
|
|
117
|
-
|
|
123
|
+
const end = find_regex_end(template, i + 1) + '/'.length;
|
|
124
|
+
if (end === Infinity) {
|
|
125
|
+
i++;
|
|
126
|
+
} else {
|
|
127
|
+
i = end;
|
|
128
|
+
}
|
|
118
129
|
continue;
|
|
119
130
|
}
|
|
120
131
|
default: {
|
|
@@ -2,21 +2,34 @@
|
|
|
2
2
|
/** @import { Context } from '../types' */
|
|
3
3
|
import * as b from '#compiler/builders';
|
|
4
4
|
import * as e from '../../../errors.js';
|
|
5
|
-
import {
|
|
5
|
+
import { extract_identifiers } from '../../../utils/ast.js';
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
8
|
* @param {AST.DeclarationTag} node
|
|
9
9
|
* @param {Context} context
|
|
10
10
|
*/
|
|
11
11
|
export function DeclarationTag(node, context) {
|
|
12
|
-
validate_opening_tag(node, context.state, node.declaration.kind[0]);
|
|
13
12
|
if (!context.state.analysis.runes && !context.state.analysis.maybe_runes) {
|
|
14
13
|
e.declaration_tag_no_legacy_mode(node);
|
|
15
14
|
}
|
|
16
15
|
|
|
16
|
+
const is_top_level = context.path.length === 1 && context.path[0].type === 'Fragment';
|
|
17
|
+
if (is_top_level) {
|
|
18
|
+
const duplicate = node.declaration.declarations
|
|
19
|
+
.flatMap((declaration) => extract_identifiers(declaration.id))
|
|
20
|
+
.find((id) => context.state.analysis.instance.scope.declarations.has(id.name));
|
|
21
|
+
if (duplicate) {
|
|
22
|
+
e.declaration_duplicate(duplicate, duplicate.name);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
17
26
|
context.visit(node.declaration, {
|
|
18
27
|
...context.state,
|
|
19
28
|
in_declaration_tag: true,
|
|
29
|
+
// the declaration lives in the fragment scope, which is one level deeper than the
|
|
30
|
+
// `function_depth` we're tracking here (`set_scope` doesn't update `function_depth`).
|
|
31
|
+
// align them so that `state_referenced_locally` warnings are calculated correctly
|
|
32
|
+
function_depth: context.state.scope.function_depth,
|
|
20
33
|
expression: node.metadata.expression
|
|
21
34
|
});
|
|
22
35
|
|
|
@@ -25,19 +25,24 @@ export function SnippetBlock(node, context) {
|
|
|
25
25
|
|
|
26
26
|
context.next({ ...context.state, parent_element: null });
|
|
27
27
|
|
|
28
|
-
const
|
|
29
|
-
context.path.length === 1 &&
|
|
30
|
-
context.path[0].type === 'Fragment' &&
|
|
31
|
-
can_hoist_snippet(context.state.scope, context.state.scopes);
|
|
28
|
+
const is_top_level = context.path.length === 1 && context.path[0].type === 'Fragment';
|
|
32
29
|
|
|
33
|
-
|
|
30
|
+
if (is_top_level) {
|
|
31
|
+
const name = node.expression.name;
|
|
34
32
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
33
|
+
if (context.state.analysis.instance.scope.declarations.has(name)) {
|
|
34
|
+
e.declaration_duplicate(node.expression, name);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
node.metadata.can_hoist =
|
|
38
|
+
is_top_level && can_hoist_snippet(context.state.scope, context.state.scopes);
|
|
39
39
|
|
|
40
|
-
|
|
40
|
+
if (node.metadata.can_hoist) {
|
|
41
|
+
const name = node.expression.name;
|
|
42
|
+
const binding = /** @type {Binding} */ (context.state.scope.get(name));
|
|
43
|
+
context.state.analysis.module.scope.declarations.set(name, binding);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
41
46
|
|
|
42
47
|
const { path } = context;
|
|
43
48
|
const parent = path.at(-2);
|
|
@@ -10,8 +10,10 @@ import { add_state_transformers } from './shared/declarations.js';
|
|
|
10
10
|
* @param {ComponentContext} context
|
|
11
11
|
*/
|
|
12
12
|
export function DeclarationTag(node, context) {
|
|
13
|
-
|
|
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)}`)
|
|
14
15
|
add_state_transformers(context);
|
|
16
|
+
const declaration = /** @type {Statement | undefined} */ (context.visit(node.declaration));
|
|
15
17
|
|
|
16
18
|
if (
|
|
17
19
|
node.metadata.promises_id &&
|
|
@@ -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
|
}
|
|
@@ -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) {
|
|
@@ -332,6 +332,15 @@ function set_attributes(
|
|
|
332
332
|
|
|
333
333
|
var setters = get_setters(element);
|
|
334
334
|
|
|
335
|
+
if (element.nodeName === INPUT_TAG && 'type' in next && ('value' in next || '__value' in next)) {
|
|
336
|
+
var type = next.type;
|
|
337
|
+
|
|
338
|
+
if (type !== current.type || (type === undefined && element.hasAttribute('type'))) {
|
|
339
|
+
current.type = type;
|
|
340
|
+
set_attribute(element, 'type', type, skip_warning);
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
|
|
335
344
|
// since key is captured we use const
|
|
336
345
|
for (const key in next) {
|
|
337
346
|
// let instead of var because referenced in a closure
|
|
@@ -7,7 +7,6 @@ import { is } from '../../../proxy.js';
|
|
|
7
7
|
import { queue_micro_task } from '../../task.js';
|
|
8
8
|
import { hydrating } from '../../hydration.js';
|
|
9
9
|
import { tick, untrack } from '../../../runtime.js';
|
|
10
|
-
import { is_runes } from '../../../context.js';
|
|
11
10
|
import { current_batch, previous_batch } from '../../../reactivity/batch.js';
|
|
12
11
|
import { async_mode_flag } from '../../../../flags/index.js';
|
|
13
12
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/** @import { Blocker, Effect, Value } from '#client' */
|
|
1
|
+
/** @import { Blocker, Effect, Source, Value } from '#client' */
|
|
2
2
|
import { DESTROYED, STALE_REACTION } from '#client/constants';
|
|
3
3
|
import { DEV } from 'esm-env';
|
|
4
4
|
import {
|
|
@@ -38,8 +38,22 @@ export function flatten(blockers, sync, async, fn) {
|
|
|
38
38
|
// Filter out already-settled blockers - no need to wait for them
|
|
39
39
|
var pending = blockers.filter((b) => !b.settled);
|
|
40
40
|
|
|
41
|
+
var deriveds = sync.map(d);
|
|
42
|
+
|
|
43
|
+
if (DEV) {
|
|
44
|
+
deriveds.forEach((d, i) => {
|
|
45
|
+
// TODO this is kinda useful for debugging but a lousy implementation —
|
|
46
|
+
// maybe the compiler could pass through the template string
|
|
47
|
+
d.label = sync[i]
|
|
48
|
+
.toString()
|
|
49
|
+
.replace('() => ', '')
|
|
50
|
+
.replaceAll('$.eager(() => ', '$state.eager(')
|
|
51
|
+
.replace(/\$\.get\((.+?)\)/g, (_, id) => id);
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
|
|
41
55
|
if (async.length === 0 && pending.length === 0) {
|
|
42
|
-
fn(
|
|
56
|
+
fn(deriveds);
|
|
43
57
|
return;
|
|
44
58
|
}
|
|
45
59
|
|
|
@@ -53,8 +67,10 @@ export function flatten(blockers, sync, async, fn) {
|
|
|
53
67
|
? Promise.all(pending.map((b) => b.promise))
|
|
54
68
|
: null;
|
|
55
69
|
|
|
56
|
-
/**
|
|
57
|
-
|
|
70
|
+
/**
|
|
71
|
+
* @param {Source[]} async
|
|
72
|
+
*/
|
|
73
|
+
function finish(async) {
|
|
58
74
|
if ((parent.f & DESTROYED) !== 0) {
|
|
59
75
|
return;
|
|
60
76
|
}
|
|
@@ -62,7 +78,7 @@ export function flatten(blockers, sync, async, fn) {
|
|
|
62
78
|
restore();
|
|
63
79
|
|
|
64
80
|
try {
|
|
65
|
-
fn(
|
|
81
|
+
fn([...deriveds, ...async]);
|
|
66
82
|
} catch (error) {
|
|
67
83
|
invoke_error_boundary(error, parent);
|
|
68
84
|
}
|
|
@@ -74,17 +90,14 @@ export function flatten(blockers, sync, async, fn) {
|
|
|
74
90
|
|
|
75
91
|
// Fast path: blockers but no async expressions
|
|
76
92
|
if (async.length === 0) {
|
|
77
|
-
/** @type {Promise<any>} */ (blocker_promise)
|
|
78
|
-
.then(() => finish(sync.map(d)))
|
|
79
|
-
.finally(decrement_pending);
|
|
80
|
-
|
|
93
|
+
/** @type {Promise<any>} */ (blocker_promise).then(() => finish([])).finally(decrement_pending);
|
|
81
94
|
return;
|
|
82
95
|
}
|
|
83
96
|
|
|
84
97
|
// Full path: has async expressions
|
|
85
98
|
function run() {
|
|
86
99
|
Promise.all(async.map((expression) => async_derived(expression)))
|
|
87
|
-
.then(
|
|
100
|
+
.then(finish)
|
|
88
101
|
.catch((error) => invoke_error_boundary(error, parent))
|
|
89
102
|
.finally(decrement_pending);
|
|
90
103
|
}
|
|
@@ -387,7 +387,9 @@ export function render_effect(fn, flags = 0) {
|
|
|
387
387
|
*/
|
|
388
388
|
export function template_effect(fn, sync = [], async = [], blockers = []) {
|
|
389
389
|
flatten(blockers, sync, async, (values) => {
|
|
390
|
-
create_effect(RENDER_EFFECT, () =>
|
|
390
|
+
create_effect(RENDER_EFFECT, () => {
|
|
391
|
+
fn(...values.map(get));
|
|
392
|
+
});
|
|
391
393
|
});
|
|
392
394
|
}
|
|
393
395
|
|
|
@@ -518,7 +520,7 @@ export function destroy_effect(effect, remove_dom = true) {
|
|
|
518
520
|
removed = true;
|
|
519
521
|
}
|
|
520
522
|
|
|
521
|
-
|
|
523
|
+
effect.f |= DESTROYING;
|
|
522
524
|
destroy_effect_children(effect, remove_dom && !removed);
|
|
523
525
|
remove_reactions(effect, 0);
|
|
524
526
|
|
|
@@ -132,11 +132,12 @@ export class SvelteURLSearchParams extends URLSearchParams {
|
|
|
132
132
|
* @returns {void}
|
|
133
133
|
*/
|
|
134
134
|
set(name, value) {
|
|
135
|
-
var previous = super.getAll(name)
|
|
135
|
+
var previous = super.getAll(name);
|
|
136
136
|
super.set(name, value);
|
|
137
137
|
// can't use has(name, value), because for something like https://svelte.dev?foo=1&bar=2&foo=3
|
|
138
138
|
// if you set `foo` to 1, then foo=3 gets deleted whilst `has("foo", "1")` returns true
|
|
139
|
-
|
|
139
|
+
var current = super.getAll(name);
|
|
140
|
+
if (previous.length !== current.length || previous.some((value, i) => value !== current[i])) {
|
|
140
141
|
this.#update_url();
|
|
141
142
|
increment(this.#version);
|
|
142
143
|
}
|
package/src/version.js
CHANGED