evolit 0.1.0-alpha.3 → 0.1.0-alpha.5

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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/src/server.js +60 -4
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "evolit",
3
- "version": "0.1.0-alpha.3",
3
+ "version": "0.1.0-alpha.5",
4
4
  "description": "A convention-driven application framework for LitSX and web components.",
5
5
  "type": "module",
6
6
  "packageManager": "yarn@4.10.3",
package/src/server.js CHANGED
@@ -35,6 +35,33 @@ async function writeResponseBody(response, body) {
35
35
  response.end();
36
36
  }
37
37
 
38
+ const LIVE_RELOAD_PATHNAME = "/_evolit/live-reload";
39
+ const LIVE_RELOAD_SNIPPET = `<script data-evolit-live-reload>if (typeof EventSource !== "undefined") { const source = new EventSource(${JSON.stringify(LIVE_RELOAD_PATHNAME)}); source.addEventListener("reload", () => window.location.reload()); }</script>`;
40
+
41
+ function getResponseHeader(headers, name) {
42
+ if (headers?.get) {
43
+ return headers.get(name);
44
+ }
45
+
46
+ return headers?.[name] ?? headers?.[name.toLowerCase()] ?? null;
47
+ }
48
+
49
+ function injectLiveReloadSnippet(response) {
50
+ if (
51
+ typeof response.body !== "string"
52
+ || !getResponseHeader(response.headers, "content-type")?.includes("text/html")
53
+ ) {
54
+ return response;
55
+ }
56
+
57
+ return {
58
+ ...response,
59
+ body: response.body.includes("</body>")
60
+ ? response.body.replace("</body>", `${LIVE_RELOAD_SNIPPET}</body>`)
61
+ : `${response.body}${LIVE_RELOAD_SNIPPET}`,
62
+ };
63
+ }
64
+
38
65
  async function createRecursiveDirectoryWatcher(rootDirectory, onChange) {
39
66
  const watchers = new Map();
40
67
  let closed = false;
@@ -122,6 +149,7 @@ async function createServer(projectRoot, mode, explicitPort, options = {}) {
122
149
  responseCacheRuntime,
123
150
  });
124
151
  const port = getPort(explicitPort);
152
+ const liveReloadConnections = new Set();
125
153
  let invalidationTimer = null;
126
154
  let pendingInvalidation = null;
127
155
  let resolvePendingInvalidation = null;
@@ -146,7 +174,15 @@ async function createServer(projectRoot, mode, explicitPort, options = {}) {
146
174
  invalidationPromise = invalidationPromise
147
175
  .catch(() => {})
148
176
  .then(() => deploymentRuntime.invalidateDevelopmentState());
149
- invalidationPromise.then(resolve, resolve);
177
+ invalidationPromise.then(
178
+ () => {
179
+ for (const connection of liveReloadConnections) {
180
+ connection.write("event: reload\\ndata: update\\n\\n");
181
+ }
182
+ resolve();
183
+ },
184
+ resolve,
185
+ );
150
186
  }, 40);
151
187
  }
152
188
 
@@ -159,9 +195,24 @@ async function createServer(projectRoot, mode, explicitPort, options = {}) {
159
195
 
160
196
  const server = http.createServer(async (req, res) => {
161
197
  try {
198
+ const origin = `http://${req.headers.host ?? `localhost:${port}`}`;
199
+ const pathname = new URL(req.url ?? "/", origin).pathname;
200
+ if (mode === "development" && pathname === LIVE_RELOAD_PATHNAME) {
201
+ res.writeHead(200, {
202
+ "cache-control": "no-cache, no-transform",
203
+ connection: "keep-alive",
204
+ "content-type": "text/event-stream",
205
+ });
206
+ res.write("retry: 1000\\n\\n");
207
+ liveReloadConnections.add(res);
208
+ res.on("close", () => {
209
+ liveReloadConnections.delete(res);
210
+ });
211
+ return;
212
+ }
213
+
162
214
  await pendingInvalidation;
163
215
  await invalidationPromise;
164
- const origin = `http://${req.headers.host ?? `localhost:${port}`}`;
165
216
  const method = req.method ?? "GET";
166
217
  const request = new Request(new URL(req.url ?? "/", origin), {
167
218
  method,
@@ -169,9 +220,10 @@ async function createServer(projectRoot, mode, explicitPort, options = {}) {
169
220
  ...(method === "GET" || method === "HEAD" ? {} : { body: req, duplex: "half" }),
170
221
  });
171
222
  const response = await deploymentRuntime.handle(request);
223
+ const servedResponse = mode === "development" ? injectLiveReloadSnippet(response) : response;
172
224
 
173
- res.writeHead(response.status, response.headers);
174
- await writeResponseBody(res, response.body);
225
+ res.writeHead(servedResponse.status, servedResponse.headers);
226
+ await writeResponseBody(res, servedResponse.body);
175
227
  } catch (error) {
176
228
  res.writeHead(500, { "content-type": "text/plain; charset=utf-8" });
177
229
  res.end(error instanceof Error ? error.stack ?? error.message : String(error));
@@ -192,6 +244,10 @@ async function createServer(projectRoot, mode, explicitPort, options = {}) {
192
244
  clearTimeout(invalidationTimer);
193
245
  }
194
246
  watcher?.close();
247
+ for (const connection of liveReloadConnections) {
248
+ connection.end();
249
+ }
250
+ liveReloadConnections.clear();
195
251
  await new Promise((resolve, reject) => {
196
252
  server.close((error) => {
197
253
  if (error) {