├── template ├── .gitignore ├── src │ ├── assets │ │ └── favicon.ico │ ├── style.css │ └── index.js ├── preact.config.js ├── package.json └── esp │ ├── static_files_h.ejs │ └── esp-build-plugin.js ├── .gitignore └── README.md /template/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | /build 3 | /*.log -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .DS_Store 3 | *.lock 4 | *.log -------------------------------------------------------------------------------- /template/src/assets/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mruettgers/preact-template-esp/HEAD/template/src/assets/favicon.ico -------------------------------------------------------------------------------- /template/src/style.css: -------------------------------------------------------------------------------- 1 | html, body { 2 | font: 14px/1.21 'Helvetica Neue', arial, sans-serif; 3 | font-weight: 400; 4 | } 5 | 6 | h1 { 7 | text-align: center; 8 | } 9 | -------------------------------------------------------------------------------- /template/src/index.js: -------------------------------------------------------------------------------- 1 | import './style'; 2 | 3 | export default function App() { 4 | return ( 5 |
6 |

Hello, World!

7 |
8 | ); 9 | } 10 | -------------------------------------------------------------------------------- /template/preact.config.js: -------------------------------------------------------------------------------- 1 | const webpack = require('webpack'); 2 | const ESPBuildPlugin = require('./esp/esp-build-plugin'); 3 | 4 | export default { 5 | webpack(config, env, helpers, options) { 6 | if (env.isProd) { 7 | config.devtool = false; 8 | config.plugins = [ 9 | ...config.plugins, 10 | new ESPBuildPlugin({ 11 | exclude: [ 12 | '200.html', 13 | 'preact_prerender_data.json', 14 | 'push-manifest.json' 15 | ] 16 | }) 17 | ]; 18 | }; 19 | } 20 | }; 21 | -------------------------------------------------------------------------------- /template/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "name": "{{ name }}", 4 | "version": "0.0.0", 5 | "license": "MIT", 6 | "scripts": { 7 | "build": "preact build --no-sw --no-esm --no-sw --no-json --no-prerender --no-inline-css --no-prerenderUrls", 8 | "serve": "sirv build --port 8080 --cors --single", 9 | "dev": "preact watch", 10 | "lint": "eslint src" 11 | }, 12 | "eslintConfig": { 13 | "extends": "preact" 14 | }, 15 | "eslintIgnore": [ 16 | "build/*" 17 | ], 18 | "devDependencies": { 19 | "eslint": "^7.17.0", 20 | "eslint-config-preact": "^1.1.3", 21 | "mime-types": "^2.1.29", 22 | "preact-cli": "^3.0.0", 23 | "sirv-cli": "^1.0.3" 24 | }, 25 | "dependencies": { 26 | "preact": "^10.1.0", 27 | "preact-render-to-string": "^5.1.2" 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /template/esp/static_files_h.ejs: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace static_files 4 | { 5 | struct file 6 | { 7 | const char *path; 8 | uint32_t size; 9 | const char *type; 10 | const uint8_t *contents; 11 | }; 12 | 13 | <% for(var i=0; i 14 | const uint32_t f_<%= files[i].normalizedName %>_size PROGMEM = <%= files[i].size %>; 15 | const uint8_t f_<%= files[i].normalizedName %>_contents[] PROGMEM = { 16 | <%= files[i].contents %> 17 | }; 18 | <% } %> 19 | 20 | 21 | const file files[] PROGMEM = { 22 | <% for(var i=0; i 23 | {.path = "<%= files[i].path %>", 24 | .size = f_<%= files[i].normalizedName %>_size, 25 | .type = "<%= files[i].mimeType %>", 26 | .contents = f_<%= files[i].normalizedName %>_contents}<% if (i < files.length-1) { %>,<% } %> 27 | <% } %> 28 | }; 29 | 30 | const uint8_t num_of_files PROGMEM = sizeof(files) / sizeof(const file); 31 | } -------------------------------------------------------------------------------- /template/esp/esp-build-plugin.js: -------------------------------------------------------------------------------- 1 | import mime from 'mime-types'; 2 | import ejs from 'ejs'; 3 | import { readFileSync, writeFileSync } from 'fs'; 4 | import { gzipSync } from 'zlib'; 5 | 6 | class ESPBuildPlugin { 7 | constructor(options) { 8 | this.compiler = null; 9 | this.assets = []; 10 | this.options = options || {}; 11 | this.pluginName = this.constructor.name; 12 | } 13 | 14 | apply(compiler) { 15 | this.compiler = compiler; 16 | compiler.hooks.assetEmitted.tap(this.pluginName, (file) => { 17 | this.addAsset(file); 18 | }); 19 | compiler.hooks.afterEmit.tap(this.pluginName, () => { 20 | this.createESPOutputFile(); 21 | }); 22 | } 23 | 24 | addAsset(file) { 25 | for (var pattern of (this.options.exclude || [])) { 26 | if (file.match(pattern) !== null) { 27 | // Asset is excluded 28 | return false; 29 | } 30 | } 31 | const path = this.compiler.options.output.path + '/' + file; 32 | const mimeType = mime.lookup(path) 33 | const asset = this.readAndProcessAsset(path); 34 | this.assets.push({ 35 | path: '/' + file, 36 | normalizedName: file.replace(/[^0-9a-z]/ig, '_'), 37 | mimeType, 38 | ...asset 39 | }) 40 | this.getLogger().info(`Added asset ${file} with a size of ${asset.size} bytes.`); 41 | } 42 | 43 | readAndProcessAsset(path) { 44 | var response = ""; 45 | var contents = gzipSync(readFileSync(path)); 46 | for (var i = 0; i < contents.length; i++) { 47 | if (i % 16 == 0) response += "\n"; 48 | response += '0x' + ('00' + contents[i].toString(16)).slice(-2); 49 | if (i < contents.length - 1) response += ', '; 50 | } 51 | return { 52 | contents: response, 53 | size: contents.length, 54 | } 55 | } 56 | 57 | createESPOutputFile() { 58 | ejs.renderFile( 59 | 'esp/static_files_h.ejs', 60 | {files: this.assets}, 61 | {}, 62 | (err, str) => { 63 | const outputPath = this.compiler.options.output.path + '/static_files.h'; 64 | writeFileSync(outputPath, str); 65 | this.getLogger().info(`Build artifact has been written to ${outputPath}.`); 66 | 67 | } 68 | ); 69 | } 70 | 71 | getLogger() { 72 | return this.compiler.getInfrastructureLogger(this.pluginName); 73 | } 74 | } 75 | 76 | module.exports = ESPBuildPlugin; -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # preact-simple-esp 2 | 3 | A simple, minimal "Hello World" template for Preact CLI for being used on a ESP8266/ESP32. 4 | 5 | ## Usage 6 | 7 | ### Preact 8 | 9 | To bootstrap a new project based on this template simply type: 10 | `preact create mruettgers/preact-template-esp [YOUR PROJECT NAME]` 11 | 12 | When ready build as usual with `npm run build`. 13 | If your build succeeds you will find a file named `static_files.h` within your build folder that could be directly included in your application. 14 | 15 | ``` 16 | root@a31200ddd18b:/workbench/preact/esp-test# npm run build 17 | 18 | > esp-test-3@0.0.0 build 19 | > preact build --no-sw --no-esm --no-sw --no-json --no-prerender --no-inline-css --no-prerenderUrls 20 | 21 | Build [=================== ] 95% (2.6s) emitting 22 | bundle.c3928.css ⏤ 108 B (+108 B) 23 | bundle.*****.js ⏤ 4.74 kB (+4.74 kB) 24 | polyfills.*****.js ⏤ 2.14 kB (+2.14 kB) 25 | index.html ⏤ 389 B (+389 B) 26 | 200.html ⏤ 396 B (+396 B) 27 | 28 | [ESPBuildPlugin] Added asset bundle.c3928.css with a size of 108 bytes. 29 | [ESPBuildPlugin] Added asset polyfills.03bf6.js with a size of 2139 bytes. 30 | [ESPBuildPlugin] Added asset bundle.aee86.js with a size of 4740 bytes. 31 | [ESPBuildPlugin] Added asset favicon.ico with a size of 4527 bytes. 32 | [ESPBuildPlugin] Added asset index.html with a size of 389 bytes. 33 | [ESPBuildPlugin] Added asset assets/favicon.ico with a size of 4527 bytes. 34 | [ESPBuildPlugin] Build artifact has been written to /workbench/preact/esp-test/build/static_files.h. 35 | ``` 36 | 37 |
38 | Show sample of "static_files.h" 39 | 40 | ```c++ 41 | #pragma once 42 | namespace static_files 43 | { 44 | struct file 45 | { 46 | const char *path; 47 | uint32_t size; 48 | const char *type; 49 | const uint8_t *contents; 50 | }; 51 | 52 | const uint32_t f_index_html_size PROGMEM = 350; 53 | const uint8_t f_index_html_contents[] PROGMEM = { 54 | 0x1f, 0x8b, 0x08, ... 55 | }; 56 | 57 | const uint32_t f_bundle_c3928_css_size PROGMEM = 108; 58 | const uint8_t f_bundle_c3928_css_contents[] PROGMEM = { 59 | 0x1f, 0x8b, 0x08, ... 60 | }; 61 | 62 | const file files[] PROGMEM = { 63 | {.path = "/index.html", 64 | .size = f_index_html_size, 65 | .type = "text/html", 66 | .contents = f_index_html_contents}, 67 | {.path = "/bundle.c3928.css", 68 | .size = f_bundle_c3928_css_size, 69 | .type = "text/css", 70 | .contents = f_bundle_c3928_css_contents}, 71 | }; 72 | 73 | const uint8_t num_of_files PROGMEM = sizeof(files) / sizeof(const file); 74 | } 75 | ``` 76 |
77 | 78 | ### ESP8266WebServer and ESP32's native webserver 79 | 80 | ```c++ 81 | #include 82 | #ifdef ESP32 83 | #include 84 | #elif defined(ESP8266) 85 | 136 | #include 137 | #elif defined(ESP8266) 138 | #include 139 | #include 140 | #endif 141 | #include 142 | #include "web/static_files.h" 143 | 144 | AsyncWebServer server(80); 145 | 146 | const char *ssid = "YOUR SSID"; 147 | const char *password = "YOUR PASSWORD"; 148 | 149 | void setup() 150 | { 151 | 152 | WiFi.begin(ssid, password); 153 | Serial.begin(115200); 154 | delay(100); 155 | while (WiFi.status() != WL_CONNECTED) 156 | { 157 | delay(500); 158 | Serial.print("."); 159 | } 160 | Serial.print("Connected to: "); 161 | Serial.println(ssid); 162 | Serial.print("IP address: "); 163 | Serial.println(WiFi.localIP()); 164 | 165 | // Optional, defines the default entrypoint 166 | server.on("/", HTTP_GET, [](AsyncWebServerRequest *request) { 167 | AsyncWebServerResponse *response = request->beginResponse_P(200, "text/html", static_files::f_index_html_contents, static_files::f_index_html_size); 168 | response->addHeader("Content-Encoding", "gzip"); 169 | request->send(response); 170 | }); 171 | 172 | // Create a route handler for each of the build artifacts 173 | for (int i = 0; i < static_files::num_of_files; i++) 174 | { 175 | server.on(static_files::files[i].path, HTTP_GET, [i](AsyncWebServerRequest *request) { 176 | AsyncWebServerResponse *response = request->beginResponse_P(200, static_files::files[i].type, static_files::files[i].contents, static_files::files[i].size); 177 | response->addHeader("Content-Encoding", "gzip"); 178 | request->send(response); 179 | }); 180 | } 181 | server.begin(); 182 | } 183 | 184 | void loop() {} 185 | ``` 186 | 187 | ## Acknowledgements 188 | 189 | This template is based on [https://github.com/preactjs-templates/simple](https://github.com/preactjs-templates/simple), a simple, minimal "Hello World" template for Preact CLI. 190 | 191 | ## Donate 192 | 193 | ### Paypal 194 | [![Paypal](https://www.paypalobjects.com/en_US/DK/i/btn/btn_donateCC_LG.gif)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=GK95YZCEGJT84) 195 | 196 | ## License 197 | 198 | Distributed under the GPL v3 license. 199 | See [LICENSE](LICENSE) for more information. 200 | --------------------------------------------------------------------------------