svelteesp32 1.9.1 → 1.9.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 CHANGED
@@ -18,7 +18,7 @@ This npm package provides a solution for **inserting any JS client application i
18
18
 
19
19
  > Starting with version v1.7.0, with the cachetime command line option, you can set whether the browser can cache pages
20
20
 
21
- > Starting with version v1.6.0, mime npm package is used instead of mime-types (application/javascript -> text/javascript)
21
+ > Starting with version v1.6.0, mime-types package properly handles MIME types (application/javascript -> text/javascript)
22
22
 
23
23
  > Starting with version v1.5.0, PsychicHttp v2 is also supported.
24
24
 
@@ -30,6 +30,11 @@ This npm package provides a solution for **inserting any JS client application i
30
30
 
31
31
  > Starting with version v1.1.0, the ETag header is also supported.
32
32
 
33
+ ### Requirements
34
+
35
+ - Node.js >= 20
36
+ - npm >= 9
37
+
33
38
  ### Usage
34
39
 
35
40
  **Install package** as devDependency (it is practical if the package is part of the project so that you always receive updates)
@@ -54,19 +59,25 @@ npx svelteesp32 -e async -s ../svelteapp/dist -o ../esp32project/svelteesp32.h -
54
59
  npx svelteesp32 -e espidf -s ../svelteapp/dist -o ../esp32project/svelteesp32.h --etag=true
55
60
  ```
56
61
 
57
- During the **translation process**, the processed file details are visible, and at the end, the result shows the ESP's memory allocation (gzip size)
62
+ During the **translation process**, the processed file details are visible with compression ratios, and at the end, the result shows the ESP's memory allocation (gzip size)
58
63
 
59
64
  ```
60
- [assets/index-KwubEIf-.js] ✓ gzip used (38850 -> 12547)
61
- [assets/index-Soe6cpLA.css] ✓ gzip used (32494 -> 5368)
62
- [favicon.png] x gzip unused (33249 -> 33282)
63
- [index.html] x gzip unused (too small) (472 -> 308)
64
- [roboto_regular.json] ✓ gzip used (363757 -> 93567)
65
+ [assets/index-KwubEIf-.js] ✓ gzip used (38850 -> 12547 = 32%)
66
+ [assets/index-Soe6cpLA.css] ✓ gzip used (32494 -> 5368 = 17%)
67
+ [favicon.png] x gzip unused (33249 -> 33282 = 100%)
68
+ [index.html] x gzip unused (too small) (472 -> 308 = 65%)
69
+ [roboto_regular.json] ✓ gzip used (363757 -> 93567 = 26%)
65
70
 
66
71
  5 files, 458kB original size, 142kB gzip size
67
72
  ../../../Arduino/EspSvelte/svelteesp32.h 842kB size
68
73
  ```
69
74
 
75
+ The tool automatically:
76
+
77
+ - Compresses files with gzip level 9 when beneficial (>1024 bytes and >15% reduction)
78
+ - Detects and reports duplicate files using SHA256 hashing
79
+ - Skips pre-compressed files (.gz, .br, .brottli) if the original exists
80
+
70
81
  **Include svelteesp32.h** into your Arduino or PlatformIO c++ project (copy it next to the main c++ file)
71
82
 
72
83
  ```c
@@ -111,59 +122,107 @@ void setup()
111
122
  }
112
123
  ```
113
124
 
114
- You can find a minimal buildable example platformio project in [demo/esp32](demo/esp32) folder.
125
+ or for ESP-IDF native:
126
+
127
+ ```c
128
+ ...
129
+
130
+ #include <esp_http_server.h>
131
+ #include "svelteesp32.h"
132
+
133
+ httpd_handle_t server = NULL;
134
+
135
+ ...
136
+
137
+ void app_main()
138
+ {
139
+ ...
115
140
 
116
- The content of **generated file** (do not edit, just use)
141
+ httpd_config_t config = HTTPD_DEFAULT_CONFIG();
142
+ httpd_start(&server, &config);
143
+
144
+ initSvelteStaticFiles(server);
145
+ }
146
+ ```
147
+
148
+ You can find minimal buildable example projects in [demo/esp32](demo/esp32) (Arduino/PlatformIO) and [demo/esp32idf](demo/esp32idf) (ESP-IDF native) folders.
149
+
150
+ The content of **generated file** (do not edit, just use):
117
151
 
118
152
  ```c
153
+ //engine: PsychicHttpServer
154
+ //cmdline: -e psychic -s ./dist -o ./output.h --etag=true --gzip=true
155
+ //
119
156
  #define SVELTEESP32_COUNT 5
120
- #define SVELTEESP32_SIZE 145633
157
+ #define SVELTEESP32_SIZE 468822
158
+ #define SVELTEESP32_SIZE_GZIP 145633
121
159
  #define SVELTEESP32_FILE_INDEX_HTML
122
160
  #define SVELTEESP32_HTML_FILES 1
161
+ #define SVELTEESP32_CSS_FILES 1
162
+ #define SVELTEESP32_JS_FILES 1
123
163
  ...
124
164
 
