Files
Calendink/Provider/tdd/frontend_technology_choices.md
2026-03-03 19:41:33 -05:00

10 KiB

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 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.