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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/src/client.ts +62 -7
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "melina",
3
- "version": "1.0.6",
3
+ "version": "1.0.7",
4
4
  "description": "A lightweight, islands-architecture web framework for Bun with Next.js-style routing.",
5
5
  "module": "./src/web.ts",
6
6
  "main": "./src/web.ts",
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
- // Sync existing islands immediately
1336
- syncIslands();
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(/&quot;/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
- document.body.innerHTML = newDoc.body.innerHTML;
1467
- syncIslands();
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(/&quot;/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) {