diff --git a/packages/solid/src/index.ts b/packages/solid/src/index.ts index d37b2af93..587d9d187 100644 --- a/packages/solid/src/index.ts +++ b/packages/solid/src/index.ts @@ -66,16 +66,12 @@ type JSXElement = JSX.Element; export type { JSXElement, JSX }; // dev -import { hashValue, registerGraph, serializeGraph, writeSignal } from "./reactive/signal.js"; +import { registerGraph, writeSignal } from "./reactive/signal.js"; let DEV: { writeSignal: typeof writeSignal; - serializeGraph: typeof serializeGraph; registerGraph: typeof registerGraph; - hashValue: typeof hashValue; }; -if ("_SOLID_DEV_") { - DEV = { writeSignal, serializeGraph, registerGraph, hashValue }; -} +if ("_SOLID_DEV_") DEV = { writeSignal, registerGraph }; export { DEV }; // handle multiple instance check diff --git a/packages/solid/src/reactive/signal.ts b/packages/solid/src/reactive/signal.ts index 9446cca4b..536bb5746 100644 --- a/packages/solid/src/reactive/signal.ts +++ b/packages/solid/src/reactive/signal.ts @@ -29,7 +29,6 @@ let Listener: Computation | null = null; let Updates: Computation[] | null = null; let Effects: Computation[] | null = null; let ExecCount = 0; -let rootCount = 0; // keep immediately evaluated module code, below its indirect declared let dependencies like Listener const [transPending, setTransPending] = /*@__PURE__*/ createSignal(false); @@ -41,18 +40,18 @@ declare global { export type ComputationState = 0 | 1 | 2; -export interface SourceMapValue { +export interface SignalMapValue { value: unknown; + name?: string; graph?: Owner; } -export interface SignalState extends SourceMapValue { +export interface SignalState extends SignalMapValue { value: T; observers: Computation[] | null; observerSlots: number[] | null; tValue?: T; comparator?: (prev: T, next: T) => boolean; - name?: string; } export interface Owner { @@ -60,9 +59,8 @@ export interface Owner { cleanups: (() => void)[] | null; owner: Owner | null; context: any | null; - sourceMap?: Record; + sourceMap?: SignalMapValue[]; name?: string; - componentName?: string; } export interface Computation extends Owner { @@ -129,10 +127,7 @@ export function createRoot(fn: RootFunction, detachedOwner?: Owner): T { : fn : () => fn(() => untrack(() => cleanNode(root))); - if ("_SOLID_DEV_") { - if (owner) root.name = `${owner.name}-r${rootCount++}`; - globalThis._$afterCreateRoot && globalThis._$afterCreateRoot(root); - } + if ("_SOLID_DEV_") globalThis._$afterCreateRoot && globalThis._$afterCreateRoot(root); Owner = root; Listener = null; @@ -196,8 +191,10 @@ export function createSignal( comparator: options.equals || undefined }; - if ("_SOLID_DEV_" && !options.internal) - s.name = registerGraph(options.name || hashValue(value), s as { value: unknown }); + if ("_SOLID_DEV_" && !options.internal) { + if (options.name) s.name = options.name; + registerGraph(s); + } const setter: Setter = (value?: unknown) => { if (typeof value === "function") { @@ -1058,59 +1055,11 @@ export function devComponent(Comp: (props: T) => JSX.Element, props: T) { return c.tValue !== undefined ? c.tValue : c.value; } -export function hashValue(v: any): string { - const s = new Set(); - return `s${ - typeof v === "string" - ? hash(v) - : hash( - untrack( - () => - JSON.stringify(v, (k, v) => { - if (typeof v === "object" && v != null) { - if (s.has(v)) return; - s.add(v); - const keys = Object.keys(v); - const desc = Object.getOwnPropertyDescriptors(v); - const newDesc = keys.reduce((memo, key) => { - const value = desc[key]; - // skip getters - if (!value.get) memo[key] = value; - return memo; - }, {} as any); - v = Object.create({}, newDesc); - } - if (typeof v === "bigint") { - return `${v.toString()}n`; - } - return v; - }) || "" - ) - ) - }`; -} - -export function registerGraph(name: string, value: SourceMapValue): string { - let tryName = name; - if (Owner) { - let i = 0; - Owner.sourceMap || (Owner.sourceMap = {}); - while (Owner.sourceMap[tryName]) tryName = `${name}-${++i}`; - Owner.sourceMap[tryName] = value; - value.graph = Owner; - } - return tryName; -} -interface GraphRecord { - [k: string]: GraphRecord | unknown; -} -export function serializeGraph(owner?: Owner | null): GraphRecord { - owner || (owner = Owner); - if (!"_SOLID_DEV_" || !owner) return {}; - return { - ...serializeValues(owner.sourceMap), - ...(owner.owned ? serializeChildren(owner) : {}) - }; +export function registerGraph(value: SignalMapValue): void { + if (!Owner) return; + if (Owner.sourceMap) Owner.sourceMap.push(value); + else Owner.sourceMap = [value]; + value.graph = Owner; } export type ContextProviderComponent = FlowComponent<{ value: T }>; @@ -1407,14 +1356,10 @@ function createComputation( if (!Owner.owned) Owner.owned = [c]; else Owner.owned.push(c); } - if ("_SOLID_DEV_") - c.name = - (options && options.name) || - `${(Owner as Computation).name || "c"}-${ - (Owner.owned || (Owner as Memo).tOwned!).length - }`; } + if ("_SOLID_DEV_" && options && options.name) c.name = options.name; + if (ExternalSourceFactory) { const [track, trigger] = createSignal(undefined, { equals: false }); const ordinary = ExternalSourceFactory(c.fn, trigger); @@ -1712,31 +1657,4 @@ function createProvider(id: symbol, options?: EffectOptions) { }; } -function hash(s: string) { - for (var i = 0, h = 9; i < s.length; ) h = Math.imul(h ^ s.charCodeAt(i++), 9 ** 9); - return `${h ^ (h >>> 9)}`; -} - -function serializeValues(sources: Record = {}) { - const k = Object.keys(sources); - const result: Record = {}; - for (let i = 0; i < k.length; i++) { - const key = k[i]; - result[key] = sources[key].value; - } - return result; -} - -function serializeChildren(root: Owner): GraphRecord { - const result: GraphRecord = {}; - for (let i = 0, len = root.owned!.length; i < len; i++) { - const node = root.owned![i]; - result[node.componentName ? `${node.componentName}:${node.name}` : node.name!] = { - ...serializeValues(node.sourceMap), - ...(node.owned ? serializeChildren(node) : {}) - }; - } - return result; -} - type TODO = any; diff --git a/packages/solid/store/src/index.ts b/packages/solid/store/src/index.ts index 658562b5b..58039170d 100644 --- a/packages/solid/store/src/index.ts +++ b/packages/solid/store/src/index.ts @@ -16,5 +16,5 @@ export * from "./mutable.js"; export * from "./modifiers.js"; // dev -import { $NAME, $NODE, isWrappable } from "./store.js"; -export const DEV = "_SOLID_DEV_" ? ({ $NAME, $NODE, isWrappable } as const) : undefined; +import { $NODE, isWrappable } from "./store.js"; +export const DEV = "_SOLID_DEV_" ? ({ $NODE, isWrappable } as const) : undefined; diff --git a/packages/solid/store/src/mutable.ts b/packages/solid/store/src/mutable.ts index 67424c9d8..3702b71c6 100644 --- a/packages/solid/store/src/mutable.ts +++ b/packages/solid/store/src/mutable.ts @@ -7,7 +7,6 @@ import { getDataNode, $RAW, $NODE, - $NAME, StoreNode, setProperty, ownKeys @@ -21,8 +20,7 @@ function proxyDescriptor(target: StoreNode, property: PropertyKey) { desc.set || !desc.configurable || property === $PROXY || - property === $NODE || - property === $NAME + property === $NODE ) return desc; @@ -57,9 +55,7 @@ const proxyTraps: ProxyHandler = { batch(() => Array.prototype[property as any].apply(receiver, args)); } } - return isWrappable(value) - ? wrap(value, "_SOLID_DEV_" && target[$NAME] && `${target[$NAME]}:${property.toString()}`) - : value; + return isWrappable(value) ? wrap(value) : value; }, has(target, property) { @@ -90,7 +86,7 @@ const proxyTraps: ProxyHandler = { getOwnPropertyDescriptor: proxyDescriptor }; -function wrap(value: T, name?: string): T { +function wrap(value: T): T { let p = value[$PROXY]; if (!p) { Object.defineProperty(value, $PROXY, { value: (p = new Proxy(value, proxyTraps)) }); @@ -112,7 +108,6 @@ function wrap(value: T, name?: string): T { }); } } - if ("_SOLID_DEV_" && name) Object.defineProperty(value, $NAME, { value: name }); } return p; } @@ -123,14 +118,8 @@ export function createMutable(state: T, options?: { name?: throw new Error( `Unexpected type ${typeof unwrappedStore} received when initializing 'createMutable'. Expected an object.` ); - const wrappedStore = wrap( - unwrappedStore, - "_SOLID_DEV_" && ((options && options.name) || DEV.hashValue(unwrappedStore)) - ); - if ("_SOLID_DEV_") { - const name = (options && options.name) || DEV.hashValue(unwrappedStore); - DEV.registerGraph(name, { value: unwrappedStore }); - } + const wrappedStore = wrap(unwrappedStore); + if ("_SOLID_DEV_") DEV.registerGraph({ value: unwrappedStore, name: options && options.name }); return wrappedStore; } diff --git a/packages/solid/store/src/store.ts b/packages/solid/store/src/store.ts index 05229bb8d..9d777d1fe 100644 --- a/packages/solid/store/src/store.ts +++ b/packages/solid/store/src/store.ts @@ -1,8 +1,7 @@ import { getListener, batch, DEV, $PROXY, $TRACK, createSignal } from "solid-js"; export const $RAW = Symbol("store-raw"), - $NODE = Symbol("store-node"), - $NAME = Symbol("store-name"); + $NODE = Symbol("store-node"); // dev declare global { @@ -23,7 +22,6 @@ export type OnStoreNodeUpdate = ( ) => void; export interface StoreNode { - [$NAME]?: string; [$NODE]?: DataNodes; [key: PropertyKey]: any; } @@ -43,7 +41,7 @@ export type NotWrappable = | SolidStore.Unwrappable[keyof SolidStore.Unwrappable]; export type Store = T; -function wrap(value: T, name?: string): T { +function wrap(value: T): T { let p = value[$PROXY]; if (!p) { Object.defineProperty(value, $PROXY, { value: (p = new Proxy(value, proxyTraps)) }); @@ -60,7 +58,6 @@ function wrap(value: T, name?: string): T { } } } - if ("_SOLID_DEV_" && name) Object.defineProperty(value, $NAME, { value: name }); } return p; } @@ -129,14 +126,7 @@ export function getDataNode(nodes: DataNodes, property: PropertyKey, value: any) export function proxyDescriptor(target: StoreNode, property: PropertyKey) { const desc = Reflect.getOwnPropertyDescriptor(target, property); - if ( - !desc || - desc.get || - !desc.configurable || - property === $PROXY || - property === $NODE || - property === $NAME - ) + if (!desc || desc.get || !desc.configurable || property === $PROXY || property === $NODE) return desc; delete desc.value; delete desc.writable; @@ -187,9 +177,7 @@ const proxyTraps: ProxyHandler = { ) value = getDataNode(nodes, property, value)(); } - return isWrappable(value) - ? wrap(value, "_SOLID_DEV_" && target[$NAME] && `${target[$NAME]}:${property.toString()}`) - : value; + return isWrappable(value) ? wrap(value) : value; }, has(target, property) { @@ -383,7 +371,7 @@ type MutableKeyOf = KeyOf & keyof PickMutable; type Rest< T, U extends PropertyKey[], - K extends KeyOf = KeyOf + K extends KeyOf = KeyOf > = K extends keyof PickMutable ? [Part, ...RestSetterOrContinue] : K extends KeyOf @@ -514,14 +502,8 @@ export function createStore( throw new Error( `Unexpected type ${typeof unwrappedStore} received when initializing 'createStore'. Expected an object.` ); - const wrappedStore = wrap( - unwrappedStore, - "_SOLID_DEV_" && ((options && options.name) || DEV.hashValue(unwrappedStore)) - ); - if ("_SOLID_DEV_") { - const name = (options && options.name) || DEV.hashValue(unwrappedStore); - DEV.registerGraph(name, { value: unwrappedStore }); - } + const wrappedStore = wrap(unwrappedStore); + if ("_SOLID_DEV_") DEV.registerGraph({ value: unwrappedStore, name: options && options.name }); function setStore(...args: any[]): void { batch(() => { isArray && args.length === 1 diff --git a/packages/solid/test/dev.spec.ts b/packages/solid/test/dev.spec.ts index 8126457b8..11fa00cc5 100644 --- a/packages/solid/test/dev.spec.ts +++ b/packages/solid/test/dev.spec.ts @@ -3,49 +3,52 @@ import { getOwner, createSignal, createEffect, - createComponent, createComputed, - DEV, Owner, createContext } from "../src"; import { createStore, unwrap } from "../store/src"; describe("Dev features", () => { - test("Reactive graph serialization", () => { - let owner: ReturnType, set1: (v: number) => number, setState1: any; + test("Signals being added to sourceMap with user-provided names", () => { + createRoot(() => { + const owner = getOwner()!; + createSignal(3, { name: "test" }); + createSignal(5); + createSignal(6, { name: "explicit" }); + expect(owner).toHaveProperty("sourceMap"); + expect(owner.sourceMap![0].name).toBe("test"); + expect(owner.sourceMap![0].value).toBe(3); + expect(owner.sourceMap![1].name).toBe(undefined); + expect(owner.sourceMap![1].value).toBe(5); + expect(owner.sourceMap![2].name).toBe("explicit"); + expect(owner.sourceMap![2].value).toBe(6); + }); + }); - const SNAPSHOTS = [ - `{"s1773325850":5,"s1773325850-1":5,"c-1":{"explicit":6},"CustomComponent:c-2":{"s533736025":{"firstName":"John","lastName":"Smith"}}}`, - `{"s1773325850":7,"s1773325850-1":5,"c-1":{"explicit":6},"CustomComponent:c-2":{"s533736025":{"firstName":"Matt","lastName":"Smith","middleInitial":"R."}}}` - ]; - const CustomComponent = () => { - const [state, setState] = createStore({ firstName: "John", lastName: "Smith" }); - setState1 = setState; - return ""; - }; + test("Computations can be named", () => { createRoot(() => { - owner = getOwner(); - const [s, set] = createSignal(5); - const [s2] = createSignal(5); - createEffect(() => { - const [s] = createSignal(6, { name: "explicit" }); - }); - createComponent(CustomComponent, {}); - set1 = set; + const owner = getOwner()!; + createComputed(() => {}, undefined, { name: "test" }); + createEffect(() => {}, undefined, { name: "test_effect" }); + createComputed(() => {}); + createEffect(() => {}); + expect(owner).toHaveProperty("owned"); + expect(owner.owned![0].name).toBe("test"); + expect(owner.owned![1].name).toBe("test_effect"); + expect(owner.owned![2].name).toBe(undefined); + expect(owner.owned![3].name).toBe(undefined); }); - expect(JSON.stringify(DEV.serializeGraph(owner!))).toBe(SNAPSHOTS[0]); - set1!(7); - setState1({ middleInitial: "R.", firstName: "Matt" }); - expect(JSON.stringify(DEV.serializeGraph(owner!))).toBe(SNAPSHOTS[1]); }); test("Context nodes can be named", () => { createRoot(dispose => { - const ctx = createContext(undefined, { name: "test" }); - ctx.Provider({ value: undefined, children: undefined }); - const ctxNode = getOwner()!.owned![0]; - expect(ctxNode.name).toBe("test"); + const ctx1 = createContext(undefined); + const ctx2 = createContext(undefined, { name: "test" }); + ctx1.Provider({ value: undefined, children: undefined }); + ctx2.Provider({ value: undefined, children: undefined }); + expect(getOwner()!.owned![0].name).toBe(undefined); + expect(getOwner()!.owned![1].name).toBe("test"); dispose(); }); });