Compiles to the vanilla JS you would have written.
A build-time compiler and a fine-grained reactive runtime for UI. You write
standard JavaScript — signals plus html `` tagged templates — and it runs
in the browser with no build step at all. The compiler is an optimizer, not a
requirement. No virtual DOM, no re-render-and-diff, zero runtime dependencies.
A complete compiled counter app — runtime included, minified and gzipped — is 1.98 KB.
import { signal, computed, html, mount } from '@amojs.dev/core';
function Counter() {
const count = signal(0);
const double = computed(() => count.value * 2);
return html`
<div>
<p>count = ${count} · double = ${double}</p>
<button onclick=${() => count.value++}>+1</button>
</div>`;
}
mount(Counter, document.getElementById('app'));Pre-1.0. Published so the name is claimed and tooling can depend on a real version. There is no SSR and no documentation site yet — see packages/core/README.md for the full list of what is missing.
| package | what it is | published |
|---|---|---|
@amojs.dev/core |
the runtime — signals, html ``, mount. Plain JS, ships to browsers raw. Subpaths `@amojs.dev/core/runtime` and `@amojs.dev/core/compiled` never load the template parser; `…/browser` and `…/browser/runtime` are prebuilt single files for one-request loading. |
yes |
@amojs.dev/compiler |
parse → IR → codegen for two targets (DOM, and strings for node), plus build, eject, ssg and diagnose. TypeScript, runs on node only. |
yes |
@amojs.dev/cli |
the amo binary — amo build | dev | serve (each × csr | ssr | ssg) and amo eject. TypeScript input, public/ root assets, self-contained islands. |
yes |
create-amojs |
the scaffold — npm create amojs my-app: ssg or ssr, JS or TS. |
yes |
@amojs.dev/vite |
Vite plugin — compiles templates during a normal Vite build, so tree-shaking, splitting and the dev server are Vite's job. | yes |
@amojs.dev/router |
routing on the Navigation API — pages get resolved data, so no page ever renders a loading state. Parser-free; amo eject hands it over too. |
yes |
@amojs.dev/bench |
micro-benchmarks. Competitor libraries are quarantined here so the runtime keeps zero dependencies. | never |
Editor support lives in a separate repository:
amojs-dev/language-tools —
template highlighting, embedded HTML/JS IntelliSense, and inline diagnostics
produced by this compiler's own parser, so the editor and amo build cannot
disagree.
Every claim above is a gate, not an adjective.
| gate | measured | budget |
|---|---|---|
| compiled counter app, all-in (bundle + min + gz) | 1982 B | ≤ 2048 B |
| runtime minus template parser, bundled | 2520 B | ≤ 2560 B |
| no-build app, template parser included | 4020 B | ≤ 4096 B |
| compiled bytes ÷ hand-written vanilla bytes | 1.087 | ≤ 1.10 |
Plus two correctness gates that are the real point:
- DOM-work parity. AmoJS emits exactly the mutations a hand-written vanilla app does — create, update, churn, toggle, append, prepend, remove, clear — and list moves are LIS-minimal (swap = 2, reverse = n−1, rotate = 1).
- Both modes, one meaning. Every golden fixture is executed raw and compiled and asserted identical. The uncompiled runtime is the semantic source of truth; the compiler may only be faster and smaller.
Benchmark numbers and their caveats live in
packages/bench/RESULTS.md.
These are locked, and the codebase is shaped by them:
- A hole is a constant, a signal, or a function — same meaning compiled and uncompiled.
- Zero runtime dependencies. Compiler-side dependencies run on node and never reach a browser.
- The uncompiled runtime is the semantic source of truth.
- The IR never names DOM APIs. It describes intent, so a second target is a second codegen, not a rewrite.
- Browser-shipped code is JS + JSDoc (type-checked with
tsc --checkJs); node-only code is TypeScript. - No custom file extension. The compiler finds its targets by the
import … from '@amojs.dev/core'statement, never by filename. - The compiler stays dumb. It rewrites templates and specifiers and touches nothing else — no module-boundary reasoning, no lazy-loading, no closure serialization. A local, mechanical rewrite can be trusted next to any other tool.
Deliberately never built: hydration, a virtual DOM, a component API or lifecycle object, deep reactivity via Proxy, global event delegation, and a styling solution inside the core.
pnpm install # dev tooling only — vitest, happy-dom, esbuild, tsc
pnpm test # unit, golden, size, parity and e2e suites
pnpm build # compile @amojs.dev/compiler and @amojs.dev/cli to dist/
pnpm check # build, then type-check everything including JS via JSDoc
pnpm bench # reactive-graph micro-benchmarks
pnpm amo build [csr|ssr|ssg] [src] [out] # the CLI, after pnpm build
pnpm amo eject <src> <out> [--runtime <dir>]Package manager is pnpm; pnpm-workspace.yaml drives the monorepo.
MIT © Hamidreza Behzadi