From fa1c54908f5ae47d75357be95859070f07bcd553 Mon Sep 17 00:00:00 2001
From: Arsh <69170106+lilnasy@users.noreply.github.com>
Date: Mon, 23 Dec 2024 18:00:28 +0000
Subject: [PATCH] fix: improve static server reliability
---
package/src/server/index.ts | 69 +++++++++++++++++++++++++++++++++----
1 file changed, 63 insertions(+), 6 deletions(-)
diff --git a/package/src/server/index.ts b/package/src/server/index.ts
index 642a2dd..1ced1ec 100644
--- a/package/src/server/index.ts
+++ b/package/src/server/index.ts
@@ -1,5 +1,7 @@
///
-
+import path, { relative } from 'node:path';
+import url from 'node:url';
+import { readdir } from 'node:fs/promises';
import cluster from 'node:cluster';
import os from 'node:os';
@@ -78,16 +80,25 @@ function handler(
const app = new App(manifest);
- return (req: Request, server: Server): Promise => {
+ // The dist may be copied somewhere after building.
+ // The build environment's full client path (options.client) can't be relied on in production.
+ // `resolveClientDir()` finds the full path to the client directory in the current environment
+ const clientDir = resolveClientDir(options);
+
+ const clientAssetsPromise = getStaticAssets(clientDir);
+ let clientAssets: Awaited | undefined;
+
+ return async (req: Request, server: Server): Promise => {
const routeData = app.match(req);
if (!routeData) {
const url = new URL(req.url);
-
- const manifestAssetExists = manifest.assets.has(url.pathname);
+ const staticAssetExists = (clientAssets ??= await clientAssetsPromise).has(
+ url.pathname,
+ );
// If the manifest asset doesn't exist, or the request url ends with a slash
// we should serve the index.html file from the respective directory.
- if (!manifestAssetExists || req.url.endsWith('/')) {
+ if (!staticAssetExists || req.url.endsWith('/')) {
const localPath = new URL(
`./${app.removeBase(url.pathname)}/index.html`,
clientRoot,
@@ -96,7 +107,7 @@ function handler(
}
// Otherwise we attempt to serve the static asset from the client directory.
- if (manifestAssetExists) {
+ if (staticAssetExists) {
const localPath = new URL(app.removeBase(url.pathname), clientRoot);
return serveStaticFile(url.pathname, localPath, clientRoot, options);
}
@@ -109,3 +120,49 @@ function handler(
});
};
}
+
+async function getStaticAssets(clientDir: string) {
+ const dirEntries = await readdir(clientDir, { withFileTypes: true, recursive: true });
+ const publicPath = new Set();
+ for (const entry of dirEntries) {
+ if (entry.isFile() == false) continue;
+ publicPath.add(
+ prependForwardSlash(path.relative(clientDir, entry.parentPath) + '/' + entry.name),
+ );
+ }
+ return publicPath;
+}
+
+/**
+ * From https://github.com/withastro/adapters/blob/@astrojs/node@9.0.0/packages/node/src/serve-static.ts#L109-L125
+ *
+ * Copyright of withastro/adapters contributors, Reproduced under MIT License
+ */
+function resolveClientDir(options: InternalOptions) {
+ const clientURLRaw = new URL(options.client);
+ const serverURLRaw = new URL(options.server);
+ const rel = path.relative(
+ url.fileURLToPath(serverURLRaw),
+ url.fileURLToPath(clientURLRaw),
+ );
+
+ // walk up the parent folders until you find the one that is the root of the server entry folder. This is how we find the client folder relatively.
+ const serverFolder = path.basename(options.server);
+ let serverEntryFolderURL = path.dirname(import.meta.url);
+ while (!serverEntryFolderURL.endsWith(serverFolder)) {
+ serverEntryFolderURL = path.dirname(serverEntryFolderURL);
+ }
+
+ const serverEntryURL = serverEntryFolderURL + '/entry.mjs';
+ const clientURL = new URL(appendForwardSlash(rel), serverEntryURL);
+ const client = url.fileURLToPath(clientURL);
+ return client;
+}
+
+function prependForwardSlash(pth: string) {
+ return pth.startsWith('/') ? pth : '/' + pth;
+}
+
+function appendForwardSlash(pth: string) {
+ return pth.endsWith('/') ? pth : pth + '/';
+}