Islands

Islands Architecture

Islands allow you to embed interactive Luna (MoonBit) components within static pages. The page is rendered as static HTML, and specific interactive regions ("islands") are hydrated on the client.

Overview

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  Static HTML (SSG)                  โ”‚
โ”‚  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”    โ”‚
โ”‚  โ”‚  Island (Interactive)       โ”‚    โ”‚
โ”‚  โ”‚  - Luna/MoonBit component   โ”‚    โ”‚
โ”‚  โ”‚  - Hydrated on client       โ”‚    โ”‚
โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜    โ”‚
โ”‚  More static content...             โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Configuration

1. sol.config.json

Configure the islands directory:

{
  "islands": {
    "dir": "docs/public/islands",
    "basePath": "/islands/"
  }
}

2. Build the Component

Create a MoonBit component with a hydrate export:

// src/examples/wiki/main.mbt
pub fn hydrate(el : @dom.Element, _state : @js.Any) -> Unit {
  // Initialize your component
  let container = el |> @luna_dom.DomElement::from_dom
  // ... render logic
}

Package configuration (moon.pkg.json):

{
  "is-main": true,
  "supported-targets": ["js"],
  "import": [
    "mizchi/luna/signal",
    { "path": "mizchi/luna/dom/element", "alias": "dom" },
    { "path": "mizchi/js/browser/dom", "alias": "js_dom" },
    { "path": "mizchi/js/core", "alias": "js" }
  ],
  "link": {
    "js": {
      "format": "esm",
      "exports": ["hydrate"]
    }
  }
}

3. Copy to Islands Directory

After building, copy the compiled JS to the islands directory:

moon build --target js
cp target/js/release/build/examples/wiki/wiki.js docs/public/islands/

4. Use in Markdown

Reference the island in your markdown:

---
title: Wiki
islands:
  - wiki
---

# Wiki Demo

<Island name="wiki" trigger="load" />

Island Directive Syntax

<Island name="component-name" trigger="load" />

Attributes

AttributeDescription
nameComponent name (matches filename without .js)
triggerHydration trigger (see below)

Trigger Types

TriggerDescription
loadHydrate immediately on page load
idleHydrate when browser is idle
visibleHydrate when element enters viewport

Client-Side Routing with BrowserRouter

Islands can implement full client-side routing using Luna's BrowserRouter:

fn create_routes() -> Array[@routes.Routes] {
  [
    Page(path="", component="home", title="Home", meta=[]),
    Page(path="/:slug", component="page", title="Page", meta=[]),
  ]
}

pub fn hydrate(el : @dom.Element, _state : @js.Any) -> Unit {
  let base = "/wiki"
  let routes = create_routes()
  let router = @router.BrowserRouter::new(routes, base~)

  let container = el |> @luna_dom.DomElement::from_dom
  render_app(router, container)
}

This enables:

  • Client-side navigation without page reload

  • Dynamic URL parameters (:slug)

  • Browser history (back/forward) support

Generated HTML

The Island directive generates:

<!--luna:island:wiki url=/islands/wiki.js trigger=load-->
<div luna:id="wiki"
     luna:url="/islands/wiki.js"
     luna:state="{}"
     luna:client-trigger="load">
</div>
<!--/luna:island:wiki-->

The Luna loader automatically discovers and hydrates these elements.

Example: Wiki with Dynamic Routing

See examples/sol_docs/docs/wiki/ for a complete example:

  1. Static entry: /wiki/ - Single HTML page with island placeholder

  2. Client routing: BrowserRouter handles /wiki/:slug patterns

  3. No server needed: Fully static, works with any file server

/wiki/                    โ†’ Wiki Home (index page)
/wiki/getting-started     โ†’ Handled by BrowserRouter
/wiki/configuration       โ†’ Handled by BrowserRouter

Islands vs Dynamic Routes

Use CaseSolution
Blog posts (SEO important)Dynamic Routes (_slug_)
Interactive documentationIslands
SPA within static siteIslands + BrowserRouter
Static content pagesRegular Markdown