Page Hotreloading

This commit is contained in:
kamkow1
2025-06-15 13:44:32 +02:00
parent bacc733fc3
commit 81460b1862
12 changed files with 140 additions and 8 deletions

9
.gitmodules vendored
View File

@ -7,3 +7,12 @@
[submodule "incbin"]
path = incbin
url = https://github.com/graphitemaster/incbin.git
[submodule "build-id"]
path = build-id
url = https://github.com/mattst88/build-id.git
[submodule "cJSON"]
path = cJSON
url = https://github.com/DaveGamble/cJSON.git
[submodule "md5-c"]
path = md5-c
url = https://github.com/Zunawe/md5-c.git

View File

@ -13,6 +13,7 @@ INCBIN(page_missing_t, "./tmpls/page-missing.t");
INCBIN(simple_min_css, "./etc/simple.min.css");
INCBIN(favicon_ico, "./etc/favicon.ico");
INCBIN(hotreload_js, "./etc/hotreload.js");
Baked_Resource *baked_resources = NULL;
@ -34,6 +35,7 @@ void init_baked_resources(void)
add_baked_resource("gpp1", gpp1_data, gpp1_size);
add_baked_resource("simple.min.css", simple_min_css_data, simple_min_css_size);
add_baked_resource("favicon.ico", favicon_ico_data, favicon_ico_size);
add_baked_resource("hotreload.js", hotreload_js_data, hotreload_js_size);
}
void free_baked_resources(void)

View File

@ -10,6 +10,7 @@ INCBIN_EXTERN(page_missing_t);
INCBIN_EXTERN(simple_min_css);
INCBIN_EXTERN(favicon_ico);
INCBIN_EXTERN(hotreload_js);
typedef struct {
char *key; // path

11
build.c
View File

@ -20,7 +20,8 @@ int main(int argc, char ** argv)
"./mongoose.o",
"./gpp1",
"./tmpls/home.t",
"./tmpls/page-missing.t"
"./tmpls/page-missing.t",
"./etc/hotreload.js"
) {
RULE("./mongoose.o", "./mongoose/mongoose.c") {
@ -36,13 +37,15 @@ int main(int argc, char ** argv)
}
#if DEBUG
CMD("cc", "-fPIC", "-ggdb", "-I.", "-D_GNU_SOURCE", "-DGEBS_NO_PREFIX",
CMD("cc", "-fPIC", "-ggdb", "-I.", "-DDEBUG=1", "-D_GNU_SOURCE", "-DGEBS_NO_PREFIX",
"-DINCBIN_PREFIX=", "-DINCBIN_STYLE=INCBIN_STYLE_SNAKE", "-o", "./aboba",
"./main.c", "./routes.c", "./baked.c", "./mongoose.o");
"./main.c", "./routes.c", "./baked.c", "./mongoose.o", "./cJSON/cJSON.c",
"./md5-c/md5.c");
#else
CMD("cc", "-fPIC", "-I.", "-D_GNU_SOURCE", "-DGEBS_NO_PREFIX",
"-DINCBIN_PREFIX=", "-DINCBIN_STYLE=INCBIN_STYLE_SNAKE", "-o", "./aboba",
"./main.c", "./routes.c", "./baked.c", "./mongoose.o");
"./main.c", "./routes.c", "./baked.c", "./mongoose.o", "./cJSON/cJSON.c",
"./md5-c/md5.c");
#endif
}
} else if (strcmp(cmd, "clean") == 0) {

1
cJSON Submodule

Submodule cJSON added at 8f2beb57dd

47
etc/hotreload.js Normal file
View File

@ -0,0 +1,47 @@
let build_id = "";
async function fetch_build_id()
{
const response = await fetch("/build-id");
const json = await response.json();
return json.build_id;
}
(async function() {
build_id = await fetch_build_id();
})();
const asyncIntervals = [];
const runAsyncInterval = async (cb, interval, intervalIndex) => {
await cb();
if (asyncIntervals[intervalIndex]) {
setTimeout(() => runAsyncInterval(cb, interval, intervalIndex), interval);
}
};
const setAsyncInterval = (cb, interval) => {
if (cb && typeof cb === "function") {
const intervalIndex = asyncIntervals.length;
asyncIntervals.push(true);
runAsyncInterval(cb, interval, intervalIndex);
return intervalIndex;
} else {
throw new Error('Callback must be a function');
}
};
const clearAsyncInterval = (intervalIndex) => {
if (asyncIntervals[intervalIndex]) {
asyncIntervals[intervalIndex] = false;
}
};
setAsyncInterval(async function () {
let new_build_id = await fetch_build_id();
if (build_id !== new_build_id) {
location.reload(true);
}
}, 1000);

6
main.c
View File

@ -2,9 +2,7 @@
#define GEBS_IMPLEMENTATION
#include "gebs/gebs.h"
#include "mongoose/mongoose.h"
#define STB_DS_IMPLEMENTATION
#include "stb/stb_ds.h"
@ -32,13 +30,15 @@ void event_handler(struct mg_connection *conn, int ev, void *ev_data)
}
}
static void init_route_hashtable(void)
void init_route_hashtable(void)
{
shdefault(route_hashtable, &route_page_not_found);
shput(route_hashtable, "/page-missing", &route_page_not_found);
shput(route_hashtable, "/simple.min.css", &route_simple_css);
shput(route_hashtable, "/favicon.ico", &route_favicon);
shput(route_hashtable, "/hotreload.js", &route_hotreload_js);
shput(route_hashtable, "/", &route_home);
shput(route_hashtable, "/build-id", &route_build_id);
}
int main(int argc, char ** argv)

