@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 +47 -0
- package/dist/client.d.ts +2 -0
- package/dist/client.js +30 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +19 -0
- package/dist/server.d.ts +3 -0
- package/dist/server.js +69 -0
- package/package.json +61 -0
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.
|
package/dist/client.d.ts
ADDED
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 };
|
package/dist/index.d.ts
ADDED
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 };
|
package/dist/server.d.ts
ADDED
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
|
+
}
|