README.md

# Volt ⚡

Elixir-native frontend build tool. Dev server with HMR, Tailwind CSS compilation, and production bundling — no Node.js, no esbuild, no Vite.

Built on Rust NIFs: [OXC](https://hex.pm/packages/oxc) for JS/TS, [Vize](https://hex.pm/packages/vize) for Vue SFCs + LightningCSS, [Oxide](https://hex.pm/packages/oxide_ex) for Tailwind scanning, and [QuickBEAM](https://hex.pm/packages/quickbeam) for the Tailwind compiler.

## Features

- **Zero JavaScript toolchain** — no `node_modules`, no npm, no npx
- **JS/TS bundling** — parse, transform, minify via OXC (Rust)
- **Vue SFC support** — single-file components with scoped CSS and Vapor IR
- **Tailwind CSS v4** — parallel content scanning + full compiler, ~40ms builds
- **Dev server** — on-demand compilation with mtime caching and error overlays
- **HMR** — file watcher, WebSocket push, CSS hot-swap without page reload
- **Production builds** — tree-shaken bundles with content-hashed filenames and manifests

## Installation

```elixir
def deps do
  [{:volt, "~> 0.1.0"}]
end
```

## Quick Start

### Dev Server

Add the Plug to your Phoenix endpoint:

```elixir
# lib/my_app_web/endpoint.ex
if code_reloading? do
  plug Volt.DevServer,
    root: "assets",
    prefix: "/assets",
    target: "es2020"
end
```

Start the watcher in `config/dev.exs`:

```elixir
config :my_app, MyAppWeb.Endpoint,
  watchers: [
    volt: {Mix.Tasks.Volt.Dev, :run,
      [~w(--tailwind --tailwind-css assets/css/app.css --watch-dir lib/)]}
  ]
```

### Production Build

```bash
mix volt.build \
  --entry assets/js/app.ts \
  --outdir priv/static/assets \
  --resolve-dir deps \
  --tailwind --tailwind-css assets/css/app.css
```

```
Building Tailwind CSS...
  app.css  23.9 KB
Built Tailwind in 43ms
Building assets/js/app.ts...
  app.js  128.4 KB
  manifest.json  1 entries
Built in 15ms
```

## Tailwind CSS

Volt compiles Tailwind CSS natively — no CLI binary, no CDN.

[Oxide](https://hex.pm/packages/oxide_ex) scans your source files in parallel for candidate class names, then the Tailwind v4 compiler (running in [QuickBEAM](https://hex.pm/packages/quickbeam)) generates the CSS. [LightningCSS](https://hex.pm/packages/vize) handles minification.

```elixir
# Programmatic API
{:ok, css} = Volt.Tailwind.build(
  sources: [
    %{base: "lib/", pattern: "**/*.{ex,heex}"},
    %{base: "assets/", pattern: "**/*.{vue,ts,tsx}"}
  ],
  css: File.read!("assets/css/app.css"),
  minify: true
)
```

Custom CSS works — `@import "tailwindcss"` is inlined automatically:

```css
@import "tailwindcss" source(none);

@custom-variant phx-click-loading (.phx-click-loading&, .phx-click-loading &);
[data-phx-session] { display: contents }
```

### Incremental Rebuilds

In dev mode, only changed files are re-scanned. If a `.heex` template adds new Tailwind classes, only those new candidates trigger a CSS rebuild — the browser gets a style-only update without a page reload.

## HMR

The file watcher monitors your asset and template directories:

| File type | Action |
|-----------|--------|
| `.ts`, `.tsx`, `.js`, `.jsx`, `.vue`, `.css` | Recompile via Pipeline, push update over WebSocket |
| `.ex`, `.heex`, `.eex` | Incremental Tailwind rebuild, CSS hot-swap |
| `.vue` (style-only change) | CSS hot-swap, no page reload |

The browser client auto-reconnects on disconnect and shows compilation errors as an overlay.

## Mix Tasks

### `mix volt.build`

Build production assets.

```
--entry          Entry file (default: assets/js/app.ts)
--outdir         Output directory (default: priv/static/assets)
--target         JS target (default: es2020)
--resolve-dir    Additional resolution directory (repeatable, e.g. deps)
--no-minify      Skip minification
--no-sourcemap   Skip source maps
--no-hash        Stable filenames (for dev builds)
--tailwind       Build Tailwind CSS
--tailwind-css   Custom Tailwind input CSS file
--tailwind-source  Source directory for scanning (repeatable)
```

### `mix volt.dev`

Start the file watcher for development.

```
--root           Asset source directory (default: assets)
--watch-dir      Additional directory to watch (repeatable)
--tailwind       Enable Tailwind CSS rebuilds
--tailwind-css   Custom Tailwind input CSS file
--target         JS target (default: es2020)
```

## Pipeline

`Volt.Pipeline` compiles individual files:

```elixir
# TypeScript
{:ok, result} = Volt.Pipeline.compile("app.ts", source)
result.code       #=> "const x = 42;\n"
result.sourcemap  #=> "{\"version\":3, ...}"

# Vue SFC
{:ok, result} = Volt.Pipeline.compile("App.vue", source)
result.code    #=> compiled JavaScript
result.css     #=> scoped CSS (or nil)
result.hashes  #=> %{template: "abc...", style: "def...", script: "ghi..."}

# CSS
{:ok, result} = Volt.Pipeline.compile("styles.css", source, minify: true)
```

## Stack

```
volt
├── oxc       — JS/TS parse, transform, bundle, minify (Rust NIF)
├── vize      — Vue SFC compilation, Vapor IR, LightningCSS (Rust NIF)
├── oxide_ex  — Tailwind content scanning, candidate extraction (Rust NIF)
├── quickbeam — Tailwind compiler runtime (QuickJS on BEAM)
└── plug      — HTTP dev server
```

## Demo

See [`examples/demo/`](examples/demo/) for a full Phoenix app using Volt + [PhoenixVapor](https://github.com/dannote/phoenix_vapor) — Vue templates rendered as native LiveView, Tailwind CSS, no JavaScript runtime for SSR.

## License

MIT © 2026 Danila Poyarkov