diff --git a/.changeset/silly-mammals-fold.md b/.changeset/silly-mammals-fold.md
new file mode 100644
index 0000000000..3513b4339e
--- /dev/null
+++ b/.changeset/silly-mammals-fold.md
@@ -0,0 +1,5 @@
+---
+'svelte': patch
+---
+
+fix: repair dynamic component truthy/falsy hydration mismatches
diff --git a/packages/svelte/src/compiler/phases/3-transform/server/visitors/shared/component.js b/packages/svelte/src/compiler/phases/3-transform/server/visitors/shared/component.js
index 6a2c6eb0be..b0e086b7ad 100644
--- a/packages/svelte/src/compiler/phases/3-transform/server/visitors/shared/component.js
+++ b/packages/svelte/src/compiler/phases/3-transform/server/visitors/shared/component.js
@@ -1,7 +1,14 @@
/** @import { BlockStatement, Expression, Pattern, Property, SequenceExpression, Statement } from 'estree' */
/** @import { AST } from '#compiler' */
/** @import { ComponentContext } from '../../types.js' */
-import { empty_comment, build_attribute_value, PromiseOptimiser } from './utils.js';
+import {
+ empty_comment,
+ build_attribute_value,
+ PromiseOptimiser,
+ block_open_else,
+ block_open,
+ block_close
+} from './utils.js';
import * as b from '#compiler/builders';
import { is_element_node } from '../../../../nodes.js';
import { dev } from '../../../../../state.js';
@@ -300,9 +307,22 @@ export function build_inline_component(node, expression, context) {
node.type === 'SvelteComponent' || (node.type === 'Component' && node.metadata.dynamic);
/** @type {Statement} */
- let statement = b.stmt(
- (dynamic ? b.maybe_call : b.call)(expression, b.id('$$renderer'), props_expression)
- );
+ let statement = b.stmt(b.call(expression, b.id('$$renderer'), props_expression));
+
+ if (dynamic) {
+ statement = b.if(
+ expression,
+ b.block([
+ b.stmt(b.call('$$renderer.push', block_open)),
+ statement,
+ b.stmt(b.call('$$renderer.push', block_close))
+ ]),
+ b.block([
+ b.stmt(b.call('$$renderer.push', block_open_else)),
+ b.stmt(b.call('$$renderer.push', block_close))
+ ])
+ );
+ }
if (snippet_declarations.length > 0) {
statement = b.block([...snippet_declarations, statement]);
@@ -326,16 +346,14 @@ export function build_inline_component(node, expression, context) {
optimiser.check_blockers(node.metadata.expression);
}
- context.state.template.push(
- ...optimiser.render_block([
- dynamic && custom_css_props.length === 0
- ? b.stmt(b.call('$$renderer.push', empty_comment))
- : b.empty,
- statement
- ])
- );
+ context.state.template.push(...optimiser.render_block([statement]));
- if (!optimiser.is_async() && !context.state.is_standalone && custom_css_props.length === 0) {
+ if (
+ !dynamic &&
+ !optimiser.is_async() &&
+ !context.state.is_standalone &&
+ custom_css_props.length === 0
+ ) {
context.state.template.push(empty_comment);
}
}
diff --git a/packages/svelte/src/internal/client/dom/blocks/svelte-component.js b/packages/svelte/src/internal/client/dom/blocks/svelte-component.js
index 134e57e627..0e9434386c 100644
--- a/packages/svelte/src/internal/client/dom/blocks/svelte-component.js
+++ b/packages/svelte/src/internal/client/dom/blocks/svelte-component.js
@@ -1,8 +1,17 @@
/** @import { TemplateNode, Dom } from '#client' */
import { EFFECT_TRANSPARENT } from '#client/constants';
import { block } from '../../reactivity/effects.js';
-import { hydrate_next, hydrating } from '../hydration.js';
+import {
+ hydrate_next,
+ hydrate_node,
+ hydrating,
+ read_hydration_instruction,
+ set_hydrate_node,
+ set_hydrating,
+ skip_nodes
+} from '../hydration.js';
import { BranchManager } from './branches.js';
+import { HYDRATION_START, HYDRATION_START_ELSE } from '../../../../constants.js';
/**
* @template P
@@ -13,7 +22,11 @@ import { BranchManager } from './branches.js';
* @returns {void}
*/
export function component(node, get_component, render_fn) {
+ /** @type {TemplateNode | undefined} */
+ var hydration_start_node;
+
if (hydrating) {
+ hydration_start_node = hydrate_node;
hydrate_next();
}
@@ -21,6 +34,28 @@ export function component(node, get_component, render_fn) {
block(() => {
var component = get_component() ?? null;
+
+ if (hydrating) {
+ var data = read_hydration_instruction(/** @type {TemplateNode} */ (hydration_start_node));
+
+ var server_had_component = data === HYDRATION_START;
+ var client_has_component = component !== null;
+
+ if (server_had_component !== client_has_component) {
+ // Hydration mismatch: skip the server-rendered nodes and render fresh
+ var anchor = skip_nodes();
+
+ set_hydrate_node(anchor);
+ branches.anchor = anchor;
+
+ set_hydrating(false);
+ branches.ensure(component, component && ((target) => render_fn(target, component)));
+ set_hydrating(true);
+
+ return;
+ }
+ }
+
branches.ensure(component, component && ((target) => render_fn(target, component)));
}, EFFECT_TRANSPARENT);
}
diff --git a/packages/svelte/src/internal/server/index.js b/packages/svelte/src/internal/server/index.js
index 0864c7704d..6ab32a0d6e 100644
--- a/packages/svelte/src/internal/server/index.js
+++ b/packages/svelte/src/internal/server/index.js
@@ -105,16 +105,16 @@ export function css_props(renderer, is_html, props, component, dynamic = false)
renderer.push(`