@absolutejs/absolute 0.19.0-beta.169 → 0.19.0-beta.170

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.
@@ -25,7 +25,7 @@
25
25
  "src/cli/scripts/info.ts": "1064snpc2kiy1",
26
26
  "src/cli/scripts/telemetry.ts": "2x0orrt82x06b",
27
27
  "src/cli/scripts/start.ts": "e8njhtkgh3tj",
28
- "src/cli/scripts/dev.ts": "1ao8dza0qhpbz",
28
+ "src/cli/scripts/dev.ts": "2cl20x8678dqh",
29
29
  "src/cli/scripts/eslint.ts": "25o7waeno205q",
30
30
  "src/cli/scripts/prettier.ts": "fxlaao2ialmq",
31
31
  "src/svelte/pageHandler.ts": "3lttqsq105iy",
@@ -157,7 +157,7 @@
157
157
  "types/build.ts": "8kjght2fsmik",
158
158
  "types/typeGuards.ts": "1xjkc71v0qfvp",
159
159
  "types/svelte.ts": "2y2l15nmf0gna",
160
- "example/server.ts": "2r5egolsf3qr8",
160
+ "example/server.ts": "2l2vf9qrrfpu8",
161
161
  "example/absolute.config.ts": "27kjp1g3njuw",
162
162
  "example/vueImporter.ts": "3pz876k5csl5o",
163
163
  "example/html/scripts/typescript-example.ts": "16hry8jgdyowr",
@@ -174,7 +174,7 @@
174
174
  "example/react/components/App.tsx": "sio6k26ozh68",
175
175
  "eslint.config.mjs": "rb90j2qpxvx8",
176
176
  "tsconfig.json": "3pf54bnsv1pxc",
177
- "package.json": "1t8tqppzx82ek",
177
+ "package.json": "1y8a9q4rxz3bn",
178
178
  "tsconfig.build.json": "976e92rva922",
179
179
  "native/packages/windows-arm64/package.json": "193jwo8usmsb3",
180
180
  "native/packages/darwin-x64/package.json": "1ygrye02o9pav",
