Repository navigation
WebUIBuildPipeline
How a Raft project's optional Web UI is built, gzipped and packaged into the device's filesystem image so that
RaftWebServercan serve it from/.
Raft does not prescribe a Web UI framework — any tool that emits a dist/ folder of static assets from npm run build will work. The integration is done by scripts/GenWebUI.py, wired into the build by RaftProject.cmake.
The project's TestWebUI and most demo apps use parcel.js because it needs almost no configuration, but Vite, Webpack, esbuild or rollup are equally fine.
features.cmake systypes/<sys>/ build/
set(UI_SOURCE_PATH …) ─► WebUI/ ├─ BuildWebUI/ (parcel output, gzipped)
├─ package.json └─ FSImage/ (combined static + Web UI)
├─ src/ └─ flashed as the `fs` partition
└─ (parcel config, deps)
For each SysType the build does:
-
Validate
UI_SOURCE_PATH.RaftProject.cmakeresolves it relative to the SysType folder and fails the build with a clear message if the directory or itspackage.jsonis missing. -
Run
GenWebUI.py. The script invokesnpm install(skippable with--npminstall), thennpm run build, in the source folder. -
Pick up the build output. All
.html,.js,.cssfiles (and optionally.mapfiles with--incmap) from thedist/folder are copied intobuild/<sys>/BuildWebUI/. By default each file is gzipped on the way over so the on-device server can serveContent-Encoding: gzipdirectly. -
Merge into the filesystem image.
CopyFSAndWebUIcopies the user's static FS image (FS_IMAGE_PATH) over tobuild/<sys>/FSImage/, then overlays the gzipped Web UI assets on top. -
Build the FS partition image.
RaftGenFSImage.cmakerunslittlefs_create_partition_image(or SPIFFS) withFLASH_IN_PROJECT, soidf.py flashflashes both the firmware and the FS partition.
The relevant CMake variables a SysType's features.cmake controls are:
| Variable | Purpose | Example |
|---|---|---|
UI_SOURCE_PATH |
Path (relative to the SysType folder) to the Web UI project — the directory that contains package.json. |
"../Common/WebUI" |
FS_IMAGE_PATH |
Path to extra static files merged into the FS image (data, certs, configs). | "../Common/FSImage" |
FS_TYPE |
"littlefs" (default) or "spiffs". |
"littlefs" |
Omit UI_SOURCE_PATH entirely to skip the Web UI build and ship just the static FS image.
A typical Web UI-enabled SysType looks like:
TestWebUI/
├── CMakeLists.txt ← thin shim, includes RaftProject.cmake
├── components/ ← C++ application code
└── systypes/
├── Common/
│ ├── FSImage/ ← non-WebUI static assets (fonts, JSON, ...)
│ ├── WebUI/ ← UI_SOURCE_PATH points here
│ │ ├── package.json
│ │ ├── src/
│ │ │ ├── index.html
│ │ │ ├── index.tsx
│ │ │ └── ...
│ │ └── (parcel/vite config)
│ └── features.cmake ← shared by all variants
└── RaftI2CBox/
├── partitions.csv
├── sdkconfig.defaults
├── SysTypes.json ← runtime SysMod config
└── features.cmake ← variant-specific overrides
features.cmake in a variant typically does little more than set(SYSTYPE "Common")-style sharing; both files are concatenated by Raft's SysType machinery.
The script is small and intentionally framework-agnostic. The interesting steps:
- Empties the destination folder (
build/<sys>/BuildWebUI/) so stale assets don't survive into the FS image. -
npm install(unless--npminstallis passed). Honours your project'spackage-lock.json. -
npm run build. The script does not care which bundler runs as long as it produces a flatdist/folder. The folder name is--distFolder(defaultdist). - For every output file with extension
.html/.js/.css(and optionally.map):- If gzip is enabled (default), gzip-compress to
<filename>.gzin the destination. - Otherwise move/rename verbatim.
- If gzip is enabled (default), gzip-compress to
CMake passes options via WEB_UI_GEN_FLAGS, e.g. --incmap for source maps in development builds.
RaftWebServer mounts the FS partition and serves files from it. When a file ending in .gz exists alongside the requested name (e.g. index.html.gz for /index.html), the server returns the compressed bytes with Content-Encoding: gzip. Modern browsers handle the decompression transparently. Result: the on-device flash usage is roughly halved with no runtime CPU cost on the ESP32.
The FS partition is sized in partitions.csv — see Partitions and Flash Layout. A WebUI built from a typical React+MUI starter compresses to ~150–250 KB; allocate 0x80000 (512 KB) or more for the fs partition unless you have measured otherwise.
Any bundler that emits a flat dist/ works. The conventions are:
- A single-page app: one
index.htmlplus hashed.jsand.cssbundles. - No directory nesting in the output (or, if you must nest, accept that
GenWebUI.pyonly walks the top level — extend the script if needed). - Inline images / fonts as data URLs or convert them via
optimizeDepsso they do not need to be served as additional files.
Parcel is the lowest-configuration option (npm i -D parcel; npx parcel build src/index.html). Vite produces smaller bundles with similar effort. Webpack works but typically needs an explicit config for hashed output names. The example WebUIs in RaftI2C/TestWebUI and the Raft Web App repository use parcel.
The on-device build is gzipped and intended for production. While developing the UI:
cd systypes/Common/WebUI
npm install
npm run start # parcel dev server, hot reloadPoint the dev server at a real (or simulated) device's REST API by adding a proxy or by enabling CORS on the device's RaftWebServer (enableCORS: 1 in its config). When happy, build the firmware target normally — idf.py build will pick up the latest source files (the CMake target depends on ${UI_SOURCE_PATH}/src/*) and rebuild the bundle.
If only the Web UI changed, idf.py app-flash fs-flash reflashes just the affected partitions and is much faster than a full flash.
For headless devices, comment out set(UI_SOURCE_PATH ...) in features.cmake. The build still produces an FS image from FS_IMAGE_PATH and flashes it; the WebUI step is replaced by an empty no-op target (add_custom_target(WebUI ALL COMMENT "No WebUI defined")).
| Symptom | Cause and fix |
|---|---|
WebUI Build Error … directory does not exist |
UI_SOURCE_PATH is set but resolves to nothing under the SysType folder. Either create the directory or comment out the set(UI_SOURCE_PATH ...) line. |
package.json missing error |
The directory exists but is empty / mis-pointed. Confirm it actually contains the Web UI project root. |
npm install repeats every build |
Expected on a clean checkout. Use --npminstall (passed by setting WEB_UI_GEN_FLAGS=--npminstall in your project) to skip after the first run. |
Browser gets 404 for assets |
The asset extension is not in the include set (.html / .js / .css / optionally .map). Either rename or extend GenWebUI.py's extensions_to_include list for that file type. |
| FS partition full | The Web UI is too big for the fs partition. Increase the partition size in partitions.csv or trim the bundle. See Partitions and Flash Layout. |
- Web Server SysMod — serves the assets at runtime, including transparent gzip handling.
-
Partitions and Flash Layout — sizing the
fspartition. -
SysTypes and Top-level SysType Config — where
features.cmakeandSysTypes.jsonlive. -
scripts/GenWebUI.py— the script itself; small and easy to extend.
Getting Started
- Quick Start
- Architecture at a Glance
- Writing Your First SysMod
- Adding a Comms Channel
- Adding an I2C Device Type
- PlatformIO / Arduino
Scaffolding & Building
- Raft CLI
- SysTypes
- Top-Level SysType
- Build Process
- WebUI Build Pipeline
- File System
- Partitions & Flash
- Local Dev Libraries
- Library Developer Guide
Architecture
Built-in SysMods
- NetworkManager
- WiFi Troubleshooting
- BLEManager
- WebServer
- MQTTManager
- SerialConsole
- CommandSerial
- CommandSocket
- CommandFile
- FileManager
- LogManager
- ESPOTAUpdate
- StatePublisher
- Remote Logging
- Data Source Registration
Comms & Protocols
- Stack Overview
- Comms Channels
- ProtocolExchange
- RICREST Protocol
- Real-Time Streams
- Adding REST Endpoints
- Built-in REST Endpoints
- File Download (OKTO)
- OTA Update Flow
Devices & Buses
- DeviceManager
- Device Manager REST API
- Device Factory & Classes
- Device Type Records
- Adding an I2C Device Type
- Device Data Publishing
- Data Logger
- I2C Bus
- I2C Device Scanning
- I2C ID & Polling
- MotorControl Overview
- MotorControl Config
- MotorControl Commands
ROS 2
Helpers
Reference