├── 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 | [](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 |
--------------------------------------------------------------------------------