
web: Update config. Flesh out build. Fix issue surrounding build. Fix paths. Update workspaces. Fix build steps. Apply linter. Temporarily remove problem rules. Add ignorefile. Prep for formatting. Lint website. Lint web, repo packages. Refine Prettier usage. Fix imports. Tidy build. Move node ignore files. Remove unused. Update job. Fix lint step. Build before compiling. Use root for paths. Fix issues surrounding import references, types, package names. Fix build paths. Tidy. Enforce prefix. Apply prefixes to imports. Enable linter, compiler, etc. Fix references. Update names. Mark optional. Revise mounts. Fix build order. Update package.json. Ignore all docusaurus. Fix paths, types. Clean up build steps, names. Fix paths. website: Fix nested paragraphs build warning. web: Enforce module resolution. Use consistent LTS version. Track Node version. Use default resolution. Test main entrypoint. Fix Node v20 compatibility. Add task names. WIP: Fix styles.
148 lines
4.4 KiB
JavaScript
148 lines
4.4 KiB
JavaScript
/**
|
|
* @file Live reload plugin for ESBuild.
|
|
*/
|
|
import * as http from "node:http";
|
|
import * as path from "node:path";
|
|
|
|
/**
|
|
* Serializes a custom event to a text stream.
|
|
* a
|
|
* @param {Event} event
|
|
* @returns {string}
|
|
*/
|
|
export function serializeCustomEventToStream(event) {
|
|
// @ts-expect-error - TS doesn't know about the detail property
|
|
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 liveReloadPlugin({ serverURL, logPrefix = "Build Observer", relativeRoot }) {
|
|
const timerLabel = `[${logPrefix}] 🏁`;
|
|
// eslint-disable-next-line no-console
|
|
const log = console.log.bind(console, `[${logPrefix}]`);
|
|
|
|
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) {
|
|
log(`🚫 Invalid request to ${req.url}`);
|
|
res.writeHead(404);
|
|
res.end();
|
|
return;
|
|
}
|
|
|
|
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", () => {
|
|
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,
|
|
})),
|
|
}),
|
|
);
|
|
});
|
|
},
|
|
};
|
|
}
|