npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

vite-plugin-moonbit

v0.5.1

Published

Vite plugin for MoonBit - import MoonBit modules with mbt: prefix

Readme

vite-plugin-moonbit

Vite plugin for MoonBit projects. Supports both JS and WASM-GC backends.

Features

  • Import resolution via mbt: prefix
  • Auto-starts moon build --watch
  • HMR on file changes
  • JS and WASM-GC backend support
  • MoonBit workspace (moon.work) / monorepo support
  • Understands moon.pkg / moon.work DSL (parsed by a MoonBit-native parser built from moonbitlang/parser)
  • mbt: imports support ?worker, ?url, ?raw query suffixes
  • Source maps forwarded so .mbt sources are debuggable in browser devtools
  • Auto-detects use-js-builtin-string from moon.pkg for wasm-gc
  • Mix multiple backends in one project (e.g. JS for the main thread, wasm-gc inside a Web Worker) by instantiating the plugin twice with distinct prefix options
  • Optional TypeScript bridge package generation via mizchi/ts.mbt

Install

pnpm add -D vite-plugin-moonbit
// vite.config.ts
import { defineConfig } from 'vite';
import { moonbit } from 'vite-plugin-moonbit';

export default defineConfig({
  plugins: [
    moonbit({
      target: "js",
      // run: `moon build --target js --watch` in vite
      // If you want to build manually, set `false` and `moon build`
      watch: true
    })
  ],
});

Quick Start

npx tiged github:mizchi/vite-plugin-moonbit/examples/js_project myapp
cd myapp && pnpm install
moon build && pnpm dev

Usage

JS Backend (default)

// vite.config.ts
import { defineConfig } from 'vite';
import moonbit from 'vite-plugin-moonbit';

export default defineConfig({
  plugins: [moonbit()]
});
// main.ts
import { greet } from 'mbt:username/app';

Optional: tsconfig.json's paths

{
  "compilerOptions": {
    // ...
    "paths": {
      "mbt:internal/app": [
        "./_build/js/release/build/app.js"
      ]
    }
  }
}

See examples/js_project

Check out: npx tiged mizchi/vite-plugin-moonbit/examples/wasm_project myapp

WASM-GC Backend

// vite.config.ts
import { defineConfig } from 'vite';
import moonbit from 'vite-plugin-moonbit';

export default defineConfig({
  plugins: [moonbit({ target: 'wasm-gc' })]
});
// main.ts
import init from './_build/wasm-gc/release/build/app.wasm?init';

const instance = await init();
const { add } = instance.exports as { add: (a: number, b: number) => number };
add(1, 2);

See examples/wasm_project

Check out: npx tiged mizchi/vite-plugin-moonbit/examples/wasm_project myapp

Options

