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.
This commit is contained in:
eraiza0816 committed 2026-08-11 03:06:45 +09:00
1 parent 57e3fce95e
commit 53f33bd606
1 file changed
+83
+83
View File
@@ -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.