Skip to content

Commit

Permalink
web: ESBuild performance + Live reload (#13026)
Browse files Browse the repository at this point in the history
* web: Silence ESBuild warning.

* web: Flesh out live reload. Tidy ESBuild.

---------

Signed-off-by: Teffen Ellis <[email protected]>
  • Loading branch information
GirlBossRush authored Feb 27, 2025
1 parent 2c802ca commit 5eb6d62
Show file tree
Hide file tree
Showing 8 changed files with 534 additions and 104 deletions.
141 changes: 141 additions & 0 deletions web/build-observer-plugin.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
import * as http from "http";
import path from "path";

/**
* Serializes a custom event to a text stream.
* a
* @param {Event} event
* @returns {string}
*/
export function serializeCustomEventToStream(event) {
// @ts-ignore
const data = event.detail ?? {};

const eventContent = [`event: ${event.type}`, `data: ${JSON.stringify(data)}`];

return eventContent.join("\n") + "\n\n";
}

/**
* Options for the build observer plugin.
*
* @typedef {Object} BuildObserverOptions
*
* @property {URL} serverURL
* @property {string} logPrefix
* @property {string} relativeRoot
*/

/**
* Creates a plugin that listens for build events and sends them to a server-sent event stream.
*
* @param {BuildObserverOptions} options
* @returns {import('esbuild').Plugin}
*/
export function buildObserverPlugin({ serverURL, logPrefix, relativeRoot }) {
const timerLabel = `[${logPrefix}] Build`;
const endpoint = serverURL.pathname;
const dispatcher = new EventTarget();

const eventServer = http.createServer((req, res) => {
res.setHeader("Access-Control-Allow-Origin", "*");
res.setHeader("Access-Control-Allow-Methods", "GET");
res.setHeader("Access-Control-Allow-Headers", "Content-Type");

if (req.url !== endpoint) {
console.log(`🚫 Invalid request to ${req.url}`);
res.writeHead(404);
res.end();
return;
}

console.log("🔌 Client connected");

res.writeHead(200, {
"Content-Type": "text/event-stream",
"Cache-Control": "no-cache",
"Connection": "keep-alive",
});

/**
* @param {Event} event
*/
const listener = (event) => {
const body = serializeCustomEventToStream(event);

res.write(body);
};

dispatcher.addEventListener("esbuild:start", listener);
dispatcher.addEventListener("esbuild:error", listener);
dispatcher.addEventListener("esbuild:end", listener);

req.on("close", () => {
console.log("🔌 Client disconnected");

clearInterval(keepAliveInterval);

dispatcher.removeEventListener("esbuild:start", listener);
dispatcher.removeEventListener("esbuild:error", listener);
dispatcher.removeEventListener("esbuild:end", listener);
});

const keepAliveInterval = setInterval(() => {
console.timeStamp("🏓 Keep-alive");

res.write("event: keep-alive\n\n");
res.write(serializeCustomEventToStream(new CustomEvent("esbuild:keep-alive")));
}, 15_000);
});

return {
name: "build-watcher",
setup: (build) => {
eventServer.listen(parseInt(serverURL.port, 10), serverURL.hostname);

build.onDispose(() => {
eventServer.close();
});

build.onStart(() => {
console.time(timerLabel);

dispatcher.dispatchEvent(
new CustomEvent("esbuild:start", {
detail: new Date().toISOString(),
}),
);
});

build.onEnd((buildResult) => {
console.timeEnd(timerLabel);

if (!buildResult.errors.length) {
dispatcher.dispatchEvent(
new CustomEvent("esbuild:end", {
detail: new Date().toISOString(),
}),
);

return;
}

console.warn(`Build ended with ${buildResult.errors.length} errors`);

dispatcher.dispatchEvent(
new CustomEvent("esbuild:error", {
detail: buildResult.errors.map((error) => ({
...error,
location: error.location
? {
...error.location,
file: path.resolve(relativeRoot, error.location.file),
}
: null,
})),
}),
);
});
},
};
}
Loading

0 comments on commit 5eb6d62

Please sign in to comment.