Skip to content

Build System ​

The Tova build system compiles .tova source files into JavaScript, handling shared types, server blocks, browser blocks, and runtime dependencies.

Basic Build ​

bash
tova build
tova build src
tova build src --output dist
tova build src --production
tova build src --binary my-app

By default, tova build compiles all .tova files in the current directory and outputs JavaScript to .tova-out/.

Flags:

FlagDescription
--output, -oOutput directory (default: .tova-out)
--productionProduction build with bundling, content hashing, and minification
--binary <name>Compile to a standalone executable
--watchWatch for file changes and rebuild
--no-cacheSkip incremental build cache
--strictEnable strict type checking
--strict-securityPromote security warnings to errors; shows security scorecard
--verboseShow detailed output (timing, cached files, security scorecard)
--quietSuppress non-error output

Output Structure ​

After building, the .tova-out/ directory contains:

.tova-out/
  app.shared.js          # Shared types and functions
  app.server.js          # Default server block
  app.server.api.js      # Named server block "api"
  app.server.events.js   # Named server block "events"
  app.client.js          # Default browser block
  runtime/
    reactivity.js        # Reactive state runtime
    rpc.js               # Server RPC client
    router.js            # Client-side router

Each .tova source file produces up to several output files:

OutputSourceDescription
<name>.shared.jsshared { ... }Types and functions available to both server and client
<name>.server.jsserver { ... }Default server block code
<name>.server.<block>.jsserver "<block>" { ... }Named server blocks
<name>.client.jsbrowser { ... }Default browser block code
<name>.client.<block>.jsbrowser "<block>" { ... }Named browser blocks
<name>.jscli { ... }Standalone CLI executable with #!/usr/bin/env node shebang

Incremental Caching ​

The build system caches compilation results in .tova-out/.cache/manifest.json. On subsequent builds, files whose content has not changed are skipped automatically:

  Building 5 file(s)...

  ○ src/types.tova (cached)
  ○ src/utils.tova (cached)
  ✓ src/app.tova

This significantly speeds up rebuilds in large projects. Use --no-cache to force a full recompile:

bash
tova build --no-cache

For multi-file directories, the cache tracks all files in the group -- if any file changes, the entire directory is recompiled.

Watch Mode ​

Use --watch to automatically rebuild when .tova files change:

bash
tova build --watch

The watcher uses fs.watch() with a 100ms debounce timer to batch rapid saves. On change, only affected files and their transitive dependents are recompiled.

Production Builds ​

bash
tova build --production
tova build src --production --output dist

Production builds apply four optimizations:

  1. Bundling -- All imports and dependencies are resolved and inlined into single output files, eliminating the need for a module loader at runtime. npm imports used in client code are bundled via Bun.build.

  2. Content Hashing -- Output filenames include content hashes (e.g., app.client.a1b2c3d4.js) for cache-busting. When file contents change, the hash changes, ensuring browsers fetch the new version.

  3. Minification -- Whitespace, comments, and unnecessary characters are removed to reduce file size. Uses Bun.Transpiler with a fallback to a custom minifier.

  4. Dead Function Elimination -- Unreachable top-level functions are removed via reachability analysis. Starting from non-function root code, the compiler traces all transitively called functions and removes the rest.

Production builds also generate an index.html with hashed script references.

Binary Builds ​

Compile a Tova project into a standalone executable:

bash
tova build --binary my-app

This compiles all .tova files into a single JavaScript bundle and uses bun build --compile to produce a self-contained binary. If the code defines a main() function, it is called automatically.

CLI Builds ​

When a .tova file contains a cli {} block, the build system produces a single executable JavaScript file instead of the usual shared/server/client split:

bash
tova build src --output dist
dist/
  mycli.js        # #!/usr/bin/env node + generated CLI code

The output file includes:

  • A #!/usr/bin/env node shebang line
  • Executable permissions (chmod 755)
  • All stdlib functions used by the CLI
  • Complete argument parsing, help generation, and subcommand routing

Run it directly:

bash
node dist/mycli.js --help
# or
./dist/mycli.js --help

For a standalone binary, combine with --binary:

bash
tova build --binary mycli

See the CLI Block guide for details on writing CLI tools.

Source Maps ​

The build system generates source maps for compiled files. Each .js output file gets a corresponding .js.map file that maps back to the original .tova source.

Source maps are appended as a sourceMappingURL comment:

js
// Generated JavaScript code...
//# sourceMappingURL=app.server.js.map

This enables debugging in browser DevTools and editor integrations that can step through the original Tova source.

Multi-File Projects ​

Same-Directory Merging ​

When multiple .tova files exist in the same directory, the build system merges them automatically. All same-type blocks are combined into a single output per directory:

src/
  types.tova           # shared { type Task { ... } }
  server.tova          # server { db, model, routes }
  components.tova      # browser { component StatsBar, component TaskItem }
  app.tova             # browser { state, effects, component App }

All browser {} blocks from components.tova and app.tova merge into one client output. App can reference StatsBar and TaskItem without imports. All shared {} blocks merge. All server {} blocks merge.

The output uses the directory name as the base filename:

.tova-out/
  src.shared.js        # merged shared blocks
  src.server.js        # merged server blocks
  src.client.js        # merged browser blocks
  runtime/
    ...

Single-file directories compile exactly as before -- no behavior change.

Duplicate Detection ​

If two files in the same directory declare the same top-level name, the compiler reports an error with both file locations:

Error: Duplicate component 'App'
  → first defined in app.tova:15
  → also defined in main.tova:42

The following are checked for conflicts across files:

  • Client blocks: component names, top-level state, computed, store, and fn names
  • Server blocks: fn names, model names, route conflicts (same method + path), singleton configs (db, cors, auth, session, etc.)
  • Shared blocks: type names, fn names, interface/trait names

Declarations scoped inside components or stores (like state inside a component) do not conflict across files.

Cross-Directory Imports ​

Files in subdirectories are separate modules that require explicit imports:

tova
// src/app.tova -- import from subdirectory
import { validate_email } from "./utils/validators.tova"

During compilation of cross-directory imports:

  • .tova imports are discovered and compiled first
  • Import paths are rewritten from .tova to .js in the output
  • Circular dependencies are detected and reported as errors
  • A compilation cache prevents re-compiling files that have already been processed

Runtime Files ​

The build copies Tova runtime files into the output runtime/ directory:

FilePurpose
reactivity.jsReactive state management (state, computed, effect)
rpc.jsClient-to-server RPC call infrastructure
router.jsClient-side URL routing

These are automatically imported by the generated server and client code.

Named Server Block Output ​

When using multiple named server blocks, each gets its own output file:

tova
server "api" {
  route GET "/api/users" => list_users
}

server "events" {
  // WebSocket server
}

This compiles to:

  • app.server.api.js -- the API server
  • app.server.events.js -- the events server

Each named server runs as a separate process on its own port, configurable via PORT_<NAME> environment variables (e.g., PORT_API=3001, PORT_EVENTS=3002).

Build Errors ​

When compilation fails, the build reports errors with Rust/Elm-style diagnostics including source context and caret markers pointing to the exact location:

error: Undefined identifier 'usrName'
  --> src/app.tova:12:5
   |
12 |     print(usrName)
   |           ^^^^^^^ Did you mean 'userName'?

The build continues compiling remaining files even when individual files fail, reporting a summary at the end:

  Build complete. 4/5 succeeded.

Use --debug for full stack traces on compilation errors.

Released under the MIT License.