From 792e7deae06561293a7198050b3fec23b9b1c9af Mon Sep 17 00:00:00 2001 From: Ryan Carniato Date: Wed, 9 Aug 2023 10:42:10 -0700 Subject: [PATCH] fix #1821 improve context performance --- .changeset/young-months-type.md | 5 ++ packages/solid/src/reactive/signal.ts | 90 +++++++++++++++------------ packages/solid/src/server/index.ts | 1 + packages/solid/src/server/reactive.ts | 67 ++++++++++++-------- 4 files changed, 96 insertions(+), 67 deletions(-) create mode 100644 .changeset/young-months-type.md diff --git a/.changeset/young-months-type.md b/.changeset/young-months-type.md new file mode 100644 index 000000000..bfcbd1523 --- /dev/null +++ b/.changeset/young-months-type.md @@ -0,0 +1,5 @@ +--- +"solid-js": patch +--- + +fix #1821 improve context performance diff --git a/packages/solid/src/reactive/signal.ts b/packages/solid/src/reactive/signal.ts index 66e4b5f4d..9f7cd7823 100644 --- a/packages/solid/src/reactive/signal.ts +++ b/packages/solid/src/reactive/signal.ts @@ -140,6 +140,7 @@ export function createRoot(fn: RootFunction, detachedOwner?: typeof Owner) const listener = Listener, owner = Owner, unowned = fn.length === 0, + current = detachedOwner === undefined ? owner : detachedOwner, root: Owner = unowned ? "_SOLID_DEV_" ? { owned: null, cleanups: null, context: null, owner: null } @@ -147,8 +148,8 @@ export function createRoot(fn: RootFunction, detachedOwner?: typeof Owner) : { owned: null, cleanups: null, - context: null, - owner: detachedOwner === undefined ? owner : detachedOwner + context: current ? current.context : null, + owner: current }, updateFn = unowned ? "_SOLID_DEV_" @@ -347,7 +348,7 @@ export function createEffect( ): void { runEffects = runUserEffects; const c = createComputation(fn, value!, false, STALE, "_SOLID_DEV_" ? options : undefined), - s = SuspenseContext && lookup(Owner, SuspenseContext.id); + s = SuspenseContext && useContext(SuspenseContext); if (s) c.suspense = s; if (!options || !options.render) c.user = true; Effects ? Effects.push(c) : updateComputation(c); @@ -378,7 +379,7 @@ export function createReaction(onInvalidate: () => void, options?: EffectOptions 0, "_SOLID_DEV_" ? options : undefined ), - s = SuspenseContext && lookup(Owner, SuspenseContext.id); + s = SuspenseContext && useContext(SuspenseContext); if (s) c.suspense = s; c.user = true; return (tracking: () => void) => { @@ -646,7 +647,7 @@ export function createResource( } function read() { - const c = SuspenseContext && lookup(Owner, SuspenseContext.id), + const c = SuspenseContext && useContext(SuspenseContext), v = value(), err = error(); if (err !== undefined && !pr) throw err; @@ -656,7 +657,7 @@ export function createResource( if (pr) { if (c.resolved && Transition && loadedUnderTransition) Transition.promises.add(pr); else if (!contexts.has(c)) { - c.increment(); + c.increment!(); contexts.add(c); } } @@ -981,7 +982,7 @@ export function onCleanup any>(fn: T): T { export function catchError(fn: () => T, handler: (err: Error) => void) { ERROR || (ERROR = Symbol("error")); Owner = createComputation(undefined!, undefined, true); - Owner.context = { [ERROR]: [handler] }; + Owner.context = { ...Owner.context, [ERROR]: [handler] }; if (Transition && Transition.running) Transition.sources.add(Owner as Memo); try { return fn(); @@ -992,25 +993,6 @@ export function catchError(fn: () => T, handler: (err: Error) => void) { } } -/** - * @deprecated since version 1.7.0 and will be removed in next major - use catchError instead - * onError - run an effect whenever an error is thrown within the context of the child scopes - * @param fn an error handler that receives the error - * - * * If the error is thrown again inside the error handler, it will trigger the next available parent handler - * - * @description https://www.solidjs.com/docs/latest/api#onerror - */ -export function onError(fn: (err: Error) => void): void { - ERROR || (ERROR = Symbol("error")); - if (Owner === null) - "_SOLID_DEV_" && - console.warn("error handlers created outside a `createRoot` or `render` will never be run"); - else if (Owner.context === null) Owner.context = { [ERROR]: [fn] }; - else if (!Owner.context[ERROR]) Owner.context[ERROR] = [fn]; - else Owner.context[ERROR].push(fn); -} - export function getListener() { return Listener; } @@ -1181,8 +1163,9 @@ export function createContext( * @description https://www.solidjs.com/docs/latest/api#usecontext */ export function useContext(context: Context): T { - let ctx; - return (ctx = lookup(Owner, context.id)) !== undefined ? ctx : context.defaultValue; + return Owner && Owner.context && Owner.context[context.id] !== undefined + ? Owner.context[context.id] + : context.defaultValue; } export type ResolvedJSXElement = Exclude; @@ -1401,7 +1384,7 @@ function createComputation( cleanups: null, value: init, owner: Owner, - context: null, + context: Owner ? Owner.context : null, pure }; @@ -1659,7 +1642,6 @@ function cleanNode(node: Owner) { } if (Transition && Transition.running) (node as Computation).tState = 0; else (node as Computation).state = 0; - node.context = null; "_SOLID_DEV_" && delete node.sourceMap; } @@ -1687,7 +1669,7 @@ function runErrors(err: unknown, fns: ((err: any) => void)[], owner: Owner | nul } function handleError(err: unknown, owner = Owner) { - const fns = ERROR && lookup(owner, ERROR); + const fns = ERROR && owner && owner.context && owner.context[ERROR]; const error = castError(err); if (!fns) throw error; @@ -1701,14 +1683,6 @@ function handleError(err: unknown, owner = Owner) { else runErrors(error, fns, owner); } -function lookup(owner: Owner | null, key: symbol | string): any { - return owner - ? owner.context && owner.context[key] !== undefined - ? owner.context[key] - : lookup(owner.owner, key) - : undefined; -} - function resolveChildren(children: JSX.Element | Accessor): ResolvedChildren { if (typeof children === "function" && !children.length) return resolveChildren(children()); if (Array.isArray(children)) { @@ -1728,7 +1702,7 @@ function createProvider(id: symbol, options?: EffectOptions) { createRenderEffect( () => (res = untrack(() => { - Owner!.context = { [id]: props.value }; + Owner!.context = { ...Owner!.context, [id]: props.value }; return children(() => props.children); })), undefined, @@ -1739,3 +1713,39 @@ function createProvider(id: symbol, options?: EffectOptions) { } type TODO = any; + +/** + * @deprecated since version 1.7.0 and will be removed in next major - use catchError instead + * onError - run an effect whenever an error is thrown within the context of the child scopes + * @param fn an error handler that receives the error + * + * * If the error is thrown again inside the error handler, it will trigger the next available parent handler + * + * @description https://www.solidjs.com/docs/latest/api#onerror + */ +export function onError(fn: (err: Error) => void): void { + ERROR || (ERROR = Symbol("error")); + if (Owner === null) + "_SOLID_DEV_" && + console.warn("error handlers created outside a `createRoot` or `render` will never be run"); + else if (Owner.context === null || !Owner.context[ERROR]) { + // terrible de-opt + Owner.context = { ...Owner.context, [ERROR]: [fn] }; + mutateContext(Owner, ERROR, [fn]); + } else Owner.context[ERROR].push(fn); +} + +function mutateContext(o: Owner, key: symbol, value: any) { + if (o.owned) { + for (let i = 0; i < o.owned.length; i++) { + if (o.owned[i].context === o.context) mutateContext(o.owned[i], key, value); + if (!o.owned[i].context) { + o.owned[i].context = o.context; + mutateContext(o.owned[i], key, value); + } else if (!o.owned[i].context[key]) { + o.owned[i].context[key] = value; + mutateContext(o.owned[i], key, value); + } + } + } +} diff --git a/packages/solid/src/server/index.ts b/packages/solid/src/server/index.ts index 44dd8e5f3..7a9f92f0f 100644 --- a/packages/solid/src/server/index.ts +++ b/packages/solid/src/server/index.ts @@ -1,4 +1,5 @@ export { + catchError, createRoot, createSignal, createComputed, diff --git a/packages/solid/src/server/reactive.ts b/packages/solid/src/server/reactive.ts index 0252ddeb2..774da29a5 100644 --- a/packages/solid/src/server/reactive.ts +++ b/packages/solid/src/server/reactive.ts @@ -19,7 +19,7 @@ export function castError(err: unknown): Error { } function handleError(err: unknown, owner = Owner): void { - const fns = lookup(owner, ERROR); + const fns = owner && owner.context && owner.context[ERROR]; const error = castError(err); if (!fns) throw error; @@ -41,7 +41,7 @@ interface Owner { } export function createOwner(): Owner { - const o = { owner: Owner, context: null, owned: null, cleanups: null }; + const o = { owner: Owner, context: Owner ? Owner.context : null, owned: null, cleanups: null }; if (Owner) { if (!Owner.owned) Owner.owned = [o]; else Owner.owned.push(o); @@ -51,12 +51,13 @@ export function createOwner(): Owner { export function createRoot(fn: (dispose: () => void) => T, detachedOwner?: typeof Owner): T { const owner = Owner, + current = detachedOwner === undefined ? owner : detachedOwner, root = fn.length === 0 ? UNOWNED : { - context: null, - owner: detachedOwner === undefined ? owner : detachedOwner, + context: current ? current.context : null, + owner: current, owned: null, cleanups: null }; @@ -172,7 +173,9 @@ export function cleanNode(node: Owner) { } export function catchError(fn: () => T, handler: (err: Error) => void) { - Owner = { owner: Owner, context: { [ERROR]: [handler] }, owned: null, cleanups: null }; + const owner = createOwner(); + owner.context = { ...owner.context, [ERROR]: [handler] }; + Owner = owner; try { return fn(); } catch (err) { @@ -182,17 +185,6 @@ export function catchError(fn: () => T, handler: (err: Error) => void) { } } -/** - * @deprecated since version 1.7.0 and will be removed in next major - use catchError instead - */ -export function onError(fn: (err: Error) => void): void { - if (Owner) { - if (Owner.context === null) Owner.context = { [ERROR]: [fn] }; - else if (!Owner.context[ERROR]) Owner.context[ERROR] = [fn]; - else Owner.context[ERROR].push(fn); - } -} - export function getListener() { return null; } @@ -210,8 +202,9 @@ export function createContext(defaultValue?: T): Context { } export function useContext(context: Context): T { - let ctx; - return (ctx = lookup(Owner, context.id)) !== undefined ? ctx : context.defaultValue; + return Owner && Owner.context && Owner.context[context.id] !== undefined + ? Owner.context[context.id] + : context.defaultValue; } export function getOwner() { @@ -240,14 +233,6 @@ export function runWithOwner(o: typeof Owner, fn: () => T): T | undefined { } } -export function lookup(owner: Owner | null, key: symbol | string): any { - return owner - ? owner.context && owner.context[key] !== undefined - ? owner.context[key] - : lookup(owner.owner, key) - : undefined; -} - function resolveChildren(children: any): unknown { if (typeof children === "function" && !children.length) return resolveChildren(children()); if (Array.isArray(children)) { @@ -264,7 +249,7 @@ function resolveChildren(children: any): unknown { function createProvider(id: symbol) { return function provider(props: { value: unknown; children: any }) { return createMemo(() => { - Owner!.context = { [id]: props.value }; + Owner!.context = { ...Owner!.context, [id]: props.value }; return children(() => props.children) as unknown as JSX.Element; }); }; @@ -365,3 +350,31 @@ export function from( } export function enableExternalSource(factory: any) {} + +/** + * @deprecated since version 1.7.0 and will be removed in next major - use catchError instead + */ +export function onError(fn: (err: Error) => void): void { + if (Owner) { + if (Owner.context === null || !Owner.context[ERROR]) { + // terrible de-opt + Owner.context = { ...Owner.context, [ERROR]: [fn] }; + mutateContext(Owner, ERROR, [fn]); + } else Owner.context[ERROR].push(fn); + } +} + +function mutateContext(o: Owner, key: symbol, value: any) { + if (o.owned) { + for (let i = 0; i < o.owned.length; i++) { + if (o.owned[i].context === o.context) mutateContext(o.owned[i], key, value); + if (!o.owned[i].context) { + o.owned[i].context = o.context; + mutateContext(o.owned[i], key, value); + } else if (!o.owned[i].context[key]) { + o.owned[i].context[key] = value; + mutateContext(o.owned[i], key, value); + } + } + } +}