From 53f33bd6060f8855231f7335bbd5a60c4bfb69d3 Mon Sep 17 00:00:00 2001 From: eraiza0816 <10572974+eraiza0816@users.noreply.github.com> Date: Tue, 11 Aug 2026 03:06:45 +0900 Subject: [PATCH] docs(webui): describe the minify + gzip asset compression Pipeline, measured per-stage sizes (113,015 -> 101,498 -> 34,784 bytes) and how to verify a build against the embedded image. --- doc/webui-compression.md | 83 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 83 insertions(+) create mode 100644 doc/webui-compression.md diff --git a/doc/webui-compression.md b/doc/webui-compression.md new file mode 100644 index 00000000..fea2cd19 --- /dev/null +++ b/doc/webui-compression.md @@ -0,0 +1,83 @@ +# Compressing the Web UI Assets + +The Web UI (all files under `html/`) is embedded into the flash image and +served over HTTP. Two build-time steps keep it small: a safe minifier and +gzip compression of the embedded files. + +## How it works + +``` +html/ --(minify.py)--> output/html_min/ --(fileadder -z)--> flash image +``` + +- `tools/minify.py` removes comments (``, `//`, `/* */`) and + line-leading whitespace without touching string literals, so the output + is functionally identical to the input. Binary files (`.ico`) are + copied through unchanged. The raw sources in `html/` stay untouched + for development; the minified copy in `output/` is a pure build + artifact. Comments and indentation are pure overhead for the served + bytes, so minification alone already reduces the transfer size from + 113,015 to 101,498 bytes, independent of gzip. +- `fileadder -z` gzip-compresses every file (zlib, gzip format, + `Z_BEST_COMPRESSION`) when it generates the file table + (`html_data.c`/`html_data.h`) and when it embeds the files into the + final image. Both invocations run on `output/html_min/`, so the table + and the image always agree. +- The httpd serves each file with `Content-Encoding: gzip` according to + the `gzip` flag in its `f_data` entry. The 8051 only copies compressed + bytes from flash to the socket; decompression happens in the browser. +- Only the static files are compressed. The JSON API responses + (`/information.json`, `/vlanlist`, ...) are generated on the fly and + stay uncompressed. + +## Measured effect + +Build on any machine (the Web UI is machine-independent): + +| | bytes | +|---|---:| +| raw sources | 113,015 | +| minified (comments and indentation removed) | 101,498 | +| gzip (flash + wire) | 34,784 (30.8 %) | + +The minified assets are also what the browser receives when gzip support +is missing or disabled, so the transfer size drops in both stages: +113,015 → 101,498 bytes from minification, then → 34,784 bytes from gzip. + +The embedded data block ends at `0x5b977` without compression and at +`0x487e0` with it, freeing ~78 KB of the 512 KB image. + +## Notes + +- The files stay well below the `uint16_t` size limit of the file table + (largest gzip output: 5.4 KB for `i18n.js`). +- `fileadder` terminates the embedded files with a NUL directly after the + content (previously at `data_read + 1`), so the `strlen()`-based size + computation no longer depends on uninitialised buffer content. +- `version.h` is no longer `.PHONY`; every build previously rewrote it + while a parallel build (`make -j`) could read a half-written copy. + +## Verification + +Decompress what the firmware embeds and compare it with the minified +sources: + +```python +import gzip, re +h = open('html_data.h').read(); c = open('html_data.c').read() +starts = dict((k, int(v, 16)) for k, v in + re.findall(r'#define FDATA_START_(\w+) 0x([0-9a-f]+)', h)) +sizes = dict((k, int(v)) for k, v in + re.findall(r'#define FDATA_SIZE_(\w+) (\d+)', h)) +entries = [(p, sizes[z], starts[s]) for p, s, z in + re.findall(r'\{"/([^"]+)", FDATA_START_(\w+), ' + r'FDATA_SIZE_(\w+), \w+, 1\}', c)] +d = open('output/rtlplayground.bin', 'rb').read() +for name, size, start in entries: + assert gzip.decompress(d[start:start+size]) == \ + open('output/html_min/' + name, 'rb').read() +``` + +`tools/httpd_sim` serves the raw files from `html/` and does not exercise +the gzip path; a browser session against a flashed switch (or `curl +--compressed`) is the final check.