212 lines
10 KiB
Markdown
212 lines
10 KiB
Markdown
# 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://<ESP32_IP>` | 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 & Versioning Implementation
|
|
|
|
Instead of embedding the UI directly into the firmware binary as originally considered, we implemented a **Standalone Partition OTA** for maximum flexibility:
|
|
|
|
1. **A/B Partitioning**: The frontend is staged to an inactive LittleFS slot (`www_0` or `www_1`).
|
|
2. **Semantic Versioning**: `version.json` tracks `major.minor.revision`.
|
|
3. **Auto-Increment**: A custom `node scripts/package.js` script automatically increments the revision and generates a versioned binary (e.g., `www_v0.1.6.bin`).
|
|
4. **Resilient UX**: The Svelte app implements "Resilient Recovery Polling" — it enters a dedicated `isRecovering` state during reboot that ignores connection errors until the device is confirmed back online.
|
|
|
|
This decoupled approach allows for rapid frontend iteration without touching the 1M+ firmware binary.
|
|
|
|
## 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
|
|
- **Resilient Auto-Reload**: Targeted polling during reboot that handles intermediate connection failures.
|
|
- **OTA Dashboard**: Dedicated card showing version, active slot, and real-time partition statistics.
|
|
- 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
|
|
|
|
- **ESP-IDF Header Ordering**: Some C++ linting errors persist regarding unused headers (e.g., `esp_log.h`) that are actually required for macros; these are suppressed or ignored to maintain compatibility with the unity build pattern.
|