Islands API
Server-side island rendering for partial hydration.
island (ComponentRef-based, recommended)
Create an island element using a type-safe ComponentRef. Factory functions are auto-generated by sol generate from client Props types.
// Auto-generated: app/__gen__/types/types.mbt
pub struct CounterProps { initial_count : Int } derive(ToJson, FromJson)
pub fn counter(props : CounterProps, trigger~ : @luna.Trigger) -> @luna.ComponentRef[CounterProps]
// Server-side usage
let counter_props : @types.CounterProps = { initial_count: 42 }
@sol.island(
@types.counter(counter_props),
[div([button([text("Count: 42")])])],
)
Signature
fn[T : ToJson, E] island(
cref : @luna.ComponentRef[T],
children : Array[@luna.Node[E, String]],
) -> @luna.Node[E, String]
Parameters
| Parameter | Type | Description |
|---|---|---|
cref | ComponentRef[T] | Type-safe component reference (from generated factory) |
children | Array[Node] | Server-rendered fallback content |
island_with
Variant that accepts a render function instead of children array:
@sol.island_with(
@types.counter(counter_props),
fn() { div([button([text("Count: 42")])]) },
)
Alternative: @server_dom.client()
Equivalent API provided by Luna's static DOM package:
@server_dom.client(
@types.counter(counter_props),
[div([text("Loading...")])],
)
island_raw (string-based, low-level)
Create an island element with raw string parameters. Use island() with ComponentRef for type safety.
@sol.island_raw(
"counter",
"/components/counter.js",
initial.to_string(),
[@element.div([@element.button([@element.text("Count: \{initial}")])])],
trigger=@luna.Load,
)
Signature
fn island_raw(
id : String,
url : String,
state : String,
children : Array[@luna.Node],
trigger~ : Trigger = Load,
) -> @luna.Node
Parameters
| Parameter | Type | Description |
|---|---|---|
id | String | Component identifier (matches luna:id) |
url | String | JavaScript module URL |
state | String | Serialized props (JSON) |
children | Array[Node] | Server-rendered content |
trigger | Trigger | When to hydrate (default: Load) |
HTML Output
<div
luna:id="counter"
luna:url="/components/counter.js"
luna:state="0"
luna:client-trigger="load"
>
<div><button>Count: 0</button></div>
</div>
Example with Visible Trigger
// Lazy-loaded island - hydrates when scrolled into view
@sol.island(
@types.lazy({}, trigger=@luna.TriggerType::Visible),
[@element.text("Lazy content")],
)
// Output: luna:client-trigger="visible"
Trigger
Enum for hydration timing.
enum Trigger {
Load // Immediately on page load
Idle // When browser is idle
Visible // When element enters viewport
Media(String) // When media query matches
None // Manual trigger only
}
Values
| Value | HTML Output | Description |
|---|---|---|
@luna.Load | load | Immediate hydration |
@luna.Idle | idle | requestIdleCallback |
@luna.Visible | visible | IntersectionObserver |
@luna.Media(query) | media:(query) | Media query match |
@luna.None | none | Manual via __LUNA_HYDRATE__ |
Examples
// Immediate (default)
trigger=@luna.Load
// When browser is idle
trigger=@luna.Idle
// When scrolled into view
trigger=@luna.Visible
// Desktop only
trigger=@luna.Media("(min-width: 768px)")
// Manual trigger
trigger=@luna.None
renderwithpreloads
Render and collect island URLs for preloading.
let node = @element.div([
@sol.island(@types.component_a({}), [@element.text("A")]),
@sol.island(@types.component_b({}), [@element.text("B")]),
])
let result = render_with_preloads(node)
// result.html contains the rendered HTML
// result.preload_urls = ["/static/component_a.js", "/static/component_b.js"]
Use for Link Preloading
// Generate preload links for all islands
let preload_links = result.preload_urls.map(fn(url) {
@element.link(rel="modulepreload", href=url)
})
Web Components Island
For Web Components islands, use island() with a WC-prefixed ComponentRef (auto-generated with wc: true):
// Auto-generated: WcCounterProps โ wc_counter() factory with wc=true
@sol.island(
@types.wc_counter(wc_counter_props),
[@element.button([@element.text("Count: 0")])],
)
Low-level: wcislandraw
For direct string-based Web Component islands, use @luna.wc_island or @sol.wc_island_raw:
@luna.wc_island(
name="wc-counter",
url="/static/wc-counter.js",
state=initial.to_string(),
trigger=@luna.Load,
styles=":host { display: block; }",
children=[
@element.button([@element.text("Count: \{initial}")])
],
)
Parameters (@luna.wc_island)
| Parameter | Type | Description |
|---|---|---|
name | String | Custom element tag name |
url | String | JavaScript module URL |
state | String | Serialized props (JSON) |
trigger | Trigger | When to hydrate |
styles | String | Scoped CSS for Shadow DOM |
children | Array[Node] | Server-rendered content |
HTML Output
<wc-counter
luna:wc-url="/static/wc-counter.js"
luna:wc-state="0"
luna:wc-trigger="load"
>
<template shadowrootmode="open">
<style>:host { display: block; }</style>
<button>Count: 0</button>
</template>
</wc-counter>
slot_
Create a slot element for Web Components.
@luna.wc_island(
name="wc-card",
children=[
@element.slot_(), // Default slot
@element.slot_(name="header"), // Named slot
@element.slot_(name="footer"), // Named slot
],
)
HTML Output
<slot></slot>
<slot name="header"></slot>
<slot name="footer"></slot>
Serializing State
Use derive(ToJson) for automatic serialization. With ComponentRef, serialization is handled automatically:
pub(all) struct CounterProps {
initial : Int
max : Int
} derive(ToJson, FromJson)
// ComponentRef-based (recommended) - serialization is automatic
@sol.island(
@types.counter({ initial: 0, max: 100 }),
[@element.div([@element.text("Loading...")])],
)
// String-based (low-level) - manual serialization
let state : CounterProps = { initial: 0, max: 100 }
@sol.island_raw(
"counter",
"/static/counter.js",
state.to_json().stringify(),
[@element.div([@element.text("Loading...")])],
)
Client-Side Hydration
The island loader (@luna_ui/luna-loader) handles hydration on the client:
<!-- Add to your page -->
<script type="module">
import { setupHydration } from '@luna_ui/luna-loader';
setupHydration();
</script>
Hydration Process
Loader scans for elements with
luna:idorluna:wc-*attributesBased on trigger, it:
load: Immediately imports the moduleidle: UsesrequestIdleCallbackvisible: UsesIntersectionObservermedia: UsesmatchMedia
Module's default export receives the element and parsed state
Island Module Structure
// /components/counter.js
export default function hydrate(element, state) {
// state is parsed from luna:state
const count = state.initial || 0;
// Set up reactivity
element.querySelector('button').onclick = () => {
// Update logic
};
}
API Summary
| Function | Description |
|---|---|
@sol.island(cref, children) | Create island from ComponentRef (recommended) |
@sol.island_with(cref, render) | Create island with render function |
@sol.island_raw(id, url, state, children, trigger~) | Create island from strings (low-level) |
@server_dom.client(cref, children) | Equivalent to @sol.island |
@luna.wc_island(...) | Create Web Component island (low-level) |
@sol.wc_island_raw(...) | Sol wrapper for WC island (low-level) |
@element.slot_(name~) | Create slot element |
render_with_preloads(node) | Render and collect preload URLs |
| Trigger | When |
|---|---|
@luna.Load | Page load (default) |
@luna.Idle | Browser idle |
@luna.Visible | In viewport |
@luna.Media(query) | Media query matches |
@luna.None | Manual |