@goauthentik/esbuild-plugin-live-reload 1.6.1 → 2.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -47,7 +47,7 @@ Add the following import near the beginning of your application's entry point.
47
47
 
48
48
  ```js
49
49
  if (process.env.NODE_ENV === "development") {
50
- await import("@goauthentik/esbuild-plugin-live-reload/client");
50
+ await import("@goauthentik/esbuild-plugin-live-reload");
51
51
  }
52
52
  ```
53
53
 
@@ -69,11 +69,11 @@ This code is licensed under the [MIT License](https://www.tldrlegal.com/license/
69
69
 
70
70
  #### Properties
71
71
 
72
- | Property | Type |
73
- | --------------------------------------------- | ---------------------------------------------------------------------- |
74
- | <a id="property-dispatcher"></a> `dispatcher` | `EventTarget` |
75
- | <a id="property-logger"></a> `logger?` | `Pick`\<`BaseLogger`, `"warn"` \| `"error"` \| `"debug"` \| `"info"`\> |
76
- | <a id="property-pathname"></a> `pathname` | `string` |
72
+ | Property | Type |
73
+ | --------------------------------------------- | ------------- |
74
+ | <a id="property-dispatcher"></a> `dispatcher` | `EventTarget` |
75
+ | <a id="property-logger"></a> `logger?` | `Logger` |
76
+ | <a id="property-pathname"></a> `pathname` | `string` |
77
77
 
78
78
  ---
79
79
 
@@ -84,16 +84,16 @@ This code is licensed under the [MIT License](https://www.tldrlegal.com/license/
84
84
  | Property | Type | Description |
85
85
  | ---------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------- |
86
86
  | <a id="property-listenoptions"></a> `listenOptions?` | `ListenOptions` | Options for the server's listen method. |
87
- | <a id="property-logger-1"></a> `logger?` | `Pick`\<`BaseLogger`, `"warn"` \| `"error"` \| `"debug"` \| `"info"`\> | A console-like logger. |
87
+ | <a id="property-logger-1"></a> `logger?` | `Logger` | A console-like logger. |
88
88
  | <a id="property-publicurl"></a> `publicURL?` | `string` \| `URL` | A URL to listen on. If not provided, a random port will be used. |
89
89
  | <a id="property-relativeroot"></a> `relativeRoot?` | `string` | A relative path to the root of the project. This is used to resolve build errors, line numbers, and file paths. |
90
90
  | <a id="property-server"></a> `server?` | `Server`\<_typeof_ `IncomingMessage`, _typeof_ `ServerResponse`\> \| `Server`\<_typeof_ `IncomingMessage`, _typeof_ `ServerResponse`\> | A server to listen on. If not provided, a new server will be created. |
91
91
 
92
92
  ---
93
93
 
94
- ### RequestHandler()
94
+ ### RequestHandler
95
95
 
96
- > **RequestHandler**\<\> = (`req`, `res`) => `void`
96
+ > **RequestHandler** = (`req`, `res`) => `void`
97
97
 
98
98
  #### Type Parameters
99
99
 
package/client/index.js CHANGED
@@ -1,13 +1,242 @@
1
1
  /**
2
- * @file Entry point for the ESBuild client-side observer.
2
+ * @file Client-side observer for ESBuild events.
3
+ *
4
+ * @import { BaseLogger } from "@goauthentik/logger-js";
5
+ * @import { Message as ESBuildMessage } from "esbuild";
3
6
  */
7
+
4
8
  /// <reference types="./types.js" />
5
- import { ESBuildObserver } from "./ESBuildObserver.js";
6
9
 
7
- if (import.meta.env?.ESBUILD_WATCHER_URL) {
8
- const buildObserver = new ESBuildObserver(import.meta.env.ESBUILD_WATCHER_URL);
10
+ import { createLogger } from "@goauthentik/esbuild-plugin-live-reload/shared";
11
+
12
+ if (typeof EventSource === "undefined") {
13
+ throw new TypeError("Environment doesn't appear to have an EventSource constructor");
14
+ }
15
+
16
+ /**
17
+ * @template {unknown} [Data=unknown]
18
+ * @typedef {(event: MessageEvent) => void} BuildEventListener
19
+ */
20
+
21
+ /**
22
+ * A symbol used to dispose of resources.
23
+ *
24
+ * @type {typeof Symbol.dispose}
25
+ */
26
+ const disposeSymbol = Symbol.dispose || Symbol.for("dispose");
27
+
28
+ /**
29
+ * A client-side watcher for ESBuild.
30
+ *
31
+ * Note that this should be conditionally imported in your code, so that
32
+ * ESBuild may tree-shake it out of production builds.
33
+ *
34
+ * ```ts
35
+ * if (process.env.NODE_ENV === "development") {
36
+ * await import("@goauthentik/esbuild-plugin-live-reload")
37
+ * .catch(() => console.warn("Failed to import watcher"))
38
+ * }
39
+ * ```
40
+ *
41
+ * @implements {Disposable}
42
+ * @category Plugin
43
+ * @runtime browser
44
+ */
45
+ export class ESBuildObserver extends EventSource {
46
+ /**
47
+ * @type {BaseLogger}
48
+ * @protected
49
+ */
50
+ logger;
51
+
52
+ /**
53
+ * Whether the watcher has a recent connection to the server.
54
+ */
55
+ alive = true;
56
+
57
+ /**
58
+ * The number of errors that have occurred since the watcher started.
59
+ */
60
+ errorCount = 0;
61
+
62
+ /**
63
+ * Whether a reload has been requested while offline.
64
+ */
65
+ deferredReload = false;
66
+
67
+ /**
68
+ * The last time a message was received from the server.
69
+ */
70
+ lastUpdatedAt = Date.now();
71
+
72
+ /**
73
+ * Whether the browser considers itself online.
74
+ */
75
+ online = true;
76
+
77
+ /**
78
+ * The ID of the animation frame for the reload.
79
+ */
80
+ #reloadFrameID = -1;
81
+
82
+ /**
83
+ * The interval for the keep-alive check.
84
+ * @type {ReturnType<typeof setInterval> | undefined}
85
+ */
86
+ #keepAliveInterval;
87
+
88
+ #trackActivity = () => {
89
+ this.lastUpdatedAt = Date.now();
90
+ this.alive = true;
91
+ };
92
+
93
+ /**
94
+ * @type {BuildEventListener}
95
+ */
96
+ #startListener = () => {
97
+ this.#trackActivity();
98
+ this.logger.info("⏰ Build started...");
99
+ };
100
+
101
+ #internalErrorListener = () => {
102
+ this.errorCount += 1;
103
+
104
+ if (this.errorCount > 100) {
105
+ clearTimeout(this.#keepAliveInterval);
106
+
107
+ this.close();
108
+ this.logger.info("⛔️ Closing connection");
109
+ }
110
+ };
111
+
112
+ /**
113
+ * @type {BuildEventListener<string>}
114
+ */
115
+ #errorListener = (event) => {
116
+ this.#trackActivity();
117
+
118
+ this.logger.warn("⛔️⛔️⛔️ Build error...");
119
+
120
+ /**
121
+ * @type {ESBuildMessage[]}
122
+ */
123
+ const esbuildErrorMessages = JSON.parse(event.data);
124
+
125
+ for (const error of esbuildErrorMessages) {
126
+ this.logger.warn(error.text);
9
127
 
10
- window.addEventListener("beforeunload", () => {
11
- buildObserver.dispose();
12
- });
128
+ if (error.location) {
129
+ this.logger.debug(
130
+ `file://${error.location.file}:${error.location.line}:${error.location.column}`,
131
+ );
132
+ this.logger.debug(error.location.lineText);
133
+ }
134
+ }
135
+ };
136
+
137
+ /**
138
+ * @type {BuildEventListener}
139
+ */
140
+ #endListener = () => {
141
+ cancelAnimationFrame(this.#reloadFrameID);
142
+
143
+ this.#trackActivity();
144
+
145
+ if (!this.online) {
146
+ this.logger.info("🚫 Build finished while offline.");
147
+ this.deferredReload = true;
148
+
149
+ return;
150
+ }
151
+
152
+ this.logger.info("🛎️ Build completed! Reloading...");
153
+
154
+ // We use an animation frame to keep the reload from happening before the
155
+ // event loop has a chance to process the message.
156
+ this.#reloadFrameID = requestAnimationFrame(() => {
157
+ location.reload();
158
+ });
159
+ };
160
+
161
+ /**
162
+ * @type {BuildEventListener}
163
+ */
164
+ #keepAliveListener = () => {
165
+ this.#trackActivity();
166
+ this.logger.info("🏓 Keep-alive");
167
+ };
168
+
169
+ /**
170
+ * Initialize the ESBuild observer. This should only be called once.
171
+ *
172
+ * @param {string | URL} [url]
173
+ * @param {BaseLogger} [logger]
174
+ * @returns {ESBuildObserver}
175
+ */
176
+ static initialize = (url, logger) => {
177
+ const esbuildObserver = new ESBuildObserver(url, logger);
178
+
179
+ return esbuildObserver;
180
+ };
181
+
182
+ /**
183
+ *
184
+ * @param {string | URL} [url]
185
+ * @param {BaseLogger} [logger]
186
+ */
187
+ constructor(url, logger = createLogger()) {
188
+ if (!url) {
189
+ throw new TypeError("ESBuildObserver: Cannot construct without a URL");
190
+ }
191
+
192
+ super(url);
193
+
194
+ this.logger = logger;
195
+
196
+ this.addEventListener("esbuild:start", this.#startListener);
197
+ this.addEventListener("esbuild:end", this.#endListener);
198
+ this.addEventListener("esbuild:error", this.#errorListener);
199
+ this.addEventListener("esbuild:keep-alive", this.#keepAliveListener);
200
+
201
+ this.addEventListener("error", this.#internalErrorListener);
202
+
203
+ addEventListener("offline", () => {
204
+ this.online = false;
205
+ });
206
+
207
+ addEventListener("online", () => {
208
+ this.online = true;
209
+
210
+ if (!this.deferredReload) return;
211
+
212
+ this.logger.info("🛎️ Reloading after offline build...");
213
+ this.deferredReload = false;
214
+
215
+ location.reload();
216
+ });
217
+
218
+ this.logger.debug("🛎️ Listening for build changes...");
219
+
220
+ this.#keepAliveInterval = setInterval(() => {
221
+ const now = Date.now();
222
+
223
+ if (now - this.lastUpdatedAt < 10_000) return;
224
+
225
+ this.alive = false;
226
+ }, 15_000);
227
+
228
+ addEventListener("beforeunload", this.dispose);
229
+ }
230
+
231
+ [disposeSymbol]() {
232
+ clearTimeout(this.#keepAliveInterval);
233
+
234
+ return this.close();
235
+ }
236
+
237
+ dispose = () => {
238
+ return this[disposeSymbol]();
239
+ };
13
240
  }
241
+
242
+ export default ESBuildObserver;
package/index.js CHANGED
@@ -1,6 +1,11 @@
1
1
  /**
2
- * @remarks Live reload plugin for ESBuild.
2
+ * @file Entry point for the ESBuild client-side observer.
3
3
  */
4
4
 
5
- export * from "./client/index.js";
6
- export * from "./plugin/index.js";
5
+ /// <reference types="./client/types.js" />
6
+
7
+ import { ESBuildObserver } from "./client/index.js";
8
+
9
+ if (import.meta.env?.ESBUILD_WATCHER_URL) {
10
+ ESBuildObserver.initialize(import.meta.env.ESBUILD_WATCHER_URL);
11
+ }
@@ -1,2 +1,65 @@
1
- export {};
1
+ /**
2
+ * A client-side watcher for ESBuild.
3
+ *
4
+ * Note that this should be conditionally imported in your code, so that
5
+ * ESBuild may tree-shake it out of production builds.
6
+ *
7
+ * ```ts
8
+ * if (process.env.NODE_ENV === "development") {
9
+ * await import("@goauthentik/esbuild-plugin-live-reload")
10
+ * .catch(() => console.warn("Failed to import watcher"))
11
+ * }
12
+ * ```
13
+ *
14
+ * @implements {Disposable}
15
+ * @category Plugin
16
+ * @runtime browser
17
+ */
18
+ export class ESBuildObserver extends EventSource implements Disposable {
19
+ /**
20
+ * Initialize the ESBuild observer. This should only be called once.
21
+ *
22
+ * @param {string | URL} [url]
23
+ * @param {BaseLogger} [logger]
24
+ * @returns {ESBuildObserver}
25
+ */
26
+ static initialize: (url?: string | URL, logger?: BaseLogger) => ESBuildObserver;
27
+ /**
28
+ *
29
+ * @param {string | URL} [url]
30
+ * @param {BaseLogger} [logger]
31
+ */
32
+ constructor(url?: string | URL, logger?: BaseLogger);
33
+ /**
34
+ * @type {BaseLogger}
35
+ * @protected
36
+ */
37
+ protected logger: BaseLogger;
38
+ /**
39
+ * Whether the watcher has a recent connection to the server.
40
+ */
41
+ alive: boolean;
42
+ /**
43
+ * The number of errors that have occurred since the watcher started.
44
+ */
45
+ errorCount: number;
46
+ /**
47
+ * Whether a reload has been requested while offline.
48
+ */
49
+ deferredReload: boolean;
50
+ /**
51
+ * The last time a message was received from the server.
52
+ */
53
+ lastUpdatedAt: number;
54
+ /**
55
+ * Whether the browser considers itself online.
56
+ */
57
+ online: boolean;
58
+ dispose: () => void;
59
+ [Symbol.dispose](): void;
60
+ #private;
61
+ }
62
+ export default ESBuildObserver;
63
+ export type BuildEventListener<Data extends unknown = unknown> = (event: MessageEvent) => void;
64
+ import type { BaseLogger } from "@goauthentik/logger-js";
2
65
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../client/index.js"],"names":[],"mappings":""}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../client/index.js"],"names":[],"mappings":"AA2BA;;;;;;;;;;;;;;;;GAgBG;AACH,4DAJgB,UAAU;IAgItB;;;;;;OAMG;IACH,oBAAqB,MAJV,MAAM,GAAG,GAII,EAAE,SAHf,UAGqB,KAFnB,eAAe,CAM1B;IAEF;;;;OAIG;IACH,kBAHW,MAAM,GAAG,GAAG,WACZ,UAAU,EA4CpB;IAvLD;;;OAGG;IACH,kBAHU,UAAU,CAGb;IAEP;;OAEG;IACH,eAAa;IAEb;;OAEG;IACH,mBAAe;IAEf;;OAEG;IACH,wBAAuB;IAEvB;;OAEG;IACH,sBAA2B;IAE3B;;OAEG;IACH,gBAAc;IAkKd,oBAEE;IARF,yBAIC;;CAKJ;;+BA/NuB,IAAI,SAAf,OAAS,cACT,CAAC,KAAK,EAAE,YAAY,KAAK,IAAI;gCAdX,wBAAwB"}
package/out/index.d.ts CHANGED
@@ -1,3 +1,2 @@
1
- export * from "./client/index.js";
2
- export * from "./plugin/index.js";
1
+ export {};
3
2
  //# sourceMappingURL=index.d.ts.map
@@ -66,7 +66,7 @@ export default liveReloadPlugin;
66
66
  export type EventServerInit = {
67
67
  pathname: string;
68
68
  dispatcher: EventTarget;
69
- logger?: Pick<import("pino").BaseLogger, "warn" | "error" | "debug" | "info"> | undefined;
69
+ logger?: Logger | undefined;
70
70
  };
71
71
  export type RequestHandler = (req: http.IncomingMessage, res: http.ServerResponse) => void;
72
72
  /**
@@ -88,12 +88,13 @@ export type LiveReloadPluginOptions = {
88
88
  /**
89
89
  * A console-like logger.
90
90
  */
91
- logger?: Pick<import("pino").BaseLogger, "warn" | "error" | "debug" | "info"> | undefined;
91
+ logger?: Logger | undefined;
92
92
  /**
93
93
  * A relative path to the root of the project. This is used to resolve build errors, line numbers, and file paths.
94
94
  */
95
95
  relativeRoot?: string | undefined;
96
96
  };
