
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.
203 lines
4.9 KiB
JavaScript
203 lines
4.9 KiB
JavaScript
/**
|
|
* @file Client-side observer for ESBuild events.
|
|
*
|
|
* @import { Message as ESBuildMessage } from "esbuild";
|
|
*/
|
|
|
|
const logPrefix = "👷 [ESBuild]";
|
|
const log = console.debug.bind(console, logPrefix);
|
|
|
|
/**
|
|
* @template {unknown} [Data=unknown]
|
|
* @typedef {(event: MessageEvent) => void} BuildEventListener
|
|
*/
|
|
|
|
/**
|
|
* A client-side watcher for ESBuild.
|
|
*
|
|
* Note that this should be conditionally imported in your code, so that
|
|
* ESBuild may tree-shake it out of production builds.
|
|
*
|
|
* ```ts
|
|
* if (process.env.NODE_ENV === "development" && process.env.WATCHER_URL) {
|
|
* const { ESBuildObserver } = await import("@goauthentik/esbuild-plugin-live-reload/client");
|
|
*
|
|
* ESBuildObserver.initialize(process.env.WATCHER_URL);
|
|
* }
|
|
* ```
|
|
*/
|
|
export class ESBuildObserver extends EventSource {
|
|
/**
|
|
* Whether the watcher has a recent connection to the server.
|
|
*/
|
|
alive = true;
|
|
|
|
/**
|
|
* The number of errors that have occurred since the watcher started.
|
|
*/
|
|
errorCount = 0;
|
|
|
|
/**
|
|
* Whether a reload has been requested while offline.
|
|
*/
|
|
deferredReload = false;
|
|
|
|
/**
|
|
* The last time a message was received from the server.
|
|
*/
|
|
lastUpdatedAt = Date.now();
|
|
|
|
/**
|
|
* Whether the browser considers itself online.
|
|
*/
|
|
online = true;
|
|
|
|
/**
|
|
* The ID of the animation frame for the reload.
|
|
*/
|
|
#reloadFrameID = -1;
|
|
|
|
/**
|
|
* The interval for the keep-alive check.
|
|
* @type {ReturnType<typeof setInterval> | undefined}
|
|
*/
|
|
#keepAliveInterval;
|
|
|
|
#trackActivity = () => {
|
|
this.lastUpdatedAt = Date.now();
|
|
this.alive = true;
|
|
};
|
|
|
|
/**
|
|
* @type {BuildEventListener}
|
|
*/
|
|
#startListener = () => {
|
|
this.#trackActivity();
|
|
log("⏰ Build started...");
|
|
};
|
|
|
|
#internalErrorListener = () => {
|
|
this.errorCount += 1;
|
|
|
|
if (this.errorCount > 100) {
|
|
clearTimeout(this.#keepAliveInterval);
|
|
|
|
this.close();
|
|
log("⛔️ Closing connection");
|
|
}
|
|
};
|
|
|
|
/**
|
|
* @type {BuildEventListener<string>}
|
|
*/
|
|
#errorListener = (event) => {
|
|
this.#trackActivity();
|
|
|
|
console.group(logPrefix, "⛔️⛔️⛔️ Build error...");
|
|
|
|
/**
|
|
* @type {ESBuildMessage[]}
|
|
*/
|
|
const esbuildErrorMessages = JSON.parse(event.data);
|
|
|
|
for (const error of esbuildErrorMessages) {
|
|
console.warn(error.text);
|
|
|
|
if (error.location) {
|
|
console.debug(
|
|
`file://${error.location.file}:${error.location.line}:${error.location.column}`,
|
|
);
|
|
console.debug(error.location.lineText);
|
|
}
|
|
}
|
|
|
|
console.groupEnd();
|
|
};
|
|
|
|
/**
|
|
* @type {BuildEventListener}
|
|
*/
|
|
#endListener = () => {
|
|
cancelAnimationFrame(this.#reloadFrameID);
|
|
|
|
this.#trackActivity();
|
|
|
|
if (!this.online) {
|
|
log("🚫 Build finished while offline.");
|
|
this.deferredReload = true;
|
|
|
|
return;
|
|
}
|
|
|
|
log("🛎️ Build completed! Reloading...");
|
|
|
|
// We use an animation frame to keep the reload from happening before the
|
|
// event loop has a chance to process the message.
|
|
this.#reloadFrameID = requestAnimationFrame(() => {
|
|
window.location.reload();
|
|
});
|
|
};
|
|
|
|
/**
|
|
* @type {BuildEventListener}
|
|
*/
|
|
#keepAliveListener = () => {
|
|
this.#trackActivity();
|
|
log("🏓 Keep-alive");
|
|
};
|
|
|
|
/**
|
|
* Initialize the ESBuild observer.
|
|
* This should be called once in your application.
|
|
*
|
|
* @param {string | URL} url
|
|
* @returns {ESBuildObserver}
|
|
*/
|
|
static initialize(url) {
|
|
const esbuildObserver = new ESBuildObserver(url);
|
|
|
|
return esbuildObserver;
|
|
}
|
|
|
|
/**
|
|
*
|
|
* @param {string | URL} url
|
|
*/
|
|
constructor(url) {
|
|
super(url);
|
|
|
|
this.addEventListener("esbuild:start", this.#startListener);
|
|
this.addEventListener("esbuild:end", this.#endListener);
|
|
this.addEventListener("esbuild:error", this.#errorListener);
|
|
this.addEventListener("esbuild:keep-alive", this.#keepAliveListener);
|
|
|
|
this.addEventListener("error", this.#internalErrorListener);
|
|
|
|
window.addEventListener("offline", () => {
|
|
this.online = false;
|
|
});
|
|
|
|
window.addEventListener("online", () => {
|
|
this.online = true;
|
|
|
|
if (!this.deferredReload) return;
|
|
|
|
log("🛎️ Reloading after offline build...");
|
|
this.deferredReload = false;
|
|
|
|
window.location.reload();
|
|
});
|
|
|
|
log("🛎️ Listening for build changes...");
|
|
|
|
this.#keepAliveInterval = setInterval(() => {
|
|
const now = Date.now();
|
|
|
|
if (now - this.lastUpdatedAt < 10_000) return;
|
|
|
|
this.alive = false;
|
|
log("👋 Waiting for build to start...");
|
|
}, 15_000);
|
|
}
|
|
}
|