svelteesp32 2.3.0 → 2.3.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 +139 -2
- package/dist/commandLine.js +17 -3
- package/dist/file.js +10 -1
- package/dist/index.js +3 -2
- package/package.json +10 -10
package/README.md
CHANGED
|
@@ -75,13 +75,20 @@ void setup() {
|
|
|
75
75
|
|
|
76
76
|
**That's it.** Your entire web app is now embedded and ready to serve.
|
|
77
77
|
|
|
78
|
+
> **Just want production-safe defaults?**
|
|
79
|
+
>
|
|
80
|
+
> ```bash
|
|
81
|
+
> npx svelteesp32 -e psychic -s ./dist -o ./esp32/svelteesp32.h \
|
|
82
|
+
> --etag=true --gzip=true --cachetime-html=0 --cachetime-assets=31536000
|
|
83
|
+
> ```
|
|
84
|
+
>
|
|
85
|
+
> ETags for instant 304s, gzip for smaller transfers, `no-cache` for HTML so updates are always picked up, and 1-year caching for content-hashed JS/CSS assets.
|
|
86
|
+
|
|
78
87
|
---
|
|
79
88
|
|
|
80
89
|
## What's New
|
|
81
90
|
|
|
82
91
|
- **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)
|
|
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]`)
|
|
85
92
|
- **v2.2.0** — SPA routing catch-all (`--spa`) for client-side routers on all four engines
|
|
86
93
|
- **v2.1.0** — New Arduino WebServer engine (`-e webserver`), dependency updates
|
|
87
94
|
- **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
|
|
@@ -316,6 +323,8 @@ Reduce bandwidth dramatically with HTTP 304 "Not Modified" responses. When a bro
|
|
|
316
323
|
|
|
317
324
|
All four engines support full ETag validation.
|
|
318
325
|
|
|
326
|
+
> **Browser compatibility note:** ETags and `Cache-Control: max-age` are universally supported in all modern browsers. Very old clients (IE6/7, early Android 2.x WebViews) may ignore `must-revalidate` or mishandle 304 responses. If you target these environments, set `--etag=false` and `--cachetime=0` to force full downloads on every request.
|
|
327
|
+
|
|
319
328
|
### Browser Cache Control
|
|
320
329
|
|
|
321
330
|
Fine-tune how browsers cache your content:
|
|
@@ -540,6 +549,134 @@ npx svelteesp32 --config=.svelteesp32rc.prod.json
|
|
|
540
549
|
|
|
541
550
|
CLI arguments always override RC file values.
|
|
542
551
|
|
|
552
|
+
> **Security note:** When svelteesp32 auto-loads an RC file from the current directory it prints a warning. If you cloned a project that includes `.svelteesp32rc.json`, review it before running. `outputfile` in RC files must be a relative path — use `--output` on the CLI for absolute paths.
|
|
553
|
+
|
|
554
|
+
---
|
|
555
|
+
|
|
556
|
+
## Common Recipes
|
|
557
|
+
|
|
558
|
+
### Recipe A — Arduino IDE + Built-in WebServer
|
|
559
|
+
|
|
560
|
+
No PlatformIO required. Build your frontend, run svelteesp32 manually, then compile in Arduino IDE.
|
|
561
|
+
|
|
562
|
+
```bash
|
|
563
|
+
# 1. Build your frontend
|
|
564
|
+
npm run build
|
|
565
|
+
|
|
566
|
+
# 2. Generate the header
|
|
567
|
+
npx svelteesp32 -e webserver -s ./dist -o ./MyProject/svelteesp32.h \
|
|
568
|
+
--gzip=true --etag=false
|
|
569
|
+
```
|
|
570
|
+
|
|
571
|
+
> **Note:** The Arduino `WebServer` library does not support ETag validation, so `--etag=false` is the correct setting here. Remember to call `server.handleClient()` in your `loop()`.
|
|
572
|
+
|
|
573
|
+
```c
|
|
574
|
+
#include <WebServer.h>
|
|
575
|
+
#include "svelteesp32.h"
|
|
576
|
+
|
|
577
|
+
WebServer server(80);
|
|
578
|
+
|
|
579
|
+
void setup() {
|
|
580
|
+
WiFi.begin("ssid", "password");
|
|
581
|
+
while (WiFi.status() != WL_CONNECTED) delay(500);
|
|
582
|
+
initSvelteStaticFiles(&server);
|
|
583
|
+
server.begin();
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
void loop() {
|
|
587
|
+
server.handleClient();
|
|
588
|
+
}
|
|
589
|
+
```
|
|
590
|
+
|
|
591
|
+
---
|
|
592
|
+
|
|
593
|
+
### Recipe B — PlatformIO + Vite/SvelteKit, Auto-Generated Header on `pio run`
|
|
594
|
+
|
|
595
|
+
Run svelteesp32 automatically as a PlatformIO pre-build step so your header is always up to date.
|
|
596
|
+
|
|
597
|
+
**`platformio.ini`**
|
|
598
|
+
|
|
599
|
+
```ini
|
|
600
|
+
[env:esp32dev]
|
|
601
|
+
platform = espressif32
|
|
602
|
+
board = esp32dev
|
|
603
|
+
framework = arduino
|
|
604
|
+
lib_deps =
|
|
605
|
+
ESP Async WebServer
|
|
606
|
+
extra_scripts = pre:scripts/build_frontend.py
|
|
607
|
+
```
|
|
608
|
+
|
|
609
|
+
**`scripts/build_frontend.py`**
|
|
610
|
+
|
|
611
|
+
```python
|
|
612
|
+
Import("env")
|
|
613
|
+
import subprocess
|
|
614
|
+
|
|
615
|
+
def build_frontend(source, target, env):
|
|
616
|
+
print("Building frontend...")
|
|
617
|
+
subprocess.run(["npm", "run", "build"], cwd="frontend", check=True)
|
|
618
|
+
subprocess.run([
|
|
619
|
+
"npx", "svelteesp32",
|
|
620
|
+
"-e", "async",
|
|
621
|
+
"-s", "frontend/dist",
|
|
622
|
+
"-o", "src/svelteesp32.h",
|
|
623
|
+
"--etag=true", "--gzip=true",
|
|
624
|
+
"--cachetime-html=0", "--cachetime-assets=31536000"
|
|
625
|
+
], check=True)
|
|
626
|
+
|
|
627
|
+
env.AddPreAction("buildprog", build_frontend)
|
|
628
|
+
```
|
|
629
|
+
|
|
630
|
+
This ensures the C++ header is regenerated from the latest frontend build every time you run `pio run`.
|
|
631
|
+
|
|
632
|
+
---
|
|
633
|
+
|
|
634
|
+
### Recipe C — ESP-IDF CMake, 1-Year Cached Content-Hashed Assets
|
|
635
|
+
|
|
636
|
+
Integrate svelteesp32 into a CMake build so the header is regenerated automatically.
|
|
637
|
+
|
|
638
|
+
**`CMakeLists.txt` (component or top-level)**
|
|
639
|
+
|
|
640
|
+
```cmake
|
|
641
|
+
add_custom_command(
|
|
642
|
+
OUTPUT ${CMAKE_CURRENT_SOURCE_DIR}/main/svelteesp32.h
|
|
643
|
+
COMMAND npm run build
|
|
644
|
+
COMMAND npx svelteesp32
|
|
645
|
+
-e espidf
|
|
646
|
+
-s ${CMAKE_CURRENT_SOURCE_DIR}/frontend/dist
|
|
647
|
+
-o ${CMAKE_CURRENT_SOURCE_DIR}/main/svelteesp32.h
|
|
648
|
+
--etag=true --gzip=true
|
|
649
|
+
--cachetime-html=0 --cachetime-assets=31536000
|
|
650
|
+
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
|
|
651
|
+
COMMENT "Generating svelteesp32.h from frontend build"
|
|
652
|
+
VERBATIM
|
|
653
|
+
)
|
|
654
|
+
|
|
655
|
+
add_custom_target(frontend_header
|
|
656
|
+
DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/main/svelteesp32.h
|
|
657
|
+
)
|
|
658
|
+
|
|
659
|
+
# Make your main component depend on the generated header
|
|
660
|
+
add_dependencies(${COMPONENT_LIB} frontend_header)
|
|
661
|
+
```
|
|
662
|
+
|
|
663
|
+
**`main/app_main.c`**
|
|
664
|
+
|
|
665
|
+
```c
|
|
666
|
+
#include <esp_http_server.h>
|
|
667
|
+
#include "svelteesp32.h"
|
|
668
|
+
|
|
669
|
+
void app_main(void) {
|
|
670
|
+
// ... WiFi init ...
|
|
671
|
+
httpd_config_t config = HTTPD_DEFAULT_CONFIG();
|
|
672
|
+
httpd_handle_t server = NULL;
|
|
673
|
+
httpd_start(&server, &config);
|
|
674
|
+
initSvelteStaticFiles(server);
|
|
675
|
+
}
|
|
676
|
+
```
|
|
677
|
+
|
|
678
|
+
The `--cachetime-html=0 --cachetime-assets=31536000` combination gives you `no-cache` for `index.html` (so browser always checks for updates) and a 1-year `max-age` for content-hashed JS/CSS bundles.
|
|
679
|
+
|
|
543
680
|
---
|
|
544
681
|
|
|
545
682
|
## FAQ
|
package/dist/commandLine.js
CHANGED
|
@@ -94,6 +94,15 @@ function validateBasePath(value) {
|
|
|
94
94
|
throw new Error(`basePath must not end with /: ${value}`);
|
|
95
95
|
if (value.includes('//'))
|
|
96
96
|
throw new Error(`basePath must not contain //: ${value}`);
|
|
97
|
+
if (value.includes('"'))
|
|
98
|
+
throw new Error(`basePath must not contain double quotes: ${value}`);
|
|
99
|
+
if (value.includes('\\'))
|
|
100
|
+
throw new Error(`basePath must not contain backslashes: ${value}`);
|
|
101
|
+
return value;
|
|
102
|
+
}
|
|
103
|
+
function validateVersion(value) {
|
|
104
|
+
if (!/^[\w+.-]+$/.test(value))
|
|
105
|
+
throw new Error(`version must only contain word characters, dots, hyphens, and plus signs: ${value}`);
|
|
97
106
|
return value;
|
|
98
107
|
}
|
|
99
108
|
function parseSize(value, name) {
|
|
@@ -326,6 +335,8 @@ function validateRcConfig(config, rcPath) {
|
|
|
326
335
|
throw new TypeError(`Invalid dryrun in RC file: ${configObject['dryrun']} (must be boolean)`);
|
|
327
336
|
if (configObject['spa'] !== undefined && typeof configObject['spa'] !== 'boolean')
|
|
328
337
|
throw new TypeError(`Invalid spa in RC file: ${configObject['spa']} (must be boolean)`);
|
|
338
|
+
if (configObject['outputfile'] !== undefined && node_path_1.default.isAbsolute(configObject['outputfile']))
|
|
339
|
+
throw new Error(`'outputfile' in RC file must be a relative path (use --output CLI flag for absolute paths): ${configObject['outputfile']}`);
|
|
329
340
|
return configObject;
|
|
330
341
|
}
|
|
331
342
|
function parseArguments() {
|
|
@@ -346,8 +357,11 @@ function parseArguments() {
|
|
|
346
357
|
}
|
|
347
358
|
const rcPath = findRcFile(customConfigPath);
|
|
348
359
|
const rcConfig = rcPath ? loadRcFile(rcPath) : {};
|
|
349
|
-
if (rcPath)
|
|
360
|
+
if (rcPath) {
|
|
350
361
|
console.log((0, consoleColor_1.cyanLog)(`[SvelteESP32] Using config from: ${rcPath}`));
|
|
362
|
+
if (!customConfigPath && node_path_1.default.dirname(rcPath) === process.cwd())
|
|
363
|
+
console.warn((0, consoleColor_1.yellowLog)(`Warning: Loading config from current directory — verify this file is trusted before proceeding.`));
|
|
364
|
+
}
|
|
351
365
|
const result = {
|
|
352
366
|
engine: 'psychic',
|
|
353
367
|
outputfile: 'svelteesp32.h',
|
|
@@ -420,7 +434,7 @@ function parseArguments() {
|
|
|
420
434
|
result.gzip = validateTriState(value, 'gzip');
|
|
421
435
|
break;
|
|
422
436
|
case 'version':
|
|
423
|
-
result.version = value;
|
|
437
|
+
result.version = validateVersion(value);
|
|
424
438
|
break;
|
|
425
439
|
case 'espmethod':
|
|
426
440
|
result.espmethod = validateCppIdentifier(value, 'espmethod');
|
|
@@ -576,7 +590,7 @@ function formatConfiguration(cmdLine) {
|
|
|
576
590
|
parts.push(`spa=${cmdLine.spa}`);
|
|
577
591
|
if (cmdLine.exclude.length > 0)
|
|
578
592
|
parts.push(`exclude=[${cmdLine.exclude.join(', ')}]`);
|
|
579
|
-
return parts.join(' ');
|
|
593
|
+
return parts.join(' ').replace(/[\n\r]/g, ' ');
|
|
580
594
|
}
|
|
581
595
|
exports.cmdLine = parseArguments();
|
|
582
596
|
if (!(0, node_fs_1.existsSync)(exports.cmdLine.sourcepath)) {
|
package/dist/file.js
CHANGED
|
@@ -51,7 +51,12 @@ const isExcluded = (filename, excludePatterns) => {
|
|
|
51
51
|
return isMatch(normalizedFilename);
|
|
52
52
|
};
|
|
53
53
|
const getFiles = () => {
|
|
54
|
-
const allFilenames = (0, tinyglobby_1.globSync)('**/*', {
|
|
54
|
+
const allFilenames = (0, tinyglobby_1.globSync)('**/*', {
|
|
55
|
+
cwd: commandLine_1.cmdLine.sourcepath,
|
|
56
|
+
onlyFiles: true,
|
|
57
|
+
dot: false,
|
|
58
|
+
followSymbolicLinks: false
|
|
59
|
+
});
|
|
55
60
|
const withoutCompressed = allFilenames.filter((filename) => !shouldSkipFile(filename, allFilenames));
|
|
56
61
|
const excludePatterns = commandLine_1.cmdLine.exclude || [];
|
|
57
62
|
const excludedFiles = [];
|
|
@@ -73,9 +78,13 @@ const getFiles = () => {
|
|
|
73
78
|
}
|
|
74
79
|
else if (excludePatterns.length > 0)
|
|
75
80
|
console.log((0, consoleColor_1.yellowLog)(`Warning: --exclude patterns matched no files: ${excludePatterns.join(', ')}`));
|
|
81
|
+
const MAX_FILE_SIZE = 50 * 1024 * 1024;
|
|
76
82
|
const result = new Map();
|
|
77
83
|
for (const filename of filenames) {
|
|
78
84
|
const filePath = node_path_1.default.join(commandLine_1.cmdLine.sourcepath, filename);
|
|
85
|
+
const fileSize = (0, node_fs_1.statSync)(filePath).size;
|
|
86
|
+
if (fileSize > MAX_FILE_SIZE)
|
|
87
|
+
throw new Error(`File too large (${Math.round(fileSize / 1024)}kB): ${filename}. Maximum single-file size is 50MB.`);
|
|
79
88
|
const content = (0, node_fs_1.readFileSync)(filePath, { flag: 'r' });
|
|
80
89
|
const hash = (0, node_crypto_1.createHash)('sha256').update(content).digest('hex');
|
|
81
90
|
result.set(filename, { content, hash });
|
package/dist/index.js
CHANGED
|
@@ -115,9 +115,10 @@ function main() {
|
|
|
115
115
|
const longestFilename = [...files.keys()].reduce((p, c) => Math.max(c.length, p), 0);
|
|
116
116
|
for (const [originalFilename, fileData] of files) {
|
|
117
117
|
const { content, hash: sha256 } = fileData;
|
|
118
|
-
const
|
|
119
|
-
if (!
|
|
118
|
+
const rawMime = (0, mime_types_1.lookup)(originalFilename);
|
|
119
|
+
if (!rawMime)
|
|
120
120
|
console.log((0, consoleColor_1.yellowLog)(` [${originalFilename}] unknown MIME type for extension '${node_path_1.default.extname(originalFilename)}', using text/plain`));
|
|
121
|
+
const mimeType = rawMime || 'text/plain';
|
|
121
122
|
summary.filecount++;
|
|
122
123
|
const filename = originalFilename.replace(/\\/g, '/');
|
|
123
124
|
let dataname = filename.replace(/\W/g, '_');
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "svelteesp32",
|
|
3
|
-
"version": "2.3.
|
|
3
|
+
"version": "2.3.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",
|
|
@@ -64,25 +64,25 @@
|
|
|
64
64
|
"@types/mime-types": "^3.0.1",
|
|
65
65
|
"@types/node": "^25.5.0",
|
|
66
66
|
"@types/picomatch": "^4.0.2",
|
|
67
|
-
"@typescript-eslint/eslint-plugin": "^8.57.
|
|
68
|
-
"@typescript-eslint/parser": "^8.57.
|
|
69
|
-
"@vitest/coverage-v8": "^4.1.
|
|
70
|
-
"eslint": "^10.0
|
|
67
|
+
"@typescript-eslint/eslint-plugin": "^8.57.2",
|
|
68
|
+
"@typescript-eslint/parser": "^8.57.2",
|
|
69
|
+
"@vitest/coverage-v8": "^4.1.2",
|
|
70
|
+
"eslint": "^10.1.0",
|
|
71
71
|
"eslint-config-prettier": "^10.1.8",
|
|
72
72
|
"eslint-plugin-simple-import-sort": "^12.1.1",
|
|
73
|
-
"eslint-plugin-unicorn": "^
|
|
74
|
-
"memfs": "^4.
|
|
73
|
+
"eslint-plugin-unicorn": "^64.0.0",
|
|
74
|
+
"memfs": "^4.57.1",
|
|
75
75
|
"nodemon": "^3.1.14",
|
|
76
76
|
"prettier": "^3.8.1",
|
|
77
77
|
"ts-node": "^10.9.2",
|
|
78
78
|
"tsx": "^4.21.0",
|
|
79
79
|
"typescript": "^5.9.3",
|
|
80
|
-
"vitest": "^4.1.
|
|
80
|
+
"vitest": "^4.1.2"
|
|
81
81
|
},
|
|
82
82
|
"dependencies": {
|
|
83
|
-
"handlebars": "^4.7.
|
|
83
|
+
"handlebars": "^4.7.9",
|
|
84
84
|
"mime-types": "^3.0.2",
|
|
85
|
-
"picomatch": "^4.0.
|
|
85
|
+
"picomatch": "^4.0.4",
|
|
86
86
|
"tinyglobby": "^0.2.15"
|
|
87
87
|
}
|
|
88
88
|
}
|