svelte 5.56.10 → 5.57.0
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/elements.d.ts +3 -0
- package/package.json +3 -3
- package/src/compiler/phases/1-parse/acorn.js +1 -1
- package/src/compiler/phases/1-parse/utils/html.js +12 -7
- package/src/compiler/phases/2-analyze/css/css-prune.js +10 -0
- package/src/compiler/phases/2-analyze/index.js +26 -3
- package/src/compiler/phases/2-analyze/visitors/AwaitExpression.js +10 -4
- package/src/compiler/phases/2-analyze/visitors/CallExpression.js +1 -1
- package/src/compiler/phases/2-analyze/visitors/DeclarationTag.js +2 -1
- package/src/compiler/phases/2-analyze/visitors/ExportNamedDeclaration.js +10 -10
- package/src/compiler/phases/2-analyze/visitors/SvelteBody.js +4 -4
- package/src/compiler/phases/2-analyze/visitors/SvelteDocument.js +4 -4
- package/src/compiler/phases/2-analyze/visitors/SvelteWindow.js +4 -4
- package/src/compiler/phases/2-analyze/visitors/shared/a11y/index.js +14 -2
- package/src/compiler/phases/2-analyze/visitors/shared/element.js +2 -8
- package/src/compiler/phases/2-analyze/visitors/shared/utils.js +23 -1
- package/src/compiler/phases/3-transform/client/transform-client.js +2 -2
- package/src/compiler/phases/3-transform/client/transform-template/index.js +6 -0
- package/src/compiler/phases/3-transform/client/utils.js +35 -8
- package/src/compiler/phases/3-transform/client/visitors/AwaitBlock.js +6 -6
- package/src/compiler/phases/3-transform/client/visitors/BindDirective.js +10 -13
- package/src/compiler/phases/3-transform/client/visitors/ConstTag.js +19 -8
- package/src/compiler/phases/3-transform/client/visitors/DeclarationTag.js +3 -2
- package/src/compiler/phases/3-transform/client/visitors/EachBlock.js +4 -1
- package/src/compiler/phases/3-transform/client/visitors/Fragment.js +2 -7
- package/src/compiler/phases/3-transform/client/visitors/HtmlTag.js +2 -1
- package/src/compiler/phases/3-transform/client/visitors/IfBlock.js +2 -1
- package/src/compiler/phases/3-transform/client/visitors/KeyBlock.js +2 -1
- package/src/compiler/phases/3-transform/client/visitors/RegularElement.js +106 -44
- package/src/compiler/phases/3-transform/client/visitors/SvelteElement.js +2 -1
- package/src/compiler/phases/3-transform/client/visitors/VariableDeclaration.js +11 -4
- package/src/compiler/phases/3-transform/client/visitors/shared/element.js +6 -1
- package/src/compiler/phases/3-transform/client/visitors/shared/fragment.js +1 -6
- package/src/compiler/phases/3-transform/client/visitors/shared/utils.js +5 -5
- package/src/compiler/phases/3-transform/server/transform-server.js +20 -9
- package/src/compiler/phases/3-transform/server/visitors/AssignmentExpression.js +1 -1
- package/src/compiler/phases/3-transform/server/visitors/DeclarationTag.js +1 -1
- package/src/compiler/phases/3-transform/server/visitors/EachBlock.js +8 -2
- package/src/compiler/phases/3-transform/server/visitors/IfBlock.js +4 -4
- package/src/compiler/phases/3-transform/server/visitors/RegularElement.js +1 -1
- package/src/compiler/phases/3-transform/server/visitors/SvelteBoundary.js +9 -4
- package/src/compiler/phases/3-transform/server/visitors/shared/element.js +5 -4
- package/src/compiler/phases/3-transform/server/visitors/shared/utils.js +30 -0
- package/src/compiler/phases/bindings.js +3 -1
- package/src/compiler/phases/nodes.js +4 -0
- package/src/compiler/print/index.js +21 -16
- package/src/easing/index.js +62 -0
- package/src/internal/client/constants.js +9 -0
- package/src/internal/client/context.js +15 -5
- package/src/internal/client/dev/hmr.js +15 -4
- package/src/internal/client/dom/blocks/boundary.js +21 -12
- package/src/internal/client/dom/blocks/slot.js +19 -0
- package/src/internal/client/dom/blocks/svelte-head.js +8 -0
- package/src/internal/client/dom/elements/attributes.js +35 -34
- package/src/internal/client/dom/elements/bindings/select.js +99 -9
- package/src/internal/client/dom/elements/bindings/this.js +7 -2
- package/src/internal/client/dom/elements/transitions.js +81 -68
- package/src/internal/client/dom/operations.js +21 -1
- package/src/internal/client/error-handling.js +10 -2
- package/src/internal/client/index.js +9 -2
- package/src/internal/client/proxy.js +8 -3
- package/src/internal/client/reactivity/async.js +23 -3
- package/src/internal/client/reactivity/effects.js +14 -1
- package/src/internal/client/render.js +4 -8
- package/src/internal/client/runtime.js +45 -33
- package/src/internal/client/warnings.js +0 -11
- package/src/internal/server/abort-signal.js +8 -9
- package/src/internal/server/context.js +2 -2
- package/src/internal/server/index.js +1 -1
- package/src/internal/server/renderer.js +163 -52
- package/src/internal/shared/attributes.js +2 -1
- package/src/internal/shared/clone.js +2 -0
- package/src/internal/shared/context.js +3 -2
- package/src/legacy/legacy-server.js +1 -1
- package/src/reactivity/map.js +22 -0
- package/src/reactivity/url.js +2 -0
- package/src/transition/index.js +18 -7
- package/src/version.js +1 -1
- package/types/index.d.ts +135 -37
- package/types/index.d.ts.map +2 -2
package/elements.d.ts
CHANGED
|
@@ -1352,6 +1352,9 @@ export interface HTMLSelectAttributes extends HTMLAttributes<HTMLSelectElement>
|
|
|
1352
1352
|
required?: boolean | undefined | null;
|
|
1353
1353
|
size?: number | undefined | null;
|
|
1354
1354
|
value?: any;
|
|
1355
|
+
// needs both casing variants because language tools does lowercase names of non-shorthand attributes
|
|
1356
|
+
defaultValue?: any;
|
|
1357
|
+
defaultvalue?: any;
|
|
1355
1358
|
|
|
1356
1359
|
'on:change'?: ChangeEventHandler<HTMLSelectElement> | undefined | null;
|
|
1357
1360
|
onchange?: ChangeEventHandler<HTMLSelectElement> | undefined | null;
|
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.
|
|
5
|
+
"version": "5.57.0",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"types": "./types/index.d.ts",
|
|
8
8
|
"engines": {
|
|
@@ -139,13 +139,14 @@
|
|
|
139
139
|
],
|
|
140
140
|
"devDependencies": {
|
|
141
141
|
"@jridgewell/trace-mapping": "^0.3.25",
|
|
142
|
-
"@playwright/test": "^1.
|
|
142
|
+
"@playwright/test": "^1.62.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",
|
|
146
146
|
"@rollup/plugin-virtual": "^3.0.2",
|
|
147
147
|
"@types/aria-query": "^5.0.4",
|
|
148
148
|
"@types/node": "^20.11.5",
|
|
149
|
+
"@types/trusted-types": "^2.0.7",
|
|
149
150
|
"baseline-browser-mapping": "^2.10.32",
|
|
150
151
|
"dts-buddy": "^0.5.5",
|
|
151
152
|
"esbuild": "^0.28.1",
|
|
@@ -161,7 +162,6 @@
|
|
|
161
162
|
"@jridgewell/sourcemap-codec": "^1.5.0",
|
|
162
163
|
"@sveltejs/acorn-typescript": "^1.0.10",
|
|
163
164
|
"@types/estree": "^1.0.5",
|
|
164
|
-
"@types/trusted-types": "^2.0.7",
|
|
165
165
|
"acorn": "^8.12.1",
|
|
166
166
|
"aria-query": "5.3.1",
|
|
167
167
|
"axobject-query": "^4.1.0",
|
|
@@ -59,7 +59,7 @@ export function parse(source, comments, typescript, is_script) {
|
|
|
59
59
|
|
|
60
60
|
return /** @type {Program} */ (ast);
|
|
61
61
|
} catch (err) {
|
|
62
|
-
// TODO the `return`
|
|
62
|
+
// TODO the `return` is necessary for TS<7 due to a bug; otherwise
|
|
63
63
|
// the `finally` block is regarded as unreachable
|
|
64
64
|
return handle_parse_error(err);
|
|
65
65
|
} finally {
|
|
@@ -20,7 +20,7 @@ function reg_exp_entity(entity_name, is_attribute_value) {
|
|
|
20
20
|
|
|
21
21
|
/** @param {boolean} is_attribute_value */
|
|
22
22
|
function get_entity_pattern(is_attribute_value) {
|
|
23
|
-
const reg_exp_num = '#(?:
|
|
23
|
+
const reg_exp_num = '#(?:[xX][a-fA-F\\d]+|\\d+)(?:;)?';
|
|
24
24
|
const reg_exp_entities = Object.keys(entities).map(
|
|
25
25
|
/** @param {any} entity_name */ (entity_name) => reg_exp_entity(entity_name, is_attribute_value)
|
|
26
26
|
);
|
|
@@ -50,7 +50,7 @@ export function decode_character_references(html, is_attribute_value) {
|
|
|
50
50
|
// Handle named entities
|
|
51
51
|
if (entity[0] !== '#') {
|
|
52
52
|
code = entities[entity];
|
|
53
|
-
} else if (entity[1] === 'x') {
|
|
53
|
+
} else if (entity[1] === 'x' || entity[1] === 'X') {
|
|
54
54
|
code = parseInt(entity.substring(2), 16);
|
|
55
55
|
} else {
|
|
56
56
|
code = parseInt(entity.substring(1), 10);
|
|
@@ -60,7 +60,7 @@ export function decode_character_references(html, is_attribute_value) {
|
|
|
60
60
|
return match;
|
|
61
61
|
}
|
|
62
62
|
|
|
63
|
-
return String.fromCodePoint(validate_code(code));
|
|
63
|
+
return String.fromCodePoint(validate_code(code, is_attribute_value));
|
|
64
64
|
}
|
|
65
65
|
);
|
|
66
66
|
}
|
|
@@ -75,10 +75,15 @@ const NUL = 0;
|
|
|
75
75
|
// Also see: https://en.wikipedia.org/wiki/Plane_(Unicode)
|
|
76
76
|
// Also see: https://html.spec.whatwg.org/multipage/parsing.html#preprocessing-the-input-stream
|
|
77
77
|
|
|
78
|
-
/**
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
78
|
+
/**
|
|
79
|
+
* @param {number} code
|
|
80
|
+
* @param {boolean} is_attribute_value
|
|
81
|
+
*/
|
|
82
|
+
function validate_code(code, is_attribute_value) {
|
|
83
|
+
// line feed becomes generic whitespace, since it is collapsed along with the
|
|
84
|
+
// surrounding whitespace anyway. In an attribute value it is significant, so it
|
|
85
|
+
// is left alone there
|
|
86
|
+
if (code === 10 && !is_attribute_value) {
|
|
82
87
|
return 32;
|
|
83
88
|
}
|
|
84
89
|
|
|
@@ -122,6 +122,13 @@ const any_selector = {
|
|
|
122
122
|
*/
|
|
123
123
|
const seen = new Set();
|
|
124
124
|
|
|
125
|
+
/**
|
|
126
|
+
* @param {Compiler.AST.RegularElement | Compiler.AST.SvelteElement | Compiler.AST.RenderTag | Compiler.AST.Component | Compiler.AST.SvelteComponent | Compiler.AST.SvelteSelf} node
|
|
127
|
+
*/
|
|
128
|
+
function is_inside_svelte_head(node) {
|
|
129
|
+
return node.metadata.path.some((ancestor) => ancestor.type === 'SvelteHead');
|
|
130
|
+
}
|
|
131
|
+
|
|
125
132
|
/**
|
|
126
133
|
*
|
|
127
134
|
* @param {Compiler.AST.CSS.StyleSheet} stylesheet
|
|
@@ -143,6 +150,9 @@ export function prune(stylesheet, elements) {
|
|
|
143
150
|
seen.clear();
|
|
144
151
|
|
|
145
152
|
if (
|
|
153
|
+
// Elements rendered through <svelte:head> are not style-scopable.
|
|
154
|
+
// Prevent css hash injection (class="s-...") on tags like <meta>, <link>, <script>.
|
|
155
|
+
!is_inside_svelte_head(element) &&
|
|
146
156
|
apply_selector(
|
|
147
157
|
selectors,
|
|
148
158
|
/** @type {Compiler.AST.CSS.Rule} */ (node.metadata.rule),
|
|
@@ -285,7 +285,7 @@ export function analyze_module(source, options) {
|
|
|
285
285
|
runes: true,
|
|
286
286
|
immutable: true,
|
|
287
287
|
tracing: false,
|
|
288
|
-
async_deriveds: new
|
|
288
|
+
async_deriveds: new Map(),
|
|
289
289
|
comments,
|
|
290
290
|
classes: new Map(),
|
|
291
291
|
pickled_awaits: new Set()
|
|
@@ -557,7 +557,7 @@ export function analyze_component(root, source, options) {
|
|
|
557
557
|
source,
|
|
558
558
|
snippet_renderers: new Map(),
|
|
559
559
|
snippets: new Set(),
|
|
560
|
-
async_deriveds: new
|
|
560
|
+
async_deriveds: new Map(),
|
|
561
561
|
pickled_awaits: new Set(),
|
|
562
562
|
instance_body: {
|
|
563
563
|
sync: [],
|
|
@@ -832,6 +832,11 @@ export function analyze_component(root, source, options) {
|
|
|
832
832
|
} else {
|
|
833
833
|
e.export_undefined(specifier, name);
|
|
834
834
|
}
|
|
835
|
+
} else if (binding.initial?.type === 'SnippetBlock') {
|
|
836
|
+
// If a snippet is exported, a consumer could only import this named export and not the default export (the component).
|
|
837
|
+
// In this case we need to set hasGlobal of our output to true so that e.g. vite-plugin-svelte does not tell Vite to
|
|
838
|
+
// tree-shake the CSS if the default export is not used.
|
|
839
|
+
analysis.css.has_global = true;
|
|
835
840
|
}
|
|
836
841
|
}
|
|
837
842
|
}
|
|
@@ -1228,6 +1233,23 @@ function calculate_blockers(instance, analysis) {
|
|
|
1228
1233
|
|
|
1229
1234
|
flush_sync_group();
|
|
1230
1235
|
|
|
1236
|
+
// a store subscription must wait on whatever blocks the store itself; this must happen
|
|
1237
|
+
// before function tracing so that functions reading `$store` inherit the blocker
|
|
1238
|
+
for (const [name, binding] of instance.scope.declarations) {
|
|
1239
|
+
if (binding.kind !== 'store_sub') continue;
|
|
1240
|
+
|
|
1241
|
+
const store_blocker = instance.scope.get(name.slice(1))?.blocker;
|
|
1242
|
+
if (!store_blocker) continue;
|
|
1243
|
+
|
|
1244
|
+
if (
|
|
1245
|
+
!binding.blocker ||
|
|
1246
|
+
/** @type {ESTree.SimpleLiteral & { value: number }} */ (binding.blocker.property).value <
|
|
1247
|
+
/** @type {ESTree.SimpleLiteral & { value: number }} */ (store_blocker.property).value
|
|
1248
|
+
) {
|
|
1249
|
+
binding.blocker = store_blocker;
|
|
1250
|
+
}
|
|
1251
|
+
}
|
|
1252
|
+
|
|
1231
1253
|
for (const fn of functions) {
|
|
1232
1254
|
/** @type {Set<Binding>} */
|
|
1233
1255
|
const reads_writes = new Set();
|
|
@@ -1318,7 +1340,8 @@ function order_reactive_statements(unsorted_reactive_declarations) {
|
|
|
1318
1340
|
* @returns
|
|
1319
1341
|
*/
|
|
1320
1342
|
const add_declaration = (node, declaration) => {
|
|
1321
|
-
|
|
1343
|
+
// Visited set: each ReactiveStatement is stored under exactly one LabeledStatement node
|
|
1344
|
+
if (reactive_declarations.has(node)) return;
|
|
1322
1345
|
|
|
1323
1346
|
for (const binding of declaration.dependencies) {
|
|
1324
1347
|
if (declaration.assignments.has(binding)) continue;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
/** @import { AwaitExpression, Expression, SpreadElement, Property } from 'estree' */
|
|
2
2
|
/** @import { Context } from '../types' */
|
|
3
3
|
/** @import { AST } from '#compiler' */
|
|
4
|
+
/** @import { ExpressionMetadata } from '../../nodes.js' */
|
|
4
5
|
import * as e from '../../../errors.js';
|
|
5
6
|
|
|
6
7
|
/**
|
|
@@ -10,15 +11,20 @@ import * as e from '../../../errors.js';
|
|
|
10
11
|
export function AwaitExpression(node, context) {
|
|
11
12
|
const tla = context.state.ast_type === 'instance' && context.state.function_depth === 1;
|
|
12
13
|
|
|
13
|
-
// preserve context for awaits that precede other expressions in template or `$derived(...)`
|
|
14
14
|
if (
|
|
15
15
|
is_reactive_expression(
|
|
16
16
|
context.path,
|
|
17
17
|
context.state.derived_function_depth === context.state.function_depth
|
|
18
|
-
)
|
|
19
|
-
!is_last_evaluated_expression(context.path, node)
|
|
18
|
+
)
|
|
20
19
|
) {
|
|
21
|
-
context.state.
|
|
20
|
+
const expression = /** @type {ExpressionMetadata} */ (context.state.expression);
|
|
21
|
+
|
|
22
|
+
// preserve context for awaits that precede other expressions in template or `$derived(...)`,
|
|
23
|
+
// and for any await that follows one, so the restored context ends at the next suspension
|
|
24
|
+
if (expression.has_pickled_await || !is_last_evaluated_expression(context.path, node)) {
|
|
25
|
+
context.state.analysis.pickled_awaits.add(node);
|
|
26
|
+
expression.has_pickled_await = true;
|
|
27
|
+
}
|
|
22
28
|
}
|
|
23
29
|
|
|
24
30
|
let suspend = tla;
|
|
@@ -253,7 +253,7 @@ export function CallExpression(node, context) {
|
|
|
253
253
|
});
|
|
254
254
|
|
|
255
255
|
if (expression.has_await) {
|
|
256
|
-
context.state.analysis.async_deriveds.
|
|
256
|
+
context.state.analysis.async_deriveds.set(node, expression);
|
|
257
257
|
}
|
|
258
258
|
|
|
259
259
|
// Tell surrounding declaration tag about metadata for correct calculation of blockers etc
|
|
@@ -43,7 +43,8 @@ export function DeclarationTag(node, context) {
|
|
|
43
43
|
*/
|
|
44
44
|
export function mark_async_declaration(context, metadata, declarations) {
|
|
45
45
|
const has_await = metadata.expression.has_await;
|
|
46
|
-
|
|
46
|
+
// reads inside closures must block too, like they do in template expressions
|
|
47
|
+
const blockers = [...metadata.expression.references]
|
|
47
48
|
.map((dep) => dep.blocker)
|
|
48
49
|
.filter((b) => b !== null && b.object !== context.state.async_consts?.id);
|
|
49
50
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/** @import { ExportNamedDeclaration, Identifier } from 'estree' */
|
|
1
|
+
/** @import { ExportNamedDeclaration, Identifier, VariableDeclaration } from 'estree' */
|
|
2
2
|
/** @import { Context } from '../types' */
|
|
3
3
|
import * as e from '../../../errors.js';
|
|
4
4
|
import { extract_identifiers } from '../../../utils/ast.js';
|
|
@@ -23,15 +23,6 @@ export function ExportNamedDeclaration(node, context) {
|
|
|
23
23
|
}
|
|
24
24
|
|
|
25
25
|
if (node.declaration?.type === 'VariableDeclaration') {
|
|
26
|
-
// in runes mode, forbid `export let`
|
|
27
|
-
if (
|
|
28
|
-
context.state.analysis.runes &&
|
|
29
|
-
context.state.ast_type === 'instance' &&
|
|
30
|
-
node.declaration.kind === 'let'
|
|
31
|
-
) {
|
|
32
|
-
e.legacy_export_invalid(node);
|
|
33
|
-
}
|
|
34
|
-
|
|
35
26
|
for (const declarator of node.declaration.declarations) {
|
|
36
27
|
for (const id of extract_identifiers(declarator.id)) {
|
|
37
28
|
const binding = context.state.scope.get(id.name);
|
|
@@ -46,6 +37,15 @@ export function ExportNamedDeclaration(node, context) {
|
|
|
46
37
|
}
|
|
47
38
|
}
|
|
48
39
|
}
|
|
40
|
+
|
|
41
|
+
// in runes mode, forbid `export let`
|
|
42
|
+
if (
|
|
43
|
+
context.state.analysis.runes &&
|
|
44
|
+
context.state.ast_type === 'instance' &&
|
|
45
|
+
node.declaration.kind === 'let'
|
|
46
|
+
) {
|
|
47
|
+
e.legacy_export_invalid(node);
|
|
48
|
+
}
|
|
49
49
|
}
|
|
50
50
|
|
|
51
51
|
if (context.state.analysis.runes) {
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
import * as e from '../../../errors.js';
|
|
4
4
|
import { is_event_attribute } from '../../../utils/ast.js';
|
|
5
5
|
import { disallow_children } from './shared/special-element.js';
|
|
6
|
+
import { check_global_event_reference } from './shared/utils.js';
|
|
6
7
|
|
|
7
8
|
/**
|
|
8
9
|
* @param {AST.SvelteBody} node
|
|
@@ -11,10 +12,9 @@ import { disallow_children } from './shared/special-element.js';
|
|
|
11
12
|
export function SvelteBody(node, context) {
|
|
12
13
|
disallow_children(node);
|
|
13
14
|
for (const attribute of node.attributes) {
|
|
14
|
-
if (
|
|
15
|
-
attribute
|
|
16
|
-
|
|
17
|
-
) {
|
|
15
|
+
if (attribute.type === 'Attribute' && is_event_attribute(attribute)) {
|
|
16
|
+
check_global_event_reference(attribute, context);
|
|
17
|
+
} else if (attribute.type === 'SpreadAttribute' || attribute.type === 'Attribute') {
|
|
18
18
|
e.svelte_body_illegal_attribute(attribute);
|
|
19
19
|
}
|
|
20
20
|
}
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
import { disallow_children } from './shared/special-element.js';
|
|
4
4
|
import * as e from '../../../errors.js';
|
|
5
5
|
import { is_event_attribute } from '../../../utils/ast.js';
|
|
6
|
+
import { check_global_event_reference } from './shared/utils.js';
|
|
6
7
|
|
|
7
8
|
/**
|
|
8
9
|
* @param {AST.SvelteDocument} node
|
|
@@ -12,10 +13,9 @@ export function SvelteDocument(node, context) {
|
|
|
12
13
|
disallow_children(node);
|
|
13
14
|
|
|
14
15
|
for (const attribute of node.attributes) {
|
|
15
|
-
if (
|
|
16
|
-
attribute
|
|
17
|
-
|
|
18
|
-
) {
|
|
16
|
+
if (attribute.type === 'Attribute' && is_event_attribute(attribute)) {
|
|
17
|
+
check_global_event_reference(attribute, context);
|
|
18
|
+
} else if (attribute.type === 'SpreadAttribute' || attribute.type === 'Attribute') {
|
|
19
19
|
e.illegal_element_attribute(attribute, 'svelte:document');
|
|
20
20
|
}
|
|
21
21
|
}
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
import { disallow_children } from './shared/special-element.js';
|
|
4
4
|
import * as e from '../../../errors.js';
|
|
5
5
|
import { is_event_attribute } from '../../../utils/ast.js';
|
|
6
|
+
import { check_global_event_reference } from './shared/utils.js';
|
|
6
7
|
|
|
7
8
|
/**
|
|
8
9
|
* @param {AST.SvelteWindow} node
|
|
@@ -12,10 +13,9 @@ export function SvelteWindow(node, context) {
|
|
|
12
13
|
disallow_children(node);
|
|
13
14
|
|
|
14
15
|
for (const attribute of node.attributes) {
|
|
15
|
-
if (
|
|
16
|
-
attribute
|
|
17
|
-
|
|
18
|
-
) {
|
|
16
|
+
if (attribute.type === 'Attribute' && is_event_attribute(attribute)) {
|
|
17
|
+
check_global_event_reference(attribute, context);
|
|
18
|
+
} else if (attribute.type === 'SpreadAttribute' || attribute.type === 'Attribute') {
|
|
19
19
|
e.illegal_element_attribute(attribute, 'svelte:window');
|
|
20
20
|
}
|
|
21
21
|
}
|
|
@@ -373,11 +373,23 @@ export function check_element(node, context) {
|
|
|
373
373
|
}
|
|
374
374
|
}
|
|
375
375
|
|
|
376
|
-
|
|
376
|
+
// `focus`/`blur` don't bubble, so an element that isn't focusable itself can only be notified
|
|
377
|
+
// about focus changes inside it through `focusin`/`focusout` — accept those as well
|
|
378
|
+
if (
|
|
379
|
+
!has_spread &&
|
|
380
|
+
handlers.has('mouseover') &&
|
|
381
|
+
!handlers.has('focus') &&
|
|
382
|
+
!handlers.has('focusin')
|
|
383
|
+
) {
|
|
377
384
|
w.a11y_mouse_events_have_key_events(node, 'mouseover', 'focus');
|
|
378
385
|
}
|
|
379
386
|
|
|
380
|
-
if (
|
|
387
|
+
if (
|
|
388
|
+
!has_spread &&
|
|
389
|
+
handlers.has('mouseout') &&
|
|
390
|
+
!handlers.has('blur') &&
|
|
391
|
+
!handlers.has('focusout')
|
|
392
|
+
) {
|
|
381
393
|
w.a11y_mouse_events_have_key_events(node, 'mouseout', 'blur');
|
|
382
394
|
}
|
|
383
395
|
|
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
validate_attribute_name,
|
|
10
10
|
validate_slot_attribute
|
|
11
11
|
} from './attribute.js';
|
|
12
|
+
import { check_global_event_reference } from './utils.js';
|
|
12
13
|
|
|
13
14
|
const EVENT_MODIFIERS = [
|
|
14
15
|
'preventDefault',
|
|
@@ -64,14 +65,7 @@ export function validate_element(node, context) {
|
|
|
64
65
|
e.attribute_invalid_event_handler(attribute);
|
|
65
66
|
}
|
|
66
67
|
|
|
67
|
-
|
|
68
|
-
if (
|
|
69
|
-
value.type === 'Identifier' &&
|
|
70
|
-
value.name === attribute.name &&
|
|
71
|
-
!context.state.scope.get(value.name)
|
|
72
|
-
) {
|
|
73
|
-
w.attribute_global_event_reference(attribute, attribute.name);
|
|
74
|
-
}
|
|
68
|
+
check_global_event_reference(attribute, context);
|
|
75
69
|
}
|
|
76
70
|
|
|
77
71
|
if (attribute.name === 'slot') {
|
|
@@ -4,7 +4,11 @@
|
|
|
4
4
|
/** @import { Scope } from '../../../scope' */
|
|
5
5
|
/** @import { NodeLike } from '../../../../errors.js' */
|
|
6
6
|
import * as e from '../../../../errors.js';
|
|
7
|
-
import {
|
|
7
|
+
import {
|
|
8
|
+
extract_identifiers,
|
|
9
|
+
get_attribute_expression,
|
|
10
|
+
get_parent
|
|
11
|
+
} from '../../../../utils/ast.js';
|
|
8
12
|
import * as w from '../../../../warnings.js';
|
|
9
13
|
import * as b from '#compiler/builders';
|
|
10
14
|
import { get_rune } from '../../../scope.js';
|
|
@@ -298,3 +302,21 @@ export function validate_export(node, scope, name) {
|
|
|
298
302
|
e.state_invalid_export(node);
|
|
299
303
|
}
|
|
300
304
|
}
|
|
305
|
+
|
|
306
|
+
/**
|
|
307
|
+
* Warns when an event attribute uses the shorthand form (`{onclick}`) but the
|
|
308
|
+
* referenced name isn't declared, so it silently resolves to the global handler.
|
|
309
|
+
* @param {AST.Attribute & { value: [AST.ExpressionTag] | AST.ExpressionTag }} attribute
|
|
310
|
+
* @param {Context} context
|
|
311
|
+
*/
|
|
312
|
+
export function check_global_event_reference(attribute, context) {
|
|
313
|
+
const value = get_attribute_expression(attribute);
|
|
314
|
+
|
|
315
|
+
if (
|
|
316
|
+
value.type === 'Identifier' &&
|
|
317
|
+
value.name === attribute.name &&
|
|
318
|
+
!context.state.scope.get(value.name)
|
|
319
|
+
) {
|
|
320
|
+
w.attribute_global_event_reference(attribute, attribute.name);
|
|
321
|
+
}
|
|
322
|
+
}
|
|
@@ -247,11 +247,11 @@ export function client_component(analysis, options) {
|
|
|
247
247
|
}
|
|
248
248
|
|
|
249
249
|
for (const [node] of analysis.reactive_statements) {
|
|
250
|
-
const statement =
|
|
250
|
+
const statement = state.legacy_reactive_statements.get(node);
|
|
251
251
|
if (statement === undefined) {
|
|
252
252
|
throw new Error('Could not find reactive statement');
|
|
253
253
|
}
|
|
254
|
-
instance.body.push(statement
|
|
254
|
+
instance.body.push(statement);
|
|
255
255
|
}
|
|
256
256
|
|
|
257
257
|
if (analysis.reactive_statements.size > 0) {
|
|
@@ -39,6 +39,12 @@ export function transform_template(state, name, flags = 0) {
|
|
|
39
39
|
const namespace = state.metadata.namespace;
|
|
40
40
|
const tree = state.options.fragments === 'tree';
|
|
41
41
|
|
|
42
|
+
const { nodes } = state.template;
|
|
43
|
+
const is_lone_anchor = nodes.length === 1 && nodes[0].type === 'comment';
|
|
44
|
+
|
|
45
|
+
// special case - `$.comment` creates the anchor more cheaply than cloning a template
|
|
46
|
+
if (is_lone_anchor) return b.id('$.comment');
|
|
47
|
+
|
|
42
48
|
const expression = tree ? state.template.as_tree() : state.template.as_html();
|
|
43
49
|
|
|
44
50
|
const key =
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
/** @import { Binding } from '#compiler' */
|
|
3
3
|
/** @import { ClientTransformState, ComponentClientTransformState } from './types.js' */
|
|
4
4
|
/** @import { Analysis } from '../../types.js' */
|
|
5
|
+
/** @import { ExpressionMetadata } from '../../nodes.js' */
|
|
5
6
|
/** @import { Scope } from '../../scope.js' */
|
|
6
7
|
import * as b from '#compiler/builders';
|
|
7
8
|
import { is_simple_expression, save } from '../../../utils/ast.js';
|
|
@@ -164,20 +165,46 @@ export function should_proxy(node, scope) {
|
|
|
164
165
|
return true;
|
|
165
166
|
}
|
|
166
167
|
|
|
168
|
+
/**
|
|
169
|
+
* An async thunk. If an `await` inside restores the reaction context via `$.save`,
|
|
170
|
+
* the body exits through `$.unsave` so the context cannot leak into foreign microtasks
|
|
171
|
+
* that run before the returned promise settles
|
|
172
|
+
* @param {Expression | BlockStatement} body
|
|
173
|
+
* @param {ExpressionMetadata} metadata
|
|
174
|
+
*/
|
|
175
|
+
export function async_thunk(body, metadata) {
|
|
176
|
+
if (!metadata.has_pickled_await) {
|
|
177
|
+
return b.arrow([], body, true);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
const block = body.type === 'BlockStatement' ? body : b.block([b.return(body)]);
|
|
181
|
+
|
|
182
|
+
return b.arrow(
|
|
183
|
+
[],
|
|
184
|
+
b.block([
|
|
185
|
+
{
|
|
186
|
+
type: 'TryStatement',
|
|
187
|
+
block,
|
|
188
|
+
handler: null,
|
|
189
|
+
finalizer: b.block([b.stmt(b.call('$.unsave'))])
|
|
190
|
+
}
|
|
191
|
+
]),
|
|
192
|
+
true
|
|
193
|
+
);
|
|
194
|
+
}
|
|
195
|
+
|
|
167
196
|
/**
|
|
168
197
|
* Svelte legacy mode should use safe equals in most places, runes mode shouldn't
|
|
169
198
|
* @param {ComponentClientTransformState} state
|
|
170
199
|
* @param {Expression | BlockStatement} expression
|
|
171
|
-
* @param {
|
|
200
|
+
* @param {ExpressionMetadata} [metadata]
|
|
172
201
|
*/
|
|
173
|
-
export function create_derived(state, expression,
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
if (async) {
|
|
177
|
-
return save(b.call('$.async_derived', thunk));
|
|
178
|
-
} else {
|
|
179
|
-
return b.call(state.analysis.runes ? '$.derived' : '$.derived_safe_equal', thunk);
|
|
202
|
+
export function create_derived(state, expression, metadata) {
|
|
203
|
+
if (metadata?.has_await) {
|
|
204
|
+
return save(b.call('$.async_derived', async_thunk(expression, metadata)));
|
|
180
205
|
}
|
|
206
|
+
|
|
207
|
+
return b.call(state.analysis.runes ? '$.derived' : '$.derived_safe_equal', b.thunk(expression));
|
|
181
208
|
}
|
|
182
209
|
|
|
183
210
|
/**
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
/** @import { BlockStatement, Pattern, Statement } from 'estree' */
|
|
2
2
|
/** @import { AST } from '#compiler' */
|
|
3
3
|
/** @import { ComponentClientTransformState, ComponentContext } from '../types' */
|
|
4
|
-
import { extract_identifiers
|
|
4
|
+
import { extract_identifiers } from '../../../../utils/ast.js';
|
|
5
5
|
import * as b from '#compiler/builders';
|
|
6
|
-
import { create_derived } from '../utils.js';
|
|
6
|
+
import { async_thunk, create_derived } from '../utils.js';
|
|
7
7
|
import { get_value } from './shared/declarations.js';
|
|
8
8
|
import { build_expression, add_svelte_meta } from './shared/utils.js';
|
|
9
9
|
|
|
@@ -15,10 +15,10 @@ export function AwaitBlock(node, context) {
|
|
|
15
15
|
context.state.template.push_comment();
|
|
16
16
|
|
|
17
17
|
// Visit {#await <expression>} first to ensure that scopes are in the correct order
|
|
18
|
-
const
|
|
19
|
-
|
|
20
|
-
node.metadata.expression
|
|
21
|
-
|
|
18
|
+
const input = build_expression(context, node.expression, node.metadata.expression);
|
|
19
|
+
const expression = node.metadata.expression.has_await
|
|
20
|
+
? async_thunk(input, node.metadata.expression)
|
|
21
|
+
: b.thunk(input);
|
|
22
22
|
|
|
23
23
|
let then_block;
|
|
24
24
|
let catch_block;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/** @import { CallExpression, Expression, Pattern } from 'estree' */
|
|
2
2
|
/** @import { AST } from '#compiler' */
|
|
3
3
|
/** @import { ComponentContext } from '../types' */
|
|
4
|
-
import { dev, is_ignored } from '../../../../state.js';
|
|
4
|
+
import { dev, ignore_map, is_ignored } from '../../../../state.js';
|
|
5
5
|
import { is_text_attribute } from '../../../../utils/ast.js';
|
|
6
6
|
import * as b from '#compiler/builders';
|
|
7
7
|
import { binding_properties } from '../../../bindings.js';
|
|
@@ -40,9 +40,15 @@ export function BindDirective(node, context) {
|
|
|
40
40
|
validate_binding(context.state, node, expression);
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
-
const
|
|
44
|
-
|
|
43
|
+
const raw_assignment = b.assignment(
|
|
44
|
+
'=',
|
|
45
|
+
/** @type {Pattern} */ (node.expression),
|
|
46
|
+
b.id('$$value')
|
|
45
47
|
);
|
|
48
|
+
// The assignment is generated, so inherit any ignores attached to the binding
|
|
49
|
+
ignore_map.set(raw_assignment, ignore_map.get(node) ?? []);
|
|
50
|
+
|
|
51
|
+
const assignment = /** @type {Expression} */ (context.visit(raw_assignment));
|
|
46
52
|
|
|
47
53
|
if (dev) {
|
|
48
54
|
// in dev, create named functions, so that `$inspect(...)` delivers
|
|
@@ -58,16 +64,7 @@ export function BindDirective(node, context) {
|
|
|
58
64
|
get = b.thunk(expression);
|
|
59
65
|
|
|
60
66
|
/** @type {Expression | undefined} */
|
|
61
|
-
set = b.unthunk(
|
|
62
|
-
b.arrow(
|
|
63
|
-
[b.id('$$value')],
|
|
64
|
-
/** @type {Expression} */ (
|
|
65
|
-
context.visit(
|
|
66
|
-
b.assignment('=', /** @type {Pattern} */ (node.expression), b.id('$$value'))
|
|
67
|
-
)
|
|
68
|
-
)
|
|
69
|
-
)
|
|
70
|
-
);
|
|
67
|
+
set = b.unthunk(b.arrow([b.id('$$value')], assignment));
|
|
71
68
|
|
|
72
69
|
if (get === set) {
|
|
73
70
|
set = undefined;
|
|
@@ -19,7 +19,7 @@ export function ConstTag(node, context) {
|
|
|
19
19
|
if (declaration.id.type === 'Identifier') {
|
|
20
20
|
const init = build_expression(context, declaration.init, node.metadata.expression);
|
|
21
21
|
|
|
22
|
-
let expression = create_derived(context.state, init, node.metadata.expression
|
|
22
|
+
let expression = create_derived(context.state, init, node.metadata.expression);
|
|
23
23
|
|
|
24
24
|
if (dev) {
|
|
25
25
|
expression = b.call('$.tag', expression, b.literal(declaration.id.name));
|
|
@@ -45,20 +45,31 @@ export function ConstTag(node, context) {
|
|
|
45
45
|
transform
|
|
46
46
|
});
|
|
47
47
|
|
|
48
|
-
|
|
49
|
-
|
|
48
|
+
const is_simple_object_pattern =
|
|
49
|
+
declaration.id.type === 'ObjectPattern' &&
|
|
50
|
+
declaration.id.properties.every(
|
|
51
|
+
(p) =>
|
|
52
|
+
p.type === 'Property' &&
|
|
53
|
+
!p.computed &&
|
|
54
|
+
p.key.type === 'Identifier' &&
|
|
55
|
+
p.value.type === 'Identifier' &&
|
|
56
|
+
p.key.name === p.value.name
|
|
57
|
+
);
|
|
58
|
+
|
|
50
59
|
const init = build_expression(
|
|
51
60
|
{ ...context, state: child_state },
|
|
52
61
|
declaration.init,
|
|
53
62
|
node.metadata.expression
|
|
54
63
|
);
|
|
55
64
|
|
|
56
|
-
const block =
|
|
57
|
-
b.
|
|
58
|
-
|
|
59
|
-
|
|
65
|
+
const block = is_simple_object_pattern
|
|
66
|
+
? b.block([b.return(init)])
|
|
67
|
+
: b.block([
|
|
68
|
+
b.const(/** @type {Pattern} */ (context.visit(declaration.id, child_state)), init),
|
|
69
|
+
b.return(b.object(identifiers.map((node) => b.prop('init', node, node))))
|
|
70
|
+
]);
|
|
60
71
|
|
|
61
|
-
let expression = create_derived(context.state, block, node.metadata.expression
|
|
72
|
+
let expression = create_derived(context.state, block, node.metadata.expression);
|
|
62
73
|
|
|
63
74
|
if (dev) {
|
|
64
75
|
expression = b.call('$.tag', expression, b.literal('[@const]'));
|