From e541296da4a8eae0fea0578550e5fcef48ee05f5 Mon Sep 17 00:00:00 2001 From: 7nik Date: Sat, 13 Sep 2025 15:05:16 +0300 Subject: [PATCH] fix: ensure keys are validated --- .../3-transform/client/visitors/EachBlock.js | 4 -- .../src/internal/client/dom/blocks/each.js | 6 +++ .../svelte/src/internal/client/validate.js | 38 ++++++++----------- 3 files changed, 21 insertions(+), 27 deletions(-) diff --git a/packages/svelte/src/compiler/phases/3-transform/client/visitors/EachBlock.js b/packages/svelte/src/compiler/phases/3-transform/client/visitors/EachBlock.js index 225a4f617c..39f61272fc 100644 --- a/packages/svelte/src/compiler/phases/3-transform/client/visitors/EachBlock.js +++ b/packages/svelte/src/compiler/phases/3-transform/client/visitors/EachBlock.js @@ -337,10 +337,6 @@ export function EachBlock(node, context) { const statements = [add_svelte_meta(b.call('$.each', ...args), node, 'each')]; - if (dev && node.metadata.keyed) { - statements.unshift(b.stmt(b.call('$.validate_each_keys', thunk, key_function))); - } - if (has_await) { context.state.init.push( b.stmt( diff --git a/packages/svelte/src/internal/client/dom/blocks/each.js b/packages/svelte/src/internal/client/dom/blocks/each.js index 7b027ba502..3d8e7c8870 100644 --- a/packages/svelte/src/internal/client/dom/blocks/each.js +++ b/packages/svelte/src/internal/client/dom/blocks/each.js @@ -43,6 +43,7 @@ import { DEV } from 'esm-env'; import { derived_safe_equal } from '../../reactivity/deriveds.js'; import { current_batch } from '../../reactivity/batch.js'; import { each_key_duplicate } from '../../errors.js'; +import { validate_each_keys } from '../../validate.js'; /** * The row of a keyed each block that is currently updating. We track this @@ -202,6 +203,11 @@ export function each(node, flags, get_collection, get_key, render_fn, fallback_f } was_empty = length === 0; + // skip if #each block isn't keyed + if (DEV && get_key !== index) { + validate_each_keys(array, get_key); + } + /** `true` if there was a hydration mismatch. Needs to be a `let` or else it isn't treeshaken out */ let mismatch = false; diff --git a/packages/svelte/src/internal/client/validate.js b/packages/svelte/src/internal/client/validate.js index ec3d805447..7c8cf93fdc 100644 --- a/packages/svelte/src/internal/client/validate.js +++ b/packages/svelte/src/internal/client/validate.js @@ -7,35 +7,27 @@ import * as w from './warnings.js'; import { capture_store_binding } from './reactivity/store.js'; /** - * @param {() => any} collection + * @param {Array} array * @param {(item: any, index: number) => string} key_fn * @returns {void} */ -export function validate_each_keys(collection, key_fn) { - render_effect(() => { - const keys = new Map(); - const maybe_array = collection(); - const array = is_array(maybe_array) - ? maybe_array - : maybe_array == null - ? [] - : Array.from(maybe_array); - const length = array.length; - for (let i = 0; i < length; i++) { - const key = key_fn(array[i], i); - if (keys.has(key)) { - const a = String(keys.get(key)); - const b = String(i); +export function validate_each_keys(array, key_fn) { + const keys = new Map(); + const length = array.length; + for (let i = 0; i < length; i++) { + const key = key_fn(array[i], i); + if (keys.has(key)) { + const a = String(keys.get(key)); + const b = String(i); - /** @type {string | null} */ - let k = String(key); - if (k.startsWith('[object ')) k = null; + /** @type {string | null} */ + let k = String(key); + if (k.startsWith('[object ')) k = null; - e.each_key_duplicate(a, b, k); - } - keys.set(key, i); + e.each_key_duplicate(a, b, k); } - }); + keys.set(key, i); + } } /**