astrobit 0.1.1 → 0.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,146 @@
1
+ # astrobit
2
+
3
+ A thin framework for running [MoonBit](https://www.moonbitlang.com/) components as [Astro](https://astro.build/) islands.
4
+
5
+ Write your UI logic in MoonBit, use it directly in `.astro` files — SSR and client-side hydration included.
6
+
7
+ ## Requirements
8
+
9
+ - [Astro](https://astro.build/) 6+
10
+ - [MoonBit toolchain](https://www.moonbitlang.com/download/) (`moon` CLI)
11
+
12
+ ## Installation
13
+
14
+ ```sh
15
+ npm install astrobit
16
+ ```
17
+
18
+ Add the integration to your `astro.config.mjs`:
19
+
20
+ ```js
21
+ import { defineConfig } from 'astro/config'
22
+ import astrobit from 'astrobit'
23
+
24
+ export default defineConfig({
25
+ integrations: [astrobit()],
26
+ })
27
+ ```
28
+
29
+ ## Project Setup
30
+
31
+ Place a `moon.mod.json` at the root of your Astro project:
32
+
33
+ ```json
34
+ {
35
+ "name": "yourname/your-project",
36
+ "deps": {
37
+ "SouichiroTsujimoto/astrobit": "0.1.1",
38
+ "mizchi/signals": "0.6.4"
39
+ },
40
+ "preferred-target": "js"
41
+ }
42
+ ```
43
+
44
+ Then install the MoonBit dependencies:
45
+
46
+ ```sh
47
+ moon install
48
+ ```
49
+
50
+ ## Writing a Component
51
+
52
+ Each component lives in its own directory with a `moon.pkg` file.
53
+
54
+ **`src/components/counter/moon.pkg`**
55
+ ```json
56
+ {
57
+ "import": [
58
+ "SouichiroTsujimoto/astrobit" @a,
59
+ "SouichiroTsujimoto/astrobit/dom",
60
+ "mizchi/signals"
61
+ ],
62
+ "options": {
63
+ "link": { "js": { "exports": ["mount", "render", "hydrate"], "format": "esm" } }
64
+ }
65
+ }
66
+ ```
67
+
68
+ **`src/components/counter/counter.mbt`**
69
+ ```moonbit
70
+ fn counter(props : @dom.Props) -> @a.Node {
71
+ let initial = props.get_int("initial")
72
+ let count = @signals.signal(initial)
73
+ @a.div([
74
+ @a.p(@a.dyn_text(fn() { "Count: " + count.get().to_string() })),
75
+ @a.button("-") |> @a.on_click(fn(_) { count.update(fn(n) { n - 1 }) }),
76
+ @a.button("+") |> @a.on_click(fn(_) { count.update(fn(n) { n + 1 }) }),
77
+ ])
78
+ }
79
+
80
+ pub fn mount(element : @dom.Element, props : @dom.Props) -> Unit {
81
+ @a.mount_dom(element, counter(props))
82
+ }
83
+
84
+ pub fn render(props : @dom.Props) -> String {
85
+ @a.render_to_html(counter(props))
86
+ }
87
+
88
+ pub fn hydrate(element : @dom.Element, props : @dom.Props) -> Unit {
89
+ @a.hydrate_dom(element, counter(props))
90
+ }
91
+ ```
92
+
93
+ Props are received as `@dom.Props` and extracted with typed accessors:
94
+
95
+ ```moonbit
96
+ props.get_int("key") // Int (default: 0)
97
+ props.get_string("key") // String (default: "")
98
+ props.get_bool("key") // Bool (default: false)
99
+
100
+ props.get_int("key", default=10) // with explicit default
101
+ ```
102
+
103
+ ## Using in Astro
104
+
105
+ Import the `.mbt` file directly. The Vite plugin handles the rest.
106
+
107
+ ```astro
108
+ ---
109
+ import Counter from '../components/counter/counter.mbt'
110
+ ---
111
+
112
+ <!-- client:only — mount on the client, no SSR -->
113
+ <Counter client:only="astrobit" initial={0} />
114
+
115
+ <!-- client:load — SSR + hydration -->
116
+ <Counter client:load initial={0} />
117
+ ```
118
+
119
+ TypeScript types for `*.mbt` imports are injected automatically — no manual `env.d.ts` setup required.
120
+
121
+ ## Build
122
+
123
+ Before running the dev server, build the MoonBit sources:
124
+
125
+ ```sh
126
+ moon build
127
+ ```
128
+
129
+ Then start Astro:
130
+
131
+ ```sh
132
+ npm run dev
133
+ ```
134
+
135
+ HMR is supported — saving a `.mbt` file triggers an automatic rebuild and page reload.
136
+
137
+ ## How It Works
138
+
139
+ - **SSR**: `render(props)` returns an HTML string, rendered server-side by Astro.
140
+ - **Hydration** (`client:load`): `hydrate(element, props)` attaches signals and event listeners to the existing DOM without re-rendering.
141
+ - **Mount** (`client:only`): `mount(element, props)` builds the DOM from scratch on the client.
142
+ - **Reactivity**: Powered by [`mizchi/signals`](https://mooncakes.io/docs/#/mizchi/signals/).
143
+
144
+ ## License
145
+
146
+ MIT
@@ -0,0 +1,60 @@
1
+ #!/usr/bin/env node
2
+ import { execSync, spawnSync } from "node:child_process";
3
+ import { platform } from "node:os";
4
+
5
+ const [, , command, ...args] = process.argv;
6
+
7
+ function run(cmd, opts = {}) {
8
+ const result = spawnSync(cmd, { shell: true, stdio: "inherit", ...opts });
9
+ if (result.status !== 0) process.exit(result.status ?? 1);
10
+ }
11
+
12
+ function hasMoon() {
13
+ const result = spawnSync("moon --version", { shell: true, stdio: "pipe" });
14
+ return result.status === 0;
15
+ }
16
+
17
+ function installMoon() {
18
+ if (platform() === "win32") {
19
+ console.error(
20
+ "[astrobit] moon is not installed. Please install it manually: https://www.moonbitlang.com/download"
21
+ );
22
+ process.exit(1);
23
+ }
24
+ console.log("[astrobit] moon not found, installing...");
25
+ run("curl -fsSL https://cli.moonbitlang.com/install/unix.sh | bash");
26
+ // Add moon to PATH for subsequent commands in this process
27
+ process.env.PATH = `${process.env.HOME}/.moon/bin:${process.env.PATH}`;
28
+ try {
29
+ const version = execSync("moon --version", { env: process.env })
30
+ .toString()
31
+ .trim();
32
+ console.log(`[astrobit] moon installed: ${version}`);
33
+ } catch {
34
+ // ignore version display failure
35
+ }
36
+ }
37
+
38
+ if (command === "build") {
39
+ if (!hasMoon()) {
40
+ installMoon();
41
+ } else {
42
+ try {
43
+ const version = execSync("moon --version").toString().trim();
44
+ console.log(`[astrobit] moon already available: ${version}`);
45
+ } catch {
46
+ // ignore
47
+ }
48
+ }
49
+
50
+ console.log("[astrobit] running moon build...");
51
+ run("moon build");
52
+
53
+ console.log("[astrobit] running astro build...");
54
+ run("astro build");
55
+ } else {
56
+ console.error(
57
+ `[astrobit] Unknown command: ${command ?? "(none)"}\n\nUsage:\n astrobit build`
58
+ );
59
+ process.exit(1);
60
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "astrobit",
3
- "version": "0.1.1",
3
+ "version": "0.1.3",
4
4
  "type": "module",
5
5
  "main": "./dist/integration.js",
6
6
  "types": "./dist/integration.d.ts",
@@ -13,8 +13,12 @@
13
13
  "types": "./env.d.ts"
14
14
  }
15
15
  },
16
+ "bin": {
17
+ "astrobit": "./bin/astrobit.js"
18
+ },
16
19
  "files": [
17
20
  "dist",
21
+ "bin",
18
22
  "env.d.ts"
19
23
  ],
20
24
  "scripts": {