vaderjs 2.3.3 → 2.3.4

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.
Files changed (3) hide show
  1. package/index.ts +158 -83
  2. package/main.js +23 -5
  3. package/package.json +1 -1
package/index.ts CHANGED
@@ -13,7 +13,9 @@ let deletions: Fiber[] | null = null;
13
13
  let wipFiber: Fiber | null = null;
14
14
  let hookIndex = 0;
15
15
  let isRenderScheduled = false;
16
-
16
+ // Add to the top of your Vader.js file
17
+
18
+
17
19
  interface Fiber {
18
20
  type?: string | Function;
19
21
  dom?: Node;
@@ -28,9 +30,13 @@ interface Fiber {
28
30
  effectTag?: "PLACEMENT" | "UPDATE" | "DELETION";
29
31
  hooks?: Hook[];
30
32
  key?: string | number | null;
33
+ propsCache?: Record<string, any>;
34
+ __compareProps?: (prev: any, next: any) => boolean;
35
+ __skipMemo?: boolean;
36
+ _needsUpdate?: boolean;
31
37
  }
32
38
 
33
- interface VNode {
39
+ export interface VNode {
34
40
  type: string | Function;
35
41
  props: {
36
42
  children: VNode[];
@@ -84,33 +90,55 @@ const isGone = (prev: object, next: object) => (key: string) => !(key in next);
84
90
  * @returns {Node} The created DOM node.
85
91
  */
86
92
  function createDom(fiber: Fiber): Node {
87
- const dom =
88
- fiber.type == "TEXT_ELEMENT"
89
- ? document.createTextNode("")
90
- : document.createElement(fiber.type as string);
93
+ let dom: Node;
94
+
95
+ if (fiber.type === "TEXT_ELEMENT") {
96
+ dom = document.createTextNode("");
97
+ } else {
98
+ const isSvg = isSvgElement(fiber);
99
+ if (isSvg) {
100
+ dom = document.createElementNS("http://www.w3.org/2000/svg", fiber.type as string);
101
+ } else {
102
+ dom = document.createElement(fiber.type as string);
103
+ }
104
+ }
91
105
 
92
106
  updateDom(dom, {}, fiber.props);
93
107
  return dom;
94
108
  }
95
109
 
110
+ function isSvgElement(fiber: Fiber): boolean {
111
+ // Check if the fiber is an <svg> itself or inside an <svg>
112
+ let parent = fiber.parent;
113
+ if (fiber.type === "svg") return true;
114
+ while (parent) {
115
+ if (parent.type === "svg") return true;
116
+ parent = parent.parent;
117
+ }
118
+ return false;
119
+ }
120
+
121
+
96
122
  /**
97
123
  * Applies updated props to a DOM node.
98
124
  * @param {Node} dom - The DOM node to update.
99
125
  * @param {object} prevProps - The previous properties.
100
126
  * @param {object} nextProps - The new properties.
101
127
  */
102
- function updateDom(dom: Node, prevProps: object, nextProps: object): void {
128
+ function updateDom(dom: Node, prevProps: any, nextProps: any): void {
103
129
  prevProps = prevProps || {};
104
130
  nextProps = nextProps || {};
105
131
 
132
+ const isSvg = dom instanceof SVGElement;
133
+
106
134
  // Remove old or changed event listeners
107
135
  Object.keys(prevProps)
108
136
  .filter(isEvent)
109
- .filter((key) => !(key in nextProps) || isNew(prevProps, nextProps)(key))
110
- .forEach((name) => {
137
+ .filter(key => !(key in nextProps) || isNew(prevProps, nextProps)(key))
138
+ .forEach(name => {
111
139
  const eventType = name.toLowerCase().substring(2);
112
140
  if (typeof prevProps[name] === 'function') {
113
- dom.removeEventListener(eventType, prevProps[name]);
141
+ (dom as Element).removeEventListener(eventType, prevProps[name]);
114
142
  }
115
143
  });
116
144
 
@@ -118,12 +146,17 @@ function createDom(fiber: Fiber): Node {
118
146
  Object.keys(prevProps)
119
147
  .filter(isProperty)
120
148
  .filter(isGone(prevProps, nextProps))
121
- .forEach((name) => {
122
- // FIX: Handle both `class` and `className`
149
+ .forEach(name => {
123
150
  if (name === 'className' || name === 'class') {
124
- (dom as HTMLElement).removeAttribute("class");
151
+ (dom as Element).removeAttribute('class');
152
+ } else if (name === 'style') {
153
+ (dom as HTMLElement).style.cssText = '';
125
154
  } else {
126
- dom[name] = "";
155
+ if (isSvg) {
156
+ (dom as Element).removeAttribute(name);
157
+ } else {
158
+ (dom as any)[name] = '';
159
+ }
127
160
  }
128
161
  });
129
162
 
@@ -131,14 +164,24 @@ function createDom(fiber: Fiber): Node {
131
164
  Object.keys(nextProps)
132
165
  .filter(isProperty)
133
166
  .filter(isNew(prevProps, nextProps))
134
- .forEach((name) => {
135
- if (name === 'style' && typeof nextProps[name] === 'string') {
136
- (dom as HTMLElement).style.cssText = nextProps[name];
167
+ .forEach(name => {
168
+ if (name === 'style') {
169
+ const style = nextProps[name];
170
+ if (typeof style === 'string') {
171
+ (dom as HTMLElement).style.cssText = style;
172
+ } else if (typeof style === 'object' && style !== null) {
173
+ for (const [key, value] of Object.entries(style)) {
174
+ (dom as HTMLElement).style[key] = value;
175
+ }
176
+ }
137
177
  } else if (name === 'className' || name === 'class') {
138
- // FIX: Handle both `class` and `className`
139
- (dom as HTMLElement).className = nextProps[name];
178
+ (dom as Element).setAttribute('class', nextProps[name]);
140
179
  } else {
141
- dom[name] = nextProps[name];
180
+ if (isSvg) {
181
+ (dom as Element).setAttribute(name, nextProps[name]);
182
+ } else {
183
+ (dom as any)[name] = nextProps[name];
184
+ }
142
185
  }
143
186
  });
144
187
 
@@ -146,15 +189,32 @@ function createDom(fiber: Fiber): Node {
146
189
  Object.keys(nextProps)
147
190
  .filter(isEvent)
148
191
  .filter(isNew(prevProps, nextProps))
149
- .forEach((name) => {
192
+ .forEach(name => {
150
193
  const eventType = name.toLowerCase().substring(2);
151
194
  const handler = nextProps[name];
152
195
  if (typeof handler === 'function') {
153
- dom.addEventListener(eventType, handler);
196
+ (dom as Element).addEventListener(eventType, handler);
197
+ }
198
+ });
199
+
200
+ Object.keys(nextProps)
201
+ .filter(isEvent)
202
+ .filter(isNew(prevProps, nextProps))
203
+ .forEach(name => {
204
+ const eventType = name.toLowerCase().substring(2);
205
+ const handler = nextProps[name];
206
+ if (typeof handler === 'function') {
207
+ // Remove old listener first if it exists
208
+ if (prevProps[name]) {
209
+ dom.removeEventListener(eventType, prevProps[name]);
210
+ }
211
+ // Add new listener with passive: true for better performance
212
+ dom.addEventListener(eventType, handler, { passive: true });
154
213
  }
155
214
  });
156
215
  }
157
216
 
217
+
158
218
  /**
159
219
  * Commits the entire work-in-progress tree to the DOM.
160
220
  */
@@ -227,11 +287,7 @@ export function render(element: VNode, container: Node): void {
227
287
  };
228
288
  deletions = [];
229
289
  nextUnitOfWork = wipRoot;
230
-
231
- if (!isRenderScheduled) {
232
- isRenderScheduled = true;
233
- requestAnimationFrame(workLoop);
234
- }
290
+ requestAnimationFrame(workLoop);
235
291
  }
236
292
 
237
293
  /**
@@ -247,7 +303,7 @@ function workLoop(): void {
247
303
  deletions = [];
248
304
  nextUnitOfWork = wipRoot;
249
305
  }
250
-
306
+
251
307
  while (nextUnitOfWork) {
252
308
  nextUnitOfWork = performUnitOfWork(nextUnitOfWork);
253
309
  }
@@ -257,6 +313,7 @@ function workLoop(): void {
257
313
  }
258
314
  }
259
315
 
316
+
260
317
  /**
261
318
  * Performs work on a single fiber unit.
262
319
  * @param {Fiber} fiber - The fiber to perform work on.
@@ -264,7 +321,7 @@ function workLoop(): void {
264
321
  */
265
322
  function performUnitOfWork(fiber: Fiber): Fiber | null {
266
323
  const isFunctionComponent = fiber.type instanceof Function;
267
-
324
+
268
325
  if (isFunctionComponent) {
269
326
  updateFunctionComponent(fiber);
270
327
  } else {
@@ -289,19 +346,20 @@ function performUnitOfWork(fiber: Fiber): Fiber | null {
289
346
  * Updates a function component fiber.
290
347
  * @param {Fiber} fiber - The function component fiber to update.
291
348
  */
292
- function updateFunctionComponent(fiber: Fiber): void {
349
+ function updateFunctionComponent(fiber: Fiber) {
293
350
  wipFiber = fiber;
294
351
  hookIndex = 0;
295
- wipFiber.hooks = fiber.alternate ? fiber.alternate.hooks : [];
352
+ fiber.hooks = fiber.alternate?.hooks || [];
296
353
 
297
- const children = [fiber.type(fiber.props)]
354
+ // Directly call the component function without memoization
355
+ // The 'createComponent' call is removed.
356
+ const children = [(fiber.type as Function)(fiber.props)]
298
357
  .flat()
299
358
  .filter(child => child != null && typeof child !== 'boolean')
300
- .map(child => typeof child === "object" ? child : createTextElement(child));
359
+ .map(child => typeof child === 'object' ? child : createTextElement(child));
301
360
 
302
361
  reconcileChildren(fiber, children);
303
362
  }
304
-
305
363
  /**
306
364
  * Updates a host component fiber (DOM element).
307
365
  * @param {Fiber} fiber - The host component fiber to update.
@@ -318,32 +376,31 @@ function updateHostComponent(fiber: Fiber): void {
318
376
  * @param {Fiber} wipFiber - The work-in-progress fiber.
319
377
  * @param {VNode[]} elements - The new child elements.
320
378
  */
321
- function reconcileChildren(wipFiber: Fiber, elements: VNode[]): void {
379
+ function reconcileChildren(wipFiber: Fiber, elements: VNode[]) {
322
380
  let index = 0;
323
381
  let oldFiber = wipFiber.alternate?.child;
324
- let prevSibling = null;
325
-
326
- const oldFibersByKey = new Map<string | number, Fiber>();
327
-
382
+ let prevSibling: Fiber | null = null;
383
+
384
+ // Create map of existing fibers by key
385
+ const existingFibers = new Map<string | number | null, Fiber>();
328
386
  while (oldFiber) {
329
- const key = oldFiber.key != null ? oldFiber.key : index;
330
- oldFibersByKey.set(key, oldFiber);
387
+ const key = oldFiber.key ?? index;
388
+ existingFibers.set(key, oldFiber);
331
389
  oldFiber = oldFiber.sibling;
332
390
  index++;
333
391
  }
334
392
 
335
393
  index = 0;
336
- prevSibling = null;
337
-
338
394
  for (; index < elements.length; index++) {
339
395
  const element = elements[index];
340
- const key = element.key != null ? element.key : index;
341
- const oldFiber = oldFibersByKey.get(key);
342
- const sameType = oldFiber && element.type === oldFiber.type;
343
-
396
+ const key = element?.key ?? index;
397
+ const oldFiber = existingFibers.get(key);
398
+
399
+ const sameType = oldFiber && element && element.type === oldFiber.type;
344
400
  let newFiber: Fiber | null = null;
345
401
 
346
402
  if (sameType) {
403
+ // Reuse the fiber
347
404
  newFiber = {
348
405
  type: oldFiber.type,
349
406
  props: element.props,
@@ -351,37 +408,44 @@ function reconcileChildren(wipFiber: Fiber, elements: VNode[]): void {
351
408
  parent: wipFiber,
352
409
  alternate: oldFiber,
353
410
  effectTag: "UPDATE",
354
- key,
411
+ hooks: oldFiber.hooks,
412
+ key
355
413
  };
356
- oldFibersByKey.delete(key);
357
- } else {
358
- if (element) {
359
- newFiber = {
360
- type: element.type,
361
- props: element.props,
362
- dom: null,
363
- parent: wipFiber,
364
- alternate: null,
365
- effectTag: "PLACEMENT",
366
- key,
367
- };
368
- }
414
+ existingFibers.delete(key);
415
+ } else if (element) {
416
+ // Create new fiber
417
+ newFiber = {
418
+ type: element.type,
419
+ props: element.props,
420
+ dom: null,
421
+ parent: wipFiber,
422
+ alternate: null,
423
+ effectTag: "PLACEMENT",
424
+ key
425
+ };
426
+ }
427
+
428
+ if (oldFiber && !sameType) {
429
+ oldFiber.effectTag = "DELETION";
430
+ deletions.push(oldFiber);
369
431
  }
370
432
 
371
- if (prevSibling == null) {
433
+ if (index === 0) {
372
434
  wipFiber.child = newFiber;
373
- } else {
435
+ } else if (prevSibling && newFiber) {
374
436
  prevSibling.sibling = newFiber;
375
437
  }
376
- prevSibling = newFiber;
438
+
439
+ if (newFiber) {
440
+ prevSibling = newFiber;
441
+ }
377
442
  }
378
443
 
379
- oldFibersByKey.forEach(fiber => {
444
+ // Mark remaining old fibers for deletion
445
+ existingFibers.forEach(fiber => {
380
446
  fiber.effectTag = "DELETION";
381
447
  deletions.push(fiber);
382
448
  });
383
-
384
- if (prevSibling) prevSibling.sibling = null;
385
449
  }
386
450
 
387
451
  /**
@@ -432,39 +496,45 @@ function createTextElement(text: string): VNode {
432
496
  * @param {T|(() => T)} initial - The initial state value or initializer function.
433
497
  * @returns {[T, (action: T | ((prevState: T) => T)) => void]} A stateful value and a function to update it.
434
498
  */
435
- export function useState<T>(
436
- initial: T | (() => T)
437
- ): [T, (action: T | ((prevState: T) => T)) => void] {
499
+
500
+
501
+
502
+ export function useState<T>(initial: T | (() => T)): [T, (action: T | ((prevState: T) => T)) => void] {
438
503
  if (!wipFiber) {
439
504
  throw new Error("Hooks can only be called inside a Vader.js function component.");
440
505
  }
441
506
 
442
- let hook = wipFiber.hooks[hookIndex];
507
+ let hook = wipFiber.hooks[hookIndex];
443
508
  if (!hook) {
444
509
  hook = {
445
- state: typeof initial === "function" ? (initial as () => T)() : initial,
446
- queue: []
510
+ state: typeof initial === "function" ? (initial as () => T)() : initial,
511
+ queue: [],
512
+ _needsUpdate: false
447
513
  };
448
514
  wipFiber.hooks[hookIndex] = hook;
449
515
  }
450
516
 
451
- hook.queue.forEach((action) => {
452
- hook.state = typeof action === "function" ? action(hook.state) : action;
453
- });
454
- hook.queue = [];
455
-
456
517
  const setState = (action: T | ((prevState: T) => T)) => {
457
- hook.queue.push(action);
458
- if (!isRenderScheduled) {
459
- isRenderScheduled = true;
518
+ // Calculate new state based on current state
519
+ const newState = typeof action === "function"
520
+ ? (action as (prevState: T) => T)(hook.state)
521
+ : action;
522
+
523
+ hook.state = newState;
524
+
525
+ // Reset work-in-progress root to trigger re-r
526
+
527
+ deletions = [];
528
+ nextUnitOfWork = wipRoot;
529
+
530
+ // Start the render process
460
531
  requestAnimationFrame(workLoop);
461
- }
462
532
  };
463
533
 
464
534
  hookIndex++;
465
535
  return [hook.state, setState];
466
536
  }
467
-
537
+
468
538
  /**
469
539
  * A React-like useEffect hook for side effects.
470
540
  * @param {Function} callback - The effect callback.
@@ -529,6 +599,10 @@ export function Match({ when, children }: { when: boolean, children: VNode[] }):
529
599
  return when ? children : null;
530
600
  }
531
601
 
602
+ export function Show({ when, children }: { when: boolean, children: VNode[] }): VNode | null {
603
+ return when ? children : null;
604
+ }
605
+
532
606
  /**
533
607
  * A React-like useRef hook for mutable references.
534
608
  * @template T
@@ -923,3 +997,4 @@ export function useOnClickOutside(ref: { current: HTMLElement | null }, handler:
923
997
  };
924
998
  }, [ref, handler]);
925
999
  }
1000
+
package/main.js CHANGED
@@ -29,6 +29,17 @@ const colors = {
29
29
  cyan: "\x1b[36m",
30
30
  };
31
31
 
32
+ function safeWatch(dir, cb) {
33
+ try {
34
+ const watcher = fsSync.watch(dir, { recursive: true }, cb);
35
+ watcher.on("error", (err) => logger.warn(`Watcher error on ${dir}:`, err));
36
+ return watcher;
37
+ } catch (err) {
38
+ logger.warn(`Failed to watch ${dir}:`, err);
39
+ }
40
+ }
41
+
42
+
32
43
  const logger = {
33
44
  _log: (color, ...args) => console.log(color, ...args, colors.reset),
34
45
  info: (...args) => logger._log(colors.cyan, "ℹ", ...args),
@@ -46,8 +57,8 @@ async function timedStep(name, fn) {
46
57
  const duration = (performance.now() - start).toFixed(2);
47
58
  logger.success(`Finished '${name}' in ${duration}ms`);
48
59
  } catch (e) {
49
- logger.error(`Error during '${name}':`, e.message);
50
- process.exit(1);
60
+ logger.error(`Error during '${name}':`, e);
61
+ if (!isDev) process.exit(1);
51
62
  }
52
63
  }
53
64
 
@@ -101,13 +112,14 @@ async function runPluginHook(hookName) {
101
112
  try {
102
113
  await plugin[hookName](vaderAPI);
103
114
  } catch (e) {
104
- logger.error(`Plugin hook error (${hookName} in ${plugin.name || 'anonymous plugin'}):`, e);
115
+ logger.error(`Plugin hook error (${hookName} in ${plugin.name || 'anonymous'}):`, e);
105
116
  }
106
117
  }
107
118
  }
108
119
  }
109
120
 
110
121
 
122
+
111
123
  // --- BUILD LOGIC ---
112
124
 
113
125
  /**
@@ -272,7 +284,7 @@ async function copyPublicAssets() {
272
284
  <body>
273
285
  <div id="app"></div>
274
286
  <script type="module">
275
- import App from '${name !== 'index' ? name : ''}/index.js';
287
+ import App from '${name !== 'index' ? "/" + name : ''}/index.js';
276
288
  import * as Vader from '/src/vader/index.js';
277
289
  window.Vader = Vader
278
290
  Vader.render(Vader.createElement(App, null), document.getElementById("app"));
@@ -379,7 +391,7 @@ async function runDevServer() {
379
391
 
380
392
  const watchDirs = [APP_DIR, SRC_DIR, PUBLIC_DIR].filter(fsSync.existsSync);
381
393
  for (const dir of watchDirs) {
382
- fsSync.watch(dir, { recursive: true }, debouncedBuild);
394
+ safeWatch(dir, debouncedBuild);
383
395
  }
384
396
  }
385
397
 
@@ -454,3 +466,9 @@ main().catch(err => {
454
466
  logger.error("An unexpected error occurred:", err);
455
467
  process.exit(1);
456
468
  });
469
+ process.on("unhandledRejection", (err) => {
470
+ logger.error("Unhandled Promise rejection:", err);
471
+ });
472
+ process.on("uncaughtException", (err) => {
473
+ logger.error("Uncaught Exception:", err);
474
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vaderjs",
3
- "version": "2.3.3",
3
+ "version": "2.3.4",
4
4
  "description": "A simple and powerful JavaScript library for building modern web applications.",
5
5
  "bin": {
6
6
  "vaderjs": "./main.js"