atollic 0.0.2 → 0.0.4

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Arvin Wilderink
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,5 +1,10 @@
1
1
  # atollic
2
2
 
3
+ [![npm version](https://img.shields.io/npm/v/atollic.svg)](https://www.npmjs.com/package/atollic)
4
+ [![CI](https://github.com/awilderink/atollic/actions/workflows/ci.yml/badge.svg)](https://github.com/awilderink/atollic/actions/workflows/ci.yml)
5
+ [![License: MIT](https://img.shields.io/npm/l/atollic.svg)](./LICENSE)
6
+ [![Bundle size](https://img.shields.io/bundlephobia/minzip/atollic)](https://bundlephobia.com/package/atollic)
7
+
3
8
  Island architecture for WinterCG-compatible runtimes. Bring your own server (Elysia, Hono, …) and your own UI framework, powered by Vite.
4
9
 
5
10
  > **Status: experimental.** atollic is pre-1.0 (`v0.0.x`). The API may change between minor versions until 1.0.
@@ -126,7 +131,9 @@ const app = new Elysia()
126
131
  export default app.handle;
127
132
  ```
128
133
 
129
- The Elysia adapter intercepts responses via `mapResponse`, extracts HTML from Solid's SSR format, ensures DOCTYPE, and injects production assets.
134
+ The Elysia adapter intercepts responses via `mapResponse`, extracts HTML, ensures DOCTYPE, and injects production assets.
135
+
136
+ The HTML-extraction step is **registry-based** — each `FrameworkAdapter` contributes an extractor function (via the `extractHtml` field) that knows how to convert its framework's SSR output (e.g. Solid's `{ t: "..." }` shape) into a plain HTML string. The atollic Vite plugin wires these registrations up automatically before the first request, so the core stays framework-agnostic. Plain strings are always recognized as a fallback. See [Writing a framework adapter](#writing-a-framework-adapter) and the [`registerHtmlExtractor`](#atollic) API.
130
137
 
131
138
  ### Hono
132
139
 
@@ -280,9 +287,9 @@ Listen to lifecycle events on `document`:
280
287
  | `atollic:before-morph` | `{ newDoc, mountedIslands }` | Cancelable, before HMR page morph |
281
288
  | `atollic:after-morph` | `{ defaultSwap }` | After HMR page morph |
282
289
 
283
- ## htmx integration
290
+ ## htmx, Alpine, and other DOM-mutating libraries
284
291
 
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.
292
+ Islands added to the DOM after the initial mount — by htmx swaps, Alpine templates, or any other source — are picked up automatically. The client runtime installs a `MutationObserver` on `document.body` and mounts any newly inserted `[data-island]` element. No library-specific event listener is required.
286
293
 
287
294
  ## CSS handling
288
295
 
@@ -348,10 +355,22 @@ export function myFramework(): FrameworkAdapter {
348
355
 
349
356
  // Optional: script tag for hydration bootstrap (e.g., Solid's _$HY)
350
357
  hydrationScript: `<script>/* bootstrap */</script>`,
358
+
359
+ // Optional: source code for an extractor function `(value) => string | null`.
360
+ // Atollic includes this in the server boot module so the runtime knows
361
+ // how to convert this framework's SSR output (e.g. Solid's `{ t: "..." }`
362
+ // shape) into a plain HTML string. Frameworks whose SSR output is already
363
+ // a string can omit this — plain strings are always recognized.
364
+ extractHtml: `(value) => {
365
+ if (value && typeof value === "object" && "t" in value) return value.t;
366
+ return null;
367
+ }`,
351
368
  };
352
369
  }
353
370
  ```
354
371
 
372
+ The `extractHtml` strings from every registered adapter are emitted into a generated server-boot module that runs once before the first request. Each one calls `registerHtmlExtractor()` on the atollic core, which `extractHtml` (used internally by the Elysia and Hono adapters) iterates in order. This is how the core stays decoupled from any specific UI framework's SSR output shape.
373
+
355
374
  ## API reference
356
375
 
357
376
  ### `atollic/vite`
@@ -368,9 +387,20 @@ atollic(options: AtollicOptions): Plugin[]
368
387
  ### `atollic`
369
388
 
370
389
  ```ts
371
- html(input: string): Response // Wrap HTML string in a Response with DOCTYPE
390
+ // Wrap an HTML string (or async JSX) in a Response with DOCTYPE.
391
+ // Returns a Promise<Response> when given a Promise<string>.
392
+ html(input: string | Promise<string>): Response | Promise<Response>
393
+
372
394
  setProductionAssets(assets: string): void // Set production asset tags
373
395
  getProductionAssets(): string | undefined // Get production asset tags
396
+
397
+ // Register a function that converts a framework-specific SSR output value
398
+ // into an HTML string (or returns null if it doesn't recognize the shape).
399
+ // Normally wired up automatically by the Vite plugin from each adapter's
400
+ // `extractHtml` field — call this directly only if you need a custom one.
401
+ // Returns a dispose function.
402
+ type HtmlExtractor = (value: unknown) => string | null
403
+ registerHtmlExtractor(fn: HtmlExtractor): () => void
374
404
  ```
375
405
 
376
406
  ### `atollic/elysia`
@@ -402,15 +432,26 @@ solid(): FrameworkAdapter // Solid.js framework adapter
402
432
  Types for server-side JSX:
403
433
 
404
434
  ```ts
405
- type Component<P = {}> = (props: P & { children?: Children }) => string
406
- type Children = string | string[] | Promise<string>
435
+ type Children =
436
+ | string
437
+ | number
438
+ | bigint
439
+ | boolean
440
+ | null
441
+ | undefined
442
+ | Promise<Children>
443
+ | Children[]
444
+
445
+ type Component<T = {}> = (
446
+ props: T & { children?: Children },
447
+ ) => string | Promise<string>
407
448
  ```
408
449
 
409
450
  ## Exports
410
451
 
411
452
  | Export | Description |
412
453
  |---|---|
413
- | `atollic` | Core — `html()`, `setProductionAssets()`, `getProductionAssets()` |
454
+ | `atollic` | Core — `html()`, `setProductionAssets()`, `getProductionAssets()`, `registerHtmlExtractor()` |
414
455
  | `atollic/vite` | Vite plugin |
415
456
  | `atollic/client` | Client runtime (auto-imported) |
416
457
  | `atollic/adapter` | `FrameworkAdapter` type |
package/dist/adapter.d.ts CHANGED
@@ -36,4 +36,20 @@ export interface FrameworkAdapter {
36
36
  * can omit this.
37
37
  */
38
38
  hydrationScript?: string;
39
+ /**
40
+ * Source code for an extractor function with the signature
41
+ * `(value: unknown) => string | null`.
42
+ *
43
+ * The atollic Vite plugin includes this in the server boot module so the
44
+ * runtime knows how to convert this framework's SSR output (e.g. Solid's
45
+ * `{ t: "..." }` shape) into a plain HTML string. Frameworks whose SSR
46
+ * output is already a string can omit this — the core falls back to
47
+ * recognizing plain strings on its own.
48
+ *
49
+ * @example
50
+ * extractHtml: `(value) => value && typeof value === "object" && "t" in value
51
+ * ? value.t
52
+ * : null`
53
+ */
54
+ extractHtml?: string;
39
55
  }
@@ -30,7 +30,8 @@ function n() {
30
30
  return a;
31
31
  },
32
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>"
33
+ hydrationScript: "<script>(self._$HY||(self._$HY={events:[],completed:new WeakSet,r:{}}))<\/script>",
34
+ extractHtml: "(value) => {\n if (value && typeof value === \"object\" && \"t\" in value && typeof value.t === \"string\") {\n return value.t;\n }\n if (Array.isArray(value)) {\n const html = value.flat(Infinity).map((item) => {\n if (typeof item === \"string\") return item;\n if (item && typeof item === \"object\" && \"t\" in item) return item.t;\n return \"\";\n }).join(\"\");\n return html || null;\n }\n return null;\n}"
34
35
  };
35
36
  }
36
37
  //#endregion
package/dist/index.d.ts CHANGED
@@ -1 +1,2 @@
1
- export { getProductionAssets, html, setProductionAssets } from "./server.js";
1
+ export type { HtmlExtractor } from "./server.js";
2
+ export { getProductionAssets, html, registerHtmlExtractor, setProductionAssets, } from "./server.js";
package/dist/index.js CHANGED
@@ -1,2 +1,3 @@
1
- import { n as e, r as t, t as n } from "./server-DhToIUB0.js";
2
- export { n as getProductionAssets, e as html, t as setProductionAssets };
1
+ import { o as e } from "./shared-Bv39_NsU.js";
2
+ import { n as t, r as n, t as r } from "./server-DAuZqGuP.js";
3
+ export { r as getProductionAssets, t as html, e as registerHtmlExtractor, n as setProductionAssets };
@@ -1,4 +1,4 @@
1
- import { t as e } from "./shared-CfRCLggd.js";
1
+ import { t as e } from "./shared-Bv39_NsU.js";
2
2
  //#region src/server.ts
3
3
  var t = "__atollicProdAssets";
4
4
  function n(e) {
package/dist/server.d.ts CHANGED
@@ -1,6 +1,8 @@
1
1
  /**
2
2
  * Server-side helpers for atollic.
3
3
  */
4
+ export type { HtmlExtractor } from "./shared.js";
5
+ export { registerHtmlExtractor } from "./shared.js";
4
6
  /**
5
7
  * Set the production asset tags (scripts + CSS) to inject into HTML responses.
6
8
  * Called by the generated production entry before the server starts.
@@ -1,5 +1,5 @@
1
- import { a as e, i as t, n } from "../shared-CfRCLggd.js";
2
- import { t as r } from "../server-DhToIUB0.js";
1
+ import { a as e, i as t, n } from "../shared-Bv39_NsU.js";
2
+ import { t as r } from "../server-DAuZqGuP.js";
3
3
  import { Elysia as i } from "elysia";
4
4
  //#region src/servers/elysia.ts
5
5
  function a() {
@@ -1,5 +1,5 @@
1
- import { a as e, i as t } from "../shared-CfRCLggd.js";
2
- import { t as n } from "../server-DhToIUB0.js";
1
+ import { a as e, i as t } from "../shared-Bv39_NsU.js";
2
+ import { t as n } from "../server-DAuZqGuP.js";
3
3
  //#region src/servers/hono.ts
4
4
  function r() {
5
5
  return async (r, i) => {
@@ -0,0 +1,39 @@
1
+ //#region src/shared.ts
2
+ var e = /<meta\s+name="atollic-head"\s*\/?>/;
3
+ function t(t, n) {
4
+ let r = t.replace(e, n);
5
+ return r === t ? t.includes("</head>") ? t.replace("</head>", ` ${n}\n</head>`) : t : r;
6
+ }
7
+ var n = "__atollicHtmlExtractors";
8
+ function r() {
9
+ let e = globalThis;
10
+ return Array.isArray(e[n]) || (e[n] = []), e[n];
11
+ }
12
+ function i(e) {
13
+ let t = r();
14
+ return t.push(e), () => {
15
+ let n = t.indexOf(e);
16
+ n >= 0 && t.splice(n, 1);
17
+ };
18
+ }
19
+ function a(e) {
20
+ for (let t of r()) {
21
+ let n = t(e);
22
+ if (n !== null) return n;
23
+ }
24
+ return typeof e == "string" ? e : null;
25
+ }
26
+ function o(e) {
27
+ let t = e.trimStart();
28
+ return t.startsWith("<html") && !t.startsWith("<!DOCTYPE") ? `<!DOCTYPE html>\n${e}` : e;
29
+ }
30
+ function s(e) {
31
+ let t = e.trimStart();
32
+ return t.startsWith("<!DOCTYPE") || t.startsWith("<html");
33
+ }
34
+ function c(e, n) {
35
+ let r = o(e);
36
+ return n && (r = t(r, n)), r;
37
+ }
38
+ //#endregion
39
+ export { c as a, s as i, a as n, i as o, t as r, o as t };
package/dist/shared.d.ts CHANGED
@@ -1,8 +1,23 @@
1
1
  /** Replace <Head /> marker with tags, or fall back to </head> injection. */
2
2
  export declare function injectHead(html: string, tags: string): string;
3
3
  /**
4
- * Extract HTML from Solid's SSR output format.
5
- * Handles `{t: "<html>"}` objects and arrays of fragments.
4
+ * A function that converts a framework-specific SSR output value into an HTML
5
+ * string, or returns `null` if it doesn't recognize the shape.
6
+ */
7
+ export type HtmlExtractor = (value: unknown) => string | null;
8
+ /**
9
+ * Register a function that extracts an HTML string from a framework-specific
10
+ * SSR output. The atollic Vite plugin wires this up automatically — every
11
+ * `FrameworkAdapter` with an `extractHtml` source string gets registered
12
+ * before the first request is served.
13
+ *
14
+ * @returns A dispose function that removes the extractor.
15
+ */
16
+ export declare function registerHtmlExtractor(fn: HtmlExtractor): () => void;
17
+ /**
18
+ * Convert a server response value into an HTML string by trying each
19
+ * registered framework extractor, then falling back to a plain string.
20
+ * Returns `null` when nothing recognizes the shape.
6
21
  */
7
22
  export declare function extractHtml(value: unknown): string | null;
8
23
  /** Prepend DOCTYPE if the string looks like HTML but is missing it. */
package/dist/vite.js CHANGED
@@ -1,23 +1,23 @@
1
- import { i as e, n as t, r as n, t as r } from "./shared-CfRCLggd.js";
1
+ import { i as e, n as t, r as n, t as r } from "./shared-Bv39_NsU.js";
2
2
  import { readFileSync as i } from "node:fs";
3
3
  import { glob as a, readFile as o } from "node:fs/promises";
4
4
  import { basename as s, dirname as c, extname as l, relative as u, resolve as d } from "node:path";
5
5
  import { fileURLToPath as f } from "node:url";
6
6
  import { transformWithOxc as p } from "vite";
7
7
  //#region src/vite.ts
8
- var m = c(f(import.meta.url)), h = "virtual:atollic/client", g = `\0${h}`, _ = "virtual:atollic/prod-entry", v = `\0${_}`, y = "virtual:atollic/fw-", b = "atollic:reload", x = "?raw-island", S = /\.[jt]sx?$/, C = /\.[jt]sx$/, w = /\.css($|\?)/;
9
- function T(e) {
8
+ var m = c(f(import.meta.url)), h = "virtual:atollic/client", g = `\0${h}`, _ = "virtual:atollic/prod-entry", v = `\0${_}`, y = "virtual:atollic/server-boot", b = `\0${y}`, x = "virtual:atollic/fw-", S = "atollic:reload", C = "?raw-island", w = /\.[jt]sx?$/, T = /\.[jt]sx$/, E = /\.css($|\?)/;
9
+ function D(e) {
10
10
  let t = e.replace(/^\s*(\/\*[\s\S]*?\*\/\s*|\/\/[^\n]*\n\s*)*/g, "").match(/^["']use client(?::(\w+))?["']/);
11
11
  return t ? {
12
12
  hasDirective: !0,
13
13
  framework: t[1]
14
14
  } : { hasDirective: !1 };
15
15
  }
16
- function E(e) {
16
+ function O(e) {
17
17
  return e.replace(/^["']use client(?::\w+)?["'];?\s*/m, "");
18
18
  }
19
- function D(e, t) {
20
- let n = [], r = E(e), i = r.match(/export\s+default\s+(?:function|class)\s+([A-Z]\w*)/);
19
+ function k(e, t) {
20
+ let n = [], r = O(e), i = r.match(/export\s+default\s+(?:function|class)\s+([A-Z]\w*)/);
21
21
  i ? n.push({
22
22
  exportName: "default",
23
23
  islandName: i[1]
@@ -31,7 +31,10 @@ function D(e, t) {
31
31
  });
32
32
  return n;
33
33
  }
34
- function O(e, t, n, r) {
34
+ function A(e) {
35
+ return e.map((e) => e.extractHtml).filter((e) => !!e).map((e) => `registerHtmlExtractor(${e});`).join("\n");
36
+ }
37
+ function j(e, t, n, r) {
35
38
  let i = [...n].map((e) => `import "${e}";`).join("\n"), a = [...t].map((e) => `import "/${u(r, e)}";`).join("\n"), o = /* @__PURE__ */ new Map();
36
39
  for (let [t, n] of Object.entries(e)) {
37
40
  let e = n.adapter.name;
@@ -40,7 +43,7 @@ function O(e, t, n, r) {
40
43
  let s = "import { registerIsland, registerFramework, initRuntime } from \"atollic/client\";\n";
41
44
  s += `${i}\n`, s += `${a}\n\n`;
42
45
  for (let [e, t] of o) {
43
- let n = `${y}${e}`;
46
+ let n = `${x}${e}`;
44
47
  s += `import { hydrateIsland as __hydrate_${e}, renderIsland as __render_${e} } from "${n}";\n`, s += `registerFramework("${e}", __hydrate_${e}, __render_${e});\n\n`;
45
48
  for (let [n, i] of t) {
46
49
  let t = `/${u(r, i.absPath)}`;
@@ -49,7 +52,7 @@ function O(e, t, n, r) {
49
52
  }
50
53
  return s += "\ninitRuntime();\n", s;
51
54
  }
52
- function k(e) {
55
+ function M(e) {
53
56
  let t = new URL(e.url ?? "/", `http://${e.headers.host || "localhost"}`), n = new Headers();
54
57
  for (let [t, r] of Object.entries(e.headers)) if (r) if (Array.isArray(r)) for (let e of r) n.append(t, e);
55
58
  else n.set(t, r);
@@ -61,48 +64,48 @@ function k(e) {
61
64
  e.on("data", (e) => t.enqueue(e)), e.on("end", () => t.close()), e.on("error", (e) => t.error(e));
62
65
  } }), r.duplex = "half"), new Request(t, r);
63
66
  }
64
- function A(e, t) {
67
+ function N(e, t) {
65
68
  let n = [];
66
69
  if (t) for (let e of t) n.push(`<link rel="stylesheet" href="${e}">`);
67
70
  for (let t of e) t.hydrationScript && n.push(t.hydrationScript);
68
71
  return n.push(`<script type="module">import "${h}";<\/script>`), n.join("\n ");
69
72
  }
70
- function j(e, t) {
73
+ function P(e, t) {
71
74
  let n = /* @__PURE__ */ new Set(), r = /* @__PURE__ */ new Set();
72
75
  function i(t) {
73
76
  if (r.has(t)) return;
74
77
  r.add(t);
75
78
  let a = e.moduleGraph.getModuleById(t);
76
79
  if (a) {
77
- w.test(a.url) && n.add(a.url);
80
+ E.test(a.url) && n.add(a.url);
78
81
  for (let e of a.ssrImportedModules) i(e.id ?? e.url);
79
82
  }
80
83
  }
81
84
  let a = e.moduleGraph.getModuleById(t);
82
85
  return a && i(a.id ?? a.url), n;
83
86
  }
84
- function M(u) {
85
- let f = u.frameworks ?? [], w = f[0], M = "", N = {}, P = /* @__PURE__ */ new Set(), F = /* @__PURE__ */ new Set(), I = "", L = 3e3, R = null, z = "", B = !1, V = null;
86
- function H(e) {
87
- return e ? f.find((t) => t.name === e) : w;
87
+ function F(u) {
88
+ let f = u.frameworks ?? [], E = f[0], F = "", I = {}, L = /* @__PURE__ */ new Set(), R = /* @__PURE__ */ new Set(), z = "", B = 3e3, V = null, H = "", U = !1, W = null;
89
+ function G(e) {
90
+ return e ? f.find((t) => t.name === e) : E;
88
91
  }
89
- function U() {
90
- if (!R) return;
91
- let e = R.moduleGraph.getModuleById(g);
92
- e && R.moduleGraph.invalidateModule(e);
92
+ function K() {
93
+ if (!V) return;
94
+ let e = V.moduleGraph.getModuleById(g);
95
+ e && V.moduleGraph.invalidateModule(e);
93
96
  }
94
- function W(e, t) {
95
- N[t] || (N[t] = e, U());
97
+ function q(e, t) {
98
+ I[t] || (I[t] = e, K());
96
99
  }
97
- function G(e, t, n) {
98
- let r = D(t, s(e, l(e)));
99
- for (let t of r) W({
100
+ function J(e, t, n) {
101
+ let r = k(t, s(e, l(e)));
102
+ for (let t of r) q({
100
103
  absPath: e,
101
104
  exportName: t.exportName,
102
105
  adapter: n
103
106
  }, t.islandName);
104
107
  }
105
- let K = {
108
+ let Y = {
106
109
  name: "atollic",
107
110
  enforce: "pre",
108
111
  sharedDuringBuild: !0,
@@ -129,31 +132,31 @@ function M(u) {
129
132
  },
130
133
  async buildApp(e) {
131
134
  await e.build(e.environments.client);
132
- let t = d(M, "dist/client/.vite/manifest.json"), n = JSON.parse(await o(t, "utf-8")), r = n.client ?? n["virtual:atollic/client"], i = [];
135
+ let t = d(F, "dist/client/.vite/manifest.json"), n = JSON.parse(await o(t, "utf-8")), r = n.client ?? n["virtual:atollic/client"], i = [];
133
136
  for (let e of Object.values(n)) if (e.css) for (let t of e.css) i.push(`<link rel="stylesheet" href="/${t}">`);
134
137
  for (let e of f) e.hydrationScript && i.push(e.hydrationScript);
135
- r && i.push(`<script type="module" src="/${r.file}"><\/script>`), z = i.join("\n "), await e.build(e.environments.ssr);
138
+ r && i.push(`<script type="module" src="/${r.file}"><\/script>`), H = i.join("\n "), await e.build(e.environments.ssr);
136
139
  },
137
140
  async configResolved(e) {
138
- M = e.root, I = d(M, u.entry), L = e.server.port ?? 3e3;
141
+ F = e.root, z = d(F, u.entry), B = e.server.port ?? 3e3;
139
142
  },
140
143
  async buildStart() {
141
- if (B) return;
142
- B = !0;
144
+ if (U) return;
145
+ U = !0;
143
146
  let e = /import\s+['"]([^'"]+\.css)['"]/g, t = /import\s.*?from\s+['"](\.\.?\/[^'"]+)['"]/g, n = /* @__PURE__ */ new Set();
144
147
  function r(t, n) {
145
- let r = T(n);
146
- if (r.hasDirective) if (C.test(t)) {
147
- let e = H(r.framework);
148
- e ? G(t, n, e) : r.framework && console.warn(`[atollic] Unknown framework "${r.framework}" in ${t}`);
149
- } else P.add(t);
150
- for (let t of n.matchAll(e)) F.add(t[1]);
148
+ let r = D(n);
149
+ if (r.hasDirective) if (T.test(t)) {
150
+ let e = G(r.framework);
151
+ e ? J(t, n, e) : r.framework && console.warn(`[atollic] Unknown framework "${r.framework}" in ${t}`);
152
+ } else L.add(t);
153
+ for (let t of n.matchAll(e)) R.add(t[1]);
151
154
  }
152
155
  for await (let e of a("**/*.{ts,tsx,js,jsx}", {
153
- cwd: M,
156
+ cwd: F,
154
157
  exclude: ["node_modules/**", "dist/**"]
155
158
  })) {
156
- let t = d(M, e);
159
+ let t = d(F, e);
157
160
  n.add(t);
158
161
  try {
159
162
  r(t, await o(t, "utf-8"));
@@ -183,23 +186,31 @@ function M(u) {
183
186
  } catch {}
184
187
  }
185
188
  }
186
- await i(I);
189
+ await i(z);
187
190
  },
188
191
  resolveId(e) {
189
192
  if (e === h) return g;
190
193
  if (e === _) return v;
191
- if (e.startsWith(y)) return `\0${e}`;
192
- if (e.endsWith(x)) return `${e.slice(0, -11)}${x}`;
194
+ if (e === y) return b;
195
+ if (e.startsWith(x)) return `\0${e}`;
196
+ if (e.endsWith(C)) return `${e.slice(0, -11)}${C}`;
193
197
  },
194
198
  load(e, t) {
195
- if (e === g) return O(N, P, F, M);
196
- if (e === v) return `import { resolve, join } from "node:path";
197
- import { setProductionAssets } from "${d(m, "index.js")}";
198
- setProductionAssets(${JSON.stringify(z)});
199
- import handler from "${I}";
199
+ if (e === g) return j(I, L, R, F);
200
+ if (e === b) {
201
+ let e = d(m, "index.js"), t = A(f);
202
+ return t ? `import { registerHtmlExtractor } from "${e}";\n${t}\n` : "// no html extractors registered\n";
203
+ }
204
+ if (e === v) {
205
+ let e = d(m, "index.js"), t = A(f);
206
+ return `import { resolve, join } from "node:path";
207
+ import { setProductionAssets, registerHtmlExtractor } from "${e}";
208
+ setProductionAssets(${JSON.stringify(H)});
209
+ ${t}
210
+ import handler from "${z}";
200
211
 
201
212
  const clientDir = resolve(import.meta.dirname, "..", "client");
202
- const port = process.env.PORT || ${L};
213
+ const port = process.env.PORT || ${B};
203
214
 
204
215
  Bun.serve({
205
216
  port,
@@ -215,45 +226,48 @@ Bun.serve({
215
226
  });
216
227
  console.log(\`Listening on http://localhost:\${port}\`);
217
228
  `;
218
- if (e.startsWith("\0" + y)) {
219
- let t = e.slice(("\0" + y).length), n = f.find((e) => e.name === t);
229
+ }
230
+ if (e.startsWith("\0" + x)) {
231
+ let t = e.slice(("\0" + x).length), n = f.find((e) => e.name === t);
220
232
  if (n) return n.clientRuntime;
221
233
  }
222
- if (e.endsWith(x)) {
234
+ if (e.endsWith(C)) {
223
235
  let t = e.slice(0, -11);
224
236
  try {
225
- return E(i(t, "utf-8"));
237
+ return O(i(t, "utf-8"));
226
238
  } catch {}
227
239
  return;
228
240
  }
229
- if (!(!t?.ssr || !S.test(e))) try {
230
- let t = i(e, "utf-8"), n = T(t);
241
+ if (!(!t?.ssr || !w.test(e))) try {
242
+ let t = i(e, "utf-8"), n = D(t);
231
243
  if (n.hasDirective) {
232
- if (!C.test(e)) return P.add(e), U(), "export default {};";
233
- let r = H(n.framework);
244
+ if (!T.test(e)) return L.add(e), K(), "export default {};";
245
+ let r = G(n.framework);
234
246
  if (!r) {
235
247
  n.framework && console.warn(`[atollic] Unknown framework "${n.framework}" in ${e}`);
236
248
  return;
237
249
  }
238
- let i = D(t, s(e, l(e)));
239
- for (let t of i) W({
250
+ let i = k(t, s(e, l(e)));
251
+ for (let t of i) q({
240
252
  absPath: e,
241
253
  exportName: t.exportName,
242
254
  adapter: r
243
255
  }, t.islandName);
244
- let a = `${e}${x}`;
256
+ let a = `${e}${C}`;
245
257
  return r.ssrStub(a, i);
246
258
  }
247
259
  } catch {}
248
260
  },
249
261
  configureServer(i) {
250
- R = i, i.middlewares.use(async (a, o, s) => {
262
+ V = i, i.ssrLoadModule(y).catch((e) => {
263
+ console.error("[atollic] Failed to load server boot module:", e);
264
+ }), i.middlewares.use(async (a, o, s) => {
251
265
  let c = a.url || "";
252
266
  if (c.startsWith("/@") || c.startsWith("/node_modules") || c.startsWith("/__") || /\.\w+($|\?)/.test(c)) return s();
253
267
  try {
254
- let l = await i.ssrLoadModule(I), u = l.default ?? l.handler;
268
+ let l = await i.ssrLoadModule(z), u = l.default ?? l.handler;
255
269
  if (typeof u != "function") return console.error("[atollic] Entry must default-export a fetch function: (req: Request) => Response"), s();
256
- let d = await u(k(a));
270
+ let d = await u(M(a));
257
271
  if (d.status === 404) return s();
258
272
  let p = await d.text(), m = d.headers.get("content-type") || "";
259
273
  if (m.includes("application/json")) try {
@@ -261,7 +275,7 @@ console.log(\`Listening on http://localhost:\${port}\`);
261
275
  e && (p = e, m = "");
262
276
  } catch {}
263
277
  let h = e(p);
264
- !m.includes("text/html") && h && (m = "text/html; charset=utf-8"), h && (p = r(p), V ||= j(i, I), p = n(p, A(f, V)), p = await i.transformIndexHtml(c, p));
278
+ !m.includes("text/html") && h && (m = "text/html; charset=utf-8"), h && (p = r(p), W ||= P(i, z), p = n(p, N(f, W)), p = await i.transformIndexHtml(c, p));
265
279
  let g = {};
266
280
  for (let [e, t] of d.headers) g[e] = t;
267
281
  m ? g["content-type"] = m : g["content-type"] ||= "text/html; charset=utf-8", o.writeHead(d.status, g), o.end(p);
@@ -271,23 +285,23 @@ console.log(\`Listening on http://localhost:\${port}\`);
271
285
  });
272
286
  },
273
287
  handleHotUpdate({ file: e, server: t }) {
274
- if (V = null, !e.endsWith(".ts") && !e.endsWith(".tsx")) return;
288
+ if (W = null, !e.endsWith(".ts") && !e.endsWith(".tsx")) return;
275
289
  try {
276
- if (T(i(e, "utf-8")).hasDirective) return;
290
+ if (D(i(e, "utf-8")).hasDirective) return;
277
291
  } catch {}
278
292
  let n = t.moduleGraph.getModulesByFile(e);
279
293
  if (n) for (let e of n) t.moduleGraph.invalidateModule(e);
280
294
  return t.ws.send({
281
295
  type: "custom",
282
- event: b
296
+ event: S
283
297
  }), [];
284
298
  }
285
- }, q = /@jsxImportSource\s+(\S+)/, J = {
299
+ }, X = /@jsxImportSource\s+(\S+)/, Z = {
286
300
  name: "atollic:jsx-pre-transform",
287
301
  enforce: "pre",
288
302
  async transform(e, t) {
289
303
  if (!/\.[jt]sx$/.test(t)) return;
290
- let n = e.match(q);
304
+ let n = e.match(X);
291
305
  if (n) {
292
306
  let e = n[1];
293
307
  for (let t of f) if (e.includes(t.name)) return;
@@ -301,13 +315,13 @@ console.log(\`Listening on http://localhost:\${port}\`);
301
315
  map: r.map
302
316
  };
303
317
  }
304
- }, Y = [];
305
- for (let e of f) e.plugins && Y.push(...e.plugins());
318
+ }, Q = [];
319
+ for (let e of f) e.plugins && Q.push(...e.plugins());
306
320
  return [
307
- J,
308
- ...Y,
309
- K
321
+ Z,
322
+ ...Q,
323
+ Y
310
324
  ];
311
325
  }
312
326
  //#endregion
313
- export { M as atollic };
327
+ export { F as atollic };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "atollic",
3
- "version": "0.0.2",
3
+ "version": "0.0.4",
4
4
  "description": "Island architecture for WinterCG-compatible runtimes. Bring-your-own server (Elysia, Hono, …) and UI framework, powered by Vite.",
5
5
  "keywords": [
6
6
  "wintercg",
@@ -23,15 +23,20 @@
23
23
  "homepage": "https://github.com/awilderink/atollic#readme",
24
24
  "type": "module",
25
25
  "scripts": {
26
- "dev:elysia": "bunx --bun vite --config examples/elysia/vite.config.ts",
27
- "dev:hono": "bunx --bun vite --config examples/hono/vite.config.ts",
26
+ "dev:elysia": "cd examples/elysia && bunx --bun vite",
27
+ "dev:hono": "cd examples/hono && bunx --bun vite",
28
28
  "build": "vite build && tsc",
29
+ "build:examples": "cd examples/elysia && bunx --bun vite build && cd ../hono && bunx --bun vite build",
29
30
  "prepare": "npm run build",
30
31
  "prepublishOnly": "npm run build",
31
32
  "typecheck": "tsc --noEmit && tsc --noEmit --project examples/elysia/tsconfig.json && tsc --noEmit --project examples/hono/tsconfig.json",
32
33
  "format": "biome format --write .",
33
34
  "lint": "biome lint .",
34
- "check": "biome check --write ."
35
+ "check": "biome check --write .",
36
+ "test": "bun test",
37
+ "changeset": "changeset",
38
+ "version": "changeset version",
39
+ "release": "changeset publish"
35
40
  },
36
41
  "exports": {
37
42
  ".": {
@@ -115,6 +120,8 @@
115
120
  },
116
121
  "devDependencies": {
117
122
  "@biomejs/biome": "^2.4.7",
123
+ "@changesets/changelog-github": "^0.5.0",
124
+ "@changesets/cli": "^2.27.10",
118
125
  "@types/node": "^25.5.0",
119
126
  "bun-types": "^1.3.11",
120
127
  "elysia": "^1.4.28",
@@ -1,23 +0,0 @@
1
- //#region src/shared.ts
2
- var e = /<meta\s+name="atollic-head"\s*\/?>/;
3
- function t(t, n) {
4
- let r = t.replace(e, n);
5
- return r === t ? t.includes("</head>") ? t.replace("</head>", ` ${n}\n</head>`) : t : r;
6
- }
7
- function n(e) {
8
- return e && typeof e == "object" && "t" in e && typeof e.t == "string" ? e.t : Array.isArray(e) ? e.flat(Infinity).map((e) => typeof e == "string" ? e : e && typeof e == "object" && "t" in e ? e.t : "").join("") || null : typeof e == "string" ? e : null;
9
- }
10
- function r(e) {
11
- let t = e.trimStart();
12
- return t.startsWith("<html") && !t.startsWith("<!DOCTYPE") ? `<!DOCTYPE html>\n${e}` : e;
13
- }
14
- function i(e) {
15
- let t = e.trimStart();
16
- return t.startsWith("<!DOCTYPE") || t.startsWith("<html");
17
- }
18
- function a(e, n) {
19
- let i = r(e);
20
- return n && (i = t(i, n)), i;
21
- }
22
- //#endregion
23
- export { a, i, n, t as r, r as t };