loly 0.1.0
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 +374 -0
- package/bin/loly.js +3015 -0
- package/dist/bootstrap-DgWagZ79.d.ts +16 -0
- package/dist/client.d.ts +101 -0
- package/dist/client.js +727 -0
- package/dist/client.js.map +1 -0
- package/dist/components.d.ts +39 -0
- package/dist/components.js +147 -0
- package/dist/components.js.map +1 -0
- package/dist/index.d.ts +458 -0
- package/dist/index.js +3826 -0
- package/dist/index.js.map +1 -0
- package/package.json +108 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/client/router.ts","../src/constants/server.ts","../src/constants/errors.ts","../src/utils/path.ts","../src/utils/route-matcher.ts","../src/client/bootstrap.tsx"],"sourcesContent":["import type { ClientRouteLoaded } from \"./types\";\r\nimport { bootIslands, mount, activateAsyncComponents } from \"loly-jsx\";\r\nimport { SERVER } from \"../constants\";\r\nimport { matchesRoute, extractRouteParams } from \"../utils/route-matcher\";\r\nimport { normalizeUrlPath } from \"../utils/path\";\r\nimport { ERROR_MESSAGES } from \"../constants/errors\";\r\n\r\ninterface LocationState {\r\n pathname: string;\r\n search: string;\r\n hash: string;\r\n}\r\n\r\ntype RSCPayload = {\r\n html: string;\r\n scripts: string;\r\n styles: string;\r\n islandData?: Record<string, any>;\r\n};\r\n\r\n/**\r\n * LRU Cache implementation with configurable size limit\r\n * Maintains access order using Map's insertion order\r\n */\r\nclass LRUCache<K, V> {\r\n private cache: Map<K, V>;\r\n private readonly maxSize: number;\r\n\r\n constructor(maxSize: number = 10) {\r\n this.maxSize = maxSize;\r\n this.cache = new Map();\r\n }\r\n\r\n get(key: K): V | undefined {\r\n if (!this.cache.has(key)) {\r\n return undefined;\r\n }\r\n\r\n // Move to end (most recently used)\r\n const value = this.cache.get(key)!;\r\n this.cache.delete(key);\r\n this.cache.set(key, value);\r\n return value;\r\n }\r\n\r\n set(key: K, value: V): void {\r\n if (this.cache.has(key)) {\r\n // Update existing: remove and re-add to end\r\n this.cache.delete(key);\r\n } else if (this.cache.size >= this.maxSize) {\r\n // Remove oldest (first entry)\r\n const firstKey = this.cache.keys().next().value;\r\n if (firstKey !== undefined) {\r\n this.cache.delete(firstKey);\r\n }\r\n }\r\n\r\n this.cache.set(key, value);\r\n }\r\n\r\n has(key: K): boolean {\r\n return this.cache.has(key);\r\n }\r\n\r\n delete(key: K): boolean {\r\n return this.cache.delete(key);\r\n }\r\n\r\n clear(): void {\r\n this.cache.clear();\r\n }\r\n\r\n get size(): number {\r\n return this.cache.size;\r\n }\r\n}\r\n\r\nfunction getInitialLocation(): LocationState {\r\n const { pathname, search, hash } = window.location;\r\n return { pathname, search, hash };\r\n}\r\n\r\nfunction normalizePath(path: string): LocationState {\r\n const url = new URL(path, \"http://localhost\");\r\n return {\r\n pathname: normalizeUrlPath(url.pathname || \"/\"),\r\n search: url.search,\r\n hash: url.hash,\r\n };\r\n}\r\n\r\n/**\r\n * Fetch RSC payload from server\r\n */\r\nasync function fetchRSCPayload(\r\n pathname: string,\r\n search: string = \"\",\r\n options?: { priority?: RequestPriority }\r\n): Promise<RSCPayload | null> {\r\n const url = `${SERVER.RSC_ENDPOINT}?path=${encodeURIComponent(pathname)}${\r\n search ? `&search=${encodeURIComponent(search)}` : \"\"\r\n }`;\r\n\r\n try {\r\n const fetchOptions: RequestInit = {};\r\n if (options?.priority) {\r\n fetchOptions.priority = options.priority;\r\n }\r\n\r\n const response = await fetch(url, fetchOptions);\r\n if (!response.ok) {\r\n if (response.status === 404) return null; // HTTP_STATUS.NOT_FOUND\r\n throw new Error(`Failed to fetch RSC: ${response.statusText}`);\r\n }\r\n\r\n const rscPayload = await response.json();\r\n return {\r\n html: rscPayload.html,\r\n scripts: rscPayload.scripts || \"\",\r\n styles: rscPayload.styles || \"\",\r\n islandData: rscPayload.islandData || {},\r\n };\r\n } catch (error) {\r\n return null;\r\n }\r\n}\r\n\r\n/**\r\n * Load component for client-side fallback\r\n */\r\nasync function loadComponent(route: ClientRouteLoaded): Promise<any> {\r\n try {\r\n const module = await route.component();\r\n const Component = module.default || module;\r\n\r\n if (!Component) {\r\n throw new Error(ERROR_MESSAGES.COMPONENT_NOT_FOUND(route.path));\r\n }\r\n\r\n return (props: any) => {\r\n const safeProps =\r\n props && typeof props === \"object\" && !Array.isArray(props)\r\n ? props\r\n : { params: {}, searchParams: {} };\r\n\r\n if (typeof Component === \"function\") {\r\n return Component(safeProps);\r\n }\r\n return Component;\r\n };\r\n } catch (error) {\r\n throw error;\r\n }\r\n}\r\n\r\n/**\r\n * Boot islands helper\r\n */\r\nfunction bootIslandsNow() {\r\n bootIslands();\r\n}\r\n\r\n// Global router instance (set by bootstrapClient)\r\nlet globalRouter: FrameworkRouter | null = null;\r\n\r\n/**\r\n * Set global router instance\r\n */\r\nexport function setGlobalRouter(router: FrameworkRouter) {\r\n globalRouter = router;\r\n}\r\n\r\n/**\r\n * Get global router instance\r\n */\r\nexport function getGlobalRouter(): FrameworkRouter | null {\r\n return globalRouter;\r\n}\r\n\r\n/**\r\n * Navigate function that uses framework router if available\r\n * This can be used by Link components or programmatically\r\n */\r\nexport function navigate(to: string, options: { replace?: boolean } = {}) {\r\n const router = getGlobalRouter();\r\n if (router) {\r\n router.navigate(to, options);\r\n } else {\r\n // Fallback to browser navigation if router not initialized\r\n if (options.replace) {\r\n window.history.replaceState({}, \"\", to);\r\n } else {\r\n window.history.pushState({}, \"\", to);\r\n }\r\n window.location.href = to;\r\n }\r\n}\r\n\r\n/**\r\n * Framework-specific router for SPA navigation with RSC\r\n */\r\nexport class FrameworkRouter {\r\n private routes: ClientRouteLoaded[];\r\n private currentLocation: LocationState;\r\n private appContainer: HTMLElement;\r\n private componentCache = new Map<string, any>();\r\n private loadedScripts = new Set<string>(); // Track loaded scripts\r\n private routeStyles = new Map<string, HTMLLinkElement[]>(); // Track route-specific styles\r\n private rscPayloadCache: LRUCache<string, RSCPayload>;\r\n private prefetchPromises = new Map<string, Promise<RSCPayload | null>>();\r\n private prefetchObserver: IntersectionObserver | null = null;\r\n private readonly MAX_CACHE_SIZE = 10;\r\n private invalidatedPaths = new Set<string>(); // Prepared for revalidatePath\r\n private prefetchedLinks = new Set<string>(); // Track links that have been pre-fetched\r\n\r\n constructor(routes: ClientRouteLoaded[], appContainer: HTMLElement) {\r\n this.routes = routes;\r\n this.appContainer = appContainer;\r\n this.currentLocation = getInitialLocation();\r\n this.rscPayloadCache = new LRUCache<string, RSCPayload>(this.MAX_CACHE_SIZE);\r\n\r\n // Set as global router\r\n setGlobalRouter(this);\r\n\r\n this.setupNavigation();\r\n this.setupPopState();\r\n this.setupPrefetch();\r\n }\r\n\r\n /**\r\n * Check if pre-fetch should be performed based on connection\r\n */\r\n private shouldPrefetch(): boolean {\r\n // Check if navigator.connection is available\r\n const connection = (navigator as any).connection || (navigator as any).mozConnection || (navigator as any).webkitConnection;\r\n \r\n if (!connection) {\r\n // If connection API is not available, allow pre-fetch\r\n return true;\r\n }\r\n\r\n // Don't pre-fetch on slow connections\r\n if (connection.effectiveType === 'slow-2g' || connection.effectiveType === '2g') {\r\n return false;\r\n }\r\n\r\n // Don't pre-fetch if data saver is enabled\r\n if (connection.saveData === true) {\r\n return false;\r\n }\r\n\r\n return true;\r\n }\r\n\r\n /**\r\n * Generate cache key from pathname and search\r\n */\r\n private getCacheKey(pathname: string, search: string = \"\"): string {\r\n return `${normalizeUrlPath(pathname)}${search || ''}`;\r\n }\r\n\r\n /**\r\n * Check if href is an internal link\r\n */\r\n private isInternalLink(href: string | null): boolean {\r\n if (!href) return false;\r\n\r\n // Skip external links, mailto, tel, etc.\r\n if (\r\n href.startsWith(\"http\") ||\r\n href.startsWith(\"//\") ||\r\n href.startsWith(\"mailto:\") ||\r\n href.startsWith(\"tel:\")\r\n ) {\r\n return false;\r\n }\r\n\r\n return true;\r\n }\r\n\r\n /**\r\n * Pre-fetch route data (RSC payload and JS component)\r\n */\r\n private async prefetchRoute(pathname: string, search: string = \"\"): Promise<RSCPayload | null> {\r\n // Check if pre-fetch should be performed\r\n if (!this.shouldPrefetch()) {\r\n return null;\r\n }\r\n\r\n const key = this.getCacheKey(pathname, search);\r\n\r\n // Check if path is invalidated\r\n if (this.invalidatedPaths.has(key)) {\r\n return null;\r\n }\r\n\r\n // If already in cache, mark as pre-fetched and return immediately\r\n const cached = this.rscPayloadCache.get(key);\r\n if (cached) {\r\n this.prefetchedLinks.add(key);\r\n return cached;\r\n }\r\n\r\n // If pre-fetch is already in progress, return that promise\r\n if (this.prefetchPromises.has(key)) {\r\n return this.prefetchPromises.get(key)!;\r\n }\r\n\r\n // Start pre-fetch with low priority\r\n const promise = fetchRSCPayload(pathname, search, { priority: 'low' })\r\n .then((payload) => {\r\n if (payload) {\r\n // Cache the payload (LRU handles size limit automatically)\r\n this.rscPayloadCache.set(key, payload);\r\n // Mark as pre-fetched\r\n this.prefetchedLinks.add(key);\r\n\r\n // Pre-load JS component in background if route exists\r\n const route = this.findRoute(pathname);\r\n if (route) {\r\n this.loadComponentCached(route).catch(() => {\r\n // Silently fail - this is just pre-loading\r\n });\r\n }\r\n }\r\n return payload;\r\n })\r\n .catch((error) => {\r\n // Silently fail - pre-fetch should not affect UX\r\n if (typeof console !== 'undefined' && console.debug) {\r\n console.debug('[loly-core] Pre-fetch failed:', error);\r\n }\r\n return null;\r\n })\r\n .finally(() => {\r\n // Clean up promise from map\r\n this.prefetchPromises.delete(key);\r\n });\r\n\r\n this.prefetchPromises.set(key, promise);\r\n return promise;\r\n }\r\n\r\n /**\r\n * Navigate to a new path\r\n */\r\n navigate(to: string, options: { replace?: boolean } = {}) {\r\n const next = normalizePath(to);\r\n\r\n if (options.replace) {\r\n window.history.replaceState({}, \"\", to);\r\n } else {\r\n window.history.pushState({}, \"\", to);\r\n }\r\n\r\n this.currentLocation = next;\r\n this.updateContent();\r\n\r\n // Scroll to top or hash\r\n if (!next.hash) {\r\n window.scrollTo({ top: 0 });\r\n } else {\r\n const el = document.getElementById(next.hash.slice(1));\r\n if (el) {\r\n el.scrollIntoView();\r\n }\r\n }\r\n }\r\n\r\n /**\r\n * Update app content based on current location\r\n */\r\n private async updateContent() {\r\n const { pathname, search } = this.currentLocation;\r\n const key = this.getCacheKey(pathname, search);\r\n\r\n // Check cache first\r\n let rscPayload: RSCPayload | null = this.rscPayloadCache.get(key) || null;\r\n\r\n // If not in cache, fetch normally\r\n if (!rscPayload) {\r\n rscPayload = await fetchRSCPayload(pathname, search);\r\n \r\n // Cache the result if successful\r\n if (rscPayload) {\r\n this.rscPayloadCache.set(key, rscPayload);\r\n }\r\n }\r\n\r\n // Clean up prefetch promise if it exists\r\n this.prefetchPromises.delete(key);\r\n\r\n if (rscPayload) {\r\n // 1. Check if HTML contains islands BEFORE loading component\r\n const hasIslands = rscPayload.html.includes(\"data-loly-island\");\r\n\r\n // 2. Load route component ONLY if there are islands (to register them)\r\n if (hasIslands) {\r\n const route = this.findRoute(pathname);\r\n if (route) {\r\n try {\r\n // Load component to register islands (even though we won't render it)\r\n await this.loadComponentCached(route);\r\n } catch (err) {\r\n console.warn(\r\n \"[loly-core] Failed to load route component for island registration:\",\r\n err\r\n );\r\n }\r\n }\r\n }\r\n\r\n // 3. Inject island data BEFORE updating DOM\r\n if (\r\n rscPayload.islandData &&\r\n Object.keys(rscPayload.islandData).length > 0\r\n ) {\r\n if (!(window as any).__LOLY_ISLAND_DATA__) {\r\n (window as any).__LOLY_ISLAND_DATA__ = {};\r\n }\r\n Object.assign(\r\n (window as any).__LOLY_ISLAND_DATA__,\r\n rscPayload.islandData\r\n );\r\n }\r\n\r\n // 4. Use DocumentFragment for better performance\r\n const fragment = document.createDocumentFragment();\r\n const tempDiv = document.createElement(\"div\");\r\n tempDiv.innerHTML = rscPayload.html;\r\n\r\n // Move all nodes to fragment\r\n while (tempDiv.firstChild) {\r\n fragment.appendChild(tempDiv.firstChild);\r\n }\r\n\r\n // 5. Clear container and append fragment (single DOM operation)\r\n this.appContainer.innerHTML = \"\";\r\n this.appContainer.appendChild(fragment);\r\n\r\n // 6. Inject styles (with deduplication)\r\n if (rscPayload.styles) {\r\n this.injectStyles(pathname, rscPayload.styles);\r\n }\r\n\r\n // 7. Inject scripts (with deduplication)\r\n if (rscPayload.scripts) {\r\n await this.injectScripts(rscPayload.scripts);\r\n }\r\n\r\n // 8. Boot islands after DOM is ready and component is loaded (if needed)\r\n requestAnimationFrame(() => {\r\n requestAnimationFrame(() => {\r\n bootIslandsNow();\r\n // Observe new links in the updated content\r\n this.observeNewLinks(this.appContainer);\r\n });\r\n });\r\n return;\r\n }\r\n\r\n // Fallback: load component and render in client\r\n const route = this.findRoute(pathname);\r\n if (route) {\r\n try {\r\n const Component = await this.loadComponentCached(route);\r\n const searchParams = Object.fromEntries(\r\n new URLSearchParams(search || \"\")\r\n );\r\n const params = extractRouteParams(route.path, pathname);\r\n\r\n // Render component\r\n const result = Component({ params, searchParams });\r\n\r\n // Clear and mount\r\n this.appContainer.innerHTML = \"\";\r\n mount(result, this.appContainer);\r\n\r\n // Boot islands\r\n requestAnimationFrame(() => {\r\n requestAnimationFrame(() => {\r\n bootIslandsNow();\r\n // Activate async components\r\n activateAsyncComponents(this.appContainer);\r\n // Observe new links in the updated content\r\n this.observeNewLinks(this.appContainer);\r\n });\r\n });\r\n } catch (err) {\r\n this.appContainer.innerHTML = \"<div>Error loading route</div>\";\r\n }\r\n } else {\r\n this.appContainer.innerHTML = \"<div>404 - Not Found</div>\";\r\n }\r\n }\r\n\r\n /**\r\n * Inject styles for a route with deduplication\r\n */\r\n private injectStyles(routePath: string, styles: string): void {\r\n // Remove previous route-specific styles (but keep globals.css)\r\n const existing = this.routeStyles.get(routePath);\r\n if (existing) {\r\n existing.forEach((link) => {\r\n // Only remove if it's not globals.css (which should persist)\r\n if (!link.href.includes(\"globals.css\")) {\r\n link.remove();\r\n }\r\n });\r\n }\r\n\r\n if (!styles.trim()) return;\r\n\r\n // Parse link tags from HTML string\r\n const tempDiv = document.createElement(\"div\");\r\n tempDiv.innerHTML = styles.trim();\r\n\r\n const linkElements: HTMLLinkElement[] = [];\r\n const links = tempDiv.querySelectorAll('link[rel=\"stylesheet\"]');\r\n\r\n links.forEach((link) => {\r\n const href = link.getAttribute(\"href\") || \"\";\r\n\r\n // Skip globals.css - it should only be loaded once in initial HTML\r\n if (href.includes(\"globals.css\")) {\r\n return;\r\n }\r\n\r\n const linkEl = document.createElement(\"link\");\r\n linkEl.rel = \"stylesheet\";\r\n linkEl.href = href;\r\n linkEl.setAttribute(\"data-route-styles\", routePath);\r\n\r\n // Check for duplicates by href\r\n const existingLink = document.querySelector(\r\n `link[rel=\"stylesheet\"][href=\"${href}\"]`\r\n );\r\n if (!existingLink) {\r\n document.head.appendChild(linkEl);\r\n linkElements.push(linkEl);\r\n }\r\n });\r\n\r\n if (linkElements.length > 0) {\r\n this.routeStyles.set(routePath, linkElements);\r\n }\r\n }\r\n\r\n /**\r\n * Inject scripts with deduplication\r\n */\r\n private async injectScripts(scripts: string): Promise<void> {\r\n if (!scripts.trim()) return;\r\n\r\n // Parse script tags efficiently\r\n const tempDiv = document.createElement(\"div\");\r\n tempDiv.innerHTML = scripts;\r\n const scriptTags = tempDiv.querySelectorAll(\"script\");\r\n\r\n const loadPromises: Promise<void>[] = [];\r\n\r\n scriptTags.forEach((script) => {\r\n const src = script.getAttribute(\"src\");\r\n\r\n if (src) {\r\n // External script - check if already loaded\r\n if (this.loadedScripts.has(src)) {\r\n return; // Skip if already loaded\r\n }\r\n\r\n this.loadedScripts.add(src);\r\n\r\n // Load external script\r\n const promise = new Promise<void>((resolve, reject) => {\r\n const newScript = document.createElement(\"script\");\r\n newScript.type = \"module\";\r\n newScript.src = src;\r\n newScript.onload = () => resolve();\r\n newScript.onerror = () => {\r\n this.loadedScripts.delete(src); // Remove on error to allow retry\r\n reject(new Error(`Failed to load script: ${src}`));\r\n };\r\n document.head.appendChild(newScript);\r\n });\r\n\r\n loadPromises.push(promise);\r\n } else {\r\n // Inline script - execute directly\r\n const inlineScript = document.createElement(\"script\");\r\n inlineScript.type = \"module\";\r\n inlineScript.textContent = script.textContent || \"\";\r\n document.head.appendChild(inlineScript);\r\n // Remove after execution to avoid memory leaks\r\n setTimeout(() => inlineScript.remove(), 0);\r\n }\r\n });\r\n\r\n // Wait for all external scripts to load\r\n await Promise.all(loadPromises);\r\n }\r\n\r\n /**\r\n * Find matching route for pathname\r\n */\r\n private findRoute(pathname: string): ClientRouteLoaded | null {\r\n // Simple exact match first\r\n const exact = this.routes.find((r) => r.path === pathname);\r\n if (exact) return exact;\r\n\r\n // Try dynamic routes\r\n for (const route of this.routes) {\r\n if (matchesRoute(route.path, pathname)) {\r\n return route;\r\n }\r\n }\r\n\r\n return null;\r\n }\r\n\r\n /**\r\n * Load component with caching\r\n */\r\n private async loadComponentCached(route: ClientRouteLoaded): Promise<any> {\r\n if (this.componentCache.has(route.path)) {\r\n return this.componentCache.get(route.path);\r\n }\r\n\r\n const Component = await loadComponent(route);\r\n this.componentCache.set(route.path, Component);\r\n return Component;\r\n }\r\n\r\n /**\r\n * Setup navigation interception\r\n */\r\n private setupNavigation() {\r\n // Intercept all link clicks\r\n document.addEventListener(\"click\", (e) => {\r\n const target = e.target as HTMLElement;\r\n const link = target.closest(\"a\");\r\n\r\n if (!link) return;\r\n\r\n const href = link.getAttribute(\"href\");\r\n if (!href) return;\r\n\r\n // Skip external links, mailto, tel, etc.\r\n if (\r\n href.startsWith(\"http\") ||\r\n href.startsWith(\"//\") ||\r\n href.startsWith(\"mailto:\") ||\r\n href.startsWith(\"tel:\")\r\n ) {\r\n return;\r\n }\r\n\r\n // Skip if modifier keys are pressed\r\n if (e.metaKey || e.altKey || e.ctrlKey || e.shiftKey || e.button !== 0) {\r\n return;\r\n }\r\n\r\n // Skip if target is not self\r\n if (link.target && link.target !== \"_self\") {\r\n return;\r\n }\r\n\r\n // Skip if already prevented\r\n if (e.defaultPrevented) {\r\n return;\r\n }\r\n\r\n // Prevent default and navigate\r\n e.preventDefault();\r\n this.navigate(href);\r\n });\r\n }\r\n\r\n /**\r\n * Setup popstate handler for browser back/forward\r\n */\r\n private setupPopState() {\r\n window.addEventListener(\"popstate\", () => {\r\n this.currentLocation = getInitialLocation();\r\n this.updateContent();\r\n });\r\n }\r\n\r\n /**\r\n * Observe new links in container (called after DOM updates)\r\n */\r\n private observeNewLinks(container: HTMLElement): void {\r\n if (!this.prefetchObserver) return;\r\n\r\n const links = container.querySelectorAll(\"a[href]\");\r\n links.forEach((link) => {\r\n const href = link.getAttribute(\"href\");\r\n if (href && this.isInternalLink(href)) {\r\n const { pathname, search } = normalizePath(href);\r\n const key = this.getCacheKey(pathname, search);\r\n \r\n // Only observe if not in cache and not already pre-fetched\r\n if (!this.rscPayloadCache.has(key) && !this.prefetchedLinks.has(key)) {\r\n this.prefetchObserver!.observe(link);\r\n }\r\n }\r\n });\r\n }\r\n\r\n /**\r\n * Setup pre-fetch with Intersection Observer and hover\r\n */\r\n private setupPrefetch(): void {\r\n // Create Intersection Observer with 200px margin (pre-fetch before visible)\r\n this.prefetchObserver = new IntersectionObserver(\r\n (entries) => {\r\n entries.forEach((entry) => {\r\n if (entry.isIntersecting) {\r\n const link = entry.target as HTMLAnchorElement;\r\n const href = link.getAttribute(\"href\");\r\n if (href && this.isInternalLink(href)) {\r\n const { pathname, search } = normalizePath(href);\r\n const key = this.getCacheKey(pathname, search);\r\n \r\n // Check if already in cache or in process\r\n if (this.rscPayloadCache.has(key) || this.prefetchPromises.has(key)) {\r\n // Already pre-fetched, disconnect from observer\r\n this.prefetchObserver!.unobserve(link);\r\n this.prefetchedLinks.add(key);\r\n return;\r\n }\r\n \r\n // Check if already pre-fetched\r\n if (this.prefetchedLinks.has(key)) {\r\n this.prefetchObserver!.unobserve(link);\r\n return;\r\n }\r\n \r\n // Mark as pre-fetched and trigger pre-fetch\r\n this.prefetchedLinks.add(key);\r\n this.prefetchRoute(pathname, search).then(() => {\r\n // Disconnect after successful pre-fetch\r\n this.prefetchObserver!.unobserve(link);\r\n });\r\n }\r\n }\r\n });\r\n },\r\n { rootMargin: \"200px\" }\r\n );\r\n\r\n // Observe all existing internal links\r\n const allLinks = document.querySelectorAll(\"a[href]\");\r\n allLinks.forEach((link) => {\r\n const href = link.getAttribute(\"href\");\r\n if (href && this.isInternalLink(href)) {\r\n const { pathname, search } = normalizePath(href);\r\n const key = this.getCacheKey(pathname, search);\r\n \r\n // Only observe if not in cache and not already pre-fetched\r\n if (!this.rscPayloadCache.has(key) && !this.prefetchedLinks.has(key)) {\r\n this.prefetchObserver!.observe(link);\r\n }\r\n }\r\n });\r\n\r\n // Hover listener with debounce (complement for desktop)\r\n let hoverTimeout: ReturnType<typeof setTimeout> | null = null;\r\n document.addEventListener(\r\n \"mouseenter\",\r\n (e) => {\r\n // Verify target is an Element before calling closest\r\n const target = e.target;\r\n if (!target || !(target instanceof Element)) {\r\n return;\r\n }\r\n \r\n const link = target.closest(\"a\");\r\n if (link) {\r\n const href = link.getAttribute(\"href\");\r\n if (href && this.isInternalLink(href)) {\r\n const { pathname, search } = normalizePath(href);\r\n const key = this.getCacheKey(pathname, search);\r\n \r\n // Skip if already in cache or already pre-fetched\r\n if (this.rscPayloadCache.has(key) || this.prefetchedLinks.has(key)) {\r\n return;\r\n }\r\n \r\n // Debounce hover to avoid excessive pre-fetches\r\n if (hoverTimeout) {\r\n clearTimeout(hoverTimeout);\r\n }\r\n hoverTimeout = setTimeout(() => {\r\n // Double-check before pre-fetching (might have been pre-fetched by observer)\r\n if (!this.rscPayloadCache.has(key) && !this.prefetchedLinks.has(key)) {\r\n this.prefetchedLinks.add(key);\r\n this.prefetchRoute(pathname, search);\r\n }\r\n }, 100);\r\n }\r\n }\r\n },\r\n true // Capture phase\r\n );\r\n }\r\n\r\n /**\r\n * Initialize router (call after initial page load)\r\n */\r\n init() {\r\n // With streaming SSR, we need to wait for DOMContentLoaded and add\r\n // a small delay to ensure HTML is fully parsed\r\n const self = this;\r\n \r\n const initIslands = () => {\r\n // Small delay to ensure streaming HTML is fully parsed\r\n setTimeout(() => {\r\n requestAnimationFrame(() => {\r\n requestAnimationFrame(() => {\r\n bootIslandsNow();\r\n // Activate async components\r\n activateAsyncComponents(self.appContainer);\r\n // Observe links in initial DOM\r\n self.observeNewLinks(self.appContainer);\r\n });\r\n });\r\n }, 0);\r\n };\r\n\r\n if (document.readyState === \"loading\") {\r\n document.addEventListener(\"DOMContentLoaded\", initIslands, { once: true });\r\n } else {\r\n // DOM already loaded\r\n initIslands();\r\n }\r\n }\r\n\r\n /**\r\n * Invalidate cache for a specific path (prepared for future revalidatePath implementation)\r\n * TODO: Implementar invalidación completa cuando se implemente revalidatePath\r\n */\r\n public revalidatePath(pathname: string, search: string = \"\"): void {\r\n const key = this.getCacheKey(pathname, search);\r\n \r\n // Add to invalidated paths\r\n this.invalidatedPaths.add(key);\r\n \r\n // Remove from cache\r\n this.rscPayloadCache.delete(key);\r\n \r\n // Remove from prefetch promises if exists\r\n this.prefetchPromises.delete(key);\r\n \r\n // Remove from prefetched links so it can be pre-fetched again\r\n this.prefetchedLinks.delete(key);\r\n }\r\n}\r\n","/**\r\n * Server-related constants\r\n */\r\nexport const SERVER = {\r\n DEFAULT_PORT: 3000,\r\n RSC_ENDPOINT: \"/__loly/rsc\",\r\n IMAGE_ENDPOINT: \"/_loly/image\",\r\n ASYNC_ENDPOINT: \"/__loly/async\",\r\n APP_CONTAINER_ID: \"app\",\r\n} as const;\r\n\r\n","/**\r\n * Error messages used throughout the framework\r\n */\r\nexport const ERROR_MESSAGES = {\r\n CONTEXT_NOT_INITIALIZED:\r\n \"[loly-core] Framework context not initialized. Call setContext() first.\",\r\n APP_DIR_NOT_FOUND: (dir: string) =>\r\n `[loly-core] src/app/ directory not found in ${dir}. Please ensure your project has a src/app/ directory.`,\r\n ROUTE_NOT_FOUND: (pathname: string) => `Route not found: ${pathname}`,\r\n PAGE_COMPONENT_NOT_FOUND: (path: string) =>\r\n `[loly-core] Page component not found in ${path}`,\r\n PAGE_COMPONENT_MUST_BE_FUNCTION:\r\n \"[loly-core] Page component must be a function\",\r\n COMPONENT_NOT_FOUND: (route: string) =>\r\n `Component not found for route: ${route}`,\r\n APP_CONTAINER_NOT_FOUND: \"[loly-core] App container not found (#app)\",\r\n CLIENT_BUILD_DIR_NOT_FOUND: (dir: string) =>\r\n `[loly-core] Client build directory not found: ${dir}. Run 'loly build' first or the client scripts won't load.`,\r\n ROUTES_MANIFEST_NOT_FOUND: (path: string) =>\r\n `[loly-core] Routes manifest not found: ${path}. Run 'loly build' first.`,\r\n DIRECTORY_NOT_FOUND: (dir: string) =>\r\n `[loly-core] Directory not found: ${dir}`,\r\n CLIENT_BUILD_FAILED: \"Client build failed\",\r\n SERVER_BUILD_FAILED: \"Server build failed\",\r\n FAILED_TO_LOAD_MODULE: (path: string) =>\r\n `[loly-core] Failed to load module: ${path}`,\r\n FAILED_TO_LOAD_ROUTE_COMPONENT: (path: string) =>\r\n `[bootstrap] Failed to load component for route ${path}`,\r\n FAILED_TO_LOAD_NESTED_LAYOUT: (path: string) =>\r\n `[loly-core] Failed to load nested layout at ${path}`,\r\n FAILED_TO_READ_CLIENT_MANIFEST: \"[loly-core] Failed to read client manifest:\",\r\n FAILED_TO_PARSE_ISLAND_DATA: \"[loly-core] Failed to parse island data:\",\r\n FATAL_BOOTSTRAP_ERROR: \"[bootstrap] Fatal error during bootstrap:\",\r\n UNEXPECTED_ERROR: \"[loly-core] Unexpected error:\",\r\n ERROR_STARTING_DEV_SERVER: \"[loly-core] Error starting dev server:\",\r\n ERROR_STARTING_PROD_SERVER: \"[loly-core] Error starting production server:\",\r\n ERROR_BUILDING_PROJECT: \"[loly-core] Error building project:\",\r\n ERROR_HANDLING_REQUEST: \"[loly-core] Error handling request:\",\r\n ERROR_RENDERING_PAGE: \"[loly-core] Error rendering page:\",\r\n RSC_ENDPOINT_ERROR: \"[loly-core] RSC endpoint error:\",\r\n} as const;\r\n\r\n","import { resolve } from \"path\";\r\n\r\n/**\r\n * Normalize a path by removing trailing slashes and handling root\r\n */\r\nexport function normalizePath(path: string): string {\r\n if (path === \"/\") return \"/\";\r\n return path.replace(/\\/$/, \"\") || \"/\";\r\n}\r\n\r\n/**\r\n * Normalize a URL pathname (remove trailing slashes, handle root)\r\n */\r\nexport function normalizeUrlPath(pathname: string): string {\r\n return normalizePath(pathname);\r\n}\r\n\r\n/**\r\n * Convert a file path to a file:// URL for dynamic imports\r\n */\r\nexport function resolveModulePath(path: string): string {\r\n const absolutePath = resolve(path);\r\n const normalizedPath = absolutePath.replace(/\\\\/g, \"/\");\r\n \r\n if (normalizedPath.startsWith(\"/\")) {\r\n return `file://${normalizedPath}`;\r\n }\r\n return `file:///${normalizedPath}`;\r\n}\r\n\r\n/**\r\n * Get relative path from one directory to another\r\n */\r\nexport function getRelativePath(from: string, to: string): string {\r\n const { relative } = require(\"path\");\r\n return relative(from, to);\r\n}\r\n\r\n","import { normalizeUrlPath } from \"./path\";\r\n\r\n/**\r\n * Result of a route pattern match\r\n */\r\nexport interface MatchResult {\r\n params: Record<string, string>;\r\n}\r\n\r\n/**\r\n * Match a route pattern against a pathname\r\n * Supports dynamic segments (:param) and catch-all (*param)\r\n */\r\nexport function matchRoutePattern(\r\n pattern: string,\r\n pathname: string\r\n): MatchResult | null {\r\n const normalizedPattern = normalizeUrlPath(pattern);\r\n const normalizedPathname = normalizeUrlPath(pathname);\r\n\r\n // Exact match\r\n if (normalizedPattern === normalizedPathname) {\r\n return { params: {} };\r\n }\r\n\r\n const patternParts = normalizedPattern.split(\"/\").filter(Boolean);\r\n const pathParts = normalizedPathname.split(\"/\").filter(Boolean);\r\n\r\n // Handle root route\r\n if (patternParts.length === 0 && pathParts.length === 0) {\r\n return { params: {} };\r\n }\r\n\r\n const params: Record<string, string> = {};\r\n let patternIdx = 0;\r\n let pathIdx = 0;\r\n\r\n while (patternIdx < patternParts.length && pathIdx < pathParts.length) {\r\n const patternPart = patternParts[patternIdx];\r\n const pathPart = pathParts[pathIdx];\r\n\r\n // Catch-all route (*param)\r\n if (patternPart.startsWith(\"*\")) {\r\n const paramName = patternPart.slice(1);\r\n const remaining = pathParts.slice(pathIdx);\r\n if (paramName) {\r\n params[paramName] = remaining.join(\"/\");\r\n }\r\n return { params };\r\n }\r\n\r\n // Dynamic segment (:param)\r\n if (patternPart.startsWith(\":\")) {\r\n const paramName = patternPart.slice(1);\r\n params[paramName] = decodeURIComponent(pathPart);\r\n patternIdx++;\r\n pathIdx++;\r\n continue;\r\n }\r\n\r\n // Exact match required\r\n if (patternPart === pathPart) {\r\n patternIdx++;\r\n pathIdx++;\r\n continue;\r\n }\r\n\r\n // No match\r\n return null;\r\n }\r\n\r\n // All parts must be consumed\r\n if (patternIdx === patternParts.length && pathIdx === pathParts.length) {\r\n return { params };\r\n }\r\n\r\n return null;\r\n}\r\n\r\n/**\r\n * Extract route parameters from a pathname based on a pattern\r\n */\r\nexport function extractRouteParams(\r\n pattern: string,\r\n pathname: string\r\n): Record<string, string> {\r\n const match = matchRoutePattern(pattern, pathname);\r\n return match?.params || {};\r\n}\r\n\r\n/**\r\n * Normalize a route path (for consistency)\r\n */\r\nexport function normalizeRoutePath(path: string): string {\r\n return normalizeUrlPath(path);\r\n}\r\n\r\n/**\r\n * Check if a pathname matches a route pattern\r\n */\r\nexport function matchesRoute(pattern: string, pathname: string): boolean {\r\n // Handle exact matches\r\n if (pattern === pathname) return true;\r\n\r\n const normalizedPattern = normalizeUrlPath(pattern);\r\n const normalizedPathname = normalizeUrlPath(pathname);\r\n\r\n if (normalizedPattern === normalizedPathname) return true;\r\n\r\n // Handle dynamic routes like /article/:id\r\n // Escape special regex characters except : and *\r\n const escaped = normalizedPattern.replace(/[.+?^${}()|[\\]\\\\]/g, \"\\\\$&\");\r\n const regex = new RegExp(\r\n \"^\" + escaped.replace(/:[^/]+/g, \"([^/]+)\") + \"/?$\"\r\n );\r\n return regex.test(normalizedPathname);\r\n}\r\n\r\n","import { FrameworkRouter } from \"./router\";\r\nimport type { BootstrapOptions } from \"./types\";\r\nimport { SERVER, ERROR_MESSAGES } from \"../constants\";\r\nimport { matchesRoute } from \"../utils/route-matcher\";\r\n\r\n/**\r\n * Load a component (for pre-loading current route to register islands)\r\n */\r\nasync function loadComponent(route: { path: string; component: () => Promise<any> }): Promise<any> {\r\n try {\r\n const module = await route.component();\r\n const Component = module.default || module;\r\n \r\n if (!Component) {\r\n throw new Error(ERROR_MESSAGES.COMPONENT_NOT_FOUND(route.path));\r\n }\r\n\r\n return Component;\r\n } catch (error) {\r\n console.error(ERROR_MESSAGES.FAILED_TO_LOAD_ROUTE_COMPONENT(route.path), error);\r\n throw error;\r\n }\r\n}\r\n\r\n/**\r\n * Bootstrap client application\r\n */\r\nexport function bootstrapClient(options: BootstrapOptions): void {\r\n const { routes } = options;\r\n\r\n const appContainer = document.getElementById(SERVER.APP_CONTAINER_ID);\r\n if (!appContainer) {\r\n console.error(ERROR_MESSAGES.APP_CONTAINER_NOT_FOUND);\r\n return;\r\n }\r\n\r\n // Pre-load current route component to register islands\r\n const currentPath = window.location.pathname;\r\n const currentRoute = routes.find((route) => matchesRoute(route.path, currentPath));\r\n\r\n // Pre-load current route to register islands\r\n if (currentRoute) {\r\n loadComponent(currentRoute)\r\n .then(() => {\r\n // Islands registered, will be booted by router.init()\r\n })\r\n .catch(() => {\r\n // Continue anyway\r\n });\r\n }\r\n\r\n // Create and initialize framework router\r\n const router = new FrameworkRouter(routes, appContainer);\r\n router.init();\r\n}\r\n\r\n"],"mappings":"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","names":["normalizePath","route","loadComponent"]}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { VChild } from 'loly-jsx';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Image component props
|
|
5
|
+
*/
|
|
6
|
+
interface ImageProps {
|
|
7
|
+
src: string;
|
|
8
|
+
alt: string;
|
|
9
|
+
width?: number;
|
|
10
|
+
height?: number;
|
|
11
|
+
priority?: boolean;
|
|
12
|
+
fill?: boolean;
|
|
13
|
+
sizes?: string;
|
|
14
|
+
placeholder?: "blur" | "empty";
|
|
15
|
+
blurDataURL?: string;
|
|
16
|
+
quality?: number;
|
|
17
|
+
format?: "webp" | "avif" | "auto";
|
|
18
|
+
className?: string;
|
|
19
|
+
style?: Record<string, string | number> | string;
|
|
20
|
+
deviceSizes?: number[];
|
|
21
|
+
imageSizes?: number[];
|
|
22
|
+
[key: string]: unknown;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Image component with automatic optimization, lazy loading, and responsive images.
|
|
26
|
+
*
|
|
27
|
+
* Features:
|
|
28
|
+
* - Automatic image optimization via /_loly/image endpoint
|
|
29
|
+
* - Lazy loading by default (unless priority is true)
|
|
30
|
+
* - Responsive images with srcset
|
|
31
|
+
* - Placeholder support (blur)
|
|
32
|
+
* - Fill mode for container-filling images
|
|
33
|
+
*
|
|
34
|
+
* @param props - Image component props
|
|
35
|
+
* @returns Image element
|
|
36
|
+
*/
|
|
37
|
+
declare function Image({ src, alt, width, height, priority, fill, sizes, placeholder, blurDataURL, quality, format, className, deviceSizes, imageSizes, style, ...rest }: ImageProps): VChild;
|
|
38
|
+
|
|
39
|
+
export { Image, type ImageProps };
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
// src/constants/server.ts
|
|
2
|
+
var SERVER = {
|
|
3
|
+
DEFAULT_PORT: 3e3,
|
|
4
|
+
RSC_ENDPOINT: "/__loly/rsc",
|
|
5
|
+
IMAGE_ENDPOINT: "/_loly/image",
|
|
6
|
+
ASYNC_ENDPOINT: "/__loly/async",
|
|
7
|
+
APP_CONTAINER_ID: "app"
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
// src/client/components/Image/index.tsx
|
|
11
|
+
import { jsx, jsxs } from "loly-jsx/jsx-runtime";
|
|
12
|
+
function getOptimizedImageUrl(src, width, height, quality, format) {
|
|
13
|
+
const params = new URLSearchParams();
|
|
14
|
+
params.set("src", src);
|
|
15
|
+
if (width) params.set("w", width.toString());
|
|
16
|
+
if (height) params.set("h", height.toString());
|
|
17
|
+
if (quality) params.set("q", quality.toString());
|
|
18
|
+
if (format && format !== "auto") params.set("format", format);
|
|
19
|
+
return `${SERVER.IMAGE_ENDPOINT}?${params.toString()}`;
|
|
20
|
+
}
|
|
21
|
+
function generateSrcSet(src, sizes, height, quality, format) {
|
|
22
|
+
return sizes.map((size) => {
|
|
23
|
+
const url = getOptimizedImageUrl(src, size, height, quality, format);
|
|
24
|
+
return `${url} ${size}w`;
|
|
25
|
+
}).join(", ");
|
|
26
|
+
}
|
|
27
|
+
function Image({
|
|
28
|
+
src,
|
|
29
|
+
alt,
|
|
30
|
+
width,
|
|
31
|
+
height,
|
|
32
|
+
priority = false,
|
|
33
|
+
fill = false,
|
|
34
|
+
sizes,
|
|
35
|
+
placeholder,
|
|
36
|
+
blurDataURL,
|
|
37
|
+
quality,
|
|
38
|
+
format,
|
|
39
|
+
className,
|
|
40
|
+
deviceSizes,
|
|
41
|
+
imageSizes,
|
|
42
|
+
style,
|
|
43
|
+
...rest
|
|
44
|
+
}) {
|
|
45
|
+
const defaultDeviceSizes = deviceSizes || [640, 750, 828, 1080, 1200, 1920, 2048, 3840];
|
|
46
|
+
const defaultImageSizes = imageSizes || [16, 32, 48, 64, 96, 128, 256, 384];
|
|
47
|
+
if (!fill && (!width || !height)) {
|
|
48
|
+
if (typeof process !== "undefined" && process.env?.NODE_ENV === "development") {
|
|
49
|
+
console.warn(
|
|
50
|
+
"[Image] width and height are required when fill is false. This helps prevent layout shift (CLS)."
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
const optimizedSrc = getOptimizedImageUrl(src, width, height, quality, format);
|
|
55
|
+
const srcSetSizes = width ? defaultDeviceSizes.filter((s) => s <= width * 2) : defaultImageSizes;
|
|
56
|
+
const srcSet = srcSetSizes.length > 0 ? generateSrcSet(src, srcSetSizes, height, quality, format) : void 0;
|
|
57
|
+
const imageStyle = typeof style === "string" ? {} : { ...style || {} };
|
|
58
|
+
if (fill) {
|
|
59
|
+
imageStyle.position = "absolute";
|
|
60
|
+
imageStyle.height = "100%";
|
|
61
|
+
imageStyle.width = "100%";
|
|
62
|
+
imageStyle.objectFit = "cover";
|
|
63
|
+
imageStyle.objectPosition = "center";
|
|
64
|
+
imageStyle.top = 0;
|
|
65
|
+
imageStyle.left = 0;
|
|
66
|
+
} else {
|
|
67
|
+
if (width) imageStyle.width = width;
|
|
68
|
+
if (height) imageStyle.height = height;
|
|
69
|
+
}
|
|
70
|
+
if (!fill && width && height) {
|
|
71
|
+
const aspectRatio = height / width * 100;
|
|
72
|
+
const containerStyle = {
|
|
73
|
+
display: "block",
|
|
74
|
+
position: "relative",
|
|
75
|
+
width,
|
|
76
|
+
maxWidth: "100%"
|
|
77
|
+
};
|
|
78
|
+
const spacerStyle = {
|
|
79
|
+
display: "block",
|
|
80
|
+
paddingBottom: `${aspectRatio}%`
|
|
81
|
+
};
|
|
82
|
+
return /* @__PURE__ */ jsxs("span", { style: containerStyle, className, children: [
|
|
83
|
+
/* @__PURE__ */ jsx("span", { style: spacerStyle }),
|
|
84
|
+
placeholder === "blur" && blurDataURL && /* @__PURE__ */ jsx(
|
|
85
|
+
"img",
|
|
86
|
+
{
|
|
87
|
+
src: blurDataURL,
|
|
88
|
+
alt: "",
|
|
89
|
+
"aria-hidden": "true",
|
|
90
|
+
style: {
|
|
91
|
+
position: "absolute",
|
|
92
|
+
top: 0,
|
|
93
|
+
left: 0,
|
|
94
|
+
width: "100%",
|
|
95
|
+
height: "100%",
|
|
96
|
+
objectFit: "cover",
|
|
97
|
+
filter: "blur(20px)",
|
|
98
|
+
transform: "scale(1.1)"
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
),
|
|
102
|
+
/* @__PURE__ */ jsx(
|
|
103
|
+
"img",
|
|
104
|
+
{
|
|
105
|
+
src: optimizedSrc,
|
|
106
|
+
alt,
|
|
107
|
+
width,
|
|
108
|
+
height,
|
|
109
|
+
srcSet,
|
|
110
|
+
sizes,
|
|
111
|
+
loading: priority ? "eager" : "lazy",
|
|
112
|
+
decoding: "async",
|
|
113
|
+
style: {
|
|
114
|
+
...imageStyle,
|
|
115
|
+
position: "absolute",
|
|
116
|
+
top: 0,
|
|
117
|
+
left: 0,
|
|
118
|
+
height: "100%",
|
|
119
|
+
width: "100%"
|
|
120
|
+
},
|
|
121
|
+
...rest
|
|
122
|
+
}
|
|
123
|
+
)
|
|
124
|
+
] });
|
|
125
|
+
}
|
|
126
|
+
const finalStyle = typeof style === "string" ? style : imageStyle;
|
|
127
|
+
return /* @__PURE__ */ jsx(
|
|
128
|
+
"img",
|
|
129
|
+
{
|
|
130
|
+
src: optimizedSrc,
|
|
131
|
+
alt,
|
|
132
|
+
width,
|
|
133
|
+
height,
|
|
134
|
+
srcSet,
|
|
135
|
+
sizes,
|
|
136
|
+
loading: priority ? "eager" : "lazy",
|
|
137
|
+
decoding: "async",
|
|
138
|
+
className,
|
|
139
|
+
style: finalStyle,
|
|
140
|
+
...rest
|
|
141
|
+
}
|
|
142
|
+
);
|
|
143
|
+
}
|
|
144
|
+
export {
|
|
145
|
+
Image
|
|
146
|
+
};
|
|
147
|
+
//# sourceMappingURL=components.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/constants/server.ts","../src/client/components/Image/index.tsx"],"sourcesContent":["/**\r\n * Server-related constants\r\n */\r\nexport const SERVER = {\r\n DEFAULT_PORT: 3000,\r\n RSC_ENDPOINT: \"/__loly/rsc\",\r\n IMAGE_ENDPOINT: \"/_loly/image\",\r\n ASYNC_ENDPOINT: \"/__loly/async\",\r\n APP_CONTAINER_ID: \"app\",\r\n} as const;\r\n\r\n","import type { VChild } from \"loly-jsx\";\r\nimport { SERVER } from \"../../../constants/server\";\r\n\r\n/**\r\n * Image component props\r\n */\r\nexport interface ImageProps {\r\n src: string;\r\n alt: string;\r\n width?: number;\r\n height?: number;\r\n priority?: boolean;\r\n fill?: boolean;\r\n sizes?: string;\r\n placeholder?: \"blur\" | \"empty\";\r\n blurDataURL?: string;\r\n quality?: number;\r\n format?: \"webp\" | \"avif\" | \"auto\";\r\n className?: string;\r\n style?: Record<string, string | number> | string;\r\n // Device sizes for srcset generation (defaults if not provided)\r\n deviceSizes?: number[];\r\n // Image sizes for srcset generation (defaults if not provided)\r\n imageSizes?: number[];\r\n // Additional HTML attributes\r\n [key: string]: unknown;\r\n}\r\n\r\n/**\r\n * Generates an optimized image URL.\r\n */\r\nfunction getOptimizedImageUrl(\r\n src: string,\r\n width?: number,\r\n height?: number,\r\n quality?: number,\r\n format?: \"webp\" | \"avif\" | \"auto\"\r\n): string {\r\n const params = new URLSearchParams();\r\n params.set(\"src\", src);\r\n\r\n if (width) params.set(\"w\", width.toString());\r\n if (height) params.set(\"h\", height.toString());\r\n if (quality) params.set(\"q\", quality.toString());\r\n if (format && format !== \"auto\") params.set(\"format\", format);\r\n\r\n return `${SERVER.IMAGE_ENDPOINT}?${params.toString()}`;\r\n}\r\n\r\n/**\r\n * Generates srcset for responsive images.\r\n */\r\nfunction generateSrcSet(\r\n src: string,\r\n sizes: number[],\r\n height?: number,\r\n quality?: number,\r\n format?: \"webp\" | \"avif\" | \"auto\"\r\n): string {\r\n return sizes\r\n .map((size) => {\r\n const url = getOptimizedImageUrl(src, size, height, quality, format);\r\n return `${url} ${size}w`;\r\n })\r\n .join(\", \");\r\n}\r\n\r\n/**\r\n * Image component with automatic optimization, lazy loading, and responsive images.\r\n *\r\n * Features:\r\n * - Automatic image optimization via /_loly/image endpoint\r\n * - Lazy loading by default (unless priority is true)\r\n * - Responsive images with srcset\r\n * - Placeholder support (blur)\r\n * - Fill mode for container-filling images\r\n *\r\n * @param props - Image component props\r\n * @returns Image element\r\n */\r\nexport function Image({\r\n src,\r\n alt,\r\n width,\r\n height,\r\n priority = false,\r\n fill = false,\r\n sizes,\r\n placeholder,\r\n blurDataURL,\r\n quality,\r\n format,\r\n className,\r\n deviceSizes,\r\n imageSizes,\r\n style,\r\n ...rest\r\n}: ImageProps): VChild {\r\n // Default device sizes (matching Next.js defaults)\r\n const defaultDeviceSizes = deviceSizes || [640, 750, 828, 1080, 1200, 1920, 2048, 3840];\r\n const defaultImageSizes = imageSizes || [16, 32, 48, 64, 96, 128, 256, 384];\r\n\r\n // Validate props\r\n if (!fill && (!width || !height)) {\r\n if (typeof process !== \"undefined\" && process.env?.NODE_ENV === \"development\") {\r\n console.warn(\r\n \"[Image] width and height are required when fill is false. \" +\r\n \"This helps prevent layout shift (CLS).\"\r\n );\r\n }\r\n }\r\n\r\n // Generate optimized image URL\r\n const optimizedSrc = getOptimizedImageUrl(src, width, height, quality, format);\r\n\r\n // Generate srcset for responsive images\r\n // Use deviceSizes if width is provided, otherwise use imageSizes\r\n const srcSetSizes = width\r\n ? defaultDeviceSizes.filter((s) => s <= (width * 2))\r\n : defaultImageSizes;\r\n const srcSet =\r\n srcSetSizes.length > 0\r\n ? generateSrcSet(src, srcSetSizes, height, quality, format)\r\n : undefined;\r\n\r\n // Build styles - loly-jsx accepts style objects directly\r\n const imageStyle: Record<string, string | number> =\r\n typeof style === \"string\" ? {} : { ...(style || {}) };\r\n\r\n if (fill) {\r\n imageStyle.position = \"absolute\";\r\n imageStyle.height = \"100%\";\r\n imageStyle.width = \"100%\";\r\n imageStyle.objectFit = \"cover\";\r\n imageStyle.objectPosition = \"center\";\r\n imageStyle.top = 0;\r\n imageStyle.left = 0;\r\n } else {\r\n if (width) imageStyle.width = width;\r\n if (height) imageStyle.height = height;\r\n }\r\n\r\n // Aspect ratio container to prevent CLS (only if width and height are provided)\r\n if (!fill && width && height) {\r\n const aspectRatio = (height / width) * 100;\r\n const containerStyle: Record<string, string | number> = {\r\n display: \"block\",\r\n position: \"relative\",\r\n width: width,\r\n maxWidth: \"100%\",\r\n };\r\n const spacerStyle: Record<string, string> = {\r\n display: \"block\",\r\n paddingBottom: `${aspectRatio}%`,\r\n };\r\n\r\n return (\r\n <span style={containerStyle} className={className}>\r\n <span style={spacerStyle} />\r\n {placeholder === \"blur\" && blurDataURL && (\r\n <img\r\n src={blurDataURL}\r\n alt=\"\"\r\n aria-hidden=\"true\"\r\n style={{\r\n position: \"absolute\",\r\n top: 0,\r\n left: 0,\r\n width: \"100%\",\r\n height: \"100%\",\r\n objectFit: \"cover\",\r\n filter: \"blur(20px)\",\r\n transform: \"scale(1.1)\",\r\n }}\r\n />\r\n )}\r\n <img\r\n src={optimizedSrc}\r\n alt={alt}\r\n width={width}\r\n height={height}\r\n srcSet={srcSet}\r\n sizes={sizes}\r\n loading={priority ? \"eager\" : \"lazy\"}\r\n decoding=\"async\"\r\n style={{\r\n ...imageStyle,\r\n position: \"absolute\",\r\n top: 0,\r\n left: 0,\r\n height: \"100%\",\r\n width: \"100%\",\r\n }}\r\n {...rest}\r\n />\r\n </span>\r\n );\r\n }\r\n\r\n // Simple case without aspect ratio container\r\n // Handle style prop - can be string or object\r\n const finalStyle =\r\n typeof style === \"string\" ? style : imageStyle;\r\n\r\n return (\r\n <img\r\n src={optimizedSrc}\r\n alt={alt}\r\n width={width}\r\n height={height}\r\n srcSet={srcSet}\r\n sizes={sizes}\r\n loading={priority ? \"eager\" : \"lazy\"}\r\n decoding=\"async\"\r\n className={className}\r\n style={finalStyle}\r\n {...rest}\r\n />\r\n );\r\n}\r\n\r\n"],"mappings":"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","names":[]}
|