Skip to content

Commit

Permalink
Seperate loading and revalidating functions
Browse files Browse the repository at this point in the history
  • Loading branch information
oliverlynch committed Nov 13, 2024
1 parent e844a93 commit 6955f3f
Show file tree
Hide file tree
Showing 2 changed files with 50 additions and 23 deletions.
29 changes: 14 additions & 15 deletions packages/astro/src/assets/build/generate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import { getConfiguredImageService } from '../internal.js';
import type { LocalImageService } from '../services/service.js';
import type { AssetsGlobalStaticImagesList, ImageMetadata, ImageTransform } from '../types.js';
import { isESMImportedImage } from '../utils/imageKind.js';
import { type RemoteCacheEntry, loadRemoteImage } from './remote.js';
import { type RemoteCacheEntry, loadRemoteImage, revalidateRemoteImage } from './remote.js';

interface GenerationDataUncached {
cached: CacheStatus.Miss;
Expand Down Expand Up @@ -197,16 +197,20 @@ export async function generateImagesForPath(
};
}

// Try to freshen the cache
// Try to revalidate the cache
if (JSONData.etag) {
const fresh = await loadImage(options.src as string, env, JSONData.etag);
const revalidatedData = await revalidateRemoteImage(options.src as string, JSONData.etag);

if (fresh.data.length) {
originalImage = fresh;
if (revalidatedData.data.length) {
// Image cache was stale, update original image to avoid redownload
originalImage = revalidatedData;
} else {
fresh.data = Buffer.from(JSONData.data, 'base64'); // Reuse cache data as it is still good
await writeRemoteCacheFile(cachedFileURL, fresh);
await fs.promises.writeFile(finalFileURL, fresh.data);
revalidatedData.data = Buffer.from(JSONData.data, 'base64');

// Freshen cache on disk
await writeRemoteCacheFile(cachedFileURL, revalidatedData);

await fs.promises.writeFile(finalFileURL, revalidatedData.data);
return { cached: CacheStatus.Revalidated };
}
}
Expand Down Expand Up @@ -309,14 +313,9 @@ export function getStaticImageList(): AssetsGlobalStaticImagesList {
return globalThis.astroAsset.staticImages;
}

async function loadImage(path: string, env: AssetEnv, etag?: string): Promise<ImageData> {
async function loadImage(path: string, env: AssetEnv): Promise<ImageData> {
if (isRemotePath(path)) {
const remoteImage = await loadRemoteImage(path, etag);
return {
data: remoteImage.data,
expires: remoteImage.expires,
etag: remoteImage.etag,
};
return await loadRemoteImage(path);
}

return {
Expand Down
44 changes: 36 additions & 8 deletions packages/astro/src/assets/build/remote.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,11 @@ import CachePolicy from 'http-cache-semantics';

export type RemoteCacheEntry = { data: string; expires: number; etag?: string };

export async function loadRemoteImage(src: string, etag?: string) {
const opt = etag
? {
headers: { 'If-None-Match': etag },
}
: undefined;
const req = new Request(src, opt);
export async function loadRemoteImage(src: string) {
const req = new Request(src);
const res = await fetch(req);

if (!res.ok && res.status != 304) {
if (!res.ok) {
throw new Error(
`Failed to load remote image ${src}. The request did not return a 200 OK response. (received ${res.status}))`,
);
Expand All @@ -28,6 +23,39 @@ export async function loadRemoteImage(src: string, etag?: string) {
};
}

export async function revalidateRemoteImage(src: string, etag: string) {
const req = new Request(src, { headers: { 'If-None-Match': etag } });
const res = await fetch(req);

if (!res.ok && res.status != 304) {
throw new Error(
`Failed to revalidate cached remote image ${src}. The request did not return a 200 OK / 304 NOT MODIFIED response. (received ${res.status}))`,
);
}

const data = Buffer.from(await res.arrayBuffer());

if (res.ok && !data.length) {
// Server did not include body but indicated cache was stale
return await loadRemoteImage(src);
}

// calculate an expiration date based on the response's TTL
const policy = new CachePolicy(
webToCachePolicyRequest(req),
webToCachePolicyResponse(
res.ok ? res : new Response(null, { status: 200, headers: res.headers }),
), // 304 responses themselves are not cachable, so just pretend to get the refreshed TTL
);
const expires = policy.storable() ? policy.timeToLive() : 0;

return {
data,
expires: Date.now() + expires,
etag: res.headers.get('Etag'),
};
}

function webToCachePolicyRequest({ url, method, headers: _headers }: Request): CachePolicy.Request {
let headers: CachePolicy.Headers = {};
// Be defensive here due to a cookie header bug in [email protected] + undici
Expand Down

0 comments on commit 6955f3f

Please sign in to comment.