Files
RTLPlayground/tools/minify.py
eraiza0816 67ae4598d9 feat(webui): turn the Web UI into a single-page application
index.html now contains all pages as sections (overview, ports, stat,
vlan, l2, mirror, lag, eee, bandwidth, system, update); the sidebar
switches between them via the URL hash (#/ports, #/vlan, ...).
login.html stays separate as the authentication gate.

A section's initialisation runs when the section is shown, its polling
intervals run only while it is visible, and re-entering a section
refetches the data; the checkbox builders (lag/vlan/mirror) and the L2
scanner were made idempotent so re-initialising cannot duplicate rows.

tools/minify.py: recognise regex literals - an escaped slash before the
closing delimiter was previously stripped as a // comment.

Measured (make MACHINE=KP_9000_6XHML_X2): 8 embedded files, 109,814 ->
97,730 -> 25,302 bytes gzip (23.0 %); the data block ends at 0x462d6
(~86 KB freed).  On a real PCB-K0402WS-V3: cold load ~450 ms, section
switches ~60-160 ms with no asset re-downloads, all sections render
without JavaScript errors.
2026-08-11 06:37:20 +09:00

101 lines
3.0 KiB
Python

#!/usr/bin/env python3
"""Safe minifier for the WebUI sources (HTML/JS).
Removes comments and line-leading whitespace without touching string or
regex 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)
prev = '\n'
# Characters after which a '/' starts a regex literal rather than a
# division: operator / delimiter / statement-start contexts.
regex_start = set('([{=,:;!&|?+-*%^~<>')
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
prev = c
elif c == '/' and (i + 1 >= n or s[i + 1] not in '/=*') and \
(prev in regex_start or prev == '\n'):
# Regex literal (e.g. /^#\// or /https?:\/\/x/g): copy through
# the closing '/', honouring escapes and character classes.
j = i + 1
in_class = False
while j < n:
ch = s[j]
if ch == '\\':
j += 2
continue
if ch == '[':
in_class = True
elif ch == ']':
in_class = False
elif ch == '/' and not in_class:
break
j += 1
out.append(s[i:j + 1])
i = j + 1
prev = '/'
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
if c not in ' \t':
prev = c
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()