astrobit

    A thin framework for running MoonBit components as Astro islands

    astro
    moonbit
    island
    framework
    Download zip
    Version
    0.1.6
    License
    MIT
    Last updated
    6 months ago
    Downloads
    71

    Dependencies

    #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.

    #Demo

    #Requirements

    [!NOTE] The MoonBit toolchain (moon CLI) is required, but astrobit build will install it automatically if not found.

    #Installation

    npm install astrobit // or yarn add astrobit

    Add the integration to your astro.config.mjs:

    import { defineConfig } from 'astro/config' import astrobit from 'astrobit' // here export default defineConfig({ integrations: [ astrobit() // and here ], })

    #Project Setup

    Place a moon.mod.json at the root of your Astro project:

    { "name": "yourname/your-project", "deps": { "SouichiroTsujimoto/astrobit": "0.1.6", "mizchi/signals": "0.6.4" }, "preferred-target": "js" }

    Then install the MoonBit dependencies:

    moon install

    #Writing 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 default

    #Using 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

    Add astrobit build as your build script in package.json:

    { "scripts": { "build": "astrobit build" } }

    astrobit build installs the moon CLI automatically if it's not available, then runs moon build followed by astro build. No manual toolchain setup is required for deployment (e.g. Vercel).

    For local development, build the MoonBit sources first:

    moon build

    Then start Astro:

    npm run dev

    HMR 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

    ToArrayNode

    pub(open) trait ToArrayNode {
    to_array_node(Self) -> Array[Node]
    }

    Node

    pub(all) enum Node {
    Element(String, Map[String, String], Array[Node])
    Text(String)
    Empty
    RawHtml(String)
    DynText(() -> String)
    Dynamic(() -> Node)
    WithEvents(Node, Array[(String, (
    Event
    ) -> Unit)])
    WithDynAttrs(Node, Array[(String, () -> String?)])
    }

    impl ToArrayNode for Node
    fn[C : ToArrayNode] a(attrs? : Map[String, String], children : C) -> Node

    article

    fn[C : ToArrayNode] article(attrs? : Map[String, String], children : C) -> Node

    body

    fn[C : ToArrayNode] body(attrs? : Map[String, String], children : C) -> Node

    fn br(attrs? : Map[String, String]) -> Node

    button

    fn[C : ToArrayNode] button(attrs? : Map[String, String], children : C) -> Node

    code

    fn[C : ToArrayNode] code(attrs? : Map[String, String], children : C) -> Node

    div

    fn[C : ToArrayNode] div(attrs? : Map[String, String], children : C) -> Node

    dyn

    fn dyn(f : () -> Node) -> Node

    dyn_attr

    fn dyn_attr(node : Node, name : String, f : () -> String) -> Node

    dyn_bool_attr

    fn dyn_bool_attr(node : Node, name : String, f : () -> Bool) -> Node

    dyn_text

    fn dyn_text(f : () -> String) -> Node

    fn[C : ToArrayNode] footer(attrs? : Map[String, String], children : C) -> Node

    form

    fn[C : ToArrayNode] form(attrs? : Map[String, String], children : C) -> Node

    fn[C : ToArrayNode] h1(attrs? : Map[String, String], children : C) -> Node

    fn[C : ToArrayNode] h2(attrs? : Map[String, String], children : C) -> Node

    fn[C : ToArrayNode] h3(attrs? : Map[String, String], children : C) -> Node

    fn[C : ToArrayNode] h4(attrs? : Map[String, String], children : C) -> Node

    fn[C : ToArrayNode] h5(attrs? : Map[String, String], children : C) -> Node

    fn[C : ToArrayNode] head(attrs? : Map[String, String], children : C) -> Node

    fn[C : ToArrayNode] header(attrs? : Map[String, String], children : C) -> Node

    fn hr(attrs? : Map[String, String]) -> Node

    html

    fn[C : ToArrayNode] html(attrs? : Map[String, String], children : C) -> Node

    hydrate_dom

    fn hydrate_dom(parent :
    Element
    , node : Node) -> Unit

    img

    fn img(attrs? : Map[String, String]) -> Node

    input

    fn input(attrs? : Map[String, String]) -> Node

    label

    fn[C : ToArrayNode] label(attrs? : Map[String, String], children : C) -> Node

    fn[C : ToArrayNode] li(attrs? : Map[String, String], children : C) -> Node

    main_tag

    fn[C : ToArrayNode] main_tag(attrs? : Map[String, String], children : C) -> Node

    meta

    fn[C : ToArrayNode] meta(attrs? : Map[String, String], children : C) -> Node

    mount_dom

    fn mount_dom(parent :
    Element
    , node : Node) -> Unit

    fn[C : ToArrayNode] nav(attrs? : Map[String, String], children : C) -> Node

    fn[C : ToArrayNode] ol(attrs? : Map[String, String], children : C) -> Node

    fn on(node : Node, event : String, handler : (
    Event
    ) -> Unit) -> Node

    on_click

    fn on_click(node : Node, handler : (
    Event
    ) -> Unit) -> Node

    on_input

    fn on_input(node : Node, handler : (
    Event
    ) -> Unit) -> Node

    on_submit

    fn on_submit(node : Node, handler : (
    Event
    ) -> Unit) -> Node

    option

    fn[C : ToArrayNode] option(attrs? : Map[String, String], children : C) -> Node

    fn[C : ToArrayNode] p(attrs? : Map[String, String], children : C) -> Node

    pre

    fn[C : ToArrayNode] pre(attrs? : Map[String, String], children : C) -> Node

    raw_html

    fn raw_html(s : String) -> Node

    render_to_html

    fn render_to_html(node : Node) -> String

    section

    fn[C : ToArrayNode] section(attrs? : Map[String, String], children : C) -> Node

    select

    fn[C : ToArrayNode] select(attrs? : Map[String, String], children : C) -> Node

    span

    fn[C : ToArrayNode] span(attrs? : Map[String, String], children : C) -> Node

    tag

    fn[C : ToArrayNode] tag(tag : String, attrs? : Map[String, String], children : C) -> Node

    textarea

    fn[C : ToArrayNode] textarea(attrs? : Map[String, String], children : C) -> Node

    title

    fn[C : ToArrayNode] title(attrs? : Map[String, String], children : C) -> Node

    fn[C : ToArrayNode] ul(attrs? : Map[String, String], children : C) -> Node