From 0db3edc3e87c29073ece739b97af6d0618ff226d Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Tue, 28 Nov 2023 20:17:23 -0500 Subject: [PATCH] eliminate closures --- packages/svelte/src/internal/client/magic.js | 173 +++++++++++-------- 1 file changed, 99 insertions(+), 74 deletions(-) diff --git a/packages/svelte/src/internal/client/magic.js b/packages/svelte/src/internal/client/magic.js index 814eaf9c1a..11dc82af49 100644 --- a/packages/svelte/src/internal/client/magic.js +++ b/packages/svelte/src/internal/client/magic.js @@ -1,107 +1,132 @@ import { effect_active, get, set, increment, source } from './runtime.js'; +/** @typedef {{ p: MagicObject | null; s: Map>; v: import('./types').SourceSignal; a: boolean }} Magic */ +/** @typedef {Record & { [MAGIC_SYMBOL]: Magic }} MagicObject */ + export const MAGIC_SYMBOL = Symbol(); export const MAGIC_EACH_SYMBOL = Symbol(); /** - * @template T + * @template {MagicObject} T * @param {T} value * @returns {T} */ export function magic(value) { - if (value && typeof value === 'object') { - return wrap(value, null); - } - - return value; + return wrap(value, null); } /** - * @template T + * @template {MagicObject} T + * @template {MagicObject} P * @param {T} value - * @param {T | null} parent + * @param {P | null} parent * @returns {T} */ function wrap(value, parent) { if (value && typeof value === 'object') { - return object(value, parent); + return proxy(value, parent); } return value; } +/** @type {ProxyHandler} */ +const handler = { + get(target, prop, receiver) { + if (prop === MAGIC_EACH_SYMBOL) { + return parent; + } + + const metadata = target[MAGIC_SYMBOL]; + let s = metadata.s.get(prop); + + if (s === undefined && effect_active()) { + s = source(wrap(target[prop], receiver)); + metadata.s.set(prop, s); + } + + const value = s !== undefined ? get(s) : target[prop]; + + if (typeof value === 'function') { + // @ts-ignore + return (...args) => { + return value.apply(receiver, args); + }; + } + return value; + }, + set(target, prop, value) { + const metadata = target[MAGIC_SYMBOL]; + + const s = metadata.s.get(prop); + if (s !== undefined) set(s, wrap(value, target)); + + if (metadata.a && prop === 'length') { + for (let i = value; i < target.length; i += 1) { + const s = metadata.s.get(i + ''); + if (s !== undefined) set(s, undefined); + } + } + + if (!(prop in target)) increment(metadata.v); + // @ts-ignore + target[prop] = value; + + return true; + }, + deleteProperty(target, prop) { + const metadata = target[MAGIC_SYMBOL]; + + const s = metadata.s.get(prop); + if (s !== undefined) set(s, undefined); + + if (prop in target) increment(metadata.v); + + return delete target[prop]; + }, + has(target, prop) { + if (prop === MAGIC_SYMBOL) return true; + + const metadata = target[MAGIC_SYMBOL]; + + get(metadata.v); + return Reflect.has(target, prop); + }, + ownKeys(target) { + const metadata = target[MAGIC_SYMBOL]; + + get(metadata.v); + return Reflect.ownKeys(target); + } +}; + /** - * @template {Record} T - * @template P + * @template {MagicObject} T + * @template {MagicObject} P * @param {T} value * @param {P | null} parent * @returns {T} */ -function object(value, parent) { +function proxy(value, parent) { if (MAGIC_SYMBOL in value) return value; - /** @type {Map} */ - const sources = new Map(); - let version = source(0); - - const is_array = Array.isArray(value); + // @ts-expect-error + value[MAGIC_SYMBOL] = init(value, parent); - return new Proxy(value, { - get(target, prop, receiver) { - if (prop === MAGIC_EACH_SYMBOL) { - return parent; - } - let s = sources.get(prop); - - if (s === undefined && effect_active()) { - s = source(wrap(target[prop], receiver)); - sources.set(prop, s); - } - - const value = s !== undefined ? get(s) : target[prop]; - - if (typeof value === 'function') { - // @ts-ignore - return (...args) => { - return value.apply(receiver, args); - }; - } - return value; - }, - set(target, prop, value) { - const s = sources.get(prop); - if (s !== undefined) set(s, wrap(value, target)); - - if (is_array && prop === 'length') { - for (let i = value; i < target.length; i += 1) { - const s = sources.get(i + ''); - if (s !== undefined) set(s, undefined); - } - } + // @ts-expect-error not sure how to fix this + return new Proxy(value, handler); +} - if (!(prop in target)) increment(version); - // @ts-ignore - target[prop] = value; - - return true; - }, - deleteProperty(target, prop) { - const s = sources.get(prop); - if (s !== undefined) set(s, undefined); - - if (prop in target) increment(version); - - return delete target[prop]; - }, - has(target, prop) { - if (prop === MAGIC_SYMBOL) return true; - - get(version); - return Reflect.has(target, prop); - }, - ownKeys(target) { - get(version); - return Reflect.ownKeys(target); - } - }); +/** + * @param {MagicObject} value + * @param {MagicObject | null} parent + * @returns {Magic} + */ +function init(value, parent) { + return { + p: parent, + s: new Map(), + v: source(0), + a: Array.isArray(value) + }; }