jsx-framework-test-pb 0.1.6 → 0.1.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -1,7 +1,7 @@
1
- export { render, unmount } from './runtime';
2
- export { renderToString, hydrate } from './ssr';
3
- export { Fragment } from './jsx-runtime';
4
- export { isElement } from './utils';
5
- export { state, computed, effect } from './reactivity';
6
- export type { StateObject } from './reactivity';
1
+ export { render, unmount } from './runtime/index.js';
2
+ export { renderToString, hydrate } from './ssr/index.js';
3
+ export { Fragment } from './jsx-runtime.js';
4
+ export { isElement } from './utils/index.js';
5
+ export { state, computed, effect } from './reactivity/index.js';
6
+ export type { StateObject } from './reactivity/index.js';
7
7
  export type { Element, ElementProps, ElementChild, DevElement, FC, HTMLAttributes, CSSProperties } from './types';
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
- export { render, unmount } from './runtime';
2
- export { renderToString, hydrate } from './ssr';
3
- export { Fragment } from './jsx-runtime';
4
- export { isElement } from './utils';
5
- export { state, computed, effect } from './reactivity';
1
+ export { render, unmount } from './runtime/index.js';
2
+ export { renderToString, hydrate } from './ssr/index.js';
3
+ export { Fragment } from './jsx-runtime.js';
4
+ export { isElement } from './utils/index.js';
5
+ export { state, computed, effect } from './reactivity/index.js';
@@ -1,4 +1,4 @@
1
- import { ELEMENT_TYPE, FRAGMENT_TYPE } from './utils';
1
+ import { ELEMENT_TYPE, FRAGMENT_TYPE } from './utils/index.js';
2
2
  export const Fragment = FRAGMENT_TYPE;
