Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
- Idle power saving now puts the device into automatic light sleep between loop ticks instead of only lowering the CPU clock, cutting idle draw while leaving buttons, touch, and the frontlight working exactly as before. The screen, Wi-Fi transfers, and USB sessions stay awake while they are in use.
- GitHub workflows and release documentation links now follow the `development` default branch.
- The built-in Bitter and Lexend Deca TTF fonts carry only the ligature and kerning data the reader uses, freeing about 290 KB of flash on ESP32-S3 builds. Text renders the same.
- The web portal pages ship minified JavaScript and CSS, freeing about 22 KB of flash and loading the Files page faster over Wi-Fi. Builds need Node (npx) for this; without it the pages are served unminified as before.
- Idle power saving now engages after 250 ms instead of 3 seconds, and battery level is polled every 6 seconds instead of every 1.5, trading slightly less frequent battery updates for lower average power draw.
- The Library opens instantly when nothing on the SD card has changed since its last scan, instead of rescanning the whole card on every visit. Moving the selection no longer re-reads each visible book from the card.
- After a restart, Home starts indexing the Library in the background as soon as it appears, so the first Library visit usually opens without the "Reading your books" wait. Indexing pauses the moment you press a button or touch the screen and picks up where it left off.
Expand Down
40 changes: 37 additions & 3 deletions scripts/build_web.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,16 @@
web/assets/logo.png - drop logo, served once at /logo.png

Outputs land in src/network/html/ with the identifiers the C++ already uses.
No third-party dependencies (runs inside the PlatformIO pre-build step)."""
Page JS and CSS are minified with esbuild (a pinned version via npx, or one on
PATH) when Node is available; without it the build still works and ships the
sources unminified, which costs about 23 KB of flash."""
import os
import re
import gzip
import hashlib
import shutil
import subprocess
import tempfile

try:
ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
Expand Down Expand Up @@ -51,6 +56,30 @@ def minify_css(css):
css = re.sub(r"/\*.*?\*/", "", css, flags=re.DOTALL)
return re.sub(r"\s+", " ", css).strip()

ESBUILD_VERSION = "0.24.2"

def esbuild_commands():
npx = shutil.which("npx")
if npx:
yield [npx, "--yes", f"esbuild@{ESBUILD_VERSION}"]
exe = shutil.which("esbuild")
if exe:
yield [exe]

def minify_page_assets(paths):
"""Minify page JS/CSS in one esbuild run. Returns {path: text}, or {} when
esbuild is unavailable so callers fall back to the unminified sources."""
with tempfile.TemporaryDirectory() as out:
for cmd in esbuild_commands():
try:
subprocess.run(cmd + ["--minify", f"--outdir={out}", "--log-level=error", *paths],
check=True, capture_output=True, timeout=180)
except (OSError, subprocess.SubprocessError):
continue
return {p: read(out, os.path.basename(p)) for p in paths}
print("build_web: esbuild not available (needs Node/npx); page JS/CSS left unminified")
return {}

def render(template, values):
# Single pass so substituted content is never re-scanned for placeholders.
return re.sub(r"{{\s*(\w+)\s*}}", lambda m: values[m.group(1)], template)
Expand Down Expand Up @@ -91,10 +120,15 @@ def emit_gzip(path, ident, text):

base = read(WEB, "templates", "base.html")

page_assets = [os.path.join(WEB, "pages", f"{slug}.{ext}") for slug in PAGES for ext in ("css", "js")]
minified = minify_page_assets([p for p in page_assets if os.path.getsize(p)])

for slug, (ident, title, active, head_extra) in PAGES.items():
page_css = read(WEB, "pages", f"{slug}.css")
css_path = os.path.join(WEB, "pages", f"{slug}.css")
js_path = os.path.join(WEB, "pages", f"{slug}.js")
page_css = minified.get(css_path) or read(css_path)
page_html = read(WEB, "pages", f"{slug}.html")
page_js = read(WEB, "pages", f"{slug}.js").strip()
page_js = (minified.get(js_path) or read(js_path)).strip()
script = f"<script>\n{page_js}\n</script>" if page_js else ""
values = {
"title": title, "v": v, "head_extra": head_extra,
Expand Down
Loading