mirror of
https://github.com/logicog/RTLPlayground.git
synced 2026-10-29 17:54:14 +08:00
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).
This commit is contained in:
1 parent
52692d05b1
commit
57e3fce95e
5 files changed
+156
-15
No files matched your search
+1
-1
@@ -15,7 +15,7 @@ $(BUILDDIR)/injector: injector.c
|
||||
gcc $^ $(CCFLAGS) $@
|
||||
|
||||
$(BUILDDIR)/fileadder: fileadder.c
|
||||
gcc $^ $(CCFLAGS) $@
|
||||
gcc $^ $(CCFLAGS) $@ -lz
|
||||
|
||||
$(BUILDDIR)/crc_calculator: crc_calculator.c
|
||||
gcc $^ $(CCFLAGS) $@
|
||||
|
||||
+61
-8
@@ -11,6 +11,7 @@
|
||||
#include <string.h>
|
||||
#include <argp.h>
|
||||
#include <stdbool.h>
|
||||
#include <zlib.h>
|
||||
|
||||
#define OFFSET 2
|
||||
|
||||
@@ -33,6 +34,7 @@ int fbuf_p;
|
||||
int xbuf_p;
|
||||
bool addsDir = false;
|
||||
int callNum = 0;
|
||||
struct arguments arguments;
|
||||
|
||||
const char *argp_program_version = "fileadder 0.1";
|
||||
const char *argp_program_bug_address = "<git@logicog.de>";
|
||||
@@ -45,6 +47,7 @@ static struct argp_option options[] = {
|
||||
{ "address", 'a', "SIZE", 0, "Address where data is placed, default is 0x1000000 if option is used, otherwise 0x1fd000"},
|
||||
{ "prefix", 'p', "FILE", 0, "Prefix for header and index file generation"},
|
||||
{ "bank", 'b', "BANKNAME", 0, "Generate #pragma with given bank-name"},
|
||||
{ "gzip", 'z', 0, 0, "gzip-compress the added files"},
|
||||
{ 0 }
|
||||
};
|
||||
|
||||
@@ -57,6 +60,7 @@ struct arguments {
|
||||
char *bank;
|
||||
bool overwrite;
|
||||
bool add_zero;
|
||||
bool gzip;
|
||||
};
|
||||
|
||||
|
||||
@@ -86,6 +90,9 @@ static error_t parse_opt(int key, char *arg, struct argp_state *state)
|
||||
case 'b':
|
||||
arguments->bank = arg? arg : "BANK1";
|
||||
break;
|
||||
case 'z':
|
||||
arguments->gzip = true;
|
||||
break;
|
||||
default:
|
||||
return ARGP_ERR_UNKNOWN;
|
||||
}
|
||||
@@ -118,6 +125,47 @@ int addfile(const char *name, int addr)
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* gzip-compresses the data at buffer[addr] in place (gzip format, as
|
||||
* expected by the Content-Encoding: gzip header the httpd serves it
|
||||
* with). Returns the compressed size.
|
||||
*/
|
||||
int gzipBuffer(int addr, int len)
|
||||
{
|
||||
uLong bound = compressBound(len);
|
||||
unsigned char *out = malloc(bound);
|
||||
if (!out) {
|
||||
perror("malloc");
|
||||
return 0;
|
||||
}
|
||||
|
||||
z_stream zs;
|
||||
memset(&zs, 0, sizeof(zs));
|
||||
if (deflateInit2(&zs, Z_BEST_COMPRESSION, Z_DEFLATED, 15 + 16, 8,
|
||||
Z_DEFAULT_STRATEGY) != Z_OK) {
|
||||
fprintf(stderr, "gzip: deflateInit2 failed\n");
|
||||
free(out);
|
||||
return 0;
|
||||
}
|
||||
zs.next_in = (Bytef *)&buffer[addr];
|
||||
zs.avail_in = len;
|
||||
zs.next_out = out;
|
||||
zs.avail_out = bound;
|
||||
if (deflate(&zs, Z_FINISH) != Z_STREAM_END) {
|
||||
fprintf(stderr, "gzip: deflate failed\n");
|
||||
deflateEnd(&zs);
|
||||
free(out);
|
||||
return 0;
|
||||
}
|
||||
int out_len = zs.total_out;
|
||||
deflateEnd(&zs);
|
||||
|
||||
memcpy(&buffer[addr], out, out_len);
|
||||
free(out);
|
||||
return out_len;
|
||||
}
|
||||
|
||||
|
||||
int hasSuffix(const char *str, const char *suffix)
|
||||
{
|
||||
if (!str || !suffix)
|
||||
@@ -164,9 +212,9 @@ int addidx(const char *name, int addr, int len)
|
||||
|
||||
defbuf_p += snprintf(&dbuf[defbuf_p], DEF_SIZE - defbuf_p, "#define FDATA_START_%s 0x%x\n", s, addr);
|
||||
defbuf_p += snprintf(&dbuf[defbuf_p], DEF_SIZE - defbuf_p, "#define FDATA_SIZE_%s %d\n", s, len);
|
||||
ibuf_p += snprintf(&ibuf[ibuf_p], INDEX_SIZE - ibuf_p, " {\"/%s\", FDATA_START_%s, FDATA_SIZE_%s, %s},\n", name, s, s, getMime(name));
|
||||
ibuf_p += snprintf(&ibuf[ibuf_p], INDEX_SIZE - ibuf_p, " {\"/%s\", FDATA_START_%s, FDATA_SIZE_%s, %s, %d},\n", name, s, s, getMime(name), arguments.gzip? 1 : 0);
|
||||
if (!strcmp(name, "index.html"))
|
||||
ibuf_p += snprintf(&ibuf[ibuf_p], INDEX_SIZE - ibuf_p, " {\"/\", FDATA_START_%s, FDATA_SIZE_%s, mime_HTML},\n", s, s);
|
||||
ibuf_p += snprintf(&ibuf[ibuf_p], INDEX_SIZE - ibuf_p, " {\"/\", FDATA_START_%s, FDATA_SIZE_%s, mime_HTML, %d},\n", s, s, arguments.gzip? 1 : 0);
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -216,7 +264,6 @@ int replaceCalls(int pos)
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
struct arguments arguments;
|
||||
int arg_index;
|
||||
char tmpfilename[] = "image_XXXXXX";
|
||||
struct stat s;
|
||||
@@ -227,6 +274,7 @@ int main(int argc, char **argv)
|
||||
arguments.overwrite = true;
|
||||
arguments.prefix = 0;
|
||||
arguments.bank = NULL;
|
||||
arguments.gzip = false;
|
||||
|
||||
argp_parse(&argp, argc, argv, 0, &arg_index, &arguments);
|
||||
if (!arg_index)
|
||||
@@ -269,7 +317,7 @@ int main(int argc, char **argv)
|
||||
defbuf_p += snprintf(&dbuf[defbuf_p], DEF_SIZE - defbuf_p, "#include <stdint.h>\n\n");
|
||||
defbuf_p += snprintf(&dbuf[defbuf_p], DEF_SIZE - defbuf_p,
|
||||
"typedef enum mime_type_e {\n mime_HTML = 0,\n mime_SVG,\n mime_ICO,\n mime_PNG,\n mime_JS,\n mime_CSS,\n mime_TXT\n} mime_type_t;\n\n");
|
||||
defbuf_p += snprintf(&dbuf[defbuf_p], DEF_SIZE - defbuf_p, "struct f_data {\n __code char *file;\n uint32_t start;\n uint16_t len;\n mime_type_t mime;\n};\n\n");
|
||||
defbuf_p += snprintf(&dbuf[defbuf_p], DEF_SIZE - defbuf_p, "struct f_data {\n __code char *file;\n uint32_t start;\n uint16_t len;\n mime_type_t mime;\n uint8_t gzip;\n};\n\n");
|
||||
// defbuf_p += snprintf(&dbuf[defbuf_p], DEF_SIZE - defbuf_p, "typedef uint16_t (* fcall_ptr)(void);\n\n");
|
||||
|
||||
ibuf_p += snprintf(&ibuf[ibuf_p], INDEX_SIZE - ibuf_p, "// This file is automatically generated, do not edit!\n\n");
|
||||
@@ -309,16 +357,21 @@ int main(int argc, char **argv)
|
||||
|
||||
size_t data_read = addfile(pathbuffer, addr);
|
||||
if (data_read) {
|
||||
if (arguments.add_zero) {
|
||||
buffer[addr + data_read + 1] = '\0';
|
||||
data_read++;
|
||||
}
|
||||
if (arguments.add_zero) {
|
||||
/* NUL-terminate the data so the file table and the
|
||||
* strlen()-based size computation are deterministic
|
||||
* (the buffer past the file is otherwise uninitialised) */
|
||||
buffer[addr + data_read] = '\0';
|
||||
data_read++;
|
||||
}
|
||||
printf("Data inserted from %s at 0x%x, size: %ld\n", pathbuffer, addr, data_read);
|
||||
}
|
||||
int old_len = data_read;
|
||||
data_read = replaceCalls(addr);
|
||||
if (old_len > data_read)
|
||||
memset(buffer + addr + data_read, 0, old_len - data_read);
|
||||
if (arguments.gzip)
|
||||
data_read = gzipBuffer(addr, data_read);
|
||||
addidx(in_file->d_name, addr, data_read);
|
||||
addr += data_read;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
#!/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()
|
||||
Reference in new issue
Block a user