svelteesp32 2.2.1 → 2.3.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 CHANGED
@@ -79,6 +79,8 @@ void setup() {
79
79
 
80
80
  ## What's New
81
81
 
82
+ - **v2.3.0** — `--cachetime-html` and `--cachetime-assets` for per-type cache control (e.g. `no-cache` for HTML, 1-year for content-hashed JS/CSS)
83
+ - **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)
82
84
  - **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]`)
83
85
  - **v2.2.0** — SPA routing catch-all (`--spa`) for client-side routers on all four engines
84
86
  - **v2.1.0** — New Arduino WebServer engine (`-e webserver`), dependency updates
@@ -291,7 +293,7 @@ void initSvelteStaticFiles(PsychicHttpServer * server) {
291
293
 
292
294
  **Recommendation:** For ESP32-only projects, use PsychicHttpServer V2 (`-e psychic`) for the fastest, most stable experience.
293
295
 
294
- **Note:** For PsychicHttp, configure `server.config.max_uri_handlers` to match your file count.
296
+ **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.
295
297
 
296
298
  ---
297
299
 
@@ -320,6 +322,22 @@ Fine-tune how browsers cache your content:
320
322
 
321
323
  - **Default:** `no-cache` — browsers always validate with server (ETag check)
322
324
  - **Long-term caching:** `--cachetime=86400` — cache for 24 hours without any server requests
325
+ - **Per-type caching:** Use `--cachetime-html` and `--cachetime-assets` independently
326
+
327
+ Vite and webpack produce content-hashed filenames for JS/CSS (e.g., `app.a1b2c3.js`). Those can be cached for up to a year because the hash changes with every build, but `index.html` must stay `no-cache` since it's the entry point that references them:
328
+
329
+ ```bash
330
+ npx svelteesp32 -e psychic -s ./dist -o ./output.h \
331
+ --etag=true --cachetime-html=0 --cachetime-assets=31536000
332
+ ```
333
+
334
+ This emits `Cache-Control: no-cache` for every `text/html` file and `Cache-Control: max-age=31536000` for all other assets in the same header, with no per-file configuration needed.
335
+
336
+ | Option | Applies to | Falls back to |
337
+ | -------------------- | -------------------------------- | -------------- |
338
+ | `--cachetime-html` | `text/html` only | `--cachetime` |
339
+ | `--cachetime-assets` | everything else | `--cachetime` |
340
+ | `--cachetime` | all files (when no override set) | `0` (no-cache) |
323
341
 
324
342
  ### Automatic Index Handling
325
343
 
@@ -343,7 +361,7 @@ npx svelteesp32 -s ./dist -o ./output.h --exclude="*.map"
343
361
  npx svelteesp32 -s ./dist -o ./output.h --exclude="*.map,*.md,test/**/*"
344
362
  ```
345
363
 
346
- **Default exclusions:** `.DS_Store`, `Thumbs.db`, `.git`, `.svn`, `*.swp`, `*~`, `.gitignore`, `.gitattributes`
364
+ No patterns are excluded by default specify everything you need explicitly.
347
365
 
348
366
  Build output shows exactly what's excluded:
349
367
 
@@ -446,26 +464,28 @@ Called for every response (200 = content served, 304 = cache hit).
446
464
 
447
465
  ## CLI Reference
448
466
 