package/ROADMAP.md ADDED
@@ -0,0 +1,582 @@
1
+ # AbsoluteJS Roadmap — Next.js Feature Parity
2
+
3
+ Features missing from AbsoluteJS that Next.js provides, ordered by priority. Each entry includes what Next.js does, what AbsoluteJS currently has, and what needs to be built.
4
+
5
+ ---
6
+
7
+ ## P0 — Static Site Generation (SSG)
8
+
9
+ **What Next.js does:**
10
+ Pages can be pre-rendered at build time into static HTML. `generateStaticParams()` tells the framework which dynamic routes to pre-render. The output is plain HTML + JS that can be served from a CDN with zero server runtime. Next.js also supports a full `output: 'export'` mode that produces a completely static site.
11
+
12
+ **What AbsoluteJS has today:**
13
+ Nothing. Every page is rendered at request time via streaming SSR. The `handleHTMLPageRequest` serves pre-written HTML files, but there's no mechanism to run a React/Svelte/Vue component through SSR at build time and save the output.
14
+
15
+ **What needs to be built:**
16
+ - A `static` option per-route or per-page that tells the build to render the component and write the HTML to disk
17
+ - For dynamic routes, a way to declare the set of params to pre-render (equivalent to `generateStaticParams`)
18
+ - A static export mode that produces a directory of HTML/CSS/JS with no server dependency
19
+ - The build pipeline already has streaming SSR for all frameworks — the core work is calling those renderers during `build()` instead of at request time, and writing the output to files
20
+ - Static pages should still hydrate on the client (same as today, just the initial HTML comes from disk instead of runtime SSR)
21
+
22
+ **Files likely involved:**
23
+ - `src/core/build.ts` — add a static rendering pass after bundling
24
+ - Each framework's `pageHandler.ts` — extract the rendering logic so it can be called at build time without an HTTP request
25
+ - `src/build/generateManifest.ts` — static pages need manifest entries pointing to `.html` files
26
+ - New: a config option or per-page export to opt into static rendering
27
+
28
+ ---
29
+
30
+ ## P0 — Metadata API / SEO
31
+
32
+ **What Next.js does:**
33
+ Pages export a `metadata` object or `generateMetadata()` async function that defines title, description, Open Graph tags, Twitter cards, canonical URLs, etc. Next.js also supports file-based conventions: `sitemap.ts`, `robots.ts`, `opengraph-image.tsx` that auto-generate SEO assets. The metadata merges and deduplicates across layouts.
34
+
35
+ **What AbsoluteJS has today:**
36
+ `generateHeadElement()` utility that produces a `<head>` string with title, description, favicon, Google Fonts, and CSS paths. Vue and Angular page handlers accept a `headTag` parameter. React pages build their own `<Head>` component manually.
37
+
38
+ **What needs to be built:**
39
+ - A richer metadata type that covers Open Graph (`og:title`, `og:image`, `og:description`, `og:url`), Twitter cards (`twitter:card`, `twitter:title`, `twitter:image`), canonical URL, robots directives, and arbitrary meta tags
40
+ - Update `generateHeadElement()` to accept and render all of these
41
+ - A `sitemap.ts` convention or helper that generates `/sitemap.xml` from the route list
42
+ - A `robots.ts` convention or helper that generates `/robots.txt`
43
+ - JSON-LD / structured data helper for rich search results
44
+
45
+ **Files likely involved:**
46
+ - `src/utils/generateHeadElement.ts` — expand the metadata type and rendering
47
+ - `types/build.ts` or new `types/metadata.ts` — define the metadata type
48
+ - New: `src/utils/generateSitemap.ts`, `src/utils/generateRobots.ts`
49
+
50
+ ---
51
+
52
+ ## P1 — Image Optimization
53
+
54
+ **What Next.js does:**
55
+ `<Image>` component that automatically converts images to WebP/AVIF, generates responsive `srcset` attributes, lazy loads with blur placeholders, and serves optimized images through an on-demand image optimization API route. Prevents layout shift with required width/height.
56
+
57
+ **What AbsoluteJS has today:**
58
+ Nothing. Images are served as-is from the public/assets directory.
59
+
60
+ **What needs to be built:**
61
+ - An `<Image>` component (React version at minimum, ideally per-framework) that renders responsive `<img>` tags with `srcset`, `sizes`, `loading="lazy"`, and `width`/`height` for CLS prevention
62
+ - An image optimization endpoint or build-time processor that converts to WebP/AVIF and generates multiple sizes
63
+ - Sharp or libvips integration for the actual image processing (Sharp works with Bun)
64
+ - Caching layer for optimized images so they're only processed once
65
+ - Optional blur placeholder generation (tiny base64 inline preview)
66
+
67
+ **Files likely involved:**
68
+ - New: `src/react/components/Image.tsx`, and equivalents for other frameworks
69
+ - New: `src/plugins/imageOptimizer.ts` — Elysia plugin that handles `/image?url=...&w=...&q=...` requests
70
+ - New: `src/build/optimizeImages.ts` — optional build-time optimization pass
71
+
72
+ ---
73
+
74
+ ## P1 — Loading / Error / Not-Found States
75
+
76
+ **What Next.js does:**
77
+ Per-route-segment `loading.tsx` (shows during async data fetch), `error.tsx` (catches runtime errors with React error boundary), and `not-found.tsx` (404 page). These are automatic — drop the file in and it works.
78
+
79
+ **What AbsoluteJS has today:**
80
+ - Dev error overlay (`src/dev/client/errorOverlay.ts`) for compilation/runtime errors in development
81
+ - SSR error page (`src/utils/ssrErrorPage.ts`) that returns a styled error page when server rendering fails
82
+ - No production error boundaries, no loading states, no 404 handling
83
+
84
+ **What needs to be built:**
85
+ - A documented pattern for error boundaries per framework (React has `ErrorBoundary`, Vue has `onErrorCaptured`, Svelte has `<svelte:boundary>`, Angular has `ErrorHandler`)
86
+ - A helper or wrapper that each page handler can use to catch SSR errors and render a user-defined error page instead of the generic one
87
+ - A loading state pattern — for React this means Suspense boundaries with fallback UI; for streaming SSR, sending the shell immediately and streaming content as it resolves
88
+ - A 404 handler — an Elysia catch-all route that renders a user-defined not-found page
89
+ - Framework-specific examples showing how to wire each of these up
90
+
91
+ **Files likely involved:**
92
+ - `src/utils/ssrErrorPage.ts` — make it accept a user-defined error component
93
+ - Each framework's `pageHandler.ts` — add error/loading handling to the streaming pipeline
94
+ - New: `src/utils/notFoundPage.ts` or a convention for 404 pages
95
+ - Example directory — add error/loading/not-found examples
96
+
97
+ ---
98
+
99
+ ## P1 — Client-Side Navigation / SPA Mode with `<Link>`
100
+
101
+ **What Next.js does:**
102
+ `<Link>` component that intercepts clicks and does client-side navigation — fetches only the new page's data/RSC payload, swaps the content, and preserves layout state (scroll position, open menus, form inputs). Prefetches linked pages on hover or when they enter the viewport. This is what makes Next.js apps feel like SPAs even though they're server-rendered.
103
+
104
+ **What AbsoluteJS has today:**
105
+ Plain `<a>` tags. Every navigation is a full page load — the browser tears down the entire DOM, re-requests the HTML, and re-hydrates from scratch. Shared UI like navbars and sidebars re-mount every time.
106
+
107
+ **Why this matters:**
108
+ Without client-side navigation, layouts are just a component pattern (wrap your page in a shared component — users can do this today). WITH client-side navigation, layouts become persistent — the navbar stays mounted, sidebar scroll position is preserved, and only the page content swaps. This is the single feature that turns a server-rendered app into an SPA experience.
109
+
110
+ **Architecture: One shared navigation module, thin framework wrappers**
111
+
112
+ React Router and similar client-side routers can't help here — they swap client-side components, but AbsoluteJS pages are server-rendered. The navigation module needs to fetch server-rendered HTML and swap it into the DOM. This is fundamentally framework-agnostic — the DOM swap logic is identical whether the page is React, Svelte, Vue, or Angular. So the right design is:
113
+
114
+ 1. **One shared navigation module** (`navigate.ts`) that handles all the core logic: intercept clicks, fetch partial HTML, swap DOM content, manage history, prefetch. This runs as plain JS on every page regardless of framework.
115
+ 2. **Thin per-framework `<Link>` wrappers** that just render an `<a>` tag with the right attributes. The shared module picks up all `<a>` tags with a `data-link` attribute (or all internal links by default) — the framework components are just ergonomic sugar so users don't have to remember the attribute.
116
+
117
+ **What needs to be built:**
118
+
119
+ *Shared navigation module (`navigate.ts`):*
120
+ - Intercept clicks on internal `<a>` tags (skip external links, `target="_blank"`, modifier keys)
121
+ - `fetch()` the URL with an `X-AbsoluteJS-Nav: partial` header
122
+ - Receive partial HTML (just the `<main>` content, not the full document)
123
+ - Swap the `<main>` innerHTML with the new content
124
+ - Load any new CSS before swapping to avoid FOUC
125
+ - `history.pushState()` on navigate, handle `popstate` for back/forward
126
+ - Hydrate the new content after swap (call the framework's hydration entry point)
127
+ - View Transitions API for smooth animated swaps (already used in Angular HMR)
128
+
129
+ *Prefetching:*
130
+ - On `mouseenter` (default) — prefetch the page so it's ready on click
131
+ - On viewport intersection (opt-in via `data-prefetch="viewport"`) — for visible links
132
+ - `data-prefetch="none"` to disable
133
+ - Cache prefetched responses in a Map to avoid duplicate fetches
134
+ - Smart limits — don't prefetch more than N pages at once
135
+
136
+ *Server-side partial rendering:*
137
+ - An Elysia `onBeforeHandle` hook that checks for the `X-AbsoluteJS-Nav: partial` header
138
+ - When present, each framework's page handler skips the outer `<html>/<head>/<body>` shell and returns only the inner page content (the part inside `<main>`)
139
+ - Also returns metadata in a response header or JSON wrapper: page title, CSS paths, framework type, hydration entry point
140
+
141
+ *Per-framework `<Link>` wrappers:*
142
+ - React: `<Link href="/about">About</Link>` → renders `<a href="/about">About</a>` with the right attributes. Thin component, no routing logic.
143
+ - Svelte: `<Link href="/about">About</Link>` → same thing
144
+ - Vue: `<Link href="/about">About</Link>` → same thing
145
+ - Angular: `<a absLink href="/about">About</a>` directive → same thing
146
+ - All wrappers accept `prefetch` prop (`"hover"` | `"viewport"` | `"none"`)
147
+
148
+ *Cross-framework navigation:*
149
+ - Same-framework navigations: swap `<main>`, hydrate with the same runtime. Fast.
150
+ - Cross-framework navigations (React page → Svelte page): need to tear down the old framework's hydration and bootstrap the new one. The partial response includes the framework type so the navigation module knows which hydration to call. May need to swap `<body>` instead of just `<main>` if the framework runtimes conflict.
151
+ - Fallback: if cross-framework swap is too complex initially, just do a full page load for cross-framework links. Still use View Transitions for visual continuity.
152
+
153
+ **Design considerations:**
154
+ - Progressive enhancement — the `<Link>` renders a real `<a>` tag with a real `href`. If JS fails, it's a normal link. If the partial fetch errors, fall back to full navigation. No JS-only routes.
155
+ - The shared module should be small (<5KB) and loaded on every page as part of the AbsoluteJS client runtime, alongside the HMR client in dev.
156
+ - CSS handling: the partial response should include which stylesheets the new page needs. The navigation module loads them before swapping to prevent FOUC. Stylesheets shared between pages stay loaded.
157
+ - Scroll behavior: scroll to top on navigation by default, restore scroll position on back/forward via `scrollRestoration: 'manual'`.
158
+
159
+ **Files likely involved:**
160
+ - New: `src/client/navigate.ts` — shared framework-agnostic navigation module (click interception, fetch, DOM swap, history, prefetch, View Transitions)
161
+ - New: `src/react/components/Link.tsx` — thin React wrapper
162
+ - New: `src/svelte/Link.svelte` — thin Svelte wrapper
163
+ - New: `src/vue/Link.vue` — thin Vue wrapper
164
+ - New: `src/angular/link.directive.ts` — thin Angular directive
165
+ - New: `src/plugins/navigation.ts` — Elysia plugin that detects `X-AbsoluteJS-Nav: partial` header and adjusts response
166
+ - Each framework's `pageHandler.ts` — add partial rendering mode that returns only inner content + metadata
167
+ - `src/dev/client/hmrClient.ts` — ensure HMR reconnects correctly after client-side navigation
168
+
169
+ ---
170
+
171
+ ## P1 — Islands Architecture (Multi-Framework Pages)
172
+
173
+ **What Astro does:**
174
+ Pages are rendered as static HTML with interactive "islands" — individual components that hydrate independently. Each island can be a different framework (React, Svelte, Vue, etc.) on the same page. Hydration is controlled with directives: `client:load` (immediate), `client:idle` (requestIdleCallback), `client:visible` (IntersectionObserver). Non-interactive content ships zero JS.
175
+
176
+ **What AbsoluteJS has today:**
177
+ Each page is owned by one framework. A React page is fully React, a Svelte page is fully Svelte. All the framework SSR renderers, vendor bundles, and hydration entry points already exist — but they can't be mixed on a single page.
178
+
179
+ **Why AbsoluteJS is uniquely positioned:**
180
+ No other meta-framework has SSR renderers, build pipelines, and vendor bundles for React, Svelte, Vue, and Angular already running in the same process. Astro supports islands but the host page uses Astro's own template language — you can't use a React component as the page shell with Svelte islands inside it. AbsoluteJS can because the framework renderers are already first-class.
181
+
182
+ **What needs to be built:**
183
+
184
+ *Island rendering (SSR side):*
185
+ - An `island()` function that takes a component, its framework type, and props, and returns the server-rendered HTML wrapped in a marker element:
186
+ ```html
187
+ <div data-island="react" data-component="Chart" data-props="..." data-hydrate="load">
188
+ <!-- SSR'd React HTML -->
189
+ </div>
190
+ ```
191
+ - The page handler renders the host page first, then renders each island with its framework's SSR renderer and injects the HTML into the marker elements
192
+ - For streaming: the page shell streams immediately, islands can resolve asynchronously and stream in as they complete (similar to React Suspense boundaries)
193
+
194
+ *Island hydration (client side):*
195
+ - A small island runtime (~2-3KB) that runs on page load and finds all `data-island` elements
196
+ - For each island, loads the framework vendor bundle and component module, then hydrates that specific DOM subtree
197
+ - Hydration directives control timing:
198
+ - `client:load` — hydrate immediately on page load
199
+ - `client:idle` — hydrate during `requestIdleCallback` (after main thread is free)
200
+ - `client:visible` — hydrate when the island scrolls into the viewport (`IntersectionObserver`)
201
+ - `client:none` — never hydrate (static HTML only, zero JS for that island)
202
+ - Each island hydrates independently — React hydrates its div, Svelte mounts into its div, Vue creates its app on its div. They don't interfere with each other.
203
+
204
+ *Shared state between islands (zustand/vanilla):*
205
+ - Use `zustand/vanilla` as an internal dependency (~1KB, no framework coupling) for cross-island state
206
+ - AbsoluteJS exposes a simple API per framework — users never see zustand:
207
+ - React: `useIslandState(key)` hook — wraps zustand subscribe with `useSyncExternalStore`
208
+ - Svelte: `getIslandState(key)` — returns a Svelte-compatible readable store backed by zustand subscribe
209
+ - Vue: `useIslandState(key)` composable — returns a `ref` that syncs via zustand subscribe
210
+ - Angular: `IslandState` injectable service — wraps zustand subscribe in an Observable
211
+ - State is initialized on the server and serialized to `window.__ISLAND_STATE__` alongside `window.__INITIAL_PROPS__`
212
+ - Islands that share state subscribe to the same zustand store keys and stay in sync automatically
213
+
214
+ *Build pipeline changes:*
215
+ - Island components need to be compiled and bundled individually (not as part of a full-page bundle)
216
+ - Each island becomes its own entry point so the browser only loads the framework code for islands actually on the page
217
+ - The manifest needs to track island components: `{ "ReactChart": "/islands/react-chart.hash.js", ... }`
218
+ - Framework vendor bundles are loaded on-demand — if a page has only React and Svelte islands, Vue and Angular vendors are never loaded
219
+
220
+ *Type-safe island registry — instant IDE autocomplete, no codegen:*
221
+
222
+ Type safety comes from a user-maintained registry file. The user defines which island components exist per framework and what their props are. AbsoluteJS provides `defineIslandRegistry()` for validation and an `IslandRegistry` type that the `<Island>` components read from. This gives full IDE autocomplete immediately — no build step needed.
223
+
224
+ **The registry file (user creates once, updates as islands are added):**
225
+ ```ts
226
+ // islands/registry.ts
227
+ import { defineIslandRegistry } from 'absolutejs'
228
+ import SvelteForm from './SvelteForm.svelte'
229
+ import ContactForm from './ContactForm.svelte'
230
+ import Notifications from './Notifications.vue'
231
+ import { Chart } from './Chart'
232
+
233
+ export const islandRegistry = defineIslandRegistry({
234
+ svelte: {
235
+ SvelteForm,
236
+ ContactForm,
237
+ },
238
+ vue: {
239
+ Notifications,
240
+ },
241
+ react: {
242
+ Chart,
243
+ },
244
+ })
245
+ ```
246
+
247
+ No type casts. You pass the actual components and `defineIslandRegistry()` infers the props types from them — the same way `handleReactPageRequest` infers `Props` from `ReactComponent<Props>` and `handleSveltePageRequest` infers `P` from `SvelteComponent<P>`. The generic signature extracts props from each framework's component type (`ComponentType<P>` for React, `Component<P>` for Svelte, etc.) so the registry knows exactly what props each island accepts.
248
+
249
+ `defineIslandRegistry()` also serves as the runtime registry — the island renderer uses it to look up the actual component at SSR time. One object, two purposes: type inference for the IDE and component resolution for the server.
250
+
251
+ **How the types flow through `<Island>`:**
252
+
253
+ Same pattern as Eden Treaty — you pass the registry to a factory function that returns a typed `<Island>` component. No module augmentation, no global declarations. The type safety lives at the call site.
254
+
255
+ Each framework exports a `createIsland` function that takes the registry and returns a typed `<Island>` component:
256
+
257
+ ```tsx
258
+ // islands/index.ts — one line per framework you use as a host
259
+ import { createIsland } from 'absolutejs/react'
260
+ import { islandRegistry } from './registry'
261
+
262
+ export const Island = createIsland(islandRegistry)
263
+ ```
264
+
265
+ Now `Island` knows the full registry type. `framework` narrows `component`, `component` narrows `props` — all inferred from what you passed in.
266
+
267
+ **React host page:**
268
+ ```tsx
269
+ import { Island } from '../islands'
270
+
271
+ export const Dashboard = ({ data }: DashboardProps) => (
272
+ <div>
273
+ <ReactChart data={data} />
274
+ <Island
275
+ framework="svelte" // autocomplete: "svelte" | "vue" | "react"
276
+ component="SvelteForm" // autocomplete: "SvelteForm" | "ContactForm"
277
+ props={{ fields: data.form }} // typed as SvelteFormProps — error if wrong
278
+ hydrate="load"
279
+ />
280
+ <Island
281
+ framework="vue"
282
+ component="Notifications" // autocomplete: "Notifications"
283
+ props={{ count: 3 }} // typed as NotificationsProps
284
+ hydrate="visible"
285
+ />
286
+ </div>
287
+ )
288
+ ```
289
+
290
+ **Svelte host page:**
291
+ ```svelte
292
+ <script lang="ts">
293
+ // Svelte equivalent: createIsland from 'absolutejs/svelte'
294
+ import { Island } from '../islands/svelte'
295
+ </script>
296
+
297
+ <h1>Analytics</h1>
298
+ <Island
299
+ framework="react"
300
+ component="Chart"
301
+ props={{ data: [1, 2, 3] }}
302
+ hydrate="load"
303
+ />
304
+ <Island
305
+ framework="vue"
306
+ component="Notifications"
307
+ props={{ count: 5 }}
308
+ hydrate="idle"
309
+ />
310
+ ```
311
+
312
+ **Vue host page:**
313
+ ```vue
314
+ <script setup lang="ts">
315
+ // Vue equivalent: createIsland from 'absolutejs/vue'
316
+ import { Island } from '../islands/vue'
317
+ const chartData = [1, 2, 3]
318
+ </script>
319
+
320
+ <template>
321
+ <h1>Dashboard</h1>
322
+ <Island
323
+ framework="react"
324
+ component="Chart"
325
+ :props="{ data: chartData }"
326
+ hydrate="visible"
327
+ />
328
+ <Island
329
+ framework="svelte"
330
+ component="SvelteForm"
331
+ :props="{ fields: ['name', 'email'] }"
332
+ hydrate="load"
333
+ />
334
+ </template>
335
+ ```
336
+
337
+ **Angular host page:**
338
+ ```typescript
339
+ import { Component } from '@angular/core'
340
+ // Angular: createIslandDirective from 'absolutejs/angular'
341
+ import { IslandComponent } from '../islands/angular'
342
+
343
+ @Component({
344
+ selector: 'app-dashboard',
345
+ imports: [IslandComponent],
346
+ template: `
347
+ <h1>Dashboard</h1>
348
+ <abs-island
349
+ framework="react"
350
+ component="Chart"
351
+ [props]="{ data: chartData }"
352
+ hydrate="load"
353
+ />
354
+ <abs-island
355
+ framework="svelte"
356
+ component="ContactForm"
357
+ [props]="{ fields: formFields }"
358
+ hydrate="idle"
359
+ />
360
+ `
361
+ })
362
+ export class DashboardComponent {
363
+ chartData = [1, 2, 3]
364
+ formFields = ['name', 'email']
365
+ }
366
+ ```
367
+
368
+ **HTML/HTMX host page (attribute-based, no type safety):**
369
+ ```html
370
+ <h1>Landing Page</h1>
371
+ <div data-island="react" data-component="Chart" data-hydrate="visible"
372
+ data-props='{"data":[1,2,3]}'>
373
+ <!-- SSR'd at request time -->
374
+ </div>
375
+ <div data-island="svelte" data-component="ContactForm" data-hydrate="load">
376
+ <!-- SSR'd at request time -->
377
+ </div>
378
+ ```
379
+
380
+ *What AbsoluteJS provides (in `types/island.ts`):*
381
+ ```ts
382
+ import type { ComponentType as ReactComponent } from 'react'
383
+ import type { Component as SvelteComponent } from 'svelte'
384
+
385
+ // Extract props from any framework's component type
386
+ type ExtractReactProps<C> = C extends ReactComponent<infer P> ? P : never
387
+ type ExtractSvelteProps<C> = C extends SvelteComponent<infer P> ? P : never
388
+ // Vue and Angular equivalents follow the same pattern
389
+
390
+ // Maps a record of components to a record of their extracted props
391
+ type ExtractFrameworkProps<F extends string, Components extends Record<string, unknown>> = {
392
+ [K in keyof Components]: F extends 'react' ? ExtractReactProps<Components[K]>
393
+ : F extends 'svelte' ? ExtractSvelteProps<Components[K]>
394
+ : F extends 'vue' ? ExtractVueProps<Components[K]>
395
+ : F extends 'angular' ? ExtractAngularProps<Components[K]>
396
+ : never
397
+ }
398
+
399
+ // Inferred registry type — maps framework -> component name -> props
400
+ type InferredRegistry<T> = {
401
+ [F in keyof T]: ExtractFrameworkProps<F & string, T[F] & Record<string, unknown>>
402
+ }
403
+
404
+ // Defaults to 'load' — most islands are interactive, so the common case needs no prop
405
+ type IslandHydrate = 'load' | 'idle' | 'visible' | 'none'
406
+
407
+ // defineIslandRegistry — accepts actual components, infers all props types
408
+ // Returns the registry for both runtime SSR lookup and type-level inference
409
+ const defineIslandRegistry = <
410
+ T extends Partial<Record<'react' | 'svelte' | 'vue' | 'angular', Record<string, unknown>>>
411
+ >(registry: T) => registry as unknown as InferredRegistry<T>
412
+
413
+ // createIsland — factory that takes a registry and returns a typed component
414
+ // Same pattern as Eden's treaty() — pass the type source, get type safety out
415
+ // Each framework exports its own version:
416
+ // absolutejs/react → createIsland(registry) returns a React <Island> component
417
+ // absolutejs/svelte → createIsland(registry) returns a Svelte <Island> component
418
+ // absolutejs/vue → createIsland(registry) returns a Vue <Island> component
419
+ // absolutejs/angular → createIslandDirective(registry) returns an Angular directive
420
+ //
421
+ // The returned component's props are constrained by the registry:
422
+ // <Island framework={F} component={C} props={InferredRegistry[F][C]} hydrate={...} />
423
+ ```
424
+
425
+ The key insight: `defineIslandRegistry` accepts actual imported components at runtime (for SSR lookup) and infers their props types through generics (for IDE autocomplete). `createIsland` takes that registry and returns a framework-specific `<Island>` component whose `framework` → `component` → `props` chain is fully typed. Same pattern as Eden Treaty — pass the type source in, get type safety out. No module augmentation, no codegen, no type casts.
426
+
427
+ *How `<Island>` works under the hood:*
428
+ - **On the server:** The `<Island>` component calls the target framework's SSR renderer with the given props, gets back HTML, and renders it inside a `<div>` marker with `data-island`, `data-component`, `data-hydrate`, and serialized `data-props` attributes. In React this uses `dangerouslySetInnerHTML`, in Svelte it uses `{@html}`, in Vue it uses `v-html`, in Angular it uses `[innerHTML]`.
429
+ - **On the client:** The island runtime script finds all `data-island` elements, loads the right framework vendor + component module from the manifest, and hydrates each one independently based on its `hydrate` directive.
430
+ - **The `<Island>` component itself ships zero JS to the client** — it's SSR-only. The client-side hydration is handled entirely by the island runtime.
431
+
432
+ **Design considerations:**
433
+ - Islands must be self-contained — they hydrate independently with their own props. Cross-island communication goes through the shared zustand store, not prop drilling.
434
+ - The island runtime should detect which frameworks are used on the page and only load those vendor bundles. If a page has 5 React islands and 1 Svelte island, React vendor loads once (shared), Svelte vendor loads once.
435
+ - CSS for each island should be scoped or co-located. CSS Modules work naturally here — each island's styles are hashed and don't collide.
436
+ - Islands inside islands (nested cross-framework) should be explicitly unsupported in v1 to keep complexity down.
437
+ - HMR needs to work per-island — editing a Svelte island component should hot-reload just that island, not the entire page.
438
+
439
+ **Files likely involved:**
440
+ - New: `src/react/components/Island.tsx` — React `<Island>` component (SSR-only, renders other frameworks inline)
441
+ - New: `src/svelte/Island.svelte` — Svelte `<Island>` component
442
+ - New: `src/vue/Island.vue` — Vue `<Island>` component
443
+ - New: `src/angular/island.component.ts` — Angular `<abs-island>` component
444
+ - New: `src/core/islandRenderer.ts` — shared server-side logic that calls the right framework's SSR renderer for an island
445
+ - New: `types/island.ts` — `IslandRegistryMap`, `IslandFramework`, `IslandComponent`, `IslandProps`, `IslandHydrate` types + `defineIslandRegistry()`
446
+ - New: `src/build/scanIslands.ts` — scans the registry at build time to discover island entry points for bundling
447
+ - New: `src/client/islandRuntime.ts` — client-side island discovery, framework loading, and hydration orchestration
448
+ - New: `src/client/islandState.ts` — zustand/vanilla-backed shared state with per-framework wrappers
449
+ - New: `src/react/hooks/useIslandState.ts` — React hook for shared island state
450
+ - New: `src/svelte/islandState.ts` — Svelte store wrapper for shared island state
451
+ - New: `src/vue/useIslandState.ts` — Vue composable for shared island state
452
+ - New: `src/angular/island-state.service.ts` — Angular service for shared island state
453
+ - `src/core/build.ts` — add island entry point discovery and per-island bundling
454
+ - `src/build/generateManifest.ts` — track island components in the manifest
455
+ - Each framework's `pageHandler.ts` — support rendering island components inline during page SSR
456
+ - `src/plugins/hmr.ts` — per-island HMR updates
457
+
458
+ ---
459
+
460
+ ## P2 — Incremental Static Regeneration (ISR)
461
+
462
+ **What Next.js does:**
463
+ Static pages can declare a `revalidate` interval (e.g., 60 seconds). After the interval, the next request triggers a background re-render. The stale page is served immediately while the new one generates. `revalidatePath()` and `revalidateTag()` allow on-demand revalidation from API routes or server actions.
464
+
465
+ **What AbsoluteJS has today:**
466
+ Nothing. No static generation means no revalidation.
467
+
468
+ **What needs to be built (requires SSG first):**
469
+ - A `revalidate` option per static page that sets a TTL
470
+ - Background re-rendering: when a request comes in for a stale page, serve the cached version and trigger a rebuild in the background
471
+ - On-demand revalidation: an API to invalidate specific pages programmatically
472
+ - A cache store for rendered pages (filesystem or in-memory)
473
+
474
+ **Files likely involved:**
475
+ - Builds on top of SSG implementation
476
+ - New: `src/core/staticCache.ts` — manages cached HTML with TTLs
477
+ - New: `src/plugins/revalidation.ts` — Elysia plugin for on-demand revalidation endpoints
478
+
479
+ ---
480
+
481
+ ## P2 — Sass/SCSS/Less Preprocessing
482
+
483
+ **What Next.js does:**
484
+ Built-in Sass support. Import `.scss` or `.sass` files directly. Also supports `.module.scss` for scoped Sass modules.
485
+
486
+ **What AbsoluteJS has today:**
487
+ Only `.css` files. Tailwind handles utility classes. No preprocessor support.
488
+
489
+ **What needs to be built:**
490
+ - A Bun build plugin that compiles `.scss`/`.sass`/`.less` files to CSS before bundling
491
+ - Bun has a plugin API for custom loaders — register `.scss` extension with a loader that calls the sass compiler
492
+ - Support `.module.scss` for scoped Sass modules (Bun's CSS Module handling + Sass compilation)
493
+ - Add `sass` as an optional peer dependency
494
+
495
+ **Files likely involved:**
496
+ - New: `src/build/sassPlugin.ts` — Bun plugin that compiles Sass
497
+ - `src/core/build.ts` — register the plugin in the Bun.build() calls
498
+ - `src/build/scanCssEntryPoints.ts` — extend glob to include `**/*.scss`, `**/*.sass`, `**/*.less`
499
+
500
+ ---
501
+
502
+ ## P2 — Middleware
503
+
504
+ **What Next.js does:**
505
+ A single `middleware.ts` at the project root that runs before every request. Can rewrite URLs, redirect, set headers, check auth, do A/B testing, geolocation-based routing. Runs on the edge (lightweight V8 isolate).
506
+
507
+ **What AbsoluteJS has today:**
508
+ Elysia's full middleware system — `onBeforeHandle`, `onAfterHandle`, `.use()` plugin chain, `derive`, `guard`. This is actually more powerful than Next.js middleware but less conventionalized.
509
+
510
+ **What needs to be built:**
511
+ - Honestly, this might just need documentation. Elysia's `onBeforeHandle` IS middleware. A `guard()` block with auth checks IS the auth middleware pattern.
512
+ - Consider a thin `middleware()` helper that wraps the common pattern: check auth, redirect if not authenticated, rewrite URLs, set CORS headers
513
+ - Examples showing: auth guard, redirect, URL rewrite, rate limiting, CORS
514
+ - The key gap isn't functionality — it's discoverability. New users don't know that Elysia's `onBeforeHandle` is the middleware they're looking for.
515
+
516
+ **Files likely involved:**
517
+ - Mostly documentation/examples
518
+ - Optional: `src/utils/middleware.ts` — convenience wrappers for common patterns
519
+
520
+ ---
521
+
522
+ ## P3 — Internationalization (i18n)
523
+
524
+ **What Next.js does:**
525
+ Built-in locale routing (`/en/about`, `/fr/about`), locale detection from Accept-Language header, and domain-based routing. Integrates with i18n libraries like next-intl.
526
+
527
+ **What AbsoluteJS has today:**
528
+ Nothing.
529
+
530
+ **What needs to be built:**
531
+ - Locale detection middleware (Accept-Language header parsing, cookie-based locale persistence)
532
+ - URL prefix routing pattern (`/:locale/page`)
533
+ - A helper to load translation files and inject them as props
534
+ - Per-framework translation access patterns (React context, Vue provide/inject, Svelte stores)
535
+ - This is mostly a pattern/plugin, not core framework work
536
+
537
+ **Files likely involved:**
538
+ - New: `src/plugins/i18n.ts` — Elysia plugin for locale detection and routing
539
+ - Documentation showing integration with popular i18n libraries
540
+
541
+ ---
542
+
543
+ ## P3 — Font Optimization
544
+
545
+ **What Next.js does:**
546
+ `next/font` automatically downloads Google Fonts at build time (no external requests), subsets them, adds `font-display: swap`, and inlines the CSS. Self-hosted fonts get the same optimizations. Zero layout shift from font loading.
547
+
548
+ **What AbsoluteJS has today:**
549
+ `generateHeadElement()` adds a Google Fonts `<link>` with `display=swap`. Fonts are loaded at runtime from Google's CDN.
550
+
551
+ **What needs to be built:**
552
+ - A build-time step that downloads declared Google Fonts, subsets them (woff2), and writes them to the assets directory
553
+ - Inline the `@font-face` CSS directly in the `<head>` instead of linking to Google
554
+ - This eliminates the external request to Google, improves privacy, and prevents FOUT
555
+ - Consider a `defineFont()` helper that takes a font config and returns the CSS + paths
556
+
557
+ **Files likely involved:**
558
+ - New: `src/build/downloadFonts.ts` — fetches and subsets Google Fonts at build time
559
+ - `src/utils/generateHeadElement.ts` — inline font CSS instead of external link
560
+ - `src/core/build.ts` — add font download step to build pipeline
561
+
562
+ ---
563
+
564
+ ## P3 — Edge Runtime / Serverless Deployment
565
+
566
+ **What Next.js does:**
567
+ Routes can opt into the Edge Runtime (lightweight V8) for lower latency at the edge. Serverless function deployment on Vercel, AWS Lambda, Cloudflare Workers. Middleware always runs on edge.
568
+
569
+ **What AbsoluteJS has today:**
570
+ Bun-only. Requires a long-running Bun server process. No serverless or edge adapter.
571
+
572
+ **What needs to be built:**
573
+ - Deployment adapters for common platforms (Fly.io, Railway, Render are easy since they support Bun directly)
574
+ - Docker template with a minimal Bun image
575
+ - For serverless: an adapter that wraps the Elysia server as a Lambda/Cloud Function handler
576
+ - Edge runtime is unlikely to be worth pursuing — Bun doesn't run on Cloudflare Workers, and the Bun server is already fast enough that edge latency gains are marginal
577
+ - Focus on making traditional deployment dead simple rather than chasing edge
578
+
579
+ **Files likely involved:**
580
+ - New: `src/adapters/docker/Dockerfile`
581
+ - New: `src/adapters/lambda.ts` — AWS Lambda adapter
582
+ - Documentation for common deployment targets
package/dist/build.js CHANGED
@@ -256,7 +256,19 @@ var getManifestKey = (folder, pascalName, isClientComponent, isReact, isVue, isS
256
256
  const pascalName = toPascal(baseName);
257
257
  const ext = extname(fileWithHash);
258
258
  if (ext === ".css") {
259
- const cssKey = `${pascalName}CSS`;
259
+ const isFromReact = segments.some((seg) => seg === "react");
260
+ const isFromVue = segments.some((seg) => seg === "vue");
261
+ const isFromSvelte = segments.some((seg) => seg === "svelte");
262
+ const isFromAngular = segments.some((seg) => seg === "angular");
263
+ const isFromFramework = isFromReact || isFromVue || isFromSvelte || isFromAngular;
264
+ let cssKey;
265
+ if (isFromVue && segments.includes("css")) {
266
+ cssKey = `${pascalName}CompiledCSS`;
267
+ } else if (isFromFramework) {
268
+ cssKey = `${pascalName}BundledCSS`;
269
+ } else {
270
+ cssKey = `${pascalName}CSS`;
271
+ }
260
272
  if (manifest[cssKey] && manifest[cssKey] !== `/${relative}`)
261
273
  logWarn(`Duplicate manifest key "${cssKey}" \u2014 "${manifest[cssKey]}" will be overwritten by "/${relative}". Use unique page names across frameworks.`);
262
274
  manifest[cssKey] = `/${relative}`;
@@ -205516,5 +205528,5 @@ export {
205516
205528
  build
205517
205529
  };
205518
205530
 
205519
- //# debugId=ECB2F84724C74AB964756E2164756E21
205531
+ //# debugId=BBCCF5F1F6563B7664756E2164756E21
205520
205532
  //# sourceMappingURL=build.js.map