Skip to content
This repository was archived by the owner on Jul 6, 2025. It is now read-only.

Commit c87c17c

Browse files
committed
Improve module cache system
1 parent 1d6d8c0 commit c87c17c

File tree

4 files changed

+55
-28
lines changed

4 files changed

+55
-28
lines changed

lib/cache.ts

Lines changed: 44 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,16 @@ import { existsDir, existsFile } from "./fs.ts";
33
import log from "./log.ts";
44
import util from "./util.ts";
55

6-
const memoryCache = new Map<string, [Uint8Array, { headers: Record<string, string> }]>();
6+
type Meta = {
7+
url: string;
8+
headers: Record<string, string>;
9+
now: {
10+
secs_since_epoch: number;
11+
nanos_since_epoch: number;
12+
};
13+
};
14+
15+
const memoryCache = new Map<string, [content: Uint8Array, meta: Meta]>();
716

817
/** fetch and cache remote contents */
918
export default async function cache(
@@ -27,20 +36,24 @@ export default async function cache(
2736
if (!options?.forceRefresh && !isLocalhost) {
2837
if (modulesCacheDir) {
2938
if (await existsFile(contentFilepath) && await existsFile(metaFilepath)) {
30-
const [content, meta] = await Promise.all([
39+
const [content, metaJSON] = await Promise.all([
3140
Deno.readFile(contentFilepath),
3241
Deno.readTextFile(metaFilepath),
3342
]);
3443
try {
35-
const { headers = {} } = JSON.parse(meta);
36-
return new Response(content, { headers: { ...headers, "cache-hit": "true" } });
44+
const meta = JSON.parse(metaJSON);
45+
if (!isExpired(meta)) {
46+
return new Response(content, { headers: { ...meta.headers, "cache-hit": "true" } });
47+
}
3748
} catch (_e) {
38-
return new Response(content);
49+
log.debug(`skip cache of ${url}: invalid cache metadata file`);
3950
}
4051
}
4152
} else if (memoryCache.has(hashname)) {
42-
const [content, { headers }] = memoryCache.get(hashname)!;
43-
return new Response(content, { headers: { ...headers, "cache-hit": "true" } });
53+
const [content, meta] = memoryCache.get(hashname)!;
54+
if (!isExpired(meta)) {
55+
return new Response(content, { headers: { ...meta.headers, "cache-hit": "true" } });
56+
}
4457
}
4558
}
4659

@@ -64,27 +77,27 @@ export default async function cache(
6477
if (res.ok && !isLocalhost) {
6578
const buffer = await res.arrayBuffer();
6679
const content = new Uint8Array(buffer);
67-
const headers: Record<string, string> = {};
80+
const meta: Meta = {
81+
url,
82+
headers: {},
83+
now: {
84+
secs_since_epoch: Math.round(Date.now() / 1000),
85+
nanos_since_epoch: 0,
86+
},
87+
};
6888
res.headers.forEach((val, key) => {
69-
headers[key] = val;
89+
meta.headers[key] = val;
7090
});
7191
if (modulesCacheDir) {
7292
if (!(await existsDir(cacheDir))) {
7393
await Deno.mkdir(cacheDir, { recursive: true });
7494
}
7595
await Promise.all([
7696
Deno.writeFile(contentFilepath, content),
77-
Deno.writeTextFile(
78-
metaFilepath,
79-
JSON.stringify(
80-
{ headers, url, now: { secs_since_epoch: Math.round(Date.now() / 1000), nanos_since_epoch: 0 } },
81-
undefined,
82-
2,
83-
),
84-
),
97+
Deno.writeTextFile(metaFilepath, JSON.stringify(meta, undefined, 2)),
8598
]);
8699
} else {
87-
memoryCache.set(hashname, [content, { headers }]);
100+
memoryCache.set(hashname, [content, meta]);
88101
}
89102
return new Response(content, { headers: res.headers });
90103
}
@@ -94,3 +107,16 @@ export default async function cache(
94107

95108
return finalRes;
96109
}
110+
111+
function isExpired(meta: Meta) {
112+
const cc = meta.headers["cache-control"];
113+
const dataCacheTtl = cc && cc.includes("max-age=") ? parseInt(cc.split("max-age=")[1]) : undefined;
114+
if (dataCacheTtl) {
115+
const now = Date.now();
116+
const expireTime = (meta.now.secs_since_epoch + dataCacheTtl) * 1000;
117+
if (now > expireTime) {
118+
return true;
119+
}
120+
}
121+
return false;
122+
}

lib/helpers.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import util from "./util.ts";
22

3+
export const regFullVersion = /@\d+\.\d+\.\d+/;
34
export const builtinModuleExts = ["tsx", "ts", "mts", "jsx", "js", "mjs"];
45

56
export class FetchError extends Error {

server/serve_dist.ts

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,8 @@
11
import { readableStreamFromReader } from "https://deno.land/[email protected]/streams/conversion.ts";
2-
import { builtinModuleExts } from "../lib/helpers.ts";
2+
import { builtinModuleExts, regFullVersion } from "../lib/helpers.ts";
33
import log from "../lib/log.ts";
44
import type { AlephConfig } from "./types.ts";
55

6-
const REG_FULL_VERSION = /@\d+\.\d+\.\d+/;
7-
86
export default {
97
test: (pathname: string) => {
108
return pathname.startsWith("/-/") ||
@@ -47,7 +45,7 @@ export default {
4745
if (etag) {
4846
headers.append("Etag", etag);
4947
}
50-
if (searchParams.get("v") || (pathname.startsWith("/-/") && REG_FULL_VERSION.test(pathname))) {
48+
if (searchParams.get("v") || (pathname.startsWith("/-/") && regFullVersion.test(pathname))) {
5149
headers.append("Cache-Control", "public, max-age=31536000, immutable");
5250
}
5351
return new Response(readableStreamFromReader(file), { headers });

server/transformer.ts

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import MagicString from "https://esm.sh/[email protected]";
22
import { parseDeps, transform } from "../compiler/mod.ts";
33
import type { TransformOptions, TransformResult } from "../compiler/types.ts";
44
import { readCode } from "../lib/fs.ts";
5-
import { builtinModuleExts, restoreUrl, toLocalPath } from "../lib/helpers.ts";
5+
import { builtinModuleExts, regFullVersion, restoreUrl, toLocalPath } from "../lib/helpers.ts";
66
import log from "../lib/log.ts";
77
import util from "../lib/util.ts";
88
import { bundleCSS } from "./bundle_css.ts";
@@ -163,11 +163,13 @@ export default {
163163
resBody = code;
164164
}
165165
}
166-
return new Response(resBody, {
167-
headers: {
168-
"Content-Type": `${resType}; charset=utf-8`,
169-
"Etag": etag,
170-
},
166+
const headers = new Headers({
167+
"Content-Type": `${resType}; charset=utf-8`,
168+
"Etag": etag,
171169
});
170+
if (searchParams.get("v") || (pathname.startsWith("/-/") && regFullVersion.test(pathname))) {
171+
headers.append("Cache-Control", "public, max-age=31536000, immutable");
172+
}
173+
return new Response(resBody, { headers });
172174
},
173175
};

0 commit comments

Comments
 (0)