Skip to content

Commit 388f210

Browse files
authored
feat: simpler hydration of CSS custom property wrappers (#11948)
* simplify * DRY out * simplify * changeset * rename * simplify * we don't actually need the function, we can flatten it. more efficient * tidy up
1 parent 57ca37d commit 388f210

File tree

5 files changed

+61
-99
lines changed

5 files changed

+61
-99
lines changed

.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

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

Lines changed: 26 additions & 31 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
}
@@ -2947,8 +2947,6 @@ export const template_visitors = {
29472947
}
29482948
},
29492949
Component(node, context) {
2950-
context.state.template.push('<!>');
2951-
29522950
const binding = context.state.scope.get(
29532951
node.name.includes('.') ? node.name.slice(0, node.name.indexOf('.')) : node.name
29542952
);
@@ -2974,13 +2972,10 @@ export const template_visitors = {
29742972
context.state.init.push(component);
29752973
},
29762974
SvelteSelf(node, context) {
2977-
context.state.template.push('<!>');
29782975
const component = serialize_inline_component(node, context.state.analysis.name, context);
29792976
context.state.init.push(component);
29802977
},
29812978
SvelteComponent(node, context) {
2982-
context.state.template.push('<!>');
2983-
29842979
let component = serialize_inline_component(node, '$$component', context);
29852980

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

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

Lines changed: 12 additions & 21 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') {

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/server/index.js

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -162,15 +162,15 @@ export function attr(name, value, boolean) {
162162
export function css_props(payload, is_html, props, component) {
163163
const styles = style_object_to_string(props);
164164
if (is_html) {
165-
payload.out += `<div style="display: contents; ${styles}"><!--[-->`;
165+
payload.out += `<div style="display: contents; ${styles}">`;
166166
} else {
167-
payload.out += `<g style="${styles}"><!--[-->`;
167+
payload.out += `<g style="${styles}">`;
168168
}
169169
component();
170170
if (is_html) {
171-
payload.out += `<!--]--></div>`;
171+
payload.out += `<!----></div>`;
172172
} else {
173-
payload.out += `<!--]--></g>`;
173+
payload.out += `<!----></g>`;
174174
}
175175
}
176176

0 commit comments

Comments
 (0)