Files
RTLPlayground/tools/minify.py
T
eraiza0816 57e3fce95e feat(webui): minify and gzip-compress the embedded web assets
Two build-time steps shrink the embedded Web UI (html/) from 113,015 to
34,784 bytes (30.8 %):

- tools/minify.py (new): string-literal-safe minifier that strips
  comments and indentation; comments are pure overhead on the wire, so
  the transfer size drops already before gzip (113,015 -> 101,498).
  Binary files (.ico) are copied through unchanged.  The output goes to
  output/html_min/, the new fileadder input.
- fileadder -z: gzip-compresses every added file (zlib gzip format,
  Z_BEST_COMPRESSION); the generated f_data entries carry a gzip flag.
  tools/Makefile links the tool against -lz.
- httpd: adds "Content-Encoding: gzip" for flagged entries; the 8051
  only copies compressed bytes, decompression happens in the browser.
  The JSON API stays uncompressed.

Fixes required to make the sizes deterministic:
- fileadder terminated files at data_read + 1, so the strlen()-based
  size computation depended on uninitialised buffer content; the NUL
  now lands directly after the content.
- version.h was .PHONY, so every build rewrote it while parallel builds
  could read a half-written copy.
- the file list was unscoped and missed .css/.ico; it is now scoped to
  html/ and covers all embedded types.

Measured with make MACHINE=KP_9000_6XHML_X2: data block end moves from
0x5b977 to 0x487e0 (~78 KB freed on a 512 KB image).  Verified with
MACHINE=PCB_K0402WS_V3 and on real hardware (PCB-K0402WS-V3).
2026-08-11 03:04:19 +09:00

73 lines
2.0 KiB
Python

#!/usr/bin/env python3
"""Safe minifier for the WebUI sources (HTML/JS).
Removes comments and line-leading whitespace without touching string
literals, so the output is functionally identical to the input. Used
by the firmware Makefile before the files are embedded in the flash;
the raw sources in html/ stay untouched for development.
Binary files (.ico) are copied through unchanged.
Usage: minify.py <src> <dst>
"""
import re
import shutil
import sys
def js_min(s):
out = []
i = 0
n = len(s)
while i < n:
c = s[i]
if c == '"' or c == "'":
j = i + 1
while j < n and s[j] != c:
if s[j] == '\\':
j += 1
j += 1
out.append(s[i:j + 1])
i = j + 1
elif c == '/' and i + 1 < n and s[i + 1] == '/':
j = s.find('\n', i)
i = j if j >= 0 else n
elif c == '/' and i + 1 < n and s[i + 1] == '*':
j = s.find('*/', i + 2)
i = j + 2 if j >= 0 else n
else:
out.append(c)
i += 1
lines = [l.strip() for l in ''.join(out).split('\n')]
return '\n'.join(l for l in lines if l) + '\n'
def html_min(s):
s = re.sub(r'<!--.*?-->', '', s, flags=re.S)
lines = [l.strip() for l in s.split('\n')]
return '\n'.join(l for l in lines if l) + '\n'
def main():
if len(sys.argv) != 3:
print('usage: minify.py <src> <dst>', file=sys.stderr)
sys.exit(1)
src, dst = sys.argv[1], sys.argv[2]
if src.endswith('.ico'):
shutil.copyfile(src, dst)
print('%s: copied %d bytes' % (src.split('/')[-1], len(open(src, 'rb').read())))
return
with open(src, 'r', encoding='utf-8', errors='replace') as f:
s = f.read()
if src.endswith('.js'):
out = js_min(s)
else:
out = html_min(s)
with open(dst, 'w', encoding='utf-8') as f:
f.write(out)
print('%s: %d -> %d bytes' % (src.split('/')[-1], len(s), len(out)))
if __name__ == '__main__':
main()