svelteesp32 1.15.0 → 1.16.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 +21 -21
- package/dist/commandLine.d.ts +6 -1
- package/dist/commandLine.js +75 -5
- package/dist/errorMessages.d.ts +1 -0
- package/dist/errorMessages.js +28 -1
- package/dist/index.js +8 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -75,7 +75,7 @@ void setup() {
|
|
|
75
75
|
|
|
76
76
|
## What's New
|
|
77
77
|
|
|
78
|
-
- **v1.15.0** — `--
|
|
78
|
+
- **v1.15.0** — `--basepath` for multiple frontends (e.g., `/admin`, `/app`)
|
|
79
79
|
- **v1.13.0** — npm package variable interpolation in RC files
|
|
80
80
|
- **v1.12.0** — RC file configuration support
|
|
81
81
|
- **v1.11.0** — File exclusion patterns
|
|
@@ -301,10 +301,10 @@ Fine-tune how browsers cache your content:
|
|
|
301
301
|
|
|
302
302
|
Your `index.html` is automatically served at the root URL — just like any web server. Visit `http://esp32.local/` and your app loads.
|
|
303
303
|
|
|
304
|
-
**API-only projects?** Skip index validation with `--
|
|
304
|
+
**API-only projects?** Skip index validation with `--noindexcheck`:
|
|
305
305
|
|
|
306
306
|
```bash
|
|
307
|
-
npx svelteesp32 -e psychic -s ./dist -o ./output.h --
|
|
307
|
+
npx svelteesp32 -e psychic -s ./dist -o ./output.h --noindexcheck
|
|
308
308
|
```
|
|
309
309
|
|
|
310
310
|
### File Exclusion
|
|
@@ -335,8 +335,8 @@ Excluded 3 file(s):
|
|
|
335
335
|
Serve multiple web apps from one ESP32 using URL prefixes:
|
|
336
336
|
|
|
337
337
|
```bash
|
|
338
|
-
npx svelteesp32 -s ./admin-dist -o ./admin.h --
|
|
339
|
-
npx svelteesp32 -s ./user-dist -o ./user.h --
|
|
338
|
+
npx svelteesp32 -s ./admin-dist -o ./admin.h --basepath=/admin
|
|
339
|
+
npx svelteesp32 -s ./user-dist -o ./user.h --basepath=/app
|
|
340
340
|
```
|
|
341
341
|
|
|
342
342
|
```c
|
|
@@ -402,22 +402,22 @@ Called for every response (200 = content served, 304 = cache hit).
|
|
|
402
402
|
|
|
403
403
|
## CLI Reference
|
|
404
404
|
|
|
405
|
-
| Option
|
|
406
|
-
|
|
|
407
|
-
| `-s`
|
|
408
|
-
| `-e`
|
|
409
|
-
| `-o`
|
|
410
|
-
| `--etag`
|
|
411
|
-
| `--gzip`
|
|
412
|
-
| `--exclude`
|
|
413
|
-
| `--
|
|
414
|
-
| `--cachetime`
|
|
415
|
-
| `--version`
|
|
416
|
-
| `--define`
|
|
417
|
-
| `--espmethod`
|
|
418
|
-
| `--config`
|
|
419
|
-
| `--
|
|
420
|
-
| `-h`
|
|
405
|
+
| Option | Description | Default |
|
|
406
|
+
| ---------------- | ------------------------------------------------- | ----------------------- |
|
|
407
|
+
| `-s` | Source folder with compiled web files | (required) |
|
|
408
|
+
| `-e` | Web server engine (psychic/psychic2/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
|
+
| `--cachetime` | Cache-Control max-age in seconds | `0` |
|
|
415
|
+
| `--version` | Version string in header | (none) |
|
|
416
|
+
| `--define` | C++ define prefix | `SVELTEESP32` |
|
|
417
|
+
| `--espmethod` | Init function name | `initSvelteStaticFiles` |
|
|
418
|
+
| `--config` | Custom RC file path | `.svelteesp32rc.json` |
|
|
419
|
+
| `--noindexcheck` | Skip index.html validation | `false` |
|
|
420
|
+
| `-h` | Show help | |
|
|
421
421
|
|
|
422
422
|
---
|
|
423
423
|
|
package/dist/commandLine.d.ts
CHANGED
|
@@ -11,6 +11,8 @@ interface ICopyFilesArguments {
|
|
|
11
11
|
version: string;
|
|
12
12
|
exclude: string[];
|
|
13
13
|
basePath: string;
|
|
14
|
+
maxSize?: number;
|
|
15
|
+
maxGzipSize?: number;
|
|
14
16
|
noIndexCheck?: boolean;
|
|
15
17
|
help?: boolean;
|
|
16
18
|
}
|
|
@@ -27,10 +29,13 @@ interface IRcFileConfig {
|
|
|
27
29
|
version?: string;
|
|
28
30
|
exclude?: string[];
|
|
29
31
|
basePath?: string;
|
|
32
|
+
maxSize?: number | string;
|
|
33
|
+
maxGzipSize?: number | string;
|
|
30
34
|
}
|
|
35
|
+
declare function parseSize(value: string, name: string): number;
|
|
31
36
|
declare function getNpmPackageVariable(packageJson: Record<string, unknown>, variableName: string): string | undefined;
|
|
32
37
|
declare function hasNpmVariables(config: IRcFileConfig): boolean;
|
|
33
38
|
declare function interpolateNpmVariables(config: IRcFileConfig, rcFilePath: string): IRcFileConfig;
|
|
34
|
-
export { getNpmPackageVariable, hasNpmVariables, interpolateNpmVariables };
|
|
39
|
+
export { getNpmPackageVariable, hasNpmVariables, interpolateNpmVariables, parseSize };
|
|
35
40
|
export declare function formatConfiguration(cmdLine: ICopyFilesArguments): string;
|
|
36
41
|
export declare const cmdLine: ICopyFilesArguments;
|
package/dist/commandLine.js
CHANGED
|
@@ -7,6 +7,7 @@ exports.cmdLine = void 0;
|
|
|
7
7
|
exports.getNpmPackageVariable = getNpmPackageVariable;
|
|
8
8
|
exports.hasNpmVariables = hasNpmVariables;
|
|
9
9
|
exports.interpolateNpmVariables = interpolateNpmVariables;
|
|
10
|
+
exports.parseSize = parseSize;
|
|
10
11
|
exports.formatConfiguration = formatConfiguration;
|
|
11
12
|
const node_fs_1 = require("node:fs");
|
|
12
13
|
const node_os_1 = require("node:os");
|
|
@@ -34,7 +35,9 @@ Options:
|
|
|
34
35
|
--cachetime <seconds> max-age cache time in seconds (default: 0)
|
|
35
36
|
--exclude <pattern> Exclude files matching glob pattern (repeatable or comma-separated)
|
|
36
37
|
Examples: --exclude="*.map" --exclude="test/**/*.ts"
|
|
37
|
-
--
|
|
38
|
+
--basepath <path> URL prefix for all routes (e.g., "/ui") (default: "")
|
|
39
|
+
--maxsize <size> Maximum total uncompressed size (e.g., 400k, 1.5m, 409600)
|
|
40
|
+
--maxgzipsize <size> Maximum total gzip size (e.g., 150k, 1m, 153600)
|
|
38
41
|
-h, --help Shows this help
|
|
39
42
|
|
|
40
43
|
RC File:
|
|
@@ -78,6 +81,25 @@ function validateBasePath(value) {
|
|
|
78
81
|
throw new Error(`basePath must not contain //: ${value}`);
|
|
79
82
|
return value;
|
|
80
83
|
}
|
|
84
|
+
function parseSize(value, name) {
|
|
85
|
+
const trimmed = value.trim();
|
|
86
|
+
const match = trimmed.match(/^(\d+(?:\.\d+)?)\s*([KMkm])?$/);
|
|
87
|
+
if (!match || !match[1])
|
|
88
|
+
throw new Error(`${name} must be a positive number with optional k/K (×1024) or m/M (×1024²) suffix: ${value}`);
|
|
89
|
+
const numericPart = Number.parseFloat(match[1]);
|
|
90
|
+
const suffix = match[2]?.toLowerCase();
|
|
91
|
+
let bytes;
|
|
92
|
+
if (suffix === 'k')
|
|
93
|
+
bytes = numericPart * 1024;
|
|
94
|
+
else if (suffix === 'm')
|
|
95
|
+
bytes = numericPart * 1024 * 1024;
|
|
96
|
+
else
|
|
97
|
+
bytes = numericPart;
|
|
98
|
+
bytes = Math.round(bytes);
|
|
99
|
+
if (bytes <= 0 || !Number.isFinite(bytes))
|
|
100
|
+
throw new Error(`${name} must be a positive integer: ${value}`);
|
|
101
|
+
return bytes;
|
|
102
|
+
}
|
|
81
103
|
const DEFAULT_EXCLUDE_PATTERNS = [
|
|
82
104
|
'.DS_Store',
|
|
83
105
|
'Thumbs.db',
|
|
@@ -229,7 +251,9 @@ function validateRcConfig(config, rcPath) {
|
|
|
229
251
|
'created',
|
|
230
252
|
'version',
|
|
231
253
|
'exclude',
|
|
232
|
-
'basePath'
|
|
254
|
+
'basePath',
|
|
255
|
+
'maxSize',
|
|
256
|
+
'maxGzipSize'
|
|
233
257
|
]);
|
|
234
258
|
for (const key of Object.keys(configObject))
|
|
235
259
|
if (!validKeys.has(key))
|
|
@@ -250,6 +274,30 @@ function validateRcConfig(config, rcPath) {
|
|
|
250
274
|
if (typeof pattern !== 'string')
|
|
251
275
|
throw new TypeError('All exclude patterns must be strings');
|
|
252
276
|
}
|
|
277
|
+
if (configObject['maxSize'] !== undefined) {
|
|
278
|
+
const value = configObject['maxSize'];
|
|
279
|
+
if (typeof value === 'string')
|
|
280
|
+
try {
|
|
281
|
+
configObject['maxSize'] = parseSize(value, 'maxSize');
|
|
282
|
+
}
|
|
283
|
+
catch {
|
|
284
|
+
throw new TypeError(`Invalid maxSize in RC file: ${value} (must be a positive number with optional k/m suffix)`);
|
|
285
|
+
}
|
|
286
|
+
else if (typeof value !== 'number' || Number.isNaN(value) || value <= 0)
|
|
287
|
+
throw new TypeError(`Invalid maxSize in RC file: ${value} (must be a positive number)`);
|
|
288
|
+
}
|
|
289
|
+
if (configObject['maxGzipSize'] !== undefined) {
|
|
290
|
+
const value = configObject['maxGzipSize'];
|
|
291
|
+
if (typeof value === 'string')
|
|
292
|
+
try {
|
|
293
|
+
configObject['maxGzipSize'] = parseSize(value, 'maxGzipSize');
|
|
294
|
+
}
|
|
295
|
+
catch {
|
|
296
|
+
throw new TypeError(`Invalid maxGzipSize in RC file: ${value} (must be a positive number with optional k/m suffix)`);
|
|
297
|
+
}
|
|
298
|
+
else if (typeof value !== 'number' || Number.isNaN(value) || value <= 0)
|
|
299
|
+
throw new TypeError(`Invalid maxGzipSize in RC file: ${value} (must be a positive number)`);
|
|
300
|
+
}
|
|
253
301
|
return configObject;
|
|
254
302
|
}
|
|
255
303
|
function parseArguments() {
|
|
@@ -307,6 +355,10 @@ function parseArguments() {
|
|
|
307
355
|
result.define = rcConfig.define;
|
|
308
356
|
if (rcConfig.basePath !== undefined)
|
|
309
357
|
result.basePath = validateBasePath(rcConfig.basePath);
|
|
358
|
+
if (rcConfig.maxSize !== undefined)
|
|
359
|
+
result.maxSize = rcConfig.maxSize;
|
|
360
|
+
if (rcConfig.maxGzipSize !== undefined)
|
|
361
|
+
result.maxGzipSize = rcConfig.maxGzipSize;
|
|
310
362
|
if (rcConfig.exclude && rcConfig.exclude.length > 0)
|
|
311
363
|
result.exclude = [...rcConfig.exclude];
|
|
312
364
|
const cliExclude = [];
|
|
@@ -363,9 +415,15 @@ function parseArguments() {
|
|
|
363
415
|
cliExclude.push(...patterns);
|
|
364
416
|
break;
|
|
365
417
|
}
|
|
366
|
-
case '
|
|
418
|
+
case 'basepath':
|
|
367
419
|
result.basePath = validateBasePath(value);
|
|
368
420
|
break;
|
|
421
|
+
case 'maxsize':
|
|
422
|
+
result.maxSize = parseSize(value, '--maxsize');
|
|
423
|
+
break;
|
|
424
|
+
case 'maxgzipsize':
|
|
425
|
+
result.maxGzipSize = parseSize(value, '--maxgzipsize');
|
|
426
|
+
break;
|
|
369
427
|
default:
|
|
370
428
|
throw new Error(`Unknown flag: ${flag}`);
|
|
371
429
|
}
|
|
@@ -375,7 +433,7 @@ function parseArguments() {
|
|
|
375
433
|
result.created = true;
|
|
376
434
|
continue;
|
|
377
435
|
}
|
|
378
|
-
if (argument === '--
|
|
436
|
+
if (argument === '--noindexcheck') {
|
|
379
437
|
result.noIndexCheck = true;
|
|
380
438
|
continue;
|
|
381
439
|
}
|
|
@@ -458,10 +516,18 @@ function parseArguments() {
|
|
|
458
516
|
index++;
|
|
459
517
|
break;
|
|
460
518
|
}
|
|
461
|
-
case '
|
|
519
|
+
case 'basepath':
|
|
462
520
|
result.basePath = validateBasePath(nextArgument);
|
|
463
521
|
index++;
|
|
464
522
|
break;
|
|
523
|
+
case 'maxsize':
|
|
524
|
+
result.maxSize = parseSize(nextArgument, '--maxsize');
|
|
525
|
+
index++;
|
|
526
|
+
break;
|
|
527
|
+
case 'maxgzipsize':
|
|
528
|
+
result.maxGzipSize = parseSize(nextArgument, '--maxgzipsize');
|
|
529
|
+
index++;
|
|
530
|
+
break;
|
|
465
531
|
default:
|
|
466
532
|
throw new Error(`Unknown flag: --${flag}`);
|
|
467
533
|
}
|
|
@@ -496,6 +562,10 @@ function formatConfiguration(cmdLine) {
|
|
|
496
562
|
parts.push(`define=${cmdLine.define}`);
|
|
497
563
|
if (cmdLine.basePath)
|
|
498
564
|
parts.push(`basePath=${cmdLine.basePath}`);
|
|
565
|
+
if (cmdLine.maxSize !== undefined)
|
|
566
|
+
parts.push(`maxSize=${cmdLine.maxSize}`);
|
|
567
|
+
if (cmdLine.maxGzipSize !== undefined)
|
|
568
|
+
parts.push(`maxGzipSize=${cmdLine.maxGzipSize}`);
|
|
499
569
|
if (cmdLine.exclude.length > 0)
|
|
500
570
|
parts.push(`exclude=[${cmdLine.exclude.join(', ')}]`);
|
|
501
571
|
return parts.join(' ');
|
package/dist/errorMessages.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export declare function getMissingIndexError(engine: string): string;
|
|
2
2
|
export declare function getInvalidEngineError(attempted: string): string;
|
|
3
3
|
export declare function getSourcepathNotFoundError(sourcepath: string, reason: 'not_found' | 'not_directory'): string;
|
|
4
|
+
export declare function getSizeBudgetExceededError(type: 'size' | 'gzipSize', limit: number, actual: number): string;
|
|
4
5
|
export declare function getMaxUriHandlersHint(engine: string, routeCount: number): string;
|
package/dist/errorMessages.js
CHANGED
|
@@ -6,6 +6,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
6
6
|
exports.getMissingIndexError = getMissingIndexError;
|
|
7
7
|
exports.getInvalidEngineError = getInvalidEngineError;
|
|
8
8
|
exports.getSourcepathNotFoundError = getSourcepathNotFoundError;
|
|
9
|
+
exports.getSizeBudgetExceededError = getSizeBudgetExceededError;
|
|
9
10
|
exports.getMaxUriHandlersHint = getMaxUriHandlersHint;
|
|
10
11
|
const node_path_1 = __importDefault(require("node:path"));
|
|
11
12
|
const consoleColor_1 = require("./consoleColor");
|
|
@@ -45,7 +46,7 @@ How to fix (for ${getEngineName(engine)}):
|
|
|
45
46
|
${hint}
|
|
46
47
|
|
|
47
48
|
Alternative:
|
|
48
|
-
If you use a different entry point (e.g., main.html), you can add --
|
|
49
|
+
If you use a different entry point (e.g., main.html), you can add --noindexcheck flag,
|
|
49
50
|
but users must navigate to http://your-esp32/main.html explicitly.`);
|
|
50
51
|
}
|
|
51
52
|
function getInvalidEngineError(attempted) {
|
|
@@ -109,6 +110,32 @@ How to fix:
|
|
|
109
110
|
Current directory: ${currentDirectory}
|
|
110
111
|
Attempted path: ${resolvedPath} (resolved)`);
|
|
111
112
|
}
|
|
113
|
+
function getSizeBudgetExceededError(type, limit, actual) {
|
|
114
|
+
const typeLabel = type === 'size' ? 'Uncompressed' : 'Gzip';
|
|
115
|
+
const flagName = type === 'size' ? '--maxsize' : '--maxgzipsize';
|
|
116
|
+
const overage = actual - limit;
|
|
117
|
+
const overagePercent = Math.round((overage / limit) * 100);
|
|
118
|
+
return ((0, consoleColor_1.redLog)(`[ERROR] ${typeLabel} size budget exceeded`) +
|
|
119
|
+
`
|
|
120
|
+
|
|
121
|
+
Budget: ${limit.toLocaleString()} bytes
|
|
122
|
+
Actual: ${actual.toLocaleString()} bytes
|
|
123
|
+
Overage: ${overage.toLocaleString()} bytes (+${overagePercent}%)
|
|
124
|
+
|
|
125
|
+
Why this matters:
|
|
126
|
+
Size budgets help prevent frontend bloat and ensure your application
|
|
127
|
+
fits within ESP32 flash memory constraints.
|
|
128
|
+
|
|
129
|
+
How to fix:
|
|
130
|
+
1. Reduce bundle size (tree-shaking, code splitting, smaller dependencies)
|
|
131
|
+
2. Optimize assets (compress images, minify CSS/JS)
|
|
132
|
+
3. Remove unused files with --exclude patterns
|
|
133
|
+
4. Increase the budget: ${flagName}=${actual}
|
|
134
|
+
|
|
135
|
+
CI integration:
|
|
136
|
+
This non-zero exit code allows build pipelines to fail early when
|
|
137
|
+
the frontend exceeds allocated flash space.`);
|
|
138
|
+
}
|
|
112
139
|
function getMaxUriHandlersHint(engine, routeCount) {
|
|
113
140
|
const recommended = routeCount + 5;
|
|
114
141
|
const hints = {
|
package/dist/index.js
CHANGED
|
@@ -84,6 +84,14 @@ for (const [originalFilename, content] of files) {
|
|
|
84
84
|
}
|
|
85
85
|
console.log('');
|
|
86
86
|
filesByExtension.sort((left, right) => left.extension.localeCompare(right.extension));
|
|
87
|
+
if (commandLine_1.cmdLine.maxSize !== undefined && summary.size > commandLine_1.cmdLine.maxSize) {
|
|
88
|
+
console.error((0, errorMessages_1.getSizeBudgetExceededError)('size', commandLine_1.cmdLine.maxSize, summary.size));
|
|
89
|
+
process.exit(1);
|
|
90
|
+
}
|
|
91
|
+
if (commandLine_1.cmdLine.maxGzipSize !== undefined && summary.gzipsize > commandLine_1.cmdLine.maxGzipSize) {
|
|
92
|
+
console.error((0, errorMessages_1.getSizeBudgetExceededError)('gzipSize', commandLine_1.cmdLine.maxGzipSize, summary.gzipsize));
|
|
93
|
+
process.exit(1);
|
|
94
|
+
}
|
|
87
95
|
const cppFile = (0, cppCode_1.getCppCode)(sources, filesByExtension);
|
|
88
96
|
(0, node_fs_1.mkdirSync)(node_path_1.default.normalize(node_path_1.default.dirname(commandLine_1.cmdLine.outputfile)), { recursive: true });
|
|
89
97
|
(0, node_fs_1.writeFileSync)(commandLine_1.cmdLine.outputfile, cppFile, { flush: true, encoding: 'utf8' });
|