astrobit
v0.1.5
Published
A thin framework for running MoonBit components as Astro islands
Maintainers
Readme
astrobit
A thin framework for running MoonBit components as Astro islands.
Write your UI logic in MoonBit, use it directly in .astro files — SSR and client-side hydration included.
Requirements
- Astro 6+
- MoonBit toolchain (
moonCLI)
Installation
npm install astrobitAdd the integration to your astro.config.mjs:
import { defineConfig } from 'astro/config'
import astrobit from 'astrobit'
export default defineConfig({
integrations: [astrobit()],
})Project Setup
Place a moon.mod.json at the root of your Astro project:
{
"name": "yourname/your-project",
"deps": {
"SouichiroTsujimoto/astrobit": "0.1.1",
"mizchi/signals": "0.6.4"
},
"preferred-target": "js"
}Then install the MoonBit dependencies:
moon installWriting a Component
Each component lives in its own directory with a moon.pkg file.
src/components/counter/moon.pkg
{
"import": [
"SouichiroTsujimoto/astrobit" @a,
"SouichiroTsujimoto/astrobit/dom",
"mizchi/signals"
],
"options": {
"link": { "js": { "exports": ["mount", "render", "hydrate"], "format": "esm" } }
}
}src/components/counter/counter.mbt
fn counter(props : @dom.Props) -> @a.Node {
let initial = props.get_int("initial")
let count = @signals.signal(initial)
@a.div([
@a.p(@a.dyn_text(fn() { "Count: " + count.get().to_string() })),
@a.button("-") |> @a.on_click(fn(_) { count.update(fn(n) { n - 1 }) }),
@a.button("+") |> @a.on_click(fn(_) { count.update(fn(n) { n + 1 }) }),
])
}
pub fn mount(element : @dom.Element, props : @dom.Props) -> Unit {
@a.mount_dom(element, counter(props))
}
pub fn render(props : @dom.Props) -> String {
@a.render_to_html(counter(props))
}
pub fn hydrate(element : @dom.Element, props : @dom.Props) -> Unit {
@a.hydrate_dom(element, counter(props))
}Props are received as @dom.Props and extracted with typed accessors:
props.get_int("key") // Int (default: 0)
props.get_string("key") // String (default: "")
props.get_bool("key") // Bool (default: false)
props.get_int("key", default=10) // with explicit defaultUsing in Astro
Import the .mbt file directly. The Vite plugin handles the rest.
---
import Counter from '../components/counter/counter.mbt'
---
<!-- client:only — mount on the client, no SSR -->
<Counter client:only="astrobit" initial={0} />
<!-- client:load — SSR + hydration -->
<Counter client:load initial={0} />TypeScript types for *.mbt imports are injected automatically — no manual env.d.ts setup required.
Build
Before running the dev server, build the MoonBit sources:
moon buildThen start Astro:
npm run devHMR is supported — saving a .mbt file triggers an automatic rebuild and page reload.
How It Works
- SSR:
render(props)returns an HTML string, rendered server-side by Astro. - Hydration (
client:load):hydrate(element, props)attaches signals and event listeners to the existing DOM without re-rendering. - Mount (
client:only):mount(element, props)builds the DOM from scratch on the client. - Reactivity: Powered by
mizchi/signals.
License
MIT
