atollic 0.0.2

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 ADDED
@@ -0,0 +1,432 @@
1
+ # atollic
2
+
3
+ Island architecture for WinterCG-compatible runtimes. Bring your own server (Elysia, Hono, …) and your own UI framework, powered by Vite.
4
+
5
+ > **Status: experimental.** atollic is pre-1.0 (`v0.0.x`). The API may change between minor versions until 1.0.
6
+
7
+ - **Server-agnostic** — any WinterCG runtime that speaks `Request`/`Response` (Elysia, Hono, Bun, Node via adapter, Workers, …).
8
+ - **UI-framework-agnostic** — ships with a Solid.js adapter; Preact / React / others pluggable via `FrameworkAdapter`.
9
+ - **Zero-JS by default** — pages render to HTML strings on the server; only `"use client"` islands ship JavaScript.
10
+ - **HMR with state preserved** — server changes morph the DOM via idiomorph, keeping mounted islands alive.
11
+
12
+ ## How it works
13
+
14
+ ```
15
+ Server (Elysia, Hono, ...) Client (Browser)
16
+ ───────────────────────────── ─────────────────────────
17
+ 1. Route handler returns JSX 4. Find [data-island] elements
18
+ 2. Islands SSR to real HTML 5. Lazy-import component module
19
+ 3. Full page sent to browser 6. Hydrate with matching props
20
+ ```
21
+
22
+ - Pages are **server-rendered JSX** using atollic's built-in HTML runtime — no virtual DOM, just strings.
23
+ - Components marked with `"use client"` become **islands** — they SSR on the server and hydrate on the client.
24
+ - Everything else is zero-JS static HTML.
25
+
26
+ ## Quick start
27
+
28
+ ```bash
29
+ bun add atollic elysia solid-js vite-plugin-solid
30
+ ```
31
+
32
+ ### Project structure
33
+
34
+ ```
35
+ my-app/
36
+ src/
37
+ app.tsx # Server entry — routes and layouts
38
+ islands/
39
+ Counter.tsx # Interactive island component
40
+ vite.config.ts
41
+ ```
42
+
43
+ ### `vite.config.ts`
44
+
45
+ ```ts
46
+ import { defineConfig } from "vite";
47
+ import { solid } from "atollic/solid";
48
+ import { atollic } from "atollic/vite";
49
+
50
+ export default defineConfig({
51
+ plugins: [
52
+ atollic({
53
+ entry: "./src/app.tsx",
54
+ frameworks: [solid()],
55
+ }),
56
+ ],
57
+ });
58
+ ```
59
+
60
+ ### `src/app.tsx` — Server entry (Elysia)
61
+
62
+ ```tsx
63
+ import { Elysia } from "elysia";
64
+ import { Head } from "atollic/head";
65
+ import { atollic } from "atollic/elysia";
66
+ import Counter from "./islands/Counter.js";
67
+
68
+ const app = new Elysia()
69
+ .use(atollic())
70
+ .get("/", () => (
71
+ <html lang="en">
72
+ <head>
73
+ <meta charset="UTF-8" />
74
+ <Head />
75
+ </head>
76
+ <body>
77
+ <h1>Hello from the server</h1>
78
+ <Counter initial={0} />
79
+ </body>
80
+ </html>
81
+ ));
82
+
83
+ export default app.handle;
84
+ ```
85
+
86
+ ### `src/islands/Counter.tsx` — Island component
87
+
88
+ ```tsx
89
+ /** @jsxImportSource solid-js */
90
+ "use client";
91
+
92
+ import { createSignal } from "solid-js";
93
+
94
+ export default function Counter(props: { initial: number }) {
95
+ const [count, setCount] = createSignal(props.initial);
96
+ return (
97
+ <button onClick={() => setCount((c) => c + 1)}>
98
+ Count: {count()}
99
+ </button>
100
+ );
101
+ }
102
+ ```
103
+
104
+ ### Run
105
+
106
+ ```bash
107
+ bunx --bun vite # Dev server with HMR
108
+ bunx --bun vite build # Production build
109
+ bun dist/server/app.js # Start production server
110
+ ```
111
+
112
+ ## Server adapters
113
+
114
+ Atollic is decoupled from any specific server. Your entry file exports a fetch function — atollic handles the rest.
115
+
116
+ ### Elysia
117
+
118
+ ```ts
119
+ import { Elysia } from "elysia";
120
+ import { atollic } from "atollic/elysia";
121
+
122
+ const app = new Elysia()
123
+ .use(atollic())
124
+ .get("/", () => <h1>Hello</h1>);
125
+
126
+ export default app.handle;
127
+ ```
128
+
129
+ The Elysia adapter intercepts responses via `mapResponse`, extracts HTML from Solid's SSR format, ensures DOCTYPE, and injects production assets.
130
+
131
+ ### Hono
132
+
133
+ ```ts
134
+ import { Hono } from "hono";
135
+ import { atollic } from "atollic/hono";
136
+ import { html } from "atollic";
137
+
138
+ const app = new Hono();
139
+ app.use(atollic());
140
+
141
+ app.get("/", () => html(<h1>Hello</h1>));
142
+
143
+ export default app.fetch;
144
+ ```
145
+
146
+ The Hono adapter is middleware that processes `text/html` responses. Use the `html()` helper to wrap JSX into a proper `Response`.
147
+
148
+ ## Islands
149
+
150
+ Any `.tsx` or `.jsx` file with `"use client"` at the top becomes an island:
151
+
152
+ ```tsx
153
+ /** @jsxImportSource solid-js */
154
+ "use client";
155
+
156
+ // This component SSRs on the server and hydrates on the client
157
+ ```
158
+
159
+ ### How islands work
160
+
161
+ 1. **Discovery** — The Vite plugin scans for files with `"use client"` and identifies PascalCase component exports
162
+ 2. **SSR stub** — During SSR, island files are replaced with stubs that render the component to HTML inside a `<div data-island="Name">` wrapper with serialized props
163
+ 3. **Client entry** — A virtual module registers all discovered islands with lazy imports
164
+ 4. **Hydration** — The client runtime finds `[data-island]` elements and:
165
+ - If SSR content exists: hydrates with matching `renderId` (reuses existing DOM)
166
+ - If empty (dynamically added): falls back to full client-side render
167
+
168
+ ### Framework directive
169
+
170
+ When using multiple UI frameworks, specify which one:
171
+
172
+ ```tsx
173
+ "use client:solid" // Solid.js
174
+ "use client:preact" // Preact (when adapter exists)
175
+ ```
176
+
177
+ Plain `"use client"` uses the first registered framework.
178
+
179
+ ### Named exports
180
+
181
+ A single file can export multiple island components. Each PascalCase export becomes its own island boundary:
182
+
183
+ ```tsx
184
+ "use client";
185
+
186
+ export function SearchBar(props: { placeholder: string }) { /* ... */ }
187
+ export function TagCloud(props: { tags: string[] }) { /* ... */ }
188
+ ```
189
+
190
+ Both are independently hydratable islands.
191
+
192
+ ### Client scripts
193
+
194
+ Non-JSX files (`.ts`, `.js`) with `"use client"` are bundled as client-side scripts — no framework, just plain JS:
195
+
196
+ ```ts
197
+ "use client";
198
+
199
+ document.addEventListener("click", (e) => {
200
+ // Runs only in the browser
201
+ });
202
+ ```
203
+
204
+ Import the script from your server entry — it's skipped during SSR and loaded in the browser.
205
+
206
+ ### Cross-island state
207
+
208
+ Islands are independent roots, but you can share reactive state between them using module-level signals:
209
+
210
+ ```ts
211
+ // shared.ts
212
+ import { createSignal } from "solid-js";
213
+ export const [count, setCount] = createSignal(0);
214
+ ```
215
+
216
+ ```tsx
217
+ // Increment.tsx
218
+ "use client";
219
+ import { setCount } from "./shared";
220
+ export default () => <button onClick={() => setCount((c) => c + 1)}>+</button>;
221
+ ```
222
+
223
+ ```tsx
224
+ // Display.tsx
225
+ "use client";
226
+ import { count } from "./shared";
227
+ export default () => <p>Count: {count()}</p>;
228
+ ```
229
+
230
+ Both islands share the same signal — no context provider needed.
231
+
232
+ ## JSX runtime
233
+
234
+ Atollic includes a server-side JSX runtime (`atollic/jsx-runtime`) that compiles JSX to HTML strings:
235
+
236
+ - All standard HTML elements and attributes
237
+ - Async components (`Promise<string>` return values)
238
+ - Boolean attributes (`disabled`, `checked`, etc.)
239
+ - Automatic XSS escaping
240
+ - Void elements (`<br />`, `<img />`, etc.)
241
+
242
+ ```json
243
+ {
244
+ "compilerOptions": {
245
+ "jsx": "react-jsx",
246
+ "jsxImportSource": "atollic"
247
+ }
248
+ }
249
+ ```
250
+
251
+ ## `<Head />`
252
+
253
+ Place `<Head />` in your document `<head>` to mark where atollic injects CSS and script tags:
254
+
255
+ ```tsx
256
+ import { Head } from "atollic/head";
257
+
258
+ <head>
259
+ <meta charset="UTF-8" />
260
+ <Head />
261
+ </head>
262
+ ```
263
+
264
+ In dev, atollic injects the hydration bootstrap, collected CSS, and the client entry. In production, it injects the built asset tags. If `<Head />` is omitted, assets are injected before `</head>` as a fallback.
265
+
266
+ ## HMR
267
+
268
+ Dual HMR strategy for instant feedback:
269
+
270
+ - **Server file changes** — atollic sends a custom `atollic:reload` event. The client refetches the page and uses [idiomorph](https://github.com/bigskysoftware/idiomorph) to morph the DOM, preserving mounted island state.
271
+ - **Island file changes** — handled by the framework's own HMR (e.g., `solid-refresh`).
272
+
273
+ ### Events
274
+
275
+ Listen to lifecycle events on `document`:
276
+
277
+ | Event | Detail | Description |
278
+ |---|---|---|
279
+ | `atollic:ready` | — | All initial islands mounted |
280
+ | `atollic:before-morph` | `{ newDoc, mountedIslands }` | Cancelable, before HMR page morph |
281
+ | `atollic:after-morph` | `{ defaultSwap }` | After HMR page morph |
282
+
283
+ ## htmx integration
284
+
285
+ Islands hydrate automatically after htmx swaps. The client runtime listens for `htmx:afterSettle` and mounts any new `[data-island]` elements. A `MutationObserver` also catches dynamically added islands from any source.
286
+
287
+ ## CSS handling
288
+
289
+ CSS files imported in your server entry (or its transitive imports) are automatically discovered:
290
+
291
+ - **Dev**: collected from Vite's module graph and injected as `<link>` tags via `<Head />`
292
+ - **Prod**: included in the client build, hashed, and referenced in the build manifest
293
+
294
+ ```tsx
295
+ import "./styles.css"; // discovered automatically
296
+ ```
297
+
298
+ ## Production build
299
+
300
+ ```bash
301
+ bunx --bun vite build
302
+ ```
303
+
304
+ Produces:
305
+
306
+ ```
307
+ dist/
308
+ client/ # Static assets (JS, CSS) with content-hashed filenames
309
+ assets/
310
+ server/
311
+ app.js # Self-contained server entry
312
+ ```
313
+
314
+ The generated server entry calls `setProductionAssets()` with the built CSS/JS tags, imports your app, and starts `Bun.serve()` with static file serving from `dist/client/`.
315
+
316
+ ```bash
317
+ PORT=3000 bun dist/server/app.js
318
+ ```
319
+
320
+ ## Writing a framework adapter
321
+
322
+ Implement `FrameworkAdapter` to add support for any UI framework:
323
+
324
+ ```ts
325
+ import type { FrameworkAdapter } from "atollic/adapter";
326
+
327
+ export function myFramework(): FrameworkAdapter {
328
+ return {
329
+ name: "my-framework",
330
+
331
+ // Vite plugins for JSX transform
332
+ plugins: () => [myFrameworkVitePlugin()],
333
+
334
+ // Generate SSR stub for "use client" components
335
+ ssrStub(rawImportPath, fileExports) {
336
+ return `/* SSR stub that renders to HTML string */`;
337
+ },
338
+
339
+ // Client-side hydrate/render functions
340
+ clientRuntime: `
341
+ export function hydrateIsland(el, Component, props, id) {
342
+ // Hydrate existing SSR content — return dispose function
343
+ }
344
+ export function renderIsland(el, Component, props) {
345
+ // Render into empty container — return dispose function
346
+ }
347
+ `,
348
+
349
+ // Optional: script tag for hydration bootstrap (e.g., Solid's _$HY)
350
+ hydrationScript: `<script>/* bootstrap */</script>`,
351
+ };
352
+ }
353
+ ```
354
+
355
+ ## API reference
356
+
357
+ ### `atollic/vite`
358
+
359
+ ```ts
360
+ atollic(options: AtollicOptions): Plugin[]
361
+ ```
362
+
363
+ | Option | Type | Description |
364
+ |---|---|---|
365
+ | `entry` | `string` | Path to server entry that default-exports a fetch function |
366
+ | `frameworks` | `FrameworkAdapter[]` | UI framework adapters (e.g., `[solid()]`) |
367
+
368
+ ### `atollic`
369
+
370
+ ```ts
371
+ html(input: string): Response // Wrap HTML string in a Response with DOCTYPE
372
+ setProductionAssets(assets: string): void // Set production asset tags
373
+ getProductionAssets(): string | undefined // Get production asset tags
374
+ ```
375
+
376
+ ### `atollic/elysia`
377
+
378
+ ```ts
379
+ atollic(): Elysia // Elysia plugin — intercepts HTML responses, injects assets
380
+ ```
381
+
382
+ ### `atollic/hono`
383
+
384
+ ```ts
385
+ atollic(): MiddlewareHandler // Hono middleware — processes HTML responses, injects assets
386
+ ```
387
+
388
+ ### `atollic/head`
389
+
390
+ ```ts
391
+ Head(): string // Returns marker for asset injection
392
+ ```
393
+
394
+ ### `atollic/solid`
395
+
396
+ ```ts
397
+ solid(): FrameworkAdapter // Solid.js framework adapter
398
+ ```
399
+
400
+ ### `atollic/html`
401
+
402
+ Types for server-side JSX:
403
+
404
+ ```ts
405
+ type Component<P = {}> = (props: P & { children?: Children }) => string
406
+ type Children = string | string[] | Promise<string>
407
+ ```
408
+
409
+ ## Exports
410
+
411
+ | Export | Description |
412
+ |---|---|
413
+ | `atollic` | Core — `html()`, `setProductionAssets()`, `getProductionAssets()` |
414
+ | `atollic/vite` | Vite plugin |
415
+ | `atollic/client` | Client runtime (auto-imported) |
416
+ | `atollic/adapter` | `FrameworkAdapter` type |
417
+ | `atollic/head` | `<Head />` component |
418
+ | `atollic/solid` | Solid.js adapter |
419
+ | `atollic/elysia` | Elysia server adapter |
420
+ | `atollic/hono` | Hono server adapter |
421
+ | `atollic/jsx-runtime` | Server JSX runtime |
422
+ | `atollic/html` | HTML types (`Component`, `Children`, JSX namespace) |
423
+
424
+ ## Requirements
425
+
426
+ - A WinterCG-compatible runtime — [Bun](https://bun.sh), [Node](https://nodejs.org) (via fetch adapter), [Deno](https://deno.com), or Cloudflare Workers
427
+ - [Vite](https://vite.dev) ^8.0.0
428
+ - Examples and the bundled production server template currently target Bun; other runtimes work but require providing your own server entry
429
+
430
+ ## License
431
+
432
+ MIT
@@ -0,0 +1,39 @@
1
+ import type { Plugin } from "vite";
2
+ export interface IslandExport {
3
+ /** "default" for default exports, or the named export identifier */
4
+ exportName: string;
5
+ /** PascalCase name used in the `data-island` attribute */
6
+ islandName: string;
7
+ }
8
+ export interface FrameworkAdapter {
9
+ /** Identifier matching the directive suffix, e.g. `"solid"` for `"use client:solid"` */
10
+ name: string;
11
+ /** Vite plugins needed for this framework's JSX/TSX transform */
12
+ plugins?: () => Plugin[];
13
+ /**
14
+ * Generate SSR stub code for a `"use client"` component file.
15
+ *
16
+ * The returned source must:
17
+ * - Import the real component from `rawImportPath` (the `?raw-island` version)
18
+ * - For each export in `fileExports`, produce a wrapper that renders the
19
+ * component to an HTML string inside a `<div data-island>` wrapper with
20
+ * serialized props
21
+ * - The stub may use framework-specific JSX if the adapter provides Vite
22
+ * plugins that transform it (e.g. Solid's SSR stub uses Solid JSX)
23
+ */
24
+ ssrStub(rawImportPath: string, fileExports: IslandExport[]): string;
25
+ /**
26
+ * Source code string for the client-side hydrate/render functions.
27
+ *
28
+ * Must export:
29
+ * - `hydrateIsland(el, Component, props, id)` → returns dispose function
30
+ * - `renderIsland(el, Component, props)` → returns dispose function
31
+ */
32
+ clientRuntime: string;
33
+ /**
34
+ * HTML script tag to inject in `<head>` for hydration bootstrap.
35
+ * For example, Solid needs `_$HY`. Frameworks without a bootstrap script
36
+ * can omit this.
37
+ */
38
+ hydrationScript?: string;
39
+ }
File without changes
@@ -0,0 +1,10 @@
1
+ import type { FrameworkAdapter } from "../adapter.js";
2
+ /**
3
+ * Solid.js framework adapter for atollic.
4
+ *
5
+ * Requires peer dependencies: `solid-js`, `vite-plugin-solid`
6
+ *
7
+ * Files are automatically detected via the `@jsxImportSource solid-js`
8
+ * pragma — no include/exclude patterns needed.
9
+ */
10
+ export declare function solid(): FrameworkAdapter;
@@ -0,0 +1,37 @@
1
+ import { createRequire as e } from "node:module";
2
+ //#region src/adapters/solid.ts
3
+ var t = e(import.meta.url);
4
+ function n() {
5
+ return {
6
+ name: "solid",
7
+ plugins() {
8
+ let e = t("vite-plugin-solid"), n = (typeof e == "function" ? e : e.default)({ ssr: !0 });
9
+ return Array.isArray(n) ? n : [n];
10
+ },
11
+ ssrStub(e, t) {
12
+ let n = t.find((e) => e.exportName === "default"), r = t.filter((e) => e.exportName !== "default"), i = [];
13
+ if (n && i.push("__raw_default"), r.length) {
14
+ let e = r.map((e) => `${e.exportName} as __raw_${e.exportName}`).join(", ");
15
+ i.push(`{ ${e} }`);
16
+ }
17
+ let a = "import { renderToString } from \"solid-js/web\";\n";
18
+ a += `import ${i.join(", ")} from "${e}";\n`, a += "let __ix_idx = 0;\n", a += "function __unwrap(v) { return v && typeof v === \"object\" && \"t\" in v ? v.t : String(v); }\n";
19
+ for (let e of t) {
20
+ let t = e.exportName === "default", n = t ? "__raw_default" : `__raw_${e.exportName}`, r = t ? "export default function" : `export function ${e.exportName}`;
21
+ a += `${r}(props) {
22
+ const id = "ix-${e.islandName}-" + __ix_idx++;
23
+ const { children: __children, ...jsonProps } = props;
24
+ const propsJson = JSON.stringify(jsonProps);
25
+ const html = __unwrap(renderToString(() => ${n}(props), { renderId: id }));
26
+ return '<div data-island="${e.islandName}" data-framework="solid" id="' + id + '">'
27
+ + html + '<script type="application/json">' + propsJson + '<\/script></div>';
28
+ }\n`;
29
+ }
30
+ return a;
31
+ },
32
+ clientRuntime: "\nimport { hydrate as _hydrate, render as _render } from \"solid-js/web\";\nimport { $DEVCOMP } from \"solid-js\";\n\nexport function hydrateIsland(el, Component, props, id) {\n if ($DEVCOMP && !($DEVCOMP in Component)) {\n Component[$DEVCOMP] = true;\n }\n return _hydrate(() => Component(props), el, { renderId: id });\n}\n\nexport function renderIsland(el, Component, props) {\n if ($DEVCOMP && !($DEVCOMP in Component)) {\n Component[$DEVCOMP] = true;\n }\n el.textContent = \"\";\n return _render(() => Component(props), el);\n}\n",
33
+ hydrationScript: "<script>(self._$HY||(self._$HY={events:[],completed:new WeakSet,r:{}}))<\/script>"
34
+ };
35
+ }
36
+ //#endregion
37
+ export { n as solid };
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Client-side island runtime for atollic.
3
+ *
4
+ * Discovers [data-island] elements in the DOM, dynamically imports
5
+ * the corresponding component, and mounts it using the registered
6
+ * framework adapter. Handles cleanup on removal and HMR.
7
+ *
8
+ * Events (on `document`):
9
+ * - `atollic:ready` — all initial islands mounted
10
+ * - `atollic:before-morph` — cancelable, detail: { newDoc, mountedIslands }
11
+ * - `atollic:after-morph` — detail: { defaultSwap }, after HMR page replacement
12
+ */
13
+ type HydrateFn = (el: HTMLElement, Component: any, props: Record<string, unknown>, id: string) => () => void;
14
+ type RenderFn = (el: HTMLElement, Component: any, props: Record<string, unknown>) => () => void;
15
+ export declare function registerFramework(name: string, hydrate: HydrateFn, render: RenderFn): void;
16
+ type IslandLoader = () => Promise<{
17
+ default: (props: any) => any;
18
+ }>;
19
+ export declare function registerIsland(name: string, framework: string, loader: IslandLoader): void;
20
+ export declare function mountIslands(root?: Element | Document): Promise<void>;
21
+ export declare function disposeIslands(root?: Element | Document): void;
22
+ export declare function initRuntime(): void;
23
+ export {};
package/dist/client.js ADDED
@@ -0,0 +1,113 @@
1
+ //#region src/client.ts
2
+ var e = null;
3
+ function t() {
4
+ return e ||= import("idiomorph").then((e) => e.Idiomorph), e;
5
+ }
6
+ var n = "data-island", r = `[${n}]`, i = "atollic:reload", a = /* @__PURE__ */ new Map();
7
+ function o(e, t, n) {
8
+ a.set(e, {
9
+ hydrate: t,
10
+ render: n
11
+ });
12
+ }
13
+ var s = /* @__PURE__ */ new Map(), c = /* @__PURE__ */ new Map(), l = !1;
14
+ function u(e, t, n) {
15
+ s.set(e, {
16
+ framework: t,
17
+ loader: n
18
+ });
19
+ }
20
+ function d(e) {
21
+ let t = c.get(e);
22
+ t && (t.dispose(), c.delete(e));
23
+ }
24
+ async function f(e = document) {
25
+ let t = e.querySelectorAll(r), i = [];
26
+ for (let e of t) {
27
+ if (c.has(e)) continue;
28
+ let t = e.getAttribute(n);
29
+ if (!t) continue;
30
+ let r = s.get(t);
31
+ if (!r) {
32
+ console.warn(`[atollic] Unknown island: "${t}"`);
33
+ continue;
34
+ }
35
+ let o = a.get(r.framework);
36
+ if (!o) {
37
+ console.warn(`[atollic] Unknown framework "${r.framework}" for island "${t}"`);
38
+ continue;
39
+ }
40
+ let l = e.querySelector(":scope > script[type=\"application/json\"]"), u = l ? JSON.parse(l.textContent || "{}") : {};
41
+ l && l.remove();
42
+ let d = e.childNodes.length > 0;
43
+ i.push((async () => {
44
+ try {
45
+ let n = (await r.loader()).default, i;
46
+ i = d && e.id ? o.hydrate(e, n, u, e.id) : o.render(e, n, u), c.set(e, {
47
+ dispose: i,
48
+ name: t,
49
+ props: u
50
+ });
51
+ } catch (e) {
52
+ console.error(`[atollic] Failed to mount island "${t}":`, e);
53
+ }
54
+ })());
55
+ }
56
+ await Promise.all(i);
57
+ }
58
+ function p(e = document) {
59
+ for (let t of e.querySelectorAll(r)) d(t);
60
+ }
61
+ async function m(e) {
62
+ let n = new DOMParser().parseFromString(e, "text/html");
63
+ l = !0;
64
+ let r = new Set(c.keys()), i = document.dispatchEvent(new CustomEvent("atollic:before-morph", {
65
+ cancelable: !0,
66
+ detail: {
67
+ newDoc: n,
68
+ mountedIslands: r
69
+ }
70
+ }));
71
+ if (i) {
72
+ let e = new Set(c.keys());
73
+ (await t()).morph(document.body, n.body, { morphStyle: "innerHTML" });
74
+ for (let t of e) t.isConnected || d(t);
75
+ }
76
+ let a = n.querySelector("title");
77
+ a && (document.title = a.textContent || ""), await f(), l = !1, document.dispatchEvent(new CustomEvent("atollic:after-morph", { detail: { defaultSwap: i } }));
78
+ }
79
+ function h() {
80
+ if (f().then(() => {
81
+ document.dispatchEvent(new CustomEvent("atollic:ready"));
82
+ }), s.size > 0) {
83
+ let e = !1;
84
+ new MutationObserver((t) => {
85
+ if (!l) for (let i of t) {
86
+ for (let e of i.removedNodes) if (e instanceof HTMLElement) {
87
+ e.hasAttribute(n) && d(e);
88
+ for (let t of e.querySelectorAll(r)) d(t);
89
+ }
90
+ if (!e) {
91
+ for (let t of i.addedNodes) if (t instanceof HTMLElement && (t.hasAttribute(n) || t.querySelector(r))) {
92
+ e = !0, queueMicrotask(() => {
93
+ e = !1, f();
94
+ });
95
+ break;
96
+ }
97
+ }
98
+ }
99
+ }).observe(document.body, {
100
+ childList: !0,
101
+ subtree: !0
102
+ });
103
+ }
104
+ import.meta.hot && import.meta.hot.on(i, async () => {
105
+ try {
106
+ await m(await (await fetch(window.location.href, { headers: { "X-Atollic-HMR": "1" } })).text()), console.log("[atollic] Server HMR applied");
107
+ } catch (e) {
108
+ console.warn("[atollic] Server HMR failed, full reload", e), window.location.reload();
109
+ }
110
+ });
111
+ }
112
+ //#endregion
113
+ export { p as disposeIslands, h as initRuntime, f as mountIslands, o as registerFramework, u as registerIsland };
package/dist/head.d.ts ADDED
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Returns the marker tag for atollic asset injection.
3
+ * Place this in your document `<head>` — the framework replaces it
4
+ * with the actual CSS and script tags.
5
+ *
6
+ * @example
7
+ * ```tsx
8
+ * <head>
9
+ * <meta charset="UTF-8" />
10
+ * <Head />
11
+ * </head>
12
+ * ```
13
+ */
14
+ export declare function Head(_props?: {}): JSX.Element;
package/dist/head.js ADDED
@@ -0,0 +1,7 @@
1
+ import { jsx as e } from "./html/jsx-runtime.js";
2
+ //#region src/head.tsx
3
+ function t(t) {
4
+ return /* @__PURE__ */ e("meta", { name: "atollic-head" });
5
+ }
6
+ //#endregion
7
+ export { t as Head };
@@ -0,0 +1,2 @@
1
+ export { Fragment, jsx, jsxs } from "./jsx-runtime.js";
2
+ export type { Children, Component } from "./types.js";
@@ -0,0 +1,2 @@
1
+ import { Fragment as e, jsx as t, jsxs as n } from "./jsx-runtime.js";
2
+ export { e as Fragment, t as jsx, n as jsxs };
@@ -0,0 +1,3 @@
1
+ import { Fragment, jsx, jsxs } from "./jsx-runtime.js";
2
+ export { Fragment, jsx, jsxs };
3
+ export declare function jsxDEV(tag: string | ((props: Record<string, unknown>) => unknown), props: Record<string, unknown>, _key?: string, _isStaticChildren?: boolean, _source?: unknown, _self?: unknown): string | Promise<string>;