svelteesp32 1.0.4 → 1.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
@@ -10,6 +10,8 @@ In order to be able to easily update OTA, it is important - from the users' poin
10
10
 
11
11
  This npm package provides a solution for **inserting any JS client application into the ESP32 web server** (PsychicHttp is my favorite, faster than ESPAsyncWebServer). For this, JS, html, css, font, assets, etc. files must be converted to binary byte array. Npm mode is easy to use and easy to **integrate into your CI/CD pipeline**.
12
12
 
13
+ > Starting with version v1.1.0, the ETag header is also supported.
14
+
13
15
  ### Usage
14
16
 
15
17
  **Install package** as devDependency (it is practical if the package is part of the project so that you always receive updates)
@@ -21,7 +23,7 @@ npm install -D svelteesp32
21
23
  After a successful Svelte build (rollup/webpack/vite) **create an includeable c++ header** file
22
24
 
23
25
  ```bash
24
- npx svelteesp32 -s ../svelteapp/dist -o ../esp32project/svelteesp32.h
26
+ npx svelteesp32 -s ../svelteapp/dist -o ../esp32project/svelteesp32.h --etag
25
27
  ```
26
28
 
27
29
  During the **translation process**, the processed file details are visible, and at the end, the result shows the ESP32's memory allocation (gzip size)
@@ -73,10 +75,17 @@ The content of **generated file** (do not edit, just use)
73
75
  ```c
74
76
  const uint8_t data0[12547] = {0x1f, 0x8b, 0x8, 0x0, ...
75
77
  const uint8_t data1[5368] = {0x1f, 0x8b, 0x8, 0x0, 0x0, ...
78
+ const char * etag0 = "387b88e345cc56ef9091...";
76
79
 
77
80
  void initSvelteStaticFiles(PsychicHttpServer * server) {
78
81
  server->on("/assets/index-KwubEIf-.js", HTTP_GET, [](PsychicRequest * request)
79
82
  {
83
+ if (request->hasHeader("If-None-Match") && ...) {
84
+ PsychicResponse response304(request);
85
+ response304.setCode(304);
86
+ return response304.send();
87
+ }
88
+
80
89
  PsychicResponse response(request);
81
90
  response.setContentType("application/javascript");
82
91
  response.addHeader("Content-Encoding", "gzip");
@@ -86,6 +95,10 @@ void initSvelteStaticFiles(PsychicHttpServer * server) {
86
95
 
87
96
  server->on("/assets/index-Soe6cpLA.css", HTTP_GET, [](PsychicRequest * request)
88
97
  {
98
+ if (request->hasHeader("If-None-Match") && ...) {
99
+ ...
100
+ }
101
+
89
102
  PsychicResponse response(request);
90
103
  response.setContentType("text/css");
91
104
  response.addHeader("Content-Encoding", "gzip");
@@ -101,23 +114,32 @@ void initSvelteStaticFiles(PsychicHttpServer * server) {
101
114
 
102
115
  All modern browsers have been able to handle gzip-compressed content for years. For this reason, there is no question that the easily compressed JS and CSS files are stored compressed in the ESP32 and sent to the browser.
103
116
 
104
- During the translation process, data in gzip format is generated and will be used if the size is greater than 100 bytes and we experience a reduction of at least 15%. In such a case, the compressed data is unconditionally sent to the browser with the appropriate header information.
117
+ During the translation process, data in gzip format is generated and will be used if the **size is greater than 100 bytes** and we experience a **reduction of at least 15%**. In such a case, the compressed data is unconditionally sent to the browser with the appropriate **Content-Encoding** header information.
105
118
 
106
119
  Automatic compression can be turned off with the `--no-gzip` option.
107
120
 
121
+ ### ETag
122
+
123
+ The ETag HTTP header can be used to significantly reduce network traffic. If the server sends ETag information, the client can check the integrity of the file by sending back this ETag (in `If-None-Match` header) without sending the data back again. All browsers use this function, the 304 HTTP response code is clearly visible in the network traffic.
124
+
125
+ Since ESP32 data traffic is moderately expensive, it is an individual decision whether to use the ETag or not. We **recommend using ETag**, which adds a bit more code (about 1-3%) but results in a much cleaner operation.
126
+
127
+ The use of ETag is **not enabled by default**, this can be achieved with the `--etag` switch.
128
+
108
129
  ### Main entry point - index.html
109
130
 
110
131
  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://esp32_xxx.local` or just entering the `http://x.y.w.z/` IP address will serve this main file.
111
132
 
