@coherent.js/client 1.0.0-beta.3 → 1.0.0-beta.6
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 +19 -0
- package/dist/index.js +83 -30
- package/dist/index.js.map +2 -2
- package/package.json +25 -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/README.md
CHANGED
|
@@ -23,6 +23,25 @@ Requirements:
|
|
|
23
23
|
- Node.js >= 20
|
|
24
24
|
- ESM module system
|
|
25
25
|
|
|
26
|
+
|
|
27
|
+
## Exports
|
|
28
|
+
|
|
29
|
+
Client-side hydration and HMR utilities
|
|
30
|
+
|
|
31
|
+
### Modular Imports (Tree-Shakable)
|
|
32
|
+
|
|
33
|
+
- Hydration utilities: `@coherent.js/client`
|
|
34
|
+
- Client router: `@coherent.js/client/router`
|
|
35
|
+
- HMR support: `@coherent.js/client/hmr`
|
|
36
|
+
|
|
37
|
+
### Example Usage
|
|
38
|
+
|
|
39
|
+
```javascript
|
|
40
|
+
import { hydrateComponent } from '@coherent.js/client';
|
|
41
|
+
import { createClientRouter } from '@coherent.js/client/router';
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
> **Note**: All exports are tree-shakable. Import only what you need for optimal bundle size.
|
|
26
45
|
## Quick start
|
|
27
46
|
|
|
28
47
|
The client package pairs with server-rendered HTML produced by `@coherent.js/core`.
|
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) {
|