# SCRIPT TO GZIP CRITICAL FILES FOR ACCELERATED WEBSERVING # see also https://community.platformio.org/t/question-esp32-compress-files-in-data-to-gzip-before-upload-possible-to-spiffs/6274/10 import glob import shutil import gzip import os import subprocess import platform Import("env") Import("projenv") def ensure_node_tool(package_name, binary_name=None): """Installiert das Tool lokal, wenn es fehlt – mit npm init bei Bedarf""" if binary_name is None: binary_name = package_name project_dir = env.subst('$PROJECT_DIR') tools_dir = os.path.join(project_dir, 'tools_node') local_bin = os.path.join(tools_dir, 'node_modules', '.bin', binary_name) os.makedirs(tools_dir, exist_ok=True) # Initialisiere npm, falls noch nicht geschehen if not os.path.isfile(os.path.join(tools_dir, 'package.json')): print("🛠️ Initializing local npm project in tools_node...") subprocess.run(['npm', 'init', '-y'], cwd=tools_dir, check=True) # Installiere Tool (idempotent) try: subprocess.run(['npm', 'install', package_name], cwd=tools_dir, check=True) except Exception as e: print(f"❌ Fehler beim Installieren von {package_name}: {e}") return local_bin if os.path.isfile(local_bin) else binary_name # Tools sicherstellen # Tools sicherstellen – Package-Name und CLI-Binary ggf. unterschiedlich html_minifier_path = ensure_node_tool("html-minifier") terser_path = ensure_node_tool("terser") cssnano_path = ensure_node_tool("cssnano-cli", "cssnano") def minify_html(input_path, output_path): subprocess.run([html_minifier_path, '--collapse-whitespace', '--remove-comments', input_path, '-o', output_path]) def minify_js(input_path, output_path): subprocess.run([terser_path, input_path, '-o', output_path, '-c', '-m']) def minify_css(input_path, output_path): subprocess.run([cssnano_path, '--no-discardUnused', input_path, output_path]) def process_file(src_path, dest_path): _, file_extension = os.path.splitext(src_path) os.makedirs(os.path.dirname(dest_path), exist_ok=True) if file_extension.lower() == '.js': minify_js(src_path, dest_path) elif file_extension.lower() == '.css': minify_css(src_path, dest_path) elif file_extension.lower() in ['.html', '.htm']: minify_html(src_path, dest_path) else: shutil.copy2(src_path, dest_path) def strip_files(src_dir, dest_dir): os.makedirs(dest_dir, exist_ok=True) for root, _, files in os.walk(src_dir): for filename in files: src_path = os.path.join(root, filename) rel_path = os.path.relpath(src_path, src_dir) dest_path = os.path.join(dest_dir, rel_path) process_file(src_path, dest_path) def gzip_file(src_path, dst_path): with open(src_path, 'rb') as src, gzip.open(dst_path, 'wb') as dst: for chunk in iter(lambda: src.read(4096), b""): dst.write(chunk) def getListOfFiles(dirName): entries = os.listdir(dirName) allFiles = [] for entry in entries: fullPath = os.path.join(dirName, entry) if os.path.isdir(fullPath): allFiles += getListOfFiles(fullPath) else: allFiles.append(fullPath) return allFiles def safe_relpath(path, start): return os.path.relpath(path, start).replace("\\", "/") def gzip_webfiles(source, target, env): filetypes_to_gzip = ['.css', '.png', '.js', '.ico', '.woff2', '.json'] print('\nGZIP: Starting gzip-Process for LittleFS-Image...\n') src_dir = os.path.join(env.get('PROJECT_DIR'), 'data_src') temp_dir = os.path.join(env.get('PROJECT_DIR'), 'data_stripped') dst_dir = env.get('PROJECT_DATA_DIR') strip_files(src_dir, temp_dir) if os.path.exists(dst_dir): shutil.rmtree(dst_dir) os.mkdir(dst_dir) files_to_copy = [] files_to_gzip = [] for file in getListOfFiles(temp_dir): _, ext = os.path.splitext(file) if ext in filetypes_to_gzip: files_to_gzip.append(file) else: files_to_copy.append(safe_relpath(file, temp_dir)) for file in files_to_copy: full_dst = os.path.join(dst_dir, file) os.makedirs(os.path.dirname(full_dst), exist_ok=True) shutil.copy(os.path.join(temp_dir, file), full_dst) was_error = False try: for src in files_to_gzip: rel_path = safe_relpath(src, temp_dir) dst_path = os.path.join(dst_dir, rel_path + '.gz') os.makedirs(os.path.dirname(dst_path), exist_ok=True) print('GZIP: compressing... ' + rel_path) gzip_file(src, dst_path) except IOError as e: was_error = True print('GZIP: Fehler beim Komprimieren:', e) if was_error: print('⚠️ GZIP: Nicht alle Dateien konnten verarbeitet werden.\n') else: print('✅ GZIP: Komprimierung abgeschlossen.\n') shutil.rmtree(temp_dir) def gzip_binffiles(source, target, env): littlefsbin = target[0].get_abspath() tmpbin = os.path.join(os.path.dirname(littlefsbin), 'filesystem.fs') shutil.copyfile(littlefsbin, tmpbin) gzip_file(tmpbin, tmpbin + '.gz') os.remove(tmpbin) # Hooks setzen env.AddPreAction('$BUILD_DIR/littlefs.bin', gzip_webfiles) env.AddPostAction('$BUILD_DIR/littlefs.bin', gzip_binffiles)