112
133
  ### Command line options
113
134
 
114
- | Option | Mandatory | Description | default |
115
- | ------------- | :-------: | ---------------------------------------------- | ----------------------- |
116
- | `-s` | x | Source dist folder contains compiled web files | |
117
- | `-o` | x | Generated output file with path | `svelteesp32.h` |
118
- | `--no-gzip` | | Do not compress content with gzip | |
119
- | `--espmethod` | x | Name of generated method | `initSvelteStaticFiles` |
120
- | `-h` | | Show help | |
135
+ | Option | Required | Description | default |
136
+ | ------------- | :------: | ---------------------------------------------- | ----------------------- |
137
+ | `-s` | x | Source dist folder contains compiled web files | |
138
+ | `-o` | x | Generated output file with path | `svelteesp32.h` |
139
+ | `--etag` | | Use ETag header for cache | false |
140
+ | `--no-gzip` | | Do not compress content with gzip | false -> gzip used |
141
+ | `--espmethod` | x | Name of generated method | `initSvelteStaticFiles` |
142
+ | `-h` | | Show help | |
121
143
 
122
144
  ### Q&A
123
145
 
@@ -3,6 +3,7 @@ interface ICopyFilesArguments {
3
3
  outputfile: string;
4
4
  espmethod: string;
5
5
  'no-gzip': boolean;
6
+ etag: boolean;
6
7
  help?: boolean;
7
8
  }
8
9
  export declare const cmdLine: ICopyFilesArguments;