449
- | Option | Description | Default |
450
- | ---------------- | --------------------------------------------------- | ----------------------- |
451
- | `-s` | Source folder with compiled web files | (required) |
452
- | `-e` | Web server engine (psychic/async/espidf/webserver) | `psychic` |
453
- | `-o` | Output header file path | `svelteesp32.h` |
454
- | `--etag` | ETag caching (true/false/compiler) | `false` |
455
- | `--gzip` | Gzip compression (true/false/compiler) | `true` |
456
- | `--exclude` | Exclude files by glob pattern | System files |
457
- | `--basepath` | URL prefix for all routes | (none) |
458
- | `--maxsize` | Max total uncompressed size (e.g., `400k`, `1m`) | (none) |
459
- | `--maxgzipsize` | Max total gzip size (e.g., `150k`, `500k`) | (none) |
460
- | `--cachetime` | Cache-Control max-age in seconds | `0` |
461
- | `--version` | Version string in header | (none) |
462
- | `--define` | C++ define prefix | `SVELTEESP32` |
463
- | `--espmethod` | Init function name | `initSvelteStaticFiles` |
464
- | `--config` | Custom RC file path | `.svelteesp32rc.json` |
465
- | `--dryrun` | Show route table + summary without writing output | `false` |
466
- | `--spa` | Serve index.html for unmatched routes (SPA routing) | `false` |
467
- | `--noindexcheck` | Skip index.html validation | `false` |
468
- | `-h` | Show help | |
467
+ | Option | Description | Default |
468
+ | -------------------- | ---------------------------------------------------- | ----------------------- |
469
+ | `-s` | Source folder with compiled web files | (required) |
470
+ | `-e` | Web server engine (psychic/async/espidf/webserver) | `psychic` |
471
+ | `-o` | Output header file path | `svelteesp32.h` |
472
+ | `--etag` | ETag caching (true/false/compiler) | `false` |
473
+ | `--gzip` | Gzip compression (true/false/compiler) | `true` |
474
+ | `--exclude` | Exclude files by glob pattern | (none) |
475
+ | `--basepath` | URL prefix for all routes | (none) |
476
+ | `--maxsize` | Max total uncompressed size (e.g., `400k`, `1m`) | (none) |
477
+ | `--maxgzipsize` | Max total gzip size (e.g., `150k`, `500k`) | (none) |
478
+ | `--cachetime` | Cache-Control max-age in seconds (all files) | `0` |
479
+ | `--cachetime-html` | max-age for HTML files (overrides `--cachetime`) | (unset) |
480
+ | `--cachetime-assets` | max-age for non-HTML files (overrides `--cachetime`) | (unset) |
481
+ | `--version` | Version string in header | (none) |
482
+ | `--define` | C++ define prefix | `SVELTEESP32` |
483
+ | `--espmethod` | Init function name | `initSvelteStaticFiles` |
484
+ | `--config` | Custom RC file path | `.svelteesp32rc.json` |
485
+ | `--dryrun` | Show route table + summary without writing output | `false` |
486
+ | `--spa` | Serve index.html for unmatched routes (SPA routing) | `false` |
487
+ | `--noindexcheck` | Skip index.html validation | `false` |
488
+ | `-h` | Show help | |
469
489
 
470
490
  ---
471
491
 
@@ -484,6 +504,9 @@ Store your settings in `.svelteesp32rc.json` for zero-argument builds:
484
504
  "basepath": "/ui",
485
505
  "maxsize": "400k",
486
506
  "maxgzipsize": "150k",
507
+ "cachetime": 0,
508
+ "cachetimehtml": 0,
509
+ "cachetimeassets": 31536000,
487
510
  "noindexcheck": false,
488
511
  "dryrun": false,
489
512
  "spa": false
