Skip to content

Commit 91c2e30

Browse files
committed
merge main
2 parents 96e59c8 + bd950a0 commit 91c2e30

File tree

25 files changed

+184
-206
lines changed

25 files changed

+184
-206
lines changed

.changeset/breezy-waves-camp.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"svelte": patch
3+
---
4+
5+
fix: validate form inside a form

.changeset/funny-dragons-double.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'svelte': patch
3+
---
4+
5+
feat: simpler hydration of CSS custom property wrappers

.changeset/spotty-crabs-give.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'svelte': patch
3+
---
4+
5+
feat: more efficient output for attributes in SSR

.changeset/thin-spoons-float.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'svelte': patch
3+
---
4+
5+
fix: update reactive set when deleting initial values

.changeset/tiny-taxis-whisper.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'svelte': patch
3+
---
4+
5+
feat: simpler string normalization

packages/svelte/src/compiler/phases/1-parse/state/element.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ const regex_closing_comment = /-->/;
7272
const regex_capital_letter = /[A-Z]/;
7373

7474
/** @param {import('../index.js').Parser} parser */
75-
export default function tag(parser) {
75+
export default function element(parser) {
7676
const start = parser.index++;
7777

7878
let parent = parser.current();

packages/svelte/src/compiler/phases/1-parse/state/tag.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import { walk } from 'zimmerframe';
77
const regex_whitespace_with_closing_curly_brace = /^\s*}/;
88

99
/** @param {import('../index.js').Parser} parser */
10-
export default function mustache(parser) {
10+
export default function tag(parser) {
1111
const start = parser.index;
1212
parser.index += 1;
1313

packages/svelte/src/compiler/phases/2-analyze/validation.js

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -568,6 +568,17 @@ const validation = {
568568
}
569569
}
570570

571+
// can't add form to interactive elements because those are also used by the parser
572+
// to check for the last auto-closing parent.
573+
if (node.name === 'form') {
574+
const path = context.path;
575+
for (let parent of path) {
576+
if (parent.type === 'RegularElement' && parent.name === 'form') {
577+
e.node_invalid_placement(node, `<${node.name}>`, parent.name);
578+
}
579+
}
580+
}
581+
571582
if (interactive_elements.has(node.name)) {
572583
const path = context.path;
573584
for (let parent of path) {

packages/svelte/src/compiler/phases/3-transform/client/visitors/template.js

Lines changed: 28 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -896,30 +896,35 @@ function serialize_inline_component(node, component_name, context) {
896896
'$.spread_props',
897897
...props_and_spreads.map((p) => (Array.isArray(p) ? b.object(p) : p))
898898
);
899-
/** @param {import('estree').Identifier} node_id */
900-
let fn = (node_id) =>
901-
b.call(
899+
900+
/** @param {import('estree').Expression} node_id */
901+
let fn = (node_id) => {
902+
return b.call(
902903
context.state.options.dev
903904
? b.call('$.validate_component', b.id(component_name))
904905
: component_name,
905906
node_id,
906907
props_expression
907908
);
909+
};
908910

909911
if (bind_this !== null) {
910912
const prev = fn;
911-
fn = (node_id) =>
912-
serialize_bind_this(
913+
914+
fn = (node_id) => {
915+
return serialize_bind_this(
913916
/** @type {import('estree').Identifier | import('estree').MemberExpression} */ (bind_this),
914917
context,
915918
prev(node_id)
916919
);
920+
};
917921
}
918922

919923
if (node.type === 'SvelteComponent') {
920924
const prev = fn;
925+
921926
fn = (node_id) => {
922-
let component = b.call(
927+
return b.call(
923928
'$.component',
924929
b.thunk(/** @type {import('estree').Expression} */ (context.visit(node.expression))),
925930
b.arrow(
@@ -933,31 +938,26 @@ function serialize_inline_component(node, component_name, context) {
933938
])
934939
)
935940
);
936-
return component;
937941
};
938942
}
939943

944+
const statements = [...snippet_declarations, ...binding_initializers];
945+
940946
if (Object.keys(custom_css_props).length > 0) {
941-
const prev = fn;
942-
fn = (node_id) =>
943-
b.call(
944-
'$.css_props',
945-
node_id,
946-
// TODO would be great to do this at runtime instead. Svelte 4 also can't handle cases today
947-
// where it's not statically determinable whether the component is used in a svg or html context
948-
context.state.metadata.namespace === 'svg' || context.state.metadata.namespace === 'mathml'
949-
? b.false
950-
: b.true,
951-
b.thunk(b.object(custom_css_props)),
952-
b.arrow([b.id('$$node')], prev(b.id('$$node')))
953-
);
954-
}
947+
context.state.template.push(
948+
context.state.metadata.namespace === 'svg'
949+
? '<g><!></g>'
950+
: '<div style="display: contents"><!></div>'
951+
);
955952

956-
const statements = [
957-
...snippet_declarations,
958-
...binding_initializers,
959-
b.stmt(fn(context.state.node))
960-
];
953+
statements.push(
954+
b.stmt(b.call('$.css_props', context.state.node, b.thunk(b.object(custom_css_props)))),
955+
b.stmt(fn(b.member(context.state.node, b.id('lastChild'))))
956+
);
957+
} else {
958+
context.state.template.push('<!>');
959+
statements.push(b.stmt(fn(context.state.node)));
960+
}
961961

962962
return statements.length > 1 ? b.block(statements) : statements[0];
963963
}
@@ -1337,10 +1337,7 @@ function process_children(nodes, expression, is_element, { visit, state }) {
13371337
b.assignment(
13381338
'=',
13391339
b.member(text_id, b.id('nodeValue')),
1340-
b.call(
1341-
'$.stringify',
1342-
/** @type {import('estree').Expression} */ (visit(node.expression))
1343-
)
1340+
/** @type {import('estree').Expression} */ (visit(node.expression))
13441341
)
13451342
)
13461343
);
@@ -1524,7 +1521,7 @@ function serialize_template_literal(values, visit, state) {
15241521
);
15251522
expressions.push(b.call('$.get', id));
15261523
} else {
1527-
expressions.push(b.call('$.stringify', visit(node.expression, state)));
1524+
expressions.push(b.logical('??', visit(node.expression, state), b.literal('')));
15281525
}
15291526
quasis.push(b.quasi('', i + 1 === values.length));
15301527
}
@@ -2947,8 +2944,6 @@ export const template_visitors = {
29472944
}
29482945
},
29492946
Component(node, context) {
2950-
context.state.template.push('<!>');
2951-
29522947
const binding = context.state.scope.get(
29532948
node.name.includes('.') ? node.name.slice(0, node.name.indexOf('.')) : node.name
29542949
);
@@ -2974,13 +2969,10 @@ export const template_visitors = {
29742969
context.state.init.push(component);
29752970
},
29762971
SvelteSelf(node, context) {
2977-
context.state.template.push('<!>');
29782972
const component = serialize_inline_component(node, context.state.analysis.name, context);
29792973
context.state.init.push(component);
29802974
},
29812975
SvelteComponent(node, context) {
2982-
context.state.template.push('<!>');
2983-
29842976
let component = serialize_inline_component(node, '$$component', context);
29852977

29862978
context.state.init.push(component);

packages/svelte/src/compiler/phases/3-transform/server/transform-server.js

Lines changed: 13 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -907,7 +907,6 @@ function serialize_element_spread_attributes(
907907
* @param {import('#compiler').Component | import('#compiler').SvelteComponent | import('#compiler').SvelteSelf} node
908908
* @param {string | import('estree').Expression} component_name
909909
* @param {import('./types').ComponentContext} context
910-
* @returns {import('estree').Statement}
911910
*/
912911
function serialize_inline_component(node, component_name, context) {
913912
/** @type {Array<import('estree').Property[] | import('estree').Expression>} */
@@ -1103,6 +1102,10 @@ function serialize_inline_component(node, component_name, context) {
11031102
)
11041103
);
11051104

1105+
if (snippet_declarations.length > 0) {
1106+
statement = b.block([...snippet_declarations, statement]);
1107+
}
1108+
11061109
if (custom_css_props.length > 0) {
11071110
statement = b.stmt(
11081111
b.call(
@@ -1113,13 +1116,13 @@ function serialize_inline_component(node, component_name, context) {
11131116
b.thunk(b.block([statement]))
11141117
)
11151118
);
1116-
}
11171119

1118-
if (snippet_declarations.length > 0) {
1119-
statement = b.block([...snippet_declarations, statement]);
1120+
context.state.template.push(t_statement(statement));
1121+
} else {
1122+
context.state.template.push(block_open);
1123+
context.state.template.push(t_statement(statement));
1124+
context.state.template.push(block_close);
11201125
}
1121-
1122-
return statement;
11231126
}
11241127

11251128
/**
@@ -1666,29 +1669,17 @@ const template_visitors = {
16661669
}
16671670
},
16681671
Component(node, context) {
1669-
const state = context.state;
1670-
state.template.push(block_open);
1671-
const call = serialize_inline_component(node, node.name, context);
1672-
state.template.push(t_statement(call));
1673-
state.template.push(block_close);
1672+
serialize_inline_component(node, node.name, context);
16741673
},
16751674
SvelteSelf(node, context) {
1676-
const state = context.state;
1677-
state.template.push(block_open);
1678-
const call = serialize_inline_component(node, context.state.analysis.name, context);
1679-
state.template.push(t_statement(call));
1680-
state.template.push(block_close);
1675+
serialize_inline_component(node, context.state.analysis.name, context);
16811676
},
16821677
SvelteComponent(node, context) {
1683-
const state = context.state;
1684-
state.template.push(block_open);
1685-
const call = serialize_inline_component(
1678+
serialize_inline_component(
16861679
node,
16871680
/** @type {import('estree').Expression} */ (context.visit(node.expression)),
16881681
context
16891682
);
1690-
state.template.push(t_statement(call));
1691-
state.template.push(block_close);
16921683
},
16931684
LetDirective(node, { state }) {
16941685
if (node.expression && node.expression.type !== 'Identifier') {
@@ -2068,7 +2059,7 @@ function serialize_element_attributes(node, context) {
20682059
);
20692060

20702061
context.state.template.push(
2071-
t_expression(b.call('$.attr', b.literal(name), value, b.literal(is_boolean)))
2062+
t_expression(b.call('$.attr', b.literal(name), value, is_boolean && b.literal(is_boolean)))
20722063
);
20732064
}
20742065
}

packages/svelte/src/internal/client/dom/blocks/css-props.js

Lines changed: 14 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1,64 +1,35 @@
1-
import { namespace_svg } from '../../../../constants.js';
2-
import { hydrate_anchor, hydrate_start, hydrating } from '../hydration.js';
3-
import { empty } from '../operations.js';
1+
import { hydrating, set_hydrate_nodes } from '../hydration.js';
42
import { render_effect } from '../../reactivity/effects.js';
53

64
/**
7-
* @param {Element | Text | Comment} anchor
8-
* @param {boolean} is_html
9-
* @param {() => Record<string, string>} props
10-
* @param {(anchor: Element | Text | Comment) => any} component
5+
* @param {HTMLDivElement | SVGGElement} element
6+
* @param {() => Record<string, string>} get_styles
117
* @returns {void}
128
*/
13-
export function css_props(anchor, is_html, props, component) {
14-
/** @type {HTMLElement | SVGElement} */
15-
let element;
16-
17-
/** @type {Text | Comment} */
18-
let component_anchor;
19-
9+
export function css_props(element, get_styles) {
2010
if (hydrating) {
21-
// Hydration: css props element is surrounded by a ssr comment ...
22-
element = /** @type {HTMLElement | SVGElement} */ (hydrate_start);
23-
// ... and the child(ren) of the css props element is also surround by a ssr comment
24-
component_anchor = /** @type {Comment} */ (
25-
hydrate_anchor(/** @type {Comment} */ (element.firstChild))
11+
set_hydrate_nodes(
12+
/** @type {import('#client').TemplateNode[]} */ ([...element.childNodes]).slice(0, -1)
2613
);
27-
} else {
28-
if (is_html) {
29-
element = document.createElement('div');
30-
element.style.display = 'contents';
31-
} else {
32-
element = document.createElementNS(namespace_svg, 'g');
33-
}
34-
35-
anchor.before(element);
36-
component_anchor = element.appendChild(empty());
3714
}
3815

39-
component(component_anchor);
40-
4116
render_effect(() => {
42-
/** @type {Record<string, string>} */
43-
let current_props = {};
44-
4517
render_effect(() => {
46-
const next_props = props();
18+
var styles = get_styles();
4719

48-
for (const key in current_props) {
49-
if (!(key in next_props)) {
20+
for (var key in styles) {
21+
var value = styles[key];
22+
23+
if (value) {
24+
element.style.setProperty(key, value);
25+
} else {
5026
element.style.removeProperty(key);
5127
}
5228
}
53-
54-
for (const key in next_props) {
55-
element.style.setProperty(key, next_props[key]);
56-
}
57-
58-
current_props = next_props;
5929
});
6030

6131
return () => {
32+
// TODO use `teardown` instead of creating a nested effect, post-https://github.com/sveltejs/svelte/pull/11936
6233
element.remove();
6334
};
6435
});

packages/svelte/src/internal/client/dom/elements/bindings/input.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import { DEV } from 'esm-env';
22
import { render_effect, teardown } from '../../../reactivity/effects.js';
3-
import { stringify } from '../../../render.js';
43
import { listen_to_event_and_reset_event } from './shared.js';
54
import * as e from '../../../errors.js';
65
import { get_proxied_value, is } from '../../../proxy.js';
@@ -44,7 +43,8 @@ export function bind_value(input, get_value, update) {
4443
return;
4544
}
4645

47-
input.value = stringify(value);
46+
// @ts-expect-error the value is coerced on assignment
47+
input.value = value ?? '';
4848
});
4949
}
5050

packages/svelte/src/internal/client/index.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,7 @@ export {
118118
update_pre_store,
119119
update_store
120120
} from './reactivity/store.js';
121-
export { append_styles, sanitize_slots, set_text, slot, stringify } from './render.js';
121+
export { append_styles, sanitize_slots, set_text, slot } from './render.js';
122122
export {
123123
get,
124124
invalidate_inner_signals,

packages/svelte/src/internal/client/render.js

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -66,14 +66,6 @@ export function slot(anchor, slot_fn, slot_props, fallback_fn) {
6666
}
6767
}
6868

69-
/**
70-
* @param {unknown} value
71-
* @returns {string}
72-
*/
73-
export function stringify(value) {
74-
return typeof value === 'string' ? value : value == null ? '' : value + '';
75-
}
76-
7769
/**
7870
* Mounts a component to the given target and returns the exports and potentially the props (if compiled with `accessors: true`) of the component
7971
*

0 commit comments

Comments
 (0)