125
- const uint8_t data0[12547] = {0x1f, 0x8b, 0x8, 0x0, ...
126
- const uint8_t data1[5368] = {0x1f, 0x8b, 0x8, 0x0, 0x0, ...
127
- const char * etag0 = "387b88e345cc56ef9091...";
165
+ #include <Arduino.h>
166
+ #include <PsychicHttp.h>
167
+ #include <PsychicHttpsServer.h>
168
+
169
+ const uint8_t datagzip_assets_index_KwubEIf__js[12547] = {0x1f, 0x8b, 0x8, 0x0, ...
170
+ const uint8_t datagzip_assets_index_Soe6cpLA_css[5368] = {0x1f, 0x8b, 0x8, 0x0, 0x0, ...
171
+ const char * etag_assets_index_KwubEIf__js = "387b88e345cc56ef9091...";
172
+ const char * etag_assets_index_Soe6cpLA_css = "d4f23bc45ef67890ab12...";
173
+ ...
128
174
 
129
175
  void initSvelteStaticFiles(PsychicHttpServer * server) {
130
- server->on("/assets/index-KwubEIf-.js", HTTP_GET, [](PsychicRequest * request)
131
- {
132
- if (request->hasHeader("If-None-Match") && ...) {
176
+ server->on("/assets/index-KwubEIf-.js", HTTP_GET, [](PsychicRequest * request) {
177
+ if (request->hasHeader("If-None-Match") &&
178
+ request->header("If-None-Match") == String(etag_assets_index_KwubEIf__js)) {
133
179
  PsychicResponse response304(request);
134
180
  response304.setCode(304);
135
181
  return response304.send();
136
182
  }
137
183
 
138
184
  PsychicResponse response(request);
139
- response.setContentType("application/javascript");
185
+ response.setContentType("text/javascript");
140
186
  response.addHeader("Content-Encoding", "gzip");
141
- response.setContent(data0, sizeof(data0));
187
+ response.addHeader("Cache-Control", "no-cache");
188
+ response.addHeader("ETag", etag_assets_index_KwubEIf__js);
189
+ response.setContent(datagzip_assets_index_KwubEIf__js, 12547);
142
190
  return response.send();
143
191
  });
144
192
 
145
- server->on("/assets/index-Soe6cpLA.css", HTTP_GET, [](PsychicRequest * request)
146
- {
147
- if (request->hasHeader("If-None-Match") && ...) {
148
- ...
193
+ server->on("/assets/index-Soe6cpLA.css", HTTP_GET, [](PsychicRequest * request) {
194
+ if (request->hasHeader("If-None-Match") &&
195
+ request->header("If-None-Match") == String(etag_assets_index_Soe6cpLA_css)) {
196
+ PsychicResponse response304(request);
197
+ response304.setCode(304);
198
+ return response304.send();
149
199
  }
150
200
 
151
201
  PsychicResponse response(request);
152
202
  response.setContentType("text/css");
153
203
  response.addHeader("Content-Encoding", "gzip");
154
- response.setContent(data1, sizeof(data1));
204
+ response.addHeader("Cache-Control", "no-cache");
205
+ response.addHeader("ETag", etag_assets_index_Soe6cpLA_css);
206
+ response.setContent(datagzip_assets_index_Soe6cpLA_css, 5368);
155
207
  return response.send();
156
208
  });
157
209
 
158
- ...
210
+ // ... more routes
159
211
  }
160
212
  ```
161
213
 
162
214
  ### Engines and ESP variants
163
215
 
164
- ESPAsyncWebServer (current location https://github.com/ESP32Async/ESPAsyncWebServer) is a popular web server. When you want to generate a file for this, use the `-e async` switch.
216
+ Four web server engines are supported:
217
+
218
+ - **`-e psychic`** (default): PsychicHttpServer V1 - Fast ESP32-only server using ESP-IDF internally. Recommended for ESP32 projects.
219
+ - **`-e psychic2`**: PsychicHttpServer V2 - Updated version with improved API
220
+ - **`-e async`**: ESPAsyncWebServer (https://github.com/ESP32Async/ESPAsyncWebServer) - Popular async server supporting both ESP32 and ESP8266/ESP8285
221
+ - **`-e espidf`**: Native ESP-IDF web server - For projects using ESP-IDF framework directly (not Arduino)
222
+
223
+ If you **only work on ESP32**, we recommend using PsychicHttpServer (`-e psychic` or `-e psychic2`), which uses the native ESP-IDF web server internally for significantly faster and more stable operation.
165
224
 
166
- If you **only work on ESP32**, I recommend using PsychicHttpServer, which uses the native mode ESP-IDF web server inside. This way, its operation is significantly faster and more continuous. You can access this mode with the `-e psychic` switch.
225
+ **Note for PsychicHttpServer users:** The generated header includes a comment suggesting to configure `server.config.max_uri_handlers` to match or exceed your file count to ensure all routes are properly registered.
167
226
 
168
227
  ### Gzip
169
228
 
@@ -239,19 +298,19 @@ You can use the following c++ directives at the project level if you want to con
239
298
 
240
299
  ### Command line options
241
300
 
242
- | Option | Description | default |
243
- | ------------- | ------------------------------------------------------------------------- | ----------------------- |
244
- | `-s` | **Source dist folder contains compiled web files** | |
245
- | `-e` | The engine for which the include file is created (psychic/psychic2/async) | psychic |
246
- | `-o` | Generated output file with path | `svelteesp32.h` |
247
- | `--etag` | Use ETag header for cache (true/false/compiler) | false |
248
- | `--cachetime` | Override no-cache response with a max-age=<cachetime> response | 0 |
249
- | `--gzip` | Compress content with gzip (true/false/compiler) | true |
250
- | `--created` | Include creation time | false |
251
- | `--version` | Include a version string, `--version=v$npm_package_version` | '' |
252
- | `--espmethod` | Name of generated method | `initSvelteStaticFiles` |
253
- | `--define` | Prefix of c++ defines | `SVELTEESP32` |
254
- | `-h` | Show help | |
301
+ | Option | Description | default |
302
+ | ------------- | ------------------------------------------------------------------------------------ | ----------------------- |
303
+ | `-s` | **Source dist folder contains compiled web files** | (required) |
304
+ | `-e` | The engine for which the include file is created (psychic/psychic2/async/espidf) | psychic |
305
+ | `-o` | Generated output file with path | `svelteesp32.h` |
306
+ | `--etag` | Use ETag header for cache (true/false/compiler) | false |
307
+ | `--cachetime` | Override no-cache response with a max-age=\<cachetime\> response (seconds) | 0 |
308
+ | `--gzip` | Compress content with gzip (true/false/compiler) | true |
309
+ | `--created` | Include creation time in generated header | false |
310
+ | `--version` | Include a version string in generated header, e.g. `--version=v$npm_package_version` | '' |
311
+ | `--espmethod` | Name of generated initialization method | `initSvelteStaticFiles` |
312
+ | `--define` | Prefix of c++ defines (e.g., SVELTEESP32_COUNT) | `SVELTEESP32` |
313
+ | `-h` | Show help | |
255
314
 
256
315
  ### Q&A
257
316
 
@@ -259,9 +318,9 @@ You can use the following c++ directives at the project level if you want to con
259
318
 
260
319
  - **How fast is cpp file compilation?** The cpp (.h) file can be large, but it can be compiled in a few seconds on any machine. If you don't modify your svelte/react app, it will use the already compiled cpp file (not recompile). This does not increase the speed of ESP32/ESP8266 development.
261
320
 
262
- - **Does the solution use PROGMEM?** No and yes. ESP32 no longer has PROGMEM. (Exists, but does not affect the translation). Instead, if we use a const array in the global namespace, its content will be placed in the code area, i.e. it will not be used from the heap or the stack, so the content of the files to be served will be placed next to the code. When working on ESP8266, PROGMEM will actually be used.
321
+ - **Does the solution use PROGMEM?** It depends on the engine. For PsychicHttpServer (`psychic`, `psychic2`) and ESP-IDF (`espidf`) engines, const arrays are used which are automatically placed in program memory on ESP32. For ESPAsyncWebServer (`async`), PROGMEM directive is explicitly used to support ESP8266/ESP8285. In both cases, the file data is stored in flash memory, not RAM, so your heap and stack remain available for your application.
263
322
 
264
- - **Why is the .h file so big?** The source files are always larger than the binary compiled from them. Does the size information in the header (SVELTEESP32_SIZE, SVELTEESP32_SIZE_GZIP) show the actual memory allocation.
323
+ - **Why is the .h file so big?** The source files are always larger than the binary compiled from them. The .h file contains byte arrays in text format (comma-separated decimal numbers), which takes more space than the binary data itself. The actual memory allocation is shown in the header file defines (SVELTEESP32_SIZE for uncompressed, SVELTEESP32_SIZE_GZIP for compressed).
265
324
 
266
325
  - **Is collaboration between groups supported?** Yes, the Frontend team produces the application, the use of svelteesp32 is part of the build process. Then, provided with a version number, the .h file is placed in git, which the ESP team translates into the platformio application.
267
326
 
package/dist/cppCode.js CHANGED
@@ -4,15 +4,7 @@ exports.getCppCode = void 0;
4
4
  const handlebars_1 = require("handlebars");
5
5
  const commandLine_1 = require("./commandLine");
6
6
  const cppCodeEspIdf_1 = require("./cppCodeEspIdf");
7
- const psychicTemplate = `
8
- //engine: PsychicHttpServer
9
- //cmdline: {{{commandLine}}}
10
- //You should use server.config.max_uri_handlers = {{fileCount}}; or higher value to proper handles all files
11
- {{#if created }}
12
- //created: {{now}}
13
- {{/if}}
14
- //
15
-
7
+ const commonHeaderSection = `
16
8
  {{#switch etag}}
17
9
  {{#case "true"}}
18
10
  #ifdef {{definePrefix}}_ENABLE_ETAG
@@ -56,38 +48,36 @@ const psychicTemplate = `
56
48
  {{#each filesByExtension}}
57
49
  #define {{../definePrefix}}_{{this.extension}}_FILES {{this.count}}
58
50
  {{/each}}
59
-
60
- //
61
- #include <Arduino.h>
62
- #include <PsychicHttp.h>
63
- #include <PsychicHttpsServer.h>
64
-
65
- //
51
+ `;
52
+ const dataArraysSection = (progmem = false) => {
53
+ const memDirective = progmem ? ' PROGMEM' : '';
54
+ return `
66
55
  {{#switch gzip}}
67
56
  {{#case "true"}}
68
57
  {{#each sources}}
69
- const uint8_t datagzip_{{this.dataname}}[{{this.lengthGzip}}] = { {{this.bytesGzip}} };
58
+ const uint8_t datagzip_{{this.dataname}}[{{this.lengthGzip}}]${memDirective} = { {{this.bytesGzip}} };
70
59
  {{/each}}
71
60
  {{/case}}
72
61
  {{#case "false"}}
73
62
  {{#each sources}}
74
- const uint8_t data_{{this.dataname}}[{{this.length}}] = { {{this.bytes}} };
63
+ const uint8_t data_{{this.dataname}}[{{this.length}}]${memDirective} = { {{this.bytes}} };
75
64
  {{/each}}
76
65
  {{/case}}
77
66
  {{#case "compiler"}}
78
67
  #ifdef {{definePrefix}}_ENABLE_GZIP
79
68
  {{#each sources}}
80
- const uint8_t datagzip_{{this.dataname}}[{{this.lengthGzip}}] = { {{this.bytesGzip}} };
69
+ const uint8_t datagzip_{{this.dataname}}[{{this.lengthGzip}}]${memDirective} = { {{this.bytesGzip}} };
81
70
  {{/each}}
82
71
  #else
83
72
  {{#each sources}}
84
- const uint8_t data_{{this.dataname}}[{{this.length}}] = { {{this.bytes}} };
73
+ const uint8_t data_{{this.dataname}}[{{this.length}}]${memDirective} = { {{this.bytes}} };
85
74
  {{/each}}
86
- #endif
75
+ #endif
87
76
  {{/case}}
88
77
  {{/switch}}
89
-
90
- //
78
+ `;
79
+ };
80
+ const etagArraysSection = `
91
81
  {{#switch etag}}
92
82
  {{#case "true"}}
93
83
  {{#each sources}}
@@ -101,9 +91,30 @@ const char * etag_{{this.dataname}} = "{{this.md5}}";
101
91
  {{#each sources}}
102
92
  const char * etag_{{this.dataname}} = "{{this.md5}}";
103
93
  {{/each}}
104
- #endif
94
+ #endif
105
95
  {{/case}}
106
96
  {{/switch}}
97
+ `;
98
+ const psychicTemplate = `
99
+ //engine: PsychicHttpServer
100
+ //cmdline: {{{commandLine}}}
101
+ //You should use server.config.max_uri_handlers = {{fileCount}}; or higher value to proper handles all files
102
+ {{#if created }}
103
+ //created: {{now}}
104
+ {{/if}}
105
+ //
106
+ ${commonHeaderSection}
107
+
108
+ //
109
+ #include <Arduino.h>
110
+ #include <PsychicHttp.h>
111
+ #include <PsychicHttpsServer.h>
112
+
113
+ //
114
+ ${dataArraysSection(false)}
115
+
116
+ //
117
+ ${etagArraysSection}
107
118
 
108
119
  //
109
120
  // Http Handlers
@@ -201,50 +212,7 @@ const psychic2Template = `
201
212
  //created: {{now}}
202
213
  {{/if}}
203
214
  //
204
-
205
- {{#switch etag}}
206
- {{#case "true"}}
207
- #ifdef {{definePrefix}}_ENABLE_ETAG
208
- #warning {{definePrefix}}_ENABLE_ETAG has no effect because it is permanently switched ON
209
- #endif
210
- {{/case}}
211
- {{#case "false"}}
212
- #ifdef {{definePrefix}}_ENABLE_ETAG
213
- #warning {{definePrefix}}_ENABLE_ETAG has no effect because it is permanently switched OFF
214
- #endif
215
- {{/case}}
216
- {{/switch}}
217
-
218
- {{#switch gzip}}
219
- {{#case "true"}}
220
- #ifdef {{definePrefix}}_ENABLE_GZIP
221
- #warning {{definePrefix}}_ENABLE_GZIP has no effect because it is permanently switched ON
222
- #endif
223
- {{/case}}
224
- {{#case "false"}}
225
- #ifdef {{definePrefix}}_ENABLE_GZIP
226
- #warning {{definePrefix}}_ENABLE_GZIP has no effect because it is permanently switched OFF
227
- #endif
228
- {{/case}}
229
- {{/switch}}
230
-
231
- //
232
- {{#if version }}
233
- #define {{definePrefix}}_VERSION "{{version}}"
234
- {{/if}}
235
- #define {{definePrefix}}_COUNT {{fileCount}}
236
- #define {{definePrefix}}_SIZE {{fileSize}}
237
- #define {{definePrefix}}_SIZE_GZIP {{fileGzipSize}}
238
-
239
- //
240
- {{#each sources}}
241
- #define {{../definePrefix}}_FILE_{{this.datanameUpperCase}}
242
- {{/each}}
243
-
244
- //
245
- {{#each filesByExtension}}
246
- #define {{../definePrefix}}_{{this.extension}}_FILES {{this.count}}
247
- {{/each}}
215
+ ${commonHeaderSection}
248
216
 
249
217
  //
250
218
  #include <Arduino.h>
@@ -252,47 +220,10 @@ const psychic2Template = `
252
220
  #include <PsychicHttpsServer.h>
253
221
 
254
222
  //
255
- {{#switch gzip}}
256
- {{#case "true"}}
257
- {{#each sources}}
258
- const uint8_t datagzip_{{this.dataname}}[{{this.lengthGzip}}] = { {{this.bytesGzip}} };
259
- {{/each}}
260
- {{/case}}
261
- {{#case "false"}}
262
- {{#each sources}}
263
- const uint8_t data_{{this.dataname}}[{{this.length}}] = { {{this.bytes}} };
264
- {{/each}}
265
- {{/case}}
266
- {{#case "compiler"}}
267
- #ifdef {{definePrefix}}_ENABLE_GZIP
268
- {{#each sources}}
269
- const uint8_t datagzip_{{this.dataname}}[{{this.lengthGzip}}] = { {{this.bytesGzip}} };
270
- {{/each}}
271
- #else
272
- {{#each sources}}
273
- const uint8_t data_{{this.dataname}}[{{this.length}}] = { {{this.bytes}} };
274
- {{/each}}
275
- #endif
276
- {{/case}}
277
- {{/switch}}
223
+ ${dataArraysSection(false)}
278
224
 
279
225
  //
280
- {{#switch etag}}
281
- {{#case "true"}}
282
- {{#each sources}}
283
- const char * etag_{{this.dataname}} = "{{this.md5}}";
284
- {{/each}}
285
- {{/case}}
286
- {{#case "false"}}
287
- {{/case}}
288
- {{#case "compiler"}}
289
- #ifdef {{definePrefix}}_ENABLE_ETAG
290
- {{#each sources}}
291
- const char * etag_{{this.dataname}} = "{{this.md5}}";
292
- {{/each}}
293
- #endif
294
- {{/case}}
295
- {{/switch}}
226
+ ${etagArraysSection}
296
227
 
297
228
  //
298
229
  // Http Handlers
@@ -387,97 +318,17 @@ const asyncTemplate = `
387
318
  //created: {{now}}
388
319
  {{/if}}
389
320
  //
390
-
391
- {{#switch etag}}
392
- {{#case "true"}}
393
- #ifdef {{definePrefix}}_ENABLE_ETAG
394
- #warning {{definePrefix}}_ENABLE_ETAG has no effect because it is permanently switched ON
395
- #endif
396
- {{/case}}
397
- {{#case "false"}}
398
- #ifdef {{definePrefix}}_ENABLE_ETAG
399
- #warning {{definePrefix}}_ENABLE_ETAG has no effect because it is permanently switched OFF
400
- #endif
401
- {{/case}}
402
- {{/switch}}
403
-
404
- {{#switch gzip}}
405
- {{#case "true"}}
406
- #ifdef {{definePrefix}}_ENABLE_GZIP
407
- #warning {{definePrefix}}_ENABLE_GZIP has no effect because it is permanently switched ON
408
- #endif
409
- {{/case}}
410
- {{#case "false"}}
411
- #ifdef {{definePrefix}}_ENABLE_GZIP
412
- #warning {{definePrefix}}_ENABLE_GZIP has no effect because it is permanently switched OFF
413
- #endif
414
- {{/case}}
415
- {{/switch}}
416
-
417
- //
418
- {{#if version }}
419
- #define {{definePrefix}}_VERSION "{{version}}"
420
- {{/if}}
421
- #define {{definePrefix}}_COUNT {{fileCount}}
422
- #define {{definePrefix}}_SIZE {{fileSize}}
423
- #define {{definePrefix}}_SIZE_GZIP {{fileGzipSize}}
424
-
425
- //
426
- {{#each sources}}
427
- #define {{../definePrefix}}_FILE_{{this.datanameUpperCase}}
428
- {{/each}}
429
-
430
- //
431
- {{#each filesByExtension}}
432
- #define {{../definePrefix}}_{{this.extension}}_FILES {{this.count}}
433
- {{/each}}
321
+ ${commonHeaderSection}
434
322
 
435
323
  //
436
324
  #include <Arduino.h>
437
325
  #include <ESPAsyncWebServer.h>
438
326
 
439
327
  //
440
- {{#switch gzip}}
441
- {{#case "true"}}
442
- {{#each sources}}
443
- const uint8_t datagzip_{{this.dataname}}[{{this.lengthGzip}}] PROGMEM = { {{this.bytesGzip}} };
444
- {{/each}}
445
- {{/case}}
446
- {{#case "false"}}
447
- {{#each sources}}
448
- const uint8_t data_{{this.dataname}}[{{this.length}}] PROGMEM = { {{this.bytes}} };
449
- {{/each}}
450
- {{/case}}
451
- {{#case "compiler"}}
452
- #ifdef {{definePrefix}}_ENABLE_GZIP
453
- {{#each sources}}
454
- const uint8_t datagzip_{{this.dataname}}[{{this.lengthGzip}}] PROGMEM = { {{this.bytesGzip}} };
455
- {{/each}}
456
- #else
457
- {{#each sources}}
458
- const uint8_t data_{{this.dataname}}[{{this.length}}] PROGMEM = { {{this.bytes}} };
459
- {{/each}}
460
- #endif
461
- {{/case}}
462
- {{/switch}}
328
+ ${dataArraysSection(true)}
463
329
 
464
330
  //
465
- {{#switch etag}}
466
- {{#case "true"}}
467
- {{#each sources}}
468
- const char * etag_{{this.dataname}} = "{{this.md5}}";
469
- {{/each}}
470
- {{/case}}
471
- {{#case "false"}}
472
- {{/case}}
473
- {{#case "compiler"}}
474
- #ifdef {{definePrefix}}_ENABLE_ETAG
475
- {{#each sources}}
476
- const char * etag_{{this.dataname}} = "{{this.md5}}";
477
- {{/each}}
478
- #endif
479
- {{/case}}
480
- {{/switch}}
331
+ ${etagArraysSection}
481
332
 
482
333
  //
483
334
  // Http Handlers
@@ -570,33 +421,26 @@ const getTemplate = (engine) => {
570
421
  return asyncTemplate;
571
422
  }
572
423
  };
573
- let switchValue;
574
- const getCppCode = (sources, filesByExtension) => (0, handlebars_1.compile)(getTemplate(commandLine_1.cmdLine.engine))({
575
- commandLine: process.argv.slice(2).join(' '),
576
- now: `${new Date().toLocaleDateString()} ${new Date().toLocaleTimeString()}`,
577
- fileCount: sources.length.toString(),
578
- fileSize: sources.reduce((previous, current) => previous + current.content.length, 0).toString(),
579
- fileGzipSize: sources.reduce((previous, current) => previous + current.contentGzip.length, 0).toString(),
580
- sources: sources.map((s) => ({
581
- ...s,
582
- length: s.content.length,
583
- bytes: [...s.content].map((v) => `${v.toString(10)}`).join(','),
584
- lengthGzip: s.contentGzip.length,
585
- bytesGzip: [...s.contentGzip].map((v) => `${v.toString(10)}`).join(','),
586
- isDefault: s.filename.startsWith('index.htm')
587
- })),
588
- filesByExtension,
589
- etag: commandLine_1.cmdLine.etag,
590
- gzip: commandLine_1.cmdLine.gzip,
591
- created: commandLine_1.cmdLine.created,
592
- version: commandLine_1.cmdLine.version,
593
- methodName: commandLine_1.cmdLine.espmethod,
594
- cacheTime: commandLine_1.cmdLine.cachetime ? { value: commandLine_1.cmdLine.cachetime } : undefined,
595
- definePrefix: commandLine_1.cmdLine.define
596
- }, {
597
- helpers: {
424
+ const transformSourceToTemplateData = (s) => ({
425
+ ...s,
426
+ length: s.content.length,
427
+ bytes: [...s.content].map((v) => `${v.toString(10)}`).join(','),
428
+ lengthGzip: s.contentGzip.length,
429
+ bytesGzip: [...s.contentGzip].map((v) => `${v.toString(10)}`).join(','),
430
+ isDefault: s.filename.startsWith('index.htm')
431
+ });
432
+ const postProcessCppCode = (code) => code
433
+ .split('\n')
434
+ .map((line) => line.trimEnd())
435
+ .filter(Boolean)
436
+ .map((line) => (line === '//' ? '' : line))
437
+ .join('\n')
438
+ .replace(/\\n{2}/, '\n');
439
+ const createHandlebarsHelpers = () => {
440
+ let switchValue;
441
+ return {
598
442
  ifeq: function (a, b, options) {
599
- if (a == b)
443
+ if (a === b)
600
444
  return options.fn(this);
601
445
  return options.inverse(this);
602
446
  },
@@ -605,16 +449,31 @@ const getCppCode = (sources, filesByExtension) => (0, handlebars_1.compile)(getT
605
449
  return options.fn(this);
606
450
  },
607
451
  case: function (value, options) {
608
- if (value == switchValue)
452
+ if (value === switchValue)
609
453
  return options.fn(this);
610
454
  return options.inverse(this);
611
455
  }
612
- }
613
- })
614
- .split('\n')
615
- .map((line) => line.trimEnd())
616
- .filter(Boolean)
617
- .map((line) => (line == '//' ? '' : line))
618
- .join('\n')
619
- .replace(/\\n{2}/, '\n');
456
+ };
457
+ };
458
+ const getCppCode = (sources, filesByExtension) => {
459
+ const template = (0, handlebars_1.compile)(getTemplate(commandLine_1.cmdLine.engine));
460
+ const templateData = {
461
+ commandLine: process.argv.slice(2).join(' '),
462
+ now: `${new Date().toLocaleDateString()} ${new Date().toLocaleTimeString()}`,
463
+ fileCount: sources.length.toString(),
464
+ fileSize: sources.reduce((previous, current) => previous + current.content.length, 0).toString(),
465
+ fileGzipSize: sources.reduce((previous, current) => previous + current.contentGzip.length, 0).toString(),
466
+ sources: sources.map((s) => transformSourceToTemplateData(s)),
467
+ filesByExtension,
468
+ etag: commandLine_1.cmdLine.etag,
469
+ gzip: commandLine_1.cmdLine.gzip,
470
+ created: commandLine_1.cmdLine.created,
471
+ version: commandLine_1.cmdLine.version,
472
+ methodName: commandLine_1.cmdLine.espmethod,
473
+ cacheTime: commandLine_1.cmdLine.cachetime ? { value: commandLine_1.cmdLine.cachetime } : undefined,
474
+ definePrefix: commandLine_1.cmdLine.define
475
+ };
476
+ const rawCode = template(templateData, { helpers: createHandlebarsHelpers() });
477
+ return postProcessCppCode(rawCode);
478
+ };
620
479
  exports.getCppCode = getCppCode;
package/dist/file.js CHANGED
@@ -14,35 +14,46 @@ const findSimilarFiles = (files) => {
14
14
  const contentComparer = new Map();
15
15
  for (const [filename, content] of files.entries()) {
16
16
  const hash = (0, node_crypto_1.createHash)('sha256').update(content).digest('hex');
17
- if (contentComparer.has(hash))
18
- contentComparer.get(hash).push(filename);
19
- else
17
+ const existingFiles = contentComparer.get(hash);
18
+ if (existingFiles) {
19
+ existingFiles.push(filename);
20
+ }
21
+ else {
20
22
  contentComparer.set(hash, [filename]);
23
+ }
21
24
  }
22
25
  const result = [];
23
- for (const filenames of contentComparer.values())
24
- if (filenames.length > 1)
26
+ for (const filenames of contentComparer.values()) {
27
+ if (filenames.length > 1) {
25
28
  result.push(filenames);
29
+ }
30
+ }
26
31
  return result;
27
32
  };
28
- const getFiles = () => {
29
- let filenames = (0, glob_1.globSync)('**/*', { cwd: commandLine_1.cmdLine.sourcepath, nodir: true });
30
- filenames = filenames.filter((filename) => {
31
- const extension = node_path_1.default.extname(filename);
32
- if (['.gz', '.brottli', '.br'].includes(extension)) {
33
- const original = filename.slice(0, -1 * extension.length);
34
- if (filenames.includes(original)) {
35
- console.log((0, consoleColor_1.redLog)(` ${filename} skipped because is perhaps a compressed version of ${original}`));
36
- return false;
37
- }
33
+ const shouldSkipFile = (filename, allFilenames) => {
34
+ const extension = node_path_1.default.extname(filename);
35
+ const compressedExtensions = ['.gz', '.brottli', '.br'];
36
+ if (compressedExtensions.includes(extension)) {
37
+ const original = filename.slice(0, -1 * extension.length);
38
+ if (allFilenames.includes(original)) {
39
+ console.log((0, consoleColor_1.redLog)(` ${filename} skipped because is perhaps a compressed version of ${original}`));
40
+ return true;
38
41
  }
39
- return true;
40
- });
42
+ }
43
+ return false;
44
+ };
45
+ const getFiles = () => {
46
+ const allFilenames = (0, glob_1.globSync)('**/*', { cwd: commandLine_1.cmdLine.sourcepath, nodir: true });
47
+ const filenames = allFilenames.filter((filename) => !shouldSkipFile(filename, allFilenames));
41
48
  const result = new Map();
42
- for (const filename of filenames)
43
- result.set(filename, (0, node_fs_1.readFileSync)(node_path_1.default.join(commandLine_1.cmdLine.sourcepath, filename), { flag: 'r' }));
44
- for (const sameFile of findSimilarFiles(result))
45
- console.log((0, consoleColor_1.yellowLog)(` ${sameFile.join(', ')} files look like identical`));
49
+ for (const filename of filenames) {
50
+ const filePath = node_path_1.default.join(commandLine_1.cmdLine.sourcepath, filename);
51
+ result.set(filename, (0, node_fs_1.readFileSync)(filePath, { flag: 'r' }));
52
+ }
53
+ const duplicates = findSimilarFiles(result);
54
+ for (const sameFiles of duplicates) {
55
+ console.log((0, consoleColor_1.yellowLog)(` ${sameFiles.join(', ')} files look like identical`));
56
+ }
46
57
  return result;
47
58
  };
48
59
  exports.getFiles = getFiles;
package/dist/index.js CHANGED
@@ -12,6 +12,8 @@ const commandLine_1 = require("./commandLine");
12
12
  const consoleColor_1 = require("./consoleColor");
13
13
  const cppCode_1 = require("./cppCode");
14
14
  const file_1 = require("./file");
15
+ const GZIP_MIN_SIZE = 1024;
16
+ const GZIP_MIN_REDUCTION_RATIO = 0.85;
15
17
  const summary = {
16
18
  filecount: 0,
17
19
  size: 0,
@@ -19,6 +21,36 @@ const summary = {
19
21
  };
20
22
  const sources = [];
21
23
  const filesByExtension = [];
24
+ const shouldUseGzip = (originalSize, compressedSize) => originalSize > GZIP_MIN_SIZE && compressedSize < originalSize * GZIP_MIN_REDUCTION_RATIO;
25
+ const calculateCompressionRatio = (originalSize, compressedSize) => Math.round((compressedSize / originalSize) * 100);
26
+ const formatCompressionLog = (filename, padding, originalSize, compressedSize, useGzip) => {
27
+ const ratio = calculateCompressionRatio(originalSize, compressedSize);
28
+ const sizeInfo = `(${originalSize} -> ${compressedSize} = ${ratio}%)`;
29
+ if (useGzip) {
30
+ return (0, consoleColor_1.greenLog)(` [${filename}] ${padding} ✓ gzip used ${sizeInfo}`);
31
+ }
32
+ const tooSmall = originalSize <= GZIP_MIN_SIZE ? '(too small) ' : '';
33
+ return (0, consoleColor_1.yellowLog)(` [${filename}] ${padding} x gzip unused ${tooSmall}${sizeInfo}`);
34
+ };
35
+ const createSourceEntry = (filename, dataname, content, contentGzip, mimeType, md5, isGzip) => ({
36
+ filename,
37
+ dataname,
38
+ datanameUpperCase: dataname.toUpperCase(),
39
+ content,
40
+ contentGzip: isGzip ? contentGzip : content,
41
+ isGzip,
42
+ mime: mimeType,
43
+ md5
44
+ });
45
+ const updateExtensionGroup = (extension) => {
46
+ const group = filesByExtension.find((fe) => fe.extension === extension);
47
+ if (group) {
48
+ group.count += 1;
49
+ }
50
+ else {
51
+ filesByExtension.push({ extension, count: 1 });
52
+ }
53
+ };
22
54
  console.log('Collecting source files');
23
55
  const files = (0, file_1.getFiles)();
24
56
  if (files.size === 0) {
@@ -36,42 +68,15 @@ for (const [originalFilename, content] of files) {
36
68
  let extension = node_path_1.default.extname(filename).toUpperCase();
37
69
  if (extension.startsWith('.'))
38
70
  extension = extension.slice(1);
39
- const group = filesByExtension.find((fe) => fe.extension === extension);
40
- if (group)
41
- group.count += 1;
42
- else
43
- filesByExtension.push({ extension, count: 1 });
71
+ updateExtensionGroup(extension);
44
72
  const md5 = (0, node_crypto_1.createHash)('md5').update(content).digest('hex');
45
73
  summary.size += content.length;
46
74
  const zipContent = (0, node_zlib_1.gzipSync)(content, { level: 9 });
47
75
  summary.gzipsize += zipContent.length;
48
- const zipRatio = Math.round((zipContent.length / content.length) * 100);
49
- if (content.length > 1024 && zipContent.length < content.length * 0.85) {
50
- sources.push({
51
- filename,
52
- dataname,
53
- datanameUpperCase: dataname.toUpperCase(),
54
- content,
55
- contentGzip: zipContent,
56
- isGzip: true,
57
- mime: mimeType,
58
- md5
59
- });
60
- console.log((0, consoleColor_1.greenLog)(` [${originalFilename}] ${' '.repeat(longestFilename - originalFilename.length)} ✓ gzip used (${content.length} -> ${zipContent.length} = ${zipRatio}%)`));
61
- }
62
- else {
63
- sources.push({
64
- filename,
65
- dataname,
66
- datanameUpperCase: dataname.toUpperCase(),
67
- content,
68
- contentGzip: content,
69
- isGzip: false,
70
- mime: mimeType,
71
- md5
72
- });
73
- console.log((0, consoleColor_1.yellowLog)(` [${originalFilename}] ${' '.repeat(longestFilename - originalFilename.length)} x gzip unused ${content.length <= 1024 ? `(too small) ` : ''}(${content.length} -> ${zipContent.length} = ${zipRatio}%)`));
74
- }
76
+ const useGzip = shouldUseGzip(content.length, zipContent.length);
77
+ sources.push(createSourceEntry(filename, dataname, content, zipContent, mimeType, md5, useGzip));
78
+ const padding = ' '.repeat(longestFilename - originalFilename.length);
79
+ console.log(formatCompressionLog(originalFilename, padding, content.length, zipContent.length, useGzip));
75
80
  }
76
81
  console.log('');
77
82
  filesByExtension.sort((left, right) => left.extension.localeCompare(right.extension));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "svelteesp32",
3
- "version": "1.9.1",
3
+ "version": "1.9.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",
@@ -31,7 +31,9 @@
31
31
  "dev:async": "nodemon src/index.ts -- -e async -s ./demo/svelte/dist -o ./demo/esp32/include/svelteesp32.h --etag=true --gzip=true --cachetime=86400 --version=v$npm_package_version",
32
32
  "dev:psychic": "nodemon src/index.ts -- -e psychic -s ./demo/svelte/dist -o ./demo/esp32/include/svelteesp32.h --etag=false --gzip=false --version=v$npm_package_version",
33
33
  "dev:psychic2": "nodemon src/index.ts -- -e psychic2 -s ./demo/svelte/dist -o ./demo/esp32/include/svelteesp32.h --etag=false --gzip=false --version=v$npm_package_version",
34
- "test:all": "./package.script && ~/.platformio/penv/bin/pio run -d ./demo/esp32 && ~/.platformio/penv/bin/pio run -d ./demo/esp32idf",
34
+ "test:esp32": "./package.script && ~/.platformio/penv/bin/pio run -d ./demo/esp32",
35
+ "test:esp32idf": "./package.script && ~/.platformio/penv/bin/pio run -d ./demo/esp32idf",
36
+ "test:all": "npm run test:esp32 && npm run test:esp32idf",
35
37
  "clean": "tsc --build --clean",
36
38
  "build": "tsc --build --clean && tsc --build --force",
37
39
  "format:check": "prettier --check .",
@@ -55,18 +57,18 @@
55
57
  ],
56
58
  "devDependencies": {
57
59
  "@types/mime-types": "^3.0.1",
58
- "@types/node": "^24.3.3",
59
- "@typescript-eslint/eslint-plugin": "^8.43.0",
60
- "@typescript-eslint/parser": "^8.43.0",
61
- "eslint": "^9.35.0",
60
+ "@types/node": "^24.8.1",
61
+ "@typescript-eslint/eslint-plugin": "^8.46.1",
62
+ "@typescript-eslint/parser": "^8.46.1",
63
+ "eslint": "^9.38.0",
62
64
  "eslint-config-prettier": "^10.1.8",
63
65
  "eslint-plugin-simple-import-sort": "^12.1.1",
64
66
  "eslint-plugin-unicorn": "^61.0.2",
65
67
  "nodemon": "^3.1.10",
66
68
  "prettier": "^3.6.2",
67
69
  "ts-node": "^10.9.2",
68
- "tsx": "^4.20.5",
69
- "typescript": "^5.9.2"
70
+ "tsx": "^4.20.6",
71
+ "typescript": "^5.9.3"
70
72
  },
71
73
  "dependencies": {
72
74
  "glob": "^11.0.3",