# Frontend Technology Choices for ESP32-S3 Provider **Authored by Claude Opus 4.6** **Date:** 2026-03-02 --- ## 1. Goal Create a lightweight web frontend for the ESP32-S3 Provider that displays system information and provides a reboot control. The frontend must compile to static files small enough to live in ESP32 flash memory. ## 2. Chosen Stack | Technology | Version | Role | |---|---|---| | **Svelte** | 5 | UI framework (compiles away at build time) | | **Vite** | Latest | Build tool and dev server | | **TailwindCSS** | 4 | Utility-first CSS (JIT, tree-shaken at build time) | | **vite-plugin-singlefile** | Latest | Inlines all JS+CSS into one HTML file | | **gzip** | — | Compresses final output for ESP32 flash | ## 3. Why Svelte Over Other Frameworks Svelte is a **compile-time** framework. Unlike React or Vue, it does not ship a runtime to the browser. The compiler transforms `.svelte` components into highly optimized vanilla JavaScript at build time. This means: - **No virtual DOM overhead** — direct DOM manipulation in the compiled output. - **Tiny bundle size** — a simple Svelte app compiles to a few kilobytes of JS, compared to React's ~40kB runtime or Vue's ~30kB. - **Ideal for constrained devices** — the ESP32-S3 has limited flash (typically 4-16MB shared with firmware) and serves files over a potentially slow connection. Other frameworks considered: | Framework | Runtime Size | Why Not | |---|---|---| | React | ~42kB min+gzip | Ships a runtime; too heavy for ESP32 | | Vue | ~33kB min+gzip | Ships a runtime; unnecessary for a simple dashboard | | Vanilla JS | 0kB | No tooling, harder to maintain, no component model | | Svelte | **~2kB** min+gzip | ✅ Chosen — compiles away, smallest output | Vanilla JS was considered but Svelte provides a component model, reactivity, and maintainability with effectively zero runtime cost. ## 4. Why TailwindCSS Over Vanilla CSS TailwindCSS v4 uses a **Just-In-Time (JIT) compiler** that scans source files and generates only the CSS classes actually used. For a simple dashboard page, this produces **under 5kB of CSS** (before gzip). Benefits over vanilla CSS: - **Guaranteed small output** — unused styles are never generated (no manual purging needed). - **Faster development** — utility classes directly in markup, no switching between files. - **Consistent design system** — spacing, colors, typography are constrained to a scale. - **Dark mode** — built-in `dark:` variant with zero extra effort. TailwindCSS is a build-time tool only. It produces a plain `.css` file — no runtime, no JavaScript. ## 5. Why Single-File Build The `vite-plugin-singlefile` plugin inlines all compiled JavaScript and CSS directly into `index.html`. The ESP32 serves **one file** instead of multiple. Benefits for ESP32: - **One HTTP request** — avoids multiple round-trips over a slow embedded server. - **No filesystem overhead** — SPIFFS/LittleFS have per-file overhead; one file is optimal. - **No filename length issues** — LittleFS has a 32-character filename limit; Vite normally generates hashed filenames like `assets/index-a1b2c3d4.js` that can exceed this. - **Simpler backend** — the HTTP server only needs one route to serve the frontend. ## 6. Why Not ESP32-SvelteKit [ESP32-SvelteKit](https://github.com/theelims/ESP32-sveltekit) is a full-featured framework that bundles SvelteKit + TailwindCSS + DaisyUI with an ESP32 backend. It was evaluated and rejected for the following reasons: ### Incompatible Build System ESP32-SvelteKit requires **PlatformIO with the Arduino framework**. Our project uses **native ESP-IDF with CMake**. Adopting ESP32-SvelteKit would require rewriting all backend code (`connect.cpp`, `led_status.cpp`, `main.cpp`) to use Arduino APIs. This is not acceptable. ### Unnecessary Complexity ESP32-SvelteKit includes features we do not need: - MQTT client - JWT-based user authentication - WiFi provisioning UI (we already have our own wifi/ethernet logic in `connect.cpp`) - DaisyUI theming system - PsychicHttp web server - ArduinoJson These add weight to both the firmware and the frontend bundle without benefit for our use case (a simple system info dashboard). ### Licensing Concern The ESP32-SvelteKit backend code is licensed under **LGPL v3**, which has copyleft implications for linked code. Our custom setup has no such constraints. ### What We Borrow From It Although we don't use the framework, we adopt the same **proven patterns**: - Svelte + TailwindCSS + Vite as the frontend toolchain - Gzipped static files served from ESP32 flash - The concept of embedding frontend into firmware binary for OTA (future task) ## 7. Build Pipeline ``` Source Files Build Steps Output ───────────── ─────────── ────── src/App.svelte ─┐ src/app.css ├─→ Vite + Svelte Compiler ─→ dist/index.html (47kB) src/lib/api.js ─┘ + TailwindCSS JIT │ + vite-plugin-singlefile ▼ gzip compression (level 9) │ ▼ dist/index.html.gz (16kB) │ ▼ ESP32 Flash (SPIFFS/LittleFS) or embedded in firmware binary ``` ## 8. Environment-Based API URL The frontend calls the ESP32's REST API. The base URL depends on the environment: | Environment | `VITE_API_BASE` | Reason | |---|---|---| | **Development** (PC) | `http://` | Frontend on PC, API on ESP32 over network | | **Production** (ESP32) | *(empty string)* | Frontend and API on same device, relative URLs | This is handled via Vite's `.env.development` and `.env.production` files. The value is baked in at compile time — zero runtime overhead. ## 9. OTA Considerations (Future) When OTA updates are implemented, the frontend will be embedded into the firmware binary as a C header array: 1. `npm run build:esp32` → `dist/index.html.gz` (~16kB) 2. A script converts the gzipped file to a C `const uint8_t[]` array 3. The array is compiled into the firmware binary 4. OTA flashes one binary that includes both firmware and frontend This avoids needing a separate SPIFFS partition for the frontend and ensures the UI always matches the firmware version. ## 10. Summary We chose **Svelte + TailwindCSS + Vite** because they are build-time tools that produce the smallest possible static output, ideal for ESP32 constraints. We build to a **single gzipped HTML file** of ~16kB. We did not adopt ESP32-SvelteKit because it requires PlatformIO/Arduino, which is incompatible with our native ESP-IDF project, and includes unnecessary complexity for our simple dashboard. --- ## 11. Implementation Results *Added 2026-03-02 after implementation was completed.* ### Final Build Output | File | Size | Notes | |---|---|---| | `dist/index.html` | **47.0 kB** | Single self-contained file (all JS + CSS inlined) | | `dist/index.html.gz` | **16.1 kB** | Gzipped at max level 9 (65% reduction) | ### Size Breakdown (before inlining) | Asset | Uncompressed | Gzipped | |---|---|---| | HTML shell | 0.4 kB | 0.3 kB | | CSS (TailwindCSS JIT) | 12.4 kB | 3.2 kB | | JS (Svelte compiled) | 34.9 kB | 13.5 kB | | **Total** | **47.7 kB** | **17.0 kB** | The singlefile plugin reduces total size by ~0.8kB (removes multi-file boilerplate). Its main value is operational (one file, one HTTP request, no filesystem overhead). ### File Structure ``` Provider/frontend/ ├── .env.development # VITE_API_BASE=http://ESP32_IP_HERE ├── .env.production # VITE_API_BASE= (empty, same-origin) ├── index.html # Vite entry point ├── package.json # npm scripts: dev, build, build:esp32 ├── vite.config.js # Svelte + TailwindCSS + singlefile plugins ├── scripts/ │ └── gzip.js # Node.js gzip script (built-in zlib, no deps) └── src/ ├── main.js # Svelte mount point ├── app.css # TailwindCSS v4 imports + @theme tokens ├── App.svelte # Dashboard UI (system info, reboot button) └── lib/ └── api.js # API helpers (getSystemInfo, reboot) ``` ### Dashboard Features - System info display: chip model, free heap, uptime, firmware version, connection type - Reboot button with confirmation modal - Auto-refresh polling every 5 seconds - Four status states: loading, connected, offline, rebooting - Dark theme with custom color tokens - Fully responsive layout ### npm Scripts | Script | Command | Purpose | |---|---|---| | `npm run dev` | `vite` | Dev server with HMR (for PC development) | | `npm run build` | `vite build` | Production build (single `index.html`) | | `npm run build:esp32` | `vite build && node scripts/gzip.js` | Build + gzip for ESP32 deployment | ### Versions Used | Package | Version | |---|---| | Svelte | 5.45.2 | | Vite | 7.3.1 | | TailwindCSS | 4.2.1 | | @tailwindcss/vite | 4.2.1 | | vite-plugin-singlefile | 2.3.0 | ### Known Issues - **W: drive**: Vite requires `resolve.preserveSymlinks: true` in `vite.config.js` because `W:` is a `subst` drive mapped to `C:\Dev\...`. Without this, the build fails with `fileName` path resolution errors. - **ESP32 backend not yet implemented**: The frontend expects `GET /api/system/info` and `POST /api/system/reboot` endpoints. These need to be added to `main.cpp` using `esp_http_server`.