| Option | Type | Default | Description | |--------|------|---------|-------------| | root | string | cwd() | MoonBit project root | | watch | boolean | true (dev) | Run moon build --watch | | target | 'js' \| 'wasm' \| 'wasm-gc' | 'js' | Build target | | showLogs | boolean | true | Show build logs | | prefix | string | 'mbt:' | Import prefix for this plugin instance | | tsBridge | MoonbitTsBridgeOptions | undefined | Type-check TS entrypoints and generate MoonBit bridge packages before build | | npmPackage | MoonbitNpmPackageOptions | undefined | Emit an npm-publishable ESM + .d.ts package from MoonBit | | normalizedDts | MoonbitNormalizedDtsOptions | undefined | Experimental: rewrite MoonBit-generated _build/.../*.d.ts with clearer TS declarations |

TypeScript bridge packages

Use tsBridge when MoonBit should consume a TypeScript entrypoint through a generated typed bridge package. Each entry is strictly type-checked before its MoonBit bindings are generated. If mtsc reports an error, Vite stops before writing or using a bridge package.

tsBridge is the public integration for TypeScript → MoonBit. Its consumer contract is the option shape below and generated moon.pkg, bridge.mbti, bridge.mbt, bridge.js, and package.json. Do not import the checker module or generated glue directly from application code.

// vite.config.ts
import { defineConfig } from "vite";
import moonbit from "vite-plugin-moonbit";

export default defineConfig({
  plugins: [
    moonbit({
      root: "./moonbit-app",
      tsBridge: {
        generatorRoot: "../ts.mbt",
        entries: ["./src/api/client.ts"],
      },
    }),
  ],
});

For the string shorthand above, the plugin infers:

  • moduleSpec: "/src/api/client.ts"
  • outDir: "src/gen/client_bridge"

You can still pass the full object form when you need to override either of those defaults.

To bridge a dependency rather than a project file, use package. The plugin resolves the package from the Vite root, reads its package.json, and selects its declaration entrypoint. A subpath such as @types/node/fs resolves the declaration for that subpath. Keep moduleSpec explicit when the runtime name differs from the declaration package name.

tsBridge: {
  generatorRoot: "../ts.mbt",
  entries: [
    "./src/api/client.ts",
    {
      package: "@types/node/fs",
      moduleSpec: "node:fs",
      outDir: "src/gen/node_fs_bridge",
    },
  ],
}

Install the selected package in the consuming project. The generated node:fs bridge is for Node/SSR MoonBit code, not a browser Vite entrypoint.

Use a non-relative moduleSpec whenever possible, for example /src/api/client.ts, node:fs, or a bare package name. The generator can emit leaner MoonBit FFI for non-relative specifiers. Relative specs like ./client.ts still work, but they force more bindings through generated bridge.js wrappers because MoonBit #module("...") does not currently accept relative module paths.

For each configured generator root, the plugin builds src/mtsc for the MoonBit JS target and imports its checkModuleGraph ESM export directly. It uses Vite's resolver to collect local TS/TSX dependencies and resolved import edges for every configured entry, then type-checks that complete graph before generating a bridge. It does not run mtsc as a command or create a temporary JavaScript output file. The built checker module is reused until its src/ or Moon manifest inputs change; diagnostic paths are displayed relative to the Vite root.

Only after that succeeds, it generates the bridge package:

moon -C ../ts.mbt run src/cmd/ts2mbt -- package \
  /abs/path/to/src/api/client.ts \
  /src/api/client.ts \
  /abs/path/to/moonbit-app/src/gen/client_bridge

Set runtimeValidation: true when the generated package must validate untrusted JS values at an explicit boundary. This selects package-validated and emits validate<Type>(value : JSValue) -> Type? only for generated structural types; it does not add hidden checks to normal bridge calls.

tsBridge: {
  generatorRoot: "../ts.mbt",
  runtimeValidation: true,
  entries: ["./src/api/client.ts"],
}

The generated package contains:

  • moon.pkg
  • bridge.mbti
  • bridge.mbt
  • bridge.js
  • package.json

Everything inside outDir is generated. The surrounding MoonBit package that imports that bridge package remains hand-written.

When a MoonBit package imports the generated bridge package, the plugin reads that package's bridge.js exports and injects the needed bindings into the compiled MoonBit JS module automatically.

Today bridge.js is still needed for some surfaces even with a non-relative moduleSpec, especially static class members, value exports, and namespace-like exports. Plain exported functions, instance members, and class constructors are already emitted with less wrapper code when direct #module("...") imports are available.

See examples/ts_bridge_project for a complete example that checks in the generated bridge package and wraps a TypeScript entrypoint from MoonBit.

npm package output

npmPackage makes a MoonBit package publishable as a conventional npm ESM library. It invokes moon info, uses mbt2ts to generate the public .d.ts, package.json, and MoonBit facade, then bundles that facade together with local TypeScript bridge modules. Consumers therefore receive ordinary JavaScript and declarations; they do not need MoonBit, Vite, or generated @tsmbt-bridge/* dependencies at runtime.

moonbit({
  root: __dirname,
  tsBridge: {
    generatorRoot: "../ts.mbt",
    entries: ["./src/api/client.ts"],
  },
  npmPackage: {
    entry: "internal/app", // MoonBit package name, or pkg.generated.mbti path
    outDir: "dist/npm",
    name: "@acme/app",
    version: "0.1.0",
  },
})

name and version override the values derived by mbt2ts, so the generated directory is ready for your registry namespace and release version without a manual package.json edit.

During vite build, the npm package is generated in closeBundle, after Vite has cleared its output directory. outDir: "dist/npm" is therefore safe. The generated directory contains index.js, index.d.ts, source map, package.json, and AUTOLINK_DIAGNOSTICS.md; verify and publish it with:

cd dist/npm
npm pack --dry-run
npm publish

facade defaults to true, so eligible MoonBit methods and constructors are made callable from JS. Set strict: true to reject omitted runtime members, and set bundle: false only when your consumer will provide all external runtime imports itself.

npmPackage is the public MoonBit → TypeScript integration. Its required options are entry and outDir; it reuses tsBridge.generatorRoot and tsBridge.command when present, otherwise provide generatorRoot (and, optionally, command) directly. name, version, importRewrites, and diagnostics are passed to the package generator as documented controls.

At this boundary, public MoonBit structs and enums are recursively normalized to ordinary JavaScript objects (tagged unions use $tag). Values returned by the package carry an internal opaque brand so they can safely be passed back to MoonBit APIs, but callers cannot construct a lookalike object themselves. MoonBit trait methods are intentionally not exposed on these plain values.

An exported function whose parameter or result mentions a MoonBit type from outside the selected package tree is omitted from both the generated .d.ts and runtime exports, unless that import has been explicitly mapped to a publishable TypeScript module. The omission is listed in AUTOLINK_DIAGNOSTICS.md. Publish a primitive/JSON boundary package for that API (for example, mizchi/markdown/api) instead of leaking a cross-package MoonBit value. If such a type occurs only inside an otherwise public struct or enum, that nested slot is emitted as unknown so the declaration remains standalone.

Experimental normalized .d.ts

Use normalizedDts when you want the plugin to post-process MoonBit-generated _build/.../*.d.ts files with mizchi/ts.mbt and replace MoonBit.Double, MoonBit.String, and similar aliases with clearer TypeScript surface types.

This integration is experimental. It rewrites generated declaration files in-place after MoonBit build output appears, and the exact normalization rules may still change. This post-process only runs on the Vite plugin path. If your workflow calls moon build directly, run the normalizer explicitly after build:

moon -C ../ts.mbt run src/cmd/mbt2ts -- normalize \
  /abs/path/to/_build/js/release/build/app.d.ts \
  /abs/path/to/_build/js/release/build/app.d.ts
moonbit({
  normalizedDts: {
    generatorRoot: "../ts.mbt",
  },
})

If you already use tsBridge, you can reuse its generator settings:

moonbit({
  tsBridge: {
    generatorRoot: "../ts.mbt",
    entries: ["./src/api/client.ts"],
  },
  normalizedDts: {},
})

Today the normalizer is intentionally narrow. It mainly rewrites the MoonBit-generated namespace alias layer into clearer primitives and named type imports. Treat it as a readability pass for MoonBit output, not a general purpose .d.ts pretty-printer.

When normalizedDts is enabled but skipped, the plugin now logs why:

  • no generator root was configured
  • the MoonBit build directory does not exist yet
  • no generated declaration files were found

Path Resolution

The plugin searches upward from root for moon.work / moon.work.json (workspace) first, then for moon.mod.json (single module). The resolved project root is where _build/ is expected.

Single module (legacy layout)

| Import | JS | WASM-GC | |---|---|---| | mbt:user/pkg | _build/js/release/build/pkg.js | _build/wasm-gc/release/build/pkg.wasm | | mbt:user/pkg/sub | _build/js/release/build/sub/sub.js | _build/wasm-gc/release/build/sub/sub.wasm |

Workspace (moon.work, multi-root layout)

Module name segments are inserted before the package path. mbt: imports are matched against every workspace member's moon.mod.json#name by longest prefix, so all members share one resolver.

| Import | JS | |---|---| | mbt:internal/app | _build/js/release/build/internal/app/app.js | | mbt:internal/shared | _build/js/release/build/internal/shared/shared.js | | mbt:internal/app/util | _build/js/release/build/internal/app/util/util.js |

See examples/monorepo_project for a full working workspace with two members (internal/app depends on internal/shared via a path deps).

// moon.work (at the workspace root)
members = [
  "./app",
  "./shared",
]

When running in workspace mode, moon build --watch is spawned at the workspace root so all members are built together into a single _build/.

Virtual / implement / overrides

MoonBit's moon.pkg.json supports three fields for swappable implementations:

  • "virtual": { "has-default": bool } — interface-only package (signatures declared in pkg.mbti)
  • "implement": "user/mod/iface" — a package that implements the above
  • "overrides": ["user/mod/impl_x"] — a main/app package selects which implementation to link

These are resolved entirely by moon's linker; the plugin serves the final .js output transparently. Virtual and implement-only packages produce only .mi/.core intermediates (no runtime .js); trying to import 'mbt:<virtual>' directly prints a hint pointing to the app package that owns the overrides.

See examples/overrides_project.

Multiple backends in one project

Instantiate the plugin per backend with a unique prefix:

// vite.config.ts
const plugins = [
  moonbit({ target: 'js' }),                        // mbt:*
  moonbit({ target: 'wasm-gc', prefix: 'mbtw:' }),  // mbtw:*
];
export default defineConfig({
  plugins,
  worker: {
    format: 'es',
    plugins: () => [
      moonbit({ target: 'wasm-gc', prefix: 'mbtw:', watch: false }),
    ],
  },
});
// main.ts — runs on JS backend
import { greet } from 'mbt:my';

// worker.ts — runs as a Web Worker using the wasm-gc backend
import init from 'mbtw:my/heavy';

See examples/multi_backend_project for a complete setup that offloads CPU-heavy work to a Wasm-GC worker while the main thread stays on the JS backend.

License

MIT