JavaScript and TypeScript

Volt compiles JavaScript and TypeScript through OXC, a Rust-based toolchain. ES2020+ syntax is downleveled to your configured target. TypeScript types are stripped at compile time without type-checking — use tsc --noEmit separately if you want type safety.

Vue and Svelte

Vue single-file components (.vue) compile through Vize with scoped CSS and optional Vapor mode. Svelte components (.svelte) compile through QuickBEAM. Both work without Node.js installed. Import .vue and .svelte files directly from your application code.

React and JSX

JSX and TSX files are transformed by OXC. Set import_source: "react" in your Volt config to use React's automatic JSX runtime. Volt includes a React plugin that pre-bundles react, react-dom/client, and react/jsx-runtime into a single vendor module for efficient loading.

See Frontend Frameworks for setup instructions and entry point examples for each framework.

Tailwind CSS

Volt compiles Tailwind CSS v4 natively. Oxide scans source files in parallel for candidate class names, then the Tailwind compiler generates CSS. In dev mode, only changed files are re-scanned — editing a .heex template triggers an incremental CSS rebuild and hot-swaps the stylesheet without a page reload.

See Tailwind CSS for configuration and the programmatic API.

Hot Module Replacement

The dev server pushes updates over a WebSocket. CSS changes hot-swap without a page reload. JavaScript modules that call import.meta.hot.accept() are re-imported in place. Vue style-only changes skip the full recompile.

See HMR for the import.meta.hot API.

Code Splitting

Dynamic import() calls automatically create separate async chunks. Shared modules between chunks are extracted to avoid duplication. Manual chunk boundaries can be configured for vendor splitting.

See Code Splitting for examples and configuration.

CSS Modules

Files ending in .module.css get scoped class names via LightningCSS:

/* button.module.css */
.primary { color: blue }
import styles from './button.module.css'
console.log(styles.primary) // "ewq3O_primary"

Static Assets

Images, fonts, and other files are handled automatically when imported:

import logo from './logo.svg'  // small files → data URI
import photo from './photo.jpg' // large files → hashed URL

JSON Imports

import config from './config.json'
console.log(config.apiUrl)

Environment Variables

Create .env files in your project root. Variables prefixed with VOLT_ are available as import.meta.env.VOLT_* in client code. Built-in variables include MODE, DEV, and PROD.

See Environment Variables for file loading order and modes.

Glob Imports

import.meta.glob() resolves glob patterns at build time:

const pages = import.meta.glob('./pages/*.ts', { eager: true })

See Glob Imports for lazy vs eager loading.

Volt.entry_path/2

Use Volt.entry_path/2 in your root layout to link to the entry script:

<script defer phx-track-static type="module" src={Volt.entry_path(MyAppWeb.Endpoint)}></script>

In development, this returns the source path served by the dev server (e.g. /assets/js/app.ts). In production, it reads manifest.json and returns the content-hashed path (e.g. /assets/js/app-5e6f7a8b.js).

Multi-Entry Builds

For multi-page apps, specify multiple entry points:

config :volt, entry: ["assets/js/app.ts", "assets/js/admin.ts"]

Or via CLI: mix volt.build --entry assets/js/app.ts --entry assets/js/admin.ts

Each entry produces its own bundle and manifest entry. Use Volt.entry_path/2 with the :name override to reference a specific entry:

Volt.entry_path(MyAppWeb.Endpoint, name: "admin")

HTML Entry Points

Entry files can be HTML — <script src="..."> tags are extracted as JS entry points:

mix volt.build --entry index.html

Import Aliases

Configure path aliases in Volt config:

config :volt, aliases: %{"@" => "assets/src"}
import { Button } from '@/components/Button'

Volt also reads compilerOptions.paths from tsconfig.json automatically.

External Modules

Exclude packages the host page already provides:

config :volt, external: ~w(phoenix phoenix_html phoenix_live_view)

Source Maps

Production builds write .map files by default. Use sourcemap: :hidden to write maps without the URL comment (for Sentry, Datadog, etc.), or sourcemap: false to skip.

Formatting and Linting

Volt includes Prettier-compatible JS/TS formatting via oxfmt (~30× faster) and 650+ oxlint rules, both as Rust NIFs. Volt.Formatter integrates with mix format. Custom lint rules can be written in Elixir.

See Formatting and Linting for setup and configuration.

Web Workers

Worker URLs using the standard pattern are rewritten in production builds:

const worker = new Worker(new URL('./worker.ts', import.meta.url))

The worker file is built as a separate entry and the URL is rewritten to the hashed output path.

Dev Console Forwarding

In development, browser console.log, console.warn, and console.error calls are forwarded to the Elixir terminal, so you can see client-side logs alongside server logs without opening browser DevTools.

Error Overlay

Compilation errors in development are displayed as a full-screen browser overlay with the error message. The overlay dismisses on click and clears automatically when the error is fixed.

Plugins

Extend the build pipeline with the Volt.Plugin behaviour. Plugins can resolve imports, load custom file formats, transform code, and run JavaScript build tools through Volt.JS.Runtime.

See Plugins for the full hook API and examples.