svelteesp32 1.13.1 → 1.14.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +92 -9
- package/dist/cppCode.d.ts +1 -1
- package/dist/cppCode.js +34 -5
- package/dist/cppCodeEspIdf.d.ts +1 -1
- package/dist/cppCodeEspIdf.js +20 -3
- package/dist/index.d.ts +7 -1
- package/dist/index.js +10 -4
- package/package.json +9 -9
package/README.md
CHANGED
|
@@ -14,15 +14,15 @@ This npm package provides a solution for **inserting any JS client application i
|
|
|
14
14
|
|
|
15
15
|
**Quick Comparison:**
|
|
16
16
|
|
|
17
|
-
| Feature | SvelteESP32
|
|
18
|
-
| --------------------- |
|
|
19
|
-
| **Single Binary OTA** | ✓ Everything embedded in firmware
|
|
20
|
-
| **Gzip Compression** | ✓ Automatic build-time (>15% reduction)
|
|
21
|
-
| **ETag Support** | ✓ Built-in
|
|
22
|
-
| **CI/CD Integration** | ✓ npm package, simple build step
|
|
23
|
-
| **Memory Efficiency** | Flash only (PROGMEM/const arrays)
|
|
24
|
-
| **Performance** | Direct byte array serving
|
|
25
|
-
| **Setup Complexity** | Include header, call init function
|
|
17
|
+
| Feature | SvelteESP32 | Traditional Filesystem (SPIFFS/LittleFS) |
|
|
18
|
+
| --------------------- | ------------------------------------------ | ---------------------------------------- |
|
|
19
|
+
| **Single Binary OTA** | ✓ Everything embedded in firmware | ✗ Requires separate partition upload |
|
|
20
|
+
| **Gzip Compression** | ✓ Automatic build-time (>15% reduction) | Manual or runtime compression |
|
|
21
|
+
| **ETag Support** | ✓ Built-in SHA256 ETags with 304 responses | Manual implementation required |
|
|
22
|
+
| **CI/CD Integration** | ✓ npm package, simple build step | Complex with upload_fs tools |
|
|
23
|
+
| **Memory Efficiency** | Flash only (PROGMEM/const arrays) | Flash partition + filesystem overhead |
|
|
24
|
+
| **Performance** | Direct byte array serving | Filesystem read overhead |
|
|
25
|
+
| **Setup Complexity** | Include header, call init function | Partition setup, upload tools, handlers |
|
|
26
26
|
|
|
27
27
|
**When to use:**
|
|
28
28
|
|
|
@@ -240,6 +240,21 @@ const uint8_t datagzip_assets_index_KwubEIf__js[12547] = {0x1f, 0x8b, 0x8, 0x0,
|
|
|
240
240
|
const uint8_t datagzip_assets_index_Soe6cpLA_css[5368] = {0x1f, 0x8b, 0x8, 0x0, 0x0, ...
|
|
241
241
|
const char * etag_assets_index_KwubEIf__js = "387b88e345cc56ef9091...";
|
|
242
242
|
const char * etag_assets_index_Soe6cpLA_css = "d4f23bc45ef67890ab12...";
|
|
243
|
+
|
|
244
|
+
// File manifest for runtime introspection
|
|
245
|
+
struct SVELTEESP32_FileInfo {
|
|
246
|
+
const char* path;
|
|
247
|
+
uint32_t size;
|
|
248
|
+
uint32_t gzipSize;
|
|
249
|
+
const char* etag;
|
|
250
|
+
const char* contentType;
|
|
251
|
+
};
|
|
252
|
+
const SVELTEESP32_FileInfo SVELTEESP32_FILES[] = {
|
|
253
|
+
{ "/assets/index-KwubEIf-.js", 38850, 12547, etag_assets_index_KwubEIf__js, "text/javascript" },
|
|
254
|
+
{ "/assets/index-Soe6cpLA.css", 32494, 5368, etag_assets_index_Soe6cpLA_css, "text/css" },
|
|
255
|
+
...
|
|
256
|
+
};
|
|
257
|
+
const size_t SVELTEESP32_FILE_COUNT = sizeof(SVELTEESP32_FILES) / sizeof(SVELTEESP32_FILES[0]);
|
|
243
258
|
...
|
|
244
259
|
|
|
245
260
|
void initSvelteStaticFiles(PsychicHttpServer * server) {
|
|
@@ -445,6 +460,74 @@ You can include a blocker error if a named file accidentally missing from the bu
|
|
|
445
460
|
|
|
446
461
|
You can use the following c++ directives at the project level if you want to configure the usage there: `SVELTEESP32_ENABLE_ETAG` and `SVELTEESP32_ENABLE_GZIP`. (Do not forget `--etag=compiler` or `--gzip=compiler` command line arg!)
|
|
447
462
|
|
|
463
|
+
### File Manifest
|
|
464
|
+
|
|
465
|
+
The generated header includes a file manifest struct and array that allows runtime introspection of all embedded static assets. This is useful for logging, runtime diagnostics, or building a JSON endpoint that lists all served files.
|
|
466
|
+
|
|
467
|
+
**Generated Structure:**
|
|
468
|
+
|
|
469
|
+
```c
|
|
470
|
+
// File manifest struct
|
|
471
|
+
struct SVELTEESP32_FileInfo {
|
|
472
|
+
const char* path; // URL path (e.g., "/index.html")
|
|
473
|
+
uint32_t size; // Original file size in bytes
|
|
474
|
+
uint32_t gzipSize; // Compressed size (0 if not gzipped)
|
|
475
|
+
const char* etag; // ETag pointer (nullptr if etag disabled)
|
|
476
|
+
const char* contentType; // MIME type (e.g., "text/html")
|
|
477
|
+
};
|
|
478
|
+
|
|
479
|
+
// File manifest array
|
|
480
|
+
const SVELTEESP32_FileInfo SVELTEESP32_FILES[] = { ... };
|
|
481
|
+
const size_t SVELTEESP32_FILE_COUNT = sizeof(SVELTEESP32_FILES) / sizeof(SVELTEESP32_FILES[0]);
|
|
482
|
+
```
|
|
483
|
+
|
|
484
|
+
**Usage Example - List All Files:**
|
|
485
|
+
|
|
486
|
+
```c
|
|
487
|
+
#include "svelteesp32.h"
|
|
488
|
+
|
|
489
|
+
void listFiles() {
|
|
490
|
+
Serial.printf("Embedded files (%d):\n", SVELTEESP32_FILE_COUNT);
|
|
491
|
+
for (size_t i = 0; i < SVELTEESP32_FILE_COUNT; i++) {
|
|
492
|
+
const auto& f = SVELTEESP32_FILES[i];
|
|
493
|
+
Serial.printf(" %s (%d bytes", f.path, f.size);
|
|
494
|
+
if (f.gzipSize > 0) {
|
|
495
|
+
Serial.printf(", gzip: %d bytes", f.gzipSize);
|
|
496
|
+
}
|
|
497
|
+
Serial.printf(", type: %s)\n", f.contentType);
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
```
|
|
501
|
+
|
|
502
|
+
**Usage Example - JSON Endpoint:**
|
|
503
|
+
|
|
504
|
+
```c
|
|
505
|
+
server->on("/api/files", HTTP_GET, [](PsychicRequest* request) {
|
|
506
|
+
String json = "[";
|
|
507
|
+
for (size_t i = 0; i < SVELTEESP32_FILE_COUNT; i++) {
|
|
508
|
+
const auto& f = SVELTEESP32_FILES[i];
|
|
509
|
+
if (i > 0) json += ",";
|
|
510
|
+
json += "{\"path\":\"" + String(f.path) + "\",";
|
|
511
|
+
json += "\"size\":" + String(f.size) + ",";
|
|
512
|
+
json += "\"gzipSize\":" + String(f.gzipSize) + ",";
|
|
513
|
+
json += "\"contentType\":\"" + String(f.contentType) + "\"}";
|
|
514
|
+
}
|
|
515
|
+
json += "]";
|
|
516
|
+
PsychicResponse response(request);
|
|
517
|
+
response.setContentType("application/json");
|
|
518
|
+
response.setContent(json.c_str(), json.length());
|
|
519
|
+
return response.send();
|
|
520
|
+
});
|
|
521
|
+
```
|
|
522
|
+
|
|
523
|
+
**Notes:**
|
|
524
|
+
|
|
525
|
+
- The manifest is always generated (no CLI flag needed)
|
|
526
|
+
- `gzipSize` is 0 when the file is not gzipped (either too small, poor compression ratio, or gzip disabled)
|
|
527
|
+
- `etag` is `NULL` when etag is disabled (`--etag=false`)
|
|
528
|
+
- The struct and array names use the `--define` prefix (default: `SVELTEESP32`)
|
|
529
|
+
- For ESP-IDF engine, C-compatible `typedef struct` syntax is used instead of C++ `struct`
|
|
530
|
+
|
|
448
531
|
### Command line options
|
|
449
532
|
|
|
450
533
|
| Option | Description | default |
|
package/dist/cppCode.d.ts
CHANGED
package/dist/cppCode.js
CHANGED
|
@@ -81,7 +81,7 @@ const etagArraysSection = `
|
|
|
81
81
|
{{#switch etag}}
|
|
82
82
|
{{#case "true"}}
|
|
83
83
|
{{#each sources}}
|
|
84
|
-
const char * etag_{{this.dataname}} = "{{this.
|
|
84
|
+
const char * etag_{{this.dataname}} = "{{this.sha256}}";
|
|
85
85
|
{{/each}}
|
|
86
86
|
{{/case}}
|
|
87
87
|
{{#case "false"}}
|
|
@@ -89,12 +89,30 @@ const char * etag_{{this.dataname}} = "{{this.md5}}";
|
|
|
89
89
|
{{#case "compiler"}}
|
|
90
90
|
#ifdef {{definePrefix}}_ENABLE_ETAG
|
|
91
91
|
{{#each sources}}
|
|
92
|
-
const char * etag_{{this.dataname}} = "{{this.
|
|
92
|
+
const char * etag_{{this.dataname}} = "{{this.sha256}}";
|
|
93
93
|
{{/each}}
|
|
94
94
|
#endif
|
|
95
95
|
{{/case}}
|
|
96
96
|
{{/switch}}
|
|
97
97
|
`;
|
|
98
|
+
const manifestSection = `
|
|
99
|
+
// File manifest struct
|
|
100
|
+
struct {{definePrefix}}_FileInfo {
|
|
101
|
+
const char* path;
|
|
102
|
+
uint32_t size;
|
|
103
|
+
uint32_t gzipSize;
|
|
104
|
+
const char* etag;
|
|
105
|
+
const char* contentType;
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
// File manifest array
|
|
109
|
+
const {{definePrefix}}_FileInfo {{definePrefix}}_FILES[] = {
|
|
110
|
+
{{#each sources}}
|
|
111
|
+
{ "/{{this.filename}}", {{this.length}}, {{this.gzipSizeForManifest}}, {{this.etagForManifest}}, "{{this.mime}}" },
|
|
112
|
+
{{/each}}
|
|
113
|
+
};
|
|
114
|
+
const size_t {{definePrefix}}_FILE_COUNT = sizeof({{definePrefix}}_FILES) / sizeof({{definePrefix}}_FILES[0]);
|
|
115
|
+
`;
|
|
98
116
|
const psychicTemplate = `
|
|
99
117
|
//engine: PsychicHttpServer
|
|
100
118
|
//config: {{{config}}}
|
|
@@ -116,6 +134,9 @@ ${dataArraysSection(false)}
|
|
|
116
134
|
//
|
|
117
135
|
${etagArraysSection}
|
|
118
136
|
|
|
137
|
+
//
|
|
138
|
+
${manifestSection}
|
|
139
|
+
|
|
119
140
|
//
|
|
120
141
|
// Http Handlers
|
|
121
142
|
void {{methodName}}(PsychicHttpServer * server) {
|
|
@@ -225,6 +246,9 @@ ${dataArraysSection(false)}
|
|
|
225
246
|
//
|
|
226
247
|
${etagArraysSection}
|
|
227
248
|
|
|
249
|
+
//
|
|
250
|
+
${manifestSection}
|
|
251
|
+
|
|
228
252
|
//
|
|
229
253
|
// Http Handlers
|
|
230
254
|
void {{methodName}}(PsychicHttpServer * server) {
|
|
@@ -330,6 +354,9 @@ ${dataArraysSection(true)}
|
|
|
330
354
|
//
|
|
331
355
|
${etagArraysSection}
|
|
332
356
|
|
|
357
|
+
//
|
|
358
|
+
${manifestSection}
|
|
359
|
+
|
|
333
360
|
//
|
|
334
361
|
// Http Handlers
|
|
335
362
|
void {{methodName}}(AsyncWebServer * server) {
|
|
@@ -489,13 +516,15 @@ const getTemplate = (engine) => {
|
|
|
489
516
|
return asyncTemplate;
|
|
490
517
|
}
|
|
491
518
|
};
|
|
492
|
-
const transformSourceToTemplateData = (s) => ({
|
|
519
|
+
const transformSourceToTemplateData = (s, etag) => ({
|
|
493
520
|
...s,
|
|
494
521
|
length: s.content.length,
|
|
495
522
|
bytes: [...s.content].map((v) => `${v.toString(10)}`).join(','),
|
|
496
523
|
lengthGzip: s.contentGzip.length,
|
|
497
524
|
bytesGzip: [...s.contentGzip].map((v) => `${v.toString(10)}`).join(','),
|
|
498
|
-
isDefault: s.filename.startsWith('index.htm')
|
|
525
|
+
isDefault: s.filename.startsWith('index.htm'),
|
|
526
|
+
gzipSizeForManifest: s.isGzip ? s.contentGzip.length : 0,
|
|
527
|
+
etagForManifest: etag === 'false' ? 'NULL' : `etag_${s.dataname}`
|
|
499
528
|
});
|
|
500
529
|
const postProcessCppCode = (code) => code
|
|
501
530
|
.split('\n')
|
|
@@ -531,7 +560,7 @@ const getCppCode = (sources, filesByExtension) => {
|
|
|
531
560
|
fileCount: sources.length.toString(),
|
|
532
561
|
fileSize: sources.reduce((previous, current) => previous + current.content.length, 0).toString(),
|
|
533
562
|
fileGzipSize: sources.reduce((previous, current) => previous + current.contentGzip.length, 0).toString(),
|
|
534
|
-
sources: sources.map((s) => transformSourceToTemplateData(s)),
|
|
563
|
+
sources: sources.map((s) => transformSourceToTemplateData(s, commandLine_1.cmdLine.etag)),
|
|
535
564
|
filesByExtension,
|
|
536
565
|
etag: commandLine_1.cmdLine.etag,
|
|
537
566
|
gzip: commandLine_1.cmdLine.gzip,
|
package/dist/cppCodeEspIdf.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const espidfTemplate = "\n//engine: espidf\n//config: {{{config}}}\n{{#if created }}\n//created: {{now}}\n{{/if}}\n//\n\n{{#switch etag}}\n{{#case \"true\"}}\n#ifdef {{definePrefix}}_ENABLE_ETAG\n#warning {{definePrefix}}_ENABLE_ETAG has no effect because it is permanently switched ON\n#endif\n{{/case}}\n{{#case \"false\"}}\n#ifdef {{definePrefix}}_ENABLE_ETAG\n#warning {{definePrefix}}_ENABLE_ETAG has no effect because it is permanently switched OFF\n#endif\n{{/case}}\n{{/switch}}\n\n{{#switch gzip}}\n{{#case \"true\"}}\n#ifdef {{definePrefix}}_ENABLE_GZIP\n#warning {{definePrefix}}_ENABLE_GZIP has no effect because it is permanently switched ON\n#endif\n{{/case}}\n{{#case \"false\"}}\n#ifdef {{definePrefix}}_ENABLE_GZIP\n#warning {{definePrefix}}_ENABLE_GZIP has no effect because it is permanently switched OFF\n#endif\n{{/case}}\n{{/switch}}\n\n//\n{{#if version }}\n#define {{definePrefix}}_VERSION \"{{version}}\"\n{{/if}}\n#define {{definePrefix}}_COUNT {{fileCount}}\n#define {{definePrefix}}_SIZE {{fileSize}}\n#define {{definePrefix}}_SIZE_GZIP {{fileGzipSize}}\n\n//\n{{#each sources}}\n#define {{../definePrefix}}_FILE_{{this.datanameUpperCase}}\n{{/each}}\n\n//\n{{#each filesByExtension}}\n#define {{../definePrefix}}_{{this.extension}}_FILES {{this.count}}\n{{/each}}\n\n#include <stdint.h>\n#include <string.h>\n#include <stdlib.h>\n#include <esp_err.h>\n#include <esp_http_server.h>\n\n//\n{{#switch gzip}}\n{{#case \"true\"}}\n {{#each sources}}\nconst char datagzip_{{this.dataname}}[{{this.lengthGzip}}] = { {{this.bytesGzip}} };\n {{/each}}\n{{/case}}\n{{#case \"false\"}}\n {{#each sources}}\nconst char data_{{this.dataname}}[{{this.length}}] = { {{this.bytes}} };\n {{/each}}\n{{/case}}\n{{#case \"compiler\"}}\n#ifdef {{definePrefix}}_ENABLE_GZIP\n {{#each sources}}\nconst char datagzip_{{this.dataname}}[{{this.lengthGzip}}] = { {{this.bytesGzip}} };\n {{/each}}\n#else\n {{#each sources}}\nconst char data_{{this.dataname}}[{{this.length}}] = { {{this.bytes}} };\n {{/each}}\n#endif \n{{/case}}\n{{/switch}}\n\n//\n{{#switch etag}}\n{{#case \"true\"}}\n {{#each sources}}\
|
|
1
|
+
export declare const espidfTemplate = "\n//engine: espidf\n//config: {{{config}}}\n{{#if created }}\n//created: {{now}}\n{{/if}}\n//\n\n{{#switch etag}}\n{{#case \"true\"}}\n#ifdef {{definePrefix}}_ENABLE_ETAG\n#warning {{definePrefix}}_ENABLE_ETAG has no effect because it is permanently switched ON\n#endif\n{{/case}}\n{{#case \"false\"}}\n#ifdef {{definePrefix}}_ENABLE_ETAG\n#warning {{definePrefix}}_ENABLE_ETAG has no effect because it is permanently switched OFF\n#endif\n{{/case}}\n{{/switch}}\n\n{{#switch gzip}}\n{{#case \"true\"}}\n#ifdef {{definePrefix}}_ENABLE_GZIP\n#warning {{definePrefix}}_ENABLE_GZIP has no effect because it is permanently switched ON\n#endif\n{{/case}}\n{{#case \"false\"}}\n#ifdef {{definePrefix}}_ENABLE_GZIP\n#warning {{definePrefix}}_ENABLE_GZIP has no effect because it is permanently switched OFF\n#endif\n{{/case}}\n{{/switch}}\n\n//\n{{#if version }}\n#define {{definePrefix}}_VERSION \"{{version}}\"\n{{/if}}\n#define {{definePrefix}}_COUNT {{fileCount}}\n#define {{definePrefix}}_SIZE {{fileSize}}\n#define {{definePrefix}}_SIZE_GZIP {{fileGzipSize}}\n\n//\n{{#each sources}}\n#define {{../definePrefix}}_FILE_{{this.datanameUpperCase}}\n{{/each}}\n\n//\n{{#each filesByExtension}}\n#define {{../definePrefix}}_{{this.extension}}_FILES {{this.count}}\n{{/each}}\n\n#include <stdint.h>\n#include <string.h>\n#include <stdlib.h>\n#include <esp_err.h>\n#include <esp_http_server.h>\n\n//\n{{#switch gzip}}\n{{#case \"true\"}}\n {{#each sources}}\nconst char datagzip_{{this.dataname}}[{{this.lengthGzip}}] = { {{this.bytesGzip}} };\n {{/each}}\n{{/case}}\n{{#case \"false\"}}\n {{#each sources}}\nconst char data_{{this.dataname}}[{{this.length}}] = { {{this.bytes}} };\n {{/each}}\n{{/case}}\n{{#case \"compiler\"}}\n#ifdef {{definePrefix}}_ENABLE_GZIP\n {{#each sources}}\nconst char datagzip_{{this.dataname}}[{{this.lengthGzip}}] = { {{this.bytesGzip}} };\n {{/each}}\n#else\n {{#each sources}}\nconst char data_{{this.dataname}}[{{this.length}}] = { {{this.bytes}} };\n {{/each}}\n#endif \n{{/case}}\n{{/switch}}\n\n//\n{{#switch etag}}\n{{#case \"true\"}}\n {{#each sources}}\nstatic const char etag_{{this.dataname}}[] = \"{{this.sha256}}\";\n {{/each}}\n{{/case}}\n{{#case \"false\"}}\n{{/case}}\n{{#case \"compiler\"}}\n#ifdef {{definePrefix}}_ENABLE_ETAG\n {{#each sources}}\nstatic const char etag_{{this.dataname}}[] = \"{{this.sha256}}\";\n {{/each}}\n#endif\n{{/case}}\n{{/switch}}\n\n// File manifest struct (C-compatible typedef)\ntypedef struct {\n const char* path;\n uint32_t size;\n uint32_t gzipSize;\n const char* etag;\n const char* contentType;\n} {{definePrefix}}_FileInfo;\n\n// File manifest array\nstatic const {{definePrefix}}_FileInfo {{definePrefix}}_FILES[] = {\n{{#each sources}}\n { \"/{{this.filename}}\", {{this.length}}, {{this.gzipSizeForManifest}}, {{this.etagForManifest}}, \"{{this.mime}}\" },\n{{/each}}\n};\nstatic const size_t {{definePrefix}}_FILE_COUNT = sizeof({{definePrefix}}_FILES) / sizeof({{definePrefix}}_FILES[0]);\n\n{{#each sources}}\n\nstatic esp_err_t file_handler_{{this.datanameUpperCase}} (httpd_req_t *req)\n{\n{{#switch ../etag}}\n{{#case \"true\"}}\n size_t hdr_len = httpd_req_get_hdr_value_len(req, \"If-None-Match\");\n if (hdr_len > 0) {\n char* hdr_value = malloc(hdr_len + 1);\n if (httpd_req_get_hdr_value_str(req, \"If-None-Match\", hdr_value, hdr_len + 1) == ESP_OK) {\n if (strcmp(hdr_value, etag_{{this.dataname}}) == 0) {\n free(hdr_value);\n httpd_resp_set_status(req, \"304 Not Modified\");\n httpd_resp_send(req, NULL, 0);\n return ESP_OK;\n }\n }\n free(hdr_value);\n }\n{{/case}}\n{{#case \"compiler\"}}\n #ifdef {{../definePrefix}}_ENABLE_ETAG\n size_t hdr_len = httpd_req_get_hdr_value_len(req, \"If-None-Match\");\n if (hdr_len > 0) {\n char* hdr_value = malloc(hdr_len + 1);\n if (httpd_req_get_hdr_value_str(req, \"If-None-Match\", hdr_value, hdr_len + 1) == ESP_OK) {\n if (strcmp(hdr_value, etag_{{this.dataname}}) == 0) {\n free(hdr_value);\n httpd_resp_set_status(req, \"304 Not Modified\");\n httpd_resp_send(req, NULL, 0);\n return ESP_OK;\n }\n }\n free(hdr_value);\n }\n #endif\n{{/case}}\n{{/switch}}\n httpd_resp_set_type(req, \"{{this.mime}}\");\n{{#switch ../gzip}}\n{{#case \"true\"}}\n{{#if this.isGzip}}\n httpd_resp_set_hdr(req, \"Content-Encoding\", \"gzip\");\n{{/if}}\n{{/case}}\n{{#case \"compiler\"}}\n {{#if this.isGzip}}\n #ifdef {{../definePrefix}}_ENABLE_GZIP\n httpd_resp_set_hdr(req, \"Content-Encoding\", \"gzip\");\n #endif \n {{/if}}\n{{/case}}\n{{/switch}}\n\n{{#switch ../etag}}\n{{#case \"true\"}}\n{{#../cacheTime}}\n httpd_resp_set_hdr(req, \"Cache-Control\", \"max-age={{value}}\");\n{{/../cacheTime}}\n{{^../cacheTime}}\n httpd_resp_set_hdr(req, \"Cache-Control\", \"no-cache\");\n{{/../cacheTime}}\n httpd_resp_set_hdr(req, \"ETag\", etag_{{this.dataname}});\n{{/case}}\n{{#case \"compiler\"}}\n #ifdef {{../definePrefix}}_ENABLE_ETAG\n{{#../cacheTime}}\n httpd_resp_set_hdr(req, \"Cache-Control\", \"max-age={{value}}\");\n{{/../cacheTime}}\n{{^../cacheTime}}\n httpd_resp_set_hdr(req, \"Cache-Control\", \"no-cache\");\n{{/../cacheTime}}\n httpd_resp_set_hdr(req, \"ETag\", etag_{{this.dataname}});\n #endif \n{{/case}}\n{{/switch}}\n\n{{#switch ../gzip}}\n{{#case \"true\"}}\n httpd_resp_send(req, datagzip_{{this.dataname}}, {{this.lengthGzip}});\n{{/case}}\n{{#case \"false\"}}\n httpd_resp_send(req, data_{{this.dataname}}, {{this.length}});\n{{/case}}\n{{#case \"compiler\"}}\n #ifdef {{../definePrefix}}_ENABLE_GZIP\n httpd_resp_send(req, datagzip_{{this.dataname}}, {{this.lengthGzip}});\n #else\n httpd_resp_send(req, data_{{this.dataname}}, {{this.length}});\n #endif \n{{/case}}\n{{/switch}}\n return ESP_OK;\n}\n\n{{#if this.isDefault}}\nstatic const httpd_uri_t route_def_{{this.datanameUpperCase}} = {\n .uri = \"/\",\n .method = HTTP_GET,\n .handler = file_handler_{{this.datanameUpperCase}},\n};\n{{/if}}\n\nstatic const httpd_uri_t route_{{this.datanameUpperCase}} = {\n .uri = \"/{{this.filename}}\",\n .method = HTTP_GET,\n .handler = file_handler_{{this.datanameUpperCase}},\n};\n\n{{/each}}\n\n\n\nstatic inline void {{methodName}}(httpd_handle_t server) {\n{{#each sources}}\n{{#if this.isDefault}}\n httpd_register_uri_handler(server, &route_def_{{this.datanameUpperCase}});\n{{/if}}\n httpd_register_uri_handler(server, &route_{{this.datanameUpperCase}});\n{{/each}}\n\n}";
|
package/dist/cppCodeEspIdf.js
CHANGED
|
@@ -88,7 +88,7 @@ const char data_{{this.dataname}}[{{this.length}}] = { {{this.bytes}} };
|
|
|
88
88
|
{{#switch etag}}
|
|
89
89
|
{{#case "true"}}
|
|
90
90
|
{{#each sources}}
|
|
91
|
-
const char
|
|
91
|
+
static const char etag_{{this.dataname}}[] = "{{this.sha256}}";
|
|
92
92
|
{{/each}}
|
|
93
93
|
{{/case}}
|
|
94
94
|
{{#case "false"}}
|
|
@@ -96,12 +96,29 @@ const char * etag_{{this.dataname}} = "{{this.md5}}";
|
|
|
96
96
|
{{#case "compiler"}}
|
|
97
97
|
#ifdef {{definePrefix}}_ENABLE_ETAG
|
|
98
98
|
{{#each sources}}
|
|
99
|
-
const char
|
|
99
|
+
static const char etag_{{this.dataname}}[] = "{{this.sha256}}";
|
|
100
100
|
{{/each}}
|
|
101
|
-
#endif
|
|
101
|
+
#endif
|
|
102
102
|
{{/case}}
|
|
103
103
|
{{/switch}}
|
|
104
104
|
|
|
105
|
+
// File manifest struct (C-compatible typedef)
|
|
106
|
+
typedef struct {
|
|
107
|
+
const char* path;
|
|
108
|
+
uint32_t size;
|
|
109
|
+
uint32_t gzipSize;
|
|
110
|
+
const char* etag;
|
|
111
|
+
const char* contentType;
|
|
112
|
+
} {{definePrefix}}_FileInfo;
|
|
113
|
+
|
|
114
|
+
// File manifest array
|
|
115
|
+
static const {{definePrefix}}_FileInfo {{definePrefix}}_FILES[] = {
|
|
116
|
+
{{#each sources}}
|
|
117
|
+
{ "/{{this.filename}}", {{this.length}}, {{this.gzipSizeForManifest}}, {{this.etagForManifest}}, "{{this.mime}}" },
|
|
118
|
+
{{/each}}
|
|
119
|
+
};
|
|
120
|
+
static const size_t {{definePrefix}}_FILE_COUNT = sizeof({{definePrefix}}_FILES) / sizeof({{definePrefix}}_FILES[0]);
|
|
121
|
+
|
|
105
122
|
{{#each sources}}
|
|
106
123
|
|
|
107
124
|
static esp_err_t file_handler_{{this.datanameUpperCase}} (httpd_req_t *req)
|
package/dist/index.d.ts
CHANGED
|
@@ -1 +1,7 @@
|
|
|
1
|
-
|
|
1
|
+
import { CppCodeSource } from './cppCode';
|
|
2
|
+
declare const shouldUseGzip: (originalSize: number, compressedSize: number) => boolean;
|
|
3
|
+
declare const calculateCompressionRatio: (originalSize: number, compressedSize: number) => number;
|
|
4
|
+
declare const formatCompressionLog: (filename: string, padding: string, originalSize: number, compressedSize: number, useGzip: boolean) => string;
|
|
5
|
+
declare const createSourceEntry: (filename: string, dataname: string, content: Buffer, contentGzip: Buffer, mimeType: string, sha256: string, isGzip: boolean) => CppCodeSource;
|
|
6
|
+
declare const updateExtensionGroup: (extension: string) => void;
|
|
7
|
+
export { calculateCompressionRatio, createSourceEntry, formatCompressionLog, shouldUseGzip, updateExtensionGroup };
|
package/dist/index.js
CHANGED
|
@@ -3,6 +3,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.updateExtensionGroup = exports.shouldUseGzip = exports.formatCompressionLog = exports.createSourceEntry = exports.calculateCompressionRatio = void 0;
|
|
6
7
|
const node_crypto_1 = require("node:crypto");
|
|
7
8
|
const node_fs_1 = require("node:fs");
|
|
8
9
|
const node_path_1 = __importDefault(require("node:path"));
|
|
@@ -23,7 +24,9 @@ const summary = {
|
|
|
23
24
|
const sources = [];
|
|
24
25
|
const filesByExtension = [];
|
|
25
26
|
const shouldUseGzip = (originalSize, compressedSize) => originalSize > GZIP_MIN_SIZE && compressedSize < originalSize * GZIP_MIN_REDUCTION_RATIO;
|
|
27
|
+
exports.shouldUseGzip = shouldUseGzip;
|
|
26
28
|
const calculateCompressionRatio = (originalSize, compressedSize) => Math.round((compressedSize / originalSize) * 100);
|
|
29
|
+
exports.calculateCompressionRatio = calculateCompressionRatio;
|
|
27
30
|
const formatCompressionLog = (filename, padding, originalSize, compressedSize, useGzip) => {
|
|
28
31
|
const ratio = calculateCompressionRatio(originalSize, compressedSize);
|
|
29
32
|
const sizeInfo = `(${originalSize} -> ${compressedSize} = ${ratio}%)`;
|
|
@@ -32,7 +35,8 @@ const formatCompressionLog = (filename, padding, originalSize, compressedSize, u
|
|
|
32
35
|
const tooSmall = originalSize <= GZIP_MIN_SIZE ? '(too small) ' : '';
|
|
33
36
|
return (0, consoleColor_1.yellowLog)(` [${filename}] ${padding} x gzip unused ${tooSmall}${sizeInfo}`);
|
|
34
37
|
};
|
|
35
|
-
|
|
38
|
+
exports.formatCompressionLog = formatCompressionLog;
|
|
39
|
+
const createSourceEntry = (filename, dataname, content, contentGzip, mimeType, sha256, isGzip) => ({
|
|
36
40
|
filename,
|
|
37
41
|
dataname,
|
|
38
42
|
datanameUpperCase: dataname.toUpperCase(),
|
|
@@ -40,8 +44,9 @@ const createSourceEntry = (filename, dataname, content, contentGzip, mimeType, m
|
|
|
40
44
|
contentGzip: isGzip ? contentGzip : content,
|
|
41
45
|
isGzip,
|
|
42
46
|
mime: mimeType,
|
|
43
|
-
|
|
47
|
+
sha256
|
|
44
48
|
});
|
|
49
|
+
exports.createSourceEntry = createSourceEntry;
|
|
45
50
|
const updateExtensionGroup = (extension) => {
|
|
46
51
|
const group = filesByExtension.find((fe) => fe.extension === extension);
|
|
47
52
|
if (group)
|
|
@@ -49,6 +54,7 @@ const updateExtensionGroup = (extension) => {
|
|
|
49
54
|
else
|
|
50
55
|
filesByExtension.push({ extension, count: 1 });
|
|
51
56
|
};
|
|
57
|
+
exports.updateExtensionGroup = updateExtensionGroup;
|
|
52
58
|
console.log('Collecting source files');
|
|
53
59
|
const files = (0, file_1.getFiles)();
|
|
54
60
|
if (files.size === 0) {
|
|
@@ -67,12 +73,12 @@ for (const [originalFilename, content] of files) {
|
|
|
67
73
|
if (extension.startsWith('.'))
|
|
68
74
|
extension = extension.slice(1);
|
|
69
75
|
updateExtensionGroup(extension);
|
|
70
|
-
const
|
|
76
|
+
const sha256 = (0, node_crypto_1.createHash)('sha256').update(content).digest('hex');
|
|
71
77
|
summary.size += content.length;
|
|
72
78
|
const zipContent = (0, node_zlib_1.gzipSync)(content, { level: 9 });
|
|
73
79
|
summary.gzipsize += zipContent.length;
|
|
74
80
|
const useGzip = shouldUseGzip(content.length, zipContent.length);
|
|
75
|
-
sources.push(createSourceEntry(filename, dataname, content, zipContent, mimeType,
|
|
81
|
+
sources.push(createSourceEntry(filename, dataname, content, zipContent, mimeType, sha256, useGzip));
|
|
76
82
|
const padding = ' '.repeat(longestFilename - originalFilename.length);
|
|
77
83
|
console.log(formatCompressionLog(originalFilename, padding, content.length, zipContent.length, useGzip));
|
|
78
84
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "svelteesp32",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.14.0",
|
|
4
4
|
"description": "Convert Svelte (or any frontend) JS application to serve it from ESP32 webserver (PsychicHttp)",
|
|
5
5
|
"author": "BCsabaEngine",
|
|
6
6
|
"license": "ISC",
|
|
@@ -61,22 +61,22 @@
|
|
|
61
61
|
],
|
|
62
62
|
"devDependencies": {
|
|
63
63
|
"@types/mime-types": "^3.0.1",
|
|
64
|
-
"@types/node": "^25.
|
|
64
|
+
"@types/node": "^25.1.0",
|
|
65
65
|
"@types/picomatch": "^4.0.2",
|
|
66
|
-
"@typescript-eslint/eslint-plugin": "^8.
|
|
67
|
-
"@typescript-eslint/parser": "^8.
|
|
68
|
-
"@vitest/coverage-v8": "^4.0.
|
|
69
|
-
"eslint": "^9.39.
|
|
66
|
+
"@typescript-eslint/eslint-plugin": "^8.54.0",
|
|
67
|
+
"@typescript-eslint/parser": "^8.54.0",
|
|
68
|
+
"@vitest/coverage-v8": "^4.0.18",
|
|
69
|
+
"eslint": "^9.39.2",
|
|
70
70
|
"eslint-config-prettier": "^10.1.8",
|
|
71
71
|
"eslint-plugin-simple-import-sort": "^12.1.1",
|
|
72
72
|
"eslint-plugin-unicorn": "^62.0.0",
|
|
73
|
-
"memfs": "^4.
|
|
73
|
+
"memfs": "^4.56.10",
|
|
74
74
|
"nodemon": "^3.1.11",
|
|
75
|
-
"prettier": "^3.
|
|
75
|
+
"prettier": "^3.8.1",
|
|
76
76
|
"ts-node": "^10.9.2",
|
|
77
77
|
"tsx": "^4.21.0",
|
|
78
78
|
"typescript": "^5.9.3",
|
|
79
|
-
"vitest": "^4.0.
|
|
79
|
+
"vitest": "^4.0.18"
|
|
80
80
|
},
|
|
81
81
|
"dependencies": {
|
|
82
82
|
"handlebars": "^4.7.8",
|