@stacksjs/stx
Version:
A Bun plugin that allows for using Laravel Blade-like syntax.
91 lines (90 loc) • 3.03 kB
JavaScript
// src/client.ts
var isBrowser = typeof window !== "undefined" && typeof document !== "undefined";
async function hydrateIsland(element, name, handler) {
if (!isBrowser)
return;
try {
const id = element.getAttribute("data-island-id");
const propsScript = document.querySelector(`script[data-island-props="${id}"]`);
const props = propsScript ? JSON.parse(propsScript.textContent || "{}") : {};
const module = await handler();
const hydrateFn = module.default || module.hydrate;
if (typeof hydrateFn !== "function") {
console.error(`Island handler for "${name}" does not export a valid hydration function`);
return;
}
await hydrateFn(element, props);
element.setAttribute("data-hydrated", "true");
} catch (error) {
console.error(`Failed to hydrate island "${name}":`, error);
}
}
function hydrateIslands(handlers) {
if (!isBrowser)
return;
const islands = document.querySelectorAll("[data-island]");
const lazyIslands = [];
islands.forEach((island) => {
const name = island.getAttribute("data-island");
const priority = island.getAttribute("data-priority") || "lazy";
if (!name || !handlers[name] || island.getAttribute("data-hydrated") === "true") {
return;
}
if (priority === "eager") {
hydrateIsland(island, name, handlers[name]);
} else {
lazyIslands.push([island, name]);
}
});
if (lazyIslands.length > 0 && "IntersectionObserver" in window) {
const observer = new IntersectionObserver((entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
const element = entry.target;
const name = element.getAttribute("data-island");
if (name && handlers[name] && element.getAttribute("data-hydrated") !== "true") {
hydrateIsland(element, name, handlers[name]);
observer.unobserve(element);
}
}
});
}, {
rootMargin: "200px",
threshold: 0
});
lazyIslands.forEach(([element]) => {
observer.observe(element);
});
} else {
setTimeout(() => {
lazyIslands.forEach(([element, name]) => {
if (element.getAttribute("data-hydrated") !== "true") {
hydrateIsland(element, name, handlers[name]);
}
});
}, 1000);
}
}
function preloadIslandHandlers(handlers) {
if (!isBrowser)
return;
Object.entries(handlers).forEach(([name, getHandler]) => {
try {
const handlerString = getHandler.toString();
const moduleMatch = handlerString.match(/import\(['"](.+)['"]\)/);
if (moduleMatch && moduleMatch[1]) {
const link = document.createElement("link");
link.rel = "modulepreload";
link.href = moduleMatch[1];
link.setAttribute("data-island-module", name);
document.head.appendChild(link);
}
} catch (error) {
console.warn(`Failed to preload island handler for "${name}":`, error);
}
});
}
export {
preloadIslandHandlers,
hydrateIslands
};