@ilha/astro 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,47 @@
1
+ # `@ilha/astro`
2
+
3
+ An [Astro](https://astro.build) integration that renders and hydrates [Ilha](https://github.com/ilhajs/ilha) islands as Astro components.
4
+
5
+ ---
6
+
7
+ ## Installation
8
+
9
+ ```bash
10
+ bun add @ilha/astro
11
+ ```
12
+
13
+ ---
14
+
15
+ ## Usage
16
+
17
+ ```ts
18
+ // astro.config.mjs
19
+ import { defineConfig } from "astro/config";
20
+ import ilha from "@ilha/astro";
21
+
22
+ export default defineConfig({
23
+ integrations: [ilha()],
24
+ });
25
+ ```
26
+
27
+ Then use any Ilha island as an Astro component, with a [client directive](https://docs.astro.build/en/reference/directives-reference/#client-directives) to control hydration:
28
+
29
+ ```astro
30
+ ---
31
+ import { Counter } from "../islands/counter";
32
+ ---
33
+
34
+ <Counter client:load start={10} />
35
+ ```
36
+
37
+ ## How it works
38
+
39
+ - **Server-side:** the component is rendered with [`.hydratable()`](https://github.com/ilhajs/ilha), which wraps the island's SSR output in a `[data-ilha]` element carrying serialized props and a state snapshot.
40
+ - **Client-side:** on hydration, the renderer finds that `[data-ilha]` element inside Astro's island wrapper and calls the island's own `.mount()` — no re-render, no flicker, `.onMount()` is skipped since the snapshot already matches the DOM.
41
+
42
+ Because hydration is driven entirely by Ilha's own `data-ilha-*` attributes, every [Astro client directive](https://docs.astro.build/en/reference/directives-reference/#client-directives) (`client:load`, `client:idle`, `client:visible`, `client:media`, `client:only`) works as expected.
43
+
44
+ ## Notes
45
+
46
+ - Ilha islands don't support Astro's `<slot />` / children forwarding — render everything through the island's own `.render()` function and props.
47
+ - `client:only` skips SSR entirely and mounts fresh in the browser, matching Astro's usual behavior for other framework renderers.
@@ -0,0 +1,2 @@
1
+ declare const _default: (element: HTMLElement) => (Component: unknown, props: Record<string, unknown>, slotted: Record<string, string>, _metadata: Record<string, string>) => Promise<void>;
2
+ export default _default;
package/dist/client.js ADDED
@@ -0,0 +1,30 @@
1
+ //#region src/client.ts
2
+ function isMountable(Component) {
3
+ return !!Component && typeof Component.mount === "function";
4
+ }
5
+ var client_default = (element) => async (Component, props, slotted, _metadata) => {
6
+ if (!element.hasAttribute("ssr")) {
7
+ if (isMountable(Component)) {
8
+ const unmount = Component.mount(element, props);
9
+ element.addEventListener("astro:unmount", () => unmount(), { once: true });
10
+ }
11
+ return;
12
+ }
13
+ if (isMountable(Component)) {
14
+ const host = element.querySelector("[data-ilha]") ?? element;
15
+ const unmount = Component.mount(host);
16
+ element.addEventListener("astro:unmount", () => unmount(), { once: true });
17
+ return;
18
+ }
19
+ if (typeof Component !== "function") return;
20
+ const merged = { ...props };
21
+ for (const [name, value] of Object.entries(slotted ?? {})) merged[name === "default" ? "children" : name] = value;
22
+ try {
23
+ Component(merged);
24
+ } catch {
25
+ throw new Error(`[@ilha/astro] Failed to bind client component — expected a function that can re-run against SSR markup.`);
26
+ }
27
+ };
28
+
29
+ //#endregion
30
+ export { client_default as default };
@@ -0,0 +1,3 @@
1
+ import type { AstroIntegration, AstroRenderer } from "astro";
2
+ export declare function getRenderer(): AstroRenderer;
3
+ export default function ilhaIntegration(): AstroIntegration;
package/dist/index.js ADDED
@@ -0,0 +1,19 @@
1
+ //#region src/index.ts
2
+ function getRenderer() {
3
+ return {
4
+ name: "@ilha/astro",
5
+ clientEntrypoint: "@ilha/astro/client.js",
6
+ serverEntrypoint: "@ilha/astro/server.js"
7
+ };
8
+ }
9
+ function ilhaIntegration() {
10
+ return {
11
+ name: "@ilha/astro",
12
+ hooks: { "astro:config:setup": ({ addRenderer }) => {
13
+ addRenderer(getRenderer());
14
+ } }
15
+ };
16
+ }
17
+
18
+ //#endregion
19
+ export { ilhaIntegration as default, getRenderer };
@@ -0,0 +1,3 @@
1
+ import type { NamedSSRLoadedRendererValue } from "astro";
2
+ declare const renderer: NamedSSRLoadedRendererValue;
3
+ export default renderer;
package/dist/server.js ADDED
@@ -0,0 +1,69 @@
1
+ //#region src/server.ts
2
+ const ISLAND = Symbol.for("ilha.island");
3
+ const RAW = Symbol.for("ilha.raw");
4
+ function isIsland(Component) {
5
+ return typeof Component === "function" && ISLAND in Component;
6
+ }
7
+ function isRawHtml(value) {
8
+ return !!(value && typeof value === "object" && RAW in value);
9
+ }
10
+ function toRawHtml(value) {
11
+ return {
12
+ [RAW]: true,
13
+ value: String(value)
14
+ };
15
+ }
16
+ function isAstroRenderable(value) {
17
+ return !!value && typeof value === "object" && !isRawHtml(value) && typeof value.render === "function";
18
+ }
19
+ async function resolveAstroRenderable(value) {
20
+ let out = "";
21
+ await value.render({ write(chunk) {
22
+ if (chunk != null) out += String(chunk);
23
+ } });
24
+ return out;
25
+ }
26
+ async function resolvePropValue(value) {
27
+ if (isAstroRenderable(value)) return toRawHtml(await resolveAstroRenderable(value));
28
+ if (Array.isArray(value)) return Promise.all(value.map(resolvePropValue));
29
+ return value;
30
+ }
31
+ async function resolveProps(props) {
32
+ const resolved = {};
33
+ for (const [key, value] of Object.entries(props)) resolved[key] = await resolvePropValue(value);
34
+ return resolved;
35
+ }
36
+ async function renderPlainComponent(Component, props, slots) {
37
+ const merged = await resolveProps(props);
38
+ for (const [name, value] of Object.entries(slots)) merged[name === "default" ? "children" : name] = toRawHtml(value);
39
+ const result = Component(merged);
40
+ if (typeof result === "string") return result;
41
+ if (isRawHtml(result)) return result.value;
42
+ }
43
+ async function check(Component, props, slots) {
44
+ if (isIsland(Component)) return true;
45
+ if (typeof Component !== "function") return false;
46
+ try {
47
+ return await renderPlainComponent(Component, props, slots) !== void 0;
48
+ } catch {
49
+ return false;
50
+ }
51
+ }
52
+ async function renderToStaticMarkup(Component, props, slots, metadata) {
53
+ if (isIsland(Component)) return { html: await Component.hydratable(await resolveProps(props), {
54
+ name: metadata?.displayName ?? "IlhaIsland",
55
+ snapshot: true,
56
+ skipOnMount: true
57
+ }) };
58
+ const html = await renderPlainComponent(Component, props, slots);
59
+ if (html === void 0) throw new Error(`[@ilha/astro] Unable to render "${metadata?.displayName ?? "component"}" — expected an ilha island or a function returning ilha RawHtml/string.`);
60
+ return { html };
61
+ }
62
+ const renderer = {
63
+ name: "@ilha/astro",
64
+ check,
65
+ renderToStaticMarkup
66
+ };
67
+
68
+ //#endregion
69
+ export { renderer as default };
package/package.json ADDED
@@ -0,0 +1,61 @@
1
+ {
2
+ "name": "@ilha/astro",
3
+ "version": "0.1.0",
4
+ "description": "Astro integration for Ilha islands",
5
+ "keywords": [
6
+ "astro-component",
7
+ "astro-integration",
8
+ "frontend",
9
+ "ilha",
10
+ "island-architecture",
11
+ "islands",
12
+ "ssr",
13
+ "withastro"
14
+ ],
15
+ "homepage": "https://github.com/ilhajs/ilha/tree/main/packages/astro#readme",
16
+ "bugs": {
17
+ "url": "https://github.com/ilhajs/ilha/issues"
18
+ },
19
+ "license": "MIT",
20
+ "author": "Ryuz <ryuzer@proton.me>",
21
+ "repository": {
22
+ "type": "git",
23
+ "url": "git+https://github.com/ilhajs/ilha.git"
24
+ },
25
+ "files": [
26
+ "dist"
27
+ ],
28
+ "type": "module",
29
+ "sideEffects": false,
30
+ "module": "./dist/index.js",
31
+ "types": "./dist/index.d.ts",
32
+ "exports": {
33
+ ".": {
34
+ "types": "./dist/index.d.ts",
35
+ "import": "./dist/index.js"
36
+ },
37
+ "./client.js": {
38
+ "types": "./dist/client.d.ts",
39
+ "import": "./dist/client.js"
40
+ },
41
+ "./server.js": {
42
+ "types": "./dist/server.d.ts",
43
+ "import": "./dist/server.js"
44
+ }
45
+ },
46
+ "publishConfig": {
47
+ "access": "public"
48
+ },
49
+ "scripts": {
50
+ "build": "tsdown && tsc -p tsconfig.build.json",
51
+ "test": "bun test"
52
+ },
53
+ "devDependencies": {
54
+ "astro": "^7.1.3",
55
+ "ilha": "^0.9.5"
56
+ },
57
+ "peerDependencies": {
58
+ "astro": "^7.0.0",
59
+ "ilha": ">=0.9.5"
60
+ }
61
+ }