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 +21 -0
- package/README.md +48 -7
- package/dist/adapter.d.ts +16 -0
- package/dist/adapters/solid.js +2 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.js +3 -2
- package/dist/{server-DhToIUB0.js → server-DAuZqGuP.js} +1 -1
- package/dist/server.d.ts +2 -0
- package/dist/servers/elysia.js +2 -2
- package/dist/servers/hono.js +2 -2
- package/dist/shared-Bv39_NsU.js +39 -0
- package/dist/shared.d.ts +17 -2
- package/dist/vite.js +88 -74
- package/package.json +11 -4
- package/dist/shared-CfRCLggd.js +0 -23
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
|
+
[](https://www.npmjs.com/package/atollic)
|
|
4
|
+
[](https://github.com/awilderink/atollic/actions/workflows/ci.yml)
|
|
5
|
+
[](./LICENSE)
|
|
6
|
+
[](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
|
|
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
|
|
290
|
+
## htmx, Alpine, and other DOM-mutating libraries
|
|
284
291
|
|
|
285
|
-
Islands
|
|
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
|
-
|
|
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
|
|
406
|
-
|
|
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
|
}
|
package/dist/adapters/solid.js
CHANGED
|
@@ -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 {
|
|
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 {
|
|
2
|
-
|
|
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 };
|
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.
|
package/dist/servers/elysia.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { a as e, i as t, n } from "../shared-
|
|
2
|
-
import { t as r } from "../server-
|
|
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() {
|
package/dist/servers/hono.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { a as e, i as t } from "../shared-
|
|
2
|
-
import { t as n } from "../server-
|
|
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
|
-
*
|
|
5
|
-
*
|
|
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-
|
|
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/
|
|
9
|
-
function
|
|
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
|
|
16
|
+
function O(e) {
|
|
17
17
|
return e.replace(/^["']use client(?::\w+)?["'];?\s*/m, "");
|
|
18
18
|
}
|
|
19
|
-
function
|
|
20
|
-
let n = [], r =
|
|
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
|
|
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 = `${
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
85
|
-
let f = u.frameworks ?? [],
|
|
86
|
-
function
|
|
87
|
-
return e ? f.find((t) => t.name === e) :
|
|
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
|
|
90
|
-
if (!
|
|
91
|
-
let e =
|
|
92
|
-
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
|
|
95
|
-
|
|
97
|
+
function q(e, t) {
|
|
98
|
+
I[t] || (I[t] = e, K());
|
|
96
99
|
}
|
|
97
|
-
function
|
|
98
|
-
let r =
|
|
99
|
-
for (let t of r)
|
|
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
|
|
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(
|
|
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>`),
|
|
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
|
-
|
|
141
|
+
F = e.root, z = d(F, u.entry), B = e.server.port ?? 3e3;
|
|
139
142
|
},
|
|
140
143
|
async buildStart() {
|
|
141
|
-
if (
|
|
142
|
-
|
|
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 =
|
|
146
|
-
if (r.hasDirective) if (
|
|
147
|
-
let e =
|
|
148
|
-
e ?
|
|
149
|
-
} else
|
|
150
|
-
for (let t of n.matchAll(e))
|
|
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:
|
|
156
|
+
cwd: F,
|
|
154
157
|
exclude: ["node_modules/**", "dist/**"]
|
|
155
158
|
})) {
|
|
156
|
-
let t = d(
|
|
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(
|
|
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
|
|
192
|
-
if (e.
|
|
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
|
|
196
|
-
if (e ===
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
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 || ${
|
|
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
|
-
|
|
219
|
-
|
|
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(
|
|
234
|
+
if (e.endsWith(C)) {
|
|
223
235
|
let t = e.slice(0, -11);
|
|
224
236
|
try {
|
|
225
|
-
return
|
|
237
|
+
return O(i(t, "utf-8"));
|
|
226
238
|
} catch {}
|
|
227
239
|
return;
|
|
228
240
|
}
|
|
229
|
-
if (!(!t?.ssr || !
|
|
230
|
-
let t = i(e, "utf-8"), n =
|
|
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 (!
|
|
233
|
-
let r =
|
|
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 =
|
|
239
|
-
for (let t of i)
|
|
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}${
|
|
256
|
+
let a = `${e}${C}`;
|
|
245
257
|
return r.ssrStub(a, i);
|
|
246
258
|
}
|
|
247
259
|
} catch {}
|
|
248
260
|
},
|
|
249
261
|
configureServer(i) {
|
|
250
|
-
|
|
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(
|
|
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(
|
|
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),
|
|
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 (
|
|
288
|
+
if (W = null, !e.endsWith(".ts") && !e.endsWith(".tsx")) return;
|
|
275
289
|
try {
|
|
276
|
-
if (
|
|
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:
|
|
296
|
+
event: S
|
|
283
297
|
}), [];
|
|
284
298
|
}
|
|
285
|
-
},
|
|
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(
|
|
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
|
-
},
|
|
305
|
-
for (let e of f) e.plugins &&
|
|
318
|
+
}, Q = [];
|
|
319
|
+
for (let e of f) e.plugins && Q.push(...e.plugins());
|
|
306
320
|
return [
|
|
307
|
-
|
|
308
|
-
...
|
|
309
|
-
|
|
321
|
+
Z,
|
|
322
|
+
...Q,
|
|
323
|
+
Y
|
|
310
324
|
];
|
|
311
325
|
}
|
|
312
326
|
//#endregion
|
|
313
|
-
export {
|
|
327
|
+
export { F as atollic };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "atollic",
|
|
3
|
-
"version": "0.0.
|
|
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
|
|
27
|
-
"dev:hono": "bunx --bun vite
|
|
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",
|
package/dist/shared-CfRCLggd.js
DELETED
|
@@ -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 };
|