@unsetsoft/ryunixjs 0.4.15-nightly.9 → 0.5.2

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/Ryunix.js CHANGED
@@ -1,17 +1,17 @@
1
1
  (function (global, factory) {
2
- typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
3
- typeof define === 'function' && define.amd ? define(['exports'], factory) :
4
- (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.Ryunix = {}));
5
- })(this, (function (exports) { 'use strict';
2
+ typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('lodash')) :
3
+ typeof define === 'function' && define.amd ? define(['exports', 'lodash'], factory) :
4
+ (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.Ryunix = {}, global.lodash));
5
+ })(this, (function (exports, lodash) { 'use strict';
6
6
 
7
7
  const vars = {
8
- containerRoot: null,
9
- nextUnitOfWork: null,
10
- currentRoot: null,
11
- wipRoot: null,
12
- deletions: null,
13
- wipFiber: null,
14
- hookIndex: null,
8
+ containerRoot: undefined,
9
+ nextUnitOfWork: undefined,
10
+ currentRoot: undefined,
11
+ wipRoot: undefined,
12
+ deletions: undefined,
13
+ wipFiber: undefined,
14
+ hookIndex: undefined,
15
15
  };
16
16
 
17
17
  const reg = /[A-Z]/g;
@@ -19,6 +19,9 @@
19
19
  const RYUNIX_TYPES = Object.freeze({
20
20
  TEXT_ELEMENT: Symbol('text.element'),
21
21
  RYUNIX_EFFECT: Symbol('ryunix.effect'),
22
+ RYUNIX_MEMO: Symbol('ryunix.memo'),
23
+ RYUNIX_URL_QUERY: Symbol('ryunix.urlQuery'),
24
+ RYUNIX_REF: Symbol('ryunix.ref'),
22
25
  });
23
26
 
24
27
  const STRINGS = Object.freeze({
@@ -27,6 +30,7 @@
27
30
  style: 'ryunix-style',
28
31
  className: 'ryunix-class',
29
32
  children: 'children',
33
+ boolean: 'boolean',
30
34
  });
31
35
 
32
36
  const OLD_STRINGS = Object.freeze({
@@ -40,14 +44,20 @@
40
44
  DELETION: Symbol(),
41
45
  });
42
46
 
43
- const cloneElement = (element, props) => {
44
- return {
45
- ...element,
46
- props: {
47
- ...element.props,
48
- ...props,
49
- },
47
+ const Fragments = (props) => {
48
+ return props.children
49
+ };
50
+
51
+ const childArray = (children, out) => {
52
+ out = out || [];
53
+ if (children == undefined || typeof children == STRINGS.boolean) ; else if (Array.isArray(children)) {
54
+ children.some((child) => {
55
+ childArray(child, out);
56
+ });
57
+ } else {
58
+ out.push(children);
50
59
  }
60
+ return out
51
61
  };
52
62
 
53
63
  /**
@@ -68,25 +78,33 @@
68
78
  */
69
79
 
70
80
  const createElement = (type, props, ...children) => {
81
+ children = childArray(children, []);
71
82
  return {
72
83
  type,
73
84
  props: {
74
85
  ...props,
75
- children: children
76
- .flat()
77
- .map((child, index) =>
78
- typeof child === STRINGS.object && child !== null
79
- ? cloneElement(child, { key: index })
80
- : child,
81
- ),
86
+ children: children.map((child) =>
87
+ typeof child === STRINGS.object ? child : createTextElement(child),
88
+ ),
82
89
  },
83
90
  }
84
91
  };
85
92
 
86
- const Fragments = ({ children }) => {
87
- return children.map((c, index) =>
88
- createElement(c.type, c.props, c.props.children),
89
- )
93
+ /**
94
+ * The function creates a text element with a given text value.
95
+ * @param text - The text content that will be used to create a new text element.
96
+ * @returns A JavaScript object with a `type` property set to `"TEXT_ELEMENT"` and a `props` property
97
+ * that contains a `nodeValue` property set to the `text` parameter and an empty `children` array.
98
+ */
99
+
100
+ const createTextElement = (text) => {
101
+ return {
102
+ type: RYUNIX_TYPES.TEXT_ELEMENT,
103
+ props: {
104
+ nodeValue: text,
105
+ children: [],
106
+ },
107
+ }
90
108
  };
91
109
 
92
110
  /**
@@ -119,50 +137,6 @@
119
137
  vars.containerRoot = document.getElementById(rootElement);
120
138
  };
121
139
 
122
- const isEvent = (key) => key.startsWith('on');
123
- const isProperty = (key) => key !== STRINGS.children && !isEvent(key);
124
- const isNew = (prev, next) => (key) => prev[key] !== next[key];
125
- const isGone = (next) => (key) => !(key in next);
126
- const hasDepsChanged = (prevDeps, nextDeps) =>
127
- !prevDeps ||
128
- !nextDeps ||
129
- prevDeps.length !== nextDeps.length ||
130
- prevDeps.some((dep, index) => dep !== nextDeps[index]);
131
-
132
- /**
133
- * The function cancels all effect hooks in a given fiber.
134
- * @param fiber - The "fiber" parameter is likely referring to a data structure used in React.js to
135
- * represent a component and its state. It contains information about the component's props, state, and
136
- * children, as well as metadata used by React to manage updates and rendering. The function
137
- * "cancelEffects" is likely intended
138
- */
139
- const cancelEffects = (fiber) => {
140
- if (fiber.hooks) {
141
- fiber.hooks
142
- .filter((hook) => hook.tag === RYUNIX_TYPES.RYUNIX_EFFECT && hook.cancel)
143
- .forEach((effectHook) => {
144
- effectHook.cancel();
145
- });
146
- }
147
- };
148
-
149
- /**
150
- * The function runs all effect hooks in a given fiber.
151
- * @param fiber - The "fiber" parameter is likely referring to a data structure used in the
152
- * implementation of a fiber-based reconciliation algorithm, such as the one used in React. A fiber
153
- * represents a unit of work that needs to be performed by the reconciliation algorithm, and it
154
- * contains information about a component and its children, as
155
- */
156
- const runEffects = (fiber) => {
157
- if (fiber.hooks) {
158
- fiber.hooks
159
- .filter((hook) => hook.tag === RYUNIX_TYPES.RYUNIX_EFFECT && hook.effect)
160
- .forEach((effectHook) => {
161
- effectHook.cancel = effectHook.effect();
162
- });
163
- }
164
- };
165
-
166
140
  /**
167
141
  * @description The function creates a state.
168
142
  * @param initial - The initial value of the state for the hook.
@@ -203,8 +177,10 @@
203
177
  vars.deletions = [];
204
178
  };
205
179
 
206
- vars.wipFiber.hooks.push(hook);
207
- vars.hookIndex++;
180
+ if (vars.wipFiber && vars.wipFiber.hooks) {
181
+ vars.wipFiber.hooks.push(hook);
182
+ vars.hookIndex++;
183
+ }
208
184
  return [hook.state, setState]
209
185
  };
210
186
 
@@ -217,23 +193,31 @@
217
193
  * between renders, the effect will be re-run. If the array is empty, the effect will only run once on
218
194
  * mount and never again.
219
195
  */
220
- const useEffect = (effect, deps) => {
196
+
197
+ const useEffect = (callback, deps) => {
221
198
  const oldHook =
222
199
  vars.wipFiber.alternate &&
223
200
  vars.wipFiber.alternate.hooks &&
224
201
  vars.wipFiber.alternate.hooks[vars.hookIndex];
225
202
 
226
- const hasChanged = hasDepsChanged(oldHook ? oldHook.deps : undefined, deps);
227
-
228
203
  const hook = {
229
- tag: RYUNIX_TYPES.RYUNIX_EFFECT,
230
- effect: hasChanged ? effect : null,
231
- cancel: hasChanged && oldHook && oldHook.cancel,
204
+ type: RYUNIX_TYPES.RYUNIX_EFFECT,
232
205
  deps,
233
206
  };
234
207
 
235
- vars.wipFiber.hooks.push(hook);
236
- vars.hookIndex++;
208
+ if (!oldHook) {
209
+ // invoke callback if this is the first time
210
+ callback();
211
+ } else {
212
+ if (!lodash.isEqual(oldHook.deps, hook.deps)) {
213
+ callback();
214
+ }
215
+ }
216
+
217
+ if (vars.wipFiber.hooks) {
218
+ vars.wipFiber.hooks.push(hook);
219
+ vars.hookIndex++;
220
+ }
237
221
  };
238
222
 
239
223
  /**
@@ -242,8 +226,6 @@
242
226
  * @returns The `useQuery` function returns the `query` property of the `hook` object.
243
227
  */
244
228
  const useQuery = () => {
245
- vars.hookIndex++;
246
-
247
229
  const oldHook =
248
230
  vars.wipFiber.alternate &&
249
231
  vars.wipFiber.alternate.hooks &&
@@ -256,101 +238,107 @@
256
238
  const Query = hasOld ? hasOld : params;
257
239
 
258
240
  const hook = {
259
- tag: RYUNIX_TYPES.RYUNIX_EFFECT,
241
+ type: RYUNIX_TYPES.RYUNIX_URL_QUERY,
260
242
  query: Query,
261
243
  };
262
244
 
263
- vars.wipFiber.hooks.push(hook);
245
+ if (vars.wipFiber.hooks) {
246
+ vars.wipFiber.hooks.push(hook);
247
+ vars.hookIndex++;
248
+ }
264
249
 
265
250
  return hook.query
266
251
  };
267
252
 
268
- const Router = ({ path, component, children }) => {
269
- const [currentPath, setCurrentPath] = useStore(window.location.pathname);
270
-
271
- useEffect(() => {
272
- const onLocationChange = () => {
273
- setCurrentPath(() => window.location.pathname);
274
- };
253
+ const useRef = (initial) => {
254
+ const oldHook =
255
+ vars.wipFiber.alternate &&
256
+ vars.wipFiber.alternate.hooks &&
257
+ vars.wipFiber.alternate.hooks[vars.hookIndex];
275
258
 
276
- window.addEventListener('navigate', onLocationChange);
277
- window.addEventListener('pushsatate', onLocationChange);
278
- window.addEventListener('popstate', onLocationChange);
259
+ const hook = {
260
+ type: RYUNIX_TYPES.RYUNIX_REF,
261
+ value: oldHook ? oldHook.value : { current: initial },
262
+ };
279
263
 
280
- return () => {
281
- setTimeout(() => {
282
- window.removeEventListener('navigate', onLocationChange);
283
- window.removeEventListener('pushsatate', onLocationChange);
284
- window.removeEventListener('popstate', onLocationChange);
285
- }, 100);
286
- }
287
- }, [currentPath]);
264
+ if (vars.wipFiber.hooks) {
265
+ vars.wipFiber.hooks.push(hook);
266
+ vars.hookIndex++;
267
+ }
288
268
 
289
- return currentPath === path ? component() : null
269
+ return hook.value
290
270
  };
291
271
 
292
- const Navigate = () => {
293
- /**
294
- * The function `push` is used to push a new state to the browser's history and trigger a custom
295
- * event called 'pushstate'.
296
- * @param to - The `to` parameter is a string representing the URL path to which you want to
297
- * navigate.
298
- * @param [state] - The `state` parameter is an optional object that represents the state associated
299
- * with the new history entry. It can be used to store any data that you want to associate with the
300
- * new URL. When you navigate back or forward in the browser history, this state object will be
301
- * passed to the `popstate
302
- * @returns The function `push` does not have a return statement, so it returns `undefined` by
303
- * default.
304
- */
305
- const push = (to, state = {}) => {
306
- if (window.location.pathname === to) return
272
+ const useMemo = (comp, deps) => {
273
+ const oldHook =
274
+ vars.wipFiber.alternate &&
275
+ vars.wipFiber.alternate.hooks &&
276
+ vars.wipFiber.alternate.hooks[vars.hookIndex];
307
277
 
308
- window.history.pushState(state, '', to);
309
- const navigationEvent = new PopStateEvent('popstate');
310
- window.dispatchEvent(navigationEvent);
278
+ const hook = {
279
+ type: RYUNIX_TYPES.RYUNIX_MEMO,
280
+ value: null,
281
+ deps,
311
282
  };
312
283
 
313
- return { push }
314
- };
315
-
316
- const Link = (props) => {
317
- if (props.style) {
318
- throw new Error(
319
- 'The style attribute is not supported on internal components, use className.',
320
- )
321
- }
322
- if (props.to === '') {
323
- throw new Error("'to=' cannot be empty.")
324
- }
325
- if (props.className === '') {
326
- throw new Error('className cannot be empty.')
327
- }
328
- if (props.label === '' && !props.children) {
329
- throw new Error("'label=' cannot be empty.")
284
+ if (oldHook) {
285
+ if (lodash.isEqual(oldHook.deps, hook.deps)) {
286
+ hook.value = oldHook.value;
287
+ } else {
288
+ hook.value = comp();
289
+ }
290
+ } else {
291
+ hook.value = comp();
330
292
  }
331
293
 
332
- if (!props.to) {
333
- throw new Error("Missig 'to' param.")
294
+ if (vars.wipFiber.hooks) {
295
+ vars.wipFiber.hooks.push(hook);
296
+ vars.hookIndex++;
334
297
  }
335
- const preventReload = (event) => {
336
- if (event.metaKey || event.ctrlKey) {
337
- return
338
- }
339
298
 
340
- event.preventDefault();
341
- const { push } = Navigate();
342
- push(props.to);
343
- };
299
+ return hook.value
300
+ };
301
+ const useCallback = (callback, deps) => {
302
+ return useMemo(() => callback, deps)
303
+ };
344
304
 
345
- const anchor = {
346
- href: props.to,
347
- onClick: preventReload,
348
- ...props,
349
- };
305
+ const isEvent = (key) => key.startsWith('on');
306
+ const isProperty = (key) => key !== STRINGS.children && !isEvent(key);
307
+ const isNew = (prev, next) => (key) => prev[key] !== next[key];
308
+ const isGone = (next) => (key) => !(key in next);
350
309
 
351
- const children = props.label ? props.label : props.children;
310
+ /**
311
+ * The function cancels all effect hooks in a given fiber.
312
+ * @param fiber - The "fiber" parameter is likely referring to a data structure used in React.js to
313
+ * represent a component and its state. It contains information about the component's props, state, and
314
+ * children, as well as metadata used by React to manage updates and rendering. The function
315
+ * "cancelEffects" is likely intended
316
+ */
317
+ const cancelEffects = (fiber) => {
318
+ if (fiber.hooks) {
319
+ fiber.hooks
320
+ .filter((hook) => hook.tag === RYUNIX_TYPES.RYUNIX_EFFECT && hook.cancel)
321
+ .forEach((effectHook) => {
322
+ effectHook.cancel();
323
+ });
324
+ }
325
+ };
352
326
 
353
- return createElement('a', anchor, children)
327
+ /**
328
+ * The function runs all effect hooks in a given fiber.
329
+ * @param fiber - The "fiber" parameter is likely referring to a data structure used in the
330
+ * implementation of a fiber-based reconciliation algorithm, such as the one used in React. A fiber
331
+ * represents a unit of work that needs to be performed by the reconciliation algorithm, and it
332
+ * contains information about a component and its children, as
333
+ */
334
+ const runEffects = (fiber) => {
335
+ if (fiber.hooks) {
336
+ fiber.hooks
337
+ .filter((hook) => hook.tag === RYUNIX_TYPES.RYUNIX_EFFECT && hook.effect)
338
+ .forEach((effectHook) => {
339
+ effectHook.cancel = effectHook.effect();
340
+ });
341
+ }
354
342
  };
355
343
 
356
344
  /**
@@ -457,9 +445,11 @@
457
445
  */
458
446
  const commitRoot = () => {
459
447
  vars.deletions.forEach(commitWork);
460
- commitWork(vars.wipRoot.child);
461
- vars.currentRoot = vars.wipRoot;
462
- vars.wipRoot = null;
448
+ if (vars.wipRoot && vars.wipRoot.child) {
449
+ commitWork(vars.wipRoot.child);
450
+ vars.currentRoot = vars.wipRoot;
451
+ }
452
+ vars.wipRoot = undefined;
463
453
  };
464
454
 
465
455
  /**
@@ -481,13 +471,13 @@
481
471
  const domParent = domParentFiber.dom;
482
472
 
483
473
  if (fiber.effectTag === EFFECT_TAGS.PLACEMENT) {
484
- if (fiber.dom != null) {
474
+ if (fiber.dom != undefined) {
485
475
  domParent.appendChild(fiber.dom);
486
476
  }
487
477
  runEffects(fiber);
488
478
  } else if (fiber.effectTag === EFFECT_TAGS.UPDATE) {
489
479
  cancelEffects(fiber);
490
- if (fiber.dom != null) {
480
+ if (fiber.dom != undefined) {
491
481
  updateDom(fiber.dom, fiber.alternate.props, fiber.props);
492
482
  }
493
483
  runEffects(fiber);
@@ -509,9 +499,9 @@
509
499
  * @param domParent - The parent DOM element from which the fiber's DOM element needs to be removed.
510
500
  */
511
501
  const commitDeletion = (fiber, domParent) => {
512
- if (fiber.dom) {
502
+ if (fiber && fiber.dom) {
513
503
  domParent.removeChild(fiber.dom);
514
- } else {
504
+ } else if (fiber && fiber.child) {
515
505
  commitDeletion(fiber.child, domParent);
516
506
  }
517
507
  };
@@ -537,7 +527,7 @@
537
527
  let oldFiber = wipFiber.alternate && wipFiber.alternate.child;
538
528
  let prevSibling;
539
529
 
540
- while (index < elements.length || oldFiber != null) {
530
+ while (index < elements.length || oldFiber != undefined) {
541
531
  const element = elements[index];
542
532
  let newFiber;
543
533
 
@@ -545,9 +535,9 @@
545
535
 
546
536
  if (sameType) {
547
537
  newFiber = {
548
- type: oldFiber.type,
538
+ type: oldFiber ? oldFiber.type : undefined,
549
539
  props: element.props,
550
- dom: oldFiber.dom,
540
+ dom: oldFiber ? oldFiber.dom : undefined,
551
541
  parent: wipFiber,
552
542
  alternate: oldFiber,
553
543
  effectTag: EFFECT_TAGS.UPDATE,
@@ -557,9 +547,9 @@
557
547
  newFiber = {
558
548
  type: element.type,
559
549
  props: element.props,
560
- dom: null,
550
+ dom: undefined,
561
551
  parent: wipFiber,
562
- alternate: null,
552
+ alternate: undefined,
563
553
  effectTag: EFFECT_TAGS.PLACEMENT,
564
554
  };
565
555
  }
@@ -574,7 +564,7 @@
574
564
 
575
565
  if (index === 0) {
576
566
  wipFiber.child = newFiber;
577
- } else if (element) {
567
+ } else if (element && prevSibling) {
578
568
  prevSibling.sibling = newFiber;
579
569
  }
580
570
 
@@ -599,8 +589,16 @@
599
589
  vars.wipFiber = fiber;
600
590
  vars.hookIndex = 0;
601
591
  vars.wipFiber.hooks = [];
602
- const children = [fiber.type(fiber.props)];
603
- reconcileChildren(fiber, children);
592
+ const children = fiber.type(fiber.props);
593
+ let childArr = [];
594
+ if (Array.isArray(children)) {
595
+ // Fragment results returns array
596
+ childArr = [...children];
597
+ } else {
598
+ // Normal function component returns single root node
599
+ childArr = [children];
600
+ }
601
+ reconcileChildren(fiber, childArr);
604
602
  };
605
603
 
606
604
  /**
@@ -613,7 +611,7 @@
613
611
  if (!fiber.dom) {
614
612
  fiber.dom = createDom(fiber);
615
613
  }
616
- reconcileChildren(fiber, fiber.props.children.flat());
614
+ reconcileChildren(fiber, fiber.props.children);
617
615
  };
618
616
 
619
617
  var Components = /*#__PURE__*/Object.freeze({
@@ -656,7 +654,7 @@
656
654
  * @returns The function `performUnitOfWork` returns the next fiber to be processed. If the current
657
655
  * fiber has a child, it returns the child. Otherwise, it looks for the next sibling of the current
658
656
  * fiber. If there are no more siblings, it goes up the tree to the parent and looks for the next
659
- * sibling of the parent. The function returns `null` if there are no more fibers to process.
657
+ * sibling of the parent. The function returns `undefined` if there are no more fibers to process.
660
658
  */
661
659
  const performUnitOfWork = (fiber) => {
662
660
  const isFunctionComponent = fiber.type instanceof Function;
@@ -675,6 +673,7 @@
675
673
  }
676
674
  nextFiber = nextFiber.parent;
677
675
  }
676
+ return undefined
678
677
  };
679
678
 
680
679
  var Workers = /*#__PURE__*/Object.freeze({
@@ -698,12 +697,12 @@
698
697
  window.Ryunix = Ryunix;
699
698
 
700
699
  exports.Fragments = Fragments;
701
- exports.Link = Link;
702
- exports.Navigate = Navigate;
703
- exports.Router = Router;
704
700
  exports.default = Ryunix;
701
+ exports.useCallback = useCallback;
705
702
  exports.useEffect = useEffect;
703
+ exports.useMemo = useMemo;
706
704
  exports.useQuery = useQuery;
705
+ exports.useRef = useRef;
707
706
  exports.useStore = useStore;
708
707
 
709
708
  Object.defineProperty(exports, '__esModule', { value: true });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@unsetsoft/ryunixjs",
3
- "version": "0.4.15-nightly.9",
3
+ "version": "0.5.2",
4
4
  "license": "MIT",
5
5
  "main": "./dist/Ryunix.js",
6
6
  "types": "./dist/Ryunix.d.ts",
@@ -10,7 +10,6 @@
10
10
  },
11
11
  "homepage": "https://github.com/UnSetSoft/Ryunixjs#readme",
12
12
  "scripts": {
13
- "build:ts": "npm run lint && tsc",
14
13
  "build:js": "rollup ./src/main.js --file ./dist/Ryunix.js --format umd --name Ryunix",
15
14
  "prepublishOnly": "npm run build:js",
16
15
  "postinstall": "npm run build:js",
@@ -20,13 +19,8 @@
20
19
  },
21
20
  "dependencies": {
22
21
  "eslint": "8.56.0",
23
- "@typescript-eslint/parser": "6.17.0",
24
- "@typescript-eslint/eslint-plugin": "6.17.0",
25
- "rollup": "4.9.2",
26
- "ts-node": "10.9.2",
27
- "tsup": "8.0.1",
28
- "typescript": "5.3.3",
29
- "tslint": "6.1.3"
22
+ "lodash": "^4.17.20",
23
+ "rollup": "4.9.2"
30
24
  },
31
25
  "engines": {
32
26
  "node": ">=18.16.0"
@@ -7,9 +7,11 @@ import { EFFECT_TAGS, vars } from '../utils/index'
7
7
  */
8
8
  const commitRoot = () => {
9
9
  vars.deletions.forEach(commitWork)
10
- commitWork(vars.wipRoot.child)
11
- vars.currentRoot = vars.wipRoot
12
- vars.wipRoot = null
10
+ if (vars.wipRoot && vars.wipRoot.child) {
11
+ commitWork(vars.wipRoot.child)
12
+ vars.currentRoot = vars.wipRoot
13
+ }
14
+ vars.wipRoot = undefined
13
15
  }
14
16
 
15
17
  /**
@@ -31,13 +33,13 @@ const commitWork = (fiber) => {
31
33
  const domParent = domParentFiber.dom
32
34
 
33
35
  if (fiber.effectTag === EFFECT_TAGS.PLACEMENT) {
34
- if (fiber.dom != null) {
36
+ if (fiber.dom != undefined) {
35
37
  domParent.appendChild(fiber.dom)
36
38
  }
37
39
  runEffects(fiber)
38
40
  } else if (fiber.effectTag === EFFECT_TAGS.UPDATE) {
39
41
  cancelEffects(fiber)
40
- if (fiber.dom != null) {
42
+ if (fiber.dom != undefined) {
41
43
  updateDom(fiber.dom, fiber.alternate.props, fiber.props)
42
44
  }
43
45
  runEffects(fiber)
@@ -59,9 +61,9 @@ const commitWork = (fiber) => {
59
61
  * @param domParent - The parent DOM element from which the fiber's DOM element needs to be removed.
60
62
  */
61
63
  const commitDeletion = (fiber, domParent) => {
62
- if (fiber.dom) {
64
+ if (fiber && fiber.dom) {
63
65
  domParent.removeChild(fiber.dom)
64
- } else {
66
+ } else if (fiber && fiber.child) {
65
67
  commitDeletion(fiber.child, domParent)
66
68
  }
67
69
  }
@@ -13,8 +13,16 @@ const updateFunctionComponent = (fiber) => {
13
13
  vars.wipFiber = fiber
14
14
  vars.hookIndex = 0
15
15
  vars.wipFiber.hooks = []
16
- const children = [fiber.type(fiber.props)]
17
- reconcileChildren(fiber, children)
16
+ const children = fiber.type(fiber.props)
17
+ let childArr = []
18
+ if (Array.isArray(children)) {
19
+ // Fragment results returns array
20
+ childArr = [...children]
21
+ } else {
22
+ // Normal function component returns single root node
23
+ childArr = [children]
24
+ }
25
+ reconcileChildren(fiber, childArr)
18
26
  }
19
27
 
20
28
  /**
@@ -27,7 +35,7 @@ const updateHostComponent = (fiber) => {
27
35
  if (!fiber.dom) {
28
36
  fiber.dom = createDom(fiber)
29
37
  }
30
- reconcileChildren(fiber, fiber.props.children.flat())
38
+ reconcileChildren(fiber, fiber.props.children)
31
39
  }
32
40
 
33
41
  export { updateFunctionComponent, updateHostComponent }
@@ -1,5 +1,23 @@
1
1
  import { RYUNIX_TYPES, STRINGS } from '../utils/index'
2
2
 
3
+ const Fragments = (props) => {
4
+ return props.children
5
+ }
6
+
7
+ const childArray = (children, out) => {
8
+ out = out || []
9
+ if (children == undefined || typeof children == STRINGS.boolean) {
10
+ // pass
11
+ } else if (Array.isArray(children)) {
12
+ children.some((child) => {
13
+ childArray(child, out)
14
+ })
15
+ } else {
16
+ out.push(children)
17
+ }
18
+ return out
19
+ }
20
+
3
21
  const cloneElement = (element, props) => {
4
22
  return {
5
23
  ...element,
@@ -28,17 +46,14 @@ const cloneElement = (element, props) => {
28
46
  */
29
47
 
30
48
  const createElement = (type, props, ...children) => {
49
+ children = childArray(children, [])
31
50
  return {
32
51
  type,
33
52
  props: {
34
53
  ...props,
35
- children: children
36
- .flat()
37
- .map((child, index) =>
38
- typeof child === STRINGS.object && child !== null
39
- ? cloneElement(child, { key: index })
40
- : child,
41
- ),
54
+ children: children.map((child) =>
55
+ typeof child === STRINGS.object ? child : createTextElement(child),
56
+ ),
42
57
  },
43
58
  }
44
59
  }
@@ -60,10 +75,4 @@ const createTextElement = (text) => {
60
75
  }
61
76
  }
62
77
 
63
- const Fragments = ({ children }) => {
64
- return children.map((c, index) =>
65
- createElement(c.type, c.props, c.props.children),
66
- )
67
- }
68
-
69
- export { createElement, createTextElement, Fragments }
78
+ export { createElement, createTextElement, Fragments, cloneElement }
package/src/lib/hooks.js CHANGED
@@ -1,6 +1,5 @@
1
- import { hasDepsChanged } from './effects'
2
1
  import { RYUNIX_TYPES, STRINGS, vars } from '../utils/index'
3
-
2
+ import { isEqual } from 'lodash'
4
3
  /**
5
4
  * @description The function creates a state.
6
5
  * @param initial - The initial value of the state for the hook.
@@ -41,8 +40,10 @@ const useStore = (initial) => {
41
40
  vars.deletions = []
42
41
  }
43
42
 
44
- vars.wipFiber.hooks.push(hook)
45
- vars.hookIndex++
43
+ if (vars.wipFiber && vars.wipFiber.hooks) {
44
+ vars.wipFiber.hooks.push(hook)
45
+ vars.hookIndex++
46
+ }
46
47
  return [hook.state, setState]
47
48
  }
48
49
 
@@ -55,23 +56,31 @@ const useStore = (initial) => {
55
56
  * between renders, the effect will be re-run. If the array is empty, the effect will only run once on
56
57
  * mount and never again.
57
58
  */
58
- const useEffect = (effect, deps) => {
59
+
60
+ const useEffect = (callback, deps) => {
59
61
  const oldHook =
60
62
  vars.wipFiber.alternate &&
61
63
  vars.wipFiber.alternate.hooks &&
62
64
  vars.wipFiber.alternate.hooks[vars.hookIndex]
63
65
 
64
- const hasChanged = hasDepsChanged(oldHook ? oldHook.deps : undefined, deps)
65
-
66
66
  const hook = {
67
- tag: RYUNIX_TYPES.RYUNIX_EFFECT,
68
- effect: hasChanged ? effect : null,
69
- cancel: hasChanged && oldHook && oldHook.cancel,
67
+ type: RYUNIX_TYPES.RYUNIX_EFFECT,
70
68
  deps,
71
69
  }
72
70
 
73
- vars.wipFiber.hooks.push(hook)
74
- vars.hookIndex++
71
+ if (!oldHook) {
72
+ // invoke callback if this is the first time
73
+ callback()
74
+ } else {
75
+ if (!isEqual(oldHook.deps, hook.deps)) {
76
+ callback()
77
+ }
78
+ }
79
+
80
+ if (vars.wipFiber.hooks) {
81
+ vars.wipFiber.hooks.push(hook)
82
+ vars.hookIndex++
83
+ }
75
84
  }
76
85
 
77
86
  /**
@@ -80,8 +89,6 @@ const useEffect = (effect, deps) => {
80
89
  * @returns The `useQuery` function returns the `query` property of the `hook` object.
81
90
  */
82
91
  const useQuery = () => {
83
- vars.hookIndex++
84
-
85
92
  const oldHook =
86
93
  vars.wipFiber.alternate &&
87
94
  vars.wipFiber.alternate.hooks &&
@@ -94,13 +101,68 @@ const useQuery = () => {
94
101
  const Query = hasOld ? hasOld : params
95
102
 
96
103
  const hook = {
97
- tag: RYUNIX_TYPES.RYUNIX_EFFECT,
104
+ type: RYUNIX_TYPES.RYUNIX_URL_QUERY,
98
105
  query: Query,
99
106
  }
100
107
 
101
- vars.wipFiber.hooks.push(hook)
108
+ if (vars.wipFiber.hooks) {
109
+ vars.wipFiber.hooks.push(hook)
110
+ vars.hookIndex++
111
+ }
102
112
 
103
113
  return hook.query
104
114
  }
105
115
 
106
- export { useStore, useEffect, useQuery }
116
+ const useRef = (initial) => {
117
+ const oldHook =
118
+ vars.wipFiber.alternate &&
119
+ vars.wipFiber.alternate.hooks &&
120
+ vars.wipFiber.alternate.hooks[vars.hookIndex]
121
+
122
+ const hook = {
123
+ type: RYUNIX_TYPES.RYUNIX_REF,
124
+ value: oldHook ? oldHook.value : { current: initial },
125
+ }
126
+
127
+ if (vars.wipFiber.hooks) {
128
+ vars.wipFiber.hooks.push(hook)
129
+ vars.hookIndex++
130
+ }
131
+
132
+ return hook.value
133
+ }
134
+
135
+ const useMemo = (comp, deps) => {
136
+ const oldHook =
137
+ vars.wipFiber.alternate &&
138
+ vars.wipFiber.alternate.hooks &&
139
+ vars.wipFiber.alternate.hooks[vars.hookIndex]
140
+
141
+ const hook = {
142
+ type: RYUNIX_TYPES.RYUNIX_MEMO,
143
+ value: null,
144
+ deps,
145
+ }
146
+
147
+ if (oldHook) {
148
+ if (isEqual(oldHook.deps, hook.deps)) {
149
+ hook.value = oldHook.value
150
+ } else {
151
+ hook.value = comp()
152
+ }
153
+ } else {
154
+ hook.value = comp()
155
+ }
156
+
157
+ if (vars.wipFiber.hooks) {
158
+ vars.wipFiber.hooks.push(hook)
159
+ vars.hookIndex++
160
+ }
161
+
162
+ return hook.value
163
+ }
164
+ const useCallback = (callback, deps) => {
165
+ return useMemo(() => callback, deps)
166
+ }
167
+
168
+ export { useStore, useEffect, useQuery, useRef, useMemo, useCallback }
package/src/lib/index.js CHANGED
@@ -1,14 +1,28 @@
1
- import { createElement, Fragments } from './createElement'
1
+ import { createElement, cloneElement, Fragments } from './createElement'
2
2
  import { render, init } from './render'
3
- import { useStore, useEffect, useQuery } from './hooks'
4
- import { Router, Navigate, Link } from './navigation'
3
+ import {
4
+ useStore,
5
+ useEffect,
6
+ useQuery,
7
+ useRef,
8
+ useMemo,
9
+ useCallback,
10
+ } from './hooks'
5
11
  import * as Dom from './dom'
6
12
  import * as Workers from './workers'
7
13
  import * as Reconciler from './reconciler'
8
14
  import * as Components from './components'
9
15
  import * as Commits from './commits'
10
16
 
11
- export { useStore, useEffect, useQuery, Fragments, Router, Navigate, Link }
17
+ export {
18
+ useStore,
19
+ useEffect,
20
+ useQuery,
21
+ useRef,
22
+ useMemo,
23
+ useCallback,
24
+ Fragments,
25
+ }
12
26
 
13
27
  export default {
14
28
  createElement,
@@ -14,7 +14,7 @@ const reconcileChildren = (wipFiber, elements) => {
14
14
  let oldFiber = wipFiber.alternate && wipFiber.alternate.child
15
15
  let prevSibling
16
16
 
17
- while (index < elements.length || oldFiber != null) {
17
+ while (index < elements.length || oldFiber != undefined) {
18
18
  const element = elements[index]
19
19
  let newFiber
20
20
 
@@ -22,9 +22,9 @@ const reconcileChildren = (wipFiber, elements) => {
22
22
 
23
23
  if (sameType) {
24
24
  newFiber = {
25
- type: oldFiber.type,
25
+ type: oldFiber ? oldFiber.type : undefined,
26
26
  props: element.props,
27
- dom: oldFiber.dom,
27
+ dom: oldFiber ? oldFiber.dom : undefined,
28
28
  parent: wipFiber,
29
29
  alternate: oldFiber,
30
30
  effectTag: EFFECT_TAGS.UPDATE,
@@ -34,9 +34,9 @@ const reconcileChildren = (wipFiber, elements) => {
34
34
  newFiber = {
35
35
  type: element.type,
36
36
  props: element.props,
37
- dom: null,
37
+ dom: undefined,
38
38
  parent: wipFiber,
39
- alternate: null,
39
+ alternate: undefined,
40
40
  effectTag: EFFECT_TAGS.PLACEMENT,
41
41
  }
42
42
  }
@@ -51,7 +51,7 @@ const reconcileChildren = (wipFiber, elements) => {
51
51
 
52
52
  if (index === 0) {
53
53
  wipFiber.child = newFiber
54
- } else if (element) {
54
+ } else if (element && prevSibling) {
55
55
  prevSibling.sibling = newFiber
56
56
  }
57
57
 
@@ -36,7 +36,7 @@ requestIdleCallback(workLoop)
36
36
  * @returns The function `performUnitOfWork` returns the next fiber to be processed. If the current
37
37
  * fiber has a child, it returns the child. Otherwise, it looks for the next sibling of the current
38
38
  * fiber. If there are no more siblings, it goes up the tree to the parent and looks for the next
39
- * sibling of the parent. The function returns `null` if there are no more fibers to process.
39
+ * sibling of the parent. The function returns `undefined` if there are no more fibers to process.
40
40
  */
41
41
  const performUnitOfWork = (fiber) => {
42
42
  const isFunctionComponent = fiber.type instanceof Function
@@ -55,6 +55,7 @@ const performUnitOfWork = (fiber) => {
55
55
  }
56
56
  nextFiber = nextFiber.parent
57
57
  }
58
+ return undefined
58
59
  }
59
60
 
60
61
  export { performUnitOfWork, workLoop }
package/src/main.js CHANGED
@@ -3,10 +3,10 @@ export {
3
3
  useStore,
4
4
  useEffect,
5
5
  useQuery,
6
+ useRef,
7
+ useMemo,
8
+ useCallback,
6
9
  Fragments,
7
- Navigate,
8
- Router,
9
- Link,
10
10
  } from './lib/index.js'
11
11
 
12
12
  window.Ryunix = Ryunix
@@ -1,11 +1,11 @@
1
1
  const vars = {
2
- containerRoot: null,
3
- nextUnitOfWork: null,
4
- currentRoot: null,
5
- wipRoot: null,
6
- deletions: null,
7
- wipFiber: null,
8
- hookIndex: null,
2
+ containerRoot: undefined,
3
+ nextUnitOfWork: undefined,
4
+ currentRoot: undefined,
5
+ wipRoot: undefined,
6
+ deletions: undefined,
7
+ wipFiber: undefined,
8
+ hookIndex: undefined,
9
9
  }
10
10
 
11
11
  const reg = /[A-Z]/g
@@ -13,6 +13,9 @@ const reg = /[A-Z]/g
13
13
  const RYUNIX_TYPES = Object.freeze({
14
14
  TEXT_ELEMENT: Symbol('text.element'),
15
15
  RYUNIX_EFFECT: Symbol('ryunix.effect'),
16
+ RYUNIX_MEMO: Symbol('ryunix.memo'),
17
+ RYUNIX_URL_QUERY: Symbol('ryunix.urlQuery'),
18
+ RYUNIX_REF: Symbol('ryunix.ref'),
16
19
  })
17
20
 
18
21
  const STRINGS = Object.freeze({
@@ -21,6 +24,7 @@ const STRINGS = Object.freeze({
21
24
  style: 'ryunix-style',
22
25
  className: 'ryunix-class',
23
26
  children: 'children',
27
+ boolean: 'boolean',
24
28
  })
25
29
 
26
30
  const OLD_STRINGS = Object.freeze({
@@ -1,91 +0,0 @@
1
- import { useStore, useEffect } from './hooks'
2
- import { createElement } from './createElement'
3
- const Router = ({ path, component, children }) => {
4
- const [currentPath, setCurrentPath] = useStore(window.location.pathname)
5
-
6
- useEffect(() => {
7
- const onLocationChange = () => {
8
- setCurrentPath(() => window.location.pathname)
9
- }
10
-
11
- window.addEventListener('navigate', onLocationChange)
12
- window.addEventListener('pushsatate', onLocationChange)
13
- window.addEventListener('popstate', onLocationChange)
14
-
15
- return () => {
16
- setTimeout(() => {
17
- window.removeEventListener('navigate', onLocationChange)
18
- window.removeEventListener('pushsatate', onLocationChange)
19
- window.removeEventListener('popstate', onLocationChange)
20
- }, 100)
21
- }
22
- }, [currentPath])
23
-
24
- return currentPath === path ? component() : null
25
- }
26
-
27
- const Navigate = () => {
28
- /**
29
- * The function `push` is used to push a new state to the browser's history and trigger a custom
30
- * event called 'pushstate'.
31
- * @param to - The `to` parameter is a string representing the URL path to which you want to
32
- * navigate.
33
- * @param [state] - The `state` parameter is an optional object that represents the state associated
34
- * with the new history entry. It can be used to store any data that you want to associate with the
35
- * new URL. When you navigate back or forward in the browser history, this state object will be
36
- * passed to the `popstate
37
- * @returns The function `push` does not have a return statement, so it returns `undefined` by
38
- * default.
39
- */
40
- const push = (to, state = {}) => {
41
- if (window.location.pathname === to) return
42
-
43
- window.history.pushState(state, '', to)
44
- const navigationEvent = new PopStateEvent('popstate')
45
- window.dispatchEvent(navigationEvent)
46
- }
47
-
48
- return { push }
49
- }
50
-
51
- const Link = (props) => {
52
- if (props.style) {
53
- throw new Error(
54
- 'The style attribute is not supported on internal components, use className.',
55
- )
56
- }
57
- if (props.to === '') {
58
- throw new Error("'to=' cannot be empty.")
59
- }
60
- if (props.className === '') {
61
- throw new Error('className cannot be empty.')
62
- }
63
- if (props.label === '' && !props.children) {
64
- throw new Error("'label=' cannot be empty.")
65
- }
66
-
67
- if (!props.to) {
68
- throw new Error("Missig 'to' param.")
69
- }
70
- const preventReload = (event) => {
71
- if (event.metaKey || event.ctrlKey) {
72
- return
73
- }
74
-
75
- event.preventDefault()
76
- const { push } = Navigate()
77
- push(props.to)
78
- }
79
-
80
- const anchor = {
81
- href: props.to,
82
- onClick: preventReload,
83
- ...props,
84
- }
85
-
86
- const children = props.label ? props.label : props.children
87
-
88
- return createElement('a', anchor, children)
89
- }
90
-
91
- export { Router, Navigate, Link }