From 152c28a0c84b0b232faeece4ce42c8dab7ef09ed Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Thu, 6 Jun 2024 18:48:38 -0400 Subject: [PATCH 1/8] simplify --- .../3-transform/server/transform-server.js | 22 +++++-------------- 1 file changed, 6 insertions(+), 16 deletions(-) diff --git a/packages/svelte/src/compiler/phases/3-transform/server/transform-server.js b/packages/svelte/src/compiler/phases/3-transform/server/transform-server.js index 6deb02385ff4..337f66be98d5 100644 --- a/packages/svelte/src/compiler/phases/3-transform/server/transform-server.js +++ b/packages/svelte/src/compiler/phases/3-transform/server/transform-server.js @@ -1119,7 +1119,9 @@ function serialize_inline_component(node, component_name, context) { statement = b.block([...snippet_declarations, statement]); } - return statement; + context.state.template.push(block_open); + context.state.template.push(t_statement(statement)); + context.state.template.push(block_close); } /** @@ -1666,29 +1668,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') { From 473d011e28d06ca795ff32a1dac946a7fac2995d Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Thu, 6 Jun 2024 21:39:22 -0400 Subject: [PATCH 2/8] DRY out --- .../phases/3-transform/client/visitors/template.js | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/packages/svelte/src/compiler/phases/3-transform/client/visitors/template.js b/packages/svelte/src/compiler/phases/3-transform/client/visitors/template.js index 519423a5cec5..6cf2876bbf61 100644 --- a/packages/svelte/src/compiler/phases/3-transform/client/visitors/template.js +++ b/packages/svelte/src/compiler/phases/3-transform/client/visitors/template.js @@ -937,6 +937,9 @@ function serialize_inline_component(node, component_name, context) { }; } + // TODO for CSS prop wrappers, push `
` instead + context.state.template.push(''); + if (Object.keys(custom_css_props).length > 0) { const prev = fn; fn = (node_id) => @@ -2947,8 +2950,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 +2975,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); From 3c8af457db3a0a43ba1db9e840259ef6a8c3a6f2 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Thu, 6 Jun 2024 22:26:32 -0400 Subject: [PATCH 3/8] simplify --- .../3-transform/client/visitors/template.js | 29 +++++++------ .../3-transform/server/transform-server.js | 16 ++++---- .../internal/client/dom/blocks/css-props.js | 41 +++++-------------- packages/svelte/src/internal/server/index.js | 8 ++-- 4 files changed, 39 insertions(+), 55 deletions(-) diff --git a/packages/svelte/src/compiler/phases/3-transform/client/visitors/template.js b/packages/svelte/src/compiler/phases/3-transform/client/visitors/template.js index 6cf2876bbf61..18aa9506bd19 100644 --- a/packages/svelte/src/compiler/phases/3-transform/client/visitors/template.js +++ b/packages/svelte/src/compiler/phases/3-transform/client/visitors/template.js @@ -937,25 +937,28 @@ function serialize_inline_component(node, component_name, context) { }; } - // TODO for CSS prop wrappers, push `
` instead - context.state.template.push(''); - if (Object.keys(custom_css_props).length > 0) { - const prev = fn; - fn = (node_id) => + context.state.template.push( + context.state.metadata.namespace === 'svg' + ? '' + : '
' + ); + + return b.stmt( 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, + context.state.node, b.thunk(b.object(custom_css_props)), - b.arrow([b.id('$$node')], prev(b.id('$$node'))) - ); + b.arrow( + [b.id('$$node')], + b.block([...snippet_declarations, ...binding_initializers, b.stmt(fn(b.id('$$node')))]) + ) + ) + ); } + context.state.template.push(''); + const statements = [ ...snippet_declarations, ...binding_initializers, diff --git a/packages/svelte/src/compiler/phases/3-transform/server/transform-server.js b/packages/svelte/src/compiler/phases/3-transform/server/transform-server.js index 337f66be98d5..2b0be98a7395 100644 --- a/packages/svelte/src/compiler/phases/3-transform/server/transform-server.js +++ b/packages/svelte/src/compiler/phases/3-transform/server/transform-server.js @@ -1103,6 +1103,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,15 +1117,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); } - - context.state.template.push(block_open); - context.state.template.push(t_statement(statement)); - context.state.template.push(block_close); } /** diff --git a/packages/svelte/src/internal/client/dom/blocks/css-props.js b/packages/svelte/src/internal/client/dom/blocks/css-props.js index 042b6aa9e58f..ab8971da2ade 100644 --- a/packages/svelte/src/internal/client/dom/blocks/css-props.js +++ b/packages/svelte/src/internal/client/dom/blocks/css-props.js @@ -1,47 +1,25 @@ -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 {HTMLDivElement | SVGGElement} element * @param {() => Record} props * @param {(anchor: Element | Text | Comment) => any} component * @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, props, component) { 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); + component(/** @type {Comment} */ (element.lastChild)); - render_effect(() => { - /** @type {Record} */ - let current_props = {}; + /** @type {Record} */ + let current_props = {}; + render_effect(() => { render_effect(() => { const next_props = props(); @@ -59,6 +37,7 @@ export function css_props(anchor, is_html, props, component) { }); return () => { + // TODO use `teardown` instead of creating a nested effect, post-https://github.com/sveltejs/svelte/pull/11936 element.remove(); }; }); diff --git a/packages/svelte/src/internal/server/index.js b/packages/svelte/src/internal/server/index.js index 3b6a0158b351..7f7b0ebe9849 100644 --- a/packages/svelte/src/internal/server/index.js +++ b/packages/svelte/src/internal/server/index.js @@ -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 += `
`; + payload.out += `
`; } else { - payload.out += ``; + payload.out += ``; } component(); if (is_html) { - payload.out += `
`; + payload.out += `
`; } else { - payload.out += ``; + payload.out += ``; } } From 31b50dfde0ad50725492bdf33f98effd7e208049 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Thu, 6 Jun 2024 22:27:07 -0400 Subject: [PATCH 4/8] changeset --- .changeset/funny-dragons-double.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/funny-dragons-double.md diff --git a/.changeset/funny-dragons-double.md b/.changeset/funny-dragons-double.md new file mode 100644 index 000000000000..6a539769de16 --- /dev/null +++ b/.changeset/funny-dragons-double.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +feat: simpler hydration of CSS custom property wrappers From 18e4dec3037adfa10a756d953b21c3dc506e923f Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Thu, 6 Jun 2024 22:31:47 -0400 Subject: [PATCH 5/8] rename --- .../internal/client/dom/blocks/css-props.js | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/packages/svelte/src/internal/client/dom/blocks/css-props.js b/packages/svelte/src/internal/client/dom/blocks/css-props.js index ab8971da2ade..c67398e83b54 100644 --- a/packages/svelte/src/internal/client/dom/blocks/css-props.js +++ b/packages/svelte/src/internal/client/dom/blocks/css-props.js @@ -3,11 +3,11 @@ import { render_effect } from '../../reactivity/effects.js'; /** * @param {HTMLDivElement | SVGGElement} element - * @param {() => Record} props + * @param {() => Record} get_styles * @param {(anchor: Element | Text | Comment) => any} component * @returns {void} */ -export function css_props(element, props, component) { +export function css_props(element, get_styles, component) { if (hydrating) { set_hydrate_nodes( /** @type {import('#client').TemplateNode[]} */ ([...element.childNodes]).slice(0, -1) @@ -17,23 +17,23 @@ export function css_props(element, props, component) { component(/** @type {Comment} */ (element.lastChild)); /** @type {Record} */ - let current_props = {}; + let styles = {}; render_effect(() => { render_effect(() => { - const next_props = props(); + const next = get_styles(); - for (const key in current_props) { - if (!(key in next_props)) { + for (const key in styles) { + if (!(key in next)) { element.style.removeProperty(key); } } - for (const key in next_props) { - element.style.setProperty(key, next_props[key]); + for (const key in next) { + element.style.setProperty(key, next[key]); } - current_props = next_props; + styles = next; }); return () => { From 57999d339f2c37cf5ec8dc629bbd5af6656ce2ca Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Thu, 6 Jun 2024 22:34:10 -0400 Subject: [PATCH 6/8] simplify --- .../internal/client/dom/blocks/css-props.js | 19 +++++++------------ 1 file changed, 7 insertions(+), 12 deletions(-) diff --git a/packages/svelte/src/internal/client/dom/blocks/css-props.js b/packages/svelte/src/internal/client/dom/blocks/css-props.js index c67398e83b54..545c3b51f7c0 100644 --- a/packages/svelte/src/internal/client/dom/blocks/css-props.js +++ b/packages/svelte/src/internal/client/dom/blocks/css-props.js @@ -16,24 +16,19 @@ export function css_props(element, get_styles, component) { component(/** @type {Comment} */ (element.lastChild)); - /** @type {Record} */ - let styles = {}; - render_effect(() => { render_effect(() => { - const next = get_styles(); + var styles = get_styles(); + + for (var key in styles) { + var value = styles[key]; - for (const key in styles) { - if (!(key in next)) { + if (value) { + element.style.setProperty(key, value); + } else { element.style.removeProperty(key); } } - - for (const key in next) { - element.style.setProperty(key, next[key]); - } - - styles = next; }); return () => { From d45a62ada995215de92a6a30749d843ec01ce0b2 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Thu, 6 Jun 2024 22:44:45 -0400 Subject: [PATCH 7/8] we don't actually need the function, we can flatten it. more efficient --- .../3-transform/client/visitors/template.js | 28 ++++++------------- .../3-transform/server/transform-server.js | 1 - .../internal/client/dom/blocks/css-props.js | 5 +--- 3 files changed, 10 insertions(+), 24 deletions(-) diff --git a/packages/svelte/src/compiler/phases/3-transform/client/visitors/template.js b/packages/svelte/src/compiler/phases/3-transform/client/visitors/template.js index 18aa9506bd19..b9ea81dde73a 100644 --- a/packages/svelte/src/compiler/phases/3-transform/client/visitors/template.js +++ b/packages/svelte/src/compiler/phases/3-transform/client/visitors/template.js @@ -896,7 +896,7 @@ 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 */ + /** @param {import('estree').Expression} node_id */ let fn = (node_id) => b.call( context.state.options.dev @@ -937,6 +937,8 @@ function serialize_inline_component(node, component_name, context) { }; } + const statements = [...snippet_declarations, ...binding_initializers]; + if (Object.keys(custom_css_props).length > 0) { context.state.template.push( context.state.metadata.namespace === 'svg' @@ -944,27 +946,15 @@ function serialize_inline_component(node, component_name, context) { : '
' ); - return b.stmt( - b.call( - '$.css_props', - context.state.node, - b.thunk(b.object(custom_css_props)), - b.arrow( - [b.id('$$node')], - b.block([...snippet_declarations, ...binding_initializers, b.stmt(fn(b.id('$$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))); } - context.state.template.push(''); - - const statements = [ - ...snippet_declarations, - ...binding_initializers, - b.stmt(fn(context.state.node)) - ]; - return statements.length > 1 ? b.block(statements) : statements[0]; } diff --git a/packages/svelte/src/compiler/phases/3-transform/server/transform-server.js b/packages/svelte/src/compiler/phases/3-transform/server/transform-server.js index 2b0be98a7395..eb04cd9397d3 100644 --- a/packages/svelte/src/compiler/phases/3-transform/server/transform-server.js +++ b/packages/svelte/src/compiler/phases/3-transform/server/transform-server.js @@ -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} */ diff --git a/packages/svelte/src/internal/client/dom/blocks/css-props.js b/packages/svelte/src/internal/client/dom/blocks/css-props.js index 545c3b51f7c0..b2534a46525c 100644 --- a/packages/svelte/src/internal/client/dom/blocks/css-props.js +++ b/packages/svelte/src/internal/client/dom/blocks/css-props.js @@ -4,18 +4,15 @@ import { render_effect } from '../../reactivity/effects.js'; /** * @param {HTMLDivElement | SVGGElement} element * @param {() => Record} get_styles - * @param {(anchor: Element | Text | Comment) => any} component * @returns {void} */ -export function css_props(element, get_styles, component) { +export function css_props(element, get_styles) { if (hydrating) { set_hydrate_nodes( /** @type {import('#client').TemplateNode[]} */ ([...element.childNodes]).slice(0, -1) ); } - component(/** @type {Comment} */ (element.lastChild)); - render_effect(() => { render_effect(() => { var styles = get_styles(); From bb591c9418818520579b8071c3bffe6096713328 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Thu, 6 Jun 2024 22:47:01 -0400 Subject: [PATCH 8/8] tidy up --- .../3-transform/client/visitors/template.js | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/packages/svelte/src/compiler/phases/3-transform/client/visitors/template.js b/packages/svelte/src/compiler/phases/3-transform/client/visitors/template.js index b9ea81dde73a..fa96cfe29679 100644 --- a/packages/svelte/src/compiler/phases/3-transform/client/visitors/template.js +++ b/packages/svelte/src/compiler/phases/3-transform/client/visitors/template.js @@ -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').Expression} node_id */ - let fn = (node_id) => - b.call( + 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,7 +938,6 @@ function serialize_inline_component(node, component_name, context) { ]) ) ); - return component; }; }