melina 1.0.6 → 1.0.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/package.json +1 -1
- package/src/client.ts +62 -7
package/package.json
CHANGED
package/src/client.ts
CHANGED
|
@@ -1322,7 +1322,7 @@ export async function navigate(href: string): Promise<void> {
|
|
|
1322
1322
|
// Update URL
|
|
1323
1323
|
window.history.pushState({}, '', href);
|
|
1324
1324
|
|
|
1325
|
-
// DOM update function
|
|
1325
|
+
// DOM update function - optimized with DocumentFragment for minimal reflow
|
|
1326
1326
|
const performUpdate = () => {
|
|
1327
1327
|
// Dispatch navigation event (for island state updates)
|
|
1328
1328
|
window.dispatchEvent(new CustomEvent('melina:navigation-start', {
|
|
@@ -1330,10 +1330,41 @@ export async function navigate(href: string): Promise<void> {
|
|
|
1330
1330
|
}));
|
|
1331
1331
|
|
|
1332
1332
|
document.title = newDoc.title;
|
|
1333
|
-
document.body.innerHTML = newDoc.body.innerHTML;
|
|
1334
1333
|
|
|
1335
|
-
//
|
|
1336
|
-
|
|
1334
|
+
// OPTIMIZATION: Build new body off-screen using DocumentFragment
|
|
1335
|
+
// This minimizes layout thrashing by preparing everything before touching DOM
|
|
1336
|
+
|
|
1337
|
+
// 1. Create a DocumentFragment with all new children
|
|
1338
|
+
const fragment = document.createDocumentFragment();
|
|
1339
|
+
|
|
1340
|
+
// 2. Move all children from newDoc.body to fragment (off-screen)
|
|
1341
|
+
while (newDoc.body.firstChild) {
|
|
1342
|
+
fragment.appendChild(newDoc.body.firstChild);
|
|
1343
|
+
}
|
|
1344
|
+
|
|
1345
|
+
// 3. Find island placeholders in the fragment and pre-insert storage nodes
|
|
1346
|
+
// This happens OFF-SCREEN, no reflow triggered
|
|
1347
|
+
const placeholders = fragment.querySelectorAll('[data-melina-island]');
|
|
1348
|
+
for (let i = 0; i < placeholders.length; i++) {
|
|
1349
|
+
const el = placeholders[i] as HTMLElement;
|
|
1350
|
+
const name = el.getAttribute('data-melina-island');
|
|
1351
|
+
if (!name) continue;
|
|
1352
|
+
|
|
1353
|
+
const instanceId = el.getAttribute('data-instance') || `${name}-${i}`;
|
|
1354
|
+
const existing = islandRegistry.get(instanceId);
|
|
1355
|
+
|
|
1356
|
+
if (existing) {
|
|
1357
|
+
// Pre-insert storage node into fragment (still off-screen)
|
|
1358
|
+
const propsStr = (el.getAttribute('data-props') || '{}').replace(/"/g, '"');
|
|
1359
|
+
existing.props = JSON.parse(propsStr);
|
|
1360
|
+
el.appendChild(existing.storageNode);
|
|
1361
|
+
}
|
|
1362
|
+
}
|
|
1363
|
+
|
|
1364
|
+
// 4. SINGLE REFLOW: Clear body and append prepared fragment
|
|
1365
|
+
// All island storage nodes are already in place
|
|
1366
|
+
document.body.replaceChildren(fragment);
|
|
1367
|
+
|
|
1337
1368
|
window.scrollTo(0, 0);
|
|
1338
1369
|
};
|
|
1339
1370
|
|
|
@@ -1345,7 +1376,7 @@ export async function navigate(href: string): Promise<void> {
|
|
|
1345
1376
|
performUpdate();
|
|
1346
1377
|
}
|
|
1347
1378
|
|
|
1348
|
-
// Hydrate new islands
|
|
1379
|
+
// Hydrate new islands (ones that weren't in registry)
|
|
1349
1380
|
await hydrateIslands();
|
|
1350
1381
|
|
|
1351
1382
|
console.log('[Melina Client] Navigation complete');
|
|
@@ -1463,8 +1494,32 @@ export async function init(): Promise<void> {
|
|
|
1463
1494
|
const newDoc = new DOMParser().parseFromString(html, 'text/html');
|
|
1464
1495
|
|
|
1465
1496
|
document.title = newDoc.title;
|
|
1466
|
-
|
|
1467
|
-
|
|
1497
|
+
|
|
1498
|
+
// OPTIMIZATION: Build off-screen with DocumentFragment
|
|
1499
|
+
const fragment = document.createDocumentFragment();
|
|
1500
|
+
while (newDoc.body.firstChild) {
|
|
1501
|
+
fragment.appendChild(newDoc.body.firstChild);
|
|
1502
|
+
}
|
|
1503
|
+
|
|
1504
|
+
// Pre-insert islands into fragment (off-screen)
|
|
1505
|
+
const placeholders = fragment.querySelectorAll('[data-melina-island]');
|
|
1506
|
+
for (let i = 0; i < placeholders.length; i++) {
|
|
1507
|
+
const el = placeholders[i] as HTMLElement;
|
|
1508
|
+
const name = el.getAttribute('data-melina-island');
|
|
1509
|
+
if (!name) continue;
|
|
1510
|
+
|
|
1511
|
+
const instanceId = el.getAttribute('data-instance') || `${name}-${i}`;
|
|
1512
|
+
const existing = islandRegistry.get(instanceId);
|
|
1513
|
+
|
|
1514
|
+
if (existing) {
|
|
1515
|
+
const propsStr = (el.getAttribute('data-props') || '{}').replace(/"/g, '"');
|
|
1516
|
+
existing.props = JSON.parse(propsStr);
|
|
1517
|
+
el.appendChild(existing.storageNode);
|
|
1518
|
+
}
|
|
1519
|
+
}
|
|
1520
|
+
|
|
1521
|
+
// Single reflow: swap prepared fragment
|
|
1522
|
+
document.body.replaceChildren(fragment);
|
|
1468
1523
|
};
|
|
1469
1524
|
|
|
1470
1525
|
if (document.startViewTransition) {
|