-
Notifications
You must be signed in to change notification settings - Fork 2
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
fix: Improve Static Server Reliability #11
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
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'; | ||||||
|
||||||
|
@@ -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, | ||||||
|
@@ -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,50 @@ 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 | ||||||
*/ | ||||||
// @ts-expect-error client and server fields are always present | ||||||
function resolveClientDir(options: InternalOptions) { | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
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. | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
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) { | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
return pth.startsWith('/') ? pth : '/' + pth; | ||||||
} | ||||||
|
||||||
function appendForwardSlash(pth: string) { | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
return pth.endsWith('/') ? pth : pth + '/'; | ||||||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.