svelteesp32 2.2.0 → 2.2.2
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 +5 -3
- package/dist/commandLine.js +18 -37
- package/dist/cppCode.js +14 -9
- package/dist/cppCodeEspIdf.d.ts +1 -1
- package/dist/cppCodeEspIdf.js +4 -4
- package/dist/errorMessages.js +1 -1
- package/dist/index.d.ts +3 -2
- package/dist/index.js +48 -3
- package/package.json +4 -4
package/README.md
CHANGED
|
@@ -79,6 +79,8 @@ void setup() {
|
|
|
79
79
|
|
|
80
80
|
## What's New
|
|
81
81
|
|
|
82
|
+
- **v2.2.2** — `static const` data/ETag arrays prevent multi-TU linker collisions; `SVELTEESP32_MAX_URI_HANDLERS` define added for psychic engine; default exclude patterns removed (now explicit-only)
|
|
83
|
+
- **v2.2.1** — Enhanced `--dryrun` output: engine/ETag/gzip/SPA summary header + aligned route table with MIME types, sizes, and tags (`[default]`, `[no gzip]`, `[SPA catch-all]`)
|
|
82
84
|
- **v2.2.0** — SPA routing catch-all (`--spa`) for client-side routers on all four engines
|
|
83
85
|
- **v2.1.0** — New Arduino WebServer engine (`-e webserver`), dependency updates
|
|
84
86
|
- **v2.0.0** — **BREAKING**: PsychicHttpServer V2 is now the default `psychic` engine. The `psychic2` engine has been removed. Dry run mode, C++ identifier validation, improved MIME type warnings
|
|
@@ -290,7 +292,7 @@ void initSvelteStaticFiles(PsychicHttpServer * server) {
|
|
|
290
292
|
|
|
291
293
|
**Recommendation:** For ESP32-only projects, use PsychicHttpServer V2 (`-e psychic`) for the fastest, most stable experience.
|
|
292
294
|
|
|
293
|
-
**Note:** For PsychicHttp, configure `server.config.max_uri_handlers
|
|
295
|
+
**Note:** For PsychicHttp, configure `server.config.max_uri_handlers`. The generated header provides `SVELTEESP32_MAX_URI_HANDLERS` (file count + 5 safety margin) for use directly in your sketch.
|
|
294
296
|
|
|
295
297
|
---
|
|
296
298
|
|
|
@@ -342,7 +344,7 @@ npx svelteesp32 -s ./dist -o ./output.h --exclude="*.map"
|
|
|
342
344
|
npx svelteesp32 -s ./dist -o ./output.h --exclude="*.map,*.md,test/**/*"
|
|
343
345
|
```
|
|
344
346
|
|
|
345
|
-
|
|
347
|
+
No patterns are excluded by default — specify everything you need explicitly.
|
|
346
348
|
|
|
347
349
|
Build output shows exactly what's excluded:
|
|
348
350
|
|
|
@@ -461,7 +463,7 @@ Called for every response (200 = content served, 304 = cache hit).
|
|
|
461
463
|
| `--define` | C++ define prefix | `SVELTEESP32` |
|
|
462
464
|
| `--espmethod` | Init function name | `initSvelteStaticFiles` |
|
|
463
465
|
| `--config` | Custom RC file path | `.svelteesp32rc.json` |
|
|
464
|
-
| `--dryrun` | Show summary without writing output
|
|
466
|
+
| `--dryrun` | Show route table + summary without writing output | `false` |
|
|
465
467
|
| `--spa` | Serve index.html for unmatched routes (SPA routing) | `false` |
|
|
466
468
|
| `--noindexcheck` | Skip index.html validation | `false` |
|
|
467
469
|
| `-h` | Show help | |
|
package/dist/commandLine.js
CHANGED
|
@@ -113,16 +113,6 @@ function parseSize(value, name) {
|
|
|
113
113
|
throw new Error(`${name} must be a positive integer: ${value}`);
|
|
114
114
|
return bytes;
|
|
115
115
|
}
|
|
116
|
-
const DEFAULT_EXCLUDE_PATTERNS = [
|
|
117
|
-
'.DS_Store',
|
|
118
|
-
'Thumbs.db',
|
|
119
|
-
'.git',
|
|
120
|
-
'.svn',
|
|
121
|
-
'*.swp',
|
|
122
|
-
'*~',
|
|
123
|
-
'.gitignore',
|
|
124
|
-
'.gitattributes'
|
|
125
|
-
];
|
|
126
116
|
function findRcFile(customConfigPath) {
|
|
127
117
|
if (customConfigPath) {
|
|
128
118
|
if ((0, node_fs_1.existsSync)(customConfigPath))
|
|
@@ -186,8 +176,7 @@ function hasNpmVariables(config) {
|
|
|
186
176
|
checkStringForNpmVariable(config.define) ||
|
|
187
177
|
checkStringForNpmVariable(config.version) ||
|
|
188
178
|
checkStringForNpmVariable(config.basepath) ||
|
|
189
|
-
(Array.isArray(config.exclude) && config.exclude.some((pattern) => checkStringForNpmVariable(pattern)))
|
|
190
|
-
false);
|
|
179
|
+
(Array.isArray(config.exclude) && config.exclude.some((pattern) => checkStringForNpmVariable(pattern))));
|
|
191
180
|
}
|
|
192
181
|
function interpolateNpmVariables(config, rcFilePath) {
|
|
193
182
|
if (!hasNpmVariables(config))
|
|
@@ -250,6 +239,20 @@ function loadRcFile(rcPath) {
|
|
|
250
239
|
throw error;
|
|
251
240
|
}
|
|
252
241
|
}
|
|
242
|
+
function validateSizeOption(configObject, key) {
|
|
243
|
+
const value = configObject[key];
|
|
244
|
+
if (value === undefined)
|
|
245
|
+
return;
|
|
246
|
+
if (typeof value === 'string')
|
|
247
|
+
try {
|
|
248
|
+
configObject[key] = parseSize(value, key);
|
|
249
|
+
}
|
|
250
|
+
catch {
|
|
251
|
+
throw new TypeError(`Invalid ${key} in RC file: ${value} (must be a positive number with optional k/m suffix)`);
|
|
252
|
+
}
|
|
253
|
+
else if (typeof value !== 'number' || Number.isNaN(value) || value <= 0)
|
|
254
|
+
throw new TypeError(`Invalid ${key} in RC file: ${value} (must be a positive number)`);
|
|
255
|
+
}
|
|
253
256
|
function validateRcConfig(config, rcPath) {
|
|
254
257
|
if (typeof config !== 'object' || config === null)
|
|
255
258
|
throw new Error(`RC file ${rcPath} must contain a JSON object`);
|
|
@@ -299,30 +302,8 @@ function validateRcConfig(config, rcPath) {
|
|
|
299
302
|
if (typeof pattern !== 'string')
|
|
300
303
|
throw new TypeError('All exclude patterns must be strings');
|
|
301
304
|
}
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
if (typeof value === 'string')
|
|
305
|
-
try {
|
|
306
|
-
configObject['maxsize'] = parseSize(value, 'maxsize');
|
|
307
|
-
}
|
|
308
|
-
catch {
|
|
309
|
-
throw new TypeError(`Invalid maxsize in RC file: ${value} (must be a positive number with optional k/m suffix)`);
|
|
310
|
-
}
|
|
311
|
-
else if (typeof value !== 'number' || Number.isNaN(value) || value <= 0)
|
|
312
|
-
throw new TypeError(`Invalid maxsize in RC file: ${value} (must be a positive number)`);
|
|
313
|
-
}
|
|
314
|
-
if (configObject['maxgzipsize'] !== undefined) {
|
|
315
|
-
const value = configObject['maxgzipsize'];
|
|
316
|
-
if (typeof value === 'string')
|
|
317
|
-
try {
|
|
318
|
-
configObject['maxgzipsize'] = parseSize(value, 'maxgzipsize');
|
|
319
|
-
}
|
|
320
|
-
catch {
|
|
321
|
-
throw new TypeError(`Invalid maxgzipsize in RC file: ${value} (must be a positive number with optional k/m suffix)`);
|
|
322
|
-
}
|
|
323
|
-
else if (typeof value !== 'number' || Number.isNaN(value) || value <= 0)
|
|
324
|
-
throw new TypeError(`Invalid maxgzipsize in RC file: ${value} (must be a positive number)`);
|
|
325
|
-
}
|
|
305
|
+
validateSizeOption(configObject, 'maxsize');
|
|
306
|
+
validateSizeOption(configObject, 'maxgzipsize');
|
|
326
307
|
if (configObject['noindexcheck'] !== undefined && typeof configObject['noindexcheck'] !== 'boolean')
|
|
327
308
|
throw new TypeError(`Invalid noindexcheck in RC file: ${configObject['noindexcheck']} (must be boolean)`);
|
|
328
309
|
if (configObject['dryrun'] !== undefined && typeof configObject['dryrun'] !== 'boolean')
|
|
@@ -361,7 +342,7 @@ function parseArguments() {
|
|
|
361
342
|
espmethod: 'initSvelteStaticFiles',
|
|
362
343
|
define: 'SVELTEESP32',
|
|
363
344
|
cachetime: 0,
|
|
364
|
-
exclude: [
|
|
345
|
+
exclude: [],
|
|
365
346
|
basePath: ''
|
|
366
347
|
};
|
|
367
348
|
if (rcConfig.engine)
|
package/dist/cppCode.js
CHANGED
|
@@ -38,6 +38,9 @@ const commonHeaderSection = `
|
|
|
38
38
|
#define {{definePrefix}}_COUNT {{fileCount}}
|
|
39
39
|
#define {{definePrefix}}_SIZE {{fileSize}}
|
|
40
40
|
#define {{definePrefix}}_SIZE_GZIP {{fileGzipSize}}
|
|
41
|
+
{{#if isPsychic}}
|
|
42
|
+
#define {{definePrefix}}_MAX_URI_HANDLERS {{maxUriHandlers}}
|
|
43
|
+
{{/if}}
|
|
41
44
|
|
|
42
45
|
//
|
|
43
46
|
{{#each sources}}
|
|
@@ -55,22 +58,22 @@ const dataArraysSection = (progmem = false) => {
|
|
|
55
58
|
{{#switch gzip}}
|
|
56
59
|
{{#case "true"}}
|
|
57
60
|
{{#each sources}}
|
|
58
|
-
const uint8_t datagzip_{{this.dataname}}[{{this.lengthGzip}}]${memDirective} = { {{this.bytesGzip}} };
|
|
61
|
+
static const uint8_t datagzip_{{this.dataname}}[{{this.lengthGzip}}]${memDirective} = { {{this.bytesGzip}} };
|
|
59
62
|
{{/each}}
|
|
60
63
|
{{/case}}
|
|
61
64
|
{{#case "false"}}
|
|
62
65
|
{{#each sources}}
|
|
63
|
-
const uint8_t data_{{this.dataname}}[{{this.length}}]${memDirective} = { {{this.bytes}} };
|
|
66
|
+
static const uint8_t data_{{this.dataname}}[{{this.length}}]${memDirective} = { {{this.bytes}} };
|
|
64
67
|
{{/each}}
|
|
65
68
|
{{/case}}
|
|
66
69
|
{{#case "compiler"}}
|
|
67
70
|
#ifdef {{definePrefix}}_ENABLE_GZIP
|
|
68
71
|
{{#each sources}}
|
|
69
|
-
const uint8_t datagzip_{{this.dataname}}[{{this.lengthGzip}}]${memDirective} = { {{this.bytesGzip}} };
|
|
72
|
+
static const uint8_t datagzip_{{this.dataname}}[{{this.lengthGzip}}]${memDirective} = { {{this.bytesGzip}} };
|
|
70
73
|
{{/each}}
|
|
71
74
|
#else
|
|
72
75
|
{{#each sources}}
|
|
73
|
-
const uint8_t data_{{this.dataname}}[{{this.length}}]${memDirective} = { {{this.bytes}} };
|
|
76
|
+
static const uint8_t data_{{this.dataname}}[{{this.length}}]${memDirective} = { {{this.bytes}} };
|
|
74
77
|
{{/each}}
|
|
75
78
|
#endif
|
|
76
79
|
{{/case}}
|
|
@@ -81,7 +84,7 @@ const etagArraysSection = `
|
|
|
81
84
|
{{#switch etag}}
|
|
82
85
|
{{#case "true"}}
|
|
83
86
|
{{#each sources}}
|
|
84
|
-
const char
|
|
87
|
+
static const char etag_{{this.dataname}}[] = "{{this.sha256}}";
|
|
85
88
|
{{/each}}
|
|
86
89
|
{{/case}}
|
|
87
90
|
{{#case "false"}}
|
|
@@ -89,7 +92,7 @@ const char * etag_{{this.dataname}} = "{{this.sha256}}";
|
|
|
89
92
|
{{#case "compiler"}}
|
|
90
93
|
#ifdef {{definePrefix}}_ENABLE_ETAG
|
|
91
94
|
{{#each sources}}
|
|
92
|
-
const char
|
|
95
|
+
static const char etag_{{this.dataname}}[] = "{{this.sha256}}";
|
|
93
96
|
{{/each}}
|
|
94
97
|
#endif
|
|
95
98
|
{{/case}}
|
|
@@ -106,12 +109,12 @@ struct {{definePrefix}}_FileInfo {
|
|
|
106
109
|
};
|
|
107
110
|
|
|
108
111
|
// File manifest array
|
|
109
|
-
const {{definePrefix}}_FileInfo {{definePrefix}}_FILES[] = {
|
|
112
|
+
static const {{definePrefix}}_FileInfo {{definePrefix}}_FILES[] = {
|
|
110
113
|
{{#each sources}}
|
|
111
114
|
{ "{{../basePath}}/{{this.filename}}", {{this.length}}, {{this.gzipSizeForManifest}}, {{this.etagForManifest}}, "{{this.mime}}" },
|
|
112
115
|
{{/each}}
|
|
113
116
|
};
|
|
114
|
-
const size_t {{definePrefix}}_FILE_COUNT = sizeof({{definePrefix}}_FILES) / sizeof({{definePrefix}}_FILES[0]);
|
|
117
|
+
static const size_t {{definePrefix}}_FILE_COUNT = sizeof({{definePrefix}}_FILES) / sizeof({{definePrefix}}_FILES[0]);
|
|
115
118
|
`;
|
|
116
119
|
const hookSection = `
|
|
117
120
|
// File served hook - override with your own implementation
|
|
@@ -1030,7 +1033,9 @@ const getCppCode = (sources, filesByExtension) => {
|
|
|
1030
1033
|
definePrefix: commandLine_1.cmdLine.define,
|
|
1031
1034
|
basePath: commandLine_1.cmdLine.basePath,
|
|
1032
1035
|
spa: !!commandLine_1.cmdLine.spa,
|
|
1033
|
-
spaSource
|
|
1036
|
+
spaSource,
|
|
1037
|
+
isPsychic: commandLine_1.cmdLine.engine === 'psychic',
|
|
1038
|
+
maxUriHandlers: (sources.length + 5).toString()
|
|
1034
1039
|
};
|
|
1035
1040
|
const rawCode = template(templateData, { helpers: createHandlebarsHelpers() });
|
|
1036
1041
|
return postProcessCppCode(rawCode);
|
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}}\
|
|
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}}\nstatic const unsigned char datagzip_{{this.dataname}}[{{this.lengthGzip}}] = { {{this.bytesGzip}} };\n {{/each}}\n{{/case}}\n{{#case \"false\"}}\n {{#each sources}}\nstatic const unsigned char data_{{this.dataname}}[{{this.length}}] = { {{this.bytes}} };\n {{/each}}\n{{/case}}\n{{#case \"compiler\"}}\n#ifdef {{definePrefix}}_ENABLE_GZIP\n {{#each sources}}\nstatic const unsigned char datagzip_{{this.dataname}}[{{this.lengthGzip}}] = { {{this.bytesGzip}} };\n {{/each}}\n#else\n {{#each sources}}\nstatic const unsigned 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 { \"{{../basePath}}/{{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// File served hook - override with your own implementation\n__attribute__((weak)) void {{definePrefix}}_onFileServed(const char* path, int statusCode) {}\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 (hdr_value == NULL) { httpd_resp_send_500(req); return ESP_FAIL; }\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 {{../definePrefix}}_onFileServed(\"{{../basePath}}/{{this.filename}}\", 304);\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 (hdr_value == NULL) { httpd_resp_send_500(req); return ESP_FAIL; }\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 {{../definePrefix}}_onFileServed(\"{{../basePath}}/{{this.filename}}\", 304);\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 {{../definePrefix}}_onFileServed(\"{{../basePath}}/{{this.filename}}\", 200);\n httpd_resp_send(req, (const char *)datagzip_{{this.dataname}}, {{this.lengthGzip}});\n{{/case}}\n{{#case \"false\"}}\n {{../definePrefix}}_onFileServed(\"{{../basePath}}/{{this.filename}}\", 200);\n httpd_resp_send(req, (const char *)data_{{this.dataname}}, {{this.length}});\n{{/case}}\n{{#case \"compiler\"}}\n {{../definePrefix}}_onFileServed(\"{{../basePath}}/{{this.filename}}\", 200);\n #ifdef {{../definePrefix}}_ENABLE_GZIP\n httpd_resp_send(req, (const char *)datagzip_{{this.dataname}}, {{this.lengthGzip}});\n #else\n httpd_resp_send(req, (const char *)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 = \"{{#if ../basePath}}{{../basePath}}{{else}}/{{/if}}\",\n .method = HTTP_GET,\n .handler = file_handler_{{this.datanameUpperCase}},\n};\n{{/if}}\n\nstatic const httpd_uri_t route_{{this.datanameUpperCase}} = {\n .uri = \"{{../basePath}}/{{this.filename}}\",\n .method = HTTP_GET,\n .handler = file_handler_{{this.datanameUpperCase}},\n};\n\n{{/each}}\n\n{{#if spa}}\n{{#with spaSource}}\nstatic esp_err_t spa_handler_{{this.datanameUpperCase}}(httpd_req_t *req, httpd_err_code_t err) {\n{{#if ../basePath}}\n const char* prefix = \"{{../basePath}}/\";\n if (strncmp(req->uri, prefix, strlen(prefix)) != 0 && strcmp(req->uri, \"{{../basePath}}\") != 0) {\n httpd_resp_send_err(req, HTTPD_404_NOT_FOUND, \"Not found\");\n return ESP_FAIL;\n }\n{{/if}}\n return file_handler_{{this.datanameUpperCase}}(req);\n}\n{{/with}}\n{{/if}}\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{{#if spa}}\n{{#with spaSource}}\n httpd_register_err_handler(server, HTTPD_404_NOT_FOUND, spa_handler_{{this.datanameUpperCase}});\n{{/with}}\n{{/if}}\n\n}";
|
package/dist/cppCodeEspIdf.js
CHANGED
|
@@ -63,22 +63,22 @@ exports.espidfTemplate = `
|
|
|
63
63
|
{{#switch gzip}}
|
|
64
64
|
{{#case "true"}}
|
|
65
65
|
{{#each sources}}
|
|
66
|
-
const unsigned char datagzip_{{this.dataname}}[{{this.lengthGzip}}] = { {{this.bytesGzip}} };
|
|
66
|
+
static const unsigned char datagzip_{{this.dataname}}[{{this.lengthGzip}}] = { {{this.bytesGzip}} };
|
|
67
67
|
{{/each}}
|
|
68
68
|
{{/case}}
|
|
69
69
|
{{#case "false"}}
|
|
70
70
|
{{#each sources}}
|
|
71
|
-
const unsigned char data_{{this.dataname}}[{{this.length}}] = { {{this.bytes}} };
|
|
71
|
+
static const unsigned char data_{{this.dataname}}[{{this.length}}] = { {{this.bytes}} };
|
|
72
72
|
{{/each}}
|
|
73
73
|
{{/case}}
|
|
74
74
|
{{#case "compiler"}}
|
|
75
75
|
#ifdef {{definePrefix}}_ENABLE_GZIP
|
|
76
76
|
{{#each sources}}
|
|
77
|
-
const unsigned char datagzip_{{this.dataname}}[{{this.lengthGzip}}] = { {{this.bytesGzip}} };
|
|
77
|
+
static const unsigned char datagzip_{{this.dataname}}[{{this.lengthGzip}}] = { {{this.bytesGzip}} };
|
|
78
78
|
{{/each}}
|
|
79
79
|
#else
|
|
80
80
|
{{#each sources}}
|
|
81
|
-
const unsigned char data_{{this.dataname}}[{{this.length}}] = { {{this.bytes}} };
|
|
81
|
+
static const unsigned char data_{{this.dataname}}[{{this.length}}] = { {{this.bytes}} };
|
|
82
82
|
{{/each}}
|
|
83
83
|
#endif
|
|
84
84
|
{{/case}}
|
package/dist/errorMessages.js
CHANGED
|
@@ -140,7 +140,7 @@ function getMaxUriHandlersHint(engine, routeCount, espmethod = 'initSvelteStatic
|
|
|
140
140
|
const recommended = routeCount + 5;
|
|
141
141
|
const hints = {
|
|
142
142
|
psychic: `PsychicHttpServer server;
|
|
143
|
-
server.config.max_uri_handlers =
|
|
143
|
+
server.config.max_uri_handlers = SVELTEESP32_MAX_URI_HANDLERS; // already defined in the header (${recommended})
|
|
144
144
|
${espmethod}(&server);
|
|
145
145
|
server.listen(80);`,
|
|
146
146
|
espidf: `httpd_config_t config = HTTPD_DEFAULT_CONFIG();
|
package/dist/index.d.ts
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
import { CppCodeSource, ExtensionGroups } from './cppCode';
|
|
1
|
+
import { CppCodeSource, CppCodeSources, ExtensionGroups } from './cppCode';
|
|
2
2
|
declare const shouldUseGzip: (originalSize: number, compressedSize: number) => boolean;
|
|
3
3
|
declare const calculateCompressionRatio: (originalSize: number, compressedSize: number) => number;
|
|
4
4
|
declare const formatCompressionLog: (filename: string, padding: string, originalSize: number, compressedSize: number, useGzip: boolean) => string;
|
|
5
5
|
declare const formatSize: (bytes: number) => string;
|
|
6
6
|
declare const createSourceEntry: (filename: string, dataname: string, content: Buffer, contentGzip: Buffer, mimeType: string, sha256: string, isGzip: boolean) => CppCodeSource;
|
|
7
7
|
declare const updateExtensionGroup: (filesByExtension: ExtensionGroups, extension: string) => void;
|
|
8
|
+
declare const formatDryRunRoutes: (sources: CppCodeSources, engine: "psychic" | "async" | "espidf" | "webserver", basePath: string, spa: boolean) => string;
|
|
8
9
|
export declare function main(): void;
|
|
9
|
-
export { calculateCompressionRatio, createSourceEntry, formatCompressionLog, formatSize, shouldUseGzip, updateExtensionGroup };
|
|
10
|
+
export { calculateCompressionRatio, createSourceEntry, formatCompressionLog, formatDryRunRoutes, formatSize, shouldUseGzip, updateExtensionGroup };
|
package/dist/index.js
CHANGED
|
@@ -3,7 +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.formatSize = exports.formatCompressionLog = exports.createSourceEntry = exports.calculateCompressionRatio = void 0;
|
|
6
|
+
exports.updateExtensionGroup = exports.shouldUseGzip = exports.formatSize = exports.formatDryRunRoutes = exports.formatCompressionLog = exports.createSourceEntry = exports.calculateCompressionRatio = void 0;
|
|
7
7
|
exports.main = main;
|
|
8
8
|
const node_fs_1 = require("node:fs");
|
|
9
9
|
const node_path_1 = __importDefault(require("node:path"));
|
|
@@ -54,6 +54,46 @@ const updateExtensionGroup = (filesByExtension, extension) => {
|
|
|
54
54
|
filesByExtension.push({ extension, count: 1 });
|
|
55
55
|
};
|
|
56
56
|
exports.updateExtensionGroup = updateExtensionGroup;
|
|
57
|
+
const sizeCellFor = (s) => {
|
|
58
|
+
const orig = formatSize(s.content.length);
|
|
59
|
+
if (s.isGzip)
|
|
60
|
+
return `${orig} → ${formatSize(s.contentGzip.length)}`;
|
|
61
|
+
return orig;
|
|
62
|
+
};
|
|
63
|
+
const formatDryRunRoutes = (sources, engine, basePath, spa) => {
|
|
64
|
+
if (sources.length === 0)
|
|
65
|
+
return ' (no files)';
|
|
66
|
+
const defaultSource = sources.find((s) => s.filename === 'index.html' || s.filename === 'index.htm');
|
|
67
|
+
const rows = [];
|
|
68
|
+
if (defaultSource)
|
|
69
|
+
rows.push({
|
|
70
|
+
url: basePath || '/',
|
|
71
|
+
mime: defaultSource.mime,
|
|
72
|
+
sizeCell: sizeCellFor(defaultSource),
|
|
73
|
+
tag: '[default]'
|
|
74
|
+
});
|
|
75
|
+
for (const source of sources)
|
|
76
|
+
rows.push({
|
|
77
|
+
url: `${basePath}/${source.filename}`,
|
|
78
|
+
mime: source.mime,
|
|
79
|
+
sizeCell: sizeCellFor(source),
|
|
80
|
+
tag: source.isGzip ? '' : '[no gzip]'
|
|
81
|
+
});
|
|
82
|
+
if (spa && defaultSource) {
|
|
83
|
+
const spaUrl = engine === 'psychic' && basePath ? `${basePath}/*` : '(SPA catch-all)';
|
|
84
|
+
rows.push({ url: spaUrl, mime: defaultSource.mime, sizeCell: '', tag: '[SPA catch-all → index.html]' });
|
|
85
|
+
}
|
|
86
|
+
const urlWidth = Math.max(...rows.map((r) => r.url.length));
|
|
87
|
+
const mimeWidth = Math.max(...rows.map((r) => r.mime.length));
|
|
88
|
+
const sizeWidth = Math.max(...rows.map((r) => r.sizeCell.length));
|
|
89
|
+
return rows
|
|
90
|
+
.map((r) => {
|
|
91
|
+
const tagPart = r.tag ? ` ${r.tag}` : '';
|
|
92
|
+
return ` GET ${r.url.padEnd(urlWidth)} ${r.mime.padEnd(mimeWidth)} ${r.sizeCell.padEnd(sizeWidth)}${tagPart}`.trimEnd();
|
|
93
|
+
})
|
|
94
|
+
.join('\n');
|
|
95
|
+
};
|
|
96
|
+
exports.formatDryRunRoutes = formatDryRunRoutes;
|
|
57
97
|
function main() {
|
|
58
98
|
const summary = {
|
|
59
99
|
filecount: 0,
|
|
@@ -106,8 +146,13 @@ function main() {
|
|
|
106
146
|
process.exit(1);
|
|
107
147
|
}
|
|
108
148
|
if (commandLine_1.cmdLine.dryRun) {
|
|
109
|
-
|
|
110
|
-
|
|
149
|
+
const baseLabel = commandLine_1.cmdLine.basePath || '(none)';
|
|
150
|
+
const spaLabel = commandLine_1.cmdLine.spa ? 'yes' : 'no';
|
|
151
|
+
console.log(`[DRY RUN] Engine: ${commandLine_1.cmdLine.engine} | ETag: ${commandLine_1.cmdLine.etag} | Gzip: ${commandLine_1.cmdLine.gzip} | Base: ${baseLabel} | SPA: ${spaLabel}`);
|
|
152
|
+
console.log(`[DRY RUN] ${summary.filecount} files, ${formatSize(summary.size)} → ${formatSize(summary.gzipsize)} gzip | would write to ${commandLine_1.cmdLine.outputfile}`);
|
|
153
|
+
console.log('');
|
|
154
|
+
console.log('[DRY RUN] Routes:');
|
|
155
|
+
console.log(formatDryRunRoutes(sources, commandLine_1.cmdLine.engine, commandLine_1.cmdLine.basePath, commandLine_1.cmdLine.spa ?? false));
|
|
111
156
|
return;
|
|
112
157
|
}
|
|
113
158
|
const cppFile = (0, cppCode_1.getCppCode)(sources, filesByExtension);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "svelteesp32",
|
|
3
|
-
"version": "2.2.
|
|
3
|
+
"version": "2.2.2",
|
|
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",
|
|
@@ -62,11 +62,11 @@
|
|
|
62
62
|
"@eslint/eslintrc": "^3.3.5",
|
|
63
63
|
"@eslint/js": "^10.0.1",
|
|
64
64
|
"@types/mime-types": "^3.0.1",
|
|
65
|
-
"@types/node": "^25.
|
|
65
|
+
"@types/node": "^25.5.0",
|
|
66
66
|
"@types/picomatch": "^4.0.2",
|
|
67
67
|
"@typescript-eslint/eslint-plugin": "^8.57.0",
|
|
68
68
|
"@typescript-eslint/parser": "^8.57.0",
|
|
69
|
-
"@vitest/coverage-v8": "^4.0
|
|
69
|
+
"@vitest/coverage-v8": "^4.1.0",
|
|
70
70
|
"eslint": "^10.0.3",
|
|
71
71
|
"eslint-config-prettier": "^10.1.8",
|
|
72
72
|
"eslint-plugin-simple-import-sort": "^12.1.1",
|
|
@@ -77,7 +77,7 @@
|
|
|
77
77
|
"ts-node": "^10.9.2",
|
|
78
78
|
"tsx": "^4.21.0",
|
|
79
79
|
"typescript": "^5.9.3",
|
|
80
|
-
"vitest": "^4.0
|
|
80
|
+
"vitest": "^4.1.0"
|
|
81
81
|
},
|
|
82
82
|
"dependencies": {
|
|
83
83
|
"handlebars": "^4.7.8",
|