Skip to content

Commit e8f6dc8

Browse files
authored
Feat: Add dev overrides (#689)
* implemented some dev override * changeset * fix linting * fix lockfile * review fix * review
1 parent ce09e57 commit e8f6dc8

File tree

11 files changed

+550
-22
lines changed

11 files changed

+550
-22
lines changed

.changeset/forty-cars-smile.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@opennextjs/aws": minor
3+
---
4+
5+
Added some override for debugging OpenNext locally

packages/open-next/package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,12 +45,14 @@
4545
"aws4fetch": "^1.0.18",
4646
"chalk": "^5.3.0",
4747
"esbuild": "0.19.2",
48+
"express": "5.0.1",
4849
"path-to-regexp": "^6.3.0",
4950
"promise.series": "^0.2.0",
5051
"urlpattern-polyfill": "^10.0.0"
5152
},
5253
"devDependencies": {
5354
"@types/aws-lambda": "^8.10.109",
55+
"@types/express": "5.0.0",
5456
"@types/node": "catalog:",
5557
"tsc-alias": "^1.8.8",
5658
"typescript": "catalog:"

packages/open-next/src/build/validateConfig.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,8 @@ const compatibilityMatrix: Record<IncludedWrapper, IncludedConverter[]> = {
2020
"cloudflare-edge": ["edge"],
2121
"cloudflare-node": ["edge"],
2222
node: ["node"],
23-
dummy: [],
23+
"express-dev": ["node"],
24+
dummy: ["dummy"],
2425
};
2526

2627
function validateFunctionOptions(fnOptions: FunctionOptions) {
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import fs from "node:fs";
2+
import type { ImageLoader } from "types/overrides";
3+
4+
export default {
5+
name: "fs-dev",
6+
load: async (url: string) => {
7+
const basePath = "../../assets";
8+
const body = fs.createReadStream(`${basePath}/${url}`);
9+
const contentType = url.endsWith(".png") ? "image/png" : "image/jpeg";
10+
return {
11+
body,
12+
contentType,
13+
cacheControl: "public, max-age=31536000, immutable",
14+
};
15+
},
16+
} satisfies ImageLoader;
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import type { IncrementalCache } from "types/overrides.js";
2+
3+
import fs from "node:fs/promises";
4+
import path from "node:path";
5+
6+
const buildId = process.env.NEXT_BUILD_ID;
7+
const basePath = path.resolve(process.cwd(), `../../cache/${buildId}`);
8+
9+
const getCacheKey = (key: string) => {
10+
return path.join(basePath, `${key}.cache`);
11+
};
12+
13+
const cache: IncrementalCache = {
14+
name: "fs-dev",
15+
get: async (key: string) => {
16+
const fileData = await fs.readFile(getCacheKey(key), "utf-8");
17+
const data = JSON.parse(fileData);
18+
const { mtime } = await fs.stat(getCacheKey(key));
19+
return {
20+
value: data,
21+
lastModified: mtime.getTime(),
22+
};
23+
},
24+
set: async (key, value, isFetch) => {
25+
const data = JSON.stringify(value);
26+
const cacheKey = getCacheKey(key);
27+
// We need to create the directory before writing the file
28+
await fs.mkdir(path.dirname(cacheKey), { recursive: true });
29+
await fs.writeFile(cacheKey, data);
30+
},
31+
delete: async (key) => {
32+
await fs.rm(getCacheKey(key));
33+
},
34+
};
35+
36+
export default cache;
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import type { Queue } from "types/overrides.js";
2+
3+
const queue: Queue = {
4+
name: "dev-queue",
5+
send: async (message) => {
6+
const prerenderManifest = (await import("../../adapters/config/index.js"))
7+
.PrerenderManifest as any;
8+
const { host, url } = message.MessageBody;
9+
const protocol = host.includes("localhost") ? "http" : "https";
10+
const revalidateId: string = prerenderManifest.preview.previewModeId;
11+
await globalThis.internalFetch(`${protocol}://${host}${url}`, {
12+
method: "HEAD",
13+
headers: {
14+
"x-prerender-revalidate": revalidateId,
15+
"x-isr": "1",
16+
},
17+
});
18+
},
19+
};
20+
21+
export default queue;
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import type { TagCache } from "types/overrides";
2+
3+
import fs from "node:fs";
4+
5+
const tagFile = "../../dynamodb-provider/dynamodb-cache.json";
6+
7+
const tagContent = fs.readFileSync(tagFile, "utf-8");
8+
9+
let tags = JSON.parse(tagContent) as {
10+
tag: { S: string };
11+
path: { S: string };
12+
revalidatedAt: { N: string };
13+
}[];
14+
15+
const tagCache: TagCache = {
16+
name: "fs-dev",
17+
getByPath: async (path: string) => {
18+
return tags
19+
.filter((tagPathMapping) => tagPathMapping.path.S === path)
20+
.map((tag) => tag.tag.S);
21+
},
22+
getByTag: async (tag: string) => {
23+
return tags
24+
.filter((tagPathMapping) => tagPathMapping.tag.S === tag)
25+
.map((tag) => tag.path.S);
26+
},
27+
getLastModified: async (path: string, lastModified?: number) => {
28+
const revalidatedTags = tags.filter(
29+
(tagPathMapping) =>
30+
tagPathMapping.path.S === path &&
31+
Number.parseInt(tagPathMapping.revalidatedAt.N) > (lastModified ?? 0),
32+
);
33+
return revalidatedTags.length > 0 ? -1 : (lastModified ?? Date.now());
34+
},
35+
writeTags: async (newTags) => {
36+
const newTagsSet = new Set(
37+
newTags.map(({ tag, path }) => `${tag}-${path}`),
38+
);
39+
const unchangedTags = tags.filter(
40+
({ tag, path }) => !newTagsSet.has(`${tag.S}-${path.S}`),
41+
);
42+
tags = unchangedTags.concat(
43+
newTags.map((tag) => ({
44+
tag: { S: tag.tag },
45+
path: { S: tag.path },
46+
revalidatedAt: { N: String(tag.revalidatedAt) },
47+
})),
48+
);
49+
},
50+
};
51+
52+
export default tagCache;
Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,14 @@
1+
import type { InternalEvent, StreamCreator } from "types/open-next";
12
import type { Wrapper, WrapperHandler } from "types/overrides";
23

3-
const dummyWrapper: WrapperHandler = async () => async () => undefined;
4+
const dummyWrapper: WrapperHandler = async (handler, converter) => {
5+
return async (event: InternalEvent, responseStream?: StreamCreator) => {
6+
return await handler(event, responseStream);
7+
};
8+
};
49

510
export default {
611
name: "dummy",
712
wrapper: dummyWrapper,
8-
supportStreaming: false,
13+
supportStreaming: true,
914
} satisfies Wrapper;
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
import express from "express";
2+
3+
import type { StreamCreator } from "types/open-next.js";
4+
import type { WrapperHandler } from "types/overrides.js";
5+
6+
const wrapper: WrapperHandler = async (handler, converter) => {
7+
const app = express();
8+
// To serve static assets
9+
app.use(express.static("../../assets"));
10+
11+
const imageHandlerPath = "../../image-optimization-function/index.mjs";
12+
const imageHandler = await import(imageHandlerPath).then((m) => m.handler);
13+
14+
app.all("/_next/image", async (req, res) => {
15+
const internalEvent = await converter.convertFrom(req);
16+
const _res: StreamCreator = {
17+
writeHeaders: (prelude) => {
18+
res.writeHead(prelude.statusCode, prelude.headers);
19+
return res;
20+
},
21+
};
22+
await imageHandler(internalEvent, _res);
23+
});
24+
25+
app.all("*paths", async (req, res) => {
26+
const internalEvent = await converter.convertFrom(req);
27+
const _res: StreamCreator = {
28+
writeHeaders: (prelude) => {
29+
res.writeHead(prelude.statusCode, prelude.headers);
30+
return res;
31+
},
32+
onFinish: () => {},
33+
};
34+
await handler(internalEvent, _res);
35+
});
36+
37+
const server = app.listen(
38+
Number.parseInt(process.env.PORT ?? "3000", 10),
39+
() => {
40+
console.log(`Server running on port ${process.env.PORT ?? 3000}`);
41+
},
42+
);
43+
44+
app.on("error", (err) => {
45+
console.error("error", err);
46+
});
47+
48+
return () => {
49+
server.close();
50+
};
51+
};
52+
53+
export default {
54+
wrapper,
55+
name: "expresss-dev",
56+
supportStreaming: true,
57+
};

packages/open-next/src/types/open-next.ts

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,7 @@ export type IncludedWrapper =
9696
| "cloudflare"
9797
| "cloudflare-edge"
9898
| "cloudflare-node"
99+
| "express-dev"
99100
| "dummy";
100101

101102
export type IncludedConverter =
@@ -133,13 +134,17 @@ export interface MiddlewareResult
133134
extends RoutingResult,
134135
BaseEventOrResult<"middleware"> {}
135136

136-
export type IncludedQueue = "sqs" | "sqs-lite" | "dummy";
137+
export type IncludedQueue = "sqs" | "sqs-lite" | "direct" | "dummy";
137138

138-
export type IncludedIncrementalCache = "s3" | "s3-lite" | "dummy";
139+
export type IncludedIncrementalCache = "s3" | "s3-lite" | "fs-dev" | "dummy";
139140

140-
export type IncludedTagCache = "dynamodb" | "dynamodb-lite" | "dummy";
141+
export type IncludedTagCache =
142+
| "dynamodb"
143+
| "dynamodb-lite"
144+
| "fs-dev"
145+
| "dummy";
141146

142-
export type IncludedImageLoader = "s3" | "host" | "dummy";
147+
export type IncludedImageLoader = "s3" | "host" | "fs-dev" | "dummy";
143148

144149
export type IncludedOriginResolver = "pattern-env" | "dummy";
145150

0 commit comments

Comments
 (0)