# How the GeoLibre Build Pipeline Produces Web, Desktop, and Jupyter Embed Outputs

> Discover how the GeoLibre build pipeline creates web, desktop, and Jupyter outputs using Vite and npm scripts. Learn to control asset bundling and more with environment variables.

- Repository: [Open Geospatial Solutions/GeoLibre](https://github.com/opengeos/GeoLibre)
- Tags: how-to-guide
- Published: 2026-08-05

---

**GeoLibre's build pipeline uses a unified Vite configuration orchestrated by npm scripts to generate three distinct distribution targets—web-desktop static sites, native Tauri installers, and Jupyter embed widgets—by toggling environment variables like `GEOLIBRE_EMBED` and `TAURI_ENV_PLATFORM` to control asset bundling, service workers, and base paths.**

The opengeos/GeoLibre repository leverages a single React codebase to power multiple deployment environments. Through environment-driven conditional logic in [`apps/geolibre-desktop/vite.config.ts`](https://github.com/opengeos/GeoLibre/blob/main/apps/geolibre-desktop/vite.config.ts), the GeoLibre build pipeline efficiently packages the application for browser-based web apps, native desktop operating systems, and Python JupyterLab integrations without maintaining separate codebases.

## Web-Desktop Build Flow

The web-desktop target produces a static Progressive Web App (PWA) optimized for browser deployment. This build process emphasizes CDN asset loading and service worker caching to minimize initial bundle size.

### Build Script and Configuration

Execute the build using the workspace-specific npm script defined in [`package.json`](https://github.com/opengeos/GeoLibre/blob/main/package.json):

```bash
npm run build -w geolibre-desktop

```

This invokes Vite with the configuration from [`apps/geolibre-desktop/vite.config.ts`](https://github.com/opengeos/GeoLibre/blob/main/apps/geolibre-desktop/vite.config.ts)【/cache/repos/github.com/opengeos/GeoLibre/main/apps/geolibre-desktop/vite.config.ts#L46-L52】. By default, the pipeline sets `APP_BASE` to root (`/`) and enables `PGLITE_CDN` (unless `GEOLIBRE_PGLITE_CDN` is explicitly set to `"0"`), which fetches the ~25MB PostGIS engine from jsDelivr at runtime rather than bundling it.

### Chunking and Caching Strategy

Vite applies **manual chunking** to isolate heavy libraries into lazy-loaded segments. The configuration splits DuckDB-WASM, PGlite, CereusDB, and Cesium into separate chunks to prevent blocking the initial render. The **PWA plugin** (lines 558-588) generates a service worker that precaches the application shell while runtime-caching these heavy chunks【/cache/repos/github.com/opengeos/GeoLibre/main/apps/geolibre-desktop/vite.config.ts#L558-L588】.

The final static assets are emitted to `apps/geolibre-desktop/dist/`, ready for deployment to any static web host.

## Native Desktop (Tauri) Build Flow

For native desktop distributions, GeoLibre wraps the web application using Tauri, producing platform-specific installers while disabling browser-specific features like service workers.

### Build Orchestration

The entry point is `npm run tauri:build`, which executes `scripts/tauri-build.mjs`【/cache/repos/github.com/opengeos/GeoLibre/main/scripts/tauri-build.mjs#L1-L10】. This script accepts optional flags:

- `--native-duckdb`: Bundles DuckDB native libraries instead of WASM
- `--mas`: Targets the Mac App Store, setting `GEOLIBRE_MAS_BUILD=1`

### Environment Detection and Output

Tauri's **beforeBuildCommand** triggers `npm run build` with `TAURI_ENV_PLATFORM` automatically set. Inside [`vite.config.ts`](https://github.com/opengeos/GeoLibre/blob/main/vite.config.ts), this environment variable sets `IS_TAURI_BUILD` to true, which:
- Disables the service worker (irrelevant in native contexts)
- Adjusts asset base paths for the Tauri bundle structure

The compiled binaries are output to `apps/geolibre-desktop/src-tauri/target/[platform]/bundle/`, containing `.exe`, `.dmg`, `.app`, or `.msix` installers depending on the host platform.

## Jupyter-Embed Build Flow

The Jupyter embed target packages GeoLibre as a static web asset inside a Python wheel, enabling use as an anywidget within JupyterLab or JupyterLite environments.

### Embed-Specific Configuration

Running `npm run build:embed` invokes `scripts/build-embed.mjs`【/cache/repos/github.com/opengeos/GeoLibre/main/scripts/build-embed.mjs#L1-L16】, which configures the build with three critical environment variables:

- **`GEOLIBRE_APP_BASE=./`**: Forces relative asset URLs, allowing the app to function when served from within a Python package directory structure
- **`GEOLIBRE_EMBED=1`**: Disables service worker registration (meaningless inside Jupyter iframe contexts)
- **`GEOLIBRE_PGLite_CDN=1`**: Maintains CDN loading for heavy assets to keep the Python wheel size minimal

### Staging into the Python Package

After Vite emits the bundle to `apps/geolibre-desktop/dist-embed`, the script stages these files into `python/src/geolibre/static/app/`【/cache/repos/github.com/opengeos/GeoLibre/main/scripts/build-embed.mjs#L24-L28】. This directory is subsequently packaged into the wheel via [`hatch_build.py`](https://github.com/opengeos/GeoLibre/blob/main/hatch_build.py), exposing the widget through the standard Python anywidget protocol.

## JupyterLite Site Generation

For environments without a backing Jupyter server (specifically web-desktop builds and Mac App Store distributions), GeoLibre bundles a self-hosted JupyterLite instance.

### Conditional Build Logic

The script `scripts/build-jupyterlite.mjs`【/cache/repos/github.com/opengeos/GeoLibre/main/scripts/build-jupyterlite.mjs#L1-L16】 executes `npm run build:jupyterlite`. This process checks for `TAURI_ENV_PLATFORM` and skips generation for standard Tauri builds, as those spawn a real JupyterLab server via sidecar. However, for **Mac App Store builds** (`GEOLIBRE_MAS_BUILD=1`), the sandbox restrictions prevent spawning external servers, making the JupyterLite site mandatory—the build aborts if the site is missing.

The generated site is placed in `apps/geolibre-desktop/public/jupyterlite/` (git-ignored), which Vite copies into `dist/` during the build phase, making it available to the Notebook panel iframe at runtime.

## Essential Build Commands

Reference these commands when working with the GeoLibre build pipeline:

**Web-Desktop (Static Site):**

```bash
npm run build -w geolibre-desktop

```

**Native Desktop Installer:**

```bash

# Build for current platform

npm run tauri:build

# Build specific bundle types only

npm run tauri:build -- --bundles deb

```

**Jupyter Embed Widget:**

```bash
npm run build:embed
ls python/src/geolibre/static/app/

```

**JupyterLite Site (Required for web/embed/MAS):**

```bash
pip install -r apps/geolibre-desktop/jupyterlite/requirements.txt
npm run build:jupyterlite

```

**Development Servers:**

```bash

# Web development

npm run dev

# Desktop development with hot reload

npm run tauri:dev

```

## Summary

- **Unified Configuration**: All targets share [`apps/geolibre-desktop/vite.config.ts`](https://github.com/opengeos/GeoLibre/blob/main/apps/geolibre-desktop/vite.config.ts), using environment variables (`GEOLIBRE_*`, `TAURI_ENV_*`) to toggle behavior rather than maintaining separate configs.
- **Asset Optimization**: Heavy WASM engines (PGlite, DuckDB) are either CDN-loaded via `GEOLIBRE_PGLITE_CDN` or manually chunked to keep bundle sizes manageable across web, desktop, and Python wheel distributions.
- **Service Worker Logic**: The PWA service worker is automatically disabled for Tauri (`IS_TAURI_BUILD`) and embed (`IS_EMBED`) contexts where it provides no benefit.
- **Path Adaptation**: The Jupyter embed build uses `GEOLIBRE_APP_BASE=./` to generate relative URLs compatible with Python package static file serving.
- **Platform Constraints**: Mac App Store builds require the JupyterLite site (`GEOLIBRE_MAS_BUILD=1`) due to sandboxing restrictions that prevent spawning external Jupyter servers.

## Frequently Asked Questions

### How does the GeoLibre build pipeline handle large WASM assets differently across targets?

The pipeline uses the `GEOLIBRE_PGLITE_CDN` environment variable to control asset loading. When set to `1` (default for web and embed builds), the ~25MB PostGIS engine loads from jsDelivr at runtime. For offline native desktop builds, you can bundle these assets directly by setting `GEOLIBRE_PGLITE_CDN=0`, though this increases installer size significantly.

### Why are service workers disabled in the Tauri and Jupyter embed builds?

Service workers are disabled when `IS_TAURI_BUILD` or `IS_EMBED` evaluates to true in [`vite.config.ts`](https://github.com/opengeos/GeoLibre/blob/main/vite.config.ts). In native desktop applications, service workers provide no caching advantage over Tauri's built-in asset handling, while in Jupyter embed contexts running inside iframes, service workers can cause security context errors and are therefore explicitly excluded.

### What distinguishes the Mac App Store build from standard desktop installers?

Mac App Store (MAS) builds set `GEOLIBRE_MAS_BUILD=1`, which disables the Python sidecar process due to sandboxing restrictions. Consequently, MAS builds must include the JupyterLite static site (`npm run build:jupyterlite`) to provide notebook functionality, whereas standard Tauri builds spawn a real JupyterLab server and skip the JupyterLite generation step.

### Can I customize the manual chunking strategy for specific deployment needs?

Yes, the `manualChunks` configuration in [`apps/geolibre-desktop/vite.config.ts`](https://github.com/opengeos/GeoLibre/blob/main/apps/geolibre-desktop/vite.config.ts) defines how heavy libraries like Cesium, DuckDB-WASM, and CereusDB are split. You can modify this object to create additional chunks or combine libraries based on your specific loading requirements, though the default configuration optimizes for parallel loading of geospatial engines.