From 2e1c7b7c55c8229273f7b4dfa7d9f34b261cce6d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Csaba=20Bal=C3=A1zs?= Date: Sun, 1 Sep 2024 20:15:07 +0200 Subject: [PATCH] feat: support PsychicHttp v2 (#29) --- .github/workflows/ci-dev.yaml | 2 +- .github/workflows/ci-full.yaml | 2 +- CHANGELOG.md | 8 ++ README.md | 31 +++--- demo/esp32/platformio.ini | 80 +++++++++++++-- demo/esp32/src/main.cpp | 34 +++++++ package-lock.json | 12 +-- package.json | 6 +- package.script | 10 ++ src/commandLine.ts | 5 +- src/cppCode.ts | 181 ++++++++++++++++++++++++++++++++- 11 files changed, 334 insertions(+), 37 deletions(-) diff --git a/.github/workflows/ci-dev.yaml b/.github/workflows/ci-dev.yaml index 2e251a1..2c5c7b6 100644 --- a/.github/workflows/ci-dev.yaml +++ b/.github/workflows/ci-dev.yaml @@ -14,7 +14,7 @@ jobs: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: - node-version: '20.16.0' + node-version: '20.17.0' - run: npm ci - run: npm run format:check diff --git a/.github/workflows/ci-full.yaml b/.github/workflows/ci-full.yaml index 2fca222..c403c79 100644 --- a/.github/workflows/ci-full.yaml +++ b/.github/workflows/ci-full.yaml @@ -14,7 +14,7 @@ jobs: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: - node-version: '20.16.0' + node-version: '20.17.0' - run: npm ci - run: npm run format:check diff --git a/CHANGELOG.md b/CHANGELOG.md index 961f46c..6ee4cb9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,13 @@ # Change log +## 1.5 + +### 1.5.0 + +- New engine type (-e psychic2) for PsychicHttp v2 + +- Run tests with github repos instead of packages + ## 1.4 ### 1.4.1 diff --git a/README.md b/README.md index 58117aa..55ba6bb 100644 --- a/README.md +++ b/README.md @@ -12,12 +12,10 @@ In order to be able to easily update OTA, it is important - from the users' poin This npm package provides a solution for **inserting any JS client application into the ESP web server** (PsychicHttp and also ESPAsyncWebServer available, PsychicHttp is the default). 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**. +> Starting with version v1.5.0, PsychicHttp v2 is also supported. > Version v1.4.0 has a breaking change! --no-gzip changed to --gzip. Starting with this version c++ compiler directives are available to setup operation in project level. - > Starting with version v1.3.0, c++ defines can be used. - > Starting with version v1.2.0, ESP8266/ESP8285 is also supported. - > Starting with version v1.1.0, the ETag header is also supported. ### Usage @@ -34,6 +32,9 @@ After a successful Svelte build (rollup/webpack/vite) **create an includeable c+ // for PsychicHttpServer npx svelteesp32 -e psychic -s ../svelteapp/dist -o ../esp32project/svelteesp32.h --etag=true +// for PsychicHttpServer V2 +npx svelteesp32 -e psychic2 -s ../svelteapp/dist -o ../esp32project/svelteesp32.h --etag=true + // for ESPAsyncWebServer npx svelteesp32 -e async -s ../svelteapp/dist -o ../esp32project/svelteesp32.h --etag=true ``` @@ -217,18 +218,18 @@ You can use the following c++ directives at the project level if you want to con ### Command line options -| Option | Description | default | -| ------------- | ---------------------------------------------------------------- | ----------------------- | -| `-s` | **Source dist folder contains compiled web files** | | -| `-e` | The engine for which the include file is created (psychic/async) | psychic | -| `-o` | Generated output file with path | `svelteesp32.h` | -| `--etag` | Use ETag header for cache (true/false/compiler) | false | -| `--gzip` | Compress content with gzip (true/false/compiler) | true | -| `--created` | Include creation time | false | -| `--version` | Include a version string, `--version=v$npm_package_version` | '' | -| `--espmethod` | Name of generated method | `initSvelteStaticFiles` | -| `--define` | Prefix of c++ defines | `SVELTEESP32` | -| `-h` | Show help | | +| Option | Description | default | +| ------------- | ------------------------------------------------------------------------- | ----------------------- | +| `-s` | **Source dist folder contains compiled web files** | | +| `-e` | The engine for which the include file is created (psychic/psychic2/async) | psychic | +| `-o` | Generated output file with path | `svelteesp32.h` | +| `--etag` | Use ETag header for cache (true/false/compiler) | false | +| `--gzip` | Compress content with gzip (true/false/compiler) | true | +| `--created` | Include creation time | false | +| `--version` | Include a version string, `--version=v$npm_package_version` | '' | +| `--espmethod` | Name of generated method | `initSvelteStaticFiles` | +| `--define` | Prefix of c++ defines | `SVELTEESP32` | +| `-h` | Show help | | ### Q&A diff --git a/demo/esp32/platformio.ini b/demo/esp32/platformio.ini index 283483a..a85cf3b 100644 --- a/demo/esp32/platformio.ini +++ b/demo/esp32/platformio.ini @@ -71,27 +71,27 @@ lib_deps = https://github.com/me-no-dev/ESPAsyncWebServer build_flags = -D PSYCHIC -I include/_ -lib_deps = hoeken/PsychicHttp +lib_deps = https://github.com/hoeken/PsychicHttp [env:psychic_E] build_flags = -D PSYCHIC -I include/e -lib_deps = hoeken/PsychicHttp +lib_deps = https://github.com/hoeken/PsychicHttp [env:psychic_EC] build_flags = -D PSYCHIC -I include/ec -D SVELTEESP32_ENABLE_ETAG -lib_deps = hoeken/PsychicHttp +lib_deps = https://github.com/hoeken/PsychicHttp [env:psychic_ECG] build_flags = -D PSYCHIC -I include/ecg -D SVELTEESP32_ENABLE_ETAG -lib_deps = hoeken/PsychicHttp +lib_deps = https://github.com/hoeken/PsychicHttp [env:psychic_ECGC] build_flags = @@ -99,30 +99,92 @@ build_flags = -I include/ecgc -D SVELTEESP32_ENABLE_ETAG -D SVELTEESP32_ENABLE_GZIP -lib_deps = hoeken/PsychicHttp +lib_deps = https://github.com/hoeken/PsychicHttp [env:psychic_EG] build_flags = -D PSYCHIC -I include/eg -lib_deps = hoeken/PsychicHttp +lib_deps = https://github.com/hoeken/PsychicHttp [env:psychic_EGC] build_flags = -D PSYCHIC -I include/egc -D SVELTEESP32_ENABLE_GZIP -lib_deps = hoeken/PsychicHttp +lib_deps = https://github.com/hoeken/PsychicHttp [env:psychic_G] build_flags = -D PSYCHIC -I include/g -lib_deps = hoeken/PsychicHttp +lib_deps = https://github.com/hoeken/PsychicHttp [env:psychic_GC] build_flags = -D PSYCHIC -I include/gc -D SVELTEESP32_ENABLE_GZIP -lib_deps = hoeken/PsychicHttp +lib_deps = https://github.com/hoeken/PsychicHttp + + + +[env:psychic2] +build_flags = + -D PSYCHIC2 + -I include/_ +lib_deps = https://github.com/hoeken/PsychicHttp#v2-dev + +[env:psychic2_E] +build_flags = + -D PSYCHIC2 + -I include/e +lib_deps = https://github.com/hoeken/PsychicHttp#v2-dev + +[env:psychic2_EC] +build_flags = + -D PSYCHIC2 + -I include/ec + -D SVELTEESP32_ENABLE_ETAG +lib_deps = https://github.com/hoeken/PsychicHttp#v2-dev + +[env:psychic2_ECG] +build_flags = + -D PSYCHIC2 + -I include/ecg + -D SVELTEESP32_ENABLE_ETAG +lib_deps = https://github.com/hoeken/PsychicHttp#v2-dev + +[env:psychic2_ECGC] +build_flags = + -D PSYCHIC2 + -I include/ecgc + -D SVELTEESP32_ENABLE_ETAG + -D SVELTEESP32_ENABLE_GZIP +lib_deps = https://github.com/hoeken/PsychicHttp#v2-dev + +[env:psychic2_EG] +build_flags = + -D PSYCHIC2 + -I include/eg +lib_deps = https://github.com/hoeken/PsychicHttp#v2-dev + +[env:psychic2_EGC] +build_flags = + -D PSYCHIC2 + -I include/egc + -D SVELTEESP32_ENABLE_GZIP +lib_deps = https://github.com/hoeken/PsychicHttp#v2-dev + +[env:psychic2_G] +build_flags = + -D PSYCHIC2 + -I include/g +lib_deps = https://github.com/hoeken/PsychicHttp#v2-dev + +[env:psychic2_GC] +build_flags = + -D PSYCHIC2 + -I include/gc + -D SVELTEESP32_ENABLE_GZIP +lib_deps = https://github.com/hoeken/PsychicHttp#v2-dev diff --git a/demo/esp32/src/main.cpp b/demo/esp32/src/main.cpp index c098bab..2b998d6 100644 --- a/demo/esp32/src/main.cpp +++ b/demo/esp32/src/main.cpp @@ -66,6 +66,40 @@ void setup() } void loop() {} +#elif PSYCHIC2 +/* PsychicHttp example */ + +#include "credentials.h" +#include +#include +#include "svelteesp32psychic2.h" + +#if SVELTEESP32_COUNT != 11 +#error Invalid file count +#endif + +#ifndef SVELTEESP32_FILE_INDEX_HTML +#error Missing index file +#endif + +#if SVELTEESP32_CSS_FILES > 1 +#error Too many CSS files +#endif + +PsychicHttpServer server(80); +void setup() +{ + WiFi.mode(WIFI_STA); + WiFi.begin(ssid, pass); + if (WiFi.waitForConnectResult() != WL_CONNECTED) + while (true) + ; + + server.begin(); + initSvelteStaticFiles(&server); +} +void loop() {} + #else #error Unknown platform #endif \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 3b2f476..eb71b92 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "svelteesp32", - "version": "1.4.2", + "version": "1.5.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "svelteesp32", - "version": "1.4.2", + "version": "1.5.0", "license": "ISC", "dependencies": { "glob": "^11.0.0", @@ -19,7 +19,7 @@ }, "devDependencies": { "@types/mime-types": "^2.1.4", - "@types/node": "^22.5.1", + "@types/node": "^22.5.2", "@typescript-eslint/eslint-plugin": "^8.3.0", "@typescript-eslint/parser": "^8.3.0", "eslint": "^9.9.1", @@ -893,9 +893,9 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "22.5.1", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.5.1.tgz", - "integrity": "sha512-KkHsxej0j9IW1KKOOAA/XBA0z08UFSrRQHErzEfA3Vgq57eXIMYboIlHJuYIfd+lwCQjtKqUu3UnmKbtUc9yRw==", + "version": "22.5.2", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.5.2.tgz", + "integrity": "sha512-acJsPTEqYqulZS/Yp/S3GgeE6GZ0qYODUR8aVr/DkhHQ8l9nd4j5x1/ZJy9/gHrRlFMqkO6i0I3E27Alu4jjPg==", "dev": true, "license": "MIT", "dependencies": { diff --git a/package.json b/package.json index e3ea8e2..48d463d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "svelteesp32", - "version": "1.4.2", + "version": "1.5.0", "description": "Convert Svelte (or any frontend) JS application to serve it from ESP32 webserver (PsychicHttp)", "author": "BCsabaEngine", "license": "ISC", @@ -30,6 +30,7 @@ "scripts": { "dev:async": "nodemon src/index.ts -- -e async -s ./demo/svelte/dist -o ./demo/esp32/include/svelteesp32.h --etag=true --gzip=true --version=v$npm_package_version", "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", + "dev:psychic2": "nodemon src/index.ts -- -e psychic2 -s ./demo/svelte/dist -o ./demo/esp32/include/svelteesp32.h --etag=false --gzip=false --version=v$npm_package_version", "test:all": "./package.script && ~/.platformio/penv/bin/pio run -d ./demo/esp32", "clean": "tsc --build --clean", "build": "tsc --build --clean && tsc --build --force", @@ -49,11 +50,12 @@ "esp8266", "webserver", "psychichttpserver", + "psychichttpserverV2", "espasyncwebserver" ], "devDependencies": { "@types/mime-types": "^2.1.4", - "@types/node": "^22.5.1", + "@types/node": "^22.5.2", "@typescript-eslint/eslint-plugin": "^8.3.0", "@typescript-eslint/parser": "^8.3.0", "eslint": "^9.9.1", diff --git a/package.script b/package.script index 62be74f..664ac2e 100755 --- a/package.script +++ b/package.script @@ -17,3 +17,13 @@ npx tsx src/index.ts -e psychic -s ./demo/svelte/dist -o ./demo/esp32/include/eg npx tsx src/index.ts -e psychic -s ./demo/svelte/dist -o ./demo/esp32/include/ec/svelteesp32psychic.h --etag=compiler --gzip=false npx tsx src/index.ts -e psychic -s ./demo/svelte/dist -o ./demo/esp32/include/ecg/svelteesp32psychic.h --etag=compiler --gzip=true npx tsx src/index.ts -e psychic -s ./demo/svelte/dist -o ./demo/esp32/include/ecgc/svelteesp32psychic.h --etag=compiler --gzip=compiler + +npx tsx src/index.ts -e psychic2 -s ./demo/svelte/dist -o ./demo/esp32/include/_/svelteesp32psychic2.h --etag=false --gzip=false +npx tsx src/index.ts -e psychic2 -s ./demo/svelte/dist -o ./demo/esp32/include/g/svelteesp32psychic2.h --etag=false --gzip=true +npx tsx src/index.ts -e psychic2 -s ./demo/svelte/dist -o ./demo/esp32/include/gc/svelteesp32psychic2.h --etag=false --gzip=compiler +npx tsx src/index.ts -e psychic2 -s ./demo/svelte/dist -o ./demo/esp32/include/e/svelteesp32psychic2.h --etag=true --gzip=false +npx tsx src/index.ts -e psychic2 -s ./demo/svelte/dist -o ./demo/esp32/include/eg/svelteesp32psychic2.h --etag=true --gzip=true +npx tsx src/index.ts -e psychic2 -s ./demo/svelte/dist -o ./demo/esp32/include/egc/svelteesp32psychic2.h --etag=true --gzip=compiler +npx tsx src/index.ts -e psychic2 -s ./demo/svelte/dist -o ./demo/esp32/include/ec/svelteesp32psychic2.h --etag=compiler --gzip=false +npx tsx src/index.ts -e psychic2 -s ./demo/svelte/dist -o ./demo/esp32/include/ecg/svelteesp32psychic2.h --etag=compiler --gzip=true +npx tsx src/index.ts -e psychic2 -s ./demo/svelte/dist -o ./demo/esp32/include/ecgc/svelteesp32psychic2.h --etag=compiler --gzip=compiler diff --git a/src/commandLine.ts b/src/commandLine.ts index b869c99..ac0cdc3 100644 --- a/src/commandLine.ts +++ b/src/commandLine.ts @@ -3,7 +3,7 @@ import { existsSync, statSync } from 'node:fs'; import { parse } from 'ts-command-line-args'; interface ICopyFilesArguments { - engine: 'psychic' | 'async'; + engine: 'psychic' | 'psychic2' | 'async'; sourcepath: string; outputfile: string; espmethod: string; @@ -20,11 +20,12 @@ export const cmdLine = parse( engine: { type: (value) => { if (value === 'psychic') return 'psychic'; + if (value === 'psychic2') return 'psychic2'; if (value === 'async') return 'async'; throw new Error(`Invalid engine: ${value}`); }, alias: 'e', - description: 'The engine for which the include file is created (psychic|async)', + description: 'The engine for which the include file is created (psychic|psychic2|async)', defaultValue: 'psychic' }, sourcepath: { diff --git a/src/cppCode.ts b/src/cppCode.ts index 29f51bb..acea1f0 100644 --- a/src/cppCode.ts +++ b/src/cppCode.ts @@ -200,6 +200,183 @@ void {{methodName}}(PsychicHttpServer * server) { {{/each}} }`; +const psychic2Template = ` +//engine: PsychicHttpServerV2 +//cmdline: {{{commandLine}}} +{{#if created }} +//created: {{now}} +{{/if}} +// + +{{#switch etag}} +{{#case "true"}} +#ifdef {{definePrefix}}_ENABLE_ETAG +#warning {{definePrefix}}_ENABLE_ETAG has no effect because it is permanently switched ON +#endif +{{/case}} +{{#case "false"}} +#ifdef {{definePrefix}}_ENABLE_ETAG +#warning {{definePrefix}}_ENABLE_ETAG has no effect because it is permanently switched OFF +#endif +{{/case}} +{{/switch}} + +{{#switch gzip}} +{{#case "true"}} +#ifdef {{definePrefix}}_ENABLE_GZIP +#warning {{definePrefix}}_ENABLE_GZIP has no effect because it is permanently switched ON +#endif +{{/case}} +{{#case "false"}} +#ifdef {{definePrefix}}_ENABLE_GZIP +#warning {{definePrefix}}_ENABLE_GZIP has no effect because it is permanently switched OFF +#endif +{{/case}} +{{/switch}} + +// +{{#if version }} +#define {{definePrefix}}_VERSION "{{version}}" +{{/if}} +#define {{definePrefix}}_COUNT {{fileCount}} +#define {{definePrefix}}_SIZE {{fileSize}} +#define {{definePrefix}}_SIZE_GZIP {{fileGzipSize}} + +// +{{#each sources}} +#define {{../definePrefix}}_FILE_{{this.datanameUpperCase}} +{{/each}} + +// +{{#each filesByExtension}} +#define {{../definePrefix}}_{{this.extension}}_FILES {{this.count}} +{{/each}} + +// +#include +#include +#include + +// +{{#switch gzip}} +{{#case "true"}} + {{#each sources}} +const uint8_t datagzip_{{this.dataname}}[{{this.lengthGzip}}] = { {{this.bytesGzip}} }; + {{/each}} +{{/case}} +{{#case "false"}} + {{#each sources}} +const uint8_t data_{{this.dataname}}[{{this.length}}] = { {{this.bytes}} }; + {{/each}} +{{/case}} +{{#case "compiler"}} +#ifdef {{definePrefix}}_ENABLE_GZIP + {{#each sources}} +const uint8_t datagzip_{{this.dataname}}[{{this.lengthGzip}}] = { {{this.bytesGzip}} }; + {{/each}} +#else + {{#each sources}} +const uint8_t data_{{this.dataname}}[{{this.length}}] = { {{this.bytes}} }; + {{/each}} +#endif +{{/case}} +{{/switch}} + +// +{{#switch etag}} +{{#case "true"}} + {{#each sources}} +const char * etag_{{this.dataname}} = "{{this.md5}}"; + {{/each}} +{{/case}} +{{#case "false"}} +{{/case}} +{{#case "compiler"}} +#ifdef {{definePrefix}}_ENABLE_ETAG + {{#each sources}} +const char * etag_{{this.dataname}} = "{{this.md5}}"; + {{/each}} +#endif +{{/case}} +{{/switch}} + +// +// Http Handlers +void {{methodName}}(PsychicHttpServer * server) { +{{#each sources}} +// +// {{this.filename}} + {{#if this.isDefault}}server->defaultEndpoint = {{/if}}server->on("/{{this.filename}}", HTTP_GET, [](PsychicRequest * request, PsychicResponse * response) { + +{{#switch ../etag}} +{{#case "true"}} + if (request->hasHeader("If-None-Match") && request->header("If-None-Match") == String(etag_{{this.dataname}})) { + response->setCode(304); + return response->send(); + } +{{/case}} +{{#case "compiler"}} + #ifdef {{../definePrefix}}_ENABLE_ETAG + if (request->hasHeader("If-None-Match") && request->header("If-None-Match") == String(etag_{{this.dataname}})) { + response->setCode(304); + return response->send(); + } + #endif +{{/case}} +{{/switch}} + + response->setContentType("{{this.mime}}"); + +{{#switch ../gzip}} +{{#case "true"}} +{{#if this.isGzip}} + response->addHeader("Content-Encoding", "gzip"); +{{/if}} +{{/case}} +{{#case "compiler"}} + {{#if this.isGzip}} + #ifdef {{../definePrefix}}_ENABLE_GZIP + response->addHeader("Content-Encoding", "gzip"); + #endif + {{/if}} +{{/case}} +{{/switch}} + +{{#switch ../etag}} +{{#case "true"}} + response->addHeader("cache-control", "no-cache"); + response->addHeader("ETag", etag_{{this.dataname}}); +{{/case}} +{{#case "compiler"}} + #ifdef {{../definePrefix}}_ENABLE_ETAG + response->addHeader("cache-control", "no-cache"); + response->addHeader("ETag", etag_{{this.dataname}}); + #endif +{{/case}} +{{/switch}} + +{{#switch ../gzip}} +{{#case "true"}} + response->setContent(datagzip_{{this.dataname}}, {{this.lengthGzip}}); +{{/case}} +{{#case "false"}} + response->setContent(data_{{this.dataname}}, {{this.length}}); +{{/case}} +{{#case "compiler"}} + #ifdef {{../definePrefix}}_ENABLE_GZIP + response->setContent(datagzip_{{this.dataname}}, {{this.lengthGzip}}); + #else + response->setContent(data_{{this.dataname}}, {{this.length}}); + #endif +{{/case}} +{{/switch}} + + return response->send(); + }); + +{{/each}} +}`; + const asyncTemplate = ` //engine: ESPAsyncWebServer //cmdline: {{{commandLine}}} @@ -371,7 +548,9 @@ void {{methodName}}(AsyncWebServer * server) { let switchValue: string; export const getCppCode = (sources: CppCodeSources, filesByExtension: ExtensionGroups): string => - handlebarsCompile(cmdLine.engine === 'psychic' ? psychicTemplate : asyncTemplate)( + handlebarsCompile( + cmdLine.engine === 'psychic' ? psychicTemplate : cmdLine.engine === 'psychic2' ? psychic2Template : asyncTemplate + )( { commandLine: process.argv.slice(2).join(' '), now: `${new Date().toLocaleDateString()} ${new Date().toLocaleTimeString()}`,