From 4d8029fd8abe30b7594c9bd830d520bb1f261b83 Mon Sep 17 00:00:00 2001 From: Abhishiv Saxena Date: Sun, 13 Oct 2024 22:46:15 +0530 Subject: [PATCH] Store improvement (#13) * rf * progress * Update api.ts * v1.3.2 --- package.json | 2 +- src/addons/animation/index.ts | 15 ++--------- src/core/state/index.ts | 1 + src/core/state/storeAPI.ts | 10 ++++--- src/core/state/wire.ts | 8 +++++- src/dom/api.ts | 49 +++++++++++------------------------ src/dom/index.ts | 40 ++++++++++++++++++++++++++-- src/dom/traverser.ts | 2 +- src/dom/types.ts | 15 +++++------ src/stdlib/Each/index.tsx | 5 ++++ src/stdlib/Portal/index.tsx | 5 ++-- src/stdlib/When/index.tsx | 19 +++++++++----- 12 files changed, 98 insertions(+), 73 deletions(-) diff --git a/package.json b/package.json index db49e7c..aaca1e3 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "alfama", - "version": "1.3.1", + "version": "1.3.2", "author": "Abhishiv Saxena", "license": "MIT", "description": "Fine-grained reactive library with no compiler, no magic, and no virtual DOM", diff --git a/src/addons/animation/index.ts b/src/addons/animation/index.ts index 6ca7072..2c39b21 100644 --- a/src/addons/animation/index.ts +++ b/src/addons/animation/index.ts @@ -1,20 +1,9 @@ -import { - animate, - Animation, - AnimationOptions, - linear, - easeInOut, -} from "popmotion"; import { ComponentUtils } from "../../dom"; export const createAnimation = ( utils: ComponentUtils, - initialValue: any, - options: AnimationOptions + initialValue: any ) => { const $signal = utils.signal("animation", initialValue); - const animation = animate({ - ...options, - }); - return [$signal, animation]; + return [$signal]; }; diff --git a/src/core/state/index.ts b/src/core/state/index.ts index 0e8d421..6c2746e 100644 --- a/src/core/state/index.ts +++ b/src/core/state/index.ts @@ -4,3 +4,4 @@ export * from "./wire"; export * from "./store"; export * from "./storeAPI"; export * from "./utils"; +export { dfsPreOrder } from "../../utils"; diff --git a/src/core/state/storeAPI.ts b/src/core/state/storeAPI.ts index 93ef9e8..258cda5 100644 --- a/src/core/state/storeAPI.ts +++ b/src/core/state/storeAPI.ts @@ -41,9 +41,13 @@ export const reify = (cursor: T): T => { const manager: StoreManager = getCursorProxyMeta( s as unknown as ObjPathProxy ); - const cursorPath = getCursor(s); - const v = getValueUsingPath(manager.value as any, cursorPath); - return v as T; + if (manager) { + const cursorPath = getCursor(s); + const v = getValueUsingPath(manager.value as any, cursorPath); + return v as T; + } else { + return cursor; + } }; export const produce = ( diff --git a/src/core/state/wire.ts b/src/core/state/wire.ts index cfd07ae..a26c275 100644 --- a/src/core/state/wire.ts +++ b/src/core/state/wire.ts @@ -109,9 +109,15 @@ export const runWire = ( } }; -const wireReset = (wire: Wire): void => { +export const wireReset = (wire: Wire): void => { wire.lower.forEach(wireReset); wire.sigRS.forEach((signal) => signal.wires.delete(wire)); + wire.storesRS.forEach((store) => { + const manager = getCursorProxyMeta(store); + if (manager) { + manager.wires.delete(wire); + } + }); _initWire(wire); }; diff --git a/src/dom/api.ts b/src/dom/api.ts index 3b13bf5..9bf529e 100644 --- a/src/dom/api.ts +++ b/src/dom/api.ts @@ -27,6 +27,7 @@ import { } from "../core/state"; import { LiveDocumentFragment } from "./dom"; import { getCursorProxyMeta } from "../utils"; +import { unmount, unrender } from "."; export const insertElement = ( renderContext: RenderContext, @@ -165,6 +166,7 @@ export const addNode = ( export const rmNodes = (node: Node | LiveDocumentFragment) => { if (node instanceof LiveDocumentFragment) { const childNodes = getLiveFragmentChildNodes(node); + //console.log("childNodes", childNodes.length, childNodes); childNodes.forEach((c) => c.parentNode?.removeChild(c)); } else { node.parentElement?.removeChild(node); @@ -175,36 +177,8 @@ export const removeNode = (renderCtx: RenderContext, node: TreeStep) => { //console.log("removeNodes", nodes); const nodes = getDescendants(node); // console.log("removeNode nodes", node, nodes); + unrender(nodes); nodes.forEach((step) => { - if (step.type === DOMConstants.ComponentTreeStep) { - //step.wires.length && console.log("s", step, step.wires.length); - step.wires.forEach((w) => { - w.storesRS.forEach((s, manager) => { - if (manager.wires.has(w)) { - //console.log("removing wire", s, manager); - manager.wires.delete(w); - } - }); - w.sigRS.forEach((sig) => { - sig.wires.delete(w); - }); - w.tasks.clear(); - }); - step.wires = []; - } - if (step.dom) { - if ( - step.type === DOMConstants.ComponentTreeStep && - step.onUnmount.length > 0 - ) { - step.onUnmount.forEach((el) => el()); - for (var s in step.state.stores) { - // todo unsubscribe from store - } - } - rmNodes(step.dom); - step.dom = undefined; - } renderCtx.reg.delete(step); step.parent ? arrayRemove(step.parent.children, step) : null; }); @@ -212,8 +186,8 @@ export const removeNode = (renderCtx: RenderContext, node: TreeStep) => { export const renderTreeStep = (renderCtx: RenderContext, element: VElement) => { // todo: move this to getRenderContext so it clears DOM properly + //if (window.ss) return; renderCtx.el.innerHTML = ""; - const { root, registry } = reifyTree(renderCtx, element); const id = getVirtualElementId(root.node); if (!id) throw createError(101); @@ -238,18 +212,20 @@ export const getRenderContext = ( emitter: new EEmitter(), } as RenderContext); + //console.log("renderContext", renderContext); renderContext.prevState.clear(); // so HMR is properly cleaned up renderContext.reg.forEach((step) => { + //console.log("step", step); if ( step.type === DOMConstants.ComponentTreeStep && step.onUnmount.length > 0 ) step.onUnmount.forEach((el) => el(step)); if (step.type === DOMConstants.ComponentTreeStep) { - if (step.mount && step.dom instanceof Element) { - step.dom.remove(); + if (step.mount && step.dom) { + rmNodes(step.dom); } } @@ -263,12 +239,17 @@ export const getRenderContext = ( } ancestor = ancestor.parent; } - renderContext.prevState.set(ids, step.state); + // dont preserve stores for now + renderContext.prevState.set(ids, { + ...step.state, + stores: {}, + signals: {}, + }); } }); + //if (window.ss) return; renderContext.reg.clear(); - (container as any)[id] = renderContext; return renderContext; diff --git a/src/dom/index.ts b/src/dom/index.ts index 8a37345..a5f9c0c 100644 --- a/src/dom/index.ts +++ b/src/dom/index.ts @@ -6,8 +6,10 @@ import { Component, ComponentVElement, NativeVElement, + RenderContext, + TreeStep, } from "./types"; -import { getRenderContext, renderTreeStep } from "./api"; +import { getRenderContext, renderTreeStep, rmNodes } from "./api"; import { reifyTree } from "./traverser"; import type { GenericEventAttrs, @@ -17,7 +19,8 @@ import type { SVGElements, TargetedEvent, } from "./jsx"; -import { Wire } from "../core/state"; +import { runWire, StoreManager, Wire, wireReset } from "../core/state"; +import { getCursorProxyMeta } from "../utils"; export * from "./types"; export * as DOMConstants from "./constants"; @@ -74,6 +77,39 @@ export function render( return renderContext; } +export const unmount = (step: TreeStep) => { + if (step.dom) rmNodes(step.dom); +}; + +export function unrender(arg: RenderContext | TreeStep[]) { + const steps = Array.isArray(arg) ? arg : Array.from(arg.reg); + steps.forEach((step) => { + unmount(step); + if (step.type == DOMConstants.ComponentTreeStep) { + step.wires.forEach((w) => { + wireReset(w); + }); + step.wires = []; + Object.values(step.state.stores).forEach((s) => { + const manager = getCursorProxyMeta(s as any); + manager.tasks.clear(); + manager.wires.clear(); + // manager.unsubscribe(); + }); + step.onUnmount.forEach((el) => el(step)); + // step.state.stores = {}; + Object.values(step.state.signals).forEach((sig) => { + sig.wires.clear(); + }); + step.state.ctx.clear(); + } else if (step.type == DOMConstants.WireTreeStep) { + const wire = step.node as Wire; + wireReset(wire); + } + }); + return arg; +} + // used to store parent wire in context export const ParentWireContext = defineContext("ParentWireContext"); diff --git a/src/dom/traverser.ts b/src/dom/traverser.ts index 1831eaa..2d7f9c6 100644 --- a/src/dom/traverser.ts +++ b/src/dom/traverser.ts @@ -161,7 +161,7 @@ export const getTreeStep = ( return { type: DOMConstants.ComponentTreeStep, ...step, - state: { signals: {}, ctx: new WeakMap(), stores: {} }, + state: { signals: {}, ctx: new Map(), stores: {} }, wires: [], onMount: [], onUnmount: [], diff --git a/src/dom/types.ts b/src/dom/types.ts index a2e0c7e..e89678e 100644 --- a/src/dom/types.ts +++ b/src/dom/types.ts @@ -1,7 +1,7 @@ import { Signal, Wire, StoreCursor, WireFunction } from "../core/state"; import * as DOMConstants from "./constants"; -export type DOMNode = HTMLElement | DocumentFragment; +export type DOMNodeType = HTMLElement | DocumentFragment; export type PrimitiveType = string | number | boolean | null | undefined; @@ -68,7 +68,7 @@ export type VElement = // Tree steps export type BaseTreeStep = { - dom?: DOMNode; + dom?: DOMNodeType; id?: string; parent?: TreeStep; meta?: Record; @@ -89,14 +89,11 @@ export interface ComponentTreeStep extends BaseTreeStep { onUnmount: Function[]; onMount: Function[]; } + export interface ComponentTreeStepState { - signals: { - [name: string]: Signal; - }; - stores: { - [name: string]: StoreCursor; - }; - ctx: WeakMap; + signals: Record; + stores: Record; + ctx: Map; } export interface PrimitiveTreeStep extends BaseTreeStep { diff --git a/src/stdlib/Each/index.tsx b/src/stdlib/Each/index.tsx index 26850e6..ad345b9 100644 --- a/src/stdlib/Each/index.tsx +++ b/src/stdlib/Each/index.tsx @@ -60,14 +60,17 @@ export const Each: ( if (!isArray) throw new Error(" needs array"); const getItemCursor = (item: ExtractElement) => { + const store: StoreManager = (listCursor as any)[META_FLAG]; const listValue: typeof listCursor = getValueUsingPath( store.value as any, listCursorPath ) as typeof listCursor; + // console.log("listValue", listValue, item); const index = listValue.indexOf(item); if (index > -1) { return props.cursor[index]; } else { + // debugger; console.error("accessing no existent item", index, item, listValue); } }; @@ -107,6 +110,7 @@ export const Each: ( if (path.slice(0, listCursorPath.length).join("/") !== path.join("/")) return; if (data?.name === "push") { + // console.log("data", data); data.args.forEach((arg, i) => { const index = previousChildren.length + i; const { treeStep, el } = renderArray( @@ -118,6 +122,7 @@ export const Each: ( utils, getItemCursor ); + // console.log({ treeStep, el, index, previousChildren }); const { registry, root } = reifyTree(renderContext, el, pStep); addNode(renderContext, pStep, root); }); diff --git a/src/stdlib/Portal/index.tsx b/src/stdlib/Portal/index.tsx index 28896b7..467a552 100644 --- a/src/stdlib/Portal/index.tsx +++ b/src/stdlib/Portal/index.tsx @@ -1,10 +1,11 @@ /** @jsx h **/ import { h, component } from "../../dom"; +import { rmNodes } from "../../dom/api"; import { ComponentTreeStep, VElement } from "../../dom/types"; export type PortalProps = { - mount: HTMLElement; + mount?: HTMLElement; children: VElement; }; @@ -24,7 +25,7 @@ export const Portal = component( } ) => { onUnmount((step: any) => { - if (step && step.dom && step.dom.remove) step.dom.remove(); + if (step && step.dom) rmNodes(step.dom); }); (parentStep as ComponentTreeStep).mount = props.mount; return props.children; diff --git a/src/stdlib/When/index.tsx b/src/stdlib/When/index.tsx index b280135..f6a8f8d 100644 --- a/src/stdlib/When/index.tsx +++ b/src/stdlib/When/index.tsx @@ -7,9 +7,11 @@ import { ParentWireContext } from "../../dom/index"; import { addNode, removeNode } from "../../dom/api"; import { reifyTree } from "../../dom/traverser"; -export type WhenViews = { [key: string]: () => VElement }; +export type WhenViews = + | Record VElement> + | ((value: any) => VElement); export type WhenProps = { - condition: ($: SubToken) => keyof WhenViews | boolean; + condition: ($: SubToken) => any; views: WhenViews; fallback?: () => VElement; options?: { cached?: boolean }; @@ -31,13 +33,16 @@ export const When = component( renderContext, } ) => { - // todo: important memory leak - const rootWire = wire(($: SubToken) => {}); - setContext(ParentWireContext, signal("$wire", rootWire)); const underlying = utils.wire(props.condition); const value = underlying.run(); - const getView = (value: any) => - props.views[value as unknown as any] || props.fallback; + const getView = (value: any) => { + //console.log("p", value, props.views, typeof props.views); + if (typeof props.views === "function") { + return () => (props.views as Function)(value); + } else { + return props.views[value as unknown as any] || props.fallback; + } + }; const task = (value: any) => { const view = getView(value); const u = view ? view() : null;