3
3
  export function jsxDEV(type, props, key, isStaticChildren, source, self) {
4
4
  return {
@@ -1,4 +1,4 @@
1
- import { ELEMENT_TYPE, FRAGMENT_TYPE } from './utils';
1
+ import { ELEMENT_TYPE, FRAGMENT_TYPE } from './utils/index.js';
2
2
  export const Fragment = FRAGMENT_TYPE;
3
3
  export function jsx(type, props, key) {
4
4
  return {
@@ -1,3 +1,3 @@
1
- import type { Effect, StateObject } from './types';
2
- export declare function effect(fn: Effect): void;
1
+ import type { Effect, StateObject, Cleanup } from './types';
2
+ export declare function effect(fn: Effect): Cleanup;
3
3
  export declare function computed<T>(fn: () => T): StateObject<T>;
@@ -1,11 +1,30 @@
1
- import { setActiveEffect, state } from './state';
1
+ import { setActiveEffect, setActiveDeps, state } from './state';
2
2
  export function effect(fn) {
3
+ let cleanup;
4
+ const deps = new Set();
3
5
  const runner = () => {
6
+ // Clear old subscriptions before re-running
7
+ deps.forEach(sub => sub.delete(runner));
8
+ deps.clear();
9
+ // Run cleanup from previous execution
10
+ cleanup?.();
11
+ // Set context for dependency tracking
4
12
  setActiveEffect(runner);
5
- fn();
13
+ setActiveDeps(deps);
14
+ // Execute effect and capture any returned cleanup
15
+ const result = fn();
16
+ cleanup = typeof result === 'function' ? result : undefined;
17
+ // Clear context
6
18
  setActiveEffect(null);
19
+ setActiveDeps(null);
7
20
  };
8
21
  runner();
22
+ // Return dispose function
23
+ return () => {
24
+ cleanup?.();
25
+ deps.forEach(sub => sub.delete(runner));
26
+ deps.clear();
27
+ };
9
28
  }
10
29
  export function computed(fn) {
11
30
  const s = state(undefined);
@@ -1,3 +1,3 @@
1
- export type { Effect, StateObject } from './types';
1
+ export type { Cleanup, Effect, StateObject } from './types';
2
2
  export { state, isState } from './state';
3
3
  export { effect, computed } from './effect';
@@ -1,5 +1,7 @@
1
1
  import type { Effect, StateObject } from './types';
2
2
  export declare function setActiveEffect(effect: Effect | null): void;
3
3
  export declare function getActiveEffect(): Effect | null;
4
+ export declare function setActiveDeps(deps: Set<Set<Effect>> | null): void;
5
+ export declare function getActiveDeps(): Set<Set<Effect>> | null;
4
6
  export declare function state<T>(initial: T): StateObject<T>;
5
7
  export declare function isState(value: unknown): value is StateObject<unknown>;
@@ -1,24 +1,52 @@
1
1
  let activeEffect = null;
2
+ let activeDeps = null;
3
+ // Batching state
4
+ let pendingEffects = new Set();
5
+ let isFlushing = false;
2
6
  export function setActiveEffect(effect) {
3
7
  activeEffect = effect;
4
8
  }
5
9
  export function getActiveEffect() {
6
10
  return activeEffect;
7
11
  }
12
+ export function setActiveDeps(deps) {
13
+ activeDeps = deps;
14
+ }
15
+ export function getActiveDeps() {
16
+ return activeDeps;
17
+ }
18
+ function queueEffect(effect) {
19
+ pendingEffects.add(effect);
20
+ if (!isFlushing) {
21
+ isFlushing = true;
22
+ queueMicrotask(flushEffects);
23
+ }
24
+ }
25
+ function flushEffects() {
26
+ const effects = pendingEffects;
27
+ pendingEffects = new Set();
28
+ isFlushing = false;
29
+ effects.forEach(fn => fn());
30
+ }
8
31
  export function state(initial) {
9
32
  let value = initial;
10
33
  const subscribers = new Set();
11
34
  return {
12
35
  get val() {
13
- if (activeEffect)
36
+ if (activeEffect) {
14
37
  subscribers.add(activeEffect);
38
+ // Track this subscriber set in the effect's dependencies
39
+ if (activeDeps) {
40
+ activeDeps.add(subscribers);
41
+ }
42
+ }
15
43
  return value;
16
44
  },
17
45
  set val(next) {
18
46
  if (Object.is(value, next))
19
47
  return;
20
48
  value = next;
21
- subscribers.forEach(fn => fn());
49
+ subscribers.forEach(fn => queueEffect(fn));
22
50
  }
23
51
  };
24
52
  }
@@ -1,4 +1,5 @@
1
- export type Effect = () => void;
1
+ export type Cleanup = () => void;
2
+ export type Effect = () => void | Cleanup;
2
3
  export interface StateObject<T> {
3
4
  val: T;
4
5
  }
@@ -1 +1,2 @@
1
+ export declare function cleanupNode(node: Node): void;
1
2
  export declare function createElement(element: any): Node | null;
@@ -1,14 +1,32 @@
1
1
  import { isElement, isValidChild, FRAGMENT_TYPE } from '../utils';
2
2
  import { effect } from '../reactivity';
3
+ // Track cleanups per node for proper disposal
4
+ const nodeCleanups = new WeakMap();
5
+ function trackCleanup(node, cleanup) {
6
+ let cleanups = nodeCleanups.get(node);
7
+ if (!cleanups) {
8
+ cleanups = new Set();
9
+ nodeCleanups.set(node, cleanups);
10
+ }
11
+ cleanups.add(cleanup);
12
+ }
13
+ export function cleanupNode(node) {
14
+ const cleanups = nodeCleanups.get(node);
15
+ if (cleanups) {
16
+ cleanups.forEach(fn => fn());
17
+ cleanups.clear();
18
+ }
19
+ }
3
20
  export function createElement(element) {
4
21
  if (!isValidChild(element)) {
5
22
  return null;
6
23
  }
7
24
  if (typeof element === 'function') {
8
25
  const textNode = document.createTextNode('');
9
- effect(() => {
26
+ const cleanup = effect(() => {
10
27
  textNode.textContent = String(element());
11
28
  });
29
+ trackCleanup(textNode, cleanup);
12
30
  return textNode;
13
31
  }
14
32
  if (typeof element === 'string' || typeof element === 'number') {
@@ -70,9 +88,10 @@ function createHTMLElement(type, props) {
70
88
  }
71
89
  function handleReactiveProp(element, attrName, value) {
72
90
  if (typeof value === 'function' && !attrName.startsWith('on')) {
73
- effect(() => {
91
+ const cleanup = effect(() => {
74
92
  element.setAttribute(attrName, String(value()));
75
93
  });
94
+ trackCleanup(element, cleanup);
76
95
  }
77
96
  else {
78
97
  element.setAttribute(attrName, String(value));
@@ -89,15 +108,21 @@ function appendChildren(parent, children) {
89
108
  }
90
109
  function attachEvent(element, key, handler) {
91
110
  const eventName = key.substring(2).toLowerCase();
92
- element.addEventListener(eventName, handler);
111
+ const listener = handler;
112
+ element.addEventListener(eventName, listener);
113
+ // Track for cleanup on unmount
114
+ trackCleanup(element, () => {
115
+ element.removeEventListener(eventName, listener);
116
+ });
93
117
  }
94
118
  function applyStyles(element, styles) {
95
119
  Object.keys(styles).forEach(key => {
96
120
  const value = styles[key];
97
121
  if (typeof value === 'function') {
98
- effect(() => {
122
+ const cleanup = effect(() => {
99
123
  element.style[key] = value();
100
124
  });
125
+ trackCleanup(element, cleanup);
101
126
  }
102
127
  else {
103
128
  element.style[key] = value;
@@ -1,4 +1,4 @@
1
1
  export type { FiberNode } from './types';
2
2
  export { reconcile } from './reconciler';
3
3
  export { render, unmount } from './render';
4
- export { createElement } from './createElement';
4
+ export { createElement, cleanupNode } from './createElement';
@@ -1,3 +1,3 @@
1
1
  export { reconcile } from './reconciler';
2
2
  export { render, unmount } from './render';
3
- export { createElement } from './createElement';
3
+ export { createElement, cleanupNode } from './createElement';
@@ -1,4 +1,4 @@
1
- import { createElement } from "./createElement";
1
+ import { createElement, cleanupNode } from "./createElement";
2
2
  import { isElement } from "../utils";
3
3
  import { effect } from "../reactivity";
4
4
  function canReuse(oldElement, newElement) {
@@ -40,6 +40,10 @@ function setAttr(el, name, value) {
40
40
  function getInsertionPoint(parent, targetIndex) {
41
41
  return parent.childNodes.item(targetIndex) ?? null;
42
42
  }
43
+ function cleanupTree(node) {
44
+ cleanupNode(node);
45
+ node.childNodes.forEach(child => cleanupTree(child));
46
+ }
43
47
  export function reconcile(parent, newElements, oldFibers = []) {
44
48
  const newFibers = [];
45
49
  const newElementsArray = Array.isArray(newElements) ? newElements : [newElements];
@@ -66,6 +70,8 @@ export function reconcile(parent, newElements, oldFibers = []) {
66
70
  oldFibersByIndex.delete(index);
67
71
  }
68
72
  let fiber;
73
+ let isNewFiber = false;
74
+ let needsMove = false;
69
75
  if (oldFiber && canReuse(oldFiber.element, element)) {
70
76
  fiber = { element, dom: oldFiber.dom, key, index };
71
77
  if (fiber.dom)
@@ -73,19 +79,26 @@ export function reconcile(parent, newElements, oldFibers = []) {
73
79
  if (oldFiber.dom && isElement(element)) {
74
80
  updateElement(oldFiber.dom, element);
75
81
  }
82
+ // Only need to move if position changed
83
+ needsMove = oldFiber.index !== index;
76
84
  }
77
85
  else {
78
86
  const dom = createElement(element);
79
87
  fiber = { element, dom, key, index };
88
+ isNewFiber = true;
89
+ // Clean up old fiber if it existed but couldn't be reused
80
90
  if (oldFiber?.dom?.parentNode === parent) {
91
+ cleanupTree(oldFiber.dom);
81
92
  parent.removeChild(oldFiber.dom);
82
93
  }
94
+ // Insert new element
83
95
  if (dom) {
84
96
  const before = getInsertionPoint(parent, index);
85
97
  parent.insertBefore(dom, before);
86
98
  }
87
99
  }
88
- if (fiber.dom) {
100
+ // Only reposition reused fibers that actually need to move
101
+ if (!isNewFiber && needsMove && fiber.dom) {
89
102
  const before = getInsertionPoint(parent, index);
90
103
  if (fiber.dom !== before) {
91
104
  parent.insertBefore(fiber.dom, before);
@@ -93,8 +106,10 @@ export function reconcile(parent, newElements, oldFibers = []) {
93
106
  }
94
107
  newFibers.push(fiber);
95
108
  });
109
+ // Clean up remaining old fibers
96
110
  [...oldFibersByKey.values(), ...oldFibersByIndex.values()].forEach((fiber) => {
97
111
  if (fiber.dom?.parentNode === parent && !reusedOldDoms.has(fiber.dom)) {
112
+ cleanupTree(fiber.dom);
98
113
  parent.removeChild(fiber.dom);
99
114
  }
100
115
  });
@@ -1,5 +1,6 @@
1
1
  import { reconcile } from './reconciler';
2
2
  import { isElement } from '../utils';
3
+ import { cleanupNode } from './createElement';
3
4
  const instances = new WeakMap();
4
5
  export function render(element, container) {
5
6
  if (!container)
@@ -16,9 +17,15 @@ export function render(element, container) {
16
17
  const newFibers = reconcile(container, children, oldFibers);
17
18
  instances.set(container, newFibers);
18
19
  }
20
+ function cleanupTree(node) {
21
+ cleanupNode(node);
22
+ node.childNodes.forEach(child => cleanupTree(child));
23
+ }
19
24
  export function unmount(container) {
20
25
  if (!container)
21
26
  return;
27
+ // Clean up all nodes recursively before removing
28
+ cleanupTree(container);
22
29
  container.replaceChildren();
23
30
  instances.delete(container);
24
31
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "jsx-framework-test-pb",
3
- "version": "0.1.6",
3
+ "version": "0.1.8",
4
4
  "description": "",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",