evolit 0.1.0-alpha.5 → 0.1.0-alpha.7

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "evolit",
3
- "version": "0.1.0-alpha.5",
3
+ "version": "0.1.0-alpha.7",
4
4
  "description": "A convention-driven application framework for LitSX and web components.",
5
5
  "type": "module",
6
6
  "packageManager": "yarn@4.10.3",
@@ -55,7 +55,8 @@
55
55
  "lit": "^3.3.3",
56
56
  "magic-string": "^1.1.0",
57
57
  "rollup": "^4.46.1",
58
- "typescript": "^6.0.0"
58
+ "typescript": "^6.0.0",
59
+ "ws": "^8.18.3"
59
60
  },
60
61
  "devDependencies": {
61
62
  "vite": "^8.1.5"
package/src/build.js CHANGED
@@ -15,6 +15,7 @@ import {
15
15
  emitBundledClientAssets,
16
16
  emitHashedClientAssets,
17
17
  normalizeHydrationDataForClient,
18
+ resolveRouteClientImports,
18
19
  resolveSharedVendorModuleUrl,
19
20
  rewriteHydrationDataScript,
20
21
  rewriteServerAssetPlaceholders,
@@ -241,8 +242,11 @@ export async function buildProject(projectRoot) {
241
242
  );
242
243
  const ssrAdapter = createSsrAdapter({
243
244
  assetResolver,
244
- async resolveAdditionalHead({ result }) {
245
- const clientImports = Array.isArray(result.clientImports) ? result.clientImports : [];
245
+ async resolveAdditionalHead({ routeResult, result }) {
246
+ const clientImports = [
247
+ ...(Array.isArray(result.clientImports) ? result.clientImports : []),
248
+ ...resolveRouteClientImports(routeResult, projectRoot, clientAssets),
249
+ ];
246
250
  const urls = collectTransitiveAssetPreloads(clientImports, clientAssets);
247
251
  const styleUrls = collectTransitiveStyleUrls(clientImports, clientAssets);
248
252
 
@@ -1776,6 +1776,28 @@ export function collectTransitiveAssetPreloads(publicUrls, assetManifest) {
1776
1776
  return discovered;
1777
1777
  }
1778
1778
 
1779
+ export function resolveRouteClientImports(routeResult, projectRoot, assetManifest) {
1780
+ const normalizedManifest = normalizeClientAssetManifest(assetManifest);
1781
+ if (!normalizedManifest) {
1782
+ return [];
1783
+ }
1784
+
1785
+ const routeModules = [
1786
+ routeResult?.route?.page,
1787
+ ...(routeResult?.route?.layouts ?? []),
1788
+ routeResult?.boundaryModule,
1789
+ ].filter((modulePath) => typeof modulePath === "string");
1790
+
1791
+ return [...new Set(routeModules.map((modulePath) => {
1792
+ const relativePath = path.relative(projectRoot, modulePath).split(path.sep).join("/");
1793
+ const extension = path.extname(relativePath);
1794
+ const clientModule = extension
1795
+ ? `${relativePath.slice(0, -extension.length)}.mjs`
1796
+ : `${relativePath}.mjs`;
1797
+ return normalizedManifest.byClientModule[clientModule] ?? null;
1798
+ }).filter((publicUrl) => typeof publicUrl === "string"))];
1799
+ }
1800
+
1779
1801
  export function collectTransitiveStyleUrls(publicUrls, assetManifest) {
1780
1802
  const normalizedManifest = normalizeClientAssetManifest(assetManifest);
1781
1803
  if (!normalizedManifest) {
@@ -12,6 +12,7 @@ import {
12
12
  getSharedOutputRoot,
13
13
  normalizeHydrationDataForClient,
14
14
  normalizeClientAssetManifest,
15
+ resolveRouteClientImports,
15
16
  resolveSharedVendorModuleUrl,
16
17
  resolveBrowserPackageAssetFilePath,
17
18
  rewriteHydrationDataScript,
@@ -233,8 +234,11 @@ export async function createRequestRenderer({ projectRoot, mode, assetManifest,
233
234
  assetResolver(moduleId) {
234
235
  return currentAssetResolver(moduleId);
235
236
  },
236
- async resolveAdditionalHead({ result }) {
237
- const clientImports = Array.isArray(result.clientImports) ? result.clientImports : [];
237
+ async resolveAdditionalHead({ routeResult, result }) {
238
+ const clientImports = [
239
+ ...(Array.isArray(result.clientImports) ? result.clientImports : []),
240
+ ...resolveRouteClientImports(routeResult, projectRoot, currentAssetManifest),
241
+ ];
238
242
  const urls = currentAssetManifest
239
243
  ? collectTransitiveAssetPreloads(clientImports, currentAssetManifest)
240
244
  : [];
package/src/server.js CHANGED
@@ -2,6 +2,7 @@ import http from "node:http";
2
2
  import { watch } from "node:fs";
3
3
  import fs from "node:fs/promises";
4
4
  import path from "node:path";
5
+ import { WebSocketServer, WebSocket } from "ws";
5
6
  import { loadEvolitConfig } from "./config.js";
6
7
  import { createDeploymentRuntime } from "./deployment-runtime.js";
7
8
  import { APP_DIRECTORY, BUILD_DIRECTORY, INTERNAL_DIRECTORY, MANIFEST_FILENAME } from "./constants.js";
@@ -36,7 +37,6 @@ async function writeResponseBody(response, body) {
36
37
  }
37
38
 
38
39
  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
40
 
41
41
  function getResponseHeader(headers, name) {
42
42
  if (headers?.get) {
@@ -57,11 +57,15 @@ function injectLiveReloadSnippet(response) {
57
57
  return {
58
58
  ...response,
59
59
  body: response.body.includes("</body>")
60
- ? response.body.replace("</body>", `${LIVE_RELOAD_SNIPPET}</body>`)
61
- : `${response.body}${LIVE_RELOAD_SNIPPET}`,
60
+ ? response.body.replace("</body>", `${createLiveReloadSnippet()}</body>`)
61
+ : `${response.body}${createLiveReloadSnippet()}`,
62
62
  };
63
63
  }
64
64
 
65
+ function createLiveReloadSnippet() {
66
+ return `<script data-evolit-live-reload>const evolitProtocol=window.location.protocol==="https:"?"wss:":"ws:";const evolitSocket=new WebSocket(evolitProtocol+"//"+window.location.host+${JSON.stringify(LIVE_RELOAD_PATHNAME)});evolitSocket.addEventListener("message",({data})=>{if(data==="reload")window.location.reload()});</script>`;
67
+ }
68
+
65
69
  async function createRecursiveDirectoryWatcher(rootDirectory, onChange) {
66
70
  const watchers = new Map();
67
71
  let closed = false;
@@ -149,7 +153,14 @@ async function createServer(projectRoot, mode, explicitPort, options = {}) {
149
153
  responseCacheRuntime,
150
154
  });
151
155
  const port = getPort(explicitPort);
152
- const liveReloadConnections = new Set();
156
+ const liveReloadServer = mode === "development" ? new WebSocketServer({ noServer: true }) : null;
157
+ const liveReloadSockets = new Set();
158
+ liveReloadServer?.on("connection", (socket) => {
159
+ liveReloadSockets.add(socket);
160
+ socket.on("close", () => {
161
+ liveReloadSockets.delete(socket);
162
+ });
163
+ });
153
164
  let invalidationTimer = null;
154
165
  let pendingInvalidation = null;
155
166
  let resolvePendingInvalidation = null;
@@ -176,8 +187,10 @@ async function createServer(projectRoot, mode, explicitPort, options = {}) {
176
187
  .then(() => deploymentRuntime.invalidateDevelopmentState());
177
188
  invalidationPromise.then(
178
189
  () => {
179
- for (const connection of liveReloadConnections) {
180
- connection.write("event: reload\\ndata: update\\n\\n");
190
+ for (const socket of liveReloadSockets) {
191
+ if (socket.readyState === WebSocket.OPEN) {
192
+ socket.send("reload");
193
+ }
181
194
  }
182
195
  resolve();
183
196
  },
@@ -197,20 +210,6 @@ async function createServer(projectRoot, mode, explicitPort, options = {}) {
197
210
  try {
198
211
  const origin = `http://${req.headers.host ?? `localhost:${port}`}`;
199
212
  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
-
214
213
  await pendingInvalidation;
215
214
  await invalidationPromise;
216
215
  const method = req.method ?? "GET";
@@ -220,7 +219,9 @@ async function createServer(projectRoot, mode, explicitPort, options = {}) {
220
219
  ...(method === "GET" || method === "HEAD" ? {} : { body: req, duplex: "half" }),
221
220
  });
222
221
  const response = await deploymentRuntime.handle(request);
223
- const servedResponse = mode === "development" ? injectLiveReloadSnippet(response) : response;
222
+ const servedResponse = mode === "development"
223
+ ? injectLiveReloadSnippet(response)
224
+ : response;
224
225
 
225
226
  res.writeHead(servedResponse.status, servedResponse.headers);
226
227
  await writeResponseBody(res, servedResponse.body);
@@ -230,6 +231,19 @@ async function createServer(projectRoot, mode, explicitPort, options = {}) {
230
231
  }
231
232
  });
232
233
 
234
+ server.on("upgrade", (request, socket, head) => {
235
+ const origin = `http://${request.headers.host ?? `localhost:${port}`}`;
236
+ const pathname = new URL(request.url ?? "/", origin).pathname;
237
+ if (!liveReloadServer || pathname !== LIVE_RELOAD_PATHNAME) {
238
+ socket.destroy();
239
+ return;
240
+ }
241
+
242
+ liveReloadServer.handleUpgrade(request, socket, head, (webSocket) => {
243
+ liveReloadServer.emit("connection", webSocket, request);
244
+ });
245
+ });
246
+
233
247
  return {
234
248
  port,
235
249
  deploymentRuntime,
@@ -244,10 +258,14 @@ async function createServer(projectRoot, mode, explicitPort, options = {}) {
244
258
  clearTimeout(invalidationTimer);
245
259
  }
246
260
  watcher?.close();
247
- for (const connection of liveReloadConnections) {
248
- connection.end();
261
+ for (const socket of liveReloadSockets) {
262
+ socket.close();
263
+ }
264
+ if (liveReloadServer) {
265
+ await new Promise((resolve) => {
266
+ liveReloadServer.close(resolve);
267
+ });
249
268
  }
250
- liveReloadConnections.clear();
251
269
  await new Promise((resolve, reject) => {
252
270
  server.close((error) => {
253
271
  if (error) {