svelteesp32 2.0.1 → 2.1.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
@@ -25,7 +25,7 @@
25
25
  - **Smart Caching** — Built-in SHA256 ETags deliver HTTP 304 responses, slashing bandwidth on constrained devices.
26
26
  - **CI/CD Ready** — Simple npm package that slots into any build pipeline.
27
27
  - **Zero Runtime Overhead** — Data served directly from flash. No filesystem reads, no RAM allocation.
28
- - **3 Web Server Engines** — PsychicHttpServer, ESPAsyncWebServer, and native ESP-IDF supported.
28
+ - **4 Web Server Engines** — PsychicHttpServer V2, ESPAsyncWebServer, Arduino WebServer, and native ESP-IDF supported.
29
29
 
30
30
  ---
31
31
 
@@ -79,6 +79,7 @@ void setup() {
79
79
 
80
80
  ## What's New
81
81
 
82
+ - **v2.1.0** — New Arduino WebServer engine (`-e webserver`), dependency updates
82
83
  - **v2.0.1** — Dependency updates (ESLint v10, eslint-plugin-unicorn 63), improved error cause chaining
83
84
  - **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
84
85
  - **v1.16.0** — Size budget constraints (`--maxsize`, `--maxgzipsize`)
@@ -116,6 +117,9 @@ npx svelteesp32 -e psychic -s ./dist -o ./esp32/svelteesp32.h --etag=true
116
117
  # ESPAsyncWebServer (ESP32 + ESP8266)
117
118
  npx svelteesp32 -e async -s ./dist -o ./esp32/svelteesp32.h --etag=true
118
119
 
120
+ # Arduino WebServer (ESP32, synchronous, no dependencies)
121
+ npx svelteesp32 -e webserver -s ./dist -o ./esp32/svelteesp32.h --etag=true
122
+
119
123
  # Native ESP-IDF
120
124
  npx svelteesp32 -e espidf -s ./dist -o ./esp32/svelteesp32.h --etag=true
121
125
  ```
@@ -171,6 +175,24 @@ void setup() {
171
175
  }
172
176
  ```
173
177
 
178
+ **Arduino WebServer (built-in, no dependencies)**
179
+
180
+ ```c
181
+ #include <WebServer.h>
182
+ #include "svelteesp32.h"
183
+
184
+ WebServer server(80);
185
+
186
+ void setup() {
187
+ initSvelteStaticFiles(&server);
188
+ server.begin();
189
+ }
190
+
191
+ void loop() {
192
+ server.handleClient();
193
+ }
194
+ ```
195
+
174
196
  **Native ESP-IDF**
175
197
 
176
198
  ```c
@@ -259,11 +281,12 @@ void initSvelteStaticFiles(PsychicHttpServer * server) {
259
281
 
260
282
  ## Supported Web Server Engines
261
283
 
262
- | Engine | Flag | Best For | Platform |
263
- | ------------------------ | ------------ | ---------------------------- | --------------- |
264
- | **PsychicHttpServer V2** | `-e psychic` | Maximum performance | ESP32 only |
265
- | **ESPAsyncWebServer** | `-e async` | Cross-platform compatibility | ESP32 + ESP8266 |
266
- | **Native ESP-IDF** | `-e espidf` | Pure ESP-IDF projects | ESP32 only |
284
+ | Engine | Flag | Best For | Platform |
285
+ | ------------------------ | -------------- | ---------------------------- | --------------- |
286
+ | **PsychicHttpServer V2** | `-e psychic` | Maximum performance | ESP32 only |
287
+ | **ESPAsyncWebServer** | `-e async` | Cross-platform compatibility | ESP32 + ESP8266 |
288
+ | **Arduino WebServer** | `-e webserver` | No dependencies, simplicity | ESP32 only |
289
+ | **Native ESP-IDF** | `-e espidf` | Pure ESP-IDF projects | ESP32 only |
267
290
 
268
291
  **Recommendation:** For ESP32-only projects, use PsychicHttpServer V2 (`-e psychic`) for the fastest, most stable experience.
269
292
 
@@ -288,7 +311,7 @@ Reduce bandwidth dramatically with HTTP 304 "Not Modified" responses. When a bro
288
311
  - **Minimal overhead** — adds ~1-3% code size for significant bandwidth savings
289
312
  - **Compiler mode** — use `--etag=compiler` and control via `-D SVELTEESP32_ENABLE_ETAG`
290
313
 
291
- All three engines support full ETag validation.
314
+ All four engines support full ETag validation.
292
315
 
293
316
  ### Browser Cache Control
294
317
 
@@ -402,25 +425,25 @@ Called for every response (200 = content served, 304 = cache hit).
402
425
 
403
426
  ## CLI Reference
404
427
 
405
- | Option | Description | Default |
406
- | ---------------- | ------------------------------------------------ | ----------------------- |
407
- | `-s` | Source folder with compiled web files | (required) |
408
- | `-e` | Web server engine (psychic/async/espidf) | `psychic` |
409
- | `-o` | Output header file path | `svelteesp32.h` |
410
- | `--etag` | ETag caching (true/false/compiler) | `false` |
411
- | `--gzip` | Gzip compression (true/false/compiler) | `true` |
412
- | `--exclude` | Exclude files by glob pattern | System files |
413
- | `--basepath` | URL prefix for all routes | (none) |
414
- | `--maxsize` | Max total uncompressed size (e.g., `400k`, `1m`) | (none) |
415
- | `--maxgzipsize` | Max total gzip size (e.g., `150k`, `500k`) | (none) |
416
- | `--cachetime` | Cache-Control max-age in seconds | `0` |
417
- | `--version` | Version string in header | (none) |
418
- | `--define` | C++ define prefix | `SVELTEESP32` |
419
- | `--espmethod` | Init function name | `initSvelteStaticFiles` |
420
- | `--config` | Custom RC file path | `.svelteesp32rc.json` |
421
- | `--dryrun` | Show summary without writing output | `false` |
422
- | `--noindexcheck` | Skip index.html validation | `false` |
423
- | `-h` | Show help | |
428
+ | Option | Description | Default |
429
+ | ---------------- | -------------------------------------------------- | ----------------------- |
430
+ | `-s` | Source folder with compiled web files | (required) |
431
+ | `-e` | Web server engine (psychic/async/espidf/webserver) | `psychic` |
432
+ | `-o` | Output header file path | `svelteesp32.h` |
433
+ | `--etag` | ETag caching (true/false/compiler) | `false` |
434
+ | `--gzip` | Gzip compression (true/false/compiler) | `true` |
435
+ | `--exclude` | Exclude files by glob pattern | System files |
436
+ | `--basepath` | URL prefix for all routes | (none) |
437
+ | `--maxsize` | Max total uncompressed size (e.g., `400k`, `1m`) | (none) |
438
+ | `--maxgzipsize` | Max total gzip size (e.g., `150k`, `500k`) | (none) |
439
+ | `--cachetime` | Cache-Control max-age in seconds | `0` |
440
+ | `--version` | Version string in header | (none) |
441
+ | `--define` | C++ define prefix | `SVELTEESP32` |
442
+ | `--espmethod` | Init function name | `initSvelteStaticFiles` |
443
+ | `--config` | Custom RC file path | `.svelteesp32rc.json` |
444
+ | `--dryrun` | Show summary without writing output | `false` |
445
+ | `--noindexcheck` | Skip index.html validation | `false` |
446
+ | `-h` | Show help | |
424
447
 
425
448
  ---
426
449
 
@@ -1,5 +1,5 @@
1
1
  interface ICopyFilesArguments {
2
- engine: 'psychic' | 'async' | 'espidf';
2
+ engine: 'psychic' | 'async' | 'espidf' | 'webserver';
3
3
  sourcepath: string;
4
4
  outputfile: string;
5
5
  espmethod: string;
@@ -18,7 +18,7 @@ interface ICopyFilesArguments {
18
18
  help?: boolean;
19
19
  }
20
20
  interface IRcFileConfig {
21
- engine?: 'psychic' | 'async' | 'espidf';
21
+ engine?: 'psychic' | 'async' | 'espidf' | 'webserver';
22
22
  sourcepath?: string;
23
23
  outputfile?: string;
24
24
  espmethod?: string;
@@ -24,7 +24,7 @@ Configuration:
24
24
 
25
25
  Options:
26
26
  -e, --engine <value> The engine for which the include file is created
27
- (psychic|async|espidf) (default: "psychic")
27
+ (psychic|async|espidf|webserver) (default: "psychic")
28
28
  -s, --sourcepath <path> Source dist folder contains compiled web files (required)
29
29
  -o, --outputfile <path> Generated output file with path (default: "svelteesp32.h")
30
30
  --etag <value> Use ETAG header for cache (true|false|compiler) (default: "false")
@@ -66,7 +66,7 @@ RC File:
66
66
  process.exit(0);
67
67
  }
68
68
  function validateEngine(value) {
69
- if (value === 'psychic' || value === 'async' || value === 'espidf')
69
+ if (value === 'psychic' || value === 'async' || value === 'espidf' || value === 'webserver')
70
70
  return value;
71
71
  console.error((0, errorMessages_1.getInvalidEngineError)(value));
72
72
  process.exit(1);
package/dist/cppCode.js CHANGED
@@ -492,6 +492,205 @@ void {{methodName}}(AsyncWebServer * server) {
492
492
  });
493
493
  {{/if}}
494
494
 
495
+ {{/each}}
496
+ }`;
497
+ const webserverTemplate = `
498
+ //engine: Arduino WebServer
499
+ //config: {{{config}}}
500
+ {{#if created }}
501
+ //created: {{now}}
502
+ {{/if}}
503
+ //
504
+ ${commonHeaderSection}
505
+
506
+ //
507
+ #include <Arduino.h>
508
+ #include <WebServer.h>
509
+
510
+ //
511
+ ${dataArraysSection(true)}
512
+
513
+ //
514
+ ${etagArraysSection}
515
+
516
+ //
517
+ ${manifestSection}
518
+
519
+ //
520
+ ${hookSection}
521
+
522
+ //
523
+ // Chunked send helper for PROGMEM data
524
+ static inline void {{definePrefix}}_sendChunked(WebServer * server, const uint8_t * data, size_t len) {
525
+ const size_t chunkSize = 4096;
526
+ for (size_t offset = 0; offset < len; offset += chunkSize) {
527
+ size_t remaining = len - offset;
528
+ size_t toSend = remaining < chunkSize ? remaining : chunkSize;
529
+ server->sendContent_P((const char *)(data + offset), toSend);
530
+ }
531
+ }
532
+
533
+ //
534
+ // Http Handlers
535
+ void {{methodName}}(WebServer * server) {
536
+ {{#each sources}}
537
+ //
538
+ // {{this.filename}}
539
+ server->on("{{../basePath}}/{{this.filename}}", HTTP_GET, [server]() {
540
+
541
+ {{#switch ../etag}}
542
+ {{#case "true"}}
543
+ if (server->hasHeader("If-None-Match") && server->header("If-None-Match").equals(etag_{{this.dataname}})) {
544
+ server->send(304);
545
+ {{../definePrefix}}_onFileServed("{{../basePath}}/{{this.filename}}", 304);
546
+ return;
547
+ }
548
+ {{/case}}
549
+ {{#case "compiler"}}
550
+ #ifdef {{../definePrefix}}_ENABLE_ETAG
551
+ if (server->hasHeader("If-None-Match") && server->header("If-None-Match").equals(etag_{{this.dataname}})) {
552
+ server->send(304);
553
+ {{../definePrefix}}_onFileServed("{{../basePath}}/{{this.filename}}", 304);
554
+ return;
555
+ }
556
+ #endif
557
+ {{/case}}
558
+ {{/switch}}
559
+
560
+ {{#switch ../etag}}
561
+ {{#case "true"}}
562
+ {{#../cacheTime}}
563
+ server->sendHeader("Cache-Control", "max-age={{value}}");
564
+ {{/../cacheTime}}
565
+ {{^../cacheTime}}
566
+ server->sendHeader("Cache-Control", "no-cache");
567
+ {{/../cacheTime}}
568
+ server->sendHeader("ETag", etag_{{this.dataname}});
569
+ {{/case}}
570
+ {{#case "compiler"}}
571
+ #ifdef {{../definePrefix}}_ENABLE_ETAG
572
+ {{#../cacheTime}}
573
+ server->sendHeader("Cache-Control", "max-age={{value}}");
574
+ {{/../cacheTime}}
575
+ {{^../cacheTime}}
576
+ server->sendHeader("Cache-Control", "no-cache");
577
+ {{/../cacheTime}}
578
+ server->sendHeader("ETag", etag_{{this.dataname}});
579
+ #endif
580
+ {{/case}}
581
+ {{/switch}}
582
+
583
+ {{#switch ../gzip}}
584
+ {{#case "true"}}
585
+ {{#if this.isGzip}}
586
+ server->sendHeader("Content-Encoding", "gzip");
587
+ {{/if}}
588
+ server->setContentLength({{this.lengthGzip}});
589
+ server->send(200, "{{this.mime}}", "");
590
+ {{../definePrefix}}_sendChunked(server, datagzip_{{this.dataname}}, {{this.lengthGzip}});
591
+ {{/case}}
592
+ {{#case "false"}}
593
+ server->setContentLength({{this.length}});
594
+ server->send(200, "{{this.mime}}", "");
595
+ {{../definePrefix}}_sendChunked(server, data_{{this.dataname}}, {{this.length}});
596
+ {{/case}}
597
+ {{#case "compiler"}}
598
+ #ifdef {{../definePrefix}}_ENABLE_GZIP
599
+ {{#if this.isGzip}}
600
+ server->sendHeader("Content-Encoding", "gzip");
601
+ {{/if}}
602
+ server->setContentLength({{this.lengthGzip}});
603
+ server->send(200, "{{this.mime}}", "");
604
+ {{../definePrefix}}_sendChunked(server, datagzip_{{this.dataname}}, {{this.lengthGzip}});
605
+ #else
606
+ server->setContentLength({{this.length}});
607
+ server->send(200, "{{this.mime}}", "");
608
+ {{../definePrefix}}_sendChunked(server, data_{{this.dataname}}, {{this.length}});
609
+ #endif
610
+ {{/case}}
611
+ {{/switch}}
612
+
613
+ {{../definePrefix}}_onFileServed("{{../basePath}}/{{this.filename}}", 200);
614
+ });
615
+ {{#if this.isDefault}}
616
+ server->on("{{#if ../basePath}}{{../basePath}}{{else}}/{{/if}}", HTTP_GET, [server]() {
617
+
618
+ {{#switch ../etag}}
619
+ {{#case "true"}}
620
+ if (server->hasHeader("If-None-Match") && server->header("If-None-Match").equals(etag_{{this.dataname}})) {
621
+ server->send(304);
622
+ {{../definePrefix}}_onFileServed("{{#if ../basePath}}{{../basePath}}{{else}}/{{/if}}", 304);
623
+ return;
624
+ }
625
+ {{/case}}
626
+ {{#case "compiler"}}
627
+ #ifdef {{../definePrefix}}_ENABLE_ETAG
628
+ if (server->hasHeader("If-None-Match") && server->header("If-None-Match").equals(etag_{{this.dataname}})) {
629
+ server->send(304);
630
+ {{../definePrefix}}_onFileServed("{{#if ../basePath}}{{../basePath}}{{else}}/{{/if}}", 304);
631
+ return;
632
+ }
633
+ #endif
634
+ {{/case}}
635
+ {{/switch}}
636
+
637
+ {{#switch ../etag}}
638
+ {{#case "true"}}
639
+ {{#../cacheTime}}
640
+ server->sendHeader("Cache-Control", "max-age={{value}}");
641
+ {{/../cacheTime}}
642
+ {{^../cacheTime}}
643
+ server->sendHeader("Cache-Control", "no-cache");
644
+ {{/../cacheTime}}
645
+ server->sendHeader("ETag", etag_{{this.dataname}});
646
+ {{/case}}
647
+ {{#case "compiler"}}
648
+ #ifdef {{../definePrefix}}_ENABLE_ETAG
649
+ {{#../cacheTime}}
650
+ server->sendHeader("Cache-Control", "max-age={{value}}");
651
+ {{/../cacheTime}}
652
+ {{^../cacheTime}}
653
+ server->sendHeader("Cache-Control", "no-cache");
654
+ {{/../cacheTime}}
655
+ server->sendHeader("ETag", etag_{{this.dataname}});
656
+ #endif
657
+ {{/case}}
658
+ {{/switch}}
659
+
660
+ {{#switch ../gzip}}
661
+ {{#case "true"}}
662
+ {{#if this.isGzip}}
663
+ server->sendHeader("Content-Encoding", "gzip");
664
+ {{/if}}
665
+ server->setContentLength({{this.lengthGzip}});
666
+ server->send(200, "{{this.mime}}", "");
667
+ {{../definePrefix}}_sendChunked(server, datagzip_{{this.dataname}}, {{this.lengthGzip}});
668
+ {{/case}}
669
+ {{#case "false"}}
670
+ server->setContentLength({{this.length}});
671
+ server->send(200, "{{this.mime}}", "");
672
+ {{../definePrefix}}_sendChunked(server, data_{{this.dataname}}, {{this.length}});
673
+ {{/case}}
674
+ {{#case "compiler"}}
675
+ #ifdef {{../definePrefix}}_ENABLE_GZIP
676
+ {{#if this.isGzip}}
677
+ server->sendHeader("Content-Encoding", "gzip");
678
+ {{/if}}
679
+ server->setContentLength({{this.lengthGzip}});
680
+ server->send(200, "{{this.mime}}", "");
681
+ {{../definePrefix}}_sendChunked(server, datagzip_{{this.dataname}}, {{this.lengthGzip}});
682
+ #else
683
+ server->setContentLength({{this.length}});
684
+ server->send(200, "{{this.mime}}", "");
685
+ {{../definePrefix}}_sendChunked(server, data_{{this.dataname}}, {{this.length}});
686
+ #endif
687
+ {{/case}}
688
+ {{/switch}}
689
+
690
+ {{../definePrefix}}_onFileServed("{{#if ../basePath}}{{../basePath}}{{else}}/{{/if}}", 200);
691
+ });
692
+ {{/if}}
693
+
495
694
  {{/each}}
496
695
  }`;
497
696
  const getTemplate = (engine) => {
@@ -502,6 +701,8 @@ const getTemplate = (engine) => {
502
701
  return asyncTemplate;
503
702
  case 'espidf':
504
703
  return cppCodeEspIdf_1.espidfTemplate;
704
+ case 'webserver':
705
+ return webserverTemplate;
505
706
  default:
506
707
  throw new Error(`Unknown engine: ${engine}`);
507
708
  }
@@ -14,7 +14,8 @@ function getEngineName(engine) {
14
14
  const names = {
15
15
  psychic: 'PsychicHttpServer',
16
16
  async: 'ESPAsyncWebServer',
17
- espidf: 'ESP-IDF'
17
+ espidf: 'ESP-IDF',
18
+ webserver: 'Arduino WebServer'
18
19
  };
19
20
  return names[engine] ?? engine;
20
21
  }
@@ -28,7 +29,10 @@ function getMissingIndexError(engine) {
28
29
  3. ESPAsyncWebServer uses: server.on("/", HTTP_GET, ...)`,
29
30
  espidf: ` 1. Add an index.html file to your source directory
30
31
  2. The file will register both "/" and "/index.html" routes
31
- 3. ESP-IDF uses: httpd_register_uri_handler(server, &route_def_...)`
32
+ 3. ESP-IDF uses: httpd_register_uri_handler(server, &route_def_...)`,
33
+ webserver: ` 1. Add an index.html file to your source directory
34
+ 2. The file will automatically create a "/" route handler
35
+ 3. Arduino WebServer uses: server.on("/", HTTP_GET, handler)`
32
36
  };
33
37
  const hint = hints[engine] ?? hints['psychic'];
34
38
  return ((0, consoleColor_1.redLog)('[ERROR] No index.html or index.htm found in source files') +
@@ -53,6 +57,7 @@ Valid engines are:
53
57
  ${(0, consoleColor_1.cyanLog)('• psychic')} - PsychicHttpServer (ESP32 only, fastest performance)
54
58
  ${(0, consoleColor_1.cyanLog)('• async')} - ESPAsyncWebServer (ESP32/ESP8266 compatible)
55
59
  ${(0, consoleColor_1.cyanLog)('• espidf')} - Native ESP-IDF web server (ESP32 only, no Arduino)
60
+ ${(0, consoleColor_1.cyanLog)('• webserver')} - Arduino WebServer (ESP32, synchronous, built-in)
56
61
 
57
62
  How to fix:
58
63
  npx svelteesp32 --engine=psychic --sourcepath=./dist
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "svelteesp32",
3
- "version": "2.0.1",
3
+ "version": "2.1.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",
@@ -30,6 +30,7 @@
30
30
  "scripts": {
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
+ "dev:webserver": "nodemon src/index.ts -- -e webserver -s ./demo/svelte/dist -o ./demo/esp32/include/svelteesp32.h --etag=true --gzip=true --cachetime=86400 --version=v$npm_package_version",
33
34
  "test": "vitest run",
34
35
  "test:watch": "vitest",
35
36
  "test:coverage": "vitest run --coverage",
@@ -58,20 +59,20 @@
58
59
  "espasyncwebserver"
59
60
  ],
60
61
  "devDependencies": {
61
- "@eslint/eslintrc": "^3.3.3",
62
- "@eslint/js": "^10.0.0",
62
+ "@eslint/eslintrc": "^3.3.4",
63
+ "@eslint/js": "^10.0.1",
63
64
  "@types/mime-types": "^3.0.1",
64
- "@types/node": "^25.3.0",
65
+ "@types/node": "^25.3.2",
65
66
  "@types/picomatch": "^4.0.2",
66
- "@typescript-eslint/eslint-plugin": "^8.56.0",
67
- "@typescript-eslint/parser": "^8.56.0",
67
+ "@typescript-eslint/eslint-plugin": "^8.56.1",
68
+ "@typescript-eslint/parser": "^8.56.1",
68
69
  "@vitest/coverage-v8": "^4.0.18",
69
- "eslint": "^10.0.0",
70
+ "eslint": "^10.0.2",
70
71
  "eslint-config-prettier": "^10.1.8",
71
72
  "eslint-plugin-simple-import-sort": "^12.1.1",
72
73
  "eslint-plugin-unicorn": "^63.0.0",
73
74
  "memfs": "^4.56.10",
74
- "nodemon": "^3.1.11",
75
+ "nodemon": "^3.1.14",
75
76
  "prettier": "^3.8.1",
76
77
  "ts-node": "^10.9.2",
77
78
  "tsx": "^4.21.0",