A MoonBit UI runtime for WeChat MiniApp Skyline with Elm-style state machines, Val-based UI composition, transactional local state, and generated MiniApp host commands.
Dependencies
moon install lampclaw/minimoon/cmd/minimoon
minimoon --version
minimoon init my-app
cd my-app
moon update
bun install
minimoon build .
minimoon verify . --candidatepub fn program() -> @minimoon.Page {
@minimoon.page(
id="home",
route=@minimoon.route("pages/home/home"),
title="Home",
build=_ => {
let (count, update_count) = @minimoon.create_variable(0)
count.view(value => @minimoon.div([
@minimoon.h1(value.to_string()),
@minimoon.button(
on_tap=update_count(current => current + 1),
event_key="increment",
"+1",
),
]))
},
)
}| Example | Use it for |
|---|---|
| Starter, created by minimoon init | Starting your own two-page application |
| Draft Workbench | Local drafts, typed echo and lifecycle: 9 pages, 4 native Tabs — 工作台 / 草稿 / 联机 / 更多 |
| UI Showcase | Exploring native UI components across 6 pages |
///|
#warnings("-unused_value")
fn readme_composition_page() -> Page {
page(
id="readme_composition",
route=route("pages/readme_composition/readme_composition"),
title="Composition",
build=_ => {
let (count, update_count) = create_variable(0)
count.view(value => {
div([
h1(value.to_string()),
button(
on_tap=update_count(current => current + 1),
event_key="increment",
"+1",
),
])
})
},
)
}///|
priv enum ReadmePageMsg {
ReadmeIncrement
ReadmeNameChanged(String)
}
///|
#warnings("-unused_value")
fn readme_page() -> Page {
elmish_page(
id="readme",
route=route("pages/readme/readme"),
title="Readme",
model=(0, "MoonBit"),
update=(model, message, _emit) => {
match message {
ReadmeIncrement => no_cmd((model.0 + 1, model.1))
ReadmeNameChanged(name) => with_cmd((model.0, name), none)
}
},
view=(model, emit) => {
div([
button(on_tap=emit(ReadmeIncrement), model.0.to_string()),
input(value=model.1, on_input=value => emit(ReadmeNameChanged(value))),
])
},
)
}///|
#warnings("-unused_value")
fn readme_request(resolve : Emit[Result[RequestResult, HostError]]) -> Cmd {
request(
"https://echo.apifox.com/post",
resolve,
http_method=Post,
query=[query("tag", "one"), query("tag", "two")],
headers={ "X-Minimoon-Test": "public-smoke" },
body=JsonBody(Json::object({ "message": Json::string("测试") })),
timeout_ms=15000,
)
}///|
priv struct ReadmeAppDeps {
count : Shared[Int]
change_count : Emit[Int]
}
///|
#warnings("-unused_value")
fn readme_app() -> App[ReadmeAppDeps] {
app(build=context => {
let (count, change_count) = context.create_pure_state(0, update=(
count,
delta : Int,
) => count + delta)
{ count, change_count, }
})
}
///|
#warnings("-unused_value")
fn readme_shared_page(deps : ReadmeAppDeps) -> Page {
page(
id="readme_shared",
route=route("pages/readme-shared/readme-shared"),
title="Shared state",
build=context => {
context
.bind(deps.count)
.view(count => {
button(
on_tap=(deps.change_count)(1),
event_key="readme/shared/increment",
"Shared count: " + count.to_string(),
)
})
},
)
}///|
priv enum ReadmeLocalMsg {
ReadmeAdd
}
///|
fn readme_local_authoring(input_value : Val[Int]) -> Val[Node] {
let (pure, pure_emit) = create_pure_state(0, update=(
model,
_message : ReadmeLocalMsg,
) => model + 1)
let (state, state_emit) = create_state(0, update=(
model,
_message : ReadmeLocalMsg,
_emit,
) => with_cmds(model + 1, []))
let (initialized, initialized_emit) = create_state_with_init(
init=emit => (0, emit(ReadmeAdd)),
update=(model, _message : ReadmeLocalMsg, _emit) => no_cmd(model + 1),
)
let (input_state, input_emit) = create_state_with_input(
input=input_value,
init=(_emit, input) => no_cmd(input),
update=(model, input, _message : ReadmeLocalMsg, _emit) => {
no_cmd(model + input)
},
)
let (flag, set_flag) = create_variable(false)
let resource = create_resource(done => done(Ok("ready")))
Val::view6(pure, state, initialized, input_state, flag, resource, (
a,
b,
c,
d,
enabled,
status,
) => {
let status_text = match status {
Pending => "pending"
Loaded(value) => value
Failed(_) => "failed"
}
div([
button(on_tap=pure_emit(ReadmeAdd), a.to_string()),
button(on_tap=state_emit(ReadmeAdd), b.to_string()),
button(on_tap=initialized_emit(ReadmeAdd), c.to_string()),
button(on_tap=input_emit(ReadmeAdd), d.to_string()),
button(on_tap=set_flag(value => !value), enabled.to_string()),
p(status_text),
])
})
}
///|
#warnings("-unused_value")
fn readme_local_page() -> Page {
page(
id="readme_local",
route=route("pages/readme-local/readme-local"),
title="Readme local",
build=_ => readme_local_authoring(Val::constant(2)),
)
}type App[Deps]type AppContextfn[Model : Eq, Msg] AppContext::create_pure_state(self : AppContext, initial : Model, update~ : (Model, Msg) -> Model) -> (Shared[Model], Emit[Msg])fn[Model : Eq, Msg] AppContext::create_state(self : AppContext, initial : Model, update~ : (Model, Msg, Emit[Msg]) -> (Model, Cmd), subscriptions? : (Model, Emit[Msg]) -> Sub) -> (Shared[Model], Emit[Msg])fn AppContext::lifecycle(self : AppContext, hook : AppLifecycleHook, decode : (Json) -> Result[Cmd, DecodeError]) -> Subtype AppRuntime[Deps]fn[Deps] AppRuntime::create_page(self : AppRuntime[Deps], page : Page, input? : Map[String, String], layout? : PageLayout) -> Result[PageRuntime, DecodeError]fn[Deps] AppRuntime::lifecycle(self : AppRuntime[Deps], hook : String, payload_json : String) -> Stringfn[Deps] AppRuntime::resolve_effect(self : AppRuntime[Deps], request_id : String, phase : String, payload_json : String) -> Stringpub struct DecodeError {
message : String
}pub struct FocusDetail {
value : String
height : Int
}pub struct HostError {
capability : Capability
kind : HostErrorKind
message : String
code : String?
raw : Json
}pub struct LocationResult {
latitude : Double
longitude : Double
speed : Double?
accuracy : Double?
altitude : Double?
vertical_accuracy : Double?
horizontal_accuracy : Double?
}pub struct MediaFile {
temp_file_path : String
size : Int?
file_type : String?
width : Int?
height : Int?
duration : Double?
thumb_temp_file_path : String?
}type Nodetype Pagefn Page::create_runtime(self : Page, input? : Map[String, String], layout? : PageLayout) -> Result[PageRuntime, DecodeError]type PageContextfn PageContext::lifecycle(self : PageContext, hook : PageLifecycleHook, decode : (Json) -> Result[Cmd, DecodeError]) -> Subfn[T : Eq, U : Eq] PageContext::select(self : PageContext, shared : Shared[T], select : (T) -> U) -> Val[U]pub(all) struct PageLayout {
window : WindowMetrics?
menu_button : LayoutRect?
} derive(Eq, ToJson, Debug)type PageRuntimefn PageRuntime::resolve_effect(self : PageRuntime, request_id : String, phase : String, payload_json : String) -> Stringpub struct PaymentParams {
time_stamp : String
nonce_str : String
package_value : String
sign_type : String
pay_sign : String
}fn PaymentParams::new(time_stamp~ : String, nonce_str~ : String, package_value~ : String, sign_type~ : String, pay_sign~ : String) -> PaymentParamspub struct ScrollDetail {
scroll_left : Double
scroll_top : Double
scroll_height : Double
scroll_width : Double
delta_x : Double
delta_y : Double
}pub(all) enum SemanticRole {
GenericRole
ButtonRole
GroupRole
HeadingRole
RegionRole
NavigationRole
TabRole
TabListRole
TabPanelRole
DialogRole
MenuRole
MenuItemRole
MenuItemCheckboxRole
MenuItemRadioRole
SeparatorRole
AlertRole
AlertDialogRole
CheckboxRole
RadioRole
RadioGroupRole
SwitchRole
TextboxRole
ComboboxRole
ListboxRole
OptionRole
SliderRole
ProgressbarRole
StatusRole
TooltipRole
TableRole
RowRole
CellRole
ColumnHeaderRole
} derive(Eq, Debug)type Shared[T]pub enum Status[T] {
Pending
Loaded(T)
Failed(Error)
}type Subpub struct TextareaBlurDetail {
value : String
cursor : Int
}pub struct TouchDetail {
touches : Array[TouchPoint]
changed_touches : Array[TouchPoint]
timestamp : Double
} derive(Eq, Debug)type Val[A]pub(all) struct WindowMetrics {
window_width : Double
window_height : Double
screen_width : Double
screen_height : Double
screen_top : Double
status_bar_height : Double
safe_area : LayoutRect?
} derive(Eq, ToJson, Debug)fn[C : IsChildren] button_children(on_tap~ : Cmd, event_key? : String, disabled? : Bool, form_type? : FormButtonType, name? : String, id? : String, class? : String, style? : String, data_section? : String, semantics? : Semantics, children : C) -> Nodefn[C : IsChildren] checkbox(value~ : String, checked? : Bool, disabled? : Bool, color? : String, id? : String, class? : String, style? : String, children : C) -> Nodefn[C : IsChildren] checkbox_group(name? : String, on_change~ : Emit[Array[String]], event_key? : String, id? : String, class? : String, style? : String, data_section? : String, children : C) -> Nodefn[C : IsChildren] div(id? : String, class? : String, style? : String, data_section? : String, semantics? : Semantics, children : C) -> Nodefn[Model : Eq, Msg] elmish_page(id~ : String, route~ : Route, title~ : String, model~ : Model, update~ : (Model, Msg, Emit[Msg]) -> (Model, Cmd), view~ : (Model, Emit[Msg]) -> Node, capabilities? : Array[Capability], init? : (Emit[Msg]) -> Cmd, subscriptions? : (PageContext, Model, Emit[Msg]) -> Sub) -> Pagefn[C : IsChildren] h1(id? : String, class? : String, style? : String, data_section? : String, semantics? : Semantics, children : C) -> Nodefn input(value~ : String, on_input~ : Emit[String], input_type? : InputType, maxlength? : Int, password? : Bool, selection_start? : Int, selection_end? : Int, name? : String, event_key? : String, disabled? : Bool, focus? : Bool, on_confirm? : Emit[String], confirm_key? : String, on_focus? : Emit[FocusDetail], focus_key? : String, on_blur? : Emit[InputBlurDetail], blur_key? : String, id? : String, class? : String, style? : String, data_section? : String, placeholder? : String, semantics? : Semantics) -> Nodefn[C : IsChildren] label(for_id~ : String, id? : String, class? : String, style? : String, children : C) -> Nodefn[C : IsChildren] navigator(target~ : Route, mode? : NavigatorMode, id? : String, class? : String, style? : String, children : C) -> Nodefn[C : IsChildren] p(id? : String, class? : String, style? : String, data_section? : String, semantics? : Semantics, children : C) -> Nodefn page(id~ : String, route~ : Route, title~ : String, capabilities? : Array[Capability], build~ : (PageContext) -> Val[Node]) -> Pagefn[Input] page_with_input(id~ : String, route~ : Route, title~ : String, capabilities? : Array[Capability], preview_input~ : () -> Input, decode_input~ : (Map[String, String]) -> Result[Input, DecodeError], build~ : (PageContext, Input) -> Val[Node]) -> Pagefn[C : IsChildren] picker_date(name? : String, value~ : String, on_change~ : Emit[String], disabled? : Bool, event_key? : String, id? : String, class? : String, style? : String, children : C) -> Nodefn[C : IsChildren] picker_region(name? : String, value~ : Array[String], on_change~ : Emit[Array[String]], disabled? : Bool, event_key? : String, id? : String, class? : String, style? : String, children : C) -> Nodefn[C : IsChildren] picker_selector(name? : String, options~ : Array[String], selected~ : Int, on_change~ : Emit[Int], event_key? : String, disabled? : Bool, id? : String, class? : String, style? : String, children : C) -> Nodefn[C : IsChildren] picker_time(name? : String, value~ : String, on_change~ : Emit[String], disabled? : Bool, event_key? : String, id? : String, class? : String, style? : String, children : C) -> Nodefn[C : IsChildren] radio(value~ : String, checked? : Bool, disabled? : Bool, color? : String, id? : String, class? : String, style? : String, children : C) -> Nodefn[C : IsChildren] radio_group(name? : String, on_change~ : Emit[String], event_key? : String, id? : String, class? : String, style? : String, data_section? : String, children : C) -> Nodefn request(url : String, resolve : Emit[Result[RequestResult, HostError]], http_method? : HttpMethod, query? : Array[Query], headers? : Map[String, String], body? : RequestBody, timeout_ms? : Int) -> Cmdfn[C : IsChildren] scroll_view(scroll_x? : Bool, scroll_y? : Bool, scroll_top? : Int, scroll_left? : Int, scroll_top_px? : Double, scroll_left_px? : Double, scroll_into_view? : String, upper_threshold? : Int, lower_threshold? : Int, on_scroll? : Emit[ScrollDetail], scroll_key? : String, on_upper? : Cmd, upper_key? : String, on_lower? : Cmd, lower_key? : String, id? : String, class? : String, style? : String, data_section? : String, children : C) -> Nodefn semantics(role? : SemanticRole, label? : String, expanded? : Bool, selected? : Bool, disabled? : Bool, hidden? : Bool, checked? : Bool, modal? : Bool, controls? : String, labelled_by? : String, described_by? : String, orientation? : String, has_popup? : String, required? : Bool, invalid? : Bool, busy? : Bool, live? : String, value_min? : Int, value_max? : Int, value_now? : Int, value_text? : String) -> Semanticsfn slider(value~ : Int, on_change~ : Emit[Int], on_changing? : Emit[Int], min? : Int, max? : Int, step? : Int, disabled? : Bool, show_value? : Bool, active_color? : String, background_color? : String, block_color? : String, block_size? : Int, name? : String, event_key? : String, changing_key? : String, id? : String, class? : String, style? : String, semantics? : Semantics) -> Nodefn[C : IsChildren] span(id? : String, class? : String, style? : String, data_section? : String, semantics? : Semantics, children : C) -> Nodefn[C : IsChildren] swiper(current? : Int, vertical? : Bool, autoplay? : Bool, circular? : Bool, interval? : Int, duration? : Int, on_change? : Emit[Int], event_key? : String, id? : String, class? : String, style? : String, children : C) -> Nodefn[C : IsChildren] swiper_item(item_id? : String, id? : String, class? : String, style? : String, children : C) -> Nodefn[C : IsChildren] tap_view(on_tap~ : Cmd, event_key? : String, id? : String, class? : String, style? : String, data_section? : String, semantics? : Semantics, children : C) -> Nodefn textarea(name? : String, value~ : String, on_input~ : Emit[String], input_key? : String, placeholder? : String, maxlength? : Int, disabled? : Bool, focus? : Bool, on_confirm? : Emit[String], confirm_key? : String, on_focus? : Emit[FocusDetail], focus_key? : String, on_blur? : Emit[TextareaBlurDetail], blur_key? : String, id? : String, class? : String, style? : String, data_section? : String, semantics? : Semantics) -> Nodefn[C : IsChildren] touch_view(on_tap? : Cmd, on_long_press? : Emit[TouchDetail], on_touch_start? : Emit[TouchDetail], on_touch_move? : Emit[TouchDetail], on_touch_end? : Emit[TouchDetail], on_touch_cancel? : Emit[TouchDetail], catch_move? : Bool, event_key? : String, id? : String, class? : String, style? : String, data_section? : String, semantics? : Semantics, children : C) -> NodeInstall
Download zipA MoonBit UI runtime for WeChat MiniApp Skyline with Elm-style state machines, Val-based UI composition, transactional local state, and generated MiniApp host commands.
Dependencies