Skip to content
Moku
@moku-labs/native pages

Quick start

From a built web app to an installer in dist-native/, with one script per command.

On this page

An installer in six steps

  1. Install. @moku-labs/core and @moku-labs/common are peers. A real node binary must be on PATH: @tauri-apps/cli does not run under Bun.

    install
    bun add @moku-labs/native @moku-labs/core @moku-labs/common
  2. Ignore the output. The generated Tauri project and the installers are never committed.

    .gitignore
    .moku/
    dist-native/
  3. Compose the native app. It is its own createApp, next to your web app. It never runs in the page.

  4. Add one script per command. The cli plugin has no argument parser. Each script calls one method.

  5. Ask the doctor. It tells you what the host is missing for each target, with the command that fixes it.

  6. Build. The installers land in dist-native/<target>/.

The code

src/native.ts
import { createApp } from "@moku-labs/native";

export const native = createApp({
  config: {
    app: { name: "MyApp", identifier: "com.example.myapp" },
    web: {
      build: "bun run build",
      devCommand: "bun run dev",
      devUrl: "http://localhost:5173",
      dist: "dist"
    },
    system: [{ name: "store" }]
  }
});
  1. Both are required. No icon yet: a placeholder is generated.
  2. Names drive dependencies, Rust init and permissions. An unknown name throws at createApp.
scripts/native-doctor.ts
import { native } from "../src/native";

await native.start();
const ok = await native.cli.doctor();
await native.stop();
if (!ok) process.exitCode = 1;
  1. doctor returns the verdict. Setting the exit code is yours.
scripts/native-build.ts
import { native } from "../src/native";

await native.start();
await native.cli.build();
await native.stop();
  1. No target: the host's own desktop target.
build
bun scripts/native-doctor.ts
bun scripts/native-build.ts

On a Mac this leaves a .dmg and a .app in dist-native/macos/. The first build compiles Rust, so it takes minutes.

The dev loop

scripts/native-dev.ts
import { native } from "../src/native";

await native.start();
await native.cli.dev();
await native.stop();
  1. Runs until you stop it. Ctrl-C ends the whole process group.

dev runs web.devCommand, waits for web.devUrl to answer and opens the app window on it.

Add a phone

Mobile is opt-in. Name the target and ask the doctor again:

scripts/native-ios.ts
import { native } from "../src/native";

await native.start();
await native.cli.doctor({ target: "ios" });
await native.cli.build({ target: "ios", simulator: true });
await native.stop();
  1. Unsigned, for the simulator. No Apple account needed.