⚠️ Warning: This project is not yet stable and may undergo significant changes before reaching version 1.0.0. We strongly advise against using it in production environments.
JavaScript/TypeScript ergonomics, Rust performance, native + wasm + napi outputs. DashScript compiles JavaScript/TypeScript to idiomatic Rust — shipped as a native binary, with WebAssembly and napi targets on the same mapping table — pursuing maximum test262 conformance, the WinterTC Minimum Common Web API, and a bridge between the npm and cargo ecosystems. Static-first, graceful degradation: oxc parses
.ts/.tsx/.js/.jsx/.mjs/.cjs, the static translator maps the ESM surface, and whatever it cannot lower statically (CommonJSrequire/module.exports, dynamic reflection, Web API edges) degrades to the embedded QuickJS engine — so compatibility holds.
| Package | Version | Description |
|---|---|---|
| dashscript | The ds toolchain in one package — translates oxc AST → Rust, package.json → Cargo.toml, bindgen, CLI, editor types. |
# one package — provides the `ds` command
$ pnpm add -g dashscript
# or as a standalone binary
$ cargo install dashscriptNo separate Rust install. DashScript manages its own pinned Rust toolchain — downloaded on first use like an npm dependency — so
pnpm add dashscriptis all you need.
// main.ts — TypeScript-flavored source
function greet(name: string): string {
return `Hello, ${name}!`;
}
const message: string = greet("DashScript");$ ds main.ts # run a file directly (like `node a.js`)
$ ds build main.ts # → dist/<name> — a native binary (default)
$ ds build main.ts --target rust # → dist/<name>/ — the translated Rust crateds main.ts runs a .ts file directly — translate → compile (cached) → run, like node a.js. ds build parses .ts with oxc, translates the AST to idiomatic Rust, and ships a native binary in dist/<name>. Pass --target rust to stop at the translated crate instead:
// generated by DashScript (--target rust)
fn greet(name: &str) -> String {
format!("Hello, {name}!")
}
fn main() {
let message: String = greet("DashScript");
}$ ds run <script> # run a package.json script (like `pnpm run`)ds run runs a shell command from package.json scripts through the system shell, like pnpm run. (ds run is always explicit — running a file is ds <file.ts>.)
DashScript projects use a package.json — the one manifest every JS tool already reads. Standard npm fields map straight to cargo: bin declares executables (one project compiles to several binaries); main → [lib]; Rust crate deps under dashscript.cargo.dependencies → [dependencies] (npm dependencies stay JS deps, never reaching Cargo.toml). On ds build, the package lowers to a Cargo.toml:
{
"name": "my-app",
"bin": {
"serve": "serve.ts",
"migrate": "migrate.ts"
},
"dashscript": {
"target": "bin",
"cargo": {
"dependencies": {
"serde": "1.0",
"tokio": "1.0"
}
}
}
}# fetch the crate — no .d.ts stub; types come straight from the crate source
$ ds add cargo:serdeds add cargo:<crate> fetches the crate and records it in package.json, with no .d.ts stub — Rust is statically typed, so the crate's own source (in ~/.cargo) is the complete type truth, read directly by the editor the way rust-analyzer reads its deps. For a local Rust file, ds add <file>.rs runs bindgen to emit a .d.ts beside it. No separate ds gen step.
ds check is the composite — translatability plus a format check (vp check style); ds lint checks translatability alone; ds fmt formats in place. With no argument each runs over every .ts in the project, and ds check --fix writes the fix. All built in-process on the parsed AST (no external oxlint/oxfmt):
$ ds check # lint + format check, whole project (like `vp check`)
$ ds check --fix # ...and write formatting fixes
$ ds lint # translatability check only
$ ds fmt # format every .ts in placeThe emitted Rust is finally verified with cargo check / cargo clippy on the generated project.
DashScript maps a TypeScript-flavored surface to Rust semantics, growing incrementally as real demand drives each mapping — never speculatively.
- Language coverage — the full Rust type/memory-safety model (ownership, borrowing, lifetimes, traits), with TypeScript as the presentation only. Today: most of the JavaScript/TypeScript surface (auto clone/borrow/narrowing bridge the gaps); the residual tail degrades to the embedded QuickJS engine rather than failing.
- Standard libraries — ES built-ins (
Math/String/Array/Object/Number, … — largely mapped today), then thenode:stdlib (node:crypto,node:zlib,node:fs, …). Web APIs are tracked separately under WinterTC below. - Compatibility — degrade, don't reject — a construct the static translator can't lower runs under an embedded QuickJS engine at the function granularity (inheriting full ECMAScript semantics) instead of failing, so existing TS/JS keeps working without a rewrite. Reflection (
typeof/instanceof/Object.keys) resolves at compile time where the type is known — zero-cost, no dynamic value model — so only the residual dynamic cases degrade. - Package integration — every package is a crate. A workspace member or an npm dependency lowers to its own cargo crate, referenced by a path dependency (
use office_open_xml::X) —node_modules-style layering, not bundling. DashScript translates each.ts/.js/.mjs/.cjsfile directly (no rolldown/esbuild): cargo's workspace + path dependency is already the Rust ecosystem'snode_modules, and.jspackages take the same static-translate-first path as.ts. Today: workspace members build as independent crates; npm-package independent crates and.jsstatic-first are in progress. - WinterTC Minimum Common Web API — the Ecma TC55 (formerly WinterCG) Minimum Common Web Platform API (shared by Node, Deno, Bun, Cloudflare Workers), on the same static-first + per-function degrade model as the ECMAScript core: each Web API maps to a Rust crate first (static, zero-cost), and an edge the static translator cannot lower takes the per-function engine path with the Web API registered as a builtin (same Rust impl). Synchronous APIs map first (URL,
URLSearchParams,TextEncoder,Headers,Blob,FormData,SubtleCrypto, … today); asynchronous ones (fetch,setTimeout, Streams) introduce a tokio runtime and a thread model. Conformance is a WPT subset, run static-first with per-function engine degrade (mirroring test262). - More outputs —
wasmandnapitargets (Rust compiled to WebAssembly / napi-rs), so.tsships to the web and Node ecosystems. The static segment (including compile-time reflection) lowers to plain wasm — no engine bundled for the common case; only the degraded minority pulls in QuickJS-wasm (Javy provesrquickjscompiles towasm32-wasip1). - Developer experience —
ds test, editor/LSP integration, conformance fixtures. - Self-hosting (north star) — rewrite the toolchain in
.tsitself, reachingoxc(and any Rust crate) through bindgen.
- Node.js 18.x or higher
- pnpm 9.x or higher (recommended package manager)
- Rust — managed by DashScript (a pinned toolchain is downloaded on demand); no separate install needed to use DashScript
- Git for version control
-
Clone the repository:
git clone https://github.com/DemoMacro/dashscript.git cd dashscript -
Install dependencies:
pnpm install
-
Build all packages:
pnpm build
pnpm build # Build all packages
cd packages/<pkg> && pnpm build # Build one package
vp check # Lint & formatWe welcome contributions! See CONTRIBUTING.md for the full contribution workflow, coding standards, and PR checklist.
This project is licensed under the MIT License - see the LICENSE file for details.
Built with ❤️ by Demo Macro