From 5f218b5f3d98a4b9333db595993c93c59815a59e Mon Sep 17 00:00:00 2001 From: Dominic Gannaway Date: Fri, 31 May 2024 16:17:46 +0100 Subject: [PATCH] chore: improve $state.frozen performance in prod (#11852) * chore: improve $state.frozen performance in prod * lint * feedback --- .changeset/silver-sheep-knock.md | 5 ++++ .../svelte/src/internal/client/constants.js | 1 + packages/svelte/src/internal/client/proxy.js | 15 ++++++---- .../svelte/src/internal/client/runtime.js | 30 ++++++++++++++----- 4 files changed, 38 insertions(+), 13 deletions(-) create mode 100644 .changeset/silver-sheep-knock.md diff --git a/.changeset/silver-sheep-knock.md b/.changeset/silver-sheep-knock.md new file mode 100644 index 0000000000..5c1ab0a910 --- /dev/null +++ b/.changeset/silver-sheep-knock.md @@ -0,0 +1,5 @@ +--- +"svelte": patch +--- + +chore: improve $state.frozen performance in prod diff --git a/packages/svelte/src/internal/client/constants.js b/packages/svelte/src/internal/client/constants.js index 543f812e79..0d691cfd96 100644 --- a/packages/svelte/src/internal/client/constants.js +++ b/packages/svelte/src/internal/client/constants.js @@ -18,4 +18,5 @@ export const EFFECT_TRANSPARENT = 1 << 15; export const LEGACY_DERIVED_PROP = 1 << 16; export const STATE_SYMBOL = Symbol('$state'); +export const STATE_FROZEN_SYMBOL = Symbol('$state.frozen'); export const LOADING_ATTR_SYMBOL = Symbol(''); diff --git a/packages/svelte/src/internal/client/proxy.js b/packages/svelte/src/internal/client/proxy.js index 31a22600b6..53ce093dc3 100644 --- a/packages/svelte/src/internal/client/proxy.js +++ b/packages/svelte/src/internal/client/proxy.js @@ -18,20 +18,25 @@ import { } from './utils.js'; import { check_ownership, widen_ownership } from './dev/ownership.js'; import { mutable_source, source, set } from './reactivity/sources.js'; -import { STATE_SYMBOL } from './constants.js'; +import { STATE_FROZEN_SYMBOL, STATE_SYMBOL } from './constants.js'; import { UNINITIALIZED } from '../../constants.js'; import * as e from './errors.js'; /** * @template T - * @param {T} value + * @param {T} initial_value * @param {boolean} [immutable] * @param {import('#client').ProxyMetadata | null} [parent] * @param {import('#client').Source} [prev] dev mode only * @returns {import('#client').ProxyStateObject | T} */ -export function proxy(value, immutable = true, parent = null, prev) { - if (typeof value === 'object' && value != null && !is_frozen(value)) { +export function proxy(initial_value, immutable = true, parent = null, prev) { + if (typeof initial_value === 'object' && initial_value != null) { + let value = initial_value; + // If the object is frozen then snapshot the value + if (is_frozen(value) || STATE_FROZEN_SYMBOL in value) { + value = snapshot(value); + } // If we have an existing proxy, return it... if (STATE_SYMBOL in value) { const metadata = /** @type {import('#client').ProxyMetadata} */ (value[STATE_SYMBOL]); @@ -94,7 +99,7 @@ export function proxy(value, immutable = true, parent = null, prev) { } } - return value; + return initial_value; } /** diff --git a/packages/svelte/src/internal/client/runtime.js b/packages/svelte/src/internal/client/runtime.js index 8fe016532d..545d3f6561 100644 --- a/packages/svelte/src/internal/client/runtime.js +++ b/packages/svelte/src/internal/client/runtime.js @@ -1,5 +1,11 @@ import { DEV } from 'esm-env'; -import { get_descriptors, get_prototype_of, is_frozen, object_freeze } from './utils.js'; +import { + define_property, + get_descriptors, + get_prototype_of, + is_frozen, + object_freeze +} from './utils.js'; import { snapshot } from './proxy.js'; import { destroy_effect, effect, execute_effect_teardown } from './reactivity/effects.js'; import { @@ -17,7 +23,8 @@ import { BLOCK_EFFECT, ROOT_EFFECT, LEGACY_DERIVED_PROP, - DISCONNECTED + DISCONNECTED, + STATE_FROZEN_SYMBOL } from './constants.js'; import { flush_tasks } from './dom/task.js'; import { add_owner } from './dev/ownership.js'; @@ -1353,19 +1360,26 @@ if (DEV) { } /** - * Expects a value that was wrapped with `freeze` and makes it frozen. + * Expects a value that was wrapped with `freeze` and makes it frozen in DEV. * @template T * @param {T} value * @returns {Readonly} */ export function freeze(value) { - if (typeof value === 'object' && value != null && !is_frozen(value)) { + if (typeof value === 'object' && value != null && !(STATE_FROZEN_SYMBOL in value)) { // If the object is already proxified, then snapshot the value - if (STATE_SYMBOL in value) { - return object_freeze(snapshot(value)); + if (STATE_SYMBOL in value || is_frozen(value)) { + value = snapshot(value); + } + define_property(value, STATE_FROZEN_SYMBOL, { + value: true, + writable: true, + enumerable: false + }); + // Freeze the object in DEV + if (DEV) { + object_freeze(value); } - // Otherwise freeze the object - object_freeze(value); } return value; }