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', '$.spread_props',
...props_and_spreads.map((p) => (Array.isArray(p) ? b.object(p) : p)) ...props_and_spreads.map((p) => (Array.isArray(p) ? b.object(p) : p))
); );
/** @param {import('estree').Identifier} node_id */
let fn = (node_id) => /** @param {import('estree').Expression} node_id */
b.call( let fn = (node_id) => {
return b.call(
context.state.options.dev context.state.options.dev
? b.call('$.validate_component', b.id(component_name)) ? b.call('$.validate_component', b.id(component_name))
: component_name, : component_name,
node_id, node_id,
props_expression props_expression
); );
};
if (bind_this !== null) { if (bind_this !== null) {
const prev = fn; 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), /** @type {import('estree').Identifier | import('estree').MemberExpression} */ (bind_this),
context, context,
prev(node_id) prev(node_id)
); );
};
} }
if (node.type === 'SvelteComponent') { if (node.type === 'SvelteComponent') {
const prev = fn; const prev = fn;
fn = (node_id) => { fn = (node_id) => {
let component = b.call( return b.call(
'$.component', '$.component',
b.thunk(/** @type {import('estree').Expression} */ (context.visit(node.expression))), b.thunk(/** @type {import('estree').Expression} */ (context.visit(node.expression))),
b.arrow( 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) { if (Object.keys(custom_css_props).length > 0) {
const prev = fn; context.state.template.push(
fn = (node_id) => context.state.metadata.namespace === 'svg'
b.call( ? '<g><!></g>'
'$.css_props', : '<div style="display: contents"><!></div>'
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')))
);
}
const statements = [ statements.push(
...snippet_declarations, b.stmt(b.call('$.css_props', context.state.node, b.thunk(b.object(custom_css_props)))),
...binding_initializers, b.stmt(fn(b.member(context.state.node, b.id('lastChild'))))
b.stmt(fn(context.state.node)) );
]; } else {
context.state.template.push('<!>');
statements.push(b.stmt(fn(context.state.node)));
}
return statements.length > 1 ? b.block(statements) : statements[0]; return statements.length > 1 ? b.block(statements) : statements[0];
} }
@ -2947,8 +2947,6 @@ export const template_visitors = {
} }
}, },
Component(node, context) { Component(node, context) {
context.state.template.push('<!>');
const binding = context.state.scope.get( const binding = context.state.scope.get(
node.name.includes('.') ? node.name.slice(0, node.name.indexOf('.')) : node.name 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); context.state.init.push(component);
}, },
SvelteSelf(node, context) { SvelteSelf(node, context) {
context.state.template.push('<!>');
const component = serialize_inline_component(node, context.state.analysis.name, context); const component = serialize_inline_component(node, context.state.analysis.name, context);
context.state.init.push(component); context.state.init.push(component);
}, },
SvelteComponent(node, context) { SvelteComponent(node, context) {
context.state.template.push('<!>');
let component = serialize_inline_component(node, '$$component', context); let component = serialize_inline_component(node, '$$component', context);
context.state.init.push(component); 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 {import('#compiler').Component | import('#compiler').SvelteComponent | import('#compiler').SvelteSelf} node
* @param {string | import('estree').Expression} component_name * @param {string | import('estree').Expression} component_name
* @param {import('./types').ComponentContext} context * @param {import('./types').ComponentContext} context
* @returns {import('estree').Statement}
*/ */
function serialize_inline_component(node, component_name, context) { function serialize_inline_component(node, component_name, context) {
/** @type {Array<import('estree').Property[] | import('estree').Expression>} */ /** @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) { if (custom_css_props.length > 0) {
statement = b.stmt( statement = b.stmt(
b.call( b.call(
@ -1113,13 +1116,13 @@ function serialize_inline_component(node, component_name, context) {
b.thunk(b.block([statement])) b.thunk(b.block([statement]))
) )
); );
}
if (snippet_declarations.length > 0) { context.state.template.push(t_statement(statement));
statement = b.block([...snippet_declarations, 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) { Component(node, context) {
const state = context.state; serialize_inline_component(node, node.name, context);
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);
}, },
SvelteSelf(node, context) { SvelteSelf(node, context) {
const state = context.state; serialize_inline_component(node, context.state.analysis.name, context);
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);
}, },
SvelteComponent(node, context) { SvelteComponent(node, context) {
const state = context.state; serialize_inline_component(
state.template.push(block_open);
const call = serialize_inline_component(
node, node,
/** @type {import('estree').Expression} */ (context.visit(node.expression)), /** @type {import('estree').Expression} */ (context.visit(node.expression)),
context context
); );
state.template.push(t_statement(call));
state.template.push(block_close);
}, },
LetDirective(node, { state }) { LetDirective(node, { state }) {
if (node.expression && node.expression.type !== 'Identifier') { if (node.expression && node.expression.type !== 'Identifier') {

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

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

Loading…
Cancel
Save