@unsetsoft/ryunixjs 1.1.6 → 1.1.7
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/README.md +2 -4
- package/dist/Ryunix.js +706 -604
- package/dist/Ryunix.min.js +1 -1
- package/package.json +16 -5
package/dist/Ryunix.js
CHANGED
|
@@ -5,13 +5,14 @@
|
|
|
5
5
|
})(this, (function (exports, lodash) { 'use strict';
|
|
6
6
|
|
|
7
7
|
let vars = {
|
|
8
|
-
containerRoot:
|
|
9
|
-
nextUnitOfWork:
|
|
10
|
-
currentRoot:
|
|
11
|
-
wipRoot:
|
|
12
|
-
deletions:
|
|
13
|
-
wipFiber:
|
|
14
|
-
hookIndex:
|
|
8
|
+
containerRoot: null,
|
|
9
|
+
nextUnitOfWork: null,
|
|
10
|
+
currentRoot: null,
|
|
11
|
+
wipRoot: null,
|
|
12
|
+
deletions: null,
|
|
13
|
+
wipFiber: null,
|
|
14
|
+
hookIndex: null,
|
|
15
|
+
effects: null,
|
|
15
16
|
};
|
|
16
17
|
|
|
17
18
|
const reg = /[A-Z]/g;
|
|
@@ -48,6 +49,10 @@
|
|
|
48
49
|
DELETION: Symbol('ryunix.reconciler.status.deletion').toString(),
|
|
49
50
|
});
|
|
50
51
|
|
|
52
|
+
const generateHash = (prefix) => {
|
|
53
|
+
return `${prefix}-${Math.random().toString(36).substring(2, 9)}`
|
|
54
|
+
};
|
|
55
|
+
|
|
51
56
|
const Fragment = (props) => {
|
|
52
57
|
return props.children
|
|
53
58
|
};
|
|
@@ -85,8 +90,8 @@
|
|
|
85
90
|
children = childArray(children, []);
|
|
86
91
|
const key =
|
|
87
92
|
props && props.key
|
|
88
|
-
? props.key
|
|
89
|
-
:
|
|
93
|
+
? generateHash(props.key)
|
|
94
|
+
: generateHash(RYUNIX_TYPES.Ryunix_ELEMENT.toString());
|
|
90
95
|
|
|
91
96
|
return {
|
|
92
97
|
type,
|
|
@@ -117,726 +122,826 @@
|
|
|
117
122
|
}
|
|
118
123
|
};
|
|
119
124
|
|
|
125
|
+
const isEvent = (key) => key.startsWith('on');
|
|
126
|
+
const isProperty = (key) => key !== STRINGS.children && !isEvent(key);
|
|
127
|
+
const isNew = (prev, next) => (key) => prev[key] !== next[key];
|
|
128
|
+
const isGone = (next) => (key) => !(key in next);
|
|
129
|
+
|
|
120
130
|
/**
|
|
121
|
-
*
|
|
122
|
-
* @
|
|
123
|
-
*
|
|
124
|
-
*
|
|
125
|
-
*
|
|
126
|
-
* It also clears any scheduled deletions and establishes the next unit of work for incremental rendering.
|
|
131
|
+
* The function cancels all effect hooks in a given fiber.
|
|
132
|
+
* @param fiber - The "fiber" parameter is likely referring to a data structure used in React.js to
|
|
133
|
+
* represent a component and its state. It contains information about the component's props, state, and
|
|
134
|
+
* children, as well as metadata used by React to manage updates and rendering. The function
|
|
135
|
+
* "cancelEffects" is likely intended
|
|
127
136
|
*/
|
|
128
|
-
const
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
vars.deletions = [];
|
|
138
|
-
vars.nextUnitOfWork = vars.wipRoot;
|
|
137
|
+
const cancelEffects = (fiber) => {
|
|
138
|
+
if (fiber.hooks) {
|
|
139
|
+
fiber.hooks
|
|
140
|
+
.filter((hook) => hook.tag === RYUNIX_TYPES.RYUNIX_EFFECT && hook.cancel)
|
|
141
|
+
.forEach((effectHook) => {
|
|
142
|
+
effectHook.cancel();
|
|
143
|
+
});
|
|
144
|
+
}
|
|
139
145
|
};
|
|
140
146
|
|
|
141
147
|
/**
|
|
142
|
-
*
|
|
143
|
-
* @
|
|
144
|
-
*
|
|
145
|
-
*
|
|
146
|
-
*
|
|
147
|
-
* Ryunix.init(App, "__ryunix"); // Initializes and renders the App component into the <div id="__ryunix"></div> element.
|
|
148
|
-
* @description This function retrieves the container element by its ID and invokes the `render` function to render the main component into it.
|
|
148
|
+
* The function runs all effect hooks in a given fiber.
|
|
149
|
+
* @param fiber - The "fiber" parameter is likely referring to a data structure used in the
|
|
150
|
+
* implementation of a fiber-based reconciliation algorithm, such as the one used in React. A fiber
|
|
151
|
+
* represents a unit of work that needs to be performed by the reconciliation algorithm, and it
|
|
152
|
+
* contains information about a component and its children, as
|
|
149
153
|
*/
|
|
150
|
-
const
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
+
const runEffects = (fiber) => {
|
|
155
|
+
if (fiber.hooks) {
|
|
156
|
+
fiber.hooks
|
|
157
|
+
.filter((hook) => hook.tag === RYUNIX_TYPES.RYUNIX_EFFECT && hook.effect)
|
|
158
|
+
.forEach((effectHook) => {
|
|
159
|
+
effectHook.cancel = effectHook.effect();
|
|
160
|
+
});
|
|
161
|
+
}
|
|
154
162
|
};
|
|
155
163
|
|
|
156
164
|
/**
|
|
157
|
-
*
|
|
158
|
-
* @param
|
|
159
|
-
*
|
|
160
|
-
* `
|
|
165
|
+
* The function creates a new DOM element based on the given fiber object and updates its properties.
|
|
166
|
+
* @param fiber - The fiber parameter is an object that represents a node in the fiber tree. It
|
|
167
|
+
* contains information about the element type, props, and children of the node.
|
|
168
|
+
* @returns The `createDom` function returns a newly created DOM element based on the `fiber` object
|
|
169
|
+
* passed as an argument. If the `fiber` object represents a text element, a text node is created using
|
|
170
|
+
* `document.createTextNode("")`. Otherwise, a new element is created using
|
|
171
|
+
* `document.createElement(fiber.type)`. The function then calls the `updateDom` function to update the
|
|
172
|
+
* properties of the newly created
|
|
161
173
|
*/
|
|
162
|
-
const
|
|
163
|
-
const
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
const hook = {
|
|
168
|
-
state: oldHook ? oldHook.state : initial,
|
|
169
|
-
queue: oldHook ? [...oldHook.queue] : [],
|
|
170
|
-
};
|
|
171
|
-
|
|
172
|
-
hook.queue.forEach((action) => {
|
|
173
|
-
hook.state =
|
|
174
|
-
typeof action === STRINGS.function ? action(hook.state) : action;
|
|
175
|
-
});
|
|
176
|
-
|
|
177
|
-
hook.queue = [];
|
|
178
|
-
|
|
179
|
-
const setState = (action) => {
|
|
180
|
-
hook.queue.push(action);
|
|
181
|
-
|
|
182
|
-
vars.wipRoot = {
|
|
183
|
-
dom: vars.currentRoot.dom,
|
|
184
|
-
props: {
|
|
185
|
-
...vars.currentRoot.props,
|
|
186
|
-
},
|
|
187
|
-
alternate: vars.currentRoot,
|
|
188
|
-
};
|
|
189
|
-
vars.nextUnitOfWork = vars.wipRoot;
|
|
190
|
-
vars.deletions = [];
|
|
191
|
-
};
|
|
174
|
+
const createDom = (fiber) => {
|
|
175
|
+
const dom =
|
|
176
|
+
fiber.type == RYUNIX_TYPES.TEXT_ELEMENT
|
|
177
|
+
? document.createTextNode('')
|
|
178
|
+
: document.createElement(fiber.type);
|
|
192
179
|
|
|
193
|
-
|
|
194
|
-
vars.wipFiber.hooks.push(hook);
|
|
195
|
-
vars.hookIndex++;
|
|
196
|
-
}
|
|
180
|
+
updateDom(dom, {}, fiber.props);
|
|
197
181
|
|
|
198
|
-
return
|
|
182
|
+
return dom
|
|
199
183
|
};
|
|
200
184
|
|
|
201
185
|
/**
|
|
202
|
-
*
|
|
203
|
-
*
|
|
204
|
-
*
|
|
205
|
-
*
|
|
206
|
-
* @param
|
|
207
|
-
* between renders, the effect will be re-run. If the array is empty, the effect will only run once on
|
|
208
|
-
* mount and never again.
|
|
186
|
+
* The function updates the DOM by removing old event listeners and properties, and adding new ones
|
|
187
|
+
* based on the previous and next props.
|
|
188
|
+
* @param dom - The DOM element that needs to be updated with new props.
|
|
189
|
+
* @param prevProps - An object representing the previous props (properties) of a DOM element.
|
|
190
|
+
* @param nextProps - An object containing the new props that need to be updated in the DOM.
|
|
209
191
|
*/
|
|
192
|
+
const updateDom = (dom, prevProps, nextProps) => {
|
|
193
|
+
Object.keys(prevProps)
|
|
194
|
+
.filter(isEvent)
|
|
195
|
+
.filter((key) => isGone(nextProps)(key) || isNew(prevProps, nextProps)(key))
|
|
196
|
+
.forEach((name) => {
|
|
197
|
+
const eventType = name.toLowerCase().substring(2);
|
|
198
|
+
dom.removeEventListener(eventType, prevProps[name]);
|
|
199
|
+
});
|
|
210
200
|
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
201
|
+
Object.keys(prevProps)
|
|
202
|
+
.filter(isProperty)
|
|
203
|
+
.filter(isGone(nextProps))
|
|
204
|
+
.forEach((name) => {
|
|
205
|
+
dom[name] = '';
|
|
206
|
+
});
|
|
216
207
|
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
208
|
+
Object.keys(nextProps)
|
|
209
|
+
.filter(isProperty)
|
|
210
|
+
.filter(isNew(prevProps, nextProps))
|
|
211
|
+
.forEach((name) => {
|
|
212
|
+
if (name === STRINGS.style) {
|
|
213
|
+
DomStyle(dom, nextProps['ryunix-style']);
|
|
214
|
+
} else if (name === OLD_STRINGS.style) {
|
|
215
|
+
DomStyle(dom, nextProps.style);
|
|
216
|
+
} else if (name === STRINGS.className) {
|
|
217
|
+
if (nextProps['ryunix-class'] === '') {
|
|
218
|
+
throw new Error('data-class cannot be empty.')
|
|
219
|
+
}
|
|
221
220
|
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
}
|
|
221
|
+
prevProps['ryunix-class'] &&
|
|
222
|
+
dom.classList.remove(...prevProps['ryunix-class'].split(/\s+/));
|
|
223
|
+
dom.classList.add(...nextProps['ryunix-class'].split(/\s+/));
|
|
224
|
+
} else if (name === OLD_STRINGS.className) {
|
|
225
|
+
if (nextProps.className === '') {
|
|
226
|
+
throw new Error('className cannot be empty.')
|
|
227
|
+
}
|
|
230
228
|
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
229
|
+
prevProps.className &&
|
|
230
|
+
dom.classList.remove(...prevProps.className.split(/\s+/));
|
|
231
|
+
dom.classList.add(...nextProps.className.split(/\s+/));
|
|
232
|
+
} else {
|
|
233
|
+
dom[name] = nextProps[name];
|
|
234
|
+
}
|
|
235
|
+
});
|
|
236
236
|
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
237
|
+
Object.keys(nextProps)
|
|
238
|
+
.filter(isEvent)
|
|
239
|
+
.filter(isNew(prevProps, nextProps))
|
|
240
|
+
.forEach((name) => {
|
|
241
|
+
const eventType = name.toLowerCase().substring(2);
|
|
242
|
+
dom.addEventListener(eventType, nextProps[name]);
|
|
243
|
+
});
|
|
244
|
+
};
|
|
242
245
|
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
246
|
+
const DomStyle = (dom, style) => {
|
|
247
|
+
dom.style = Object.keys(style).reduce((acc, styleName) => {
|
|
248
|
+
const key = styleName.replace(reg, function (v) {
|
|
249
|
+
return '-' + v.toLowerCase()
|
|
250
|
+
});
|
|
251
|
+
acc += `${key}: ${style[styleName]};`;
|
|
252
|
+
return acc
|
|
253
|
+
}, '');
|
|
254
|
+
};
|
|
247
255
|
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
256
|
+
/**
|
|
257
|
+
* The function commits changes made to the virtual DOM to the actual DOM.
|
|
258
|
+
*/
|
|
259
|
+
const commitRoot = () => {
|
|
260
|
+
vars.deletions.forEach(commitWork);
|
|
261
|
+
if (vars.wipRoot && vars.wipRoot.child) {
|
|
262
|
+
commitWork(vars.wipRoot.child);
|
|
263
|
+
vars.currentRoot = vars.wipRoot;
|
|
251
264
|
}
|
|
252
|
-
|
|
253
|
-
|
|
265
|
+
vars.effects.forEach((effect) => {
|
|
266
|
+
try {
|
|
267
|
+
effect();
|
|
268
|
+
} catch (err) {
|
|
269
|
+
console.error('Error in effect:', err);
|
|
270
|
+
}
|
|
271
|
+
});
|
|
272
|
+
vars.effects = [];
|
|
273
|
+
vars.wipRoot = null;
|
|
254
274
|
};
|
|
255
275
|
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
deps,
|
|
266
|
-
};
|
|
276
|
+
/**
|
|
277
|
+
* The function commits changes made to the DOM based on the effect tag of the fiber.
|
|
278
|
+
* @param fiber - A fiber is a unit of work in Ryunix's reconciliation process. It represents a
|
|
279
|
+
* component and its state at a particular point in time. The `commitWork` function takes a fiber as a
|
|
280
|
+
* parameter to commit the changes made during the reconciliation process to the actual DOM.
|
|
281
|
+
* @returns The function does not return anything, it performs side effects by manipulating the DOM.
|
|
282
|
+
*/
|
|
283
|
+
const commitWork = (fiber) => {
|
|
284
|
+
if (!fiber) return
|
|
267
285
|
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
} else {
|
|
272
|
-
hook.value = comp();
|
|
273
|
-
}
|
|
274
|
-
} else {
|
|
275
|
-
hook.value = comp();
|
|
286
|
+
let domParentFiber = fiber.parent;
|
|
287
|
+
while (!domParentFiber.dom) {
|
|
288
|
+
domParentFiber = domParentFiber.parent;
|
|
276
289
|
}
|
|
290
|
+
const domParent = domParentFiber.dom;
|
|
277
291
|
|
|
278
|
-
if (
|
|
279
|
-
|
|
280
|
-
|
|
292
|
+
if (fiber.effectTag === EFFECT_TAGS.PLACEMENT && fiber.dom != null) {
|
|
293
|
+
domParent.appendChild(fiber.dom);
|
|
294
|
+
runEffects(fiber);
|
|
295
|
+
} else if (fiber.effectTag === EFFECT_TAGS.UPDATE && fiber.dom != null) {
|
|
296
|
+
cancelEffects(fiber);
|
|
297
|
+
updateDom(fiber.dom, fiber.alternate.props, fiber.props);
|
|
298
|
+
runEffects(fiber);
|
|
299
|
+
} else if (fiber.effectTag === EFFECT_TAGS.DELETION) {
|
|
300
|
+
commitDeletion(fiber, domParent);
|
|
301
|
+
cancelEffects(fiber);
|
|
302
|
+
return
|
|
281
303
|
}
|
|
282
304
|
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
const useCallback = (callback, deps) => {
|
|
287
|
-
return useMemo(() => callback, deps)
|
|
305
|
+
// Recorre los "fibers" hijos y hermanos
|
|
306
|
+
commitWork(fiber.child);
|
|
307
|
+
commitWork(fiber.sibling);
|
|
288
308
|
};
|
|
289
309
|
|
|
290
310
|
/**
|
|
291
|
-
* The
|
|
292
|
-
*
|
|
293
|
-
*
|
|
311
|
+
* The function removes a fiber's corresponding DOM node from its parent node or recursively removes
|
|
312
|
+
* its child's DOM node until it finds a node to remove.
|
|
313
|
+
* @param fiber - a fiber node in a fiber tree, which represents a component or an element in the Ryunix
|
|
314
|
+
* application.
|
|
315
|
+
* @param domParent - The parent DOM element from which the fiber's DOM element needs to be removed.
|
|
294
316
|
*/
|
|
295
|
-
const
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
317
|
+
const commitDeletion = (fiber, domParent) => {
|
|
318
|
+
if (fiber.dom) {
|
|
319
|
+
domParent.removeChild(fiber.dom);
|
|
320
|
+
} else {
|
|
321
|
+
commitDeletion(fiber.child, domParent);
|
|
300
322
|
}
|
|
301
|
-
return query
|
|
302
323
|
};
|
|
303
324
|
|
|
304
325
|
/**
|
|
305
|
-
*
|
|
306
|
-
*
|
|
307
|
-
*
|
|
308
|
-
* -
|
|
309
|
-
*
|
|
310
|
-
*
|
|
311
|
-
*
|
|
312
|
-
* @param {Array} routes - An array of route objects, each containing:
|
|
313
|
-
* - `path` (string): The URL path to match (supports dynamic segments like "/user/:id").
|
|
314
|
-
* - `component` (function): The component to render when the route matches.
|
|
315
|
-
* - `subRoutes` (optional array): An optional array of nested route objects, defining sub-routes for this route.
|
|
316
|
-
* - `NotFound` (optional function): Component to render for unmatched routes (default 404 behavior).
|
|
317
|
-
*
|
|
318
|
-
* @returns {Object} - An object with:
|
|
319
|
-
* - `Children` (function): Returns the component that matches the current route, passing route parameters and query parameters as props.
|
|
320
|
-
* - `NavLink` (component): A link component to navigate within the application without refreshing the page.
|
|
321
|
-
* - `navigate` (function): Allows programmatically navigating to a specific path.
|
|
322
|
-
*
|
|
323
|
-
* @example
|
|
324
|
-
* // Define nested routes
|
|
325
|
-
* const routes = [
|
|
326
|
-
* {
|
|
327
|
-
* path: "/",
|
|
328
|
-
* component: HomePage,
|
|
329
|
-
* subRoutes: [
|
|
330
|
-
* {
|
|
331
|
-
* path: "/settings",
|
|
332
|
-
* component: SettingsPage,
|
|
333
|
-
* },
|
|
334
|
-
* ],
|
|
335
|
-
* },
|
|
336
|
-
* {
|
|
337
|
-
* path: "/user/:id",
|
|
338
|
-
* component: UserProfile,
|
|
339
|
-
* },
|
|
340
|
-
* {
|
|
341
|
-
* path: "*",
|
|
342
|
-
* NotFound: NotFoundPage,
|
|
343
|
-
* },
|
|
344
|
-
* ];
|
|
345
|
-
*
|
|
346
|
-
* // Use the routing function
|
|
347
|
-
* const { Children, NavLink } = useRouter(routes);
|
|
348
|
-
*
|
|
349
|
-
* // Render the matched component
|
|
350
|
-
* const App = () => (
|
|
351
|
-
* <>
|
|
352
|
-
* <NavLink to="/">Home</NavLink>
|
|
353
|
-
* <NavLink to="/settings">Settings</NavLink>
|
|
354
|
-
* <NavLink to="/user/123">User Profile</NavLink>
|
|
355
|
-
* <Children />
|
|
356
|
-
* </>
|
|
357
|
-
* );
|
|
326
|
+
* This function reconciles the children of a fiber node with a new set of elements, creating new
|
|
327
|
+
* fibers for new elements, updating existing fibers for elements with the same type, and marking old
|
|
328
|
+
* fibers for deletion if they are not present in the new set of elements.
|
|
329
|
+
* @param wipFiber - A work-in-progress fiber object representing a component or element in the virtual
|
|
330
|
+
* DOM tree.
|
|
331
|
+
* @param elements - an array of elements representing the new children to be rendered in the current
|
|
332
|
+
* fiber's subtree
|
|
358
333
|
*/
|
|
359
|
-
const
|
|
360
|
-
|
|
334
|
+
const shouldComponentUpdate = (oldProps, newProps) => {
|
|
335
|
+
// Comparar las propiedades antiguas y nuevas
|
|
336
|
+
return (
|
|
337
|
+
!oldProps ||
|
|
338
|
+
!newProps ||
|
|
339
|
+
Object.keys(oldProps).length !== Object.keys(newProps).length ||
|
|
340
|
+
Object.keys(newProps).some((key) => oldProps[key] !== newProps[key])
|
|
341
|
+
)
|
|
342
|
+
};
|
|
361
343
|
|
|
362
|
-
|
|
363
|
-
|
|
344
|
+
const recycleFiber = (oldFiber, newProps) => {
|
|
345
|
+
return {
|
|
346
|
+
...oldFiber,
|
|
347
|
+
props: newProps,
|
|
348
|
+
alternate: oldFiber,
|
|
349
|
+
effectTag: EFFECT_TAGS.UPDATE,
|
|
350
|
+
}
|
|
351
|
+
};
|
|
364
352
|
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
353
|
+
const reconcileChildren = (wipFiber, elements) => {
|
|
354
|
+
let index = 0;
|
|
355
|
+
let oldFiber = wipFiber.alternate && wipFiber.alternate.child;
|
|
356
|
+
let prevSibling = null;
|
|
369
357
|
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
358
|
+
const oldFibersMap = new Map();
|
|
359
|
+
while (oldFiber) {
|
|
360
|
+
const oldKey = oldFiber.props.key || oldFiber.type;
|
|
361
|
+
oldFibersMap.set(oldKey, oldFiber);
|
|
362
|
+
oldFiber = oldFiber.sibling;
|
|
363
|
+
}
|
|
375
364
|
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
365
|
+
while (index < elements.length) {
|
|
366
|
+
const element = elements[index];
|
|
367
|
+
const key = element.props.key || element.type;
|
|
368
|
+
const oldFiber = oldFibersMap.get(key);
|
|
379
369
|
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
console.info(
|
|
383
|
-
"if you are using { NotFound: NotFound } please add { path: '*', NotFound: NotFound }",
|
|
384
|
-
);
|
|
385
|
-
continue
|
|
386
|
-
}
|
|
370
|
+
let newFiber;
|
|
371
|
+
const sameType = oldFiber && element && element.type === oldFiber.type;
|
|
387
372
|
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
})}$`,
|
|
394
|
-
);
|
|
373
|
+
if (sameType && !shouldComponentUpdate(oldFiber.props, element.props)) {
|
|
374
|
+
// Reutilizar fibra existente si no hay cambios
|
|
375
|
+
newFiber = recycleFiber(oldFiber, element.props);
|
|
376
|
+
oldFibersMap.delete(key);
|
|
377
|
+
}
|
|
395
378
|
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
379
|
+
if (element && !sameType) {
|
|
380
|
+
// Crear nueva fibra
|
|
381
|
+
newFiber = {
|
|
382
|
+
type: element.type,
|
|
383
|
+
props: element.props,
|
|
384
|
+
dom: null,
|
|
385
|
+
parent: wipFiber,
|
|
386
|
+
alternate: null,
|
|
387
|
+
effectTag: EFFECT_TAGS.PLACEMENT,
|
|
388
|
+
};
|
|
389
|
+
}
|
|
402
390
|
|
|
403
|
-
|
|
404
|
-
|
|
391
|
+
if (oldFiber && !sameType) {
|
|
392
|
+
oldFiber.effectTag = EFFECT_TAGS.DELETION;
|
|
393
|
+
wipFiber.effects = wipFiber.effects || [];
|
|
394
|
+
wipFiber.effects.push(oldFiber);
|
|
405
395
|
}
|
|
406
396
|
|
|
407
|
-
|
|
408
|
-
|
|
397
|
+
if (index === 0) {
|
|
398
|
+
wipFiber.child = newFiber;
|
|
399
|
+
} else if (prevSibling) {
|
|
400
|
+
prevSibling.sibling = newFiber;
|
|
401
|
+
}
|
|
409
402
|
|
|
410
|
-
|
|
411
|
-
window.history.pushState({}, '', path);
|
|
412
|
-
updateRoute(path);
|
|
413
|
-
};
|
|
403
|
+
prevSibling = newFiber;
|
|
414
404
|
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
setLocation(cleanedPath);
|
|
418
|
-
};
|
|
405
|
+
index++;
|
|
406
|
+
}
|
|
419
407
|
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
408
|
+
oldFibersMap.forEach((oldFiber) => {
|
|
409
|
+
oldFiber.effectTag = EFFECT_TAGS.DELETION;
|
|
410
|
+
vars.deletions.push(oldFiber);
|
|
411
|
+
});
|
|
412
|
+
};
|
|
423
413
|
|
|
424
|
-
|
|
425
|
-
|
|
414
|
+
/**
|
|
415
|
+
* This function updates a function component by setting up a work-in-progress fiber, resetting the
|
|
416
|
+
* hook index, creating an empty hooks array, rendering the component, and reconciling its children.
|
|
417
|
+
* @param fiber - The fiber parameter is an object that represents a node in the fiber tree. It
|
|
418
|
+
* contains information about the component, its props, state, and children. In this function, it is
|
|
419
|
+
* used to update the state of the component and its children.
|
|
420
|
+
*/
|
|
421
|
+
const updateFunctionComponent = (fiber) => {
|
|
422
|
+
vars.wipFiber = fiber;
|
|
423
|
+
vars.hookIndex = 0;
|
|
424
|
+
vars.wipFiber.hooks = [];
|
|
426
425
|
|
|
427
|
-
const
|
|
426
|
+
const children = fiber.type(fiber.props);
|
|
427
|
+
let childArr = Array.isArray(children) ? children : [children];
|
|
428
428
|
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
const { route } = currentRouteData;
|
|
429
|
+
reconcileChildren(fiber, childArr);
|
|
430
|
+
};
|
|
432
431
|
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
432
|
+
/**
|
|
433
|
+
* This function updates a host component's DOM element and reconciles its children.
|
|
434
|
+
* @param fiber - A fiber is a unit of work in Ryunix that represents a component and its state. It
|
|
435
|
+
* contains information about the component's type, props, and children, as well as pointers to other
|
|
436
|
+
* fibers in the tree.
|
|
437
|
+
*/
|
|
438
|
+
const updateHostComponent = (fiber) => {
|
|
439
|
+
if (!fiber.dom) {
|
|
440
|
+
fiber.dom = createDom(fiber);
|
|
441
|
+
}
|
|
442
|
+
reconcileChildren(fiber, fiber.props.children);
|
|
443
|
+
};
|
|
444
|
+
|
|
445
|
+
/* Internal components*/
|
|
446
|
+
|
|
447
|
+
/**
|
|
448
|
+
* The function `optimizationImageApi` optimizes image URLs by adding query parameters for width,
|
|
449
|
+
* height, quality, and extension, and handles local and remote image sources.
|
|
450
|
+
* @returns The function `optimizationImageApi` returns either the original `src` if it is a local
|
|
451
|
+
* image and the page is being run on localhost, or it returns a modified image URL with optimization
|
|
452
|
+
* parameters added if the `src` is not local.
|
|
453
|
+
*/
|
|
454
|
+
const optimizationImageApi = ({ src, props }) => {
|
|
455
|
+
const query = new URLSearchParams();
|
|
456
|
+
const apiEndpoint = 'https://image.unsetsoft.com';
|
|
457
|
+
|
|
458
|
+
const isLocal = !src.startsWith('http') || !src.startsWith('https');
|
|
459
|
+
|
|
460
|
+
if (props.width) query.set('width', props.width);
|
|
461
|
+
if (props.height) query.set('width', props.height);
|
|
462
|
+
if (props.quality) query.set('quality', props.quality);
|
|
463
|
+
|
|
464
|
+
const extension = props.extension ? `@${props.extension}` : '';
|
|
465
|
+
|
|
466
|
+
const localhost =
|
|
467
|
+
window.location.origin === 'http://localhost:3000' ||
|
|
468
|
+
window.location.origin === 'http://localhost:5173' ||
|
|
469
|
+
window.location.origin === 'http://localhost:4173';
|
|
470
|
+
|
|
471
|
+
if (isLocal) {
|
|
472
|
+
if (localhost) {
|
|
473
|
+
console.warn(
|
|
474
|
+
'Image optimizations only work with full links and must not contain localhost.',
|
|
441
475
|
);
|
|
442
|
-
return
|
|
476
|
+
return src
|
|
443
477
|
}
|
|
444
478
|
|
|
445
|
-
return
|
|
446
|
-
|
|
447
|
-
query,
|
|
448
|
-
})
|
|
449
|
-
};
|
|
479
|
+
return `${window.location.origin}/${src}`
|
|
480
|
+
}
|
|
450
481
|
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
482
|
+
return `${apiEndpoint}/image/${src}${extension}?${query.toString()}`
|
|
483
|
+
};
|
|
484
|
+
|
|
485
|
+
/**
|
|
486
|
+
* The `Image` function in JavaScript optimizes image loading based on a specified optimization flag.
|
|
487
|
+
* @returns An `<img>` element is being returned with the specified `src` and other props passed to the
|
|
488
|
+
* `Image` component. The `src` is either the original `src` value or the result of calling
|
|
489
|
+
* `optimizationImageApi` function with `src` and `props` if `optimization` is set to 'true'.
|
|
490
|
+
*/
|
|
491
|
+
const Image = ({ src, ...props }) => {
|
|
492
|
+
const optimization = props.optimization === 'true' ? true : false;
|
|
493
|
+
|
|
494
|
+
const url = optimization
|
|
495
|
+
? optimizationImageApi({
|
|
496
|
+
src,
|
|
497
|
+
props,
|
|
498
|
+
})
|
|
499
|
+
: src;
|
|
500
|
+
|
|
501
|
+
const ImageProps = {
|
|
502
|
+
src: url,
|
|
503
|
+
props,
|
|
461
504
|
};
|
|
462
505
|
|
|
463
|
-
return
|
|
506
|
+
return createElement('img', ImageProps, null)
|
|
464
507
|
};
|
|
465
508
|
|
|
466
|
-
const isEvent = (key) => key.startsWith('on');
|
|
467
|
-
const isProperty = (key) => key !== STRINGS.children && !isEvent(key);
|
|
468
|
-
const isNew = (prev, next) => (key) => prev[key] !== next[key];
|
|
469
|
-
const isGone = (next) => (key) => !(key in next);
|
|
470
|
-
|
|
471
509
|
/**
|
|
472
|
-
*
|
|
473
|
-
*
|
|
474
|
-
*
|
|
475
|
-
*
|
|
476
|
-
*
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
});
|
|
510
|
+
* This function uses requestIdleCallback to perform work on a fiber tree until it is complete or the
|
|
511
|
+
* browser needs to yield to other tasks.
|
|
512
|
+
* @param deadline - The `deadline` parameter is an object that represents the amount of time the
|
|
513
|
+
* browser has to perform work before it needs to handle other tasks. It has a `timeRemaining()` method
|
|
514
|
+
* that returns the amount of time remaining before the deadline is reached. The `shouldYield` variable
|
|
515
|
+
* is used to determine
|
|
516
|
+
*/
|
|
517
|
+
const workLoop = (deadline) => {
|
|
518
|
+
let shouldYield = false;
|
|
519
|
+
while (vars.nextUnitOfWork && !shouldYield) {
|
|
520
|
+
vars.nextUnitOfWork = performUnitOfWork(vars.nextUnitOfWork);
|
|
521
|
+
shouldYield = deadline.timeRemaining() < 1;
|
|
485
522
|
}
|
|
523
|
+
|
|
524
|
+
if (!vars.nextUnitOfWork && vars.wipRoot) {
|
|
525
|
+
commitRoot();
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
requestIdleCallback(workLoop);
|
|
486
529
|
};
|
|
487
530
|
|
|
531
|
+
//requestIdleCallback(workLoop)
|
|
532
|
+
|
|
488
533
|
/**
|
|
489
|
-
* The function
|
|
490
|
-
*
|
|
491
|
-
*
|
|
492
|
-
*
|
|
493
|
-
*
|
|
534
|
+
* The function performs a unit of work by updating either a function component or a host component and
|
|
535
|
+
* returns the next fiber to be processed.
|
|
536
|
+
* @param fiber - A fiber is a unit of work in Ryunix that represents a component and its state. It
|
|
537
|
+
* contains information about the component's type, props, and children, as well as pointers to its
|
|
538
|
+
* parent, child, and sibling fibers. The `performUnitOfWork` function takes a fiber as a parameter and
|
|
539
|
+
* performs work
|
|
540
|
+
* @returns The function `performUnitOfWork` returns the next fiber to be processed. If the current
|
|
541
|
+
* fiber has a child, it returns the child. Otherwise, it looks for the next sibling of the current
|
|
542
|
+
* fiber. If there are no more siblings, it goes up the tree to the parent and looks for the next
|
|
543
|
+
* sibling of the parent. The function returns `undefined` if there are no more fibers to process.
|
|
494
544
|
*/
|
|
495
|
-
const
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
545
|
+
const performUnitOfWork = (fiber) => {
|
|
546
|
+
const isFunctionComponent = fiber.type instanceof Function;
|
|
547
|
+
if (isFunctionComponent) {
|
|
548
|
+
updateFunctionComponent(fiber);
|
|
549
|
+
} else {
|
|
550
|
+
updateHostComponent(fiber);
|
|
551
|
+
}
|
|
552
|
+
if (fiber.child) {
|
|
553
|
+
return fiber.child
|
|
554
|
+
}
|
|
555
|
+
let nextFiber = fiber;
|
|
556
|
+
while (nextFiber) {
|
|
557
|
+
if (nextFiber.sibling) {
|
|
558
|
+
return nextFiber.sibling
|
|
559
|
+
}
|
|
560
|
+
nextFiber = nextFiber.parent;
|
|
502
561
|
}
|
|
562
|
+
return undefined
|
|
563
|
+
};
|
|
564
|
+
|
|
565
|
+
const scheduleWork = (root) => {
|
|
566
|
+
vars.nextUnitOfWork = root;
|
|
567
|
+
vars.wipRoot = root;
|
|
568
|
+
vars.deletions = [];
|
|
569
|
+
|
|
570
|
+
vars.hookIndex = 0;
|
|
571
|
+
vars.effects = [];
|
|
572
|
+
requestIdleCallback(workLoop);
|
|
503
573
|
};
|
|
504
574
|
|
|
505
575
|
/**
|
|
506
|
-
*
|
|
507
|
-
* @
|
|
508
|
-
*
|
|
509
|
-
* @
|
|
510
|
-
*
|
|
511
|
-
*
|
|
512
|
-
* `document.createElement(fiber.type)`. The function then calls the `updateDom` function to update the
|
|
513
|
-
* properties of the newly created
|
|
576
|
+
* Renders an element into a container using a work-in-progress (WIP) root.
|
|
577
|
+
* @function render
|
|
578
|
+
* @param {Object|HTMLElement} element - The element to be rendered in the container. It can be a Ryunix component (custom element) or a standard DOM element.
|
|
579
|
+
* @param {HTMLElement} container - The container where the element will be rendered. This parameter is optional if `createRoot()` is used beforehand to set up the container.
|
|
580
|
+
* @description The function assigns the `container` to a work-in-progress root and sets up properties for reconciliation, including children and the reference to the current root.
|
|
581
|
+
* It also clears any scheduled deletions and establishes the next unit of work for incremental rendering.
|
|
514
582
|
*/
|
|
515
|
-
const
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
:
|
|
583
|
+
const render = (element, container) => {
|
|
584
|
+
vars.wipRoot = {
|
|
585
|
+
dom: container,
|
|
586
|
+
props: {
|
|
587
|
+
children: [element],
|
|
588
|
+
},
|
|
589
|
+
alternate: vars.currentRoot,
|
|
590
|
+
};
|
|
520
591
|
|
|
521
|
-
|
|
592
|
+
vars.nextUnitOfWork = vars.wipRoot;
|
|
593
|
+
vars.deletions = [];
|
|
594
|
+
scheduleWork(vars.wipRoot);
|
|
595
|
+
return vars.wipRoot
|
|
596
|
+
};
|
|
522
597
|
|
|
523
|
-
|
|
598
|
+
/**
|
|
599
|
+
* Initializes the application by creating a reference to a DOM element with the specified ID and rendering the main component.
|
|
600
|
+
* @function init
|
|
601
|
+
* @param {Object} MainElement - The main component to render, typically the root component of the application.
|
|
602
|
+
* @param {string} [root='__ryunix'] - The ID of the HTML element that serves as the container for the root element. Defaults to `'__ryunix'` if not provided.
|
|
603
|
+
* @example
|
|
604
|
+
* Ryunix.init(App, "__ryunix"); // Initializes and renders the App component into the <div id="__ryunix"></div> element.
|
|
605
|
+
* @description This function retrieves the container element by its ID and invokes the `render` function to render the main component into it.
|
|
606
|
+
*/
|
|
607
|
+
const init = (MainElement, root = '__ryunix') => {
|
|
608
|
+
vars.containerRoot = document.getElementById(root);
|
|
609
|
+
|
|
610
|
+
const renderProcess = render(MainElement, vars.containerRoot);
|
|
611
|
+
|
|
612
|
+
return renderProcess
|
|
524
613
|
};
|
|
525
614
|
|
|
526
615
|
/**
|
|
527
|
-
* The function
|
|
528
|
-
*
|
|
529
|
-
* @
|
|
530
|
-
*
|
|
531
|
-
* @param nextProps - An object containing the new props that need to be updated in the DOM.
|
|
616
|
+
* @description The function creates a state.
|
|
617
|
+
* @param initial - The initial value of the state for the hook.
|
|
618
|
+
* @returns The `useStore` function returns an array with two elements: the current state value and a
|
|
619
|
+
* `setState` function that can be used to update the state.
|
|
532
620
|
*/
|
|
533
|
-
const
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
.filter((key) => isGone(nextProps)(key) || isNew(prevProps, nextProps)(key))
|
|
537
|
-
.forEach((name) => {
|
|
538
|
-
const eventType = name.toLowerCase().substring(2);
|
|
539
|
-
dom.removeEventListener(eventType, prevProps[name]);
|
|
540
|
-
});
|
|
621
|
+
const useStore = (initialState, init) => {
|
|
622
|
+
const reducer = (state, action) =>
|
|
623
|
+
typeof action === 'function' ? action(state) : action;
|
|
541
624
|
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
.filter(isGone(nextProps))
|
|
545
|
-
.forEach((name) => {
|
|
546
|
-
dom[name] = '';
|
|
547
|
-
});
|
|
625
|
+
return useReducer(reducer, initialState, init)
|
|
626
|
+
};
|
|
548
627
|
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
.
|
|
552
|
-
.
|
|
553
|
-
|
|
554
|
-
DomStyle(dom, nextProps['ryunix-style']);
|
|
555
|
-
} else if (name === OLD_STRINGS.style) {
|
|
556
|
-
DomStyle(dom, nextProps.style);
|
|
557
|
-
} else if (name === STRINGS.className) {
|
|
558
|
-
if (nextProps['ryunix-class'] === '') {
|
|
559
|
-
throw new Error('data-class cannot be empty.')
|
|
560
|
-
}
|
|
628
|
+
const useReducer = (reducer, initialState, init) => {
|
|
629
|
+
const oldHook =
|
|
630
|
+
vars.wipFiber.alternate &&
|
|
631
|
+
vars.wipFiber.alternate.hooks &&
|
|
632
|
+
vars.wipFiber.alternate.hooks[vars.hookIndex];
|
|
561
633
|
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
if (nextProps.className === '') {
|
|
567
|
-
throw new Error('className cannot be empty.')
|
|
568
|
-
}
|
|
634
|
+
const hook = {
|
|
635
|
+
state: oldHook ? oldHook.state : init ? init(initialState) : initialState,
|
|
636
|
+
queue: oldHook && Array.isArray(oldHook.queue) ? oldHook.queue.slice() : [],
|
|
637
|
+
};
|
|
569
638
|
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
} else {
|
|
574
|
-
dom[name] = nextProps[name];
|
|
575
|
-
}
|
|
639
|
+
if (oldHook && Array.isArray(oldHook.queue)) {
|
|
640
|
+
oldHook.queue.forEach((action) => {
|
|
641
|
+
hook.state = reducer(hook.state, action);
|
|
576
642
|
});
|
|
643
|
+
}
|
|
577
644
|
|
|
578
|
-
|
|
579
|
-
.
|
|
580
|
-
.filter(isNew(prevProps, nextProps))
|
|
581
|
-
.forEach((name) => {
|
|
582
|
-
const eventType = name.toLowerCase().substring(2);
|
|
583
|
-
dom.addEventListener(eventType, nextProps[name]);
|
|
584
|
-
});
|
|
585
|
-
};
|
|
645
|
+
const dispatch = (action) => {
|
|
646
|
+
hook.queue.push(action);
|
|
586
647
|
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
}
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
648
|
+
vars.wipRoot = {
|
|
649
|
+
dom: vars.currentRoot.dom,
|
|
650
|
+
props: vars.currentRoot.props,
|
|
651
|
+
alternate: vars.currentRoot,
|
|
652
|
+
};
|
|
653
|
+
vars.deletions = [];
|
|
654
|
+
vars.hookIndex = 0;
|
|
655
|
+
scheduleWork(vars.wipRoot);
|
|
656
|
+
};
|
|
596
657
|
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
createDom: createDom,
|
|
601
|
-
updateDom: updateDom
|
|
602
|
-
});
|
|
658
|
+
hook.queue.forEach((action) => {
|
|
659
|
+
hook.state = reducer(hook.state, action);
|
|
660
|
+
});
|
|
603
661
|
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
vars.deletions.forEach(commitWork);
|
|
609
|
-
if (vars.wipRoot && vars.wipRoot.child) {
|
|
610
|
-
commitWork(vars.wipRoot.child);
|
|
611
|
-
vars.currentRoot = vars.wipRoot;
|
|
612
|
-
}
|
|
613
|
-
vars.wipRoot = undefined;
|
|
662
|
+
vars.wipFiber.hooks[vars.hookIndex] = hook;
|
|
663
|
+
vars.hookIndex++;
|
|
664
|
+
|
|
665
|
+
return [hook.state, dispatch]
|
|
614
666
|
};
|
|
615
667
|
|
|
616
668
|
/**
|
|
617
|
-
*
|
|
618
|
-
* @param
|
|
619
|
-
*
|
|
620
|
-
*
|
|
621
|
-
* @
|
|
669
|
+
* This is a function that creates a hook for managing side effects in Ryunix components.
|
|
670
|
+
* @param effect - The effect function that will be executed after the component has rendered or when
|
|
671
|
+
* the dependencies have changed. It can perform side effects such as fetching data, updating the DOM,
|
|
672
|
+
* or subscribing to events.
|
|
673
|
+
* @param deps - An array of dependencies that the effect depends on. If any of the dependencies change
|
|
674
|
+
* between renders, the effect will be re-run. If the array is empty, the effect will only run once on
|
|
675
|
+
* mount and never again.
|
|
622
676
|
*/
|
|
623
|
-
const commitWork = (fiber) => {
|
|
624
|
-
if (!fiber) return
|
|
625
677
|
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
678
|
+
const useEffect = (callback, deps) => {
|
|
679
|
+
const oldHook =
|
|
680
|
+
vars.wipFiber.alternate &&
|
|
681
|
+
vars.wipFiber.alternate.hooks &&
|
|
682
|
+
vars.wipFiber.alternate.hooks[vars.hookIndex];
|
|
631
683
|
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
684
|
+
const hook = {
|
|
685
|
+
type: RYUNIX_TYPES.RYUNIX_EFFECT,
|
|
686
|
+
deps,
|
|
687
|
+
cleanup: oldHook?.cleanup,
|
|
688
|
+
};
|
|
689
|
+
|
|
690
|
+
const hasChanged = !oldHook || !lodash.isEqual(oldHook.deps, deps);
|
|
691
|
+
|
|
692
|
+
if (hasChanged) {
|
|
693
|
+
vars.effects.push(() => {
|
|
694
|
+
// Llama al cleanup anterior si existe
|
|
695
|
+
if (typeof hook.cleanup === 'function') {
|
|
696
|
+
hook.cleanup();
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
// Ejecuta el nuevo efecto y guarda el nuevo cleanup
|
|
700
|
+
const result = callback();
|
|
701
|
+
if (typeof result === 'function') {
|
|
702
|
+
hook.cleanup = result;
|
|
703
|
+
}
|
|
704
|
+
});
|
|
643
705
|
}
|
|
644
706
|
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
commitWork(fiber.sibling);
|
|
707
|
+
vars.wipFiber.hooks[vars.hookIndex] = hook;
|
|
708
|
+
vars.hookIndex++;
|
|
648
709
|
};
|
|
649
710
|
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
711
|
+
const useRef = (initial) => {
|
|
712
|
+
const oldHook =
|
|
713
|
+
vars.wipFiber.alternate &&
|
|
714
|
+
vars.wipFiber.alternate.hooks &&
|
|
715
|
+
vars.wipFiber.alternate.hooks[vars.hookIndex];
|
|
716
|
+
|
|
717
|
+
const hook = {
|
|
718
|
+
type: RYUNIX_TYPES.RYUNIX_REF,
|
|
719
|
+
value: oldHook ? oldHook.value : { current: initial },
|
|
720
|
+
};
|
|
721
|
+
|
|
722
|
+
vars.wipFiber.hooks[vars.hookIndex] = hook;
|
|
723
|
+
vars.hookIndex++;
|
|
724
|
+
|
|
725
|
+
return hook.value
|
|
726
|
+
};
|
|
727
|
+
|
|
728
|
+
const useMemo = (comp, deps) => {
|
|
729
|
+
const oldHook =
|
|
730
|
+
vars.wipFiber.alternate &&
|
|
731
|
+
vars.wipFiber.alternate.hooks &&
|
|
732
|
+
vars.wipFiber.alternate.hooks[vars.hookIndex];
|
|
733
|
+
|
|
734
|
+
const hook = {
|
|
735
|
+
type: RYUNIX_TYPES.RYUNIX_MEMO,
|
|
736
|
+
value: null,
|
|
737
|
+
deps,
|
|
738
|
+
};
|
|
739
|
+
|
|
740
|
+
if (oldHook) {
|
|
741
|
+
if (lodash.isEqual(oldHook.deps, hook.deps)) {
|
|
742
|
+
hook.value = oldHook.value;
|
|
743
|
+
} else {
|
|
744
|
+
hook.value = comp();
|
|
745
|
+
}
|
|
660
746
|
} else {
|
|
661
|
-
|
|
747
|
+
hook.value = comp();
|
|
662
748
|
}
|
|
749
|
+
|
|
750
|
+
vars.wipFiber.hooks[vars.hookIndex] = hook;
|
|
751
|
+
vars.hookIndex++;
|
|
752
|
+
|
|
753
|
+
return hook.value
|
|
663
754
|
};
|
|
664
755
|
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
commitRoot: commitRoot,
|
|
669
|
-
commitWork: commitWork
|
|
670
|
-
});
|
|
756
|
+
const useCallback = (callback, deps) => {
|
|
757
|
+
return useMemo(() => callback, deps)
|
|
758
|
+
};
|
|
671
759
|
|
|
672
760
|
/**
|
|
673
|
-
*
|
|
674
|
-
*
|
|
675
|
-
*
|
|
676
|
-
* @param wipFiber - A work-in-progress fiber object representing a component or element in the virtual
|
|
677
|
-
* DOM tree.
|
|
678
|
-
* @param elements - an array of elements representing the new children to be rendered in the current
|
|
679
|
-
* fiber's subtree
|
|
761
|
+
* The `useQuery` function parses the query parameters from the URL and returns them as an object.
|
|
762
|
+
* @returns An object containing key-value pairs of the query parameters from the URLSearchParams in
|
|
763
|
+
* the current window's URL is being returned.
|
|
680
764
|
*/
|
|
681
|
-
const
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
let
|
|
685
|
-
|
|
686
|
-
const oldFibersMap = new Map();
|
|
687
|
-
while (oldFiber) {
|
|
688
|
-
const oldKey = oldFiber.props.key || oldFiber.type;
|
|
689
|
-
oldFibersMap.set(oldKey, oldFiber);
|
|
690
|
-
oldFiber = oldFiber.sibling;
|
|
765
|
+
const useQuery = () => {
|
|
766
|
+
const searchParams = new URLSearchParams(window.location.search);
|
|
767
|
+
const query = {};
|
|
768
|
+
for (let [key, value] of searchParams.entries()) {
|
|
769
|
+
query[key] = value;
|
|
691
770
|
}
|
|
771
|
+
return query
|
|
772
|
+
};
|
|
692
773
|
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
774
|
+
/**
|
|
775
|
+
* `useRouter` is a routing function to manage navigation, nested routes, and route pre-loading.
|
|
776
|
+
*
|
|
777
|
+
* This function handles client-side routing, URL updates, and component rendering based on defined routes. It supports:
|
|
778
|
+
* - Dynamic routes (e.g., "/user/:id").
|
|
779
|
+
* - Optional nested routes with an `subRoutes` property in route objects.
|
|
780
|
+
* - Default pre-loading of all routes except the current active route.
|
|
781
|
+
*
|
|
782
|
+
* @param {Array} routes - An array of route objects, each containing:
|
|
783
|
+
* - `path` (string): The URL path to match (supports dynamic segments like "/user/:id").
|
|
784
|
+
* - `component` (function): The component to render when the route matches.
|
|
785
|
+
* - `subRoutes` (optional array): An optional array of nested route objects, defining sub-routes for this route.
|
|
786
|
+
* - `NotFound` (optional function): Component to render for unmatched routes (default 404 behavior).
|
|
787
|
+
*
|
|
788
|
+
* @returns {Object} - An object with:
|
|
789
|
+
* - `Children` (function): Returns the component that matches the current route, passing route parameters and query parameters as props.
|
|
790
|
+
* - `NavLink` (component): A link component to navigate within the application without refreshing the page.
|
|
791
|
+
* - `navigate` (function): Allows programmatically navigating to a specific path.
|
|
792
|
+
*
|
|
793
|
+
* @example
|
|
794
|
+
* // Define nested routes
|
|
795
|
+
* const routes = [
|
|
796
|
+
* {
|
|
797
|
+
* path: "/",
|
|
798
|
+
* component: HomePage,
|
|
799
|
+
* subRoutes: [
|
|
800
|
+
* {
|
|
801
|
+
* path: "/settings",
|
|
802
|
+
* component: SettingsPage,
|
|
803
|
+
* },
|
|
804
|
+
* ],
|
|
805
|
+
* },
|
|
806
|
+
* {
|
|
807
|
+
* path: "/user/:id",
|
|
808
|
+
* component: UserProfile,
|
|
809
|
+
* },
|
|
810
|
+
* {
|
|
811
|
+
* path: "*",
|
|
812
|
+
* NotFound: NotFoundPage,
|
|
813
|
+
* },
|
|
814
|
+
* ];
|
|
815
|
+
*
|
|
816
|
+
* // Use the routing function
|
|
817
|
+
* const { Children, NavLink } = useRouter(routes);
|
|
818
|
+
*
|
|
819
|
+
* // Render the matched component
|
|
820
|
+
* const App = () => (
|
|
821
|
+
* <>
|
|
822
|
+
* <NavLink to="/">Home</NavLink>
|
|
823
|
+
* <NavLink to="/settings">Settings</NavLink>
|
|
824
|
+
* <NavLink to="/user/123">User Profile</NavLink>
|
|
825
|
+
* <Children />
|
|
826
|
+
* </>
|
|
827
|
+
* );
|
|
828
|
+
*/
|
|
829
|
+
const useRouter = (routes) => {
|
|
830
|
+
const [location, setLocation] = useStore(window.location.pathname);
|
|
697
831
|
|
|
698
|
-
|
|
699
|
-
const
|
|
832
|
+
const findRoute = (routes, path) => {
|
|
833
|
+
const pathname = path.split('?')[0];
|
|
700
834
|
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
dom: oldFiber.dom,
|
|
706
|
-
parent: wipFiber,
|
|
707
|
-
alternate: oldFiber,
|
|
708
|
-
effectTag: EFFECT_TAGS.UPDATE,
|
|
709
|
-
};
|
|
710
|
-
oldFibersMap.delete(key);
|
|
711
|
-
} else if (element) {
|
|
712
|
-
newFiber = {
|
|
713
|
-
type: element.type,
|
|
714
|
-
props: element.props,
|
|
715
|
-
dom: undefined,
|
|
716
|
-
parent: wipFiber,
|
|
717
|
-
alternate: undefined,
|
|
718
|
-
effectTag: EFFECT_TAGS.PLACEMENT,
|
|
719
|
-
};
|
|
720
|
-
}
|
|
835
|
+
const notFoundRoute = routes.find((route) => route.NotFound);
|
|
836
|
+
const notFound = notFoundRoute
|
|
837
|
+
? { route: { component: notFoundRoute.NotFound }, params: {} }
|
|
838
|
+
: { route: { component: null }, params: {} };
|
|
721
839
|
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
840
|
+
for (const route of routes) {
|
|
841
|
+
if (route.subRoutes) {
|
|
842
|
+
const childRoute = findRoute(route.subRoutes, path);
|
|
843
|
+
if (childRoute) return childRoute
|
|
844
|
+
}
|
|
727
845
|
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
846
|
+
if (route.path === '*') {
|
|
847
|
+
return notFound
|
|
848
|
+
}
|
|
731
849
|
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
850
|
+
if (!route.path || typeof route.path !== 'string') {
|
|
851
|
+
console.warn('Invalid route detected:', route);
|
|
852
|
+
console.info(
|
|
853
|
+
"if you are using { NotFound: NotFound } please add { path: '*', NotFound: NotFound }",
|
|
854
|
+
);
|
|
855
|
+
continue
|
|
856
|
+
}
|
|
737
857
|
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
858
|
+
const keys = [];
|
|
859
|
+
const pattern = new RegExp(
|
|
860
|
+
`^${route.path.replace(/:\w+/g, (match) => {
|
|
861
|
+
keys.push(match.substring(1));
|
|
862
|
+
return '([^/]+)'
|
|
863
|
+
})}$`,
|
|
864
|
+
);
|
|
742
865
|
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
*/
|
|
750
|
-
const updateFunctionComponent = (fiber) => {
|
|
751
|
-
vars.wipFiber = fiber;
|
|
752
|
-
vars.hookIndex = 0;
|
|
753
|
-
vars.wipFiber.hooks = [];
|
|
866
|
+
const match = pathname.match(pattern);
|
|
867
|
+
if (match) {
|
|
868
|
+
const params = keys.reduce((acc, key, index) => {
|
|
869
|
+
acc[key] = match[index + 1];
|
|
870
|
+
return acc
|
|
871
|
+
}, {});
|
|
754
872
|
|
|
755
|
-
|
|
756
|
-
|
|
873
|
+
return { route, params }
|
|
874
|
+
}
|
|
875
|
+
}
|
|
757
876
|
|
|
758
|
-
|
|
759
|
-
|
|
877
|
+
return notFound
|
|
878
|
+
};
|
|
760
879
|
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
* fibers in the tree.
|
|
766
|
-
*/
|
|
767
|
-
const updateHostComponent = (fiber) => {
|
|
768
|
-
if (!fiber.dom) {
|
|
769
|
-
fiber.dom = createDom(fiber);
|
|
770
|
-
}
|
|
771
|
-
reconcileChildren(fiber, fiber.props.children);
|
|
772
|
-
};
|
|
880
|
+
const navigate = (path) => {
|
|
881
|
+
window.history.pushState({}, '', path);
|
|
882
|
+
updateRoute(path);
|
|
883
|
+
};
|
|
773
884
|
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
});
|
|
885
|
+
const updateRoute = (path) => {
|
|
886
|
+
const cleanedPath = path.split('?')[0];
|
|
887
|
+
setLocation(cleanedPath);
|
|
888
|
+
};
|
|
779
889
|
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
* @param deadline - The `deadline` parameter is an object that represents the amount of time the
|
|
784
|
-
* browser has to perform work before it needs to handle other tasks. It has a `timeRemaining()` method
|
|
785
|
-
* that returns the amount of time remaining before the deadline is reached. The `shouldYield` variable
|
|
786
|
-
* is used to determine
|
|
787
|
-
*/
|
|
788
|
-
const workLoop = (deadline) => {
|
|
789
|
-
let shouldYield = false;
|
|
790
|
-
while (vars.nextUnitOfWork && !shouldYield) {
|
|
791
|
-
vars.nextUnitOfWork = performUnitOfWork(vars.nextUnitOfWork);
|
|
792
|
-
shouldYield = deadline.timeRemaining() < 1;
|
|
793
|
-
}
|
|
890
|
+
useEffect(() => {
|
|
891
|
+
const onPopState = () => updateRoute(window.location.pathname);
|
|
892
|
+
window.addEventListener('popstate', onPopState);
|
|
794
893
|
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
}
|
|
894
|
+
return () => window.removeEventListener('popstate', onPopState)
|
|
895
|
+
}, []);
|
|
798
896
|
|
|
799
|
-
|
|
800
|
-
};
|
|
897
|
+
const currentRouteData = findRoute(routes, location) || {};
|
|
801
898
|
|
|
802
|
-
|
|
899
|
+
const Children = () => {
|
|
900
|
+
const query = useQuery();
|
|
901
|
+
const { route } = currentRouteData;
|
|
803
902
|
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
* sibling of the parent. The function returns `undefined` if there are no more fibers to process.
|
|
815
|
-
*/
|
|
816
|
-
const performUnitOfWork = (fiber) => {
|
|
817
|
-
const isFunctionComponent = fiber.type instanceof Function;
|
|
818
|
-
if (isFunctionComponent) {
|
|
819
|
-
updateFunctionComponent(fiber);
|
|
820
|
-
} else {
|
|
821
|
-
updateHostComponent(fiber);
|
|
822
|
-
}
|
|
823
|
-
if (fiber.child) {
|
|
824
|
-
return fiber.child
|
|
825
|
-
}
|
|
826
|
-
let nextFiber = fiber;
|
|
827
|
-
while (nextFiber) {
|
|
828
|
-
if (nextFiber.sibling) {
|
|
829
|
-
return nextFiber.sibling
|
|
903
|
+
if (
|
|
904
|
+
!route ||
|
|
905
|
+
!route.component ||
|
|
906
|
+
typeof route.component !== STRINGS.function
|
|
907
|
+
) {
|
|
908
|
+
console.error(
|
|
909
|
+
'Component not found for current path or the component is not a valid function:',
|
|
910
|
+
currentRouteData,
|
|
911
|
+
);
|
|
912
|
+
return null
|
|
830
913
|
}
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
914
|
+
|
|
915
|
+
return route.component({
|
|
916
|
+
params: currentRouteData.params || {},
|
|
917
|
+
query,
|
|
918
|
+
})
|
|
919
|
+
};
|
|
920
|
+
|
|
921
|
+
const NavLink = ({ to, ...props }) => {
|
|
922
|
+
const handleClick = (e) => {
|
|
923
|
+
e.preventDefault();
|
|
924
|
+
navigate(to);
|
|
925
|
+
};
|
|
926
|
+
return createElement(
|
|
927
|
+
'a',
|
|
928
|
+
{ href: to, onClick: handleClick, ...props },
|
|
929
|
+
props.children,
|
|
930
|
+
)
|
|
931
|
+
};
|
|
932
|
+
|
|
933
|
+
return { Children, NavLink, navigate }
|
|
834
934
|
};
|
|
835
935
|
|
|
836
|
-
var
|
|
936
|
+
var Hooks = /*#__PURE__*/Object.freeze({
|
|
837
937
|
__proto__: null,
|
|
838
|
-
|
|
839
|
-
|
|
938
|
+
useCallback: useCallback,
|
|
939
|
+
useEffect: useEffect,
|
|
940
|
+
useMemo: useMemo,
|
|
941
|
+
useQuery: useQuery,
|
|
942
|
+
useRef: useRef,
|
|
943
|
+
useRouter: useRouter,
|
|
944
|
+
useStore: useStore
|
|
840
945
|
});
|
|
841
946
|
|
|
842
947
|
var Ryunix = {
|
|
@@ -844,15 +949,12 @@
|
|
|
844
949
|
render,
|
|
845
950
|
init,
|
|
846
951
|
Fragment,
|
|
847
|
-
|
|
848
|
-
Workers,
|
|
849
|
-
Reconciler,
|
|
850
|
-
Components,
|
|
851
|
-
Commits,
|
|
952
|
+
Hooks,
|
|
852
953
|
};
|
|
853
954
|
|
|
854
955
|
window.Ryunix = Ryunix;
|
|
855
956
|
|
|
957
|
+
exports.Image = Image;
|
|
856
958
|
exports.default = Ryunix;
|
|
857
959
|
exports.useCallback = useCallback;
|
|
858
960
|
exports.useEffect = useEffect;
|