1
md5-c Submodule

Submodule md5-c added at f3529b666b

View File

@ -1,5 +1,7 @@
#include "gebs/gebs.h"
#include "mongoose/mongoose.h"
#include "cJSON/cJSON.h"
#include "md5-c/md5.h"
#include "routes.h"
#include "baked.h"
@ -29,6 +31,31 @@ bool gpp_run(char *path, NString_List *env, String_Builder *out)
return cmd_run_collect(&cmd, out) == 0;
}
void route_build_id(struct mg_connection *conn, struct mg_http_message *msg)
{
cJSON *root = cJSON_CreateObject();
defer { cJSON_Delete(root); }
char *time = __TIME__;
uchar md5_buf[16];
md5String(time, md5_buf);
String_Builder sb = {0};
defer { sb_free(&sb); }
for (size_t i = 0; i < 16; i++) {
char tmp[3];
snprintf(tmp, sizeof(tmp), "%02x", md5_buf[i]);
sb_append_nstr(&sb, tmp);
}
sb_finish(&sb);
cJSON_AddItemToObject(root, "build_id", cJSON_CreateString(sb.items));
char *root_text = cJSON_PrintUnformatted(root);
defer { free(root_text); }
mg_http_reply(conn, 200, "Content-Type: application/json\r\n", root_text);
}
void route_page_not_found(struct mg_connection *conn, struct mg_http_message *msg)
{
NString_List env = {0};
@ -44,6 +71,12 @@ void route_page_not_found(struct mg_connection *conn, struct mg_http_message *ms
mg_http_reply(conn, 500, "Content-Type: text/plain\r\n", INTERNAL_SERVER_ERROR_MSG);
return;
}
#if DEBUG
list_append(&env, "-DHOTRELOAD=1");
#else
list_append(&env, "-DHOTRELOAD=0");
#endif
bool ok = gpp_run(path, &env, &out);
sb_finish(&out);
@ -93,6 +126,25 @@ void route_favicon(struct mg_connection *conn, struct mg_http_message *msg)
mg_http_reply(conn, 200, "Content-Type: image/x-icon\r\n", "%s", sb.items);
}
void route_hotreload_js(struct mg_connection *conn, struct mg_http_message *msg)
{
char path[PATH_MAX] = {0};
if (!get_baked_resource_path("hotreload.js", path, sizeof(path))) {
mg_http_reply(conn, 500, "Content-Type: text/plain\r\n", INTERNAL_SERVER_ERROR_MSG);
return;
}
String_Builder sb = {0};
defer { sb_free(&sb); }
if (!sb_read_file(&sb, path)) {
mg_http_reply(conn, 500, "Content-Type: text/plain\r\n", INTERNAL_SERVER_ERROR_MSG);
return;
}
sb_finish(&sb);
mg_http_reply(conn, 200, "Content-Type: text/javascript\r\n", "%s", sb.items);
}
void route_home(struct mg_connection *conn, struct mg_http_message *msg)
{
NString_List env = {0};
@ -106,7 +158,13 @@ void route_home(struct mg_connection *conn, struct mg_http_message *msg)
mg_http_reply(conn, 500, "Content-Type: text/plain\r\n", INTERNAL_SERVER_ERROR_MSG);
return;
}
#if DEBUG
list_append(&env, "-DHOTRELOAD=1");
#else
list_append(&env, "-DHOTRELOAD=0");
#endif
bool ok = gpp_run(path, &env, &out);
sb_finish(&out);

View File

@ -6,6 +6,8 @@
void route_page_not_found(struct mg_connection *conn, struct mg_http_message *msg);
void route_simple_css(struct mg_connection *conn, struct mg_http_message *msg);
void route_favicon(struct mg_connection *conn, struct mg_http_message *msg);
void route_hotreload_js(struct mg_connection *conn, struct mg_http_message *msg);
void route_home(struct mg_connection *conn, struct mg_http_message *msg);
void route_build_id(struct mg_connection *conn, struct mg_http_message *msg);
#endif // ROUTES_H_

View File

@ -41,5 +41,9 @@
<footer>
<a href="/">HOME</a>
</footer>
<#if HOTRELOAD>
<script src="/hotreload.js"></script>
<#endif>
</body>
</html>

View File

@ -12,5 +12,9 @@
<footer>
<a href="/">HOME</a>
</footer>
<#if HOTRELOAD>
<script src="/hotreload.js"></script>
<#endif>
</body>
</html>