@unsetsoft/ryunixjs 1.2.3-canary.8 → 1.2.4-canary.1
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.esm.js +1090 -610
- package/dist/Ryunix.esm.js.map +1 -1
- package/dist/Ryunix.umd.js +1113 -609
- package/dist/Ryunix.umd.js.map +1 -1
- package/dist/Ryunix.umd.min.js +1 -1
- package/dist/Ryunix.umd.min.js.map +1 -1
- package/jsx/jsx-dev-runtime.js +6 -0
- package/jsx/jsx-runtime.js +56 -0
- package/package.json +14 -3
package/dist/Ryunix.esm.js
CHANGED
|
@@ -56,6 +56,17 @@ const EFFECT_TAGS = Object.freeze({
|
|
|
56
56
|
NO_EFFECT: Symbol.for('ryunix.reconciler.status.no_effect'),
|
|
57
57
|
});
|
|
58
58
|
|
|
59
|
+
const DANGEROUSLY_SET_INNER_HTML = 'dangerousInjection';
|
|
60
|
+
|
|
61
|
+
const DANGEROUS_PROPERTIES = Object.freeze([
|
|
62
|
+
'innerHTML',
|
|
63
|
+
'outerHTML',
|
|
64
|
+
'insertAdjacentHTML',
|
|
65
|
+
'insertAdjacentText',
|
|
66
|
+
'insertAdjacentElement',
|
|
67
|
+
'setHTML',
|
|
68
|
+
]);
|
|
69
|
+
|
|
59
70
|
/**
|
|
60
71
|
* Type checking utilities
|
|
61
72
|
*/
|
|
@@ -70,7 +81,12 @@ const is = {
|
|
|
70
81
|
};
|
|
71
82
|
|
|
72
83
|
/**
|
|
73
|
-
*
|
|
84
|
+
* The `createTextElement` function creates a text element with the specified text content.
|
|
85
|
+
* @param text - The `text` parameter in the `createTextElement` function is the text content that you
|
|
86
|
+
* want to create a text element for. This text will be set as the `nodeValue` of the text element in
|
|
87
|
+
* the returned object.
|
|
88
|
+
* @returns A text element object is being returned with a type of RYUNIX_TYPES.TEXT_ELEMENT and props
|
|
89
|
+
* containing the text value provided in the function argument.
|
|
74
90
|
*/
|
|
75
91
|
const createTextElement = (text) => {
|
|
76
92
|
return {
|
|
@@ -83,7 +99,21 @@ const createTextElement = (text) => {
|
|
|
83
99
|
};
|
|
84
100
|
|
|
85
101
|
/**
|
|
86
|
-
*
|
|
102
|
+
* The `createElement` function creates a virtual DOM element with specified type, properties, and
|
|
103
|
+
* children.
|
|
104
|
+
* @param type - The `type` parameter in the `createElement` function represents the type of element
|
|
105
|
+
* you want to create, such as a HTML tag like 'div', 'span', 'p', etc.
|
|
106
|
+
* @param props - The `props` parameter in the `createElement` function is an object that contains the
|
|
107
|
+
* properties or attributes for the element being created. These properties can include things like
|
|
108
|
+
* class names, styles, event handlers, and any other custom attributes you want to assign to the
|
|
109
|
+
* element. In the code snippet you provided,
|
|
110
|
+
* @param children - The `children` parameter in the `createElement` function represents the child
|
|
111
|
+
* elements or text content that will be nested within the created element. These children can be
|
|
112
|
+
* passed as arguments to the `createElement` function and will be rendered as part of the element's
|
|
113
|
+
* content.
|
|
114
|
+
* @returns An object is being returned with a `type` property representing the type of element, and a
|
|
115
|
+
* `props` property containing the element's properties. The `props` object includes the children of
|
|
116
|
+
* the element, which are processed to ensure they are in the correct format.
|
|
87
117
|
*/
|
|
88
118
|
const createElement = (type, props, ...children) => {
|
|
89
119
|
const safeProps = props || {};
|
|
@@ -102,7 +132,14 @@ const createElement = (type, props, ...children) => {
|
|
|
102
132
|
};
|
|
103
133
|
|
|
104
134
|
/**
|
|
105
|
-
* Fragment
|
|
135
|
+
* The `Fragment` function in JavaScript creates a fragment element with the given children.
|
|
136
|
+
* @param props - The `props` parameter in the `Fragment` function is an object that contains the
|
|
137
|
+
* properties passed to the `Fragment` component. These properties can include `children`, which
|
|
138
|
+
* represents the child elements or components nested within the `Fragment`.
|
|
139
|
+
* @returns The `Fragment` component is returning a Ryunix fragment element created using the
|
|
140
|
+
* `createElement` function. The element is of type `RYUNIX_TYPES.RYUNIX_FRAGMENT` and contains the
|
|
141
|
+
* children passed to the `Fragment` component. If `props.children` is not an array, it is converted
|
|
142
|
+
* into an array before being spread into the `createElement` function.
|
|
106
143
|
*/
|
|
107
144
|
const Fragment = (props) => {
|
|
108
145
|
const children = Array.isArray(props.children)
|
|
@@ -243,6 +280,71 @@ const runEffects = (fiber) => {
|
|
|
243
280
|
}
|
|
244
281
|
};
|
|
245
282
|
|
|
283
|
+
/**
|
|
284
|
+
* Checks if a property is dangerous and should be blocked
|
|
285
|
+
* @param {string} propName - Property name
|
|
286
|
+
* @returns {boolean} True if the property is dangerous
|
|
287
|
+
*/
|
|
288
|
+
const isDangerousProperty = (propName) => {
|
|
289
|
+
return DANGEROUS_PROPERTIES.includes(propName)
|
|
290
|
+
};
|
|
291
|
+
|
|
292
|
+
/**
|
|
293
|
+
* Validates and applies DANGEROUSLY_SET_INNER_HTML safely
|
|
294
|
+
* @param {HTMLElement} dom - DOM element
|
|
295
|
+
* @param {Object} DANGEROUSLY_SET_INNER_HTML - Object with __html property
|
|
296
|
+
*/
|
|
297
|
+
const applyDangerouslySetInnerHTML = (dom, dangerousInjection) => {
|
|
298
|
+
if (!is.object(dangerousInjection)) {
|
|
299
|
+
if (process.env.NODE_ENV !== 'production') {
|
|
300
|
+
console.warn(
|
|
301
|
+
`${DANGEROUSLY_SET_INNER_HTML} must be an object with the __html property`,
|
|
302
|
+
);
|
|
303
|
+
}
|
|
304
|
+
return
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
if (!('__html' in dangerousInjection)) {
|
|
308
|
+
if (process.env.NODE_ENV !== 'production') {
|
|
309
|
+
console.warn(
|
|
310
|
+
`${DANGEROUSLY_SET_INNER_HTML} must have the __html property`,
|
|
311
|
+
);
|
|
312
|
+
}
|
|
313
|
+
return
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
const html = dangerousInjection.__html;
|
|
317
|
+
|
|
318
|
+
if (html == null) {
|
|
319
|
+
dom.innerHTML = '';
|
|
320
|
+
return
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
if (typeof html !== 'string') {
|
|
324
|
+
if (process.env.NODE_ENV !== 'production') {
|
|
325
|
+
console.warn(`${DANGEROUSLY_SET_INNER_HTML}.__html must be a string`);
|
|
326
|
+
}
|
|
327
|
+
return
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
// Warning in development
|
|
331
|
+
if (process.env.NODE_ENV !== 'production') {
|
|
332
|
+
console.warn(
|
|
333
|
+
`⚠️ WARNING: You are using ${DANGEROUSLY_SET_INNER_HTML}. ` +
|
|
334
|
+
'This can expose your application to XSS attacks if the content is not sanitized. ' +
|
|
335
|
+
'Make sure you trust the source of the HTML before using it.',
|
|
336
|
+
);
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
try {
|
|
340
|
+
dom.innerHTML = html;
|
|
341
|
+
} catch (error) {
|
|
342
|
+
if (process.env.NODE_ENV !== 'production') {
|
|
343
|
+
console.error('Error setting innerHTML:', error);
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
};
|
|
347
|
+
|
|
246
348
|
/**
|
|
247
349
|
* Convert camelCase to kebab-case for CSS properties
|
|
248
350
|
* @param {string} camelCase - CamelCase string
|
|
@@ -357,6 +459,40 @@ const createDom = (fiber) => {
|
|
|
357
459
|
* @param {Object} nextProps - Next props
|
|
358
460
|
*/
|
|
359
461
|
const updateDom = (dom, prevProps = {}, nextProps = {}) => {
|
|
462
|
+
// Block dangerous properties
|
|
463
|
+
const dangerousProps = Object.keys(nextProps).filter(isDangerousProperty);
|
|
464
|
+
if (dangerousProps.length > 0) {
|
|
465
|
+
if (process.env.NODE_ENV !== 'production') {
|
|
466
|
+
console.error(
|
|
467
|
+
`⚠️ SECURITY ERROR: Attempted to use ${DANGEROUS_PROPERTIES.join(', ')}: ${dangerousProps.join(', ')}. ` +
|
|
468
|
+
`These properties are blocked to prevent XSS injections. ` +
|
|
469
|
+
`If you need to insert HTML, use ${DANGEROUSLY_SET_INNER_HTML} with the structure: ` +
|
|
470
|
+
`{ ${DANGEROUSLY_SET_INNER_HTML}: { __html: 'your html here' } }`,
|
|
471
|
+
);
|
|
472
|
+
}
|
|
473
|
+
// Remove dangerous properties from nextProps to prevent their use
|
|
474
|
+
dangerousProps.forEach((prop) => {
|
|
475
|
+
delete nextProps[prop];
|
|
476
|
+
});
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
// Handle dangerouslySetInnerHTML
|
|
480
|
+
if (DANGEROUSLY_SET_INNER_HTML in nextProps) {
|
|
481
|
+
// If there are children along with dangerouslySetInnerHTML, warn
|
|
482
|
+
if (nextProps.children && nextProps.children.length > 0) {
|
|
483
|
+
if (process.env.NODE_ENV !== 'production') {
|
|
484
|
+
console.warn(
|
|
485
|
+
`⚠️ WARNING: You cannot use children together with ${DANGEROUSLY_SET_INNER_HTML}. ` +
|
|
486
|
+
'Children will be ignored.',
|
|
487
|
+
);
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
applyDangerouslySetInnerHTML(dom, nextProps[DANGEROUSLY_SET_INNER_HTML]);
|
|
491
|
+
} else if (DANGEROUSLY_SET_INNER_HTML in prevProps) {
|
|
492
|
+
// If dangerouslySetInnerHTML was removed, clear innerHTML
|
|
493
|
+
dom.innerHTML = '';
|
|
494
|
+
}
|
|
495
|
+
|
|
360
496
|
// Remove old event listeners
|
|
361
497
|
Object.keys(prevProps)
|
|
362
498
|
.filter(isEvent)
|
|
@@ -384,11 +520,20 @@ const updateDom = (dom, prevProps = {}, nextProps = {}) => {
|
|
|
384
520
|
OLD_STRINGS.STYLE,
|
|
385
521
|
STRINGS.CLASS_NAME,
|
|
386
522
|
OLD_STRINGS.CLASS_NAME,
|
|
523
|
+
DANGEROUSLY_SET_INNER_HTML,
|
|
387
524
|
].includes(name)
|
|
388
525
|
) {
|
|
389
526
|
return
|
|
390
527
|
}
|
|
391
|
-
|
|
528
|
+
// Don't try to clean dangerous properties
|
|
529
|
+
if (isDangerousProperty(name)) {
|
|
530
|
+
return
|
|
531
|
+
}
|
|
532
|
+
try {
|
|
533
|
+
dom[name] = '';
|
|
534
|
+
} catch (error) {
|
|
535
|
+
// Ignore errors when cleaning read-only properties
|
|
536
|
+
}
|
|
392
537
|
});
|
|
393
538
|
|
|
394
539
|
// Set new properties
|
|
@@ -397,6 +542,11 @@ const updateDom = (dom, prevProps = {}, nextProps = {}) => {
|
|
|
397
542
|
.filter(isNew(prevProps, nextProps))
|
|
398
543
|
.forEach((name) => {
|
|
399
544
|
try {
|
|
545
|
+
// Skip dangerouslySetInnerHTML (already handled above)
|
|
546
|
+
if (name === DANGEROUSLY_SET_INNER_HTML) {
|
|
547
|
+
return
|
|
548
|
+
}
|
|
549
|
+
|
|
400
550
|
// Handle style properties
|
|
401
551
|
if (name === STRINGS.STYLE || name === OLD_STRINGS.STYLE) {
|
|
402
552
|
const styleValue = nextProps[name];
|
|
@@ -450,6 +600,9 @@ const updateDom = (dom, prevProps = {}, nextProps = {}) => {
|
|
|
450
600
|
});
|
|
451
601
|
};
|
|
452
602
|
|
|
603
|
+
/**
|
|
604
|
+
* The `commitRoot` function commits the changes made to the virtual DOM by updating the actual DOM.
|
|
605
|
+
*/
|
|
453
606
|
const commitRoot = () => {
|
|
454
607
|
const state = getState();
|
|
455
608
|
state.deletions.forEach(commitWork);
|
|
@@ -587,34 +740,6 @@ const reconcileChildren = (wipFiber, elements) => {
|
|
|
587
740
|
});
|
|
588
741
|
};
|
|
589
742
|
|
|
590
|
-
const updateFunctionComponent = (fiber) => {
|
|
591
|
-
const state = getState();
|
|
592
|
-
state.wipFiber = fiber;
|
|
593
|
-
state.hookIndex = 0;
|
|
594
|
-
state.wipFiber.hooks = [];
|
|
595
|
-
|
|
596
|
-
const children = [fiber.type(fiber.props)];
|
|
597
|
-
|
|
598
|
-
if (fiber.type._contextId && fiber.props.value !== undefined) {
|
|
599
|
-
fiber._contextId = fiber.type._contextId;
|
|
600
|
-
fiber._contextValue = fiber.props.value;
|
|
601
|
-
}
|
|
602
|
-
|
|
603
|
-
reconcileChildren(fiber, children);
|
|
604
|
-
};
|
|
605
|
-
|
|
606
|
-
const updateHostComponent = (fiber) => {
|
|
607
|
-
if (!fiber.dom) {
|
|
608
|
-
fiber.dom = createDom(fiber);
|
|
609
|
-
}
|
|
610
|
-
const children = fiber.props?.children || [];
|
|
611
|
-
reconcileChildren(fiber, children);
|
|
612
|
-
};
|
|
613
|
-
|
|
614
|
-
const Image = ({ src, ...props }) => {
|
|
615
|
-
return createElement('img', { ...props, src })
|
|
616
|
-
};
|
|
617
|
-
|
|
618
743
|
/**
|
|
619
744
|
* Priority levels for updates
|
|
620
745
|
*/
|
|
@@ -628,738 +753,1068 @@ const Priority = {
|
|
|
628
753
|
|
|
629
754
|
Priority.NORMAL;
|
|
630
755
|
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
this.measures = new Map();
|
|
638
|
-
this.renderTimes = [];
|
|
639
|
-
this.maxSamples = 100;
|
|
756
|
+
const validateHookCall = () => {
|
|
757
|
+
const state = getState();
|
|
758
|
+
if (!state.wipFiber) {
|
|
759
|
+
throw new Error(
|
|
760
|
+
'Hooks can only be called inside the body of a function component.',
|
|
761
|
+
)
|
|
640
762
|
}
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
if (!this.enabled) return
|
|
644
|
-
this.measures.set(name, performance.now());
|
|
763
|
+
if (!Array.isArray(state.wipFiber.hooks)) {
|
|
764
|
+
state.wipFiber.hooks = [];
|
|
645
765
|
}
|
|
766
|
+
};
|
|
646
767
|
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
768
|
+
const haveDepsChanged = (oldDeps, newDeps) => {
|
|
769
|
+
if (!oldDeps || !newDeps) return true
|
|
770
|
+
if (oldDeps.length !== newDeps.length) return true
|
|
771
|
+
return oldDeps.some((dep, i) => !Object.is(dep, newDeps[i]))
|
|
772
|
+
};
|
|
651
773
|
|
|
652
|
-
|
|
653
|
-
|
|
774
|
+
/**
|
|
775
|
+
* The `useStore` function in JavaScript is a custom hook that uses a reducer to manage state updates
|
|
776
|
+
* based on actions provided.
|
|
777
|
+
* @param initialState - The `initialState` parameter in the `useStore` function is the initial state
|
|
778
|
+
* of the store that will be used with the `useReducer` hook. It represents the starting state of the
|
|
779
|
+
* store before any actions are dispatched to update it.
|
|
780
|
+
* @returns The `useStore` function is returning the result of calling the `useReducer` hook with the
|
|
781
|
+
* `reducer` function and the `initialState` as arguments.
|
|
782
|
+
*/
|
|
783
|
+
const useStore = (initialState) => {
|
|
784
|
+
const reducer = (state, action) =>
|
|
785
|
+
is.function(action) ? action(state) : action;
|
|
786
|
+
return useReducer(reducer, initialState)
|
|
787
|
+
};
|
|
654
788
|
|
|
655
|
-
|
|
656
|
-
|
|
789
|
+
/**
|
|
790
|
+
* The `useReducer` function in JavaScript is used to manage state and actions.
|
|
791
|
+
*
|
|
792
|
+
* @param reducer - The `reducer` parameter in the `useReducer` function is a function that specifies
|
|
793
|
+
* how the state should be updated in response to an action. It takes the current state and an action
|
|
794
|
+
* as arguments and returns the new state based on the action.
|
|
795
|
+
* @param initialState - The `initialState` parameter in the `useReducer` function represents the
|
|
796
|
+
* initial state of the reducer. It is the state that will be used when the reducer is first called or
|
|
797
|
+
* when the state needs to be reset. This initial state can be a simple value, an object, an array, or
|
|
798
|
+
* @param init - The `init` parameter in the `useReducer` function is an optional function that can be
|
|
799
|
+
* used to initialize the state. If provided, it will be called with the `initialState` as its argument
|
|
800
|
+
* and the return value will be used as the initial state for the reducer. If `init`
|
|
801
|
+
* @returns An array containing the current state and the dispatch function is being returned.
|
|
802
|
+
*/
|
|
803
|
+
const useReducer = (reducer, initialState, init) => {
|
|
804
|
+
validateHookCall();
|
|
657
805
|
|
|
658
|
-
|
|
659
|
-
|
|
806
|
+
const state = getState();
|
|
807
|
+
const { wipFiber, hookIndex } = state;
|
|
808
|
+
const oldHook = wipFiber.alternate?.hooks?.[hookIndex];
|
|
660
809
|
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
810
|
+
const hook = {
|
|
811
|
+
hookID: hookIndex,
|
|
812
|
+
type: RYUNIX_TYPES.RYUNIX_STORE,
|
|
813
|
+
state: oldHook ? oldHook.state : init ? init(initialState) : initialState,
|
|
814
|
+
queue: [],
|
|
815
|
+
};
|
|
816
|
+
|
|
817
|
+
if (oldHook?.queue) {
|
|
818
|
+
oldHook.queue.forEach((action) => {
|
|
819
|
+
try {
|
|
820
|
+
hook.state = reducer(hook.state, action);
|
|
821
|
+
} catch (error) {
|
|
822
|
+
if (process.env.NODE_ENV !== 'production') {
|
|
823
|
+
console.error('Error in reducer:', error);
|
|
824
|
+
}
|
|
825
|
+
}
|
|
665
826
|
});
|
|
827
|
+
}
|
|
666
828
|
|
|
667
|
-
|
|
668
|
-
|
|
829
|
+
const dispatch = (action) => {
|
|
830
|
+
if (action === undefined) {
|
|
831
|
+
if (process.env.NODE_ENV !== 'production') {
|
|
832
|
+
console.warn('dispatch called with undefined action');
|
|
833
|
+
}
|
|
834
|
+
return
|
|
669
835
|
}
|
|
670
|
-
}
|
|
671
836
|
|
|
672
|
-
|
|
673
|
-
if (!this.enabled) return null
|
|
837
|
+
hook.queue.push(action);
|
|
674
838
|
|
|
675
|
-
const
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
839
|
+
const currentState = getState();
|
|
840
|
+
currentState.wipRoot = {
|
|
841
|
+
dom: currentState.currentRoot.dom,
|
|
842
|
+
props: currentState.currentRoot.props,
|
|
843
|
+
alternate: currentState.currentRoot,
|
|
844
|
+
};
|
|
845
|
+
currentState.deletions = [];
|
|
846
|
+
currentState.hookIndex = 0;
|
|
847
|
+
scheduleWork(currentState.wipRoot);
|
|
848
|
+
};
|
|
679
849
|
|
|
680
|
-
|
|
681
|
-
|
|
850
|
+
wipFiber.hooks[hookIndex] = hook;
|
|
851
|
+
state.hookIndex++;
|
|
852
|
+
return [hook.state, dispatch]
|
|
853
|
+
};
|
|
682
854
|
|
|
683
|
-
|
|
684
|
-
|
|
855
|
+
/**
|
|
856
|
+
* The `useEffect` function in JavaScript is used to manage side effects in functional components by
|
|
857
|
+
* comparing dependencies and executing a callback function when dependencies change.
|
|
858
|
+
* @param callback - The `callback` parameter in the `useEffect` function is a function that will be
|
|
859
|
+
* executed as the effect. This function can perform side effects like data fetching, subscriptions, or
|
|
860
|
+
* DOM manipulations.
|
|
861
|
+
* @param deps - The `deps` parameter in the `useEffect` function stands for dependencies. It is an
|
|
862
|
+
* optional array that contains values that the effect depends on. The effect will only re-run if any
|
|
863
|
+
* of the values in the `deps` array have changed since the last render. If the `deps` array
|
|
864
|
+
*/
|
|
865
|
+
const useEffect = (callback, deps) => {
|
|
866
|
+
validateHookCall();
|
|
685
867
|
|
|
686
|
-
|
|
868
|
+
if (!is.function(callback)) {
|
|
869
|
+
throw new Error('useEffect callback must be a function')
|
|
870
|
+
}
|
|
871
|
+
if (deps !== undefined && !Array.isArray(deps)) {
|
|
872
|
+
throw new Error('useEffect dependencies must be an array or undefined')
|
|
873
|
+
}
|
|
687
874
|
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
const stats = byComponent.get(component);
|
|
693
|
-
stats.total += duration;
|
|
694
|
-
stats.count++;
|
|
695
|
-
stats.max = Math.max(stats.max, duration);
|
|
696
|
-
});
|
|
875
|
+
const state = getState();
|
|
876
|
+
const { wipFiber, hookIndex } = state;
|
|
877
|
+
const oldHook = wipFiber.alternate?.hooks?.[hookIndex];
|
|
878
|
+
const hasChanged = haveDepsChanged(oldHook?.deps, deps);
|
|
697
879
|
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
.sort((a, b) => b.avg - a.avg)
|
|
706
|
-
.slice(0, limit)
|
|
707
|
-
}
|
|
880
|
+
const hook = {
|
|
881
|
+
hookID: hookIndex,
|
|
882
|
+
type: RYUNIX_TYPES.RYUNIX_EFFECT,
|
|
883
|
+
deps,
|
|
884
|
+
effect: hasChanged ? callback : null,
|
|
885
|
+
cancel: oldHook?.cancel,
|
|
886
|
+
};
|
|
708
887
|
|
|
709
|
-
|
|
710
|
-
|
|
888
|
+
wipFiber.hooks[hookIndex] = hook;
|
|
889
|
+
state.hookIndex++;
|
|
890
|
+
};
|
|
711
891
|
|
|
712
|
-
|
|
713
|
-
|
|
892
|
+
/**
|
|
893
|
+
* The useRef function in JavaScript creates a reference object with an initial value for use in functional components.
|
|
894
|
+
* @param initialValue - The `initialValue` parameter in the `useRef` function represents the initial
|
|
895
|
+
* value that will be assigned to the `current` property of the reference object. This initial value
|
|
896
|
+
* will be used if there is no previous value stored in the hook.
|
|
897
|
+
* @returns The `useRef` function is returning the `current` property of the `hook.value` object, which
|
|
898
|
+
* contains the initial value passed to the `useRef` function.
|
|
899
|
+
*/
|
|
900
|
+
const useRef = (initialValue) => {
|
|
901
|
+
validateHookCall();
|
|
714
902
|
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
console.log(
|
|
719
|
-
`Min: ${stats.min.toFixed(2)}ms | Max: ${stats.max.toFixed(2)}ms`,
|
|
720
|
-
);
|
|
903
|
+
const state = getState();
|
|
904
|
+
const { wipFiber, hookIndex } = state;
|
|
905
|
+
const oldHook = wipFiber.alternate?.hooks?.[hookIndex];
|
|
721
906
|
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
`${i + 1}. ${comp.name}: ${comp.avg.toFixed(2)}ms avg (${comp.count} renders)`,
|
|
728
|
-
);
|
|
729
|
-
});
|
|
730
|
-
}
|
|
731
|
-
console.groupEnd();
|
|
732
|
-
}
|
|
907
|
+
const hook = {
|
|
908
|
+
hookID: hookIndex,
|
|
909
|
+
type: RYUNIX_TYPES.RYUNIX_REF,
|
|
910
|
+
value: oldHook ? oldHook.value : { current: initialValue },
|
|
911
|
+
};
|
|
733
912
|
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
913
|
+
wipFiber.hooks[hookIndex] = hook;
|
|
914
|
+
state.hookIndex++;
|
|
915
|
+
return hook.value
|
|
916
|
+
};
|
|
917
|
+
|
|
918
|
+
/**
|
|
919
|
+
* The useMemo function in JavaScript is used to memoize the result of a computation based on
|
|
920
|
+
* dependencies.
|
|
921
|
+
* @param compute - The `compute` parameter in the `useMemo` function is a callback function that
|
|
922
|
+
* calculates the value that `useMemo` will memoize and return. This function will be called to compute
|
|
923
|
+
* the memoized value when necessary.
|
|
924
|
+
* @param deps - The `deps` parameter in the `useMemo` function refers to an array of dependencies.
|
|
925
|
+
* These dependencies are used to determine whether the memoized value needs to be recalculated or if
|
|
926
|
+
* the previously calculated value can be reused. The `useMemo` hook will recompute the memoized value
|
|
927
|
+
* only if
|
|
928
|
+
* @returns The `useMemo` function is returning the `value` calculated by the `compute` function.
|
|
929
|
+
*/
|
|
930
|
+
const useMemo = (compute, deps) => {
|
|
931
|
+
validateHookCall();
|
|
932
|
+
|
|
933
|
+
if (!is.function(compute)) {
|
|
934
|
+
throw new Error('useMemo callback must be a function')
|
|
935
|
+
}
|
|
936
|
+
if (!Array.isArray(deps)) {
|
|
937
|
+
throw new Error('useMemo requires a dependencies array')
|
|
737
938
|
}
|
|
738
939
|
|
|
739
|
-
|
|
740
|
-
|
|
940
|
+
const state = getState();
|
|
941
|
+
const { wipFiber, hookIndex } = state;
|
|
942
|
+
const oldHook = wipFiber.alternate?.hooks?.[hookIndex];
|
|
943
|
+
|
|
944
|
+
let value;
|
|
945
|
+
if (oldHook && !haveDepsChanged(oldHook.deps, deps)) {
|
|
946
|
+
value = oldHook.value;
|
|
947
|
+
} else {
|
|
948
|
+
try {
|
|
949
|
+
value = compute();
|
|
950
|
+
} catch (error) {
|
|
951
|
+
if (process.env.NODE_ENV !== 'production') {
|
|
952
|
+
console.error('Error in useMemo computation:', error);
|
|
953
|
+
}
|
|
954
|
+
value = undefined;
|
|
955
|
+
}
|
|
741
956
|
}
|
|
742
957
|
|
|
743
|
-
|
|
744
|
-
|
|
958
|
+
const hook = {
|
|
959
|
+
hookID: hookIndex,
|
|
960
|
+
type: RYUNIX_TYPES.RYUNIX_MEMO,
|
|
961
|
+
value,
|
|
962
|
+
deps,
|
|
963
|
+
};
|
|
964
|
+
|
|
965
|
+
wipFiber.hooks[hookIndex] = hook;
|
|
966
|
+
state.hookIndex++;
|
|
967
|
+
return value
|
|
968
|
+
};
|
|
969
|
+
|
|
970
|
+
/**
|
|
971
|
+
* The useCallback function in JavaScript ensures that a callback function is memoized based on its
|
|
972
|
+
* dependencies.
|
|
973
|
+
* @param callback - A function that you want to memoize and return for later use.
|
|
974
|
+
* @param deps - The `deps` parameter in the `useCallback` function refers to an array of dependencies.
|
|
975
|
+
* These dependencies are used to determine when the callback function should be re-evaluated and
|
|
976
|
+
* memoized. If any of the dependencies change, the callback function will be re-executed and the
|
|
977
|
+
* memoized value will
|
|
978
|
+
* @returns The useCallback function is returning the memoized version of the callback function passed
|
|
979
|
+
* as the first argument, based on the dependencies array provided as the second argument.
|
|
980
|
+
*/
|
|
981
|
+
const useCallback = (callback, deps) => {
|
|
982
|
+
if (!is.function(callback)) {
|
|
983
|
+
throw new Error('useCallback requires a function as first argument')
|
|
745
984
|
}
|
|
746
|
-
|
|
985
|
+
return useMemo(() => callback, deps)
|
|
986
|
+
};
|
|
747
987
|
|
|
748
|
-
|
|
749
|
-
|
|
988
|
+
/**
|
|
989
|
+
* The createContext function creates a context provider and useContext hook in JavaScript.
|
|
990
|
+
* @param [contextId] - The `contextId` parameter in the `createContext` function is used to specify
|
|
991
|
+
* the unique identifier for the context being created. It defaults to `RYUNIX_TYPES.RYUNIX_CONTEXT` if
|
|
992
|
+
* not provided.
|
|
993
|
+
* @param [defaultValue] - The `defaultValue` parameter in the `createContext` function is used to
|
|
994
|
+
* specify the default value that will be returned by the `useContext` hook if no provider is found in
|
|
995
|
+
* the component tree. It is an optional parameter, and if not provided, an empty object `{}` will be
|
|
996
|
+
* used as
|
|
997
|
+
* @returns The `createContext` function returns an object with two properties: `Provider` and
|
|
998
|
+
* `useContext`. The `Provider` property is a component that accepts `children` and `value` props, and
|
|
999
|
+
* sets the `_contextId` and `_contextValue` properties on the element. The `useContext` property is a
|
|
1000
|
+
* hook function that retrieves the context value based on the context ID provided, or
|
|
1001
|
+
*/
|
|
1002
|
+
const createContext = (
|
|
1003
|
+
contextId = RYUNIX_TYPES.RYUNIX_CONTEXT,
|
|
1004
|
+
defaultValue = {},
|
|
1005
|
+
) => {
|
|
1006
|
+
const Provider = ({ children, value }) => {
|
|
1007
|
+
const element = Fragment({ children });
|
|
1008
|
+
element._contextId = contextId;
|
|
1009
|
+
element._contextValue = value;
|
|
1010
|
+
return element
|
|
1011
|
+
};
|
|
1012
|
+
|
|
1013
|
+
Provider._contextId = contextId;
|
|
1014
|
+
|
|
1015
|
+
const useContext = (ctxID = contextId) => {
|
|
1016
|
+
validateHookCall();
|
|
1017
|
+
|
|
1018
|
+
const state = getState();
|
|
1019
|
+
let fiber = state.wipFiber;
|
|
1020
|
+
|
|
1021
|
+
while (fiber) {
|
|
1022
|
+
if (fiber._contextId === ctxID && fiber._contextValue !== undefined) {
|
|
1023
|
+
return fiber._contextValue
|
|
1024
|
+
}
|
|
1025
|
+
if (
|
|
1026
|
+
fiber.type?._contextId === ctxID &&
|
|
1027
|
+
fiber.props?.value !== undefined
|
|
1028
|
+
) {
|
|
1029
|
+
return fiber.props.value
|
|
1030
|
+
}
|
|
1031
|
+
fiber = fiber.parent;
|
|
1032
|
+
}
|
|
1033
|
+
return defaultValue
|
|
1034
|
+
};
|
|
1035
|
+
|
|
1036
|
+
return { Provider, useContext }
|
|
1037
|
+
};
|
|
750
1038
|
|
|
751
1039
|
/**
|
|
752
|
-
*
|
|
1040
|
+
* The `useQuery` function extracts query parameters from the URL in a browser environment.
|
|
1041
|
+
* @returns An object containing the query parameters from the current URL is being returned.
|
|
753
1042
|
*/
|
|
754
|
-
const
|
|
755
|
-
|
|
1043
|
+
const useQuery = () => {
|
|
1044
|
+
if (typeof window === 'undefined') return {}
|
|
756
1045
|
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
1046
|
+
const searchParams = new URLSearchParams(window.location.search);
|
|
1047
|
+
const query = {};
|
|
1048
|
+
for (const [key, value] of searchParams.entries()) {
|
|
1049
|
+
query[key] = value;
|
|
760
1050
|
}
|
|
1051
|
+
return query
|
|
761
1052
|
};
|
|
762
1053
|
|
|
763
1054
|
/**
|
|
764
|
-
*
|
|
1055
|
+
* The function `useHash` in JavaScript is used to manage and update the hash portion of the URL in a
|
|
1056
|
+
* web application.
|
|
1057
|
+
* @returns The `useHash` function returns the current hash value from the window's location. If the
|
|
1058
|
+
* window is undefined (e.g., in a server-side environment), it returns an empty string. The function
|
|
1059
|
+
* also sets up an event listener to update the hash value when the hash in the URL changes and removes
|
|
1060
|
+
* the event listener when the component unmounts.
|
|
765
1061
|
*/
|
|
766
|
-
const
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
1062
|
+
const useHash = () => {
|
|
1063
|
+
if (typeof window === 'undefined') return ''
|
|
1064
|
+
|
|
1065
|
+
const [hash, setHash] = useStore(window.location.hash);
|
|
1066
|
+
useEffect(() => {
|
|
1067
|
+
const onHashChange = () => setHash(window.location.hash);
|
|
1068
|
+
window.addEventListener('hashchange', onHashChange);
|
|
1069
|
+
return () => window.removeEventListener('hashchange', onHashChange)
|
|
1070
|
+
}, []);
|
|
1071
|
+
return hash
|
|
774
1072
|
};
|
|
775
1073
|
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
1074
|
+
/**
|
|
1075
|
+
* The `useMetadata` function in JavaScript is used to dynamically update metadata tags in the document
|
|
1076
|
+
* head based on provided tags and options.
|
|
1077
|
+
* @param [tags] - The `tags` parameter in the `useMetadata` function is an object that contains
|
|
1078
|
+
* metadata information for the webpage. It can include properties like `pageTitle`, `canonical`, and
|
|
1079
|
+
* other custom metadata tags like `og:title`, `og:description`, `twitter:title`,
|
|
1080
|
+
* `twitter:description`, etc. These tags
|
|
1081
|
+
* @param [options] - The `options` parameter in the `useMetadata` function is an object that can
|
|
1082
|
+
* contain the following properties:
|
|
1083
|
+
* - `title`: An object that can have the following properties:
|
|
1084
|
+
* - `template`: A string that defines the template for the page title. It can include a placeholder
|
|
1085
|
+
* `%s` that will be replaced with the actual page title.
|
|
1086
|
+
* - `prefix`: A string that will be used as the default title if no specific page title is provided.
|
|
1087
|
+
* @returns The `useMetadata` function does not return anything. It is a custom hook that updates the
|
|
1088
|
+
* document's metadata (such as title and meta tags) based on the provided `tags` and `options` whenever
|
|
1089
|
+
* they change.
|
|
1090
|
+
* This hook can't be reached by google crawler.
|
|
1091
|
+
*/
|
|
779
1092
|
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
}
|
|
1093
|
+
const useMetadata = (tags = {}, options = {}) => {
|
|
1094
|
+
useEffect(() => {
|
|
1095
|
+
if (typeof document === 'undefined') return
|
|
784
1096
|
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
1097
|
+
let finalTitle = 'Ryunix App';
|
|
1098
|
+
const template = options.title?.template;
|
|
1099
|
+
const defaultTitle = options.title?.prefix || 'Ryunix App';
|
|
1100
|
+
const pageTitle = tags.pageTitle || tags.title;
|
|
788
1101
|
|
|
789
|
-
|
|
1102
|
+
if (is.string(pageTitle) && pageTitle.trim()) {
|
|
1103
|
+
finalTitle = template?.includes('%s')
|
|
1104
|
+
? template.replace('%s', pageTitle)
|
|
1105
|
+
: pageTitle;
|
|
1106
|
+
} else {
|
|
1107
|
+
finalTitle = defaultTitle;
|
|
1108
|
+
}
|
|
1109
|
+
|
|
1110
|
+
document.title = finalTitle;
|
|
1111
|
+
|
|
1112
|
+
if (tags.canonical) {
|
|
1113
|
+
let link = document.querySelector('link[rel="canonical"]');
|
|
1114
|
+
if (!link) {
|
|
1115
|
+
link = document.createElement('link');
|
|
1116
|
+
link.setAttribute('rel', 'canonical');
|
|
1117
|
+
document.head.appendChild(link);
|
|
1118
|
+
}
|
|
1119
|
+
link.setAttribute('href', tags.canonical);
|
|
1120
|
+
}
|
|
1121
|
+
|
|
1122
|
+
Object.entries(tags).forEach(([key, value]) => {
|
|
1123
|
+
if (['title', 'pageTitle', 'canonical'].includes(key)) return
|
|
1124
|
+
|
|
1125
|
+
const isProperty = key.startsWith('og:') || key.startsWith('twitter:');
|
|
1126
|
+
const selector = `meta[${isProperty ? 'property' : 'name'}='${key}']`;
|
|
1127
|
+
let meta = document.head.querySelector(selector);
|
|
1128
|
+
|
|
1129
|
+
if (!meta) {
|
|
1130
|
+
meta = document.createElement('meta');
|
|
1131
|
+
meta.setAttribute(isProperty ? 'property' : 'name', key);
|
|
1132
|
+
document.head.appendChild(meta);
|
|
1133
|
+
}
|
|
1134
|
+
meta.setAttribute('content', value);
|
|
1135
|
+
});
|
|
1136
|
+
}, [JSON.stringify(tags), JSON.stringify(options)]);
|
|
790
1137
|
};
|
|
791
1138
|
|
|
792
|
-
|
|
1139
|
+
// Router Context
|
|
1140
|
+
const RouterContext = createContext('ryunix.navigation', {
|
|
1141
|
+
location: '/',
|
|
1142
|
+
params: {},
|
|
1143
|
+
query: {},
|
|
1144
|
+
navigate: (path) => {},
|
|
1145
|
+
route: null,
|
|
1146
|
+
});
|
|
793
1147
|
|
|
794
|
-
const
|
|
795
|
-
const
|
|
1148
|
+
const findRoute = (routes, path) => {
|
|
1149
|
+
const pathname = path.split('?')[0].split('#')[0];
|
|
1150
|
+
const notFoundRoute = routes.find((route) => route.NotFound);
|
|
1151
|
+
const notFound = notFoundRoute
|
|
1152
|
+
? { route: { component: notFoundRoute.NotFound }, params: {} }
|
|
1153
|
+
: { route: { component: null }, params: {} };
|
|
796
1154
|
|
|
797
|
-
|
|
1155
|
+
for (const route of routes) {
|
|
1156
|
+
if (route.subRoutes) {
|
|
1157
|
+
const childRoute = findRoute(route.subRoutes, path);
|
|
1158
|
+
if (childRoute) return childRoute
|
|
1159
|
+
}
|
|
1160
|
+
if (route.path === '*') return notFound
|
|
1161
|
+
if (!route.path || typeof route.path !== 'string') continue
|
|
798
1162
|
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
1163
|
+
const keys = [];
|
|
1164
|
+
const pattern = new RegExp(
|
|
1165
|
+
`^${route.path.replace(/:\w+/g, (match) => {
|
|
1166
|
+
keys.push(match.substring(1));
|
|
1167
|
+
return '([^/]+)'
|
|
1168
|
+
})}$`,
|
|
1169
|
+
);
|
|
1170
|
+
|
|
1171
|
+
const match = pathname.match(pattern);
|
|
1172
|
+
if (match) {
|
|
1173
|
+
const params = keys.reduce((acc, key, index) => {
|
|
1174
|
+
acc[key] = match[index + 1];
|
|
1175
|
+
return acc
|
|
1176
|
+
}, {});
|
|
1177
|
+
return { route, params }
|
|
1178
|
+
}
|
|
804
1179
|
}
|
|
1180
|
+
return notFound
|
|
1181
|
+
};
|
|
805
1182
|
|
|
806
|
-
|
|
807
|
-
|
|
1183
|
+
/**
|
|
1184
|
+
* The `RouterProvider` component manages routing in a Ryunix application by updating the location based
|
|
1185
|
+
* on window events and providing context for the current route.
|
|
1186
|
+
* @returns The `RouterProvider` component is returning a `RouterContext.Provider` component with a
|
|
1187
|
+
* `value` prop set to `contextValue`, and wrapping the `children` within a `Fragment`.
|
|
1188
|
+
*/
|
|
1189
|
+
const RouterProvider = ({ routes, children }) => {
|
|
1190
|
+
const [location, setLocation] = useStore(window.location.pathname);
|
|
808
1191
|
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
1192
|
+
useEffect(() => {
|
|
1193
|
+
const update = () => setLocation(window.location.pathname);
|
|
1194
|
+
window.addEventListener('popstate', update);
|
|
1195
|
+
window.addEventListener('hashchange', update);
|
|
1196
|
+
return () => {
|
|
1197
|
+
window.removeEventListener('popstate', update);
|
|
1198
|
+
window.removeEventListener('hashchange', update);
|
|
816
1199
|
}
|
|
817
|
-
|
|
818
|
-
|
|
1200
|
+
}, [location]);
|
|
1201
|
+
|
|
1202
|
+
const navigate = (path) => {
|
|
1203
|
+
window.history.pushState({}, '', path);
|
|
1204
|
+
setLocation(path);
|
|
1205
|
+
};
|
|
1206
|
+
|
|
1207
|
+
const currentRouteData = findRoute(routes, location) || {};
|
|
1208
|
+
const query = useQuery();
|
|
1209
|
+
|
|
1210
|
+
const contextValue = {
|
|
1211
|
+
location,
|
|
1212
|
+
params: currentRouteData.params || {},
|
|
1213
|
+
query,
|
|
1214
|
+
navigate,
|
|
1215
|
+
route: currentRouteData.route,
|
|
1216
|
+
};
|
|
1217
|
+
|
|
1218
|
+
return createElement(
|
|
1219
|
+
RouterContext.Provider,
|
|
1220
|
+
{ value: contextValue },
|
|
1221
|
+
Fragment({ children }),
|
|
1222
|
+
)
|
|
819
1223
|
};
|
|
820
1224
|
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
1225
|
+
/**
|
|
1226
|
+
* The function `useRouter` returns the context of the Router for navigation in a Ryunix application.
|
|
1227
|
+
* @returns The `useRouter` function is returning the result of calling
|
|
1228
|
+
* `RouterContext.useContext('ryunix.navigation')`. This function is likely attempting to retrieve the
|
|
1229
|
+
* navigation context from the RouterContext.
|
|
1230
|
+
*/
|
|
1231
|
+
const useRouter = () => {
|
|
1232
|
+
return RouterContext.useContext('ryunix.navigation')
|
|
1233
|
+
};
|
|
828
1234
|
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
1235
|
+
/**
|
|
1236
|
+
* The `Children` function in JavaScript uses router hooks to handle scrolling to a specific element
|
|
1237
|
+
* based on the hash in the URL.
|
|
1238
|
+
* @returns The `Children` component is returning the result of calling `createElement` with
|
|
1239
|
+
* `route.component` as the first argument and an object with `key`, `params`, `query`, and `hash`
|
|
1240
|
+
* properties as the second argument. The `key` property is set to `location`, and the `params`,
|
|
1241
|
+
* `query`, and `hash` properties are passed as values from the component's props.
|
|
1242
|
+
*/
|
|
1243
|
+
const Children = () => {
|
|
1244
|
+
const { route, params, query, location } = useRouter();
|
|
1245
|
+
if (!route || !route.component) return null
|
|
1246
|
+
const hash = useHash();
|
|
1247
|
+
|
|
1248
|
+
useEffect(() => {
|
|
1249
|
+
if (hash) {
|
|
1250
|
+
const id = hash.slice(1);
|
|
1251
|
+
const el = document.getElementById(id);
|
|
1252
|
+
if (el) el.scrollIntoView({ block: 'start', behavior: 'smooth' });
|
|
1253
|
+
}
|
|
1254
|
+
}, [hash]);
|
|
1255
|
+
|
|
1256
|
+
return createElement(route.component, {
|
|
1257
|
+
key: location,
|
|
1258
|
+
params,
|
|
1259
|
+
query,
|
|
1260
|
+
hash,
|
|
1261
|
+
})
|
|
835
1262
|
};
|
|
836
1263
|
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
1264
|
+
/**
|
|
1265
|
+
* The NavLink function in JavaScript is a component that generates a link element with customizable
|
|
1266
|
+
* classes and active state based on the current location.
|
|
1267
|
+
* @returns The `NavLink` component is returning a JSX element representing an anchor (`<a>`) tag with
|
|
1268
|
+
* the following attributes and properties:
|
|
1269
|
+
*/
|
|
1270
|
+
const NavLink = ({ to, exact = false, ...props }) => {
|
|
1271
|
+
const { location, navigate } = useRouter();
|
|
1272
|
+
const isActive = exact ? location === to : location.startsWith(to);
|
|
846
1273
|
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
scheduleWork(state.wipRoot);
|
|
850
|
-
return state.wipRoot
|
|
851
|
-
};
|
|
1274
|
+
const resolveClass = (cls) =>
|
|
1275
|
+
typeof cls === 'function' ? cls({ isActive }) : cls || '';
|
|
852
1276
|
|
|
853
|
-
const
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
return renderProcess
|
|
858
|
-
};
|
|
1277
|
+
const handleClick = (e) => {
|
|
1278
|
+
e.preventDefault();
|
|
1279
|
+
navigate(to);
|
|
1280
|
+
};
|
|
859
1281
|
|
|
860
|
-
const
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
if (process.env.NODE_ENV !== 'production') {
|
|
865
|
-
console.error('Component error:', error);
|
|
866
|
-
}
|
|
867
|
-
if (onError) onError(error);
|
|
868
|
-
return null
|
|
869
|
-
}
|
|
870
|
-
};
|
|
1282
|
+
const classAttrName = props['ryunix-class'] ? 'ryunix-class' : 'className';
|
|
1283
|
+
const classAttrValue = resolveClass(
|
|
1284
|
+
props['ryunix-class'] || props['className'],
|
|
1285
|
+
);
|
|
871
1286
|
|
|
872
|
-
const
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
)
|
|
878
|
-
}
|
|
879
|
-
if (!Array.isArray(state.wipFiber.hooks)) {
|
|
880
|
-
state.wipFiber.hooks = [];
|
|
881
|
-
}
|
|
882
|
-
};
|
|
1287
|
+
const {
|
|
1288
|
+
['ryunix-class']: _omitRyunix,
|
|
1289
|
+
className: _omitClassName,
|
|
1290
|
+
...cleanedProps
|
|
1291
|
+
} = props;
|
|
883
1292
|
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
1293
|
+
return createElement(
|
|
1294
|
+
'a',
|
|
1295
|
+
{
|
|
1296
|
+
href: to,
|
|
1297
|
+
onClick: handleClick,
|
|
1298
|
+
[classAttrName]: classAttrValue,
|
|
1299
|
+
...cleanedProps,
|
|
1300
|
+
},
|
|
1301
|
+
props.children,
|
|
1302
|
+
)
|
|
888
1303
|
};
|
|
889
1304
|
|
|
890
|
-
|
|
1305
|
+
/**
|
|
1306
|
+
* useStore with priority support
|
|
1307
|
+
*/
|
|
1308
|
+
const useStorePriority = (initialState) => {
|
|
891
1309
|
const reducer = (state, action) =>
|
|
892
|
-
|
|
893
|
-
return useReducer(reducer, initialState)
|
|
894
|
-
};
|
|
1310
|
+
typeof action === 'function' ? action.value(state) : action.value;
|
|
895
1311
|
|
|
896
|
-
const
|
|
897
|
-
validateHookCall();
|
|
1312
|
+
const [state, baseDispatch] = useReducer(reducer, initialState);
|
|
898
1313
|
|
|
899
|
-
const
|
|
900
|
-
|
|
901
|
-
|
|
1314
|
+
const dispatch = (action, priority = Priority.NORMAL) => {
|
|
1315
|
+
const wrappedAction = {
|
|
1316
|
+
value: action,
|
|
1317
|
+
priority,
|
|
1318
|
+
};
|
|
902
1319
|
|
|
903
|
-
|
|
904
|
-
hookID: hookIndex,
|
|
905
|
-
type: RYUNIX_TYPES.RYUNIX_STORE,
|
|
906
|
-
state: oldHook ? oldHook.state : init ? init(initialState) : initialState,
|
|
907
|
-
queue: [], // Siempre nueva cola vacía
|
|
1320
|
+
baseDispatch(wrappedAction);
|
|
908
1321
|
};
|
|
909
1322
|
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
oldHook.queue.forEach((action) => {
|
|
913
|
-
try {
|
|
914
|
-
hook.state = reducer(hook.state, action);
|
|
915
|
-
} catch (error) {
|
|
916
|
-
if (process.env.NODE_ENV !== 'production') {
|
|
917
|
-
console.error('Error in reducer:', error);
|
|
918
|
-
}
|
|
919
|
-
}
|
|
920
|
-
});
|
|
921
|
-
}
|
|
1323
|
+
return [state, dispatch]
|
|
1324
|
+
};
|
|
922
1325
|
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
return
|
|
929
|
-
}
|
|
1326
|
+
/**
|
|
1327
|
+
* useTransition - Mark updates as non-urgent
|
|
1328
|
+
*/
|
|
1329
|
+
const useTransition = () => {
|
|
1330
|
+
const [isPending, setIsPending] = useStorePriority(false);
|
|
930
1331
|
|
|
931
|
-
|
|
1332
|
+
const startTransition = (callback) => {
|
|
1333
|
+
setIsPending(true, Priority.IMMEDIATE);
|
|
932
1334
|
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
alternate: currentState.currentRoot,
|
|
938
|
-
};
|
|
939
|
-
currentState.deletions = [];
|
|
940
|
-
currentState.hookIndex = 0;
|
|
941
|
-
scheduleWork(currentState.wipRoot);
|
|
1335
|
+
setTimeout(() => {
|
|
1336
|
+
callback();
|
|
1337
|
+
setIsPending(false, Priority.IMMEDIATE);
|
|
1338
|
+
}, 0);
|
|
942
1339
|
};
|
|
943
1340
|
|
|
944
|
-
|
|
945
|
-
state.hookIndex++;
|
|
946
|
-
return [hook.state, dispatch]
|
|
1341
|
+
return [isPending, startTransition]
|
|
947
1342
|
};
|
|
948
1343
|
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
}
|
|
955
|
-
if (deps !== undefined && !Array.isArray(deps)) {
|
|
956
|
-
throw new Error('useEffect dependencies must be an array or undefined')
|
|
957
|
-
}
|
|
1344
|
+
/**
|
|
1345
|
+
* useDeferredValue - Defer value updates
|
|
1346
|
+
*/
|
|
1347
|
+
const useDeferredValue = (value) => {
|
|
1348
|
+
const [deferredValue, setDeferredValue] = useStorePriority(value);
|
|
958
1349
|
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
1350
|
+
useEffect(() => {
|
|
1351
|
+
const timeout = setTimeout(() => {
|
|
1352
|
+
setDeferredValue(value, Priority.LOW);
|
|
1353
|
+
}, 100);
|
|
963
1354
|
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
type: RYUNIX_TYPES.RYUNIX_EFFECT,
|
|
967
|
-
deps,
|
|
968
|
-
effect: hasChanged ? callback : null,
|
|
969
|
-
cancel: oldHook?.cancel,
|
|
970
|
-
};
|
|
1355
|
+
return () => clearTimeout(timeout)
|
|
1356
|
+
}, [value]);
|
|
971
1357
|
|
|
972
|
-
|
|
973
|
-
state.hookIndex++;
|
|
1358
|
+
return deferredValue
|
|
974
1359
|
};
|
|
975
1360
|
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
1361
|
+
/**
|
|
1362
|
+
* The `usePersitentStore` function manages state using local storage in JavaScript, allowing for easy
|
|
1363
|
+
* storage and retrieval of data.
|
|
1364
|
+
* @param key - The `key` parameter in the `usePersitentStore` function is a string that represents the key
|
|
1365
|
+
* under which the data will be stored in the browser's local storage. It is used to retrieve and store
|
|
1366
|
+
* data associated with this specific key.
|
|
1367
|
+
* @param [initialState] - The `initialState` parameter in the `usePersitentStore` function is the initial
|
|
1368
|
+
* value that will be used if there is no data stored in the local storage under the specified `key`.
|
|
1369
|
+
* It serves as the default value for the state if no data is retrieved from the local storage.
|
|
1370
|
+
* @returns The `usePersitentStore` function returns an array containing two elements:
|
|
1371
|
+
* 1. The current state value retrieved from local storage or the initial state if not found.
|
|
1372
|
+
* 2. The `setValue` function that updates the state value and stores it in the local storage as a JSON
|
|
1373
|
+
* string.
|
|
1374
|
+
*/
|
|
1375
|
+
const usePersitentStore = (key, initialState = '') => {
|
|
1376
|
+
const [state, dispatch] = useStore(() => {
|
|
1377
|
+
try {
|
|
1378
|
+
const item = window.localStorage.getItem(key);
|
|
1379
|
+
return item ? JSON.parse(item) : initialState
|
|
1380
|
+
} catch (error) {
|
|
1381
|
+
return initialState
|
|
1382
|
+
}
|
|
1383
|
+
});
|
|
982
1384
|
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
1385
|
+
/**
|
|
1386
|
+
* The function `setValue` dispatches a value and stores it in the local storage as a JSON string,
|
|
1387
|
+
* handling any errors with a console log.
|
|
1388
|
+
* @param value - The `value` parameter in the `setValue` function is the data that you want to set.
|
|
1389
|
+
* It is dispatched to update the state and then stored in the browser's local storage after being
|
|
1390
|
+
* converted to a JSON string.
|
|
1391
|
+
*/
|
|
1392
|
+
const setValue = (value) => {
|
|
1393
|
+
try {
|
|
1394
|
+
dispatch(value);
|
|
1395
|
+
window.localStorage.setItem(key, JSON.stringify(value));
|
|
1396
|
+
} catch (error) {
|
|
1397
|
+
console.error(error);
|
|
1398
|
+
}
|
|
987
1399
|
};
|
|
988
1400
|
|
|
989
|
-
|
|
990
|
-
state.hookIndex++;
|
|
991
|
-
return hook.value
|
|
1401
|
+
return [state, setValue]
|
|
992
1402
|
};
|
|
993
1403
|
|
|
994
|
-
|
|
995
|
-
|
|
1404
|
+
/**
|
|
1405
|
+
* The `useSwitch` function returns a state value and a toggle function to switch the state between
|
|
1406
|
+
* true and false.
|
|
1407
|
+
* @param [initialState=false] - The `initialState` parameter in the `useSwitch` function is used to
|
|
1408
|
+
* set the initial value of the state. If no value is provided when calling `useSwitch`, the default
|
|
1409
|
+
* initial state will be `false`.
|
|
1410
|
+
* @returns An array containing the current state value and a function `toggle` that toggles the state
|
|
1411
|
+
* value.
|
|
1412
|
+
*/
|
|
1413
|
+
const useSwitch = (initialState = false) => {
|
|
1414
|
+
const [state, dispatch] = useStore(initialState);
|
|
1415
|
+
|
|
1416
|
+
/**
|
|
1417
|
+
* The function `toggle` toggles the state by dispatching the opposite value of the current state.
|
|
1418
|
+
*/
|
|
1419
|
+
const toggle = () => {
|
|
1420
|
+
dispatch(!state);
|
|
1421
|
+
};
|
|
996
1422
|
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
}
|
|
1000
|
-
if (!Array.isArray(deps)) {
|
|
1001
|
-
throw new Error('useMemo requires a dependencies array')
|
|
1002
|
-
}
|
|
1423
|
+
return [state, toggle]
|
|
1424
|
+
};
|
|
1003
1425
|
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1426
|
+
var hooks = /*#__PURE__*/Object.freeze({
|
|
1427
|
+
__proto__: null,
|
|
1428
|
+
Children: Children,
|
|
1429
|
+
NavLink: NavLink,
|
|
1430
|
+
RouterProvider: RouterProvider,
|
|
1431
|
+
createContext: createContext,
|
|
1432
|
+
useCallback: useCallback,
|
|
1433
|
+
useDeferredValue: useDeferredValue,
|
|
1434
|
+
useEffect: useEffect,
|
|
1435
|
+
useHash: useHash,
|
|
1436
|
+
useMemo: useMemo,
|
|
1437
|
+
useMetadata: useMetadata,
|
|
1438
|
+
usePersitentStore: usePersitentStore,
|
|
1439
|
+
useQuery: useQuery,
|
|
1440
|
+
useReducer: useReducer,
|
|
1441
|
+
useRef: useRef,
|
|
1442
|
+
useRouter: useRouter,
|
|
1443
|
+
useStore: useStore,
|
|
1444
|
+
useStorePriority: useStorePriority,
|
|
1445
|
+
useSwitch: useSwitch,
|
|
1446
|
+
useTransition: useTransition
|
|
1447
|
+
});
|
|
1007
1448
|
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
value = compute();
|
|
1014
|
-
} catch (error) {
|
|
1015
|
-
if (process.env.NODE_ENV !== 'production') {
|
|
1016
|
-
console.error('Error in useMemo computation:', error);
|
|
1017
|
-
}
|
|
1018
|
-
value = undefined;
|
|
1019
|
-
}
|
|
1020
|
-
}
|
|
1449
|
+
const updateFunctionComponent = (fiber) => {
|
|
1450
|
+
const state = getState();
|
|
1451
|
+
state.wipFiber = fiber;
|
|
1452
|
+
state.hookIndex = 0;
|
|
1453
|
+
state.wipFiber.hooks = [];
|
|
1021
1454
|
|
|
1022
|
-
const
|
|
1023
|
-
hookID: hookIndex,
|
|
1024
|
-
type: RYUNIX_TYPES.RYUNIX_MEMO,
|
|
1025
|
-
value,
|
|
1026
|
-
deps,
|
|
1027
|
-
};
|
|
1455
|
+
const children = [fiber.type(fiber.props)];
|
|
1028
1456
|
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1457
|
+
if (fiber.type._contextId && fiber.props.value !== undefined) {
|
|
1458
|
+
fiber._contextId = fiber.type._contextId;
|
|
1459
|
+
fiber._contextValue = fiber.props.value;
|
|
1460
|
+
}
|
|
1461
|
+
|
|
1462
|
+
reconcileChildren(fiber, children);
|
|
1032
1463
|
};
|
|
1033
1464
|
|
|
1034
|
-
const
|
|
1035
|
-
if (!
|
|
1036
|
-
|
|
1465
|
+
const updateHostComponent = (fiber) => {
|
|
1466
|
+
if (!fiber.dom) {
|
|
1467
|
+
fiber.dom = createDom(fiber);
|
|
1037
1468
|
}
|
|
1038
|
-
|
|
1469
|
+
const children = fiber.props?.children || [];
|
|
1470
|
+
reconcileChildren(fiber, children);
|
|
1039
1471
|
};
|
|
1040
1472
|
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
};
|
|
1473
|
+
/**
|
|
1474
|
+
* The Component `Image` takes in a `src` and other props, and returns an `img` element with the
|
|
1475
|
+
* specified `src` and props.
|
|
1476
|
+
* @returns The `Image` component is being returned. It is a functional component that renders an `img`
|
|
1477
|
+
* element with the specified `src` and other props passed to it.
|
|
1478
|
+
*/
|
|
1479
|
+
const Image = ({ src, ...props }) => {
|
|
1480
|
+
return createElement('img', { ...props, src })
|
|
1481
|
+
};
|
|
1051
1482
|
|
|
1052
|
-
|
|
1483
|
+
const { Provider: MDXProvider, useContext: useMDXComponents } = createContext(
|
|
1484
|
+
'ryunix.mdx',
|
|
1485
|
+
{},
|
|
1486
|
+
);
|
|
1053
1487
|
|
|
1054
|
-
|
|
1055
|
-
|
|
1488
|
+
/**
|
|
1489
|
+
* Get merged MDX components from context and provided components
|
|
1490
|
+
* @param {Object} components - Additional components to merge
|
|
1491
|
+
* @returns {Object} Merged components object
|
|
1492
|
+
*/
|
|
1493
|
+
const getMDXComponents = (components) => {
|
|
1494
|
+
const contextComponents = useMDXComponents();
|
|
1495
|
+
return {
|
|
1496
|
+
...contextComponents,
|
|
1497
|
+
...components,
|
|
1498
|
+
}
|
|
1499
|
+
};
|
|
1056
1500
|
|
|
1057
|
-
|
|
1058
|
-
|
|
1501
|
+
/**
|
|
1502
|
+
* Default MDX components with Ryunix-optimized rendering
|
|
1503
|
+
*/
|
|
1504
|
+
const defaultComponents = {
|
|
1505
|
+
// Headings
|
|
1506
|
+
h1: (props) => createElement('h1', { ...props }),
|
|
1507
|
+
h2: (props) => createElement('h2', { ...props }),
|
|
1508
|
+
h3: (props) => createElement('h3', { ...props }),
|
|
1509
|
+
h4: (props) => createElement('h4', { ...props }),
|
|
1510
|
+
h5: (props) => createElement('h5', { ...props }),
|
|
1511
|
+
h6: (props) => createElement('h6', { ...props }),
|
|
1512
|
+
|
|
1513
|
+
// Text
|
|
1514
|
+
p: (props) => createElement('p', { ...props }),
|
|
1515
|
+
a: (props) => createElement('a', { ...props }),
|
|
1516
|
+
strong: (props) => createElement('strong', { ...props }),
|
|
1517
|
+
em: (props) => createElement('em', { ...props }),
|
|
1518
|
+
code: (props) => createElement('code', { ...props }),
|
|
1519
|
+
|
|
1520
|
+
// Lists
|
|
1521
|
+
ul: (props) => createElement('ul', { ...props }),
|
|
1522
|
+
ol: (props) => createElement('ol', { ...props }),
|
|
1523
|
+
li: (props) => createElement('li', { ...props }),
|
|
1524
|
+
|
|
1525
|
+
// Blocks
|
|
1526
|
+
blockquote: (props) => createElement('blockquote', { ...props }),
|
|
1527
|
+
pre: (props) => createElement('pre', { ...props }),
|
|
1528
|
+
hr: (props) => createElement('hr', { ...props }),
|
|
1529
|
+
|
|
1530
|
+
// Tables
|
|
1531
|
+
table: (props) => createElement('table', { ...props }),
|
|
1532
|
+
thead: (props) => createElement('thead', { ...props }),
|
|
1533
|
+
tbody: (props) => createElement('tbody', { ...props }),
|
|
1534
|
+
tr: (props) => createElement('tr', { ...props }),
|
|
1535
|
+
th: (props) => createElement('th', { ...props }),
|
|
1536
|
+
td: (props) => createElement('td', { ...props }),
|
|
1537
|
+
|
|
1538
|
+
// Media
|
|
1539
|
+
img: (props) => createElement('img', { ...props }),
|
|
1540
|
+
};
|
|
1059
1541
|
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
fiber.props?.value !== undefined
|
|
1067
|
-
) {
|
|
1068
|
-
return fiber.props.value
|
|
1069
|
-
}
|
|
1070
|
-
fiber = fiber.parent;
|
|
1071
|
-
}
|
|
1072
|
-
return defaultValue
|
|
1073
|
-
};
|
|
1542
|
+
/**
|
|
1543
|
+
* MDX Wrapper component
|
|
1544
|
+
* Provides default styling and components for MDX content
|
|
1545
|
+
*/
|
|
1546
|
+
const MDXContent = ({ children, components = {} }) => {
|
|
1547
|
+
const mergedComponents = getMDXComponents(components);
|
|
1074
1548
|
|
|
1075
|
-
return
|
|
1549
|
+
return createElement(
|
|
1550
|
+
MDXProvider,
|
|
1551
|
+
{ value: mergedComponents },
|
|
1552
|
+
createElement('div', null, children),
|
|
1553
|
+
)
|
|
1076
1554
|
};
|
|
1077
1555
|
|
|
1078
|
-
|
|
1079
|
-
|
|
1556
|
+
/**
|
|
1557
|
+
* Performance profiler for Ryunix
|
|
1558
|
+
*/
|
|
1559
|
+
class Profiler {
|
|
1560
|
+
constructor() {
|
|
1561
|
+
this.enabled = process.env.NODE_ENV !== 'production';
|
|
1562
|
+
this.measures = new Map();
|
|
1563
|
+
this.renderTimes = [];
|
|
1564
|
+
this.maxSamples = 100;
|
|
1565
|
+
}
|
|
1080
1566
|
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
query[key] = value;
|
|
1567
|
+
startMeasure(name) {
|
|
1568
|
+
if (!this.enabled) return
|
|
1569
|
+
this.measures.set(name, performance.now());
|
|
1085
1570
|
}
|
|
1086
|
-
return query
|
|
1087
|
-
};
|
|
1088
1571
|
|
|
1089
|
-
|
|
1090
|
-
|
|
1572
|
+
endMeasure(name) {
|
|
1573
|
+
if (!this.enabled) return
|
|
1574
|
+
const start = this.measures.get(name);
|
|
1575
|
+
if (!start) return
|
|
1091
1576
|
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
const onHashChange = () => setHash(window.location.hash);
|
|
1095
|
-
window.addEventListener('hashchange', onHashChange);
|
|
1096
|
-
return () => window.removeEventListener('hashchange', onHashChange)
|
|
1097
|
-
}, []);
|
|
1098
|
-
return hash
|
|
1099
|
-
};
|
|
1577
|
+
const duration = performance.now() - start;
|
|
1578
|
+
this.measures.delete(name);
|
|
1100
1579
|
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
if (typeof document === 'undefined') return
|
|
1580
|
+
return duration
|
|
1581
|
+
}
|
|
1104
1582
|
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
const defaultTitle = options.title?.prefix || 'Ryunix App';
|
|
1108
|
-
const pageTitle = tags.pageTitle || tags.title;
|
|
1583
|
+
recordRender(componentName, duration) {
|
|
1584
|
+
if (!this.enabled) return
|
|
1109
1585
|
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
}
|
|
1115
|
-
|
|
1586
|
+
this.renderTimes.push({
|
|
1587
|
+
component: componentName,
|
|
1588
|
+
duration,
|
|
1589
|
+
timestamp: Date.now(),
|
|
1590
|
+
});
|
|
1591
|
+
|
|
1592
|
+
if (this.renderTimes.length > this.maxSamples) {
|
|
1593
|
+
this.renderTimes.shift();
|
|
1116
1594
|
}
|
|
1595
|
+
}
|
|
1117
1596
|
|
|
1118
|
-
|
|
1597
|
+
getStats() {
|
|
1598
|
+
if (!this.enabled) return null
|
|
1119
1599
|
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
link.setAttribute('rel', 'canonical');
|
|
1125
|
-
document.head.appendChild(link);
|
|
1126
|
-
}
|
|
1127
|
-
link.setAttribute('href', tags.canonical);
|
|
1128
|
-
}
|
|
1600
|
+
const total = this.renderTimes.reduce((sum, r) => sum + r.duration, 0);
|
|
1601
|
+
const avg = total / this.renderTimes.length;
|
|
1602
|
+
const max = Math.max(...this.renderTimes.map((r) => r.duration));
|
|
1603
|
+
const min = Math.min(...this.renderTimes.map((r) => r.duration));
|
|
1129
1604
|
|
|
1130
|
-
|
|
1131
|
-
|
|
1605
|
+
return { total, avg, max, min, count: this.renderTimes.length }
|
|
1606
|
+
}
|
|
1132
1607
|
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
let meta = document.head.querySelector(selector);
|
|
1608
|
+
getSlowestComponents(limit = 10) {
|
|
1609
|
+
if (!this.enabled) return []
|
|
1136
1610
|
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1611
|
+
const byComponent = new Map();
|
|
1612
|
+
|
|
1613
|
+
this.renderTimes.forEach(({ component, duration }) => {
|
|
1614
|
+
if (!byComponent.has(component)) {
|
|
1615
|
+
byComponent.set(component, { total: 0, count: 0, max: 0 });
|
|
1141
1616
|
}
|
|
1142
|
-
|
|
1617
|
+
const stats = byComponent.get(component);
|
|
1618
|
+
stats.total += duration;
|
|
1619
|
+
stats.count++;
|
|
1620
|
+
stats.max = Math.max(stats.max, duration);
|
|
1143
1621
|
});
|
|
1144
|
-
}, [JSON.stringify(tags), JSON.stringify(options)]);
|
|
1145
|
-
};
|
|
1146
1622
|
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1623
|
+
return Array.from(byComponent.entries())
|
|
1624
|
+
.map(([name, stats]) => ({
|
|
1625
|
+
name,
|
|
1626
|
+
avg: stats.total / stats.count,
|
|
1627
|
+
max: stats.max,
|
|
1628
|
+
count: stats.count,
|
|
1629
|
+
}))
|
|
1630
|
+
.sort((a, b) => b.avg - a.avg)
|
|
1631
|
+
.slice(0, limit)
|
|
1632
|
+
}
|
|
1155
1633
|
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
const notFoundRoute = routes.find((route) => route.NotFound);
|
|
1159
|
-
const notFound = notFoundRoute
|
|
1160
|
-
? { route: { component: notFoundRoute.NotFound }, params: {} }
|
|
1161
|
-
: { route: { component: null }, params: {} };
|
|
1634
|
+
logStats() {
|
|
1635
|
+
if (!this.enabled) return
|
|
1162
1636
|
|
|
1163
|
-
|
|
1164
|
-
if (
|
|
1165
|
-
const childRoute = findRoute(route.subRoutes, path);
|
|
1166
|
-
if (childRoute) return childRoute
|
|
1167
|
-
}
|
|
1168
|
-
if (route.path === '*') return notFound
|
|
1169
|
-
if (!route.path || typeof route.path !== 'string') continue
|
|
1637
|
+
const stats = this.getStats();
|
|
1638
|
+
if (!stats) return
|
|
1170
1639
|
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
})}$`,
|
|
1640
|
+
console.group('🔍 Ryunix Performance Stats');
|
|
1641
|
+
console.log(`Total renders: ${stats.count}`);
|
|
1642
|
+
console.log(`Avg render time: ${stats.avg.toFixed(2)}ms`);
|
|
1643
|
+
console.log(
|
|
1644
|
+
`Min: ${stats.min.toFixed(2)}ms | Max: ${stats.max.toFixed(2)}ms`,
|
|
1177
1645
|
);
|
|
1178
1646
|
|
|
1179
|
-
const
|
|
1180
|
-
if (
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
}
|
|
1188
|
-
return notFound
|
|
1189
|
-
};
|
|
1190
|
-
|
|
1191
|
-
const RouterProvider = ({ routes, children }) => {
|
|
1192
|
-
const [location, setLocation] = useStore(window.location.pathname);
|
|
1193
|
-
|
|
1194
|
-
useEffect(() => {
|
|
1195
|
-
const update = () => setLocation(window.location.pathname);
|
|
1196
|
-
window.addEventListener('popstate', update);
|
|
1197
|
-
window.addEventListener('hashchange', update);
|
|
1198
|
-
return () => {
|
|
1199
|
-
window.removeEventListener('popstate', update);
|
|
1200
|
-
window.removeEventListener('hashchange', update);
|
|
1647
|
+
const slowest = this.getSlowestComponents(5);
|
|
1648
|
+
if (slowest.length > 0) {
|
|
1649
|
+
console.log('\n⚠️ Slowest components:');
|
|
1650
|
+
slowest.forEach((comp, i) => {
|
|
1651
|
+
console.log(
|
|
1652
|
+
`${i + 1}. ${comp.name}: ${comp.avg.toFixed(2)}ms avg (${comp.count} renders)`,
|
|
1653
|
+
);
|
|
1654
|
+
});
|
|
1201
1655
|
}
|
|
1202
|
-
|
|
1656
|
+
console.groupEnd();
|
|
1657
|
+
}
|
|
1203
1658
|
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
}
|
|
1659
|
+
clear() {
|
|
1660
|
+
this.renderTimes = [];
|
|
1661
|
+
this.measures.clear();
|
|
1662
|
+
}
|
|
1208
1663
|
|
|
1209
|
-
|
|
1210
|
-
|
|
1664
|
+
enable() {
|
|
1665
|
+
this.enabled = true;
|
|
1666
|
+
}
|
|
1211
1667
|
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
navigate,
|
|
1217
|
-
route: currentRouteData.route,
|
|
1218
|
-
};
|
|
1668
|
+
disable() {
|
|
1669
|
+
this.enabled = false;
|
|
1670
|
+
}
|
|
1671
|
+
}
|
|
1219
1672
|
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1673
|
+
// Global profiler instance
|
|
1674
|
+
const profiler = new Profiler();
|
|
1675
|
+
|
|
1676
|
+
/**
|
|
1677
|
+
* Hook to profile component render
|
|
1678
|
+
*/
|
|
1679
|
+
const useProfiler = (componentName) => {
|
|
1680
|
+
const startTime = performance.now();
|
|
1681
|
+
|
|
1682
|
+
return () => {
|
|
1683
|
+
const duration = performance.now() - startTime;
|
|
1684
|
+
profiler.recordRender(componentName, duration);
|
|
1685
|
+
}
|
|
1225
1686
|
};
|
|
1226
1687
|
|
|
1227
|
-
|
|
1228
|
-
|
|
1688
|
+
/**
|
|
1689
|
+
* HOC to profile component
|
|
1690
|
+
*/
|
|
1691
|
+
const withProfiler = (Component, name) => {
|
|
1692
|
+
return (props) => {
|
|
1693
|
+
profiler.startMeasure(name);
|
|
1694
|
+
const result = Component(props);
|
|
1695
|
+
const duration = profiler.endMeasure(name);
|
|
1696
|
+
if (duration) profiler.recordRender(name, duration);
|
|
1697
|
+
return result
|
|
1698
|
+
}
|
|
1229
1699
|
};
|
|
1230
1700
|
|
|
1231
|
-
const
|
|
1232
|
-
const
|
|
1233
|
-
|
|
1234
|
-
const hash = useHash();
|
|
1701
|
+
const workLoop = (deadline) => {
|
|
1702
|
+
const state = getState();
|
|
1703
|
+
let shouldYield = false;
|
|
1235
1704
|
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
if (el) el.scrollIntoView({ block: 'start', behavior: 'smooth' });
|
|
1241
|
-
}
|
|
1242
|
-
}, [hash]);
|
|
1705
|
+
while (state.nextUnitOfWork && !shouldYield) {
|
|
1706
|
+
state.nextUnitOfWork = performUnitOfWork(state.nextUnitOfWork);
|
|
1707
|
+
shouldYield = deadline.timeRemaining() < 1;
|
|
1708
|
+
}
|
|
1243
1709
|
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
})
|
|
1710
|
+
if (!state.nextUnitOfWork && state.wipRoot) {
|
|
1711
|
+
commitRoot();
|
|
1712
|
+
}
|
|
1713
|
+
|
|
1714
|
+
requestIdleCallback(workLoop);
|
|
1250
1715
|
};
|
|
1251
1716
|
|
|
1252
|
-
|
|
1253
|
-
const { location, navigate } = useRouter();
|
|
1254
|
-
const isActive = exact ? location === to : location.startsWith(to);
|
|
1717
|
+
requestIdleCallback(workLoop);
|
|
1255
1718
|
|
|
1256
|
-
|
|
1257
|
-
|
|
1719
|
+
const performUnitOfWork = (fiber) => {
|
|
1720
|
+
const componentName = fiber.type?.name || fiber.type?.displayName || 'Unknown';
|
|
1258
1721
|
|
|
1259
|
-
|
|
1260
|
-
e.preventDefault();
|
|
1261
|
-
navigate(to);
|
|
1262
|
-
};
|
|
1722
|
+
profiler.startMeasure(componentName);
|
|
1263
1723
|
|
|
1264
|
-
const
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1724
|
+
const isFunctionComponent = fiber.type instanceof Function;
|
|
1725
|
+
if (isFunctionComponent) {
|
|
1726
|
+
updateFunctionComponent(fiber);
|
|
1727
|
+
} else {
|
|
1728
|
+
updateHostComponent(fiber);
|
|
1729
|
+
}
|
|
1268
1730
|
|
|
1269
|
-
const
|
|
1270
|
-
|
|
1271
|
-
className: _omitClassName,
|
|
1272
|
-
...cleanedProps
|
|
1273
|
-
} = props;
|
|
1731
|
+
const duration = profiler.endMeasure(componentName);
|
|
1732
|
+
if (duration) profiler.recordRender(componentName, duration);
|
|
1274
1733
|
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
}
|
|
1283
|
-
|
|
1284
|
-
|
|
1734
|
+
if (fiber.child) {
|
|
1735
|
+
return fiber.child
|
|
1736
|
+
}
|
|
1737
|
+
let nextFiber = fiber;
|
|
1738
|
+
while (nextFiber) {
|
|
1739
|
+
if (nextFiber.sibling) {
|
|
1740
|
+
return nextFiber.sibling
|
|
1741
|
+
}
|
|
1742
|
+
nextFiber = nextFiber.parent;
|
|
1743
|
+
}
|
|
1285
1744
|
};
|
|
1286
1745
|
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
const [state, baseDispatch] = useReducer(reducer, initialState);
|
|
1295
|
-
|
|
1296
|
-
const dispatch = (action, priority = Priority.NORMAL) => {
|
|
1297
|
-
const wrappedAction = {
|
|
1298
|
-
value: action,
|
|
1299
|
-
priority,
|
|
1300
|
-
};
|
|
1301
|
-
|
|
1302
|
-
baseDispatch(wrappedAction);
|
|
1303
|
-
};
|
|
1746
|
+
const scheduleWork = (root, priority = Priority.NORMAL) => {
|
|
1747
|
+
const state = getState();
|
|
1748
|
+
state.nextUnitOfWork = root;
|
|
1749
|
+
state.wipRoot = root;
|
|
1750
|
+
state.deletions = [];
|
|
1751
|
+
state.hookIndex = 0;
|
|
1752
|
+
state.effects = [];
|
|
1304
1753
|
|
|
1305
|
-
|
|
1754
|
+
// Higher priority = faster scheduling
|
|
1755
|
+
if (priority <= Priority.USER_BLOCKING) {
|
|
1756
|
+
requestIdleCallback(workLoop);
|
|
1757
|
+
} else {
|
|
1758
|
+
setTimeout(() => requestIdleCallback(workLoop), 0);
|
|
1759
|
+
}
|
|
1306
1760
|
};
|
|
1307
1761
|
|
|
1308
1762
|
/**
|
|
1309
|
-
*
|
|
1763
|
+
* The `render` function in JavaScript updates the DOM with a new element and schedules work to be done
|
|
1764
|
+
* on the element.
|
|
1765
|
+
* @param element - The `element` parameter in the `render` function is the element that you want to
|
|
1766
|
+
* render in the specified container. It could be a DOM element, a component, or any other valid
|
|
1767
|
+
* element that you want to display on the screen.
|
|
1768
|
+
* @param container - The `container` parameter in the `render` function is the DOM element where the
|
|
1769
|
+
* `element` will be rendered. It is the target container where the element will be appended as a
|
|
1770
|
+
* child.
|
|
1771
|
+
* @returns The `render` function is returning the `state.wipRoot` object.
|
|
1310
1772
|
*/
|
|
1311
|
-
const
|
|
1312
|
-
const
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
setIsPending(false, Priority.IMMEDIATE);
|
|
1320
|
-
}, 0);
|
|
1773
|
+
const render = (element, container) => {
|
|
1774
|
+
const state = getState();
|
|
1775
|
+
state.wipRoot = {
|
|
1776
|
+
dom: container,
|
|
1777
|
+
props: {
|
|
1778
|
+
children: [element],
|
|
1779
|
+
},
|
|
1780
|
+
alternate: state.currentRoot,
|
|
1321
1781
|
};
|
|
1322
1782
|
|
|
1323
|
-
|
|
1783
|
+
state.nextUnitOfWork = state.wipRoot;
|
|
1784
|
+
state.deletions = [];
|
|
1785
|
+
scheduleWork(state.wipRoot);
|
|
1786
|
+
return state.wipRoot
|
|
1324
1787
|
};
|
|
1325
1788
|
|
|
1326
1789
|
/**
|
|
1327
|
-
*
|
|
1790
|
+
* The `init` function initializes a rendering process for a main element within a specified container
|
|
1791
|
+
* root element.
|
|
1792
|
+
* @param MainElement - MainElement is the main component or element that you want to render on the
|
|
1793
|
+
* webpage. It could be a React component, a DOM element, or any other element that you want to display
|
|
1794
|
+
* on the page.
|
|
1795
|
+
* @param [root=__ryunix] - The `root` parameter in the `init` function is a default parameter with the
|
|
1796
|
+
* value `'__ryunix'`. If no value is provided for `root` when calling the `init` function, it will
|
|
1797
|
+
* default to `'__ryunix'`.
|
|
1798
|
+
* @returns The `renderProcess` function is being returned from the `init` function.
|
|
1328
1799
|
*/
|
|
1329
|
-
const
|
|
1330
|
-
const
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
setDeferredValue(value, Priority.LOW);
|
|
1335
|
-
}, 100);
|
|
1336
|
-
|
|
1337
|
-
return () => clearTimeout(timeout)
|
|
1338
|
-
}, [value]);
|
|
1339
|
-
|
|
1340
|
-
return deferredValue
|
|
1800
|
+
const init = (MainElement, root = '__ryunix') => {
|
|
1801
|
+
const state = getState();
|
|
1802
|
+
state.containerRoot = document.getElementById(root);
|
|
1803
|
+
const renderProcess = render(MainElement, state.containerRoot);
|
|
1804
|
+
return renderProcess
|
|
1341
1805
|
};
|
|
1342
1806
|
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
useMetadata: useMetadata,
|
|
1355
|
-
useQuery: useQuery,
|
|
1356
|
-
useReducer: useReducer,
|
|
1357
|
-
useRef: useRef,
|
|
1358
|
-
useRouter: useRouter,
|
|
1359
|
-
useStore: useStore,
|
|
1360
|
-
useStorePriority: useStorePriority,
|
|
1361
|
-
useTransition: useTransition
|
|
1362
|
-
});
|
|
1807
|
+
const safeRender = (component, props, onError) => {
|
|
1808
|
+
try {
|
|
1809
|
+
return component(props)
|
|
1810
|
+
} catch (error) {
|
|
1811
|
+
if (process.env.NODE_ENV !== 'production') {
|
|
1812
|
+
console.error('Component error:', error);
|
|
1813
|
+
}
|
|
1814
|
+
if (onError) onError(error);
|
|
1815
|
+
return null
|
|
1816
|
+
}
|
|
1817
|
+
};
|
|
1363
1818
|
|
|
1364
1819
|
/**
|
|
1365
1820
|
* memo - Memoize component to prevent unnecessary re-renders
|
|
@@ -1431,7 +1886,11 @@ let isBatching = false;
|
|
|
1431
1886
|
let pendingUpdates = [];
|
|
1432
1887
|
|
|
1433
1888
|
/**
|
|
1434
|
-
*
|
|
1889
|
+
* The `batchUpdates` function in JavaScript allows for batching multiple updates and flushing them all
|
|
1890
|
+
* at once.
|
|
1891
|
+
* @param callback - The `callback` parameter in the `batchUpdates` function is a function that will be
|
|
1892
|
+
* executed within a batch update. This function can contain multiple updates that need to be processed
|
|
1893
|
+
* together in a batch to improve performance and avoid unnecessary re-renders.
|
|
1435
1894
|
*/
|
|
1436
1895
|
const batchUpdates = (callback) => {
|
|
1437
1896
|
const wasBatching = isBatching;
|
|
@@ -1449,7 +1908,9 @@ const batchUpdates = (callback) => {
|
|
|
1449
1908
|
};
|
|
1450
1909
|
|
|
1451
1910
|
/**
|
|
1452
|
-
*
|
|
1911
|
+
* The `flushUpdates` function processes and executes pending updates stored in an array.
|
|
1912
|
+
* @returns If the `pendingUpdates` array is empty, the `flushUpdates` function will return nothing
|
|
1913
|
+
* (undefined).
|
|
1453
1914
|
*/
|
|
1454
1915
|
const flushUpdates = () => {
|
|
1455
1916
|
if (pendingUpdates.length === 0) return
|
|
@@ -1463,11 +1924,15 @@ const flushUpdates = () => {
|
|
|
1463
1924
|
|
|
1464
1925
|
var Ryunix = /*#__PURE__*/Object.freeze({
|
|
1465
1926
|
__proto__: null,
|
|
1927
|
+
Children: Children,
|
|
1466
1928
|
Fragment: Fragment,
|
|
1467
1929
|
Hooks: hooks,
|
|
1930
|
+
NavLink: NavLink,
|
|
1468
1931
|
Priority: Priority,
|
|
1932
|
+
RouterProvider: RouterProvider,
|
|
1469
1933
|
Suspense: Suspense,
|
|
1470
1934
|
batchUpdates: batchUpdates,
|
|
1935
|
+
createContext: createContext,
|
|
1471
1936
|
createElement: createElement,
|
|
1472
1937
|
init: init,
|
|
1473
1938
|
lazy: lazy,
|
|
@@ -1475,11 +1940,26 @@ var Ryunix = /*#__PURE__*/Object.freeze({
|
|
|
1475
1940
|
profiler: profiler,
|
|
1476
1941
|
render: render,
|
|
1477
1942
|
safeRender: safeRender,
|
|
1943
|
+
useCallback: useCallback,
|
|
1944
|
+
useDeferredValue: useDeferredValue,
|
|
1945
|
+
useEffect: useEffect,
|
|
1946
|
+
useHash: useHash,
|
|
1947
|
+
useMemo: useMemo,
|
|
1948
|
+
useMetadata: useMetadata,
|
|
1949
|
+
usePersitentStore: usePersitentStore,
|
|
1478
1950
|
useProfiler: useProfiler,
|
|
1951
|
+
useQuery: useQuery,
|
|
1952
|
+
useReducer: useReducer,
|
|
1953
|
+
useRef: useRef,
|
|
1954
|
+
useRouter: useRouter,
|
|
1955
|
+
useStore: useStore,
|
|
1956
|
+
useStorePriority: useStorePriority,
|
|
1957
|
+
useSwitch: useSwitch,
|
|
1958
|
+
useTransition: useTransition,
|
|
1479
1959
|
withProfiler: withProfiler
|
|
1480
1960
|
});
|
|
1481
1961
|
|
|
1482
1962
|
window.Ryunix = Ryunix;
|
|
1483
1963
|
|
|
1484
|
-
export { Fragment, hooks as Hooks, Image, Priority, Suspense, batchUpdates, createElement, Ryunix as default, init, lazy, memo, profiler, render, safeRender, useProfiler, withProfiler };
|
|
1964
|
+
export { Children, Fragment, hooks as Hooks, Image, MDXContent, MDXProvider, NavLink, Priority, RouterProvider, Suspense, batchUpdates, createContext, createElement, Ryunix as default, defaultComponents, getMDXComponents, init, lazy, memo, profiler, render, safeRender, useCallback, useDeferredValue, useEffect, useHash, useMDXComponents, useMemo, useMetadata, usePersitentStore, useProfiler, useQuery, useReducer, useRef, useRouter, useStore, useStorePriority, useSwitch, useTransition, withProfiler };
|
|
1485
1965
|
//# sourceMappingURL=Ryunix.esm.js.map
|