@coherent.js/client 1.0.0-beta.5 → 1.0.0-beta.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/dist/client/hmr.js +3 -3
- package/dist/client/hydration.js +37 -37
- package/dist/index.js +83 -30
- package/dist/index.js.map +2 -2
- package/package.json +28 -6
- package/src/index.js +62 -0
- package/types/hmr.d.ts +1 -0
- package/types/hydration.d.ts +66 -0
- package/types/index.d.ts +539 -46
- package/types/router.d.ts +167 -0
- package/src/hydration.js +0 -1791
package/dist/client/hmr.js
CHANGED
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
window.__coherent_hmr_initialized = true;
|
|
12
12
|
const log = (...args) => console.log('[Coherent HMR]', ...args);
|
|
13
13
|
const warn = (...args) => console.warn('[Coherent HMR]', ...args);
|
|
14
|
-
const _error = (...args) => console.
|
|
14
|
+
const _error = (...args) => console.error('[Coherent HMR]', ...args);
|
|
15
15
|
let hadDisconnect = false;
|
|
16
16
|
function connect() {
|
|
17
17
|
try {
|
|
@@ -33,8 +33,8 @@
|
|
|
33
33
|
warn('disconnected, retrying in 1s...');
|
|
34
34
|
setTimeout(connect, 1000);
|
|
35
35
|
});
|
|
36
|
-
ws.addEventListener('
|
|
37
|
-
|
|
36
|
+
ws.addEventListener('error', (e) => {
|
|
37
|
+
error('socket error', e);
|
|
38
38
|
try {
|
|
39
39
|
ws.close();
|
|
40
40
|
}
|
package/dist/client/hydration.js
CHANGED
|
@@ -58,8 +58,8 @@ function extractInitialState(element, options = {}) {
|
|
|
58
58
|
}
|
|
59
59
|
return hasState ? state : null;
|
|
60
60
|
}
|
|
61
|
-
catch (
|
|
62
|
-
console.warn('Error extracting initial state:',
|
|
61
|
+
catch (error) {
|
|
62
|
+
console.warn('Error extracting initial state:', error);
|
|
63
63
|
return options.initialState || null;
|
|
64
64
|
}
|
|
65
65
|
}
|
|
@@ -107,8 +107,8 @@ function hydrate(element, component, props = {}, options = {}) {
|
|
|
107
107
|
// Always use the fallback patching method to preserve hydration
|
|
108
108
|
this.fallbackRerender();
|
|
109
109
|
}
|
|
110
|
-
catch (
|
|
111
|
-
console.
|
|
110
|
+
catch (error) {
|
|
111
|
+
console.error('Error during component re-render:', error);
|
|
112
112
|
}
|
|
113
113
|
},
|
|
114
114
|
// Fallback re-render method using existing patching
|
|
@@ -129,8 +129,8 @@ function hydrate(element, component, props = {}, options = {}) {
|
|
|
129
129
|
this.previousVirtualElement = newVirtualElement;
|
|
130
130
|
// Component re-rendered successfully with fallback
|
|
131
131
|
}
|
|
132
|
-
catch (
|
|
133
|
-
console.
|
|
132
|
+
catch (error) {
|
|
133
|
+
console.error('Error during component re-render (fallback):', error);
|
|
134
134
|
}
|
|
135
135
|
},
|
|
136
136
|
// Create virtual element representation from existing DOM
|
|
@@ -316,9 +316,9 @@ function hydrate(element, component, props = {}, options = {}) {
|
|
|
316
316
|
(node.nodeType === Node.TEXT_NODE && node.textContent && node.textContent.trim());
|
|
317
317
|
});
|
|
318
318
|
}
|
|
319
|
-
catch (
|
|
319
|
+
catch (error) {
|
|
320
320
|
// Fallback to empty array if Array.from fails
|
|
321
|
-
console.warn('Failed to convert childNodes to array:',
|
|
321
|
+
console.warn('Failed to convert childNodes to array:', error);
|
|
322
322
|
domChildren = [];
|
|
323
323
|
}
|
|
324
324
|
}
|
|
@@ -366,7 +366,7 @@ function hydrate(element, component, props = {}, options = {}) {
|
|
|
366
366
|
const props = vNode[tagName] || {};
|
|
367
367
|
const element = document.createElement(tagName);
|
|
368
368
|
// Set attributes
|
|
369
|
-
Object.keys(
|
|
369
|
+
Object.keys(_props).forEach(key => {
|
|
370
370
|
if (key === 'children' || key === 'text')
|
|
371
371
|
return;
|
|
372
372
|
const value = props[key];
|
|
@@ -411,7 +411,7 @@ function hydrate(element, component, props = {}, options = {}) {
|
|
|
411
411
|
// Build attributes
|
|
412
412
|
let attributes = '';
|
|
413
413
|
const children = [];
|
|
414
|
-
Object.keys(
|
|
414
|
+
Object.keys(_props).forEach(key => {
|
|
415
415
|
if (key === 'children') {
|
|
416
416
|
if (Array.isArray(props.children)) {
|
|
417
417
|
children.push(...props.children);
|
|
@@ -571,8 +571,8 @@ if (typeof window !== 'undefined') {
|
|
|
571
571
|
// Call the handler function with the element as context and pass event, state, setState
|
|
572
572
|
handlerFunc.call(element, event, state, setState);
|
|
573
573
|
}
|
|
574
|
-
catch (
|
|
575
|
-
console.warn(`Error executing coherent event handler:`,
|
|
574
|
+
catch (error) {
|
|
575
|
+
console.warn(`Error executing coherent event handler:`, error);
|
|
576
576
|
}
|
|
577
577
|
}
|
|
578
578
|
else {
|
|
@@ -580,8 +580,8 @@ if (typeof window !== 'undefined') {
|
|
|
580
580
|
try {
|
|
581
581
|
handlerFunc.call(element, event);
|
|
582
582
|
}
|
|
583
|
-
catch (
|
|
584
|
-
console.warn(`Error executing coherent event handler (no component
|
|
583
|
+
catch (error) {
|
|
584
|
+
console.warn(`Error executing coherent event handler (no component _context):`, error);
|
|
585
585
|
}
|
|
586
586
|
}
|
|
587
587
|
}
|
|
@@ -630,7 +630,7 @@ function renderVirtualDOMToElement(vdom, container) {
|
|
|
630
630
|
const element = document.createElement(tagName);
|
|
631
631
|
const props = vdom[tagName] || {};
|
|
632
632
|
// Set attributes and properties
|
|
633
|
-
Object.keys(
|
|
633
|
+
Object.keys(_props).forEach(key => {
|
|
634
634
|
if (key === 'children') {
|
|
635
635
|
// Handle children
|
|
636
636
|
const children = props.children;
|
|
@@ -984,8 +984,8 @@ function attachFunctionEventListeners(rootElement, virtualElement, instance, opt
|
|
|
984
984
|
const result = handler.call(domElement, event, currentState, currentSetState);
|
|
985
985
|
return result;
|
|
986
986
|
}
|
|
987
|
-
catch (
|
|
988
|
-
console.
|
|
987
|
+
catch (error) {
|
|
988
|
+
console.error(`Error in ${eventName} handler:`, error);
|
|
989
989
|
}
|
|
990
990
|
};
|
|
991
991
|
// Remove any existing onclick attributes that might interfere
|
|
@@ -1106,8 +1106,8 @@ function attachEventListeners(element, instance) {
|
|
|
1106
1106
|
const func = new Function('event', 'state', 'setState', 'element', eventAttr);
|
|
1107
1107
|
func.call(elementWithEvent, e, state, setState, elementWithEvent);
|
|
1108
1108
|
}
|
|
1109
|
-
catch (
|
|
1110
|
-
console.warn(`Error executing ${eventName} handler:`,
|
|
1109
|
+
catch (error) {
|
|
1110
|
+
console.warn(`Error executing ${eventName} handler:`, error);
|
|
1111
1111
|
}
|
|
1112
1112
|
};
|
|
1113
1113
|
if (typeof elementWithEvent.addEventListener === 'function') {
|
|
@@ -1165,8 +1165,8 @@ function attachEventListeners(element, instance) {
|
|
|
1165
1165
|
handlerFunc.call(actionElement, e);
|
|
1166
1166
|
}
|
|
1167
1167
|
}
|
|
1168
|
-
catch (
|
|
1169
|
-
console.warn(`Error executing action handler for ${actionId}:`,
|
|
1168
|
+
catch (error) {
|
|
1169
|
+
console.warn(`Error executing action handler for ${actionId}:`, error);
|
|
1170
1170
|
}
|
|
1171
1171
|
};
|
|
1172
1172
|
if (typeof actionElement.addEventListener === 'function') {
|
|
@@ -1211,8 +1211,8 @@ function attachEventListeners(element, instance) {
|
|
|
1211
1211
|
// Bind the function to the element and call it with the event
|
|
1212
1212
|
handlerFunc.call(elementWithCoherentEvent, e, state, setState);
|
|
1213
1213
|
}
|
|
1214
|
-
catch (
|
|
1215
|
-
console.warn(`Error executing coherent event handler:`,
|
|
1214
|
+
catch (error) {
|
|
1215
|
+
console.warn(`Error executing coherent event handler:`, error);
|
|
1216
1216
|
}
|
|
1217
1217
|
};
|
|
1218
1218
|
if (typeof elementWithCoherentEvent.addEventListener === 'function') {
|
|
@@ -1230,8 +1230,8 @@ function attachEventListeners(element, instance) {
|
|
|
1230
1230
|
}
|
|
1231
1231
|
});
|
|
1232
1232
|
}
|
|
1233
|
-
catch (
|
|
1234
|
-
console.warn('Error attaching event listeners:',
|
|
1233
|
+
catch (error) {
|
|
1234
|
+
console.warn('Error attaching event listeners:', error);
|
|
1235
1235
|
}
|
|
1236
1236
|
}
|
|
1237
1237
|
/**
|
|
@@ -1324,8 +1324,8 @@ function handleComponentAction(event, action, target, instance) {
|
|
|
1324
1324
|
// Call the custom method on the instance
|
|
1325
1325
|
instance[action](event, target);
|
|
1326
1326
|
}
|
|
1327
|
-
catch (
|
|
1328
|
-
console.warn(`Error executing custom action ${action}:`,
|
|
1327
|
+
catch (error) {
|
|
1328
|
+
console.warn(`Error executing custom action ${action}:`, error);
|
|
1329
1329
|
}
|
|
1330
1330
|
}
|
|
1331
1331
|
else {
|
|
@@ -1339,8 +1339,8 @@ function handleComponentAction(event, action, target, instance) {
|
|
|
1339
1339
|
// Call the handler function with event, state, and setState
|
|
1340
1340
|
handlerFunc(event, state, setState);
|
|
1341
1341
|
}
|
|
1342
|
-
catch (
|
|
1343
|
-
console.warn(`Error executing action handler ${action}:`,
|
|
1342
|
+
catch (error) {
|
|
1343
|
+
console.warn(`Error executing action handler ${action}:`, error);
|
|
1344
1344
|
}
|
|
1345
1345
|
}
|
|
1346
1346
|
else {
|
|
@@ -1365,7 +1365,7 @@ function hydrateAll(elements, components, propsArray = []) {
|
|
|
1365
1365
|
return elements.map((element, index) => {
|
|
1366
1366
|
const component = components[index];
|
|
1367
1367
|
const props = propsArray[index] || {};
|
|
1368
|
-
return hydrate(element, component,
|
|
1368
|
+
return hydrate(element, component, _props);
|
|
1369
1369
|
});
|
|
1370
1370
|
}
|
|
1371
1371
|
/**
|
|
@@ -1381,7 +1381,7 @@ function hydrateBySelector(selector, component, props = {}) {
|
|
|
1381
1381
|
return [];
|
|
1382
1382
|
}
|
|
1383
1383
|
const elements = document.querySelectorAll(selector);
|
|
1384
|
-
return Array.from(elements).map(element => hydrate(element, component,
|
|
1384
|
+
return Array.from(elements).map(element => hydrate(element, component, _props));
|
|
1385
1385
|
}
|
|
1386
1386
|
/**
|
|
1387
1387
|
* Enable client-side interactivity for event handlers
|
|
@@ -1408,7 +1408,7 @@ function makeHydratable(component, options = {}) {
|
|
|
1408
1408
|
const componentName = options.componentName || component.name || 'AnonymousComponent';
|
|
1409
1409
|
// Create a new function that wraps the original component
|
|
1410
1410
|
const hydratableComponent = function (props = {}) {
|
|
1411
|
-
return component(
|
|
1411
|
+
return component(_props);
|
|
1412
1412
|
};
|
|
1413
1413
|
// Set the component name on the hydratable component function
|
|
1414
1414
|
Object.defineProperty(hydratableComponent, 'name', {
|
|
@@ -1477,13 +1477,13 @@ function makeHydratable(component, options = {}) {
|
|
|
1477
1477
|
hydrationAttributes: {
|
|
1478
1478
|
'data-coherent-component': componentName,
|
|
1479
1479
|
'data-coherent-state': state ? JSON.stringify(state) : (options.initialState ? JSON.stringify(options.initialState) : null),
|
|
1480
|
-
'data-coherent-props': Object.keys(
|
|
1480
|
+
'data-coherent-props': Object.keys(_props).length > 0 ? JSON.stringify(_props) : null
|
|
1481
1481
|
}
|
|
1482
1482
|
};
|
|
1483
1483
|
};
|
|
1484
1484
|
// Add a method to render with hydration data
|
|
1485
1485
|
hydratableComponent.renderWithHydration = function (props = {}) {
|
|
1486
|
-
const result = hydratableComponent(
|
|
1486
|
+
const result = hydratableComponent(_props);
|
|
1487
1487
|
// Try to extract state from the component if it's a withState wrapped component
|
|
1488
1488
|
let state = null;
|
|
1489
1489
|
if (hydratableComponent.__wrappedComponent && hydratableComponent.__stateContainer) {
|
|
@@ -1552,7 +1552,7 @@ function autoHydrate(componentRegistry = {}) {
|
|
|
1552
1552
|
}
|
|
1553
1553
|
}
|
|
1554
1554
|
if (!component) {
|
|
1555
|
-
console.
|
|
1555
|
+
console.error(`❌ Component ${componentName} not found in registry`);
|
|
1556
1556
|
return; // Skip this element
|
|
1557
1557
|
}
|
|
1558
1558
|
if (component) {
|
|
@@ -1572,8 +1572,8 @@ function autoHydrate(componentRegistry = {}) {
|
|
|
1572
1572
|
console.warn(`❌ Failed to hydrate component: ${componentName}`);
|
|
1573
1573
|
}
|
|
1574
1574
|
}
|
|
1575
|
-
catch (
|
|
1576
|
-
console.
|
|
1575
|
+
catch (error) {
|
|
1576
|
+
console.error(`❌ Failed to auto-hydrate component ${componentName}:`, error);
|
|
1577
1577
|
}
|
|
1578
1578
|
}
|
|
1579
1579
|
});
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,16 @@
|
|
|
1
1
|
// src/hydration.js
|
|
2
2
|
var componentInstances = /* @__PURE__ */ new WeakMap();
|
|
3
|
+
function getKey(vNode) {
|
|
4
|
+
if (!vNode || typeof vNode !== "object" || Array.isArray(vNode)) {
|
|
5
|
+
return void 0;
|
|
6
|
+
}
|
|
7
|
+
const tagName = Object.keys(vNode)[0];
|
|
8
|
+
const props = vNode[tagName];
|
|
9
|
+
if (props && typeof props === "object") {
|
|
10
|
+
return props.key;
|
|
11
|
+
}
|
|
12
|
+
return void 0;
|
|
13
|
+
}
|
|
3
14
|
function extractInitialState(element, options = {}) {
|
|
4
15
|
if (typeof window === "undefined") {
|
|
5
16
|
return options.initialState || null;
|
|
@@ -213,7 +224,25 @@ function hydrate(element, component, props = {}, options = {}) {
|
|
|
213
224
|
}
|
|
214
225
|
});
|
|
215
226
|
},
|
|
216
|
-
//
|
|
227
|
+
// Get children array from a vNode
|
|
228
|
+
getVNodeChildren(vNode) {
|
|
229
|
+
if (!vNode || typeof vNode !== "object" || Array.isArray(vNode)) {
|
|
230
|
+
return [];
|
|
231
|
+
}
|
|
232
|
+
const tagName = Object.keys(vNode)[0];
|
|
233
|
+
const props2 = vNode[tagName];
|
|
234
|
+
if (!props2 || typeof props2 !== "object") {
|
|
235
|
+
return [];
|
|
236
|
+
}
|
|
237
|
+
if (props2.children) {
|
|
238
|
+
return Array.isArray(props2.children) ? props2.children : [props2.children];
|
|
239
|
+
}
|
|
240
|
+
if (props2.text !== void 0) {
|
|
241
|
+
return [props2.text];
|
|
242
|
+
}
|
|
243
|
+
return [];
|
|
244
|
+
},
|
|
245
|
+
// Patch children with key-based reconciliation
|
|
217
246
|
patchChildren(domElement, oldVNode, newVNode) {
|
|
218
247
|
if (typeof window === "undefined" || typeof Node === "undefined" || typeof document === "undefined") {
|
|
219
248
|
return;
|
|
@@ -221,22 +250,8 @@ function hydrate(element, component, props = {}, options = {}) {
|
|
|
221
250
|
if (!domElement || typeof domElement.childNodes === "undefined" || typeof domElement.appendChild !== "function") {
|
|
222
251
|
return;
|
|
223
252
|
}
|
|
224
|
-
const
|
|
225
|
-
const
|
|
226
|
-
const oldProps = oldVNode && oldTagName ? oldVNode[oldTagName] || {} : {};
|
|
227
|
-
const newProps = newVNode[newTagName] || {};
|
|
228
|
-
let oldChildren = [];
|
|
229
|
-
let newChildren = [];
|
|
230
|
-
if (oldProps.children) {
|
|
231
|
-
oldChildren = Array.isArray(oldProps.children) ? oldProps.children : [oldProps.children];
|
|
232
|
-
} else if (oldProps.text) {
|
|
233
|
-
oldChildren = [oldProps.text];
|
|
234
|
-
}
|
|
235
|
-
if (newProps.children) {
|
|
236
|
-
newChildren = Array.isArray(newProps.children) ? newProps.children : [newProps.children];
|
|
237
|
-
} else if (newProps.text) {
|
|
238
|
-
newChildren = [newProps.text];
|
|
239
|
-
}
|
|
253
|
+
const oldChildren = this.getVNodeChildren(oldVNode);
|
|
254
|
+
const newChildren = this.getVNodeChildren(newVNode);
|
|
240
255
|
let domChildren = [];
|
|
241
256
|
if (typeof Array.from === "function" && domElement.childNodes) {
|
|
242
257
|
try {
|
|
@@ -248,24 +263,62 @@ function hydrate(element, component, props = {}, options = {}) {
|
|
|
248
263
|
domChildren = [];
|
|
249
264
|
}
|
|
250
265
|
}
|
|
251
|
-
const
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
const
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
266
|
+
const oldKeyMap = /* @__PURE__ */ new Map();
|
|
267
|
+
const oldIndexMap = /* @__PURE__ */ new Map();
|
|
268
|
+
oldChildren.forEach((child, i) => {
|
|
269
|
+
const key = getKey(child);
|
|
270
|
+
if (key !== void 0) {
|
|
271
|
+
oldKeyMap.set(key, { vNode: child, index: i, domNode: domChildren[i] });
|
|
272
|
+
} else {
|
|
273
|
+
oldIndexMap.set(i, { vNode: child, index: i, domNode: domChildren[i] });
|
|
274
|
+
}
|
|
275
|
+
});
|
|
276
|
+
const usedOldNodes = /* @__PURE__ */ new Set();
|
|
277
|
+
newChildren.forEach((newChild, newIndex) => {
|
|
278
|
+
const newKey = getKey(newChild);
|
|
279
|
+
let oldEntry = null;
|
|
280
|
+
if (newKey !== void 0 && oldKeyMap.has(newKey)) {
|
|
281
|
+
oldEntry = oldKeyMap.get(newKey);
|
|
282
|
+
usedOldNodes.add(newKey);
|
|
283
|
+
} else if (newKey === void 0 && oldIndexMap.has(newIndex)) {
|
|
284
|
+
oldEntry = oldIndexMap.get(newIndex);
|
|
285
|
+
usedOldNodes.add(`index:${newIndex}`);
|
|
286
|
+
}
|
|
287
|
+
if (oldEntry && oldEntry.domNode) {
|
|
288
|
+
this.patchDOM(oldEntry.domNode, oldEntry.vNode, newChild);
|
|
289
|
+
const currentPosition = Array.from(domElement.childNodes).filter((node) => {
|
|
290
|
+
return node.nodeType === Node.ELEMENT_NODE || node.nodeType === Node.TEXT_NODE && node.textContent && node.textContent.trim();
|
|
291
|
+
}).indexOf(oldEntry.domNode);
|
|
292
|
+
if (currentPosition !== newIndex) {
|
|
293
|
+
const referenceNode = domElement.childNodes[newIndex];
|
|
294
|
+
if (referenceNode) {
|
|
295
|
+
domElement.insertBefore(oldEntry.domNode, referenceNode);
|
|
296
|
+
} else {
|
|
297
|
+
domElement.appendChild(oldEntry.domNode);
|
|
298
|
+
}
|
|
259
299
|
}
|
|
260
|
-
} else
|
|
300
|
+
} else {
|
|
261
301
|
const newElement = this.createDOMElement(newChild);
|
|
262
302
|
if (newElement) {
|
|
263
|
-
domElement.
|
|
303
|
+
const referenceNode = domElement.childNodes[newIndex];
|
|
304
|
+
if (referenceNode) {
|
|
305
|
+
domElement.insertBefore(newElement, referenceNode);
|
|
306
|
+
} else {
|
|
307
|
+
domElement.appendChild(newElement);
|
|
308
|
+
}
|
|
264
309
|
}
|
|
265
|
-
} else {
|
|
266
|
-
this.patchDOM(domChild, oldChild, newChild);
|
|
267
310
|
}
|
|
268
|
-
}
|
|
311
|
+
});
|
|
312
|
+
oldKeyMap.forEach((entry, key) => {
|
|
313
|
+
if (!usedOldNodes.has(key) && entry.domNode && entry.domNode.parentNode) {
|
|
314
|
+
entry.domNode.remove();
|
|
315
|
+
}
|
|
316
|
+
});
|
|
317
|
+
oldIndexMap.forEach((entry, index) => {
|
|
318
|
+
if (!usedOldNodes.has(`index:${index}`) && entry.domNode && entry.domNode.parentNode) {
|
|
319
|
+
entry.domNode.remove();
|
|
320
|
+
}
|
|
321
|
+
});
|
|
269
322
|
},
|
|
270
323
|
// Create DOM element from virtual element
|
|
271
324
|
createDOMElement(vNode) {
|