svelteesp32 1.2.7 → 1.3.1

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
@@ -14,6 +14,8 @@ This npm package provides a solution for **inserting any JS client application i
14
14
 
15
15
  > Starting with version v1.2.0, ESP8266/ESP8285 is also supported.
16
16
 
17
+ > Starting with version v1.3.0, c++ defines can be used.
18
+
17
19
  ### Usage
18
20
 
19
21
  **Install package** as devDependency (it is practical if the package is part of the project so that you always receive updates)
@@ -98,9 +100,17 @@ void setup()
98
100
  }
99
101
  ```
100
102
 
103
+ You can find a minimal buildable example platformio project in [demo/esp32](demo/esp32) folder.
104
+
101
105
  The content of **generated file** (do not edit, just use)
102
106
 
103
107
  ```c
108
+ #define SVELTEESP32_COUNT 5
109
+ #define SVELTEESP32_SIZE 145633
110
+ #define SVELTEESP32_FILE_INDEX_HTML
111
+ #define SVELTEESP32_HTML_FILES 1
112
+ ...
113
+
104
114
  const uint8_t data0[12547] = {0x1f, 0x8b, 0x8, 0x0, ...
105
115
  const uint8_t data1[5368] = {0x1f, 0x8b, 0x8, 0x0, 0x0, ...
106
116
  const char * etag0 = "387b88e345cc56ef9091...";
@@ -164,17 +174,58 @@ The use of ETag is **not enabled by default**, this can be achieved with the `--
164
174
 
165
175
  Typically, the entry point for web applications is the **index.htm or index.html** file. This does not need to be listed in the browser's address bar because web servers know that this file should be served by default. Svelteesp32 also does this: if there is an index.htm or index.html file, it sets it as the main file to be served. So using `http://esp_xxx.local` or just entering the `http://x.y.w.z/` IP address will serve this main file.
166
176
 
177
+ ### C++ defines
178
+
179
+ To make it easy to integrate into a larger c++ project, we have made a couple of variables available as c++ defines.
180
+
181
+ You can use the COUNT and SIZE constants:
182
+
183
+ ```c
184
+ ...
185
+ #include "svelteesp32.h"
186
+
187
+ #if SVELTEESP32_COUNT != 5
188
+ #error Invalid file count
189
+ #endif
190
+ ...
191
+ ```
192
+
193
+ You can include a blocker error if a named file accidentally missing from the build:
194
+
195
+ ```c
196
+ ...
197
+ #include "svelteesp32.h"
198
+
199
+ #ifndef SVELTEESP32_FILE_INDEX_HTML
200
+ #error Missing index file
201
+ #endif
202
+ ...
203
+ ```
204
+
205
+ ...or if there are too many of a file type:
206
+
207
+ ```c
208
+ ...
209
+ #include "svelteesp32.h"
210
+
211
+ #if SVELTEESP32_CSS_FILES > 1
212
+ #error Too many CSS files
213
+ #endif
214
+ ...
215
+ ```
216
+
167
217
  ### Command line options
168
218
 
169
- | Option | Required | Description | default |
170
- | ------------- | :------: | ---------------------------------------------------------------- | ----------------------- |
171
- | `-e` | x | The engine for which the include file is created (psychic/async) | psychic |
172
- | `-s` | x | Source dist folder contains compiled web files | |
173
- | `-o` | x | Generated output file with path | `svelteesp32.h` |
174
- | `--etag` | | Use ETag header for cache | false |
175
- | `--no-gzip` | | Do not compress content with gzip | false -> gzip used |
176
- | `--espmethod` | x | Name of generated method | `initSvelteStaticFiles` |
177
- | `-h` | | Show help | |
219
+ | Option | Description | default |
220
+ | ------------- | --------------------------------------------------------- | ----------------------- | ------- |
221
+ | `-s` | **Source dist folder contains compiled web files** | |
222
+ | `-e` | The engine for which the include file is created [psychic | async] | psychic |
223
+ | `-o` | Generated output file with path | `svelteesp32.h` |
224
+ | `--etag` | Use ETag header for cache | false |
225
+ | `--no-gzip` | Do not compress content with gzip | false -> gzip used |
226
+ | `--espmethod` | Name of generated method | `initSvelteStaticFiles` |
227
+ | `--define` | Prefix of c++ defines | `SVELTEESP32` |
228
+ | `-h` | Show help | |
178
229
 
179
230
  ### Q&A
180
231
 
@@ -3,6 +3,7 @@ interface ICopyFilesArguments {
3
3
  sourcepath: string;
4
4
  outputfile: string;
5
5
  espmethod: string;
6
+ define: string;
6
7
  'no-gzip': boolean;
7
8
  etag: boolean;
8
9
  help?: boolean;
@@ -42,6 +42,11 @@ exports.cmdLine = (0, ts_command_line_args_1.parse)({
42
42
  description: 'Name of generated method',
43
43
  defaultValue: 'initSvelteStaticFiles'
44
44
  },
45
+ define: {
46
+ type: String,
47
+ description: 'Prefix of c++ defines',
48
+ defaultValue: 'SVELTEESP32'
49
+ },
45
50
  help: { type: Boolean, optional: true, alias: 'h', description: 'Shows this help' }
46
51
  }, {
47
52
  helpArg: 'help',
package/dist/cppCode.d.ts CHANGED
@@ -1,9 +1,16 @@
1
- export type cppCodeSource = {
2
- index: number;
1
+ export type CppCodeSource = {
3
2
  filename: string;
3
+ dataname: string;
4
+ datanameUpperCase: string;
4
5
  mime: string;
5
6
  content: Buffer;
6
7
  isGzip: boolean;
7
8
  md5: string;
8
9
  };
9
- export declare const getCppCode: (sources: cppCodeSource[]) => string;
10
+ export type CppCodeSources = CppCodeSource[];
11
+ export type ExtensionGroup = {
12
+ extension: string;
13
+ count: number;
14
+ };
15
+ export type ExtensionGroups = ExtensionGroup[];
16
+ export declare const getCppCode: (sources: CppCodeSources, filesByExtension: ExtensionGroups) => string;
package/dist/cppCode.js CHANGED
@@ -14,17 +14,21 @@ const psychicTemplate = `
14
14
  #include <PsychicHttp.h>
15
15
  #include <PsychicHttpsServer.h>
16
16
 
17
+ #define {{definePrefix}}_COUNT {{fileCount}}
18
+ #define {{definePrefix}}_SIZE {{fileSize}}
19
+
17
20
  {{#each sources}}
18
- {{#if this.isDefault}}
19
- const uint8_t dataDefaultDocument[{{this.length}}] = { {{this.bytes}} };
20
- {{#if ../isEtag}}
21
- const char * etagDefaultDocument = "{{this.md5}}";
22
- {{/if}}
23
- {{else}}
24
- const uint8_t data{{this.index}}[{{this.length}}] = { {{this.bytes}} };
21
+ #define {{../definePrefix}}_FILE_{{this.datanameUpperCase}}
22
+ {{/each}}
23
+
24
+ {{#each filesByExtension}}
25
+ #define {{../definePrefix}}_{{this.extension}}_FILES {{this.count}}
26
+ {{/each}}
27
+
28
+ {{#each sources}}
29
+ const uint8_t data_{{this.dataname}}[{{this.length}}] = { {{this.bytes}} };
25
30
  {{#if ../isEtag}}
26
- const char * etag{{this.index}} = "{{this.md5}}";
27
- {{/if}}
31
+ const char * etag_{{this.dataname}} = "{{this.md5}}";
28
32
  {{/if}}
29
33
  {{/each}}
30
34
 
@@ -32,7 +36,7 @@ void {{methodName}}(PsychicHttpServer * server) {
32
36
  {{#each sources}}
33
37
  {{#if this.isDefault}}server->defaultEndpoint = {{/if}}server->on("/{{this.filename}}", HTTP_GET, [](PsychicRequest * request) {
34
38
  {{#if ../isEtag}}
35
- if (request->hasHeader("If-None-Match") && request->header("If-None-Match") == String({{#if this.isDefault}}etagDefaultDocument{{else}}etag{{this.index}}{{/if}})) {
39
+ if (request->hasHeader("If-None-Match") && request->header("If-None-Match") == String(etag_{{this.dataname}})) {
36
40
  PsychicResponse response304(request);
37
41
  response304.setCode(304);
38
42
  return response304.send();
@@ -44,9 +48,9 @@ void {{methodName}}(PsychicHttpServer * server) {
44
48
  response.addHeader("Content-Encoding", "gzip");
45
49
  {{/if}}
46
50
  {{#if ../isEtag}}
47
- response.addHeader("ETag", {{#if this.isDefault}}etagDefaultDocument{{else}}etag{{this.index}}{{/if}});
51
+ response.addHeader("ETag", etag_{{this.dataname}});
48
52
  {{/if}}
49
- response.setContent({{#if this.isDefault}}dataDefaultDocument{{else}}data{{this.index}}{{/if}}, sizeof({{#if this.isDefault}}dataDefaultDocument{{else}}data{{this.index}}{{/if}}));
53
+ response.setContent(data_{{this.dataname}}, sizeof(data_{{this.dataname}}));
50
54
  return response.send();
51
55
  });
52
56
 
@@ -62,48 +66,50 @@ const asyncTemplate = `
62
66
  #include <Arduino.h>
63
67
  #include <ESPAsyncWebServer.h>
64
68
 
69
+ #define {{definePrefix}}_COUNT {{fileCount}}
70
+ #define {{definePrefix}}_SIZE {{fileSize}}
71
+
65
72
  {{#each sources}}
66
- {{#if this.isDefault}}
67
- const uint8_t dataDefaultDocument[{{this.length}}] = { {{this.bytes}} };
68
- {{#if ../isEtag}}
69
- const char * etagDefaultDocument = "{{this.md5}}";
70
- {{/if}}
71
- {{else}}
72
- const uint8_t data{{this.index}}[{{this.length}}] PROGMEM = { {{this.bytes}} };
73
+ #define {{../definePrefix}}_FILE_{{this.datanameUpperCase}}
74
+ {{/each}}
75
+
76
+ {{#each filesByExtension}}
77
+ #define {{../definePrefix}}_{{this.extension}}_FILES {{this.count}}
78
+ {{/each}}
79
+
80
+ {{#each sources}}
81
+ const uint8_t data_{{this.dataname}}[{{this.length}}] PROGMEM = { {{this.bytes}} };
73
82
  {{#if ../isEtag}}
74
- const char * etag{{this.index}} = "{{this.md5}}";
75
- {{/if}}
83
+ const char * etag_{{this.dataname}} = "{{this.md5}}";
76
84
  {{/if}}
77
85
  {{/each}}
78
86
 
79
87
  void {{methodName}}(AsyncWebServer * server) {
80
88
  {{#each sources}}
81
- ArRequestHandlerFunction {{#if this.isDefault}}funcDefaultDocument{{else}}func{{this.index}}{{/if}} = [](AsyncWebServerRequest * request) {
89
+ ArRequestHandlerFunction func_{{this.dataname}} = [](AsyncWebServerRequest * request) {
82
90
  {{#if ../isEtag}}
83
- if (request->hasHeader("If-None-Match") && request->getHeader("If-None-Match")->value() == String({{#if this.isDefault}}etagDefaultDocument{{else}}etag{{this.index}}{{/if}})) {
91
+ if (request->hasHeader("If-None-Match") && request->getHeader("If-None-Match")->value() == String(etag_{{this.dataname}})) {
84
92
  request->send(304);
85
93
  return;
86
94
  }
87
95
  {{/if}}
88
- AsyncWebServerResponse *response = request->beginResponse_P(200, "{{this.mime}}", {{#if this.isDefault}}dataDefaultDocument{{else}}data{{this.index}}{{/if}}, {{this.length}});
96
+ AsyncWebServerResponse *response = request->beginResponse_P(200, "{{this.mime}}", data_{{this.dataname}}, {{this.length}});
89
97
  {{#if this.isGzip}}
90
98
  response->addHeader("Content-Encoding", "gzip");
91
99
  {{/if}}
92
100
  {{#if ../isEtag}}
93
- response->addHeader("ETag", {{#if this.isDefault}}etagDefaultDocument{{else}}etag{{this.index}}{{/if}});
101
+ response->addHeader("ETag", etag_{{this.dataname}});
94
102
  {{/if}}
95
103
  request->send(response);
96
104
  };
105
+ server->on("/{{this.filename}}", HTTP_GET, func_{{this.dataname}});
97
106
  {{#if this.isDefault}}
98
- server->on("/{{this.filename}}", HTTP_GET, funcDefaultDocument);
99
- server->on("/", HTTP_GET, funcDefaultDocument);
100
- {{else}}
101
- server->on("/{{this.filename}}", HTTP_GET, func{{this.index}});
107
+ server->on("/", HTTP_GET, func_{{this.dataname}});
102
108
  {{/if}}
103
109
 
104
110
  {{/each}}
105
111
  }`;
106
- const getCppCode = (sources) => (0, handlebars_1.compile)(commandLine_1.cmdLine.engine === 'psychic' ? psychicTemplate : asyncTemplate)({
112
+ const getCppCode = (sources, filesByExtension) => (0, handlebars_1.compile)(commandLine_1.cmdLine.engine === 'psychic' ? psychicTemplate : asyncTemplate)({
107
113
  commandLine: process.argv.slice(2).join(' '),
108
114
  now: `${new Date().toLocaleDateString()} ${new Date().toLocaleTimeString()}`,
109
115
  fileCount: sources.length.toString(),
@@ -114,7 +120,9 @@ const getCppCode = (sources) => (0, handlebars_1.compile)(commandLine_1.cmdLine.
114
120
  bytes: [...s.content].map((v) => `0x${v.toString(16)}`).join(', '),
115
121
  isDefault: s.filename.startsWith('index.htm')
116
122
  })),
123
+ filesByExtension,
117
124
  isEtag: commandLine_1.cmdLine.etag,
118
- methodName: commandLine_1.cmdLine.espmethod
125
+ methodName: commandLine_1.cmdLine.espmethod,
126
+ definePrefix: commandLine_1.cmdLine.define
119
127
  }).trim();
120
128
  exports.getCppCode = getCppCode;
package/dist/index.js CHANGED
@@ -17,7 +17,7 @@ const summary = {
17
17
  gzipsize: 0
18
18
  };
19
19
  const sources = [];
20
- let sourceIndex = 0;
20
+ const filesByExtension = [];
21
21
  const files = (0, file_1.getFiles)();
22
22
  if (files.length === 0) {
23
23
  console.error(`Directory ${commandLine_1.cmdLine.sourcepath} is empty`);
@@ -27,13 +27,24 @@ for (const file of files) {
27
27
  const mime = (0, mime_types_1.lookup)(file) || 'text/plain';
28
28
  summary.filecount++;
29
29
  console.log(`[${file}]`);
30
+ const filename = file.replace(/\\/g, '/');
31
+ const dataname = filename.replace(/[./-]/g, '_');
32
+ let extension = node_path_1.default.extname(filename).toUpperCase();
33
+ if (extension.startsWith('.'))
34
+ extension = extension.slice(1);
35
+ const group = filesByExtension.find((fe) => fe.extension === extension);
36
+ if (group)
37
+ group.count += 1;
38
+ else
39
+ filesByExtension.push({ extension, count: 1 });
30
40
  const rawContent = (0, node_fs_1.readFileSync)(node_path_1.default.join(commandLine_1.cmdLine.sourcepath, file), { flag: 'r' });
31
41
  const md5 = (0, node_crypto_1.createHash)('md5').update(rawContent).digest('hex');
32
42
  summary.size += rawContent.length;
33
43
  if (commandLine_1.cmdLine['no-gzip']) {
34
44
  sources.push({
35
- index: sourceIndex++,
36
- filename: file.replace(/\\/g, '/'),
45
+ filename: filename,
46
+ dataname,
47
+ datanameUpperCase: dataname.toUpperCase(),
37
48
  content: rawContent,
38
49
  isGzip: false,
39
50
  mime,
@@ -46,8 +57,9 @@ for (const file of files) {
46
57
  summary.gzipsize += zipContent.length;
47
58
  if (rawContent.length > 100 && zipContent.length < rawContent.length * 0.85) {
48
59
  sources.push({
49
- index: sourceIndex++,
50
- filename: file.replace(/\\/g, '/'),
60
+ filename: filename,
61
+ dataname,
62
+ datanameUpperCase: dataname.toUpperCase(),
51
63
  content: zipContent,
52
64
  isGzip: true,
53
65
  mime,
@@ -57,8 +69,9 @@ for (const file of files) {
57
69
  }
58
70
  else {
59
71
  sources.push({
60
- index: sourceIndex++,
61
- filename: file.replace(/\\/g, '/'),
72
+ filename: filename,
73
+ dataname,
74
+ datanameUpperCase: dataname.toUpperCase(),
62
75
  content: rawContent,
63
76
  isGzip: false,
64
77
  mime,
@@ -69,7 +82,8 @@ for (const file of files) {
69
82
  }
70
83
  console.log('');
71
84
  }
72
- const cppFile = (0, cppCode_1.getCppCode)(sources);
85
+ filesByExtension.sort((left, right) => left.extension.localeCompare(right.extension));
86
+ const cppFile = (0, cppCode_1.getCppCode)(sources, filesByExtension);
73
87
  (0, node_fs_1.writeFileSync)(commandLine_1.cmdLine.outputfile, cppFile);
74
88
  if (commandLine_1.cmdLine['no-gzip']) {
75
89
  console.log(`${summary.filecount} files, ${Math.round(summary.size / 1024)}kB original size`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "svelteesp32",
3
- "version": "1.2.7",
3
+ "version": "1.3.1",
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",
@@ -28,10 +28,13 @@
28
28
  },
29
29
  "homepage": "https://github.com/BCsabaEngine/svelteesp32",
30
30
  "scripts": {
31
- "dev:psychic": "nodemon src/index.ts -- -e psychic -s ./svelte/dist -o ../../../Arduino/SvelteEsp32/svelteesp32.h --etag",
32
- "dev:psychic:local": "nodemon src/index.ts -- -e psychic -s ./svelte/dist -o ./svelteesp32.h --etag",
33
- "dev:async": "nodemon src/index.ts -- -e async -s ./svelte/dist -o ../../../Arduino/SvelteEsp8266/svelteesp32.h --etag",
34
- "dev:async:local": "nodemon src/index.ts -- -e async -s ./svelte/dist -o ./svelteesp32.h --etag",
31
+ "dev:async": "nodemon src/index.ts -- -e async -s ./demo/svelte/dist -o ./demo/esp32/include/svelteesp32async.h --etag",
32
+ "dev:psychic": "nodemon src/index.ts -- -e psychic -s ./demo/svelte/dist -o ./demo/esp32/include/svelteesp32psychic.h --etag",
33
+ "run:async": "tsx src/index.ts -e async -s ./demo/svelte/dist -o ./demo/esp32/include/svelteesp32async.h --etag",
34
+ "run:psychic": "tsx src/index.ts -e psychic -s ./demo/svelte/dist -o ./demo/esp32/include/svelteesp32psychic.h --etag",
35
+ "run:both": "npm run run:async && npm run run:psychic",
36
+ "platformio": "~/.platformio/penv/bin/pio run -d ./demo/esp32",
37
+ "run:both:build": "npm run run:both && npm run platformio",
35
38
  "clean": "tsc --build --clean",
36
39
  "build": "tsc --build --clean && tsc --build --force",
37
40
  "format:check": "prettier --check .",
@@ -53,16 +56,17 @@
53
56
  ],
54
57
  "devDependencies": {
55
58
  "@types/mime-types": "^2.1.4",
56
- "@types/node": "^20.14.12",
57
- "@typescript-eslint/eslint-plugin": "^7.17.0",
58
- "@typescript-eslint/parser": "^7.17.0",
59
- "eslint": "^8.57.0",
59
+ "@types/node": "^22.3.0",
60
+ "@typescript-eslint/eslint-plugin": "^8.1.0",
61
+ "@typescript-eslint/parser": "^8.1.0",
62
+ "eslint": "^9.9.0",
60
63
  "eslint-config-prettier": "^9.1.0",
61
64
  "eslint-plugin-simple-import-sort": "^12.1.1",
62
65
  "eslint-plugin-unicorn": "^55.0.0",
63
66
  "nodemon": "^3.1.4",
64
67
  "prettier": "^3.3.3",
65
68
  "ts-node": "^10.9.2",
69
+ "tsx": "^4.17.0",
66
70
  "typescript": "^5.5.4"
67
71
  },
68
72
  "dependencies": {