@ilha/astro 0.2.1 → 0.3.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 +7 -9
- package/dist/client.js +4 -45
- package/dist/index.js +0 -7
- package/dist/server.js +10 -67
- package/package.json +8 -6
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# `@ilha/astro`
|
|
2
2
|
|
|
3
|
-
An [Astro](https://astro.build) integration that renders and hydrates [Ilha](https://github.com/ilhajs/ilha)
|
|
3
|
+
An [Astro](https://astro.build) integration that renders and hydrates [Ilha](https://github.com/ilhajs/ilha) components as Astro islands.
|
|
4
4
|
|
|
5
5
|
---
|
|
6
6
|
|
|
@@ -34,9 +34,7 @@ export default defineConfig({
|
|
|
34
34
|
|
|
35
35
|
You can also pass `exclude` globs. Files outside the filter remain available to other Astro JSX renderers.
|
|
36
36
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
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:
|
|
37
|
+
Then use any Ilha component as an Astro component, with a [client directive](https://docs.astro.build/en/reference/directives-reference/#client-directives) to control hydration:
|
|
40
38
|
|
|
41
39
|
```astro
|
|
42
40
|
---
|
|
@@ -48,12 +46,12 @@ import { Counter } from "../islands/counter";
|
|
|
48
46
|
|
|
49
47
|
## How it works
|
|
50
48
|
|
|
51
|
-
- **Server-side:** the
|
|
52
|
-
- **Client-side:**
|
|
49
|
+
- **Server-side:** the renderer calls `renderToString()`, which wraps output in a `[data-ilha]` element with a state snapshot.
|
|
50
|
+
- **Client-side:** the renderer finds that host inside Astro's island wrapper and calls `mount(..., { hydrate: true })`.
|
|
53
51
|
|
|
54
|
-
|
|
52
|
+
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.
|
|
55
53
|
|
|
56
54
|
## Notes
|
|
57
55
|
|
|
58
|
-
-
|
|
59
|
-
- `client:only` skips SSR
|
|
56
|
+
- Astro slots arrive as string children. Plain text renders; HTML in slot content is escaped — don't pass markup through slots.
|
|
57
|
+
- `client:only` skips SSR and mounts fresh in the browser.
|
package/dist/client.js
CHANGED
|
@@ -1,54 +1,13 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { mount } from "ilha";
|
|
2
2
|
|
|
3
3
|
//#region src/client.ts
|
|
4
|
-
const RAW = Symbol.for("ilha.raw");
|
|
5
|
-
function isMountable(Component) {
|
|
6
|
-
return !!Component && typeof Component.mount === "function";
|
|
7
|
-
}
|
|
8
|
-
function isRawHtml(value) {
|
|
9
|
-
return !!(value && typeof value === "object" && RAW in value);
|
|
10
|
-
}
|
|
11
|
-
function wrapPlainAsIsland(Component, merged) {
|
|
12
|
-
return ilha(() => {
|
|
13
|
-
const result = Component(merged);
|
|
14
|
-
if (result == null) return raw("");
|
|
15
|
-
if (typeof result === "string") return raw(result);
|
|
16
|
-
if (isRawHtml(result)) return raw(result.value);
|
|
17
|
-
return raw("");
|
|
18
|
-
});
|
|
19
|
-
}
|
|
20
4
|
var client_default = (element) => async (Component, props, slotted, _metadata) => {
|
|
21
|
-
if (!element.hasAttribute("ssr")) {
|
|
22
|
-
if (isMountable(Component)) {
|
|
23
|
-
const unmount = Component.mount(element, props);
|
|
24
|
-
element.addEventListener("astro:unmount", () => unmount(), { once: true });
|
|
25
|
-
}
|
|
26
|
-
return;
|
|
27
|
-
}
|
|
28
|
-
if (isMountable(Component)) {
|
|
29
|
-
const host = element.querySelector("[data-ilha]") ?? element;
|
|
30
|
-
const unmount = Component.mount(host);
|
|
31
|
-
element.addEventListener("astro:unmount", () => unmount(), { once: true });
|
|
32
|
-
return;
|
|
33
|
-
}
|
|
34
5
|
if (typeof Component !== "function") return;
|
|
35
6
|
const merged = { ...props };
|
|
36
7
|
for (const [name, value] of Object.entries(slotted ?? {})) merged[name === "default" ? "children" : name] = value;
|
|
37
|
-
const
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
const unmount = wrapPlainAsIsland(Component, merged).mount(nestedHost);
|
|
41
|
-
element.addEventListener("astro:unmount", () => unmount(), { once: true });
|
|
42
|
-
} catch {
|
|
43
|
-
throw new Error(`[@ilha/astro] Failed to mount nested islands inside plain component — expected a function that returns ilha RawHtml/string.`);
|
|
44
|
-
}
|
|
45
|
-
return;
|
|
46
|
-
}
|
|
47
|
-
try {
|
|
48
|
-
Component(merged);
|
|
49
|
-
} catch {
|
|
50
|
-
throw new Error(`[@ilha/astro] Failed to bind client component — expected a function that can re-run against SSR markup.`);
|
|
51
|
-
}
|
|
8
|
+
const nested = element.querySelector("[data-ilha]");
|
|
9
|
+
const unmount = mount(nested ?? element, () => Component(merged), { hydrate: !!nested });
|
|
10
|
+
element.addEventListener("astro:unmount", () => unmount(), { once: true });
|
|
52
11
|
};
|
|
53
12
|
|
|
54
13
|
//#endregion
|
package/dist/index.js
CHANGED
|
@@ -1,10 +1,4 @@
|
|
|
1
1
|
//#region src/index.ts
|
|
2
|
-
const ASTRO_RENDERER_GLOBAL = Symbol.for("ilha.astroRenderer");
|
|
3
|
-
const rendererName = "@ilha/astro";
|
|
4
|
-
function setRendererMarker() {
|
|
5
|
-
globalThis[ASTRO_RENDERER_GLOBAL] = rendererName;
|
|
6
|
-
}
|
|
7
|
-
setRendererMarker();
|
|
8
2
|
const OPTIONS_MODULE = "virtual:@ilha/astro/options";
|
|
9
3
|
function globToRegExp(pattern) {
|
|
10
4
|
let source = "^";
|
|
@@ -63,7 +57,6 @@ function ilhaIntegration(options = {}) {
|
|
|
63
57
|
return {
|
|
64
58
|
name: "@ilha/astro",
|
|
65
59
|
hooks: { "astro:config:setup": ({ addRenderer, updateConfig }) => {
|
|
66
|
-
setRendererMarker();
|
|
67
60
|
addRenderer(getRenderer());
|
|
68
61
|
updateConfig({ vite: getViteConfig(options) });
|
|
69
62
|
} }
|
package/dist/server.js
CHANGED
|
@@ -1,45 +1,12 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { renderToString } from "ilha";
|
|
2
2
|
|
|
3
3
|
//#region src/server.ts
|
|
4
|
-
const ISLAND = Symbol.for("ilha.island");
|
|
5
|
-
const RAW = Symbol.for("ilha.raw");
|
|
6
4
|
let componentFilter;
|
|
7
5
|
function getComponentFilter() {
|
|
8
6
|
return componentFilter ??= import("virtual:@ilha/astro/options").then(({ default: filter }) => filter).catch(() => () => true);
|
|
9
7
|
}
|
|
10
|
-
function isIsland(Component) {
|
|
11
|
-
return typeof Component === "function" && ISLAND in Component;
|
|
12
|
-
}
|
|
13
|
-
function isRawHtml(value) {
|
|
14
|
-
return !!(value && typeof value === "object" && RAW in value);
|
|
15
|
-
}
|
|
16
|
-
function resultToHtml(result) {
|
|
17
|
-
if (typeof result === "string") return result;
|
|
18
|
-
if (isRawHtml(result)) return result.value;
|
|
19
|
-
}
|
|
20
|
-
/** True when plain SSR emitted nested island slot hosts that need a parent mount ctx. */
|
|
21
|
-
function hasNestedIslandSlots(html) {
|
|
22
|
-
return html.includes("data-ilha-slot");
|
|
23
|
-
}
|
|
24
|
-
/**
|
|
25
|
-
* Wrap a plain function so its render runs under an island render ctx.
|
|
26
|
-
* Nested islands then get unique `data-ilha-slot` ids and can be mounted
|
|
27
|
-
* when the shell hydrates on the client.
|
|
28
|
-
*/
|
|
29
|
-
function wrapPlainAsIsland(Component, merged) {
|
|
30
|
-
return ilha(() => {
|
|
31
|
-
const result = Component(merged);
|
|
32
|
-
if (result == null) return raw("");
|
|
33
|
-
if (typeof result === "string") return raw(result);
|
|
34
|
-
if (isRawHtml(result)) return raw(result.value);
|
|
35
|
-
return raw("");
|
|
36
|
-
});
|
|
37
|
-
}
|
|
38
|
-
function toRawHtml(value) {
|
|
39
|
-
return raw(String(value));
|
|
40
|
-
}
|
|
41
8
|
function isAstroRenderable(value) {
|
|
42
|
-
return !!value && typeof value === "object" &&
|
|
9
|
+
return !!value && typeof value === "object" && typeof value.render === "function";
|
|
43
10
|
}
|
|
44
11
|
async function resolveAstroRenderable(value) {
|
|
45
12
|
let out = "";
|
|
@@ -49,48 +16,24 @@ async function resolveAstroRenderable(value) {
|
|
|
49
16
|
return out;
|
|
50
17
|
}
|
|
51
18
|
async function resolvePropValue(value) {
|
|
52
|
-
if (isAstroRenderable(value)) return
|
|
19
|
+
if (isAstroRenderable(value)) return await resolveAstroRenderable(value);
|
|
53
20
|
if (Array.isArray(value)) return Promise.all(value.map(resolvePropValue));
|
|
54
21
|
return value;
|
|
55
22
|
}
|
|
56
|
-
async function
|
|
23
|
+
async function mergeProps(props, slots) {
|
|
57
24
|
const resolved = {};
|
|
58
25
|
for (const [key, value] of Object.entries(props)) resolved[key] = await resolvePropValue(value);
|
|
26
|
+
for (const [name, value] of Object.entries(slots)) resolved[name === "default" ? "children" : name] = value;
|
|
59
27
|
return resolved;
|
|
60
28
|
}
|
|
61
|
-
async function
|
|
62
|
-
const merged = await resolveProps(props);
|
|
63
|
-
for (const [name, value] of Object.entries(slots)) merged[name === "default" ? "children" : name] = toRawHtml(value);
|
|
64
|
-
return merged;
|
|
65
|
-
}
|
|
66
|
-
async function renderPlainComponent(Component, props, slots, metadata) {
|
|
67
|
-
const merged = await mergePlainProps(props, slots);
|
|
68
|
-
const html = resultToHtml(Component(merged));
|
|
69
|
-
if (html === void 0) return void 0;
|
|
70
|
-
if (!hasNestedIslandSlots(html)) return html;
|
|
71
|
-
return wrapPlainAsIsland(Component, merged).hydratable({}, {
|
|
72
|
-
name: metadata?.displayName ?? "IlhaPlain",
|
|
73
|
-
snapshot: true
|
|
74
|
-
});
|
|
75
|
-
}
|
|
76
|
-
async function check(Component, props, slots, metadata) {
|
|
29
|
+
async function check(Component, _props, _slots, metadata) {
|
|
77
30
|
if (metadata?.componentUrl && !(await getComponentFilter())(metadata.componentUrl)) return false;
|
|
78
|
-
|
|
79
|
-
if (typeof Component !== "function") return false;
|
|
80
|
-
try {
|
|
81
|
-
return await renderPlainComponent(Component, props, slots) !== void 0;
|
|
82
|
-
} catch {
|
|
83
|
-
return false;
|
|
84
|
-
}
|
|
31
|
+
return typeof Component === "function";
|
|
85
32
|
}
|
|
86
33
|
async function renderToStaticMarkup(Component, props, slots, metadata) {
|
|
87
|
-
if (
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
}) };
|
|
91
|
-
const html = await renderPlainComponent(Component, props, slots, metadata);
|
|
92
|
-
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.`);
|
|
93
|
-
return { html };
|
|
34
|
+
if (typeof Component !== "function") throw new Error(`[@ilha/astro] Unable to render "${metadata?.displayName ?? "component"}" — expected a function.`);
|
|
35
|
+
const merged = await mergeProps(props, slots);
|
|
36
|
+
return { html: await renderToString(() => Component(merged)) };
|
|
94
37
|
}
|
|
95
38
|
const renderer = {
|
|
96
39
|
name: "@ilha/astro",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ilha/astro",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "Astro integration for Ilha islands",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"astro-component",
|
|
@@ -27,6 +27,7 @@
|
|
|
27
27
|
],
|
|
28
28
|
"type": "module",
|
|
29
29
|
"sideEffects": false,
|
|
30
|
+
"main": "./dist/index.js",
|
|
30
31
|
"module": "./dist/index.js",
|
|
31
32
|
"types": "./dist/index.d.ts",
|
|
32
33
|
"exports": {
|
|
@@ -49,14 +50,15 @@
|
|
|
49
50
|
"scripts": {
|
|
50
51
|
"build": "tsdown && tsc -p tsconfig.build.json",
|
|
51
52
|
"cleanup": "rimraf dist node_modules",
|
|
52
|
-
"test": "bun test"
|
|
53
|
+
"test": "bun test src/index.test.ts",
|
|
54
|
+
"test:integration": "bun test src/integration.test.ts"
|
|
53
55
|
},
|
|
54
56
|
"devDependencies": {
|
|
55
|
-
"astro": "^7.2.
|
|
56
|
-
"ilha": "
|
|
57
|
+
"astro": "^7.2.9",
|
|
58
|
+
"ilha": "0.14.0"
|
|
57
59
|
},
|
|
58
60
|
"peerDependencies": {
|
|
59
|
-
"astro": "^7.2.
|
|
60
|
-
"ilha": "
|
|
61
|
+
"astro": "^7.2.9",
|
|
62
|
+
"ilha": "^0.14.0"
|
|
61
63
|
}
|
|
62
64
|
}
|