@@ -7,6 +7,8 @@ interface ICopyFilesArguments {
7
7
  gzip: 'true' | 'false' | 'compiler';
8
8
  etag: 'true' | 'false' | 'compiler';
9
9
  cachetime: number;
10
+ cachetimeHtml?: number;
11
+ cachetimeAssets?: number;
10
12
  created: boolean;
11
13
  version: string;
12
14
  exclude: string[];
@@ -27,6 +29,8 @@ interface IRcFileConfig {
27
29
  gzip?: 'true' | 'false' | 'compiler';
28
30
  etag?: 'true' | 'false' | 'compiler';
29
31
  cachetime?: number;
32
+ cachetimehtml?: number;
33
+ cachetimeassets?: number;
30
34
  created?: boolean;
31
35
  version?: string;
32
36
  exclude?: string[];
@@ -34,6 +34,8 @@ Options:
34
34
  --espmethod <name> Name of generated method (default: "initSvelteStaticFiles")
35
35
  --define <prefix> Prefix of c++ defines (default: "SVELTEESP32")
36
36
  --cachetime <seconds> max-age cache time in seconds (default: 0)
37
+ --cachetime-html <sec> Cache-Control max-age for HTML files (overrides --cachetime)
38
+ --cachetime-assets <sec> Cache-Control max-age for non-HTML assets (overrides --cachetime)
37
39
  --exclude <pattern> Exclude files matching glob pattern (repeatable or comma-separated)
38
40
  Examples: --exclude="*.map" --exclude="test/**/*.ts"
39
41
  --basepath <path> URL prefix for all routes (e.g., "/ui") (default: "")
@@ -113,16 +115,6 @@ function parseSize(value, name) {
113
115
  throw new Error(`${name} must be a positive integer: ${value}`);
114
116
  return bytes;
115
117
  }
116
- const DEFAULT_EXCLUDE_PATTERNS = [
117
- '.DS_Store',
118
- 'Thumbs.db',
119
- '.git',
120
- '.svn',
121
- '*.swp',
122
- '*~',
123
- '.gitignore',
124
- '.gitattributes'
125
- ];
126
118
  function findRcFile(customConfigPath) {
127
119
  if (customConfigPath) {
128
120
  if ((0, node_fs_1.existsSync)(customConfigPath))
@@ -186,8 +178,7 @@ function hasNpmVariables(config) {
186
178
  checkStringForNpmVariable(config.define) ||
187
179
  checkStringForNpmVariable(config.version) ||
188
180
  checkStringForNpmVariable(config.basepath) ||
189
- (Array.isArray(config.exclude) && config.exclude.some((pattern) => checkStringForNpmVariable(pattern))) ||
190
- false);
181
+ (Array.isArray(config.exclude) && config.exclude.some((pattern) => checkStringForNpmVariable(pattern))));
191
182
  }
192
183
  function interpolateNpmVariables(config, rcFilePath) {
193
184
  if (!hasNpmVariables(config))
@@ -250,6 +241,20 @@ function loadRcFile(rcPath) {
250
241
  throw error;
251
242
  }
252
243
  }
244
+ function validateSizeOption(configObject, key) {
245
+ const value = configObject[key];
246
+ if (value === undefined)
247
+ return;
248
+ if (typeof value === 'string')
249
+ try {
250
+ configObject[key] = parseSize(value, key);
251
+ }
252
+ catch {
253
+ throw new TypeError(`Invalid ${key} in RC file: ${value} (must be a positive number with optional k/m suffix)`);
254
+ }
255
+ else if (typeof value !== 'number' || Number.isNaN(value) || value <= 0)
256
+ throw new TypeError(`Invalid ${key} in RC file: ${value} (must be a positive number)`);
257
+ }
253
258
  function validateRcConfig(config, rcPath) {
254
259
  if (typeof config !== 'object' || config === null)
255
260
  throw new Error(`RC file ${rcPath} must contain a JSON object`);
@@ -263,6 +268,8 @@ function validateRcConfig(config, rcPath) {
263
268
  'gzip',
264
269
  'etag',
265
270
  'cachetime',
271
+ 'cachetimehtml',
272
+ 'cachetimeassets',
266
273
  'created',
267
274
  'version',
268
275
  'exclude',
@@ -292,6 +299,18 @@ function validateRcConfig(config, rcPath) {
292
299
  if (configObject['cachetime'] < 0)
293
300
  throw new TypeError(`Invalid cachetime in RC file: ${configObject['cachetime']} (must be non-negative)`);
294
301
  }
302
+ if (configObject['cachetimehtml'] !== undefined) {
303
+ if (typeof configObject['cachetimehtml'] !== 'number' || Number.isNaN(configObject['cachetimehtml']))
304
+ throw new TypeError(`Invalid cachetimehtml in RC file: ${configObject['cachetimehtml']}`);
305
+ if (configObject['cachetimehtml'] < 0)
306
+ throw new TypeError(`Invalid cachetimehtml in RC file: ${configObject['cachetimehtml']} (must be non-negative)`);
307
+ }
308
+ if (configObject['cachetimeassets'] !== undefined) {
309
+ if (typeof configObject['cachetimeassets'] !== 'number' || Number.isNaN(configObject['cachetimeassets']))
310
+ throw new TypeError(`Invalid cachetimeassets in RC file: ${configObject['cachetimeassets']}`);
311
+ if (configObject['cachetimeassets'] < 0)
312
+ throw new TypeError(`Invalid cachetimeassets in RC file: ${configObject['cachetimeassets']} (must be non-negative)`);
313
+ }
295
314
  if (configObject['exclude'] !== undefined) {
296
315
  if (!Array.isArray(configObject['exclude']))
297
316
  throw new TypeError("'exclude' in RC file must be an array");
@@ -299,30 +318,8 @@ function validateRcConfig(config, rcPath) {
299
318
  if (typeof pattern !== 'string')
300
319
  throw new TypeError('All exclude patterns must be strings');
301
320
  }
302
- if (configObject['maxsize'] !== undefined) {
303
- const value = configObject['maxsize'];
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
- }
321
+ validateSizeOption(configObject, 'maxsize');
322
+ validateSizeOption(configObject, 'maxgzipsize');
326
323
  if (configObject['noindexcheck'] !== undefined && typeof configObject['noindexcheck'] !== 'boolean')
327
324
  throw new TypeError(`Invalid noindexcheck in RC file: ${configObject['noindexcheck']} (must be boolean)`);
328
325
  if (configObject['dryrun'] !== undefined && typeof configObject['dryrun'] !== 'boolean')
@@ -361,7 +358,7 @@ function parseArguments() {
361
358
  espmethod: 'initSvelteStaticFiles',
362
359
  define: 'SVELTEESP32',
363
360
  cachetime: 0,
364
- exclude: [...DEFAULT_EXCLUDE_PATTERNS],
361
+ exclude: [],
365
362
  basePath: ''
366
363
  };
367
364
  if (rcConfig.engine)
@@ -376,6 +373,10 @@ function parseArguments() {
376
373
  result.gzip = rcConfig.gzip;
377
374
  if (rcConfig.cachetime !== undefined)
378
375
  result.cachetime = rcConfig.cachetime;
376
+ if (rcConfig.cachetimehtml !== undefined)
377
+ result.cachetimeHtml = rcConfig.cachetimehtml;
378
+ if (rcConfig.cachetimeassets !== undefined)
379
+ result.cachetimeAssets = rcConfig.cachetimeassets;
379
380
  if (rcConfig.created !== undefined)
380
381
  result.created = rcConfig.created;
381
382
  if (rcConfig.version)
@@ -434,6 +435,22 @@ function parseArguments() {
434
435
  if (result.cachetime < 0)
435
436
  throw new TypeError(`Invalid cachetime: ${value} (must be non-negative)`);
436
437
  break;
438
+ case 'cachetime-html': {
439
+ result.cachetimeHtml = Number.parseInt(value, 10);
440
+ if (Number.isNaN(result.cachetimeHtml))
441
+ throw new TypeError(`Invalid cachetime-html: ${value}`);
442
+ if (result.cachetimeHtml < 0)
443
+ throw new TypeError(`Invalid cachetime-html: ${value} (must be non-negative)`);
444
+ break;
445
+ }
446
+ case 'cachetime-assets': {
447
+ result.cachetimeAssets = Number.parseInt(value, 10);
448
+ if (Number.isNaN(result.cachetimeAssets))
449
+ throw new TypeError(`Invalid cachetime-assets: ${value}`);
450
+ if (result.cachetimeAssets < 0)
451
+ throw new TypeError(`Invalid cachetime-assets: ${value} (must be non-negative)`);
452
+ break;
453
+ }
437
454
  case 'exclude': {
438
455
  const patterns = value
439
456
  .split(',')
@@ -537,6 +554,10 @@ function formatConfiguration(cmdLine) {
537
554
  `gzip=${cmdLine.gzip}`,
538
555
  `cachetime=${cmdLine.cachetime}`
539
556
  ];
557
+ if (cmdLine.cachetimeHtml !== undefined)
558
+ parts.push(`cachetimeHtml=${cmdLine.cachetimeHtml}`);
559
+ if (cmdLine.cachetimeAssets !== undefined)
560
+ parts.push(`cachetimeAssets=${cmdLine.cachetimeAssets}`);
540
561
  if (cmdLine.created)
541
562
  parts.push(`created=${cmdLine.created}`);
542
563
  if (cmdLine.version)
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 * etag_{{this.dataname}} = "{{this.sha256}}";
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 * etag_{{this.dataname}} = "{{this.sha256}}";
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
@@ -189,22 +192,22 @@ void {{methodName}}(PsychicHttpServer * server) {
189
192
 
190
193
  {{#switch ../etag}}
191
194
  {{#case "true"}}
192
- {{#../cacheTime}}
195
+ {{#this.cacheTime}}
193
196
  response->addHeader("Cache-Control", "max-age={{value}}");
194
- {{/../cacheTime}}
195
- {{^../cacheTime}}
197
+ {{/this.cacheTime}}
198
+ {{^this.cacheTime}}
196
199
  response->addHeader("Cache-Control", "no-cache");
197
- {{/../cacheTime}}
200
+ {{/this.cacheTime}}
198
201
  response->addHeader("ETag", etag_{{this.dataname}});
199
202
  {{/case}}
200
203
  {{#case "compiler"}}
201
204
  #ifdef {{../definePrefix}}_ENABLE_ETAG
202
- {{#../cacheTime}}
205
+ {{#this.cacheTime}}
203
206
  response->addHeader("Cache-Control", "max-age={{value}}");
204
- {{/../cacheTime}}
205
- {{^../cacheTime}}
207
+ {{/this.cacheTime}}
208
+ {{^this.cacheTime}}
206
209
  response->addHeader("Cache-Control", "no-cache");
207
- {{/../cacheTime}}
210
+ {{/this.cacheTime}}
208
211
  response->addHeader("ETag", etag_{{this.dataname}});
209
212
  #endif
210
213
  {{/case}}
@@ -272,22 +275,22 @@ void {{methodName}}(PsychicHttpServer * server) {
272
275
 
273
276
  {{#switch ../etag}}
274
277
  {{#case "true"}}
275
- {{#../cacheTime}}
278
+ {{#this.cacheTime}}
276
279
  response->addHeader("Cache-Control", "max-age={{value}}");
277
- {{/../cacheTime}}
278
- {{^../cacheTime}}
280
+ {{/this.cacheTime}}
281
+ {{^this.cacheTime}}
279
282
  response->addHeader("Cache-Control", "no-cache");
280
- {{/../cacheTime}}
283
+ {{/this.cacheTime}}
281
284
  response->addHeader("ETag", etag_{{this.dataname}});
282
285
  {{/case}}
283
286
  {{#case "compiler"}}
284
287
  #ifdef {{../definePrefix}}_ENABLE_ETAG
285
- {{#../cacheTime}}
288
+ {{#this.cacheTime}}
286
289
  response->addHeader("Cache-Control", "max-age={{value}}");
287
- {{/../cacheTime}}
288
- {{^../cacheTime}}
290
+ {{/this.cacheTime}}
291
+ {{^this.cacheTime}}
289
292
  response->addHeader("Cache-Control", "no-cache");
290
- {{/../cacheTime}}
293
+ {{/this.cacheTime}}
291
294
  response->addHeader("ETag", etag_{{this.dataname}});
292
295
  #endif
293
296
  {{/case}}
@@ -360,22 +363,22 @@ void {{methodName}}(PsychicHttpServer * server) {
360
363
 
361
364
  {{#switch ../etag}}
362
365
  {{#case "true"}}
363
- {{#../cacheTime}}
366
+ {{#this.cacheTime}}
364
367
  response->addHeader("Cache-Control", "max-age={{value}}");
365
- {{/../cacheTime}}
366
- {{^../cacheTime}}
368
+ {{/this.cacheTime}}
369
+ {{^this.cacheTime}}
367
370
  response->addHeader("Cache-Control", "no-cache");
368
- {{/../cacheTime}}
371
+ {{/this.cacheTime}}
369
372
  response->addHeader("ETag", etag_{{this.dataname}});
370
373
  {{/case}}
371
374
  {{#case "compiler"}}
372
375
  #ifdef {{../definePrefix}}_ENABLE_ETAG
373
- {{#../cacheTime}}
376
+ {{#this.cacheTime}}
374
377
  response->addHeader("Cache-Control", "max-age={{value}}");
375
- {{/../cacheTime}}
376
- {{^../cacheTime}}
378
+ {{/this.cacheTime}}
379
+ {{^this.cacheTime}}
377
380
  response->addHeader("Cache-Control", "no-cache");
378
- {{/../cacheTime}}
381
+ {{/this.cacheTime}}
379
382
  response->addHeader("ETag", etag_{{this.dataname}});
380
383
  #endif
381
384
  {{/case}}
@@ -482,22 +485,22 @@ void {{methodName}}(AsyncWebServer * server) {
482
485
 
483
486
  {{#switch ../etag}}
484
487
  {{#case "true"}}
485
- {{#../cacheTime}}
488
+ {{#this.cacheTime}}
486
489
  response->addHeader("Cache-Control", "max-age={{value}}");
487
- {{/../cacheTime}}
488
- {{^../cacheTime}}
490
+ {{/this.cacheTime}}
491
+ {{^this.cacheTime}}
489
492
  response->addHeader("Cache-Control", "no-cache");
490
- {{/../cacheTime}}
493
+ {{/this.cacheTime}}
491
494
  response->addHeader("ETag", etag_{{this.dataname}});
492
495
  {{/case}}
493
496
  {{#case "compiler"}}
494
497
  #ifdef {{../definePrefix}}_ENABLE_ETAG
495
- {{#../cacheTime}}
498
+ {{#this.cacheTime}}
496
499
  response->addHeader("Cache-Control", "max-age={{value}}");
497
- {{/../cacheTime}}
498
- {{^../cacheTime}}
500
+ {{/this.cacheTime}}
501
+ {{^this.cacheTime}}
499
502
  response->addHeader("Cache-Control", "no-cache");
500
- {{/../cacheTime}}
503
+ {{/this.cacheTime}}
501
504
  response->addHeader("ETag", etag_{{this.dataname}});
502
505
  #endif
503
506
  {{/case}}
@@ -554,22 +557,22 @@ void {{methodName}}(AsyncWebServer * server) {
554
557
 
555
558
  {{#switch ../etag}}
556
559
  {{#case "true"}}
557
- {{#../cacheTime}}
560
+ {{#this.cacheTime}}
558
561
  response->addHeader("Cache-Control", "max-age={{value}}");
559
- {{/../cacheTime}}
560
- {{^../cacheTime}}
562
+ {{/this.cacheTime}}
563
+ {{^this.cacheTime}}
561
564
  response->addHeader("Cache-Control", "no-cache");
562
- {{/../cacheTime}}
565
+ {{/this.cacheTime}}
563
566
  response->addHeader("ETag", etag_{{this.dataname}});
564
567
  {{/case}}
565
568
  {{#case "compiler"}}
566
569
  #ifdef {{../definePrefix}}_ENABLE_ETAG
567
- {{#../cacheTime}}
570
+ {{#this.cacheTime}}
568
571
  response->addHeader("Cache-Control", "max-age={{value}}");
569
- {{/../cacheTime}}
570
- {{^../cacheTime}}
572
+ {{/this.cacheTime}}
573
+ {{^this.cacheTime}}
571
574
  response->addHeader("Cache-Control", "no-cache");
572
- {{/../cacheTime}}
575
+ {{/this.cacheTime}}
573
576
  response->addHeader("ETag", etag_{{this.dataname}});
574
577
  #endif
575
578
  {{/case}}
@@ -636,22 +639,22 @@ void {{methodName}}(AsyncWebServer * server) {
636
639
 
637
640
  {{#switch ../etag}}
638
641
  {{#case "true"}}
639
- {{#../cacheTime}}
642
+ {{#this.cacheTime}}
640
643
  response->addHeader("Cache-Control", "max-age={{value}}");
641
- {{/../cacheTime}}
642
- {{^../cacheTime}}
644
+ {{/this.cacheTime}}
645
+ {{^this.cacheTime}}
643
646
  response->addHeader("Cache-Control", "no-cache");
644
- {{/../cacheTime}}
647
+ {{/this.cacheTime}}
645
648
  response->addHeader("ETag", etag_{{this.dataname}});
646
649
  {{/case}}
647
650
  {{#case "compiler"}}
648
651
  #ifdef {{../definePrefix}}_ENABLE_ETAG
649
- {{#../cacheTime}}
652
+ {{#this.cacheTime}}
650
653
  response->addHeader("Cache-Control", "max-age={{value}}");
651
- {{/../cacheTime}}
652
- {{^../cacheTime}}
654
+ {{/this.cacheTime}}
655
+ {{^this.cacheTime}}
653
656
  response->addHeader("Cache-Control", "no-cache");
654
- {{/../cacheTime}}
657
+ {{/this.cacheTime}}
655
658
  response->addHeader("ETag", etag_{{this.dataname}});
656
659
  #endif
657
660
  {{/case}}
@@ -728,22 +731,22 @@ void {{methodName}}(WebServer * server) {
728
731
 
729
732
  {{#switch ../etag}}
730
733
  {{#case "true"}}
731
- {{#../cacheTime}}
734
+ {{#this.cacheTime}}
732
735
  server->sendHeader("Cache-Control", "max-age={{value}}");
733
- {{/../cacheTime}}
734
- {{^../cacheTime}}
736
+ {{/this.cacheTime}}
737
+ {{^this.cacheTime}}
735
738
  server->sendHeader("Cache-Control", "no-cache");
736
- {{/../cacheTime}}
739
+ {{/this.cacheTime}}
737
740
  server->sendHeader("ETag", etag_{{this.dataname}});
738
741
  {{/case}}
739
742
  {{#case "compiler"}}
740
743
  #ifdef {{../definePrefix}}_ENABLE_ETAG
741
- {{#../cacheTime}}
744
+ {{#this.cacheTime}}
742
745
  server->sendHeader("Cache-Control", "max-age={{value}}");
743
- {{/../cacheTime}}
744
- {{^../cacheTime}}
746
+ {{/this.cacheTime}}
747
+ {{^this.cacheTime}}
745
748
  server->sendHeader("Cache-Control", "no-cache");
746
- {{/../cacheTime}}
749
+ {{/this.cacheTime}}
747
750
  server->sendHeader("ETag", etag_{{this.dataname}});
748
751
  #endif
749
752
  {{/case}}
@@ -805,22 +808,22 @@ void {{methodName}}(WebServer * server) {
805
808
 
806
809
  {{#switch ../etag}}
807
810
  {{#case "true"}}
808
- {{#../cacheTime}}
811
+ {{#this.cacheTime}}
809
812
  server->sendHeader("Cache-Control", "max-age={{value}}");
810
- {{/../cacheTime}}
811
- {{^../cacheTime}}
813
+ {{/this.cacheTime}}
814
+ {{^this.cacheTime}}
812
815
  server->sendHeader("Cache-Control", "no-cache");
813
- {{/../cacheTime}}
816
+ {{/this.cacheTime}}
814
817
  server->sendHeader("ETag", etag_{{this.dataname}});
815
818
  {{/case}}
816
819
  {{#case "compiler"}}
817
820
  #ifdef {{../definePrefix}}_ENABLE_ETAG
818
- {{#../cacheTime}}
821
+ {{#this.cacheTime}}
819
822
  server->sendHeader("Cache-Control", "max-age={{value}}");
820
- {{/../cacheTime}}
821
- {{^../cacheTime}}
823
+ {{/this.cacheTime}}
824
+ {{^this.cacheTime}}
822
825
  server->sendHeader("Cache-Control", "no-cache");
823
- {{/../cacheTime}}
826
+ {{/this.cacheTime}}
824
827
  server->sendHeader("ETag", etag_{{this.dataname}});
825
828
  #endif
826
829
  {{/case}}
@@ -892,22 +895,22 @@ void {{methodName}}(WebServer * server) {
892
895
 
893
896
  {{#switch ../etag}}
894
897
  {{#case "true"}}
895
- {{#../cacheTime}}
898
+ {{#this.cacheTime}}
896
899
  server->sendHeader("Cache-Control", "max-age={{value}}");
897
- {{/../cacheTime}}
898
- {{^../cacheTime}}
900
+ {{/this.cacheTime}}
901
+ {{^this.cacheTime}}
899
902
  server->sendHeader("Cache-Control", "no-cache");
900
- {{/../cacheTime}}
903
+ {{/this.cacheTime}}
901
904
  server->sendHeader("ETag", etag_{{this.dataname}});
902
905
  {{/case}}
903
906
  {{#case "compiler"}}
904
907
  #ifdef {{../definePrefix}}_ENABLE_ETAG
905
- {{#../cacheTime}}
908
+ {{#this.cacheTime}}
906
909
  server->sendHeader("Cache-Control", "max-age={{value}}");
907
- {{/../cacheTime}}
908
- {{^../cacheTime}}
910
+ {{/this.cacheTime}}
911
+ {{^this.cacheTime}}
909
912
  server->sendHeader("Cache-Control", "no-cache");
910
- {{/../cacheTime}}
913
+ {{/this.cacheTime}}
911
914
  server->sendHeader("ETag", etag_{{this.dataname}});
912
915
  #endif
913
916
  {{/case}}
@@ -970,7 +973,7 @@ const bufferToByteString = (buffer) => {
970
973
  result += ',' + buffer[index].toString(10);
971
974
  return result;
972
975
  };
973
- const transformSourceToTemplateData = (s, etag) => ({
976
+ const transformSourceToTemplateData = (s, etag, effectiveCacheTime) => ({
974
977
  ...s,
975
978
  length: s.content.length,
976
979
  bytes: bufferToByteString(s.content),
@@ -978,7 +981,8 @@ const transformSourceToTemplateData = (s, etag) => ({
978
981
  bytesGzip: bufferToByteString(s.contentGzip),
979
982
  isDefault: s.filename === 'index.html' || s.filename === 'index.htm',
980
983
  gzipSizeForManifest: s.isGzip ? s.contentGzip.length : 0,
981
- etagForManifest: etag === 'false' ? 'NULL' : `etag_${s.dataname}`
984
+ etagForManifest: etag === 'false' ? 'NULL' : `etag_${s.dataname}`,
985
+ cacheTime: effectiveCacheTime ? { value: effectiveCacheTime } : undefined
982
986
  });
983
987
  const postProcessCppCode = (code) => code
984
988
  .split('\n')
@@ -1008,7 +1012,12 @@ const createHandlebarsHelpers = () => {
1008
1012
  };
1009
1013
  const getCppCode = (sources, filesByExtension) => {
1010
1014
  const template = (0, handlebars_1.compile)(getTemplate(commandLine_1.cmdLine.engine));
1011
- const transformedSources = sources.map((s) => transformSourceToTemplateData(s, commandLine_1.cmdLine.etag));
1015
+ const transformedSources = sources.map((s) => {
1016
+ const effectiveCacheTime = s.mime === 'text/html'
1017
+ ? (commandLine_1.cmdLine.cachetimeHtml ?? commandLine_1.cmdLine.cachetime)
1018
+ : (commandLine_1.cmdLine.cachetimeAssets ?? commandLine_1.cmdLine.cachetime);
1019
+ return transformSourceToTemplateData(s, commandLine_1.cmdLine.etag, effectiveCacheTime);
1020
+ });
1012
1021
  const spaSource = commandLine_1.cmdLine.spa ? transformedSources.find((s) => s.isDefault) : undefined;
1013
1022
  const templateData = {
1014
1023
  config: (0, commandLine_1.formatConfiguration)(commandLine_1.cmdLine),
@@ -1026,11 +1035,12 @@ const getCppCode = (sources, filesByExtension) => {
1026
1035
  created: commandLine_1.cmdLine.created,
1027
1036
  version: commandLine_1.cmdLine.version,
1028
1037
  methodName: commandLine_1.cmdLine.espmethod,
1029
- cacheTime: commandLine_1.cmdLine.cachetime ? { value: commandLine_1.cmdLine.cachetime } : undefined,
1030
1038
  definePrefix: commandLine_1.cmdLine.define,
1031
1039
  basePath: commandLine_1.cmdLine.basePath,
1032
1040
  spa: !!commandLine_1.cmdLine.spa,
1033
- spaSource
1041
+ spaSource,
1042
+ isPsychic: commandLine_1.cmdLine.engine === 'psychic',
1043
+ maxUriHandlers: (sources.length + 5).toString()
1034
1044
  };
1035
1045
  const rawCode = template(templateData, { helpers: createHandlebarsHelpers() });
1036
1046
  return postProcessCppCode(rawCode);
@@ -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 unsigned char datagzip_{{this.dataname}}[{{this.lengthGzip}}] = { {{this.bytesGzip}} };\n {{/each}}\n{{/case}}\n{{#case \"false\"}}\n {{#each sources}}\nconst unsigned char data_{{this.dataname}}[{{this.length}}] = { {{this.bytes}} };\n {{/each}}\n{{/case}}\n{{#case \"compiler\"}}\n#ifdef {{definePrefix}}_ENABLE_GZIP\n {{#each sources}}\nconst unsigned char datagzip_{{this.dataname}}[{{this.lengthGzip}}] = { {{this.bytesGzip}} };\n {{/each}}\n#else\n {{#each sources}}\nconst 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}";
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{{#this.cacheTime}}\n httpd_resp_set_hdr(req, \"Cache-Control\", \"max-age={{value}}\");\n{{/this.cacheTime}}\n{{^this.cacheTime}}\n httpd_resp_set_hdr(req, \"Cache-Control\", \"no-cache\");\n{{/this.cacheTime}}\n httpd_resp_set_hdr(req, \"ETag\", etag_{{this.dataname}});\n{{/case}}\n{{#case \"compiler\"}}\n #ifdef {{../definePrefix}}_ENABLE_ETAG\n{{#this.cacheTime}}\n httpd_resp_set_hdr(req, \"Cache-Control\", \"max-age={{value}}\");\n{{/this.cacheTime}}\n{{^this.cacheTime}}\n httpd_resp_set_hdr(req, \"Cache-Control\", \"no-cache\");\n{{/this.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}";
@@ -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}}
@@ -182,22 +182,22 @@ static esp_err_t file_handler_{{this.datanameUpperCase}} (httpd_req_t *req)
182
182
 
183
183
  {{#switch ../etag}}
184
184
  {{#case "true"}}
185
- {{#../cacheTime}}
185
+ {{#this.cacheTime}}
186
186
  httpd_resp_set_hdr(req, "Cache-Control", "max-age={{value}}");
187
- {{/../cacheTime}}
188
- {{^../cacheTime}}
187
+ {{/this.cacheTime}}
188
+ {{^this.cacheTime}}
189
189
  httpd_resp_set_hdr(req, "Cache-Control", "no-cache");
190
- {{/../cacheTime}}
190
+ {{/this.cacheTime}}
191
191
  httpd_resp_set_hdr(req, "ETag", etag_{{this.dataname}});
192
192
  {{/case}}
193
193
  {{#case "compiler"}}
194
194
  #ifdef {{../definePrefix}}_ENABLE_ETAG
195
- {{#../cacheTime}}
195
+ {{#this.cacheTime}}
196
196
  httpd_resp_set_hdr(req, "Cache-Control", "max-age={{value}}");
197
- {{/../cacheTime}}
198
- {{^../cacheTime}}
197
+ {{/this.cacheTime}}
198
+ {{^this.cacheTime}}
199
199
  httpd_resp_set_hdr(req, "Cache-Control", "no-cache");
200
- {{/../cacheTime}}
200
+ {{/this.cacheTime}}
201
201
  httpd_resp_set_hdr(req, "ETag", etag_{{this.dataname}});
202
202
  #endif
203
203
  {{/case}}
@@ -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 = ${recommended}; // Default is 8, you need at least ${routeCount}
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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "svelteesp32",
3
- "version": "2.2.1",
3
+ "version": "2.3.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",