Files
RTLPlayground/doc/webui-compression.md
T
eraiza0816 53f33bd606 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.
2026-08-11 03:06:45 +09:00

3.4 KiB

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:

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.