svelte 5.56.8 → 5.56.10
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/index.js +2 -1
- package/src/compiler/migrate/index.js +6 -1
- package/src/compiler/phases/1-parse/index.js +4 -0
- package/src/compiler/phases/1-parse/read/style.js +75 -29
- package/src/compiler/phases/2-analyze/css/css-analyze.js +3 -0
- package/src/compiler/phases/2-analyze/css/css-warn.js +3 -0
- package/src/compiler/phases/2-analyze/index.js +19 -15
- package/src/compiler/phases/3-transform/client/visitors/AssignmentExpression.js +16 -3
- package/src/compiler/phases/3-transform/client/visitors/RegularElement.js +13 -2
- package/src/compiler/phases/3-transform/client/visitors/UpdateExpression.js +0 -1
- package/src/compiler/phases/3-transform/css/index.js +8 -1
- package/src/compiler/phases/3-transform/server/visitors/LabeledStatement.js +1 -0
- package/src/compiler/phases/3-transform/server/visitors/VariableDeclaration.js +4 -2
- package/src/compiler/phases/3-transform/server/visitors/shared/element.js +48 -43
- package/src/compiler/phases/3-transform/server/visitors/shared/utils.js +1 -1
- package/src/compiler/print/index.js +372 -177
- package/src/internal/client/context.js +20 -53
- package/src/internal/client/dom/blocks/css-props.js +3 -3
- package/src/internal/client/dom/blocks/each.js +6 -2
- package/src/internal/client/dom/css.js +6 -1
- package/src/internal/client/dom/elements/attributes.js +2 -1
- package/src/internal/client/dom/elements/events.js +19 -1
- package/src/internal/client/reactivity/batch.js +1 -0
- package/src/internal/client/reactivity/sources.js +7 -1
- package/src/internal/server/context.js +8 -47
- package/src/internal/server/renderer.js +1 -0
- package/src/internal/shared/attributes.js +2 -1
- package/src/internal/shared/context.js +52 -0
- package/src/internal/shared/errors.js +2 -2
- package/src/version.js +1 -1
- package/types/index.d.ts +23 -9
- package/types/index.d.ts.map +1 -1
package/package.json
CHANGED
package/src/compiler/index.js
CHANGED
|
@@ -1590,7 +1590,12 @@ function migrate_slot_usage(node, path, state) {
|
|
|
1590
1590
|
*/
|
|
1591
1591
|
function extract_type_and_comment(declarator, state, path) {
|
|
1592
1592
|
const str = state.str;
|
|
1593
|
-
|
|
1593
|
+
let parent = path.at(-1);
|
|
1594
|
+
|
|
1595
|
+
if (parent?.type === 'Program') {
|
|
1596
|
+
// We don't want comments from the program node
|
|
1597
|
+
parent = undefined;
|
|
1598
|
+
}
|
|
1594
1599
|
|
|
1595
1600
|
// Try to find jsdoc above the declaration
|
|
1596
1601
|
let comment_node = /** @type {Node} */ (parent)?.leadingComments?.at(-1);
|
|
@@ -50,6 +50,9 @@ export class Parser {
|
|
|
50
50
|
/** */
|
|
51
51
|
index = 0;
|
|
52
52
|
|
|
53
|
+
/** @type {AST.CSS.CSSComment[]} */
|
|
54
|
+
css_comments = [];
|
|
55
|
+
|
|
53
56
|
/**
|
|
54
57
|
* Creates a minimal parser instance for CSS-only parsing.
|
|
55
58
|
* Skips Svelte component parsing setup.
|
|
@@ -61,6 +64,7 @@ export class Parser {
|
|
|
61
64
|
parser.template = source;
|
|
62
65
|
parser.index = 0;
|
|
63
66
|
parser.loose = false;
|
|
67
|
+
parser.css_comments = [];
|
|
64
68
|
return parser;
|
|
65
69
|
}
|
|
66
70
|
|
|
@@ -7,8 +7,11 @@ const REGEX_CLOSING_BRACKET = /[\s\]]/;
|
|
|
7
7
|
const REGEX_ATTRIBUTE_FLAGS = /[a-zA-Z]+/y; // only `i` and `s` are valid today, but make it future-proof
|
|
8
8
|
const REGEX_COMBINATOR = /(\+|~|>|\|\|)/y;
|
|
9
9
|
const REGEX_PERCENTAGE = /\d+(\.\d+)?%/y;
|
|
10
|
+
// `of` must be preceded by whitespace, otherwise it would be part of the `<an+b>` token
|
|
11
|
+
// (`2nof` is a single dimension token). It does not need to be followed by whitespace,
|
|
12
|
+
// because a `.`, `#`, `[`, `*`, `:` or `&` already ends the `of` identifier — minifiers rely on that
|
|
10
13
|
const REGEX_NTH_OF =
|
|
11
|
-
/(even|odd|\+?(\d+|\d*n(\s*[+-]\s*\d+)?)|-\d*n(\s*\+\s*\d+))((?=\s*[,)])|\s+of\s
|
|
14
|
+
/(even|odd|\+?(\d+|\d*n(\s*[+-]\s*\d+)?)|-\d*n(\s*\+\s*\d+))((?=\s*[,)])|\s+of(\s+|(?=[.#[*:&])))/y;
|
|
12
15
|
const REGEX_WHITESPACE_OR_COLON = /[\s:]/;
|
|
13
16
|
const REGEX_LEADING_HYPHEN_OR_DIGIT = /-?\d/y;
|
|
14
17
|
const REGEX_VALID_IDENTIFIER_CHAR = /[a-zA-Z0-9_-]/;
|
|
@@ -24,6 +27,7 @@ const REGEX_HTML_COMMENT_CLOSE = /-->/;
|
|
|
24
27
|
*/
|
|
25
28
|
export default function read_style(parser, start, attributes) {
|
|
26
29
|
const content_start = parser.index;
|
|
30
|
+
parser.css_comments = [];
|
|
27
31
|
const children = read_body(parser, (p) => p.match('</style') || p.index >= p.template.length);
|
|
28
32
|
const content_end = parser.index;
|
|
29
33
|
|
|
@@ -36,6 +40,7 @@ export default function read_style(parser, start, attributes) {
|
|
|
36
40
|
end: parser.index,
|
|
37
41
|
attributes,
|
|
38
42
|
children,
|
|
43
|
+
comments: parser.css_comments,
|
|
39
44
|
content: {
|
|
40
45
|
start: content_start,
|
|
41
46
|
end: content_end,
|
|
@@ -202,15 +207,18 @@ function read_selector(parser, inside_pseudo_class = false) {
|
|
|
202
207
|
});
|
|
203
208
|
} else if (parser.eat('*')) {
|
|
204
209
|
let name = '*';
|
|
210
|
+
/** @type {string | undefined} */
|
|
211
|
+
let namespace;
|
|
205
212
|
|
|
206
213
|
if (parser.eat('|')) {
|
|
207
|
-
|
|
208
|
-
name = read_identifier(parser);
|
|
214
|
+
namespace = name;
|
|
215
|
+
name = parser.eat('*') ? '*' : read_identifier(parser);
|
|
209
216
|
}
|
|
210
217
|
|
|
211
218
|
relative_selector.selectors.push({
|
|
212
219
|
type: 'TypeSelector',
|
|
213
220
|
name,
|
|
221
|
+
...(namespace !== undefined && { namespace }),
|
|
214
222
|
start,
|
|
215
223
|
end: parser.index
|
|
216
224
|
});
|
|
@@ -229,18 +237,22 @@ function read_selector(parser, inside_pseudo_class = false) {
|
|
|
229
237
|
end: parser.index
|
|
230
238
|
});
|
|
231
239
|
} else if (parser.eat('::')) {
|
|
240
|
+
const name = read_identifier(parser);
|
|
241
|
+
/** @type {AST.CSS.SelectorList | null} */
|
|
242
|
+
let args = null;
|
|
243
|
+
|
|
244
|
+
if (parser.eat('(')) {
|
|
245
|
+
args = read_selector_list(parser, true);
|
|
246
|
+
parser.eat(')', true);
|
|
247
|
+
}
|
|
248
|
+
|
|
232
249
|
relative_selector.selectors.push({
|
|
233
250
|
type: 'PseudoElementSelector',
|
|
234
|
-
name
|
|
251
|
+
name,
|
|
235
252
|
start,
|
|
236
|
-
end: parser.index
|
|
253
|
+
end: parser.index,
|
|
254
|
+
...(args && { args })
|
|
237
255
|
});
|
|
238
|
-
// We read the inner selectors of a pseudo element to ensure it parses correctly,
|
|
239
|
-
// but we don't do anything with the result.
|
|
240
|
-
if (parser.eat('(')) {
|
|
241
|
-
read_selector_list(parser, true);
|
|
242
|
-
parser.eat(')', true);
|
|
243
|
-
}
|
|
244
256
|
} else if (parser.eat(':')) {
|
|
245
257
|
const name = read_identifier(parser);
|
|
246
258
|
|
|
@@ -308,22 +320,25 @@ function read_selector(parser, inside_pseudo_class = false) {
|
|
|
308
320
|
});
|
|
309
321
|
} else if (!parser.match_regex(REGEX_COMBINATOR)) {
|
|
310
322
|
let name = read_identifier(parser);
|
|
323
|
+
/** @type {string | undefined} */
|
|
324
|
+
let namespace;
|
|
311
325
|
|
|
312
326
|
if (parser.eat('|')) {
|
|
313
|
-
|
|
314
|
-
name = read_identifier(parser);
|
|
327
|
+
namespace = name;
|
|
328
|
+
name = parser.eat('*') ? '*' : read_identifier(parser);
|
|
315
329
|
}
|
|
316
330
|
|
|
317
331
|
relative_selector.selectors.push({
|
|
318
332
|
type: 'TypeSelector',
|
|
319
333
|
name,
|
|
334
|
+
...(namespace !== undefined && { namespace }),
|
|
320
335
|
start,
|
|
321
336
|
end: parser.index
|
|
322
337
|
});
|
|
323
338
|
}
|
|
324
339
|
|
|
325
340
|
const index = parser.index;
|
|
326
|
-
allow_comment_or_whitespace(parser);
|
|
341
|
+
allow_comment_or_whitespace(parser, false);
|
|
327
342
|
|
|
328
343
|
if (parser.match(',') || (inside_pseudo_class ? parser.match(')') : parser.match('{'))) {
|
|
329
344
|
// rewind, so we know whether to continue building the selector list
|
|
@@ -449,7 +464,7 @@ function read_block_item(parser) {
|
|
|
449
464
|
// read ahead to understand whether we're dealing with a declaration or a nested rule.
|
|
450
465
|
// this involves some duplicated work, but avoids a try-catch that would disguise errors
|
|
451
466
|
const start = parser.index;
|
|
452
|
-
read_value(parser);
|
|
467
|
+
read_value(parser, false);
|
|
453
468
|
const char = parser.template[parser.index];
|
|
454
469
|
parser.index = start;
|
|
455
470
|
|
|
@@ -492,10 +507,13 @@ function read_declaration(parser) {
|
|
|
492
507
|
|
|
493
508
|
/**
|
|
494
509
|
* @param {Parser} parser
|
|
510
|
+
* @param {boolean} [capture_comments]
|
|
495
511
|
* @returns {string}
|
|
496
512
|
*/
|
|
497
|
-
function read_value(parser) {
|
|
513
|
+
function read_value(parser, capture_comments = true) {
|
|
498
514
|
let value = '';
|
|
515
|
+
/** @type {AST.CSS.CSSComment[]} */
|
|
516
|
+
const value_comments = [];
|
|
499
517
|
let escaped = false;
|
|
500
518
|
let in_url = false;
|
|
501
519
|
|
|
@@ -523,6 +541,13 @@ function read_value(parser) {
|
|
|
523
541
|
} else if (char === '(' && value.slice(-3) === 'url') {
|
|
524
542
|
in_url = true;
|
|
525
543
|
} else if ((char === ';' || char === '{' || char === '}') && !in_url && !quote_mark) {
|
|
544
|
+
const leading_whitespace = value.length - value.trimStart().length;
|
|
545
|
+
for (const comment of value_comments) {
|
|
546
|
+
comment.position = Math.max(
|
|
547
|
+
0,
|
|
548
|
+
/** @type {number} */ (comment.position) - leading_whitespace
|
|
549
|
+
);
|
|
550
|
+
}
|
|
526
551
|
return value.trim();
|
|
527
552
|
} else if (
|
|
528
553
|
char === '/' &&
|
|
@@ -530,13 +555,11 @@ function read_value(parser) {
|
|
|
530
555
|
!quote_mark &&
|
|
531
556
|
parser.template[parser.index + 1] === '*'
|
|
532
557
|
) {
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
}
|
|
539
|
-
parser.index++;
|
|
558
|
+
const comment = read_comment(parser);
|
|
559
|
+
if (capture_comments) {
|
|
560
|
+
comment.position = value.length;
|
|
561
|
+
parser.css_comments.push(comment);
|
|
562
|
+
value_comments.push(comment);
|
|
540
563
|
}
|
|
541
564
|
continue;
|
|
542
565
|
}
|
|
@@ -600,7 +623,8 @@ function read_identifier(parser) {
|
|
|
600
623
|
if (char === '\\') {
|
|
601
624
|
const sequence = parser.match_regex(REGEX_UNICODE_SEQUENCE);
|
|
602
625
|
if (sequence) {
|
|
603
|
-
|
|
626
|
+
const character = String.fromCodePoint(parseInt(sequence.slice(1), 16));
|
|
627
|
+
identifier += character === '\\' ? '\\\\' : character;
|
|
604
628
|
parser.index += sequence.length;
|
|
605
629
|
} else {
|
|
606
630
|
identifier += '\\' + parser.template[parser.index + 1];
|
|
@@ -624,13 +648,16 @@ function read_identifier(parser) {
|
|
|
624
648
|
return identifier;
|
|
625
649
|
}
|
|
626
650
|
|
|
627
|
-
/**
|
|
628
|
-
|
|
651
|
+
/**
|
|
652
|
+
* @param {Parser} parser
|
|
653
|
+
* @param {boolean} [capture_comments]
|
|
654
|
+
*/
|
|
655
|
+
function allow_comment_or_whitespace(parser, capture_comments = true) {
|
|
629
656
|
parser.allow_whitespace();
|
|
630
657
|
while (parser.match('/*') || parser.match('<!--')) {
|
|
631
|
-
if (parser.
|
|
632
|
-
parser
|
|
633
|
-
parser.
|
|
658
|
+
if (parser.match('/*')) {
|
|
659
|
+
const comment = read_comment(parser);
|
|
660
|
+
if (capture_comments) parser.css_comments.push(comment);
|
|
634
661
|
}
|
|
635
662
|
|
|
636
663
|
if (parser.eat('<!--')) {
|
|
@@ -642,6 +669,25 @@ function allow_comment_or_whitespace(parser) {
|
|
|
642
669
|
}
|
|
643
670
|
}
|
|
644
671
|
|
|
672
|
+
/**
|
|
673
|
+
* @param {Parser} parser
|
|
674
|
+
* @returns {AST.CSS.CSSComment}
|
|
675
|
+
*/
|
|
676
|
+
function read_comment(parser) {
|
|
677
|
+
const start = parser.index;
|
|
678
|
+
parser.eat('/*', true);
|
|
679
|
+
const value = parser.read_until(REGEX_COMMENT_CLOSE);
|
|
680
|
+
parser.eat('*/', true);
|
|
681
|
+
const end = parser.index;
|
|
682
|
+
|
|
683
|
+
return {
|
|
684
|
+
type: 'CSSComment',
|
|
685
|
+
value,
|
|
686
|
+
start,
|
|
687
|
+
end
|
|
688
|
+
};
|
|
689
|
+
}
|
|
690
|
+
|
|
645
691
|
/**
|
|
646
692
|
* Parse standalone CSS content (not wrapped in `<style>`).
|
|
647
693
|
* @param {Parser} parser
|
|
@@ -958,7 +958,7 @@ function calculate_blockers(instance, analysis) {
|
|
|
958
958
|
* @param {Set<Binding>} touched
|
|
959
959
|
* @param {Set<ESTree.Node>} seen
|
|
960
960
|
*/
|
|
961
|
-
const touch = (expression, scope, touched, seen
|
|
961
|
+
const touch = (expression, scope, touched, seen) => {
|
|
962
962
|
if (seen.has(expression)) return;
|
|
963
963
|
seen.add(expression);
|
|
964
964
|
|
|
@@ -1015,6 +1015,13 @@ function calculate_blockers(instance, analysis) {
|
|
|
1015
1015
|
}
|
|
1016
1016
|
}
|
|
1017
1017
|
|
|
1018
|
+
// Share seen nodes across calls so transitive assignments are only visited once.
|
|
1019
|
+
// Keep separate read/write state because the target sets can differ.
|
|
1020
|
+
/** @type {Set<ESTree.Node>} */
|
|
1021
|
+
const writes_seen = new Set();
|
|
1022
|
+
/** @type {Set<ESTree.Node>} */
|
|
1023
|
+
const reads_seen = new Set();
|
|
1024
|
+
|
|
1018
1025
|
walk(
|
|
1019
1026
|
node,
|
|
1020
1027
|
{ scope },
|
|
@@ -1044,13 +1051,7 @@ function calculate_blockers(instance, analysis) {
|
|
|
1044
1051
|
const rune = get_rune(node, context.state.scope);
|
|
1045
1052
|
if (rune === '$effect') return;
|
|
1046
1053
|
|
|
1047
|
-
|
|
1048
|
-
const touched = new Set();
|
|
1049
|
-
touch(node, context.state.scope, touched);
|
|
1050
|
-
|
|
1051
|
-
for (const b of touched) {
|
|
1052
|
-
writes.add(b);
|
|
1053
|
-
}
|
|
1054
|
+
touch(node, context.state.scope, writes, writes_seen);
|
|
1054
1055
|
},
|
|
1055
1056
|
Identifier(node, context) {
|
|
1056
1057
|
const parent = /** @type {ESTree.Node} */ (context.path.at(-1));
|
|
@@ -1066,7 +1067,7 @@ function calculate_blockers(instance, analysis) {
|
|
|
1066
1067
|
// might be called immediately, so we have to touch all references within it. Example:
|
|
1067
1068
|
// function foo() { return () => blocker; } foo(); // blocker is touched
|
|
1068
1069
|
if (node.argument) {
|
|
1069
|
-
touch(node.argument, context.state.scope, reads);
|
|
1070
|
+
touch(node.argument, context.state.scope, reads, reads_seen);
|
|
1070
1071
|
}
|
|
1071
1072
|
},
|
|
1072
1073
|
// don't look inside functions until they are called
|
|
@@ -1235,12 +1236,15 @@ function calculate_blockers(instance, analysis) {
|
|
|
1235
1236
|
? /** @type {ESTree.FunctionExpression | ESTree.ArrowFunctionExpression} */ (fn.init)
|
|
1236
1237
|
: fn;
|
|
1237
1238
|
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
reads_writes,
|
|
1242
|
-
|
|
1243
|
-
|
|
1239
|
+
const fn_scope = /** @type {Scope} */ (instance.scopes.get(init));
|
|
1240
|
+
|
|
1241
|
+
if (init.body.type === 'BlockStatement') {
|
|
1242
|
+
trace_references(init.body, reads_writes, reads_writes, fn_scope);
|
|
1243
|
+
} else {
|
|
1244
|
+
// A concise arrow body is an implicit return, so treat it like the
|
|
1245
|
+
// `ReturnStatement` visitor in `trace_references` would.
|
|
1246
|
+
touch(init.body, fn_scope, reads_writes, new Set());
|
|
1247
|
+
}
|
|
1244
1248
|
|
|
1245
1249
|
const max = [...reads_writes].reduce((max, binding) => {
|
|
1246
1250
|
if (binding.blocker) {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/** @import { AssignmentExpression, AssignmentOperator, Expression, Identifier, Pattern } from 'estree' */
|
|
1
|
+
/** @import { AssignmentExpression, AssignmentOperator, Expression, Identifier, LogicalOperator, Pattern } from 'estree' */
|
|
2
2
|
/** @import { AST } from '#compiler' */
|
|
3
3
|
/** @import { Context } from '../types.js' */
|
|
4
4
|
import * as b from '#compiler/builders';
|
|
@@ -79,8 +79,13 @@ function build_assignment(operator, left, right, context) {
|
|
|
79
79
|
|
|
80
80
|
// special case — assignment to private state field
|
|
81
81
|
if (left.property.type === 'PrivateIdentifier') {
|
|
82
|
+
const logical_operator = ['||=', '&&=', '??='].includes(operator)
|
|
83
|
+
? /** @type {LogicalOperator} */ (operator.slice(0, -1))
|
|
84
|
+
: null;
|
|
82
85
|
let value = /** @type {Expression} */ (
|
|
83
|
-
context.visit(
|
|
86
|
+
context.visit(
|
|
87
|
+
logical_operator === null ? build_assignment_value(operator, left, right) : right
|
|
88
|
+
)
|
|
84
89
|
);
|
|
85
90
|
|
|
86
91
|
const needs_proxy =
|
|
@@ -88,7 +93,15 @@ function build_assignment(operator, left, right, context) {
|
|
|
88
93
|
is_non_coercive_operator(operator) &&
|
|
89
94
|
should_proxy(value, context.state.scope);
|
|
90
95
|
|
|
91
|
-
|
|
96
|
+
const assignment = b.call('$.set', left, value, needs_proxy && b.true);
|
|
97
|
+
|
|
98
|
+
return logical_operator === null
|
|
99
|
+
? assignment
|
|
100
|
+
: b.logical(
|
|
101
|
+
logical_operator,
|
|
102
|
+
/** @type {Expression} */ (context.visit(left)),
|
|
103
|
+
assignment
|
|
104
|
+
);
|
|
92
105
|
}
|
|
93
106
|
}
|
|
94
107
|
}
|
|
@@ -664,14 +664,25 @@ function build_element_attribute_update(element, node_id, name, value, attribute
|
|
|
664
664
|
* @param {ComponentContext} context
|
|
665
665
|
*/
|
|
666
666
|
function build_custom_element_attribute_update_assignment(node_id, attribute, context) {
|
|
667
|
-
const
|
|
667
|
+
const memoizer = new Memoizer();
|
|
668
|
+
const { value, has_state } = build_attribute_value(attribute.value, context, (value, metadata) =>
|
|
669
|
+
memoizer.add(value, metadata)
|
|
670
|
+
);
|
|
668
671
|
|
|
669
672
|
// don't lowercase name, as we set the element's property, which might be case sensitive
|
|
670
673
|
const call = b.call('$.set_custom_element_data', node_id, b.literal(attribute.name), value);
|
|
671
674
|
|
|
672
675
|
// this is different from other updates — it doesn't get grouped,
|
|
673
676
|
// because set_custom_element_data may not be idempotent
|
|
674
|
-
const update = has_state
|
|
677
|
+
const update = has_state
|
|
678
|
+
? b.call(
|
|
679
|
+
'$.template_effect',
|
|
680
|
+
b.arrow(memoizer.apply(), call),
|
|
681
|
+
memoizer.sync_values(),
|
|
682
|
+
memoizer.async_values(),
|
|
683
|
+
memoizer.blockers()
|
|
684
|
+
)
|
|
685
|
+
: call;
|
|
675
686
|
|
|
676
687
|
context.state.init.push(b.stmt(update));
|
|
677
688
|
}
|
|
@@ -13,7 +13,6 @@ export function UpdateExpression(node, context) {
|
|
|
13
13
|
|
|
14
14
|
if (
|
|
15
15
|
argument.type === 'MemberExpression' &&
|
|
16
|
-
argument.object.type === 'ThisExpression' &&
|
|
17
16
|
argument.property.type === 'PrivateIdentifier' &&
|
|
18
17
|
context.state.state_fields.has('#' + argument.property.name)
|
|
19
18
|
) {
|
|
@@ -355,7 +355,11 @@ const visitors = {
|
|
|
355
355
|
continue;
|
|
356
356
|
}
|
|
357
357
|
|
|
358
|
-
if (
|
|
358
|
+
if (
|
|
359
|
+
selector.type === 'TypeSelector' &&
|
|
360
|
+
selector.name === '*' &&
|
|
361
|
+
selector.namespace === undefined
|
|
362
|
+
) {
|
|
359
363
|
context.state.code.update(selector.start, selector.end, modifier);
|
|
360
364
|
} else {
|
|
361
365
|
context.state.code.appendLeft(selector.end, modifier);
|
|
@@ -374,6 +378,9 @@ const visitors = {
|
|
|
374
378
|
if (node.name === 'is' || node.name === 'where' || node.name === 'has' || node.name === 'not') {
|
|
375
379
|
context.next();
|
|
376
380
|
}
|
|
381
|
+
},
|
|
382
|
+
PseudoElementSelector() {
|
|
383
|
+
// Functional pseudo-element arguments are not scoped as selectors.
|
|
377
384
|
}
|
|
378
385
|
};
|
|
379
386
|
|
|
@@ -102,9 +102,11 @@ export function VariableDeclaration(node, context) {
|
|
|
102
102
|
} else {
|
|
103
103
|
const call = /** @type {CallExpression} */ (declarator.init);
|
|
104
104
|
|
|
105
|
-
|
|
105
|
+
// - cannot be a SpreadElement because refused during analysis
|
|
106
|
+
// - use args[0] rather than value to avoid visiting twice (above in const value = ... and below in for-ofs)
|
|
107
|
+
let rhs = /** @type {Expression} */ (call.arguments[0]);
|
|
106
108
|
|
|
107
|
-
if (rune
|
|
109
|
+
if (rune === '$derived.by' || call.arguments[0].type !== 'Identifier') {
|
|
108
110
|
const id = b.id(context.state.scope.generate('$$d'));
|
|
109
111
|
|
|
110
112
|
rhs = b.call(id);
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
/** @import { ComponentContext, ComponentServerTransformState } from '../../types.js' */
|
|
4
4
|
import { is_event_attribute, is_text_attribute } from '../../../../../utils/ast.js';
|
|
5
5
|
import { binding_properties } from '../../../../bindings.js';
|
|
6
|
-
import {
|
|
6
|
+
import { ExpressionMetadata, is_custom_element_node } from '../../../../nodes.js';
|
|
7
7
|
import { regex_starts_with_newline } from '../../../../patterns.js';
|
|
8
8
|
import * as b from '#compiler/builders';
|
|
9
9
|
import {
|
|
@@ -21,6 +21,11 @@ import { escape_html } from '../../../../../../escaping.js';
|
|
|
21
21
|
|
|
22
22
|
const WHITESPACE_INSENSITIVE_ATTRIBUTES = ['class', 'style'];
|
|
23
23
|
|
|
24
|
+
/**
|
|
25
|
+
* @typedef {{ type: 'transformed', name: string, expression: Expression }} TransformedAttribute
|
|
26
|
+
* An attribute whose expression has already been transformed and must not be visited again.
|
|
27
|
+
*/
|
|
28
|
+
|
|
24
29
|
/**
|
|
25
30
|
* Writes the output to the template output. Some elements may have attributes on them that require the
|
|
26
31
|
* their output to be the child content instead. In this case, an object is returned.
|
|
@@ -29,7 +34,7 @@ const WHITESPACE_INSENSITIVE_ATTRIBUTES = ['class', 'style'];
|
|
|
29
34
|
* @param {(expression: Expression, metadata: ExpressionMetadata) => Expression} transform
|
|
30
35
|
*/
|
|
31
36
|
export function build_element_attributes(node, context, transform) {
|
|
32
|
-
/** @type {Array<AST.Attribute | AST.SpreadAttribute>} */
|
|
37
|
+
/** @type {Array<AST.Attribute | AST.SpreadAttribute | TransformedAttribute>} */
|
|
33
38
|
const attributes = [];
|
|
34
39
|
|
|
35
40
|
/** @type {AST.ClassDirective[]} */
|
|
@@ -145,42 +150,26 @@ export function build_element_attributes(node, context, transform) {
|
|
|
145
150
|
attr.value[0].data === 'checkbox'
|
|
146
151
|
);
|
|
147
152
|
|
|
148
|
-
attributes.push(
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
build_attribute_value(value_attribute.value, context, transform)
|
|
163
|
-
),
|
|
164
|
-
metadata: {
|
|
165
|
-
expression: new ExpressionMetadata()
|
|
166
|
-
}
|
|
167
|
-
}
|
|
168
|
-
])
|
|
169
|
-
);
|
|
153
|
+
attributes.push({
|
|
154
|
+
type: 'transformed',
|
|
155
|
+
name: 'checked',
|
|
156
|
+
expression: is_checkbox
|
|
157
|
+
? b.call(
|
|
158
|
+
b.member(expression, 'includes'),
|
|
159
|
+
build_attribute_value(value_attribute.value, context, transform)
|
|
160
|
+
)
|
|
161
|
+
: b.binary(
|
|
162
|
+
'===',
|
|
163
|
+
expression,
|
|
164
|
+
build_attribute_value(value_attribute.value, context, transform)
|
|
165
|
+
)
|
|
166
|
+
});
|
|
170
167
|
} else {
|
|
171
|
-
attributes.push(
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
end: -1,
|
|
177
|
-
expression,
|
|
178
|
-
metadata: {
|
|
179
|
-
expression: new ExpressionMetadata()
|
|
180
|
-
}
|
|
181
|
-
}
|
|
182
|
-
])
|
|
183
|
-
);
|
|
168
|
+
attributes.push({
|
|
169
|
+
type: 'transformed',
|
|
170
|
+
name: get_attribute_name(node, attribute),
|
|
171
|
+
expression
|
|
172
|
+
});
|
|
184
173
|
}
|
|
185
174
|
} else if (attribute.type === 'SpreadAttribute') {
|
|
186
175
|
attributes.push(attribute);
|
|
@@ -217,7 +206,21 @@ export function build_element_attributes(node, context, transform) {
|
|
|
217
206
|
} else {
|
|
218
207
|
const css_hash = node.metadata.scoped ? context.state.analysis.css.hash : null;
|
|
219
208
|
|
|
220
|
-
for (const attribute of /** @type {AST.Attribute
|
|
209
|
+
for (const attribute of /** @type {Array<AST.Attribute | TransformedAttribute>} */ (
|
|
210
|
+
attributes
|
|
211
|
+
)) {
|
|
212
|
+
if (attribute.type === 'transformed') {
|
|
213
|
+
context.state.template.push(
|
|
214
|
+
b.call(
|
|
215
|
+
'$.attr',
|
|
216
|
+
b.literal(attribute.name),
|
|
217
|
+
attribute.expression,
|
|
218
|
+
is_boolean_attribute(attribute.name) && b.true
|
|
219
|
+
)
|
|
220
|
+
);
|
|
221
|
+
continue;
|
|
222
|
+
}
|
|
223
|
+
|
|
221
224
|
const name = get_attribute_name(node, attribute);
|
|
222
225
|
const can_use_literal =
|
|
223
226
|
(name !== 'class' || class_directives.length === 0) &&
|
|
@@ -298,14 +301,16 @@ function get_attribute_name(element, attribute) {
|
|
|
298
301
|
|
|
299
302
|
/**
|
|
300
303
|
* @param {AST.RegularElement | AST.SvelteElement} element
|
|
301
|
-
* @param {Array<AST.Attribute | AST.SpreadAttribute | AST.BindDirective>} attributes
|
|
304
|
+
* @param {Array<AST.Attribute | AST.SpreadAttribute | AST.BindDirective | TransformedAttribute>} attributes
|
|
302
305
|
* @param {ComponentContext} context
|
|
303
306
|
* @param {(expression: Expression, metadata: ExpressionMetadata) => Expression} transform
|
|
304
307
|
*/
|
|
305
308
|
export function build_spread_object(element, attributes, context, transform) {
|
|
306
309
|
const object = b.object(
|
|
307
310
|
attributes.map((attribute) => {
|
|
308
|
-
if (attribute.type === '
|
|
311
|
+
if (attribute.type === 'transformed') {
|
|
312
|
+
return b.prop('init', b.key(attribute.name), attribute.expression);
|
|
313
|
+
} else if (attribute.type === 'Attribute') {
|
|
309
314
|
const name = get_attribute_name(element, attribute);
|
|
310
315
|
const value = build_attribute_value(
|
|
311
316
|
attribute.value,
|
|
@@ -340,7 +345,7 @@ export function build_spread_object(element, attributes, context, transform) {
|
|
|
340
345
|
/**
|
|
341
346
|
*
|
|
342
347
|
* @param {AST.RegularElement | AST.SvelteElement} element
|
|
343
|
-
* @param {Array<AST.Attribute | AST.SpreadAttribute>} attributes
|
|
348
|
+
* @param {Array<AST.Attribute | AST.SpreadAttribute | TransformedAttribute>} attributes
|
|
344
349
|
* @param {AST.StyleDirective[]} style_directives
|
|
345
350
|
* @param {AST.ClassDirective[]} class_directives
|
|
346
351
|
* @param {ComponentContext} context
|
|
@@ -356,7 +361,7 @@ function build_element_spread_attributes(
|
|
|
356
361
|
) {
|
|
357
362
|
const args = prepare_element_spread(
|
|
358
363
|
element,
|
|
359
|
-
|
|
364
|
+
attributes,
|
|
360
365
|
style_directives,
|
|
361
366
|
class_directives,
|
|
362
367
|
context,
|
|
@@ -410,7 +415,7 @@ export function prepare_element_spread_object(element, context, transform) {
|
|
|
410
415
|
/**
|
|
411
416
|
* Prepare args for $.attributes(...): compute object, css_hash, classes, styles and flags.
|
|
412
417
|
* @param {AST.RegularElement | AST.SvelteElement} element
|
|
413
|
-
* @param {Array<AST.Attribute | AST.SpreadAttribute | AST.BindDirective>} attributes
|
|
418
|
+
* @param {Array<AST.Attribute | AST.SpreadAttribute | AST.BindDirective | TransformedAttribute>} attributes
|
|
414
419
|
* @param {AST.StyleDirective[]} style_directives
|
|
415
420
|
* @param {AST.ClassDirective[]} class_directives
|
|
416
421
|
* @param {ComponentContext} context
|
|
@@ -286,7 +286,7 @@ export function build_getter(node, state) {
|
|
|
286
286
|
}
|
|
287
287
|
|
|
288
288
|
if (binding.kind === 'derived') {
|
|
289
|
-
return (binding.declaration_kind === 'var' ? b.maybe_call : b.call)(
|
|
289
|
+
return (binding.declaration_kind === 'var' ? b.maybe_call : b.call)(node);
|
|
290
290
|
}
|
|
291
291
|
|
|
292
292
|
return node;
|