Skip to main content
Rowboat uses a multi-stage build system with TypeScript compilation, esbuild bundling, and Electron Forge for packaging.

Build System Overview

The build system uses different tools for different parts:
ComponentToolPurpose
Shared packageTypeScriptCompile to JS + type definitions
Core packageTypeScriptCompile to JS + type definitions
Main processTypeScript + esbuildCompile then bundle to single file
RendererViteBundle React app
PackagingElectron ForgeCreate distributable app
Package manager: pnpm is required for the workspace:* protocol used in the monorepo.

Development Build

Quick Start

1

Install dependencies

cd apps/x
pnpm install
2

Build workspace packages

npm run deps
Builds: sharedcorepreload
3

Start development mode

npm run dev
Starts Vite dev server and Electron with hot-reload.

Available Scripts

cd apps/x

# Development
npm run dev           # Build deps + start app
npm run deps          # Build shared → core → preload
npm run lint          # Run ESLint
npm run lint:fix      # Fix ESLint issues

# Individual builds
npm run shared        # Build @x/shared
npm run core          # Build @x/core
npm run preload       # Build preload scripts
npm run renderer      # Start Vite dev server
npm run main          # Build and start main process

Build Order

Packages must be built in dependency order:
shared (no dependencies)

core (depends on shared)

preload (depends on shared)

renderer (depends on shared)
main (depends on shared, core)
Always build shared before core, and both before main. The npm run deps command handles this automatically.

Why This Order?

Each package depends on type definitions from upstream packages:
// packages/core/src/index.ts
import { SomeType } from '@x/shared'  // Must be built first

// apps/main/src/main.ts
import { CoreService } from '@x/core'  // Must be built after shared

Main Process Bundle

The main process uses a two-stage build:

Stage 1: TypeScript Compilation

cd apps/x/apps/main
npm run build  # Runs: tsc && node bundle.mjs
TypeScript compiles src/ to intermediate JavaScript with:
  • Target: ES2022
  • Module: ESNext
  • Type definitions: Generated

Stage 2: esbuild Bundling

The bundle.mjs script bundles everything into a single file:
// apps/main/bundle.mjs (simplified)
import esbuild from 'esbuild'

await esbuild.build({
  entryPoints: ['dist/main.js'],
  bundle: true,
  platform: 'node',
  target: 'node18',
  outfile: '.package/dist/main.cjs',
  format: 'cjs',
  external: ['electron']
})
Why bundle? pnpm uses symlinks for workspace packages. Electron Forge’s dependency walker can’t follow these symlinks. Bundling creates a single file with all code, eliminating the need for node_modules in the packaged app.

Renderer Bundle

The renderer uses Vite for both development and production:

Development

cd apps/x/apps/renderer
npm run dev  # Starts dev server on http://localhost:5173
Vite provides:
  • Hot module replacement (HMR)
  • React Fast Refresh
  • Instant updates

Production

cd apps/x/apps/renderer
npm run build  # Builds to dist/
Vite outputs optimized assets:
  • Minified JavaScript
  • Code splitting
  • Hashed filenames for caching

Production Packaging

Electron Forge creates distributable packages.

Package (No Installer)

Creates an application bundle:
cd apps/x/apps/main
npm run package

# Output: out/rowboat-darwin-arm64/Rowboat.app

Make (With Installer)

Creates installers for distribution:
cd apps/x/apps/main
npm run make

# Output: out/make/Rowboat-0.1.0-arm64.dmg

Forge Configuration

Packaging is configured in apps/main/forge.config.cjs:
module.exports = {
  packagerConfig: {
    name: 'Rowboat',
    icon: './assets/icon',
    osxSign: {},
    osxNotarize: {
      appleId: process.env.APPLE_ID,
      appleIdPassword: process.env.APPLE_PASSWORD,
      teamId: process.env.APPLE_TEAM_ID
    }
  },
  makers: [
    { name: '@electron-forge/maker-dmg' },      // macOS
    { name: '@electron-forge/maker-squirrel' }, // Windows
    { name: '@electron-forge/maker-deb' },      // Debian/Ubuntu
    { name: '@electron-forge/maker-rpm' }       // Fedora/RedHat
  ]
}

Code Signing (macOS)

For production builds with code signing, set these environment variables:
export APPLE_ID="[email protected]"
export APPLE_PASSWORD="app-specific-password"
export APPLE_TEAM_ID="TEAM1234567"

cd apps/x/apps/main
npm run make
Code signing is only required for public distribution. Local development and testing don’t require these variables.

Generating App-Specific Password

1

Sign in to Apple ID

2

Navigate to Security

Click “Sign-In and Security” → “App-Specific Passwords”
3

Generate password

Click ”+” to create a new app-specific password for “Electron Forge”
4

Save to environment

export APPLE_PASSWORD="xxxx-xxxx-xxxx-xxxx"

Build Troubleshooting

Workspace Dependencies Not Found

Error: Cannot find module '@x/shared'
Solution: Build dependencies first
cd apps/x
npm run deps

Main Process Not Updating

Problem: Changes to main process don’t appear in the app. Solution: Main process doesn’t hot-reload. Restart the dev server:
# Stop with Ctrl+C, then:
npm run dev

Preload Script Not Loading

Problem: Preload APIs not available in renderer. Solution: Rebuild preload scripts:
cd apps/x
npm run preload
npm run dev

TypeScript Errors

Problem: Type errors in IDE but code runs. Solution: Rebuild packages to regenerate type definitions:
cd apps/x
npm run deps

Clean Build

If you encounter persistent issues, try a clean build:
cd apps/x

# Remove all build outputs
rm -rf packages/*/dist
rm -rf apps/*/dist
rm -rf apps/main/.package
rm -rf node_modules

# Reinstall and rebuild
pnpm install
npm run deps
npm run dev

Build Performance

Development

  • Cold start: ~15-30 seconds (includes npm run deps)
  • Hot reload (renderer): Instant with Vite HMR
  • Rebuild after changes: Depends on what changed
    • Renderer only: Instant (HMR)
    • Preload/shared/core: ~5-10 seconds + restart
    • Main process: ~5-10 seconds + restart

Production

  • Package (no installer): ~30-60 seconds
  • Make (with installer): ~60-120 seconds
  • Code signing: Adds ~30-60 seconds
Build times vary based on hardware and platform. Apple Silicon Macs are generally faster than Intel.

Build docs developers (and LLMs) love