Skip to content

Commit

Permalink
fix: improve static server reliability
Browse files Browse the repository at this point in the history
  • Loading branch information
lilnasy authored Dec 24, 2024
1 parent ac2b22b commit fa1c549
Showing 1 changed file with 63 additions and 6 deletions.
69 changes: 63 additions & 6 deletions package/src/server/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
/// <reference types="astro/client" />

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';

Expand Down Expand Up @@ -78,16 +80,25 @@ function handler(

const app = new App(manifest);

return (req: Request, server: Server): Promise<Response> => {
// 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<typeof clientAssetsPromise> | undefined;

return async (req: Request, server: Server): Promise<Response> => {
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,
Expand All @@ -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);
}
Expand All @@ -109,3 +120,49 @@ function handler(
});
};
}

async function getStaticAssets(clientDir: string) {
const dirEntries = await readdir(clientDir, { withFileTypes: true, recursive: true });
const publicPath = new Set<string>();
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/[email protected]/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 + '/';
}

0 comments on commit fa1c549

Please sign in to comment.