diff --git a/.changeset/fuzzy-taxis-hunt.md b/.changeset/fuzzy-taxis-hunt.md new file mode 100644 index 0000000000..5deda96ce5 --- /dev/null +++ b/.changeset/fuzzy-taxis-hunt.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: falsy attachments on components diff --git a/packages/svelte/src/compiler/phases/3-transform/client/visitors/shared/component.js b/packages/svelte/src/compiler/phases/3-transform/client/visitors/shared/component.js index 57402c2b4a..ff98d6d378 100644 --- a/packages/svelte/src/compiler/phases/3-transform/client/visitors/shared/component.js +++ b/packages/svelte/src/compiler/phases/3-transform/client/visitors/shared/component.js @@ -258,10 +258,18 @@ export function build_component(node, component_name, context, anchor = context. } } } else if (attribute.type === 'AttachTag') { + const evaluated = context.state.scope.evaluate(attribute.expression); + let expression = /** @type {Expression} */ (context.visit(attribute.expression)); if (attribute.metadata.expression.has_state) { - expression = b.arrow([b.id('$$node')], b.call(expression, b.id('$$node'))); + expression = b.arrow( + [b.id('$$node')], + b.call( + evaluated.is_function ? expression : b.logical('||', expression, b.id('$.noop')), + b.id('$$node') + ) + ); } push_prop(b.prop('get', b.call('$.attachment'), expression, true)); diff --git a/packages/svelte/src/compiler/phases/scope.js b/packages/svelte/src/compiler/phases/scope.js index 75a26d487b..b4cdb6b446 100644 --- a/packages/svelte/src/compiler/phases/scope.js +++ b/packages/svelte/src/compiler/phases/scope.js @@ -20,6 +20,7 @@ const UNKNOWN = Symbol('unknown'); /** Includes `BigInt` */ export const NUMBER = Symbol('number'); export const STRING = Symbol('string'); +export const FUNCTION = Symbol('string'); /** @type {Record} */ const globals = { @@ -200,6 +201,13 @@ class Evaluation { */ is_number = true; + /** + * True if the value is known to be a function + * @readonly + * @type {boolean} + */ + is_function = true; + /** * @readonly * @type {any} @@ -209,7 +217,7 @@ class Evaluation { /** * * @param {Scope} scope - * @param {Expression} expression + * @param {Expression | FunctionDeclaration} expression * @param {Set} values */ constructor(scope, expression, values) { @@ -500,6 +508,13 @@ class Evaluation { break; } + case 'ArrowFunctionExpression': + case 'FunctionExpression': + case 'FunctionDeclaration': { + this.values.add(FUNCTION); + break; + } + default: { this.values.add(UNKNOWN); } @@ -516,6 +531,10 @@ class Evaluation { this.is_number = false; } + if (value !== FUNCTION) { + this.is_function = false; + } + if (value == null || value === UNKNOWN) { this.is_defined = false; } diff --git a/packages/svelte/tests/runtime-runes/samples/attachment-component-falsy/Child.svelte b/packages/svelte/tests/runtime-runes/samples/attachment-component-falsy/Child.svelte new file mode 100644 index 0000000000..6760da61fa --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/attachment-component-falsy/Child.svelte @@ -0,0 +1,5 @@ + + +
diff --git a/packages/svelte/tests/runtime-runes/samples/attachment-component-falsy/_config.js b/packages/svelte/tests/runtime-runes/samples/attachment-component-falsy/_config.js new file mode 100644 index 0000000000..27f013d6d1 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/attachment-component-falsy/_config.js @@ -0,0 +1,5 @@ +import { test } from '../../test'; + +export default test({ + test() {} +}); diff --git a/packages/svelte/tests/runtime-runes/samples/attachment-component-falsy/main.svelte b/packages/svelte/tests/runtime-runes/samples/attachment-component-falsy/main.svelte new file mode 100644 index 0000000000..993bcdd6a5 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/attachment-component-falsy/main.svelte @@ -0,0 +1,13 @@ + + + + +