# How the Vite Build Configuration Works for Production in Thunderbolt

> Explore the Vite build configuration for Thunderbolt production. Discover how it optimizes assets, externalizes modules, and enables features using a single vite.config.ts file.

- Repository: [Thunderbird/thunderbolt](https://github.com/thunderbird/thunderbolt)
- Tags: internals
- Published: 2026-04-19

---

**Thunderbolt uses a single [`vite.config.ts`](https://github.com/thunderbird/thunderbolt/blob/main/vite.config.ts) file that configures both development and production builds, where the `build` section activates only during `vite build`, generating optimized static assets in `dist/` while externalizing native modules and conditionally enabling features via environment variables.**

Thunderbolt is an open-source email client built by the Thunderbird team, leveraging modern web technologies for its user interface. Understanding the **Vite build configuration** is essential for contributors packaging the application for production or optimizing bundle sizes. The configuration is centralized in [`vite.config.ts`](https://github.com/thunderbird/thunderbolt/blob/main/vite.config.ts) and handles everything from dependency deduplication to conditional asset copying.

## Core Production Settings in vite.config.ts

The production behavior is governed by the `build` property in the configuration object, which Vite applies only when running `vite build` or `bun run build`.

### Source Map Management

Source maps are controlled by the `ENABLE_SOURCEMAP` environment variable. In [`vite.config.ts`](https://github.com/thunderbird/thunderbolt/blob/main/vite.config.ts) lines 22-24, the configuration checks this variable to set the `sourcemap` constant to either `'hidden'` or `false`. When enabled, **hidden source maps** allow error tracking services like PostHog to map minified code back to source without exposing mappings publicly. This value is passed to `build.sourcemap` on lines 28-29.

### Handling Native Dependencies

Native modules that cannot be bundled must be externalized. The configuration uses `rollupOptions.external` on lines 30-32 to mark `bun:sqlite` as external, preventing Rollup from attempting to bundle the native SQLite driver into the JavaScript bundle.

### Path Aliases and Deduplication

To minimize bundle size and resolve imports consistently, the configuration defines several aliases in the `resolve.alias` array on lines 74-81. These map `@` to `src`, `@shared` to `shared`, and provide an internal PowerSync alias required by the custom SharedWorker. Additionally, `resolve.dedupe` on lines 74-75 ensures only single instances of `react` and `@powersync/*` packages exist in the final bundle, preventing duplicate React copies that could cause hook errors.

## Build Plugins and Asset Pipeline

The `plugins` array on lines 34-51 configures the transformation pipeline, with several plugins specifically impacting production output.

### PowerSync Asset Copying

Before bundling begins, the `copy-powersync-assets` plugin executes `powersync-web copy-assets --output public`. This ensures PowerSync's WebAssembly and required static files are present in the `public` directory, which Vite copies verbatim to `dist/` during the build.

### Conditional Bundle Analysis

Bundle analysis is gated by the `ANALYZE` environment variable. When set to `true`, the configuration spreads a `vite-bundle-analyzer` plugin into the plugins array on lines 44-50, generating a static report of bundle composition and sizes. This is typically invoked via `ANALYZE=true bun run build`.

### Dependency Optimization

During development, Vite pre-bundles dependencies to improve cold-start performance. However, for production, the `optimizeDeps.exclude` array on lines 18-20 prevents pre-bundling of heavy native modules like `@journeyapps/wa-sqlite` and `@powersync/web`, ensuring they are handled correctly by the build pipeline rather than optimized away.

## Development vs. Production Mode

The configuration distinguishes between environments primarily through Vite's internal mode detection. The `server` block—which configures port 1420, HMR behavior, and file-system whitelisting—is only active during `vite dev` or when running the Tauri desktop workflow. When executing `vite build`, Vite ignores the `server` configuration entirely and outputs optimized static assets to the `dist/` directory, ready for deployment via any HTTP server or integration into the Tauri binary.

## Running Production Builds

Execute the following commands from the repository root to generate production bundles:

Standard production build:

```bash
bun run build

```

Build with bundle analysis:

```bash
ANALYZE=true bun run build

```

Build with source maps for error tracking:

```bash
ENABLE_SOURCEMAP=true bun run build

```

Verify PowerSync assets were copied:

```bash
ls public | grep powersync

```

## Summary

- Thunderbolt uses a unified [`vite.config.ts`](https://github.com/thunderbird/thunderbolt/blob/main/vite.config.ts) for both development and production, with the `build` section activating only during `vite build`.
- **Source maps** are controlled via `ENABLE_SOURCEMAP`, defaulting to disabled for privacy but supporting hidden maps for CI error tracking.
- Native modules like `bun:sqlite` are **externalized** via `rollupOptions.external` to prevent bundling errors.
- The **PowerSync asset** plugin ensures WebAssembly files are copied to `public/` before bundling.
- **Bundle analysis** can be triggered with `ANALYZE=true` to audit bundle composition.
- The configuration outputs static files to `dist/`, suitable for web deployment or Tauri packaging.

## Frequently Asked Questions

### How do I enable source maps for production builds in Thunderbolt?

Set the `ENABLE_SOURCEMAP` environment variable to `true` before running the build. This configures Vite to generate hidden source maps according to the logic in [`vite.config.ts`](https://github.com/thunderbird/thunderbolt/blob/main/vite.config.ts) lines 22-29, allowing error tracking services to map stack traces without exposing source maps publicly.

### Why is bun:sqlite marked as external in the Vite configuration?

`bun:sqlite` is a native module that provides SQLite functionality through Bun's native bindings. Marking it as external in `rollupOptions.external` (lines 30-32 of [`vite.config.ts`](https://github.com/thunderbird/thunderbolt/blob/main/vite.config.ts)) prevents Rollup from attempting to bundle this native code into the JavaScript bundle, which would cause build failures.

### What triggers the bundle analyzer in Thunderbolt's build process?

The `vite-bundle-analyzer` plugin is conditionally injected when the `ANALYZE` environment variable equals `true`. This logic appears in lines 44-50 of [`vite.config.ts`](https://github.com/thunderbird/thunderbolt/blob/main/vite.config.ts), allowing developers to generate a static bundle report by running `ANALYZE=true bun run build`.

### How does Thunderbolt handle PowerSync assets during production builds?

Before Vite bundles the application, the `copy-powersync-assets` plugin executes a CLI command to copy PowerSync's WebAssembly and static files into the `public` directory. Since Vite copies `public/` contents verbatim to `dist/`, these assets are available in the production bundle without being processed by the bundler.