commit 0412802078c5e547c4121985c228924f37f53434
parent 3cc0f920ae7b24cbb0c6df078650de06f5176dba
Author: Sebastian <sebasjm@taler-systems.com>
Date: Thu, 16 Apr 2026 11:16:21 -0300
install dir to server_dir
Diffstat:
8 files changed, 132 insertions(+), 34 deletions(-)
diff --git a/packages/bank-ui/build.mjs b/packages/bank-ui/build.mjs
@@ -19,6 +19,7 @@ import { build } from "@gnu-taler/web-util/build";
await build({
type: "production",
+ importMeta: import.meta,
source: {
js: ["src/index.tsx"],
assets: [{ base: "src", files: ["src/index.html"] }],
diff --git a/packages/bank-ui/dev.mjs b/packages/bank-ui/dev.mjs
@@ -22,6 +22,7 @@ const devEntryPoints = ["src/stories.tsx", "src/index.tsx"];
const build = initializeDev({
type: "development",
+ importMeta: import.meta,
source: {
js: devEntryPoints,
assets: [{ base: "src", files: ["src/index.html", "src/settings.json"] }],
diff --git a/packages/web-util/build.mjs b/packages/web-util/build.mjs
@@ -183,7 +183,8 @@ const buildConfigBrowser = {
"src/tests/mock.ts",
"src/tests/swr.ts",
"src/index.browser.ts",
- "src/live-reload.ts",
+ "src/live-reload-ws.ts",
+ "src/live-reload-poll.ts",
"src/stories.tsx",
],
outExtension: {
diff --git a/packages/web-util/src/index.build.ts b/packages/web-util/src/index.build.ts
@@ -91,6 +91,22 @@ function copyFilesPlugin(assets: Assets | Assets[]) {
};
}
+function buildTimePlugin() {
+ return {
+ name: "build-timestamp",
+ setup(build: PluginBuild) {
+ const outDir = build.initialOptions.outdir;
+ if (outDir === undefined) {
+ throw Error("esbuild build options does not specify outdir");
+ }
+ build.onEnd(() => {
+ const destination = path.join(outDir, 'build-time.txt');
+ fs.writeFileSync(destination, new Date().toUTCString());
+ });
+ },
+ };
+}
+
const DEFAULT_SASS_FILTER = /\.(s[ac]ss|css)$/;
const sassPlugin: esbuild.Plugin = {
@@ -228,7 +244,7 @@ export interface BuildParams {
};
public?: string;
/**
- * Location directory of the output. Can be redirected with INSTALL_DIR environment
+ * Location directory of the output. Can be redirected with SERVER_DIR environment
*/
destination: string;
css: "sass" | "postcss" | "linaria";
@@ -310,7 +326,7 @@ export function computeConfig(params: BuildParams): {
absWorkingDir: baseDir,
entryPoints: params.source.js,
publicPath: params.public,
- outdir: process.env.INSTALL_DIR ?? params.destination,
+ outdir: process.env.SERVER_DIR ?? params.destination,
treeShaking: true,
metafile: true,
minify: params.type === "production",
@@ -339,9 +355,11 @@ export async function build(config: BuildParams) {
return res;
}
+const liveReloadType = process.env.SERVER_DIR ? "poll" : "ws"
+
const LIVE_RELOAD_SCRIPT =
- "./node_modules/@gnu-taler/web-util/lib/live-reload.mjs";
-const LIVE_RELOAD_SCRIPT_LOCALLY = "./lib/live-reload.mjs";
+ `./node_modules/@gnu-taler/web-util/lib/live-reload-${liveReloadType}.mjs`;
+const LIVE_RELOAD_SCRIPT_LOCALLY = `./lib/live-reload-${liveReloadType}.mjs`;
/**
* Do startup for development environment
@@ -370,6 +388,7 @@ export function initializeDev(
): () => Promise<esbuild.BuildResult> {
function buildDevelopment() {
const { esBuildOptions } = computeConfig(config);
+ esBuildOptions.plugins?.push(buildTimePlugin())
if (!options.dontDoLiveReload) {
esBuildOptions.inject = [LIVE_RELOAD_SCRIPT];
}
diff --git a/packages/web-util/src/live-reload-poll.ts b/packages/web-util/src/live-reload-poll.ts
@@ -0,0 +1,10 @@
+import { poll } from "./live-reload.js";
+
+function setupLiveReload(): void {
+ const stopWs = localStorage.getItem("stop-ws");
+ if (!!stopWs) return;
+
+ poll();
+}
+
+setupLiveReload();
diff --git a/packages/web-util/src/live-reload-ws.ts b/packages/web-util/src/live-reload-ws.ts
@@ -0,0 +1,10 @@
+import { listen } from "./live-reload.js";
+
+function setupLiveReload(): void {
+ const stopWs = localStorage.getItem("stop-ws");
+ if (!!stopWs) return;
+
+ listen();
+}
+
+setupLiveReload();
diff --git a/packages/web-util/src/live-reload.ts b/packages/web-util/src/live-reload.ts
@@ -1,14 +1,22 @@
/* eslint-disable no-undef */
-function setupLiveReload(): void {
- const stopWs = localStorage.getItem("stop-ws");
- if (!!stopWs) return;
- const protocol = window.location.protocol === "http:" ? "ws:" : "wss:";
- const ws = new WebSocket(
- `${protocol}//${window.location.hostname}:${window.location.port}/ws`,
- );
+const protocol = window.location.protocol === "http:" ? "ws:" : "wss:";
+const POLL_URL = `./build-time.txt`;
+const WS_URL = `${protocol}//${window.location.hostname}:${window.location.port}/ws`;
+
+const POLL_MS = 1500;
+
+let lastBuildtime: string;
+
+export function listen() {
+ let reloadRequired = false;
+ const ws = new WebSocket(WS_URL);
+
ws.addEventListener("close", (e) => {
- window.location.reload();
+ if (reloadRequired) {
+ reloadAppCode();
+ }
+ setTimeout(listen, POLL_MS);
});
ws.addEventListener("message", (message) => {
@@ -19,31 +27,17 @@ function setupLiveReload(): void {
return;
}
if (event.type === "file-updated-done") {
+ reloadRequired = true;
ws.close(1000);
return;
}
if (event.type === "file-updated-failed") {
- const h1 = document.getElementById("overlay-text");
- if (h1) {
- h1.innerHTML = "compilation failed";
- h1.style.color = "red";
- h1.style.margin = "";
- }
- const div = document.getElementById("overlay");
- if (div) {
- const content = JSON.stringify(event.data, undefined, 2);
- const pre = document.createElement("pre");
- pre.id = "error-text";
- pre.style.margin = "";
- pre.textContent = content;
- div.style.backgroundColor = "rgba(0,0,0,0.8)";
- div.style.flexDirection = "column";
- div.appendChild(pre);
- }
+ showErrorOnOverlay(event.data);
console.error(event.data.error);
return;
}
if (event.type === "file-updated") {
+ reloadRequired = true;
ws.close(1000);
return;
}
@@ -56,12 +50,46 @@ function setupLiveReload(): void {
ws.addEventListener("error", (error) => {
console.error(error);
});
- ws.addEventListener("close", (message) => {
- setTimeout(setupLiveReload, 1500);
- });
}
-setupLiveReload();
+export async function poll() {
+ try {
+ const response = await fetch(POLL_URL);
+ if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`);
+
+ const currentContent = await response.text();
+
+ if (!lastBuildtime) {
+ lastBuildtime = currentContent;
+ } else if (currentContent !== lastBuildtime) {
+ reloadAppCode();
+ }
+ } catch (error) {
+ console.error("Polling error:", error);
+ } finally {
+ setTimeout(poll, POLL_MS);
+ }
+}
+
+function showErrorOnOverlay(data: unknown): void {
+ const h1 = document.getElementById("overlay-text");
+ if (h1) {
+ h1.innerHTML = "compilation failed";
+ h1.style.color = "red";
+ h1.style.margin = "";
+ }
+ const div = document.getElementById("overlay");
+ if (div) {
+ const content = JSON.stringify(data, undefined, 2);
+ const pre = document.createElement("pre");
+ pre.id = "error-text";
+ pre.style.margin = "";
+ pre.textContent = content;
+ div.style.backgroundColor = "rgba(0,0,0,0.8)";
+ div.style.flexDirection = "column";
+ div.appendChild(pre);
+ }
+}
function showReloadOverlay(): void {
const d = document.createElement("div");
d.id = "overlay";
@@ -88,3 +116,8 @@ function showReloadOverlay(): void {
document.body.appendChild(d);
}
}
+
+function reloadAppCode() {
+ // instead we could re-hydrate the preact state
+ window.location.reload();
+}
diff --git a/packages/web-util/src/serve.ts b/packages/web-util/src/serve.ts
@@ -19,6 +19,26 @@ const httpServerOptions = {
const logger = new Logger("serve.ts");
+async function watch(opts: {
+ onSourceUpdate?: () => Promise<void>;
+ folder: string;
+ source?: string;
+}): Promise<void> {
+ const watchingFolder = opts.source ?? opts.folder;
+ logger.info(`SERVER_DIR detected, server off`)
+ logger.info(`watching ${watchingFolder} for changes`);
+
+ chokidar.watch(watchingFolder).on("change", (path, stats) => {
+ logger.info(`changed: ${path}`);
+
+ if (opts.onSourceUpdate) {
+ opts.onSourceUpdate();
+ }
+ });
+
+ if (opts.onSourceUpdate) opts.onSourceUpdate();
+}
+
export async function serve(opts: {
folder: string;
port: number;
@@ -30,6 +50,9 @@ export async function serve(opts: {
appPath?: string;
appSamplePath?: string;
}): Promise<void> {
+ if (process.env.SERVER_DIR) {
+ return watch(opts)
+ }
const PATHS = {
WS: "/ws",
EXAMPLE: "/examples",