Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/empty-crabs-think.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'svelte': patch
---

chore: refactor props handling
Original file line number Diff line number Diff line change
Expand Up @@ -255,8 +255,8 @@ export function client_component(source, analysis, options) {
const key = binding.prop_alias ?? name;

properties.push(
b.get(key, [b.return(b.call('$.get', b.id(name)))]),
b.set(key, [b.stmt(b.call('$.set_sync', b.id(name), b.id('$$value')))])
b.get(key, [b.return(b.call(b.id(name)))]),
b.set(key, [b.stmt(b.call(b.id(name), b.id('$$value'))), b.stmt(b.call('$.flushSync'))])
);
}
}
Expand Down
65 changes: 45 additions & 20 deletions packages/svelte/src/compiler/phases/3-transform/client/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@ import * as b from '../../../utils/builders.js';
import { extract_paths, is_simple_expression } from '../../../utils/ast.js';
import { error } from '../../../errors.js';
import {
PROPS_CALL_DEFAULT_VALUE,
PROPS_IS_LAZY_INITIAL,
PROPS_IS_IMMUTABLE,
PROPS_IS_RUNES
PROPS_IS_RUNES,
PROPS_IS_UPDATED
} from '../../../../constants.js';

/**
Expand Down Expand Up @@ -73,12 +74,14 @@ export function serialize_get_binding(node, state) {
}

if (
!state.analysis.accessors &&
!(state.analysis.immutable ? binding.reassigned : binding.mutated) &&
!binding.initial
state.analysis.accessors ||
(state.analysis.immutable ? binding.reassigned : binding.mutated) ||
binding.initial
) {
return b.member(b.id('$$props'), node);
return b.call(node);
}

return b.member(b.id('$$props'), node);
}

if (binding.kind === 'legacy_reactive_import') {
Expand All @@ -89,7 +92,6 @@ export function serialize_get_binding(node, state) {
(binding.kind === 'state' &&
(!state.analysis.immutable || state.analysis.accessors || binding.reassigned)) ||
binding.kind === 'derived' ||
binding.kind === 'prop' ||
binding.kind === 'legacy_reactive'
) {
return b.call('$.get', node);
Expand Down Expand Up @@ -208,9 +210,12 @@ export function serialize_set_binding(node, context, fallback) {
}

const value = get_assignment_value(node, context);

const serialize = () => {
if (left === node.left) {
if (is_store) {
if (binding.kind === 'prop') {
return b.call(left, value);
} else if (is_store) {
return b.call('$.store_set', serialize_get_binding(b.id(left_name), state), value);
} else {
return b.call(
Expand All @@ -232,15 +237,27 @@ export function serialize_set_binding(node, context, fallback) {
b.call('$' + left_name)
);
} else if (!state.analysis.runes) {
return b.call(
'$.mutate',
b.id(left_name),
b.assignment(
node.operator,
/** @type {import('estree').Pattern} */ (visit(node.left)),
value
)
);
if (binding.kind === 'prop') {
return b.call(
left,
b.assignment(
node.operator,
/** @type {import('estree').Pattern} */ (visit(node.left)),
value
),
b.literal(true)
);
} else {
return b.call(
'$.mutate',
b.id(left_name),
b.assignment(
node.operator,
/** @type {import('estree').Pattern} */ (visit(node.left)),
value
)
);
}
} else {
return b.assignment(
node.operator,
Expand Down Expand Up @@ -345,12 +362,13 @@ export function serialize_hoistable_params(node, context) {
}

/**
* @param {import('#compiler').Binding} binding
* @param {import('./types').ComponentClientTransformState} state
* @param {string} name
* @param {import('estree').Expression | null} [initial]
* @returns
*/
export function get_prop_source(state, name, initial) {
export function get_prop_source(binding, state, name, initial) {
/** @type {import('estree').Expression[]} */
const args = [b.id('$$props'), b.literal(name)];

Expand All @@ -364,6 +382,13 @@ export function get_prop_source(state, name, initial) {
flags |= PROPS_IS_RUNES;
}

if (
state.analysis.accessors ||
(state.analysis.immutable ? binding.reassigned : binding.mutated)
) {
flags |= PROPS_IS_UPDATED;
}

/** @type {import('estree').Expression | undefined} */
let arg;

Expand All @@ -382,7 +407,7 @@ export function get_prop_source(state, name, initial) {
arg = b.thunk(initial);
}

flags |= PROPS_CALL_DEFAULT_VALUE;
flags |= PROPS_IS_LAZY_INITIAL;
}
}

Expand All @@ -391,7 +416,7 @@ export function get_prop_source(state, name, initial) {
if (arg) args.push(arg);
}

return b.call('$.prop_source', ...args);
return b.call('$.prop', ...args);
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ export const global_visitors = {
const binding = state.scope.get(argument.name);
const is_store = binding?.kind === 'store_sub';
const name = is_store ? argument.name.slice(1) : argument.name;

// use runtime functions for smaller output
if (
binding?.kind === 'state' ||
Expand All @@ -53,18 +54,28 @@ export const global_visitors = {
binding?.kind === 'prop' ||
is_store
) {
let fn = node.operator === '++' ? '$.increment' : '$.decrement';
/** @type {import('estree').Expression[]} */
const args = [];

let fn = '$.update';
if (node.prefix) fn += '_pre';

if (is_store) {
fn += '_store';
return b.call(fn, serialize_get_binding(b.id(name), state), b.call('$' + name));
args.push(serialize_get_binding(b.id(name), state), b.call('$' + name));
} else {
return b.call(fn, b.id(name));
if (binding.kind === 'prop') fn += '_prop';
args.push(b.id(name));
}
} else {
return next();

if (node.operator === '--') {
args.push(b.literal(-1));
}

return b.call(fn, ...args);
}

return next();
} else if (
argument.type === 'MemberExpression' &&
argument.object.type === 'ThisExpression' &&
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ export const javascript_visitors_legacy = {
b.declarator(
path.node,
binding.kind === 'prop'
? get_prop_source(state, binding.prop_alias ?? name, value)
? get_prop_source(binding, state, binding.prop_alias ?? name, value)
: value
)
);
Expand All @@ -76,6 +76,7 @@ export const javascript_visitors_legacy = {
b.declarator(
declarator.id,
get_prop_source(
binding,
state,
binding.prop_alias ?? declarator.id.name,
declarator.init &&
Expand Down Expand Up @@ -107,8 +108,11 @@ export const javascript_visitors_legacy = {
};
},
LabeledStatement(node, context) {
if (context.path.length > 1) return;
if (node.label.name !== '$') return;
if (context.path.length > 1 || node.label.name !== '$') {
context.next();
return;
}

const state = context.state;
// To recreate Svelte 4 behaviour, we track the dependencies
// the compiler can 'see', but we untrack the effect itself
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -185,7 +185,7 @@ export const javascript_visitors_runes = {
const binding = /** @type {import('#compiler').Binding} */ (state.scope.get(id.name));

if (binding.reassigned || state.analysis.accessors || initial) {
declarations.push(b.declarator(id, get_prop_source(state, name, initial)));
declarations.push(b.declarator(id, get_prop_source(binding, state, name, initial)));
}
} else {
// RestElement
Expand Down
3 changes: 2 additions & 1 deletion packages/svelte/src/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ export const EACH_IS_IMMUTABLE = 1 << 6;

export const PROPS_IS_IMMUTABLE = 1;
export const PROPS_IS_RUNES = 1 << 1;
export const PROPS_CALL_DEFAULT_VALUE = 1 << 2;
export const PROPS_IS_UPDATED = 1 << 2;
export const PROPS_IS_LAZY_INITIAL = 1 << 3;

/** List of Element events that will be delegated */
export const DelegatedEvents = [
Expand Down
6 changes: 3 additions & 3 deletions packages/svelte/src/internal/client/proxy/proxy.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import {
effect_active,
get,
set,
increment,
update,
source,
updating_derived,
UNINITIALIZED,
Expand Down Expand Up @@ -143,7 +143,7 @@ const handler = {
const s = metadata.s.get(prop);
if (s !== undefined) set(s, UNINITIALIZED);

if (prop in target) increment(metadata.v);
if (prop in target) update(metadata.v);

return delete target[prop];
},
Expand Down Expand Up @@ -224,7 +224,7 @@ const handler = {
}
}
if (not_has) {
increment(metadata.v);
update(metadata.v);
}
// @ts-ignore
target[prop] = value;
Expand Down
2 changes: 1 addition & 1 deletion packages/svelte/src/internal/client/render.js
Original file line number Diff line number Diff line change
Expand Up @@ -2658,7 +2658,7 @@ export function createRoot(component, options) {
/** @param {any} value */
set(value) {
// @ts-expect-error TS doesn't know key exists on accessor
accessors[key] = value;
flushSync(() => (accessors[key] = value));
},
enumerable: true
});
Expand Down
Loading