From 8f2a539721f0093607bd4bd13ff29942d4172aef Mon Sep 17 00:00:00 2001 From: Utkarsh Yadav Date: Fri, 28 Aug 2026 12:51:56 +0530 Subject: [PATCH] perf: use Set for rest-prop exclude lists in server, legacy, and shared utils MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Closes #18601 The client runes `rest_props` path already uses `Set` + `.has()` for O(1) exclude lookups. Three sibling paths still used arrays + `.includes()`, giving O(|props| × |exclude|) per operation: - Server `rest_props` (SSR) - `exclude_from_object` (rest destructuring, shared) - `legacy_rest_props` (Svelte 4 compat $$restProps proxy) This converts all three to accept `Set` and updates the compiler to emit `new Set([...])` instead of plain arrays, aligning all paths with the existing client runes behavior. Tested: snapshot, compiler, runtime-runes, runtime-legacy, signals, ssr, lint — all pass. 🤖 Generated with Codebuff Co-Authored-By: Codebuff --- .../3-transform/client/transform-client.js | 5 ++--- .../3-transform/server/transform-server.js | 3 +-- packages/svelte/src/compiler/utils/ast.js | 6 +++++- .../src/internal/client/reactivity/props.js | 16 ++++++++-------- packages/svelte/src/internal/server/index.js | 4 ++-- packages/svelte/src/internal/shared/utils.js | 6 +++--- 6 files changed, 21 insertions(+), 19 deletions(-) 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 cbb5f2eac7..e1830fccf4 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 @@ -450,14 +450,13 @@ export function client_component(analysis, options) { for (const [name, binding] of analysis.instance.scope.declarations) { if (binding.kind === 'bindable_prop') named_props.push(binding.prop_alias ?? name); } - component_block.body.unshift( b.const( '$$restProps', b.call( '$.legacy_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)))) ) ) ); @@ -477,7 +476,7 @@ 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)) + b.call('$.legacy_rest_props', b.id('$$props'), b.new('Set', b.array(to_remove))) ) ); } diff --git a/packages/svelte/src/compiler/phases/3-transform/server/transform-server.js b/packages/svelte/src/compiler/phases/3-transform/server/transform-server.js index 44690a1efe..9d9943ef41 100644 --- a/packages/svelte/src/compiler/phases/3-transform/server/transform-server.js +++ b/packages/svelte/src/compiler/phases/3-transform/server/transform-server.js @@ -288,14 +288,13 @@ export function server_component(analysis, options) { for (const [name, binding] of analysis.instance.scope.declarations) { if (binding.kind === 'bindable_prop') named_props.push(binding.prop_alias ?? name); } - component_block.body.unshift( b.const( '$$restProps', 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)))) ) ) ); diff --git a/packages/svelte/src/compiler/utils/ast.js b/packages/svelte/src/compiler/utils/ast.js index 75aadd905b..008215ffb3 100644 --- a/packages/svelte/src/compiler/utils/ast.js +++ b/packages/svelte/src/compiler/utils/ast.js @@ -297,7 +297,11 @@ function _extract_paths(paths, inserts, param, expression, update_expression, ha } } - const rest_expression = b.call('$.exclude_from_object', expression, b.array(props)); + const rest_expression = b.call( + '$.exclude_from_object', + expression, + b.new('Set', b.array(props)) + ); if (prop.argument.type === 'Identifier') { paths.push({ diff --git a/packages/svelte/src/internal/client/reactivity/props.js b/packages/svelte/src/internal/client/reactivity/props.js index 274d780b1e..b24dd71cbd 100644 --- a/packages/svelte/src/internal/client/reactivity/props.js +++ b/packages/svelte/src/internal/client/reactivity/props.js @@ -96,11 +96,11 @@ export function rest_props(props, exclude, name) { /** * The proxy handler for legacy $$restProps and $$props - * @type {ProxyHandler<{ props: Record, exclude: Array, special: Record unknown>, version: Source, parent_effect: Effect }>}} + * @type {ProxyHandler<{ props: Record, exclude: Set, special: Record unknown>, version: Source, 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} props - * @param {string[]} exclude + * @param {Set} exclude * @returns {Record} */ export function legacy_rest_props(props, exclude) { diff --git a/packages/svelte/src/internal/server/index.js b/packages/svelte/src/internal/server/index.js index 3d8ec5fe5e..04cfd711a4 100644 --- a/packages/svelte/src/internal/server/index.js +++ b/packages/svelte/src/internal/server/index.js @@ -343,7 +343,7 @@ export function slot(renderer, $$props, name, slot_props, fallback_fn) { /** * @param {Record} props - * @param {string[]} rest + * @param {Set} rest * @returns {Record} */ 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]; } } diff --git a/packages/svelte/src/internal/shared/utils.js b/packages/svelte/src/internal/shared/utils.js index 393b51383e..29d8b9ab92 100644 --- a/packages/svelte/src/internal/shared/utils.js +++ b/packages/svelte/src/internal/shared/utils.js @@ -121,7 +121,7 @@ export function to_array(value, n) { /** * @param {Record} obj - * @param {Array} keys + * @param {Set} keys * @returns {Record} */ export function exclude_from_object(obj, keys) { @@ -129,13 +129,13 @@ export function exclude_from_object(obj, keys) { var result = {}; for (var key in obj) { - if (!keys.includes(key)) { + if (!keys.has(key)) { result[key] = obj[key]; } } for (var symbol of Object.getOwnPropertySymbols(obj)) { - if (Object.propertyIsEnumerable.call(obj, symbol) && !keys.includes(symbol)) { + if (Object.propertyIsEnumerable.call(obj, symbol) && !keys.has(symbol)) { result[symbol] = obj[symbol]; } }