From b3eeee08b69f39e034f200c707b11829a8517768 Mon Sep 17 00:00:00 2001 From: paoloricciuti Date: Fri, 10 Jan 2025 21:36:49 +0100 Subject: [PATCH 1/5] fix: thunkify deriveds on the server --- .changeset/curvy-toes-warn.md | 5 ++ .../3-transform/server/visitors/ClassBody.js | 3 +- .../server/visitors/VariableDeclaration.js | 60 +++++++++++++++++-- .../server/visitors/shared/utils.js | 4 ++ .../_expected/server/index.svelte.js | 4 +- .../samples/server-deriveds/_config.js | 5 ++ .../_expected/server/index.svelte.js | 59 ++++++++++++++++++ .../samples/server-deriveds/index.svelte | 25 ++++++++ packages/svelte/tests/snapshot/test.ts | 6 +- 9 files changed, 159 insertions(+), 12 deletions(-) create mode 100644 .changeset/curvy-toes-warn.md create mode 100644 packages/svelte/tests/snapshot/samples/server-deriveds/_config.js create mode 100644 packages/svelte/tests/snapshot/samples/server-deriveds/_expected/server/index.svelte.js create mode 100644 packages/svelte/tests/snapshot/samples/server-deriveds/index.svelte diff --git a/.changeset/curvy-toes-warn.md b/.changeset/curvy-toes-warn.md new file mode 100644 index 0000000000..07f1c7dcd4 --- /dev/null +++ b/.changeset/curvy-toes-warn.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: thunkify deriveds on the server diff --git a/packages/svelte/src/compiler/phases/3-transform/server/visitors/ClassBody.js b/packages/svelte/src/compiler/phases/3-transform/server/visitors/ClassBody.js index 365084a284..b03e1b5ab5 100644 --- a/packages/svelte/src/compiler/phases/3-transform/server/visitors/ClassBody.js +++ b/packages/svelte/src/compiler/phases/3-transform/server/visitors/ClassBody.js @@ -85,8 +85,7 @@ export function ClassBody(node, context) { const init = /** @type {Expression} **/ ( context.visit(definition.value.arguments[0], child_state) ); - const value = - field.kind === 'derived_by' ? b.call('$.once', init) : b.call('$.once', b.thunk(init)); + const value = field.kind === 'derived_by' ? init : b.thunk(init); if (is_private) { body.push(b.prop_def(field.id, value)); diff --git a/packages/svelte/src/compiler/phases/3-transform/server/visitors/VariableDeclaration.js b/packages/svelte/src/compiler/phases/3-transform/server/visitors/VariableDeclaration.js index 31de811ac7..a530b52f76 100644 --- a/packages/svelte/src/compiler/phases/3-transform/server/visitors/VariableDeclaration.js +++ b/packages/svelte/src/compiler/phases/3-transform/server/visitors/VariableDeclaration.js @@ -1,11 +1,11 @@ -/** @import { VariableDeclaration, VariableDeclarator, Expression, CallExpression, Pattern, Identifier } from 'estree' */ +/** @import { VariableDeclaration, VariableDeclarator, Expression, CallExpression, Pattern, Identifier, ObjectPattern, ArrayPattern, Property } from 'estree' */ /** @import { Binding } from '#compiler' */ /** @import { Context } from '../types.js' */ /** @import { Scope } from '../../../scope.js' */ -import { build_fallback, extract_paths } from '../../../../utils/ast.js'; +import { walk } from 'zimmerframe'; +import { build_fallback, extract_identifiers, extract_paths } from '../../../../utils/ast.js'; import * as b from '../../../../utils/builders.js'; import { get_rune } from '../../../scope.js'; -import { walk } from 'zimmerframe'; /** * @param {VariableDeclaration} node @@ -16,6 +16,9 @@ export function VariableDeclaration(node, context) { const declarations = []; if (context.state.analysis.runes) { + /** @type {VariableDeclarator[]} */ + const destructured_reassigns = []; + for (const declarator of node.declarations) { const init = declarator.init; const rune = get_rune(init, context.state.scope); @@ -73,27 +76,72 @@ export function VariableDeclaration(node, context) { const value = args.length === 0 ? b.id('undefined') : /** @type {Expression} */ (context.visit(args[0])); + const is_destructuring = + declarator.id.type === 'ObjectPattern' || declarator.id.type === 'ArrayPattern'; + + /** + * + * @param {()=>Expression} get_generated_init + */ + function add_destructured_reassign(get_generated_init) { + // to keep everything that the user destructure as a function we need to change the original + // assignment to a generated value and then reassign a variable with the original name + if (declarator.id.type === 'ObjectPattern' || declarator.id.type === 'ArrayPattern') { + const id = /** @type {ObjectPattern | ArrayPattern} */ (context.visit(declarator.id)); + const modified = walk( + /**@type {Identifier|Property}*/ (/**@type {unknown}*/ (id)), + {}, + { + Identifier(id, { path }) { + const parent = path.at(-1); + // we only want the identifiers for the value + if (parent?.type === 'Property' && parent.value !== id) return; + const generated = context.state.scope.generate(id.name); + destructured_reassigns.push(b.declarator(b.id(id.name), b.thunk(b.id(generated)))); + return b.id(generated); + } + } + ); + declarations.push(b.declarator(/**@type {Pattern}*/ (modified), get_generated_init())); + } + } + if (rune === '$derived.by') { + if (is_destructuring) { + add_destructured_reassign(() => b.call(value)); + continue; + } declarations.push( - b.declarator(/** @type {Pattern} */ (context.visit(declarator.id)), b.call(value)) + b.declarator(/** @type {Pattern} */ (context.visit(declarator.id)), value) ); continue; } if (declarator.id.type === 'Identifier') { - declarations.push(b.declarator(declarator.id, value)); + if (is_destructuring && rune === '$derived') { + add_destructured_reassign(() => value); + continue; + } + declarations.push( + b.declarator(declarator.id, rune === '$derived' ? b.thunk(value) : value) + ); continue; } if (rune === '$derived') { + if (is_destructuring) { + add_destructured_reassign(() => value); + continue; + } declarations.push( - b.declarator(/** @type {Pattern} */ (context.visit(declarator.id)), value) + b.declarator(/** @type {Pattern} */ (context.visit(declarator.id)), b.thunk(value)) ); continue; } declarations.push(...create_state_declarators(declarator, context.state.scope, value)); } + declarations.push(...destructured_reassigns); } else { for (const declarator of node.declarations) { const bindings = /** @type {Binding[]} */ (context.state.scope.get_bindings(declarator)); diff --git a/packages/svelte/src/compiler/phases/3-transform/server/visitors/shared/utils.js b/packages/svelte/src/compiler/phases/3-transform/server/visitors/shared/utils.js index 2c6aa2f316..1a4898a3d7 100644 --- a/packages/svelte/src/compiler/phases/3-transform/server/visitors/shared/utils.js +++ b/packages/svelte/src/compiler/phases/3-transform/server/visitors/shared/utils.js @@ -237,6 +237,10 @@ export function build_getter(node, state) { b.literal(node.name), build_getter(store_id, state) ); + } else if (binding.kind === 'derived') { + // we need a maybe_call because in case of `var` + // the user might use the variable before the initialization + return b.maybe_call(node.name); } return node; diff --git a/packages/svelte/tests/snapshot/samples/await-block-scope/_expected/server/index.svelte.js b/packages/svelte/tests/snapshot/samples/await-block-scope/_expected/server/index.svelte.js index 012789a550..34e26e7fa0 100644 --- a/packages/svelte/tests/snapshot/samples/await-block-scope/_expected/server/index.svelte.js +++ b/packages/svelte/tests/snapshot/samples/await-block-scope/_expected/server/index.svelte.js @@ -2,13 +2,13 @@ import * as $ from 'svelte/internal/server'; export default function Await_block_scope($$payload) { let counter = { count: 0 }; - const promise = Promise.resolve(counter); + const promise = () => Promise.resolve(counter); function increment() { counter.count += 1; } $$payload.out += ` `; - $.await(promise, () => {}, (counter) => {}, () => {}); + $.await(promise?.(), () => {}, (counter) => {}, () => {}); $$payload.out += ` ${$.escape(counter.count)}`; } \ No newline at end of file diff --git a/packages/svelte/tests/snapshot/samples/server-deriveds/_config.js b/packages/svelte/tests/snapshot/samples/server-deriveds/_config.js new file mode 100644 index 0000000000..2a7b882b86 --- /dev/null +++ b/packages/svelte/tests/snapshot/samples/server-deriveds/_config.js @@ -0,0 +1,5 @@ +import { test } from '../../test'; + +export default test({ + mode: ['server'] +}); diff --git a/packages/svelte/tests/snapshot/samples/server-deriveds/_expected/server/index.svelte.js b/packages/svelte/tests/snapshot/samples/server-deriveds/_expected/server/index.svelte.js new file mode 100644 index 0000000000..009dd90f5b --- /dev/null +++ b/packages/svelte/tests/snapshot/samples/server-deriveds/_expected/server/index.svelte.js @@ -0,0 +1,59 @@ +import * as $ from "svelte/internal/server"; + +export default function Server_deriveds($$payload, $$props) { + $.push(); + + // destructuring stuff on the server needs a bit more code + // so that every identifier is a function + let stuff = { + foo: true, + bar: [1, 2, { baz: 'baz' }] + }; + + let { + foo: foo_1, + bar: [a_1, b_1, { baz: baz_1 }] + } = stuff, + foo = () => foo_1, + a = () => a_1, + b = () => b_1, + baz = () => baz_1; + + let stuff2 = [1, 2, 3]; + + let [d_1, e_1, f_1] = stuff2, + d = () => d_1, + e = () => e_1, + f = () => f_1; + + let count = 0; + let double = () => count * 2; + let identifier = () => count; + let dot_by = () => () => count; + + class Test { + state = 0; + #der = () => this.state * 2; + + get der() { + return this.#der(); + } + + #der_by = () => this.state; + + get der_by() { + return this.#der_by(); + } + + #identifier = () => this.state; + + get identifier() { + return this.#identifier(); + } + } + + const test = new Test(); + + $$payload.out += `${$.escape(foo?.())} ${$.escape(a?.())} ${$.escape(b?.())} ${$.escape(baz?.())} ${$.escape(d?.())} ${$.escape(e?.())} ${$.escape(f?.())} ${$.escape(double?.())} ${$.escape(identifier?.())} ${$.escape(dot_by?.())} ${$.escape(test.der)} ${$.escape(test.der_by)} ${$.escape(test.identifier)}`; + $.pop(); +} \ No newline at end of file diff --git a/packages/svelte/tests/snapshot/samples/server-deriveds/index.svelte b/packages/svelte/tests/snapshot/samples/server-deriveds/index.svelte new file mode 100644 index 0000000000..ab97da6bf8 --- /dev/null +++ b/packages/svelte/tests/snapshot/samples/server-deriveds/index.svelte @@ -0,0 +1,25 @@ + + +{foo} {a} {b} {baz} {d} {e} {f} {double} {identifier} {dot_by} {test.der} {test.der_by} {test.identifier} \ No newline at end of file diff --git a/packages/svelte/tests/snapshot/test.ts b/packages/svelte/tests/snapshot/test.ts index 88cf9193c3..f0f626f791 100644 --- a/packages/svelte/tests/snapshot/test.ts +++ b/packages/svelte/tests/snapshot/test.ts @@ -7,11 +7,13 @@ import { VERSION } from 'svelte/compiler'; interface SnapshotTest extends BaseTest { compileOptions?: Partial; + mode?: ('client' | 'server')[]; } const { test, run } = suite(async (config, cwd) => { - await compile_directory(cwd, 'client', config.compileOptions); - await compile_directory(cwd, 'server', config.compileOptions); + for (const mode of config?.mode ?? ['server', 'client']) { + await compile_directory(cwd, mode, config.compileOptions); + } // run `UPDATE_SNAPSHOTS=true pnpm test snapshot` to update snapshot tests if (process.env.UPDATE_SNAPSHOTS) { From 4b22a097091100ddf633adf5987f4695f0911f7c Mon Sep 17 00:00:00 2001 From: paoloricciuti Date: Fri, 10 Jan 2025 21:59:45 +0100 Subject: [PATCH 2/5] chore: install before generating snapshots --- .../server-deriveds/_expected/server/index.svelte.js | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/packages/svelte/tests/snapshot/samples/server-deriveds/_expected/server/index.svelte.js b/packages/svelte/tests/snapshot/samples/server-deriveds/_expected/server/index.svelte.js index 009dd90f5b..18b3379ed3 100644 --- a/packages/svelte/tests/snapshot/samples/server-deriveds/_expected/server/index.svelte.js +++ b/packages/svelte/tests/snapshot/samples/server-deriveds/_expected/server/index.svelte.js @@ -1,14 +1,11 @@ -import * as $ from "svelte/internal/server"; +import * as $ from 'svelte/internal/server'; export default function Server_deriveds($$payload, $$props) { $.push(); // destructuring stuff on the server needs a bit more code // so that every identifier is a function - let stuff = { - foo: true, - bar: [1, 2, { baz: 'baz' }] - }; + let stuff = { foo: true, bar: [1, 2, { baz: 'baz' }] }; let { foo: foo_1, From a3f7ae8c739c17fddd3dd14e7341f4a65436f130 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Thu, 17 Apr 2025 15:45:31 -0400 Subject: [PATCH 3/5] dont transform twice --- .../phases/3-transform/server/visitors/shared/element.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/svelte/src/compiler/phases/3-transform/server/visitors/shared/element.js b/packages/svelte/src/compiler/phases/3-transform/server/visitors/shared/element.js index b0bcb8fd6f..aac15cc03d 100644 --- a/packages/svelte/src/compiler/phases/3-transform/server/visitors/shared/element.js +++ b/packages/svelte/src/compiler/phases/3-transform/server/visitors/shared/element.js @@ -169,7 +169,7 @@ export function build_element_attributes(node, context) { type: 'ExpressionTag', start: -1, end: -1, - expression, + expression: attribute.expression, metadata: { expression: create_expression_metadata() } From 961c36bdf50e645790dd8e42217883db8aee0793 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Thu, 17 Apr 2025 16:01:08 -0400 Subject: [PATCH 4/5] fix --- .../3-transform/server/visitors/shared/element.js | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/packages/svelte/src/compiler/phases/3-transform/server/visitors/shared/element.js b/packages/svelte/src/compiler/phases/3-transform/server/visitors/shared/element.js index aac15cc03d..b5f6fb3806 100644 --- a/packages/svelte/src/compiler/phases/3-transform/server/visitors/shared/element.js +++ b/packages/svelte/src/compiler/phases/3-transform/server/visitors/shared/element.js @@ -163,13 +163,26 @@ export function build_element_attributes(node, context) { ]) ); } else { + /** @type {Expression} */ + let expression = attribute.expression; + + if (attribute.type === 'BindDirective' && expression.type === 'SequenceExpression') { + const getter = expression.expressions[0]; + expression = + getter.type === 'ArrowFunctionExpression' && + getter.params.length === 0 && + getter.body.type !== 'BlockStatement' + ? getter.body + : b.call(getter); + } + attributes.push( create_attribute(attribute.name, -1, -1, [ { type: 'ExpressionTag', start: -1, end: -1, - expression: attribute.expression, + expression, metadata: { expression: create_expression_metadata() } From b1a6b8cbdaa23314066dbc36480eecd9d85620b8 Mon Sep 17 00:00:00 2001 From: paoloricciuti Date: Mon, 16 Jun 2025 19:05:43 +0200 Subject: [PATCH 5/5] fix: use proxy for destructured values --- .../phases/3-transform/server/types.d.ts | 1 + .../server/visitors/CallExpression.js | 7 +- .../server/visitors/VariableDeclaration.js | 77 ++++--------------- packages/svelte/src/internal/server/index.js | 47 ++++++++++- packages/svelte/src/internal/shared/utils.js | 22 ++++++ .../_expected/server/index.svelte.js | 4 +- .../_expected/server/index.svelte.js | 44 +++++------ 7 files changed, 110 insertions(+), 92 deletions(-) diff --git a/packages/svelte/src/compiler/phases/3-transform/server/types.d.ts b/packages/svelte/src/compiler/phases/3-transform/server/types.d.ts index adde7480cb..c9000ca487 100644 --- a/packages/svelte/src/compiler/phases/3-transform/server/types.d.ts +++ b/packages/svelte/src/compiler/phases/3-transform/server/types.d.ts @@ -6,6 +6,7 @@ import type { ComponentAnalysis } from '../../types.js'; export interface ServerTransformState extends TransformState { /** The $: calls, which will be ordered in the end */ readonly legacy_reactive_statements: Map; + readonly is_destructured_derived?: boolean; } export interface ComponentServerTransformState extends ServerTransformState { diff --git a/packages/svelte/src/compiler/phases/3-transform/server/visitors/CallExpression.js b/packages/svelte/src/compiler/phases/3-transform/server/visitors/CallExpression.js index 35c79988b0..daafdc9295 100644 --- a/packages/svelte/src/compiler/phases/3-transform/server/visitors/CallExpression.js +++ b/packages/svelte/src/compiler/phases/3-transform/server/visitors/CallExpression.js @@ -31,7 +31,12 @@ export function CallExpression(node, context) { if (rune === '$derived' || rune === '$derived.by') { const fn = /** @type {Expression} */ (context.visit(node.arguments[0])); - return b.call('$.derived', rune === '$derived' ? b.thunk(fn) : fn); + + return b.call( + '$.derived', + rune === '$derived' ? b.thunk(fn) : fn, + context.state.is_destructured_derived && b.true + ); } if (rune === '$state.snapshot') { diff --git a/packages/svelte/src/compiler/phases/3-transform/server/visitors/VariableDeclaration.js b/packages/svelte/src/compiler/phases/3-transform/server/visitors/VariableDeclaration.js index 32b6e62a68..5fc5a259b1 100644 --- a/packages/svelte/src/compiler/phases/3-transform/server/visitors/VariableDeclaration.js +++ b/packages/svelte/src/compiler/phases/3-transform/server/visitors/VariableDeclaration.js @@ -1,12 +1,12 @@ -/** @import { VariableDeclaration, VariableDeclarator, Expression, CallExpression, Pattern, Identifier, ObjectPattern, ArrayPattern, Property } from 'estree' */ +/** @import { VariableDeclaration, VariableDeclarator, Expression, CallExpression, Pattern, Identifier } from 'estree' */ /** @import { Binding } from '#compiler' */ /** @import { Context } from '../types.js' */ /** @import { ComponentAnalysis } from '../../../types.js' */ /** @import { Scope } from '../../../scope.js' */ -import { walk } from 'zimmerframe'; -import { build_fallback, extract_identifiers, extract_paths } from '../../../../utils/ast.js'; +import { build_fallback, extract_paths } from '../../../../utils/ast.js'; import * as b from '#compiler/builders'; import { get_rune } from '../../../scope.js'; +import { walk } from 'zimmerframe'; /** * @param {VariableDeclaration} node @@ -17,9 +17,6 @@ export function VariableDeclaration(node, context) { const declarations = []; if (context.state.analysis.runes) { - /** @type {VariableDeclarator[]} */ - const destructured_reassigns = []; - for (const declarator of node.declarations) { const init = declarator.init; const rune = get_rune(init, context.state.scope); @@ -84,75 +81,29 @@ export function VariableDeclaration(node, context) { continue; } - const args = /** @type {CallExpression} */ (init).arguments; - const value = args.length > 0 ? /** @type {Expression} */ (context.visit(args[0])) : b.void0; - - const is_destructuring = - declarator.id.type === 'ObjectPattern' || declarator.id.type === 'ArrayPattern'; - - /** - * - * @param {()=>Expression} get_generated_init - */ - function add_destructured_reassign(get_generated_init) { - // to keep everything that the user destructure as a function we need to change the original - // assignment to a generated value and then reassign a variable with the original name - if (declarator.id.type === 'ObjectPattern' || declarator.id.type === 'ArrayPattern') { - const id = /** @type {ObjectPattern | ArrayPattern} */ (context.visit(declarator.id)); - const modified = walk( - /**@type {Identifier|Property}*/ (/**@type {unknown}*/ (id)), - {}, - { - Identifier(id, { path }) { - const parent = path.at(-1); - // we only want the identifiers for the value - if (parent?.type === 'Property' && parent.value !== id) return; - const generated = context.state.scope.generate(id.name); - destructured_reassigns.push(b.declarator(b.id(id.name), b.thunk(b.id(generated)))); - return b.id(generated); - } - } - ); - declarations.push(b.declarator(/**@type {Pattern}*/ (modified), get_generated_init())); - } - } - - if (rune === '$derived.by') { - if (is_destructuring) { - add_destructured_reassign(() => b.call(value)); - continue; - } - declarations.push( - b.declarator(/** @type {Pattern} */ (context.visit(declarator.id)), value) - ); - continue; - } + const value = init + ? /** @type {Expression} */ ( + context.visit(init, { + ...context.state, + is_destructured_derived: declarator.id.type !== 'Identifier' + }) + ) + : b.void0; if (declarator.id.type === 'Identifier') { - if (is_destructuring && rune === '$derived') { - add_destructured_reassign(() => value); - continue; - } - declarations.push( - b.declarator(declarator.id, rune === '$derived' ? b.thunk(value) : value) - ); + declarations.push(b.declarator(declarator.id, value)); continue; } - if (rune === '$derived') { - if (is_destructuring) { - add_destructured_reassign(() => value); - continue; - } + if (rune === '$derived' || rune === '$derived.by') { declarations.push( - b.declarator(/** @type {Pattern} */ (context.visit(declarator.id)), b.thunk(value)) + b.declarator(/** @type {Pattern} */ (context.visit(declarator.id)), value) ); continue; } declarations.push(...create_state_declarators(declarator, context.state.scope, value)); } - declarations.push(...destructured_reassigns); } else { for (const declarator of node.declarations) { const bindings = /** @type {Binding[]} */ (context.state.scope.get_bindings(declarator)); diff --git a/packages/svelte/src/internal/server/index.js b/packages/svelte/src/internal/server/index.js index 2ca85fff44..56f31e10d0 100644 --- a/packages/svelte/src/internal/server/index.js +++ b/packages/svelte/src/internal/server/index.js @@ -3,7 +3,7 @@ /** @import { Store } from '#shared' */ export { FILENAME, HMR } from '../../constants.js'; import { attr, clsx, to_class, to_style } from '../shared/attributes.js'; -import { is_promise, noop } from '../shared/utils.js'; +import { access_path_on_object, is_promise, noop } from '../shared/utils.js'; import { subscribe_to_store } from '../../store/utils.js'; import { UNINITIALIZED, @@ -18,6 +18,7 @@ import { validate_store } from '../shared/validate.js'; import { is_boolean_attribute, is_raw_text_element, is_void } from '../../utils.js'; import { reset_elements } from './dev.js'; import { Payload } from './payload.js'; +import { is } from '../client/proxy.js'; // https://html.spec.whatwg.org/multipage/syntax.html#attributes-2 // https://infra.spec.whatwg.org/#noncharacter @@ -518,15 +519,55 @@ export { escape_html as escape }; /** * @template T * @param {()=>T} fn - * @returns {(new_value?: T) => (T | void)} + * @param {boolean} is_destructured + * @returns {((new_value?: T) => (T | void)) | Record (T | void)>} */ -export function derived(fn) { +export function derived(fn, is_destructured = false) { const get_value = once(fn); /** * @type {T | undefined} */ let updated_value; + if (is_destructured) { + /** + * + * @param {(string | symbol)[]} path + * @returns + */ + function recursive_proxy(path = []) { + return new Proxy( + /** @type {(new_value: any)=>any} */ ( + function (new_value) { + if (arguments.length === 0) { + return ( + access_path_on_object(/** @type {*} */ (updated_value), path) ?? + access_path_on_object(/** @type {*} */ (get_value()), path) + ); + } + var last_key = path[path.length - 1]; + const to_update = access_path_on_object( + /** @type {*} */ (updated_value), + path.slice(0, -1), + (current, key) => { + current[key] = {}; + } + ); + /** @type {*} */ (to_update)[last_key] = new_value; + return /** @type {*} */ (to_update)[last_key]; + } + ), + { + get(_, key) { + return recursive_proxy([...path, key]); + } + } + ); + } + updated_value = /** @type {T} */ ({}); + return recursive_proxy(); + } + return function (new_value) { if (arguments.length === 0) { return updated_value ?? get_value(); diff --git a/packages/svelte/src/internal/shared/utils.js b/packages/svelte/src/internal/shared/utils.js index 10f8597520..10c218f7e6 100644 --- a/packages/svelte/src/internal/shared/utils.js +++ b/packages/svelte/src/internal/shared/utils.js @@ -116,3 +116,25 @@ export function to_array(value, n) { return array; } + +/** + * + * @param {Record} obj + * @param {(string | symbol)[]} path + * @param {(current: Record, key:string|symbol)=>void} [on_undefined] + * @returns + */ +export function access_path_on_object(obj, path, on_undefined) { + if (obj == null) return undefined; + + let current = obj; + for (const key of path) { + if (current == null) return undefined; + if (current[key] == null && on_undefined) { + on_undefined(current, key); + } + current = /** @type {*} */ (current)[key]; + } + + return current; +} diff --git a/packages/svelte/tests/snapshot/samples/await-block-scope/_expected/server/index.svelte.js b/packages/svelte/tests/snapshot/samples/await-block-scope/_expected/server/index.svelte.js index dd82309a2c..139dd2e132 100644 --- a/packages/svelte/tests/snapshot/samples/await-block-scope/_expected/server/index.svelte.js +++ b/packages/svelte/tests/snapshot/samples/await-block-scope/_expected/server/index.svelte.js @@ -2,7 +2,7 @@ import * as $ from 'svelte/internal/server'; export default function Await_block_scope($$payload) { let counter = { count: 0 }; - const promise = () => Promise.resolve(counter); + const promise = $.derived(() => Promise.resolve(counter)); function increment() { counter.count += 1; @@ -11,4 +11,4 @@ export default function Await_block_scope($$payload) { $$payload.out += ` `; $.await($$payload, promise?.(), () => {}, (counter) => {}); $$payload.out += ` ${$.escape(counter.count)}`; -} +} \ No newline at end of file diff --git a/packages/svelte/tests/snapshot/samples/server-deriveds/_expected/server/index.svelte.js b/packages/svelte/tests/snapshot/samples/server-deriveds/_expected/server/index.svelte.js index 18b3379ed3..ce72a3d2fd 100644 --- a/packages/svelte/tests/snapshot/samples/server-deriveds/_expected/server/index.svelte.js +++ b/packages/svelte/tests/snapshot/samples/server-deriveds/_expected/server/index.svelte.js @@ -6,51 +6,49 @@ export default function Server_deriveds($$payload, $$props) { // destructuring stuff on the server needs a bit more code // so that every identifier is a function let stuff = { foo: true, bar: [1, 2, { baz: 'baz' }] }; - - let { - foo: foo_1, - bar: [a_1, b_1, { baz: baz_1 }] - } = stuff, - foo = () => foo_1, - a = () => a_1, - b = () => b_1, - baz = () => baz_1; - + let { foo, bar: [a, b, { baz }] } = $.derived(() => stuff, true); let stuff2 = [1, 2, 3]; - - let [d_1, e_1, f_1] = stuff2, - d = () => d_1, - e = () => e_1, - f = () => f_1; - + let [d, e, f] = $.derived(() => stuff2, true); let count = 0; - let double = () => count * 2; - let identifier = () => count; - let dot_by = () => () => count; + let double = $.derived(() => count * 2); + let identifier = $.derived(() => count); + let dot_by = $.derived(() => () => count); class Test { state = 0; - #der = () => this.state * 2; + #der = $.derived(() => this.state * 2); get der() { return this.#der(); } - #der_by = () => this.state; + set der($$value) { + return this.#der($$value); + } + + #der_by = $.derived(() => this.state); get der_by() { return this.#der_by(); } - #identifier = () => this.state; + set der_by($$value) { + return this.#der_by($$value); + } + + #identifier = $.derived(() => this.state); get identifier() { return this.#identifier(); } + + set identifier($$value) { + return this.#identifier($$value); + } } const test = new Test(); - $$payload.out += `${$.escape(foo?.())} ${$.escape(a?.())} ${$.escape(b?.())} ${$.escape(baz?.())} ${$.escape(d?.())} ${$.escape(e?.())} ${$.escape(f?.())} ${$.escape(double?.())} ${$.escape(identifier?.())} ${$.escape(dot_by?.())} ${$.escape(test.der)} ${$.escape(test.der_by)} ${$.escape(test.identifier)}`; + $$payload.out += `${$.escape(foo?.())} ${$.escape(a?.())} ${$.escape(b?.())} ${$.escape(baz?.())} ${$.escape(d?.())} ${$.escape(e?.())} ${$.escape(f?.())} 0 0 ${$.escape(dot_by?.())} ${$.escape(test.der)} ${$.escape(test.der_by)} ${$.escape(test.identifier)}`; $.pop(); } \ No newline at end of file