Compilation
This page explains what happens when you run tova build or tova dev, what files are generated, and how the production build pipeline works.
Build Command
tova build [src_dir] [--output dir] [--production]By default, tova build compiles all .tova files in the current directory and writes output to .tova-out/.
| Flag | Description |
|---|---|
--output, -o | Output directory (default: .tova-out) |
--production | Production build with bundling, hashing, and minification |
--watch | Watch for file changes and rebuild |
Output Structure
For a file named app.tova with all three blocks:
.tova-out/
app.shared.js # Shared types and functions
app.server.js # Server code (Bun.serve)
app.client.js # Client code (reactive runtime)
runtime/
reactivity.js # Reactive runtime (signals, effects, DOM)
rpc.js # RPC bridge client
router.js # Client-side router
devtools.js # DevTools (opt-in, development only)With named blocks, additional files are generated:
.tova-out/
app.shared.js
app.server.js # Default (unnamed) server
app.server.api.js # server "api" { }
app.server.events.js # server "events" { }
app.client.js
runtime/
reactivity.js
rpc.js
router.jsIf only some blocks exist, only their corresponding files are generated. A file with only server {} produces only app.server.js and app.shared.js (if a shared block exists).
Server Output
The server file (app.server.js) is a standalone Bun script. It contains:
- Import of shared code --
import ... from './app.shared.js' - Standard library functions -- inline runtime utilities
- Database setup -- SQLite connection, model registration (if
dbandmodeldeclarations exist) - Server functions -- all functions defined in the server block
- RPC endpoints --
POST /rpc/<name>for each server function - Route handlers -- explicit
route GET "/path" => handlerdeclarations - Middleware -- request logging, CORS, auth, etc.
- HTTP server startup --
Bun.serve()with the configured port and request handler - Client HTML serving -- the root route (
/) serves the client HTML page
How the Server Starts
The generated server file ends with a Bun.serve() call:
Bun.serve({
port: process.env.PORT || 3000,
async fetch(req) {
// Match routes, RPC endpoints, static files
// Falls back to serving client HTML at /
}
});Routes are matched in order of specificity: static paths first, then parameterized paths (:id), then wildcards (*). RPC endpoints (/rpc/<name>) are registered before explicit routes.
Running the Server
# Direct execution
bun run .tova-out/app.server.js
# With custom port
PORT=8080 bun run .tova-out/app.server.js
# Or use the dev server (recommended for development)
tova devClient Output
The client file (app.client.js) is a JavaScript module that gets embedded into an HTML page. It contains:
- Runtime imports --
createSignal,createEffect,createComputed,mount, and other reactive primitives fromruntime/reactivity.js - RPC import --
rpcfunction fromruntime/rpc.js - Shared code -- inlined shared types and functions
- Standard library -- inline stdlib utilities
- Server RPC proxy --
const server = new Proxy(...)for transparent RPC - Reactive state --
createSignal()calls for eachstatedeclaration - Computed values --
createComputed()calls for eachcomputeddeclaration - Functions -- client-side functions and event handlers
- Effects --
createEffect()calls, auto-wrapped asasyncif they contain RPC calls - Components -- component functions using the
tova_el()virtual DOM helper - Mount call --
mount(App, document.getElementById("app"))to start the application
State Compilation
Tova reactive state compiles to SolidJS-style signal pairs:
state count = 0
state name = "World"Becomes:
const [count, setCount] = createSignal(0);
const [name, setName] = createSignal("World");In generated code:
- Reading
countbecomescount()(calling the getter) - Writing
count = 5becomessetCount(5)(calling the setter) - Compound
count += 1becomessetCount(count() + 1)
HTML Generation
The client JS is embedded into an HTML page. During development, tova dev generates an HTML file like:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Tova App</title>
</head>
<body>
<div id="app"></div>
<script type="module">
// Reactive runtime inlined here
// RPC runtime inlined here
// Client code inlined here
</script>
</body>
</html>The runtime is inlined directly into the HTML so there are no extra network requests during development. The server serves this HTML at the root route (/).
Shared Output
The shared file (app.shared.js) contains plain JavaScript from the shared {} block. Both the server and client import from it:
// app.shared.js
function User(id, name, email) {
return { id, name, email };
}
function validate_email(email) {
return email.includes("@");
}If the shared block is empty or absent, no app.shared.js file is generated.
Dev Server
tova dev provides a complete development experience:
tova dev [src_dir] [--port 3000]The dev server performs the following steps:
1. Compile
All .tova files in the source directory are compiled. Output goes to .tova-out/ inside the source directory.
2. Copy Runtime
Runtime files (reactivity.js, rpc.js, router.js) are copied from the Tova installation to .tova-out/runtime/.
3. Generate HTML
For each browser block, the dev server generates an index.html file with the reactive runtime and client code inlined as a module script.
4. Start Server Processes
Each server block (default and named) is spawned as a separate Bun child process with its assigned port:
Starting server on port 3000
Starting server:api on port 3001
Starting server:events on port 3002The default server serves the client HTML on / and handles all routes and RPC endpoints. Named servers handle their own routes.
5. Watch for Changes
The dev server watches for .tova file changes using a file watcher with debounce. When a file changes:
- All server processes are killed
- All
.tovafiles are recompiled - New server processes are spawned
- The page in the browser needs a manual refresh (no hot module replacement yet)
Dev Server Output Example
Tova dev server starting...
Compiled 1 file(s)
Output: .tova-out/
Starting server on port 3000
Client: .tova-out/index.html
1 server process(es) running
-> server: http://localhost:3000Production Build
tova build --production generates optimized output for deployment:
tova build --productionWhat Production Build Does
- Compiles all
.tovafiles - Bundles all server code into a single file, all client code into a single file, all shared code into a single file
- Inlines the reactive runtime and RPC runtime into the client bundle (eliminating import statements)
- Hashes output filenames for cache busting:
server.<hash>.js,client.<hash>.js - Minifies client JavaScript using Bun's built-in bundler (
Bun.buildwithminify: true) - Generates production HTML referencing the hashed client bundle
Production Output Structure
.tova-out/
server.a1b2c3d4e5f6.js # Bundled server (stdlib + shared + server code)
client.f6e5d4c3b2a1.js # Bundled client (runtime + shared + client code)
client.f6e5d4c3b2a1.min.js # Minified client
index.html # Production HTML referencing hashed client JSContent Hashing
File hashes are generated using SHA-256 (first 12 hex characters). The hash changes whenever the file content changes, enabling long-lived cache headers:
Cache-Control: public, max-age=31536000, immutableProduction HTML
The generated index.html is minimal:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Tova App</title>
</head>
<body>
<div id="app"></div>
<script src="client.f6e5d4c3b2a1.js"></script>
</body>
</html>Running in Production
# Build
tova build --production
# Run the server
bun run .tova-out/server.<hash>.jsThe production server is a standalone Bun script, same as development. It serves the client HTML and handles all routes and RPC endpoints.
Source Maps
The build pipeline generates source maps for debugging. When the compiler emits code, it tracks source-to-output line mappings and writes .map files alongside the JavaScript output:
.tova-out/
app.server.js
app.server.js.map # Source map for server
app.client.js
app.client.js.map # Source map for clientSource maps use the standard VLQ-encoded format and reference the original .tova file(s). The JavaScript output includes a //# sourceMappingURL= comment pointing to the map file.
This means that when debugging in browser devtools or in a Bun error stack trace, line numbers point back to the original .tova source rather than the generated JavaScript.
Source Map Structure
The Tova compiler uses a SourceMapBuilder class that tracks (sourceLine, sourceCol) -> (outputLine, outputCol) mappings during code generation. Each code generator method (genExpression, genStatement, etc.) records these mappings via a _sourceMappings array in the base codegen.
For merged multi-file output, the source map lists all contributing .tova files in its sources array and includes the content of each file in sourcesContent. This allows debuggers to map generated lines back to the correct original file.
Multi-File Projects
Same-Directory Merging
When multiple .tova files exist in the same directory, the compiler merges them before code generation. All same-type blocks are combined into a single output:
src/
types.tova # shared { type User { ... } }
server.tova # server { db, routes, functions }
components.tova # browser { component Header, component UserList }
app.tova # browser { state, effects, component App }All browser {} blocks from components.tova and app.tova merge into one client output. App can reference Header and UserList without imports. All shared {} blocks merge. All server {} blocks merge.
The compiler checks for duplicate declarations across files. If two files define the same component name, state variable, server function, route, or type, a clear error is reported showing both file locations.
Output for Merged Projects
The output uses the directory name as the base filename:
.tova-out/
src.shared.js # merged shared blocks from all files in src/
src.server.js # merged server blocks
src.client.js # merged browser blocks
runtime/
...Cross-Directory Imports
Files in subdirectories are separate modules. They require explicit imports:
// src/app.tova
import { validate_email } from "./utils/validators.tova"The compileWithImports() function resolves .tova imports across directories, compiles dependencies, and rewrites import paths to point to the generated .js files. Circular imports are detected and reported as errors.
Output for Cross-Directory Projects
.tova-out/
src.shared.js # merged from src/*.tova
src.server.js
src.client.js
validators.shared.js # from src/utils/validators.tova (separate module)
runtime/
...Build Pipeline Summary
.tova source files (grouped by directory)
|
v
+------------------+
| Group by | Same-directory files are merged;
| Directory | subdirectories compiled separately
+------------------+
|
v (per directory group)
+-------------+
| Lexer | Tokenize each file
+-------------+
|
v
+-------------+
| Parser | Build AST per file
+-------------+
|
v
+------------------+
| Merge ASTs | Combine same-type blocks, strip
| | same-directory imports, tag source files
+------------------+
|
v
+------------------+
| Validate | Detect duplicate declarations
| Merged AST | across files
+------------------+
|
v
+-------------+
| Analyzer | Validate, warn on issues
+-------------+
|
v
+-------------------+ +-------------------+ +--------------------+
| ServerCodegen | | ClientCodegen | | SharedCodegen |
| - RPC endpoints | | - Reactive state | | - Types |
| - Routes | | - Components | | - Validation |
| - DB setup | | - RPC proxy | | - Constants |
| - Bun.serve() | | - mount() | | |
+-------------------+ +-------------------+ +--------------------+
| | |
v v v
src.server.js src.client.js src.shared.js
|
v
index.html
(runtime + client inlined)Related Pages
- Architecture Overview -- the three-block model
- Server Block -- what goes in the server output
- Browser Block -- what goes in the client output
- Shared Block -- what goes in the shared output
- Named Blocks -- multi-server compilation
- RPC Bridge -- how RPC endpoints are generated