perf: use Set for rest-prop exclude lists (server, legacy)

Client runes rest_props already takes a Set (#18252). Align server
rest_props and legacy_rest_props so exclude membership is O(1).

Compiler emits new Set([...]) for both paths. Legacy exclude Sets stay
per-instance because deleteProperty mutates them via .add.

exclude_from_object left alone: its exclude list is rebuilt per update
with tiny key counts, where Set construction can lose to includes.

Closes #18601 (server + legacy paths; exclude_from_object deferred).
pull/18605/head
ljodea 4 days ago
parent 44a7813730
commit da0f4328d1

@ -0,0 +1,5 @@
---
'svelte': patch
---
perf: use Set for rest-prop exclude lists (server, legacy)

@ -457,7 +457,8 @@ export function client_component(analysis, options) {
b.call(
'$.legacy_rest_props',
b.id('$$sanitized_props'),
b.array(named_props.map((name) => b.literal(name)))
// Per-instance Set: deleteProperty mutates exclude via .add
b.new('Set', b.array(named_props.map((name) => b.literal(name))))
)
)
);
@ -477,7 +478,8 @@ export function client_component(analysis, options) {
component_block.body.unshift(
b.const(
'$$sanitized_props',
b.call('$.legacy_rest_props', b.id('$$props'), b.array(to_remove))
// Per-instance Set: deleteProperty mutates exclude via .add
b.call('$.legacy_rest_props', b.id('$$props'), b.new('Set', b.array(to_remove)))
)
);
}

@ -284,7 +284,7 @@ export function server_component(analysis, options) {
b.call(
'$.rest_props',
b.id('$$sanitized_props'),
b.array(named_props.map((name) => b.literal(name)))
b.new('Set', b.array(named_props.map((name) => b.literal(name))))
)
)
);

@ -96,11 +96,11 @@ export function rest_props(props, exclude, name) {
/**
* The proxy handler for legacy $$restProps and $$props
* @type {ProxyHandler<{ props: Record<string | symbol, unknown>, exclude: Array<string | symbol>, special: Record<string | symbol, (v?: unknown) => unknown>, version: Source<number>, parent_effect: Effect }>}}
* @type {ProxyHandler<{ props: Record<string | symbol, unknown>, exclude: Set<string | symbol>, special: Record<string | symbol, (v?: unknown) => unknown>, version: Source<number>, parent_effect: Effect }>}}
*/
const legacy_rest_props_handler = {
get(target, key) {
if (target.exclude.includes(key)) return;
if (target.exclude.has(key)) return;
get(target.version);
return key in target.special ? target.special[key]() : target.props[key];
},
@ -132,7 +132,7 @@ const legacy_rest_props_handler = {
return true;
},
getOwnPropertyDescriptor(target, key) {
if (target.exclude.includes(key)) return;
if (target.exclude.has(key)) return;
if (key in target.props) {
return {
enumerable: true,
@ -143,23 +143,23 @@ const legacy_rest_props_handler = {
},
deleteProperty(target, key) {
// Svelte 4 allowed for deletions on $$restProps
if (target.exclude.includes(key)) return true;
target.exclude.push(key);
if (target.exclude.has(key)) return true;
target.exclude.add(key);
update(target.version);
return true;
},
has(target, key) {
if (target.exclude.includes(key)) return false;
if (target.exclude.has(key)) return false;
return key in target.props;
},
ownKeys(target) {
return Reflect.ownKeys(target.props).filter((key) => !target.exclude.includes(key));
return Reflect.ownKeys(target.props).filter((key) => !target.exclude.has(key));
}
};
/**
* @param {Record<string, unknown>} props
* @param {string[]} exclude
* @param {Set<string | symbol>} exclude
* @returns {Record<string, unknown>}
*/
export function legacy_rest_props(props, exclude) {

@ -343,7 +343,7 @@ export function slot(renderer, $$props, name, slot_props, fallback_fn) {
/**
* @param {Record<string, unknown>} props
* @param {string[]} rest
* @param {Set<string>} rest
* @returns {Record<string, unknown>}
*/
export function rest_props(props, rest) {
@ -351,7 +351,7 @@ export function rest_props(props, rest) {
const rest_props = {};
let key;
for (key of Object.keys(props)) {
if (!rest.includes(key)) {
if (!rest.has(key)) {
rest_props[key] = props[key];
}
}

@ -0,0 +1,47 @@
import { describe, assert, it } from 'vitest';
import { rest_props as server_rest_props } from '../../src/internal/server/index.js';
import { legacy_rest_props } from '../../src/internal/client/reactivity/props.js';
/**
* #18601 rest-prop exclude lists accept `Set` and use O(1) membership.
* These helpers are compiler-owned; the compiler emits `new Set([...])`.
*/
describe('server rest_props exclude Set', () => {
it('omits excluded keys', () => {
const result = server_rest_props({ a: 1, b: 2, c: 3 }, new Set(['a', 'b']));
assert.deepEqual(result, { c: 3 });
});
it('returns all keys when exclude is empty', () => {
const result = server_rest_props({ a: 1, b: 2 }, new Set());
assert.deepEqual(result, { a: 1, b: 2 });
});
it('returns empty object when all keys are excluded', () => {
const result = server_rest_props({ a: 1 }, new Set(['a']));
assert.deepEqual(result, {});
});
});
describe('legacy_rest_props exclude Set', () => {
it('omits excluded keys from get/has/ownKeys', () => {
const rest = legacy_rest_props({ a: 1, b: 2, c: 3 }, new Set(['a']));
assert.equal(rest.a, undefined);
assert.equal(rest.b, 2);
assert.equal(rest.c, 3);
assert.equal('a' in rest, false);
assert.equal('b' in rest, true);
assert.deepEqual(Object.keys(rest).sort(), ['b', 'c']);
});
it('deleteProperty adds the key to the exclude Set', () => {
const rest = legacy_rest_props({ a: 1, b: 2 }, new Set());
assert.equal(rest.a, 1);
delete rest.a;
assert.equal(rest.a, undefined);
assert.equal('a' in rest, false);
assert.deepEqual(Object.keys(rest), ['b']);
});
});
Loading…
Cancel
Save