97
+ import type { Logger } from "@goauthentik/logger-js";
97
98
  import * as http from "node:http";
98
99
  import type { Server as HTTPSServer } from "node:https";
99
100
  import type { ListenOptions } from "node:net";
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../plugin/index.js"],"names":[],"mappings":"AAgBA;;;;;;;;;;GAUG;AACH,oDARW,KAAK,GACH,MAAM,CAclB;AAyBD;;;;;;;;;;;GAWG;AAEH;;;;;GAKG;AAEH;;;;;;;;GAQG;AACH,uEANW,eAAe,GACb,cAAc,CA6D1B;AAED;;;;;;;;;;;;;GAaG;AAEH;;;;;GAKG;AACH,2CAHW,uBAAuB,GACrB,OAAO,SAAS,EAAE,MAAM,CA+FpC;;;;;;cApMa,MAAM;gBACN,WAAW;;;6BAQZ,CAAC,GAAG,EAAE,IAAI,CAAC,eAAe,EAAE,GAAG,EAAE,IAAI,CAAC,cAAc,KAAK,IAAI;;;;;;;;;;;;;;;;;;;;;;;;;;sBAhEpD,WAAW;2CAJS,YAAY;mCAFpB,UAAU"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../plugin/index.js"],"names":[],"mappings":"AAgBA;;;;;;;;;;GAUG;AACH,oDARW,KAAK,GACH,MAAM,CAclB;AAyBD;;;;;;;;;;;GAWG;AAEH;;;;;GAKG;AAEH;;;;;;;;GAQG;AACH,uEANW,eAAe,GACb,cAAc,CA6D1B;AAED;;;;;;;;;;;;;GAaG;AAEH;;;;;GAKG;AACH,2CAHW,uBAAuB,GACrB,OAAO,SAAS,EAAE,MAAM,CA+FpC;;;;;;cApMa,MAAM;gBACN,WAAW;;;6BAQZ,CAAC,GAAG,EAAE,IAAI,CAAC,eAAe,EAAE,GAAG,EAAE,IAAI,CAAC,cAAc,KAAK,IAAI;;;;;;;;;;;;;;;;;;;;;;;;;;4BAnE/C,wBAAwB;sBAG7B,WAAW;2CAJS,YAAY;mCAFpB,UAAU"}
@@ -1,18 +1,14 @@
1
1
  /**
2
2
  * @file Shared utilities for the live reload plugin.
3
3
  *
4
- * @import { BaseLogger } from "pino";
5
- */
6
- /**
7
- * @typedef {Pick<BaseLogger, "info" | "warn" | "error" | "debug">} Logger
4
+ * @import { BaseLogger } from "@goauthentik/logger-js";
8
5
  */
9
6
  /**
10
7
  * Creates a logger with the given prefix.
11
8
  *
12
9
  * @param {string} [prefix]
13
- * @returns {Logger}
10
+ * @returns {BaseLogger}
14
11
  */
15
- export function createLogger(prefix?: string): Logger;
16
- export type Logger = Pick<BaseLogger, "info" | "warn" | "error" | "debug">;
17
- import type { BaseLogger } from "pino";
12
+ export function createLogger(prefix?: string): BaseLogger;
13
+ import type { BaseLogger } from "@goauthentik/logger-js";
18
14
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../shared/index.js"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH;;GAEG;AAEH;;;;;GAKG;AACH,sCAHW,MAAM,GACJ,MAAM,CASlB;qBAhBY,IAAI,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,OAAO,CAAC;gCAJnC,MAAM"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../shared/index.js"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH;;;;;GAKG;AACH,sCAHW,MAAM,GACJ,UAAU,CAUtB;gCAjB8B,wBAAwB"}