@@ -20,6 +20,11 @@ exports.cmdLine = (0, ts_command_line_args_1.parse)({
20
20
  description: 'Do not compress content with gzip',
21
21
  defaultValue: false
22
22
  },
23
+ etag: {
24
+ type: Boolean,
25
+ description: 'Use ETAG header for cache',
26
+ defaultValue: false
27
+ },
23
28
  espmethod: {
24
29
  type: String,
25
30
  description: 'Name of generated method',
package/dist/cppCode.d.ts CHANGED
@@ -5,5 +5,6 @@ export type cppCodeSource = {
5
5
  mime: string;
6
6
  content: Buffer;
7
7
  isGzip: boolean;
8
+ md5: string;
8
9
  };
9
10
  export declare const getCppCode: (sources: cppCodeSource[]) => string;
package/dist/cppCode.js CHANGED
@@ -7,12 +7,20 @@ const replaceAll = (s, from, to) => {
7
7
  s = s.replace(from, to);
8
8
  return s;
9
9
  };
10
+ const blockTemplateETag = `if (request->hasHeader("If-None-Match") && request->header("If-None-Match") == String(etag$index$)) {
11
+ PsychicResponse response304(request);
12
+ response304.setCode(304);
13
+ return response304.send();
14
+ }
15
+ `;
10
16
  const blockTemplate = `
11
17
  $default$server->on("/$filename$", HTTP_GET, [](PsychicRequest * request)
12
18
  {
19
+ ${commandLine_1.cmdLine.etag ? blockTemplateETag : ''}
13
20
  PsychicResponse response(request);
14
21
  response.setContentType("$mime$");
15
22
  response.addHeader("Content-Encoding", "$encoding$");
23
+ ${commandLine_1.cmdLine.etag ? `response.addHeader("ETag", etag$index$);` : ''}
16
24
  response.setContent(data$index$, sizeof(data$index$));
17
25
  return response.send();
18
26
  });
@@ -27,22 +35,34 @@ const getCppBlock = (source) => {
27
35
  result = replaceAll(result, '$encoding$', source.isGzip ? 'gzip' : 'identity');
28
36
  return result;
29
37
  };
30
- const fileTemplate = `
31
- $arrays$
38
+ const fileTemplate = `//cmdline: $commandline$
39
+ //created: $now$
40
+ //files: $filecount$
41
+ //memory: $filesize$
42
+
43
+ $filedataarrays$
44
+ $hashaarrays$
32
45
 
33
46
  void $method$(PsychicHttpServer * server) {
34
47
  $code$
35
48
  }`;
36
49
  const getCppCode = (sources) => {
37
- const arrays = [];
50
+ const fileDataArrays = [];
51
+ const hashArrays = [];
38
52
  const blocks = [];
39
53
  for (const source of sources) {
40
54
  const bytesString = [...source.content].map((v) => `0x${v.toString(16)}`).join(', ');
41
- arrays.push(`const uint8_t data${source.index}[${source.content.length}] = {${bytesString}};`);
55
+ fileDataArrays.push(`const uint8_t data${source.index}[${source.content.length}] = {${bytesString}};`);
56
+ hashArrays.push(`const char * etag${source.index} = "${source.md5}";`);
42
57
  blocks.push(getCppBlock(source));
43
58
  }
44
59
  let result = fileTemplate;
45
- result = replaceAll(result, '$arrays$', arrays.join('\n'));
60
+ result = replaceAll(result, '$commandline$', process.argv.slice(2).join(' '));
61
+ result = replaceAll(result, '$now$', `${new Date().toLocaleDateString()} ${new Date().toLocaleTimeString()}`);
62
+ result = replaceAll(result, '$filecount$', sources.length.toString());
63
+ result = replaceAll(result, '$filesize$', sources.reduce((previous, current) => previous + current.content.length, 0).toString());
64
+ result = replaceAll(result, '$filedataarrays$', fileDataArrays.join('\n'));
65
+ result = replaceAll(result, '$hashaarrays$', commandLine_1.cmdLine.etag ? hashArrays.join('\n') : '');
46
66
  result = replaceAll(result, '$method$', commandLine_1.cmdLine.espmethod);
47
67
  result = replaceAll(result, '$code$', blocks.join(''));
48
68
  return result;
package/dist/index.js CHANGED
@@ -1,5 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
+ const node_crypto_1 = require("node:crypto");
3
4
  const node_fs_1 = require("node:fs");
4
5
  const node_path_1 = require("node:path");
5
6
  const node_zlib_1 = require("node:zlib");
@@ -19,6 +20,7 @@ for (const file of (0, file_1.getFiles)()) {
19
20
  summary.filecount++;
20
21
  console.log(`[${file}]`);
21
22
  const rawContent = (0, node_fs_1.readFileSync)((0, node_path_1.join)(commandLine_1.cmdLine.sourcepath, file), { flag: 'r' });
23
+ const md5 = (0, node_crypto_1.createHash)('md5').update(rawContent).digest('hex');
22
24
  summary.size += rawContent.length;
23
25
  if (commandLine_1.cmdLine['no-gzip']) {
24
26
  sources.push({
@@ -26,7 +28,8 @@ for (const file of (0, file_1.getFiles)()) {
26
28
  filename: file,
27
29
  content: rawContent,
28
30
  isGzip: false,
29
- mime
31
+ mime,
32
+ md5
30
33
  });
31
34
  console.log(`- gzip skipped (${rawContent.length})`);
32
35
  }
@@ -39,7 +42,8 @@ for (const file of (0, file_1.getFiles)()) {
39
42
  filename: file,
40
43
  content: zipContent,
41
44
  isGzip: true,
42
- mime
45
+ mime,
46
+ md5
43
47
  });
44
48
  console.log(`✓ gzip used (${rawContent.length} -> ${zipContent.length})`);
45
49
  }
@@ -49,7 +53,8 @@ for (const file of (0, file_1.getFiles)()) {
49
53
  filename: file,
50
54
  content: rawContent,
51
55
  isGzip: false,
52
- mime
56
+ mime,
57
+ md5
53
58
  });
54
59
  console.log(`x gzip unused (${rawContent.length} -> ${zipContent.length})`);
55
60
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "svelteesp32",
3
- "version": "1.0.4",
3
+ "version": "1.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",
@@ -28,7 +28,8 @@
28
28
  },
29
29
  "homepage": "https://github.com/BCsabaEngine/svelteesp32",
30
30
  "scripts": {
31
- "dev": "nodemon src/index.ts -- -s ./svelte/dist -o ../../../Arduino/EspSvelte/svelteesp32.h",
31
+ "dev": "nodemon src/index.ts -- -s ./svelte/dist -o ../../../Arduino/EspSvelte/svelteesp32.h --etag",
32
+ "dev:local": "nodemon src/index.ts -- -s ./svelte/dist -o ./svelteesp32.h --etag",
32
33
  "clean": "tsc --build --clean",
33
34
  "build": "tsc --build --clean && tsc --build --force",
34
35
  "format:check": "prettier --check .",
@@ -47,7 +48,7 @@
47
48
  ],
48
49
  "devDependencies": {
49
50
  "@types/mime-types": "^2.1.4",
50
- "@types/node": "^20.11.17",
51
+ "@types/node": "^20.11.19",
51
52
  "@typescript-eslint/eslint-plugin": "^7.0.1",
52
53
  "@typescript-eslint/parser": "^7.0.1",
53
54
  "eslint": "^8.56.0",