diff --git a/.changeset/red-feet-worry.md b/.changeset/red-feet-worry.md new file mode 100644 index 000000000000..00caaf81582e --- /dev/null +++ b/.changeset/red-feet-worry.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +feat: allow state/derived/props to be explicitly exported from components diff --git a/packages/svelte/src/compiler/errors.js b/packages/svelte/src/compiler/errors.js index 050e7ec43283..f6f70ec3e39e 100644 --- a/packages/svelte/src/compiler/errors.js +++ b/packages/svelte/src/compiler/errors.js @@ -171,11 +171,9 @@ const runes = { /** @param {string} rune */ 'invalid-rune-usage': (rune) => `Cannot use ${rune} rune in non-runes mode`, 'invalid-state-export': () => - `Cannot export state if it is reassigned. Either export a function returning the state value or only mutate the state value's properties`, + `Cannot export state from a module if it is reassigned. Either export a function returning the state value or only mutate the state value's properties`, 'invalid-derived-export': () => - `Cannot export derived state. To expose the current derived value, export a function returning its value`, - 'invalid-prop-export': () => - `Cannot export properties. To expose the current value of a property, export a function returning its value`, + `Cannot export derived state from a module. To expose the current derived value, export a function returning its value`, 'invalid-props-id': () => `$props() can only be used with an object destructuring pattern`, 'invalid-props-pattern': () => `$props() assignment must not contain nested properties or computed keys`, diff --git a/packages/svelte/src/compiler/phases/2-analyze/validation.js b/packages/svelte/src/compiler/phases/2-analyze/validation.js index 959f50874299..fbf6392fba8c 100644 --- a/packages/svelte/src/compiler/phases/2-analyze/validation.js +++ b/packages/svelte/src/compiler/phases/2-analyze/validation.js @@ -733,10 +733,6 @@ function validate_export(node, scope, name) { const binding = scope.get(name); if (!binding) return; - if (binding.kind === 'prop') { - error(node, 'invalid-prop-export'); - } - if (binding.kind === 'derived') { error(node, 'invalid-derived-export'); } @@ -964,25 +960,12 @@ export const validation_runes = merge(validation, a11y_validators, { if (node.label.name !== '$' || path.at(-1)?.type !== 'Program') return; error(node, 'invalid-legacy-reactive-statement'); }, - ExportNamedDeclaration(node, { state, next }) { + ExportNamedDeclaration(node, { state }) { if (node.declaration?.type !== 'VariableDeclaration') return; - - // visit children, so bindings are correctly initialised - next(); - - for (const declarator of node.declaration.declarations) { - for (const id of extract_identifiers(declarator.id)) { - validate_export(node, state.scope, id.name); - } - } - - if (state.analysis.instance.scope !== state.scope) return; if (node.declaration.kind !== 'let') return; + if (state.analysis.instance.scope !== state.scope) return; error(node, 'invalid-legacy-export'); }, - ExportSpecifier(node, { state }) { - validate_export(node, state.scope, node.local.name); - }, CallExpression(node, { state, path }) { validate_call_expression(node, state.scope, path); }, diff --git a/packages/svelte/src/compiler/phases/3-transform/client/transform-client.js b/packages/svelte/src/compiler/phases/3-transform/client/transform-client.js index 80628cc2f2d4..139df0afea5b 100644 --- a/packages/svelte/src/compiler/phases/3-transform/client/transform-client.js +++ b/packages/svelte/src/compiler/phases/3-transform/client/transform-client.js @@ -225,28 +225,24 @@ export function client_component(source, analysis, options) { // Bind static exports to props so that people can access them with bind:x const static_bindings = analysis.exports.map(({ name, alias }) => { - const binding = analysis.instance.scope.get(name); return b.stmt( b.call( '$.bind_prop', b.id('$$props'), b.literal(alias ?? name), - binding?.kind === 'state' || binding?.kind === 'frozen_state' - ? b.call('$.get', b.id(name)) - : b.id(name) + serialize_get_binding(b.id(name), instance_state) ) ); }); const properties = analysis.exports.map(({ name, alias }) => { - const binding = analysis.instance.scope.get(name); - const is_source = binding !== null && is_state_source(binding, state); + const expression = serialize_get_binding(b.id(name), instance_state); - if (is_source || options.dev) { - return b.get(alias ?? name, [b.return(is_source ? b.call('$.get', b.id(name)) : b.id(name))]); + if (expression.type === 'Identifier' && !options.dev) { + return b.init(alias ?? name, expression); } - return b.init(alias ?? name, b.id(name)); + return b.get(alias ?? name, [b.return(expression)]); }); if (analysis.accessors) { diff --git a/packages/svelte/tests/compiler-errors/samples/export-derived-state/_config.js b/packages/svelte/tests/compiler-errors/samples/export-derived-state/_config.js index 31ee52b2ba2b..fd7049583611 100644 --- a/packages/svelte/tests/compiler-errors/samples/export-derived-state/_config.js +++ b/packages/svelte/tests/compiler-errors/samples/export-derived-state/_config.js @@ -4,6 +4,7 @@ export default test({ error: { code: 'invalid-derived-export', message: - 'Cannot export derived state. To expose the current derived value, export a function returning its value' + 'Cannot export derived state from a module. To expose the current derived value, export a function returning its value', + position: [24, 66] } }); diff --git a/packages/svelte/tests/compiler-errors/samples/export-derived-state/main.svelte b/packages/svelte/tests/compiler-errors/samples/export-derived-state/main.svelte deleted file mode 100644 index 634022db6aca..000000000000 --- a/packages/svelte/tests/compiler-errors/samples/export-derived-state/main.svelte +++ /dev/null @@ -1,4 +0,0 @@ - diff --git a/packages/svelte/tests/compiler-errors/samples/export-state-2/_config.js b/packages/svelte/tests/compiler-errors/samples/export-state-2/_config.js deleted file mode 100644 index 09fc4498faca..000000000000 --- a/packages/svelte/tests/compiler-errors/samples/export-state-2/_config.js +++ /dev/null @@ -1,10 +0,0 @@ -import { test } from '../../test'; - -export default test({ - error: { - code: 'invalid-state-export', - message: - "Cannot export state if it is reassigned. Either export a function returning the state value or only mutate the state value's properties", - position: [59, 99] - } -}); diff --git a/packages/svelte/tests/compiler-errors/samples/export-state-2/main.svelte b/packages/svelte/tests/compiler-errors/samples/export-state-2/main.svelte deleted file mode 100644 index ed9877766457..000000000000 --- a/packages/svelte/tests/compiler-errors/samples/export-state-2/main.svelte +++ /dev/null @@ -1,15 +0,0 @@ - diff --git a/packages/svelte/tests/compiler-errors/samples/export-state/_config.js b/packages/svelte/tests/compiler-errors/samples/export-state/_config.js index a8e8b4b25896..1f7bd0f76735 100644 --- a/packages/svelte/tests/compiler-errors/samples/export-state/_config.js +++ b/packages/svelte/tests/compiler-errors/samples/export-state/_config.js @@ -4,7 +4,7 @@ export default test({ error: { code: 'invalid-state-export', message: - "Cannot export state if it is reassigned. Either export a function returning the state value or only mutate the state value's properties", + "Cannot export state from a module if it is reassigned. Either export a function returning the state value or only mutate the state value's properties", position: [46, 86] } }); diff --git a/packages/svelte/tests/compiler-errors/samples/runes-export-named-state/_config.js b/packages/svelte/tests/compiler-errors/samples/runes-export-named-state/_config.js index 1eac29db14bb..fb1be5730bcf 100644 --- a/packages/svelte/tests/compiler-errors/samples/runes-export-named-state/_config.js +++ b/packages/svelte/tests/compiler-errors/samples/runes-export-named-state/_config.js @@ -4,7 +4,7 @@ export default test({ error: { code: 'invalid-state-export', message: - "Cannot export state if it is reassigned. Either export a function returning the state value or only mutate the state value's properties", + "Cannot export state from a module if it is reassigned. Either export a function returning the state value or only mutate the state value's properties", position: [28, 53] } }); diff --git a/packages/svelte/tests/compiler-errors/samples/runes-export-prop/_config.js b/packages/svelte/tests/compiler-errors/samples/runes-export-prop/_config.js deleted file mode 100644 index ce8f97e0d14c..000000000000 --- a/packages/svelte/tests/compiler-errors/samples/runes-export-prop/_config.js +++ /dev/null @@ -1,9 +0,0 @@ -import { test } from '../../test'; - -export default test({ - error: { - code: 'invalid-prop-export', - message: - 'Cannot export properties. To expose the current value of a property, export a function returning its value' - } -}); diff --git a/packages/svelte/tests/compiler-errors/samples/runes-export-prop/main.svelte b/packages/svelte/tests/compiler-errors/samples/runes-export-prop/main.svelte deleted file mode 100644 index 3d3fd554fa73..000000000000 --- a/packages/svelte/tests/compiler-errors/samples/runes-export-prop/main.svelte +++ /dev/null @@ -1,4 +0,0 @@ - diff --git a/packages/svelte/tests/runtime-runes/samples/exports1/_config.js b/packages/svelte/tests/runtime-runes/samples/exports-1/_config.js similarity index 100% rename from packages/svelte/tests/runtime-runes/samples/exports1/_config.js rename to packages/svelte/tests/runtime-runes/samples/exports-1/_config.js diff --git a/packages/svelte/tests/runtime-runes/samples/exports1/main.svelte b/packages/svelte/tests/runtime-runes/samples/exports-1/main.svelte similarity index 100% rename from packages/svelte/tests/runtime-runes/samples/exports1/main.svelte rename to packages/svelte/tests/runtime-runes/samples/exports-1/main.svelte diff --git a/packages/svelte/tests/runtime-runes/samples/exports1/sub.svelte b/packages/svelte/tests/runtime-runes/samples/exports-1/sub.svelte similarity index 100% rename from packages/svelte/tests/runtime-runes/samples/exports1/sub.svelte rename to packages/svelte/tests/runtime-runes/samples/exports-1/sub.svelte diff --git a/packages/svelte/tests/runtime-runes/samples/exports2/_config.js b/packages/svelte/tests/runtime-runes/samples/exports-2/_config.js similarity index 100% rename from packages/svelte/tests/runtime-runes/samples/exports2/_config.js rename to packages/svelte/tests/runtime-runes/samples/exports-2/_config.js diff --git a/packages/svelte/tests/runtime-runes/samples/exports2/main.svelte b/packages/svelte/tests/runtime-runes/samples/exports-2/main.svelte similarity index 100% rename from packages/svelte/tests/runtime-runes/samples/exports2/main.svelte rename to packages/svelte/tests/runtime-runes/samples/exports-2/main.svelte diff --git a/packages/svelte/tests/runtime-runes/samples/exports2/sub.svelte b/packages/svelte/tests/runtime-runes/samples/exports-2/sub.svelte similarity index 100% rename from packages/svelte/tests/runtime-runes/samples/exports2/sub.svelte rename to packages/svelte/tests/runtime-runes/samples/exports-2/sub.svelte diff --git a/packages/svelte/tests/runtime-runes/samples/exports3/_config.js b/packages/svelte/tests/runtime-runes/samples/exports-3/_config.js similarity index 80% rename from packages/svelte/tests/runtime-runes/samples/exports3/_config.js rename to packages/svelte/tests/runtime-runes/samples/exports-3/_config.js index d56d3b306d65..6eb043db3dea 100644 --- a/packages/svelte/tests/runtime-runes/samples/exports3/_config.js +++ b/packages/svelte/tests/runtime-runes/samples/exports-3/_config.js @@ -7,6 +7,6 @@ export default test({ btn?.click(); await Promise.resolve(); - assert.htmlEqual(target.innerHTML, '0 1 '); + assert.htmlEqual(target.innerHTML, '1 2 '); } }); diff --git a/packages/svelte/tests/runtime-runes/samples/exports3/main.svelte b/packages/svelte/tests/runtime-runes/samples/exports-3/main.svelte similarity index 51% rename from packages/svelte/tests/runtime-runes/samples/exports3/main.svelte rename to packages/svelte/tests/runtime-runes/samples/exports-3/main.svelte index ea5c63234398..b1cabb399fa9 100644 --- a/packages/svelte/tests/runtime-runes/samples/exports3/main.svelte +++ b/packages/svelte/tests/runtime-runes/samples/exports-3/main.svelte @@ -4,4 +4,4 @@ - + diff --git a/packages/svelte/tests/runtime-runes/samples/exports-3/sub.svelte b/packages/svelte/tests/runtime-runes/samples/exports-3/sub.svelte new file mode 100644 index 000000000000..53fad513d4f3 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/exports-3/sub.svelte @@ -0,0 +1,13 @@ + + +{count} +{doubled} diff --git a/packages/svelte/tests/runtime-runes/samples/exports3/sub.svelte b/packages/svelte/tests/runtime-runes/samples/exports3/sub.svelte deleted file mode 100644 index 37de78094a22..000000000000 --- a/packages/svelte/tests/runtime-runes/samples/exports3/sub.svelte +++ /dev/null @@ -1,15 +0,0 @@ - - -{count1.value} -{count2.value} - - -