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
pull/11966/head
Rich Harris 1 year ago committed by GitHub
parent 57ca37d6a1
commit 388f210183
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -0,0 +1,5 @@
---
'svelte': patch
---
feat: simpler hydration of CSS custom property wrappers

@ -896,30 +896,35 @@ function serialize_inline_component(node, component_name, context) {
'$.spread_props',
...props_and_spreads.map((p) => (Array.isArray(p) ? b.object(p) : p))
);
/** @param {import('estree').Identifier} node_id */
let fn = (node_id) =>
b.call(
/** @param {import('estree').Expression} node_id */
let fn = (node_id) => {
return b.call(
context.state.options.dev
? b.call('$.validate_component', b.id(component_name))
: component_name,
node_id,
props_expression
);
};
if (bind_this !== null) {
const prev = fn;
fn = (node_id) =>
serialize_bind_this(
fn = (node_id) => {
return serialize_bind_this(
/** @type {import('estree').Identifier | import('estree').MemberExpression} */ (bind_this),
context,
prev(node_id)
);
};
}
if (node.type === 'SvelteComponent') {
const prev = fn;
fn = (node_id) => {
let component = b.call(
return b.call(
'$.component',
b.thunk(/** @type {import('estree').Expression} */ (context.visit(node.expression))),
b.arrow(
@ -933,31 +938,26 @@ function serialize_inline_component(node, component_name, context) {
])
)
);
return component;
};
}
const statements = [...snippet_declarations, ...binding_initializers];
if (Object.keys(custom_css_props).length > 0) {
const prev = fn;
fn = (node_id) =>
b.call(
'$.css_props',
node_id,
// TODO would be great to do this at runtime instead. Svelte 4 also can't handle cases today
// where it's not statically determinable whether the component is used in a svg or html context
context.state.metadata.namespace === 'svg' || context.state.metadata.namespace === 'mathml'
? b.false
: b.true,
b.thunk(b.object(custom_css_props)),
b.arrow([b.id('$$node')], prev(b.id('$$node')))
);
}
context.state.template.push(
context.state.metadata.namespace === 'svg'
? '<g><!></g>'
: '<div style="display: contents"><!></div>'
);
const statements = [
...snippet_declarations,
...binding_initializers,
b.stmt(fn(context.state.node))
];
statements.push(
b.stmt(b.call('$.css_props', context.state.node, b.thunk(b.object(custom_css_props)))),
b.stmt(fn(b.member(context.state.node, b.id('lastChild'))))
);
} else {
context.state.template.push('<!>');
statements.push(b.stmt(fn(context.state.node)));
}
return statements.length > 1 ? b.block(statements) : statements[0];
}
@ -2947,8 +2947,6 @@ export const template_visitors = {
}
},
Component(node, context) {
context.state.template.push('<!>');
const binding = context.state.scope.get(
node.name.includes('.') ? node.name.slice(0, node.name.indexOf('.')) : node.name
);
@ -2974,13 +2972,10 @@ export const template_visitors = {
context.state.init.push(component);
},
SvelteSelf(node, context) {
context.state.template.push('<!>');
const component = serialize_inline_component(node, context.state.analysis.name, context);
context.state.init.push(component);
},
SvelteComponent(node, context) {
context.state.template.push('<!>');
let component = serialize_inline_component(node, '$$component', context);
context.state.init.push(component);

@ -907,7 +907,6 @@ function serialize_element_spread_attributes(
* @param {import('#compiler').Component | import('#compiler').SvelteComponent | import('#compiler').SvelteSelf} node
* @param {string | import('estree').Expression} component_name
* @param {import('./types').ComponentContext} context
* @returns {import('estree').Statement}
*/
function serialize_inline_component(node, component_name, context) {
/** @type {Array<import('estree').Property[] | import('estree').Expression>} */
@ -1103,6 +1102,10 @@ function serialize_inline_component(node, component_name, context) {
)
);
if (snippet_declarations.length > 0) {
statement = b.block([...snippet_declarations, statement]);
}
if (custom_css_props.length > 0) {
statement = b.stmt(
b.call(
@ -1113,13 +1116,13 @@ function serialize_inline_component(node, component_name, context) {
b.thunk(b.block([statement]))
)
);
}
if (snippet_declarations.length > 0) {
statement = b.block([...snippet_declarations, statement]);
context.state.template.push(t_statement(statement));
} else {
context.state.template.push(block_open);
context.state.template.push(t_statement(statement));
context.state.template.push(block_close);
}
return statement;
}
/**
@ -1666,29 +1669,17 @@ const template_visitors = {
}
},
Component(node, context) {
const state = context.state;
state.template.push(block_open);
const call = serialize_inline_component(node, node.name, context);
state.template.push(t_statement(call));
state.template.push(block_close);
serialize_inline_component(node, node.name, context);
},
SvelteSelf(node, context) {
const state = context.state;
state.template.push(block_open);
const call = serialize_inline_component(node, context.state.analysis.name, context);
state.template.push(t_statement(call));
state.template.push(block_close);
serialize_inline_component(node, context.state.analysis.name, context);
},
SvelteComponent(node, context) {
const state = context.state;
state.template.push(block_open);
const call = serialize_inline_component(
serialize_inline_component(
node,
/** @type {import('estree').Expression} */ (context.visit(node.expression)),
context
);
state.template.push(t_statement(call));
state.template.push(block_close);
},
LetDirective(node, { state }) {
if (node.expression && node.expression.type !== 'Identifier') {

@ -1,64 +1,35 @@
import { namespace_svg } from '../../../../constants.js';
import { hydrate_anchor, hydrate_start, hydrating } from '../hydration.js';
import { empty } from '../operations.js';
import { hydrating, set_hydrate_nodes } from '../hydration.js';
import { render_effect } from '../../reactivity/effects.js';
/**
* @param {Element | Text | Comment} anchor
* @param {boolean} is_html
* @param {() => Record<string, string>} props
* @param {(anchor: Element | Text | Comment) => any} component
* @param {HTMLDivElement | SVGGElement} element
* @param {() => Record<string, string>} get_styles
* @returns {void}
*/
export function css_props(anchor, is_html, props, component) {
/** @type {HTMLElement | SVGElement} */
let element;
/** @type {Text | Comment} */
let component_anchor;
export function css_props(element, get_styles) {
if (hydrating) {
// Hydration: css props element is surrounded by a ssr comment ...
element = /** @type {HTMLElement | SVGElement} */ (hydrate_start);
// ... and the child(ren) of the css props element is also surround by a ssr comment
component_anchor = /** @type {Comment} */ (
hydrate_anchor(/** @type {Comment} */ (element.firstChild))
set_hydrate_nodes(
/** @type {import('#client').TemplateNode[]} */ ([...element.childNodes]).slice(0, -1)
);
} else {
if (is_html) {
element = document.createElement('div');
element.style.display = 'contents';
} else {
element = document.createElementNS(namespace_svg, 'g');
}
anchor.before(element);
component_anchor = element.appendChild(empty());
}
component(component_anchor);
render_effect(() => {
/** @type {Record<string, string>} */
let current_props = {};
render_effect(() => {
const next_props = props();
var styles = get_styles();
for (const key in current_props) {
if (!(key in next_props)) {
for (var key in styles) {
var value = styles[key];
if (value) {
element.style.setProperty(key, value);
} else {
element.style.removeProperty(key);
}
}
for (const key in next_props) {
element.style.setProperty(key, next_props[key]);
}
current_props = next_props;
});
return () => {
// TODO use `teardown` instead of creating a nested effect, post-https://github.com/sveltejs/svelte/pull/11936
element.remove();
};
});

@ -162,15 +162,15 @@ export function attr(name, value, boolean) {
export function css_props(payload, is_html, props, component) {
const styles = style_object_to_string(props);
if (is_html) {
payload.out += `<div style="display: contents; ${styles}"><!--[-->`;
payload.out += `<div style="display: contents; ${styles}">`;
} else {
payload.out += `<g style="${styles}"><!--[-->`;
payload.out += `<g style="${styles}">`;
}
component();
if (is_html) {
payload.out += `<!--]--></div>`;
payload.out += `<!----></div>`;
} else {
payload.out += `<!--]--></g>`;
payload.out += `<!----></g>`;
}
}

Loading…
Cancel
Save