@ilha/astro 0.2.2 → 0.3.1
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 -32
- package/dist/index.js +0 -7
- package/dist/server.js +10 -43
- package/package.json +6 -4
- package/dist/plain-Dy-BNLLq.js +0 -24
- package/dist/plain.d.ts +0 -8
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,41 +1,13 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { mount } from "ilha";
|
|
2
2
|
|
|
3
3
|
//#region src/client.ts
|
|
4
|
-
function isMountable(Component) {
|
|
5
|
-
return !!Component && typeof Component.mount === "function";
|
|
6
|
-
}
|
|
7
4
|
var client_default = (element) => async (Component, props, slotted, _metadata) => {
|
|
8
|
-
if (!element.hasAttribute("ssr")) {
|
|
9
|
-
if (isMountable(Component)) {
|
|
10
|
-
const unmount = Component.mount(element, props);
|
|
11
|
-
element.addEventListener("astro:unmount", () => unmount(), { once: true });
|
|
12
|
-
}
|
|
13
|
-
return;
|
|
14
|
-
}
|
|
15
|
-
if (isMountable(Component)) {
|
|
16
|
-
const host = element.querySelector("[data-ilha]") ?? element;
|
|
17
|
-
const unmount = Component.mount(host);
|
|
18
|
-
element.addEventListener("astro:unmount", () => unmount(), { once: true });
|
|
19
|
-
return;
|
|
20
|
-
}
|
|
21
5
|
if (typeof Component !== "function") return;
|
|
22
6
|
const merged = { ...props };
|
|
23
7
|
for (const [name, value] of Object.entries(slotted ?? {})) merged[name === "default" ? "children" : name] = value;
|
|
24
|
-
const
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
const unmount = wrapPlainAsIsland(Component, merged).mount(nestedHost);
|
|
28
|
-
element.addEventListener("astro:unmount", () => unmount(), { once: true });
|
|
29
|
-
} catch {
|
|
30
|
-
throw new Error(`[@ilha/astro] Failed to mount nested islands inside plain component — expected a function that returns ilha RawHtml/string.`);
|
|
31
|
-
}
|
|
32
|
-
return;
|
|
33
|
-
}
|
|
34
|
-
try {
|
|
35
|
-
Component(merged);
|
|
36
|
-
} catch {
|
|
37
|
-
throw new Error(`[@ilha/astro] Failed to bind client component — expected a function that can re-run against SSR markup.`);
|
|
38
|
-
}
|
|
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 });
|
|
39
11
|
};
|
|
40
12
|
|
|
41
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,20 +1,12 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { raw } from "ilha";
|
|
1
|
+
import { renderToString } from "ilha";
|
|
3
2
|
|
|
4
3
|
//#region src/server.ts
|
|
5
4
|
let componentFilter;
|
|
6
5
|
function getComponentFilter() {
|
|
7
6
|
return componentFilter ??= import("virtual:@ilha/astro/options").then(({ default: filter }) => filter).catch(() => () => true);
|
|
8
7
|
}
|
|
9
|
-
/** True when plain SSR emitted nested island slot hosts that need a parent mount ctx. */
|
|
10
|
-
function hasNestedIslandSlots(html) {
|
|
11
|
-
return html.includes("data-ilha-slot");
|
|
12
|
-
}
|
|
13
|
-
function toRawHtml(value) {
|
|
14
|
-
return raw(String(value));
|
|
15
|
-
}
|
|
16
8
|
function isAstroRenderable(value) {
|
|
17
|
-
return !!value && typeof value === "object" &&
|
|
9
|
+
return !!value && typeof value === "object" && typeof value.render === "function";
|
|
18
10
|
}
|
|
19
11
|
async function resolveAstroRenderable(value) {
|
|
20
12
|
let out = "";
|
|
@@ -24,49 +16,24 @@ async function resolveAstroRenderable(value) {
|
|
|
24
16
|
return out;
|
|
25
17
|
}
|
|
26
18
|
async function resolvePropValue(value) {
|
|
27
|
-
if (isAstroRenderable(value)) return
|
|
19
|
+
if (isAstroRenderable(value)) return await resolveAstroRenderable(value);
|
|
28
20
|
if (Array.isArray(value)) return Promise.all(value.map(resolvePropValue));
|
|
29
21
|
return value;
|
|
30
22
|
}
|
|
31
|
-
async function
|
|
23
|
+
async function mergeProps(props, slots) {
|
|
32
24
|
const resolved = {};
|
|
33
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;
|
|
34
27
|
return resolved;
|
|
35
28
|
}
|
|
36
|
-
async function
|
|
37
|
-
const merged = await resolveProps(props);
|
|
38
|
-
for (const [name, value] of Object.entries(slots)) merged[name === "default" ? "children" : name] = toRawHtml(value);
|
|
39
|
-
return merged;
|
|
40
|
-
}
|
|
41
|
-
async function renderPlainComponent(Component, props, slots, metadata) {
|
|
42
|
-
const merged = await mergePlainProps(props, slots);
|
|
43
|
-
const result = Component(merged);
|
|
44
|
-
const html = resultToHtml(result);
|
|
45
|
-
if (html === void 0) return void 0;
|
|
46
|
-
if (!hasNestedIslandSlots(html)) return html;
|
|
47
|
-
return wrapPlainAsIsland(Component, merged).hydratable({}, {
|
|
48
|
-
name: metadata?.displayName ?? "IlhaPlain",
|
|
49
|
-
snapshot: true
|
|
50
|
-
});
|
|
51
|
-
}
|
|
52
|
-
async function check(Component, props, slots, metadata) {
|
|
29
|
+
async function check(Component, _props, _slots, metadata) {
|
|
53
30
|
if (metadata?.componentUrl && !(await getComponentFilter())(metadata.componentUrl)) return false;
|
|
54
|
-
|
|
55
|
-
if (typeof Component !== "function") return false;
|
|
56
|
-
try {
|
|
57
|
-
return await renderPlainComponent(Component, props, slots) !== void 0;
|
|
58
|
-
} catch {
|
|
59
|
-
return false;
|
|
60
|
-
}
|
|
31
|
+
return typeof Component === "function";
|
|
61
32
|
}
|
|
62
33
|
async function renderToStaticMarkup(Component, props, slots, metadata) {
|
|
63
|
-
if (
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
}) };
|
|
67
|
-
const html = await renderPlainComponent(Component, props, slots, metadata);
|
|
68
|
-
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.`);
|
|
69
|
-
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)) };
|
|
70
37
|
}
|
|
71
38
|
const renderer = {
|
|
72
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.1",
|
|
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
57
|
"astro": "^7.2.9",
|
|
56
|
-
"ilha": "0.
|
|
58
|
+
"ilha": "0.14.1"
|
|
57
59
|
},
|
|
58
60
|
"peerDependencies": {
|
|
59
61
|
"astro": "^7.2.9",
|
|
60
|
-
"ilha": "^0.
|
|
62
|
+
"ilha": "^0.14.1"
|
|
61
63
|
}
|
|
62
64
|
}
|
package/dist/plain-Dy-BNLLq.js
DELETED
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
import { ilha, raw } from "ilha";
|
|
2
|
-
|
|
3
|
-
//#region src/plain.ts
|
|
4
|
-
const ISLAND = Symbol.for("ilha.island");
|
|
5
|
-
const RAW = Symbol.for("ilha.raw");
|
|
6
|
-
function isIsland(Component) {
|
|
7
|
-
return typeof Component === "function" && ISLAND in Component;
|
|
8
|
-
}
|
|
9
|
-
function isRawHtml(value) {
|
|
10
|
-
return !!(value && typeof value === "object" && RAW in value);
|
|
11
|
-
}
|
|
12
|
-
function resultToHtml(result) {
|
|
13
|
-
if (typeof result === "string") return result;
|
|
14
|
-
if (isRawHtml(result)) return result.value;
|
|
15
|
-
}
|
|
16
|
-
function wrapPlainAsIsland(Component, merged) {
|
|
17
|
-
return ilha(() => {
|
|
18
|
-
const html = resultToHtml(Component(merged));
|
|
19
|
-
return html === void 0 ? raw("") : raw(html);
|
|
20
|
-
});
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
//#endregion
|
|
24
|
-
export { wrapPlainAsIsland as i, isRawHtml as n, resultToHtml as r, isIsland as t };
|
package/dist/plain.d.ts
DELETED
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
import { type Island } from "ilha";
|
|
2
|
-
export type PlainComponent = (props: Record<string, unknown>) => unknown;
|
|
3
|
-
export declare function isIsland(Component: unknown): Component is Island;
|
|
4
|
-
export declare function isRawHtml(value: unknown): value is {
|
|
5
|
-
value: string;
|
|
6
|
-
};
|
|
7
|
-
export declare function resultToHtml(result: unknown): string | undefined;
|
|
8
|
-
export declare function wrapPlainAsIsland(Component: PlainComponent, merged: Record<string, unknown>): Island<unknown>;
|