build_site.py (4189B)
1 #!/usr/bin/env python3 2 # Copyright (c) the JPEG XL Project Authors. All rights reserved. 3 # 4 # Use of this source code is governed by a BSD-style 5 # license that can be found in the LICENSE file. 6 7 import shutil 8 import subprocess 9 import sys 10 11 from pathlib import Path 12 13 BROTLIFY = False 14 ZOPFLIFY = False 15 LEAN = True 16 NETLIFY = False 17 18 REMOVE_SHEBANG = ['jxl_decoder.js'] 19 EMBED_BIN = [ 20 'jxl_decoder.js', 21 'jxl_decoder.worker.js' 22 ] 23 EMBED_SRC = ['client_worker.js'] 24 TEMPLATES = ['service_worker.js'] 25 COPY_BIN = ['jxl_decoder.wasm'] + [] if LEAN else EMBED_BIN 26 COPY_SRC = [ 27 'one_line_demo.html', 28 'one_line_demo_with_console.html', 29 'manual_decode_demo.html', 30 ] + [] if not NETLIFY else [ 31 'netlify.toml', 32 'netlify' 33 ] + [] if LEAN else EMBED_SRC 34 35 COMPRESS = COPY_BIN + COPY_SRC + TEMPLATES 36 COMPRESSIBLE_EXT = ['.html', '.js', '.wasm'] 37 38 def escape_js(js): 39 return js.replace('\\', '\\\\').replace('\'', '\\\'') 40 41 def remove_shebang(txt): 42 lines = txt.splitlines(True) # Keep line-breaks 43 if len(lines) > 0: 44 if lines[0].startswith('#!'): 45 lines = lines[1:] 46 return ''.join(lines) 47 48 def compress(path): 49 name = path.name 50 compressible = any([name.endswith(ext) for ext in COMPRESSIBLE_EXT]) 51 if not compressible: 52 print(f'Not compressing {name}') 53 return 54 print(f'Processing {name}') 55 orig_size = path.stat().st_size 56 if BROTLIFY: 57 cmd_brotli = ['brotli', '-Zfk', path.absolute()] 58 subprocess.run(cmd_brotli, check=True, stdout=sys.stdout, stderr=sys.stderr) 59 br_size = path.parent.joinpath(name + '.br').stat().st_size 60 print(f' Brotli: {orig_size} -> {br_size}') 61 if ZOPFLIFY: 62 cmd_zopfli = ['zopfli', path.absolute()] 63 subprocess.run(cmd_zopfli, check=True, stdout=sys.stdout, stderr=sys.stderr) 64 gz_size = path.parent.joinpath(name + '.gz').stat().st_size 65 print(f' Zopfli: {orig_size} -> {gz_size}') 66 67 def check_util(name): 68 cmd = [name, '-h'] 69 try: 70 subprocess.run(cmd, check=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) 71 except: 72 print(f"NOTE: {name} not installed") 73 return False 74 return True 75 76 def check_utils(): 77 global BROTLIFY 78 BROTLIFY = BROTLIFY and check_util('brotli') 79 global ZOPFLIFY 80 ZOPFLIFY = ZOPFLIFY and check_util('zopfli') 81 if not check_util('uglifyjs'): 82 print("FAIL: uglifyjs is required to build a site") 83 sys.exit() 84 85 def uglify(text, name): 86 cmd = ['uglifyjs', '-m', '-c'] 87 ugly_result = subprocess.run( 88 cmd, capture_output=True, check=True, input=text, text=True) 89 ugly_text = ugly_result.stdout.strip() 90 print(f'Uglify {name}: {len(text)} -> {len(ugly_text)}') 91 return ugly_text 92 93 if __name__ == "__main__": 94 if len(sys.argv) != 4: 95 print(f"Usage: python3 {sys.argv[0]} SRC_DIR BINARY_DIR OUTPUT_DIR") 96 exit(-1) 97 source_path = Path(sys.argv[1]) # CMake build dir 98 binary_path = Path(sys.argv[2]) # Site template dir 99 output_path = Path(sys.argv[3]) # Site output 100 101 check_utils() 102 103 for name in REMOVE_SHEBANG: 104 path = binary_path.joinpath(name) 105 text = path.read_text().strip() 106 path.write_text(remove_shebang(text)) 107 remove_shebang 108 109 substitutes = {} 110 111 for name in EMBED_BIN: 112 key = '$' + name + '$' 113 path = binary_path.joinpath(name) 114 value = escape_js(uglify(path.read_text().strip(), name)) 115 substitutes[key] = value 116 117 for name in EMBED_SRC: 118 key = '$' + name + '$' 119 path = source_path.joinpath(name) 120 value = escape_js(uglify(path.read_text().strip(), name)) 121 substitutes[key] = value 122 123 for name in TEMPLATES: 124 print(f'Processing template {name}') 125 path = source_path.joinpath(name) 126 text = path.read_text().strip() 127 for key, value in substitutes.items(): 128 text = text.replace(key, value) 129 #text = uglify(text, name) 130 output_path.joinpath(name).write_text(text) 131 132 for name in COPY_SRC: 133 path = source_path.joinpath(name) 134 if path.is_dir(): 135 shutil.copytree(path, output_path.joinpath( 136 name).absolute(), dirs_exist_ok=True) 137 else: 138 shutil.copy(path, output_path.absolute()) 139 140 # TODO(eustas): uglify 141 for name in COPY_BIN: 142 shutil.copy(binary_path.joinpath(name), output_path.absolute()) 143 144 for name in COMPRESS: 145 compress(output_path.joinpath(name))