@opennextjs/cloudflare 1.0.4 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/api/cloudflare-context.d.ts +5 -0
- package/dist/api/config.d.ts +12 -2
- package/dist/api/config.js +9 -2
- package/dist/api/durable-objects/bucket-cache-purge.d.ts +7 -0
- package/dist/api/durable-objects/bucket-cache-purge.js +75 -0
- package/dist/api/durable-objects/bucket-cache-purge.spec.js +121 -0
- package/dist/api/overrides/cache-purge/index.d.ts +12 -0
- package/dist/api/overrides/cache-purge/index.js +26 -0
- package/dist/api/overrides/internal.d.ts +2 -0
- package/dist/api/overrides/internal.js +52 -0
- package/dist/api/overrides/queue/do-queue.js +1 -1
- package/dist/api/overrides/queue/queue-cache.d.ts +36 -0
- package/dist/api/overrides/queue/queue-cache.js +93 -0
- package/dist/api/overrides/queue/queue-cache.spec.d.ts +1 -0
- package/dist/api/overrides/queue/queue-cache.spec.js +92 -0
- package/dist/api/overrides/tag-cache/d1-next-tag-cache.js +2 -1
- package/dist/api/overrides/tag-cache/do-sharded-tag-cache.d.ts +20 -0
- package/dist/api/overrides/tag-cache/do-sharded-tag-cache.js +70 -7
- package/dist/api/overrides/tag-cache/do-sharded-tag-cache.spec.js +81 -1
- package/dist/cli/build/bundle-server.d.ts +1 -1
- package/dist/cli/build/bundle-server.js +16 -38
- package/dist/cli/build/open-next/compileDurableObjects.js +1 -0
- package/dist/cli/build/open-next/createServerBundle.js +9 -10
- package/dist/cli/build/patches/index.d.ts +0 -1
- package/dist/cli/build/patches/index.js +0 -1
- package/dist/cli/build/patches/investigated/index.d.ts +0 -1
- package/dist/cli/build/patches/investigated/index.js +0 -1
- package/dist/cli/build/patches/plugins/load-manifest.d.ts +3 -1
- package/dist/cli/build/patches/plugins/load-manifest.js +49 -7
- package/dist/cli/build/patches/plugins/next-server.d.ts +25 -0
- package/dist/cli/build/patches/plugins/next-server.js +110 -0
- package/dist/cli/build/patches/plugins/next-server.spec.d.ts +1 -0
- package/dist/cli/build/patches/plugins/next-server.spec.js +429 -0
- package/dist/cli/build/patches/plugins/open-next.d.ts +8 -0
- package/dist/cli/build/patches/plugins/open-next.js +39 -0
- package/dist/cli/templates/init.js +5 -0
- package/dist/cli/templates/shims/throw.d.ts +2 -0
- package/dist/cli/templates/shims/throw.js +2 -0
- package/dist/cli/templates/worker.d.ts +1 -0
- package/dist/cli/templates/worker.js +2 -0
- package/package.json +3 -3
- package/dist/cli/build/patches/investigated/patch-cache.d.ts +0 -14
- package/dist/cli/build/patches/investigated/patch-cache.js +0 -40
- package/dist/cli/build/patches/plugins/build-id.d.ts +0 -6
- package/dist/cli/build/patches/plugins/build-id.js +0 -29
- package/dist/cli/build/patches/plugins/build-id.spec.js +0 -82
- package/dist/cli/build/patches/plugins/eval-manifest.d.ts +0 -7
- package/dist/cli/build/patches/plugins/eval-manifest.js +0 -61
- package/dist/cli/build/patches/to-investigate/inline-middleware-manifest.d.ts +0 -6
- package/dist/cli/build/patches/to-investigate/inline-middleware-manifest.js +0 -15
- /package/dist/{cli/build/patches/plugins/build-id.spec.d.ts → api/durable-objects/bucket-cache-purge.spec.d.ts} +0 -0
|
@@ -2,6 +2,9 @@ import type { NextModeTagCache } from "@opennextjs/aws/types/overrides.js";
|
|
|
2
2
|
export declare const DEFAULT_WRITE_RETRIES = 3;
|
|
3
3
|
export declare const DEFAULT_NUM_SHARDS = 4;
|
|
4
4
|
export declare const NAME = "do-sharded-tag-cache";
|
|
5
|
+
export declare const DEFAULT_REGION: "enam";
|
|
6
|
+
export declare const AVAILABLE_REGIONS: readonly ["enam", "weur", "apac", "sam", "afr", "oc"];
|
|
7
|
+
type AllowedDurableObjectRegion = (typeof AVAILABLE_REGIONS)[number];
|
|
5
8
|
interface ShardedDOTagCacheOptions {
|
|
6
9
|
/**
|
|
7
10
|
* The number of shards that will be used.
|
|
@@ -49,6 +52,18 @@ interface ShardedDOTagCacheOptions {
|
|
|
49
52
|
shardReplication?: {
|
|
50
53
|
numberOfSoftReplicas: number;
|
|
51
54
|
numberOfHardReplicas: number;
|
|
55
|
+
/**
|
|
56
|
+
* Enable regional replication for the shards.
|
|
57
|
+
*
|
|
58
|
+
* If not set, no regional replication will be performed and durable objects will be created without a location hint
|
|
59
|
+
*
|
|
60
|
+
* Can be used to reduce latency for users in different regions and to spread the load across multiple regions.
|
|
61
|
+
*
|
|
62
|
+
* This will increase the number of durable objects created, as each shard will be replicated in all regions.
|
|
63
|
+
*/
|
|
64
|
+
regionalReplication?: {
|
|
65
|
+
defaultRegion: AllowedDurableObjectRegion;
|
|
66
|
+
};
|
|
52
67
|
};
|
|
53
68
|
/**
|
|
54
69
|
* The number of retries to perform when writing tags
|
|
@@ -62,11 +77,13 @@ interface DOIdOptions {
|
|
|
62
77
|
numberOfReplicas: number;
|
|
63
78
|
shardType: "soft" | "hard";
|
|
64
79
|
replicaId?: number;
|
|
80
|
+
region?: DurableObjectLocationHint;
|
|
65
81
|
}
|
|
66
82
|
export declare class DOId {
|
|
67
83
|
options: DOIdOptions;
|
|
68
84
|
shardId: string;
|
|
69
85
|
replicaId: number;
|
|
86
|
+
region?: DurableObjectLocationHint;
|
|
70
87
|
constructor(options: DOIdOptions);
|
|
71
88
|
private generateRandomNumberBetween;
|
|
72
89
|
get key(): string;
|
|
@@ -83,6 +100,8 @@ declare class ShardedDOTagCache implements NextModeTagCache {
|
|
|
83
100
|
readonly numSoftReplicas: number;
|
|
84
101
|
readonly numHardReplicas: number;
|
|
85
102
|
readonly maxWriteRetries: number;
|
|
103
|
+
readonly enableRegionalReplication: boolean;
|
|
104
|
+
readonly defaultRegion: AllowedDurableObjectRegion;
|
|
86
105
|
localCache?: Cache;
|
|
87
106
|
constructor(opts?: ShardedDOTagCacheOptions);
|
|
88
107
|
private getDurableObjectStub;
|
|
@@ -94,6 +113,7 @@ declare class ShardedDOTagCache implements NextModeTagCache {
|
|
|
94
113
|
* @returns An array of TagCacheDOId and tag
|
|
95
114
|
*/
|
|
96
115
|
private generateDOIdArray;
|
|
116
|
+
getClosestRegion(): "enam" | "sam" | "weur" | "apac" | "oc" | "afr";
|
|
97
117
|
/**
|
|
98
118
|
* Same tags are guaranteed to be in the same shard
|
|
99
119
|
* @param tags
|
|
@@ -2,26 +2,30 @@ import { debug, error } from "@opennextjs/aws/adapters/logger.js";
|
|
|
2
2
|
import { generateShardId } from "@opennextjs/aws/core/routing/queue.js";
|
|
3
3
|
import { IgnorableError } from "@opennextjs/aws/utils/error.js";
|
|
4
4
|
import { getCloudflareContext } from "../../cloudflare-context";
|
|
5
|
-
import { debugCache } from "../internal";
|
|
5
|
+
import { debugCache, purgeCacheByTags } from "../internal";
|
|
6
6
|
export const DEFAULT_WRITE_RETRIES = 3;
|
|
7
7
|
export const DEFAULT_NUM_SHARDS = 4;
|
|
8
8
|
export const NAME = "do-sharded-tag-cache";
|
|
9
9
|
const SOFT_TAG_PREFIX = "_N_T_/";
|
|
10
|
+
export const DEFAULT_REGION = "enam";
|
|
11
|
+
export const AVAILABLE_REGIONS = ["enam", "weur", "apac", "sam", "afr", "oc"];
|
|
10
12
|
export class DOId {
|
|
11
13
|
options;
|
|
12
14
|
shardId;
|
|
13
15
|
replicaId;
|
|
16
|
+
region;
|
|
14
17
|
constructor(options) {
|
|
15
18
|
this.options = options;
|
|
16
|
-
const { baseShardId, shardType, numberOfReplicas, replicaId } = options;
|
|
19
|
+
const { baseShardId, shardType, numberOfReplicas, replicaId, region } = options;
|
|
17
20
|
this.shardId = `tag-${shardType};${baseShardId}`;
|
|
18
21
|
this.replicaId = replicaId ?? this.generateRandomNumberBetween(1, numberOfReplicas);
|
|
22
|
+
this.region = region;
|
|
19
23
|
}
|
|
20
24
|
generateRandomNumberBetween(min, max) {
|
|
21
25
|
return Math.floor(Math.random() * (max - min + 1) + min);
|
|
22
26
|
}
|
|
23
27
|
get key() {
|
|
24
|
-
return `${this.shardId};replica-${this.replicaId}`;
|
|
28
|
+
return `${this.shardId};replica-${this.replicaId}${this.region ? `;region-${this.region}` : ""}`;
|
|
25
29
|
}
|
|
26
30
|
}
|
|
27
31
|
class ShardedDOTagCache {
|
|
@@ -31,19 +35,27 @@ class ShardedDOTagCache {
|
|
|
31
35
|
numSoftReplicas;
|
|
32
36
|
numHardReplicas;
|
|
33
37
|
maxWriteRetries;
|
|
38
|
+
enableRegionalReplication;
|
|
39
|
+
defaultRegion;
|
|
34
40
|
localCache;
|
|
35
41
|
constructor(opts = { baseShardSize: DEFAULT_NUM_SHARDS }) {
|
|
36
42
|
this.opts = opts;
|
|
37
43
|
this.numSoftReplicas = opts.shardReplication?.numberOfSoftReplicas ?? 1;
|
|
38
44
|
this.numHardReplicas = opts.shardReplication?.numberOfHardReplicas ?? 1;
|
|
39
45
|
this.maxWriteRetries = opts.maxWriteRetries ?? DEFAULT_WRITE_RETRIES;
|
|
46
|
+
this.enableRegionalReplication = Boolean(opts.shardReplication?.regionalReplication);
|
|
47
|
+
this.defaultRegion = opts.shardReplication?.regionalReplication?.defaultRegion ?? DEFAULT_REGION;
|
|
40
48
|
}
|
|
41
49
|
getDurableObjectStub(doId) {
|
|
42
50
|
const durableObject = getCloudflareContext().env.NEXT_TAG_CACHE_DO_SHARDED;
|
|
43
51
|
if (!durableObject)
|
|
44
52
|
throw new IgnorableError("No durable object binding for cache revalidation");
|
|
45
53
|
const id = durableObject.idFromName(doId.key);
|
|
46
|
-
|
|
54
|
+
debug("[shardedTagCache] - Accessing Durable Object : ", {
|
|
55
|
+
key: doId.key,
|
|
56
|
+
region: doId.region,
|
|
57
|
+
});
|
|
58
|
+
return durableObject.get(id, { locationHint: doId.region });
|
|
47
59
|
}
|
|
48
60
|
/**
|
|
49
61
|
* Generates a list of DO ids for the shards and replicas
|
|
@@ -55,9 +67,14 @@ class ShardedDOTagCache {
|
|
|
55
67
|
generateDOIdArray({ tags, shardType, generateAllReplicas = false, }) {
|
|
56
68
|
let replicaIndexes = [1];
|
|
57
69
|
const isSoft = shardType === "soft";
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
70
|
+
let numReplicas = 1;
|
|
71
|
+
if (this.opts.shardReplication) {
|
|
72
|
+
numReplicas = isSoft ? this.numSoftReplicas : this.numHardReplicas;
|
|
73
|
+
replicaIndexes = generateAllReplicas
|
|
74
|
+
? Array.from({ length: numReplicas }, (_, i) => i + 1)
|
|
75
|
+
: [undefined];
|
|
76
|
+
}
|
|
77
|
+
const regionalReplicas = replicaIndexes.flatMap((replicaId) => {
|
|
61
78
|
return tags
|
|
62
79
|
.filter((tag) => (isSoft ? tag.startsWith(SOFT_TAG_PREFIX) : !tag.startsWith(SOFT_TAG_PREFIX)))
|
|
63
80
|
.map((tag) => {
|
|
@@ -72,6 +89,51 @@ class ShardedDOTagCache {
|
|
|
72
89
|
};
|
|
73
90
|
});
|
|
74
91
|
});
|
|
92
|
+
if (!this.enableRegionalReplication)
|
|
93
|
+
return regionalReplicas;
|
|
94
|
+
// If we have regional replication enabled, we need to further duplicate the shards in all the regions
|
|
95
|
+
const regionalReplicasInAllRegions = generateAllReplicas
|
|
96
|
+
? regionalReplicas.flatMap(({ doId, tag }) => {
|
|
97
|
+
return AVAILABLE_REGIONS.map((region) => {
|
|
98
|
+
return {
|
|
99
|
+
doId: new DOId({
|
|
100
|
+
baseShardId: doId.options.baseShardId,
|
|
101
|
+
numberOfReplicas: numReplicas,
|
|
102
|
+
shardType,
|
|
103
|
+
replicaId: doId.replicaId,
|
|
104
|
+
region,
|
|
105
|
+
}),
|
|
106
|
+
tag,
|
|
107
|
+
};
|
|
108
|
+
});
|
|
109
|
+
})
|
|
110
|
+
: regionalReplicas.map(({ doId, tag }) => {
|
|
111
|
+
doId.region = this.getClosestRegion();
|
|
112
|
+
return { doId, tag };
|
|
113
|
+
});
|
|
114
|
+
return regionalReplicasInAllRegions;
|
|
115
|
+
}
|
|
116
|
+
getClosestRegion() {
|
|
117
|
+
const continent = getCloudflareContext().cf?.continent;
|
|
118
|
+
if (!continent)
|
|
119
|
+
return this.defaultRegion;
|
|
120
|
+
debug("[shardedTagCache] - Continent : ", continent);
|
|
121
|
+
switch (continent) {
|
|
122
|
+
case "AF":
|
|
123
|
+
return "afr";
|
|
124
|
+
case "AS":
|
|
125
|
+
return "apac";
|
|
126
|
+
case "EU":
|
|
127
|
+
return "weur";
|
|
128
|
+
case "NA":
|
|
129
|
+
return "enam";
|
|
130
|
+
case "OC":
|
|
131
|
+
return "oc";
|
|
132
|
+
case "SA":
|
|
133
|
+
return "sam";
|
|
134
|
+
default:
|
|
135
|
+
return this.defaultRegion;
|
|
136
|
+
}
|
|
75
137
|
}
|
|
76
138
|
/**
|
|
77
139
|
* Same tags are guaranteed to be in the same shard
|
|
@@ -196,6 +258,7 @@ class ShardedDOTagCache {
|
|
|
196
258
|
await Promise.all(shardedTagGroups.map(async ({ doId, tags }) => {
|
|
197
259
|
await this.performWriteTagsWithRetry(doId, tags, currentTime);
|
|
198
260
|
}));
|
|
261
|
+
await purgeCacheByTags(tags);
|
|
199
262
|
}
|
|
200
263
|
async performWriteTagsWithRetry(doId, tags, lastModified, retryNumber = 0) {
|
|
201
264
|
try {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
2
|
-
import shardedDOTagCache, { DOId } from "./do-sharded-tag-cache";
|
|
2
|
+
import shardedDOTagCache, { AVAILABLE_REGIONS, DOId } from "./do-sharded-tag-cache";
|
|
3
3
|
const hasBeenRevalidatedMock = vi.fn();
|
|
4
4
|
const writeTagsMock = vi.fn();
|
|
5
5
|
const idFromNameMock = vi.fn();
|
|
@@ -7,6 +7,8 @@ const getMock = vi
|
|
|
7
7
|
.fn()
|
|
8
8
|
.mockReturnValue({ hasBeenRevalidated: hasBeenRevalidatedMock, writeTags: writeTagsMock });
|
|
9
9
|
const waitUntilMock = vi.fn().mockImplementation(async (fn) => fn());
|
|
10
|
+
// @ts-expect-error - We define it here only for the test
|
|
11
|
+
globalThis.continent = undefined;
|
|
10
12
|
const sendDLQMock = vi.fn();
|
|
11
13
|
vi.mock("../../cloudflare-context", () => ({
|
|
12
14
|
getCloudflareContext: () => ({
|
|
@@ -17,6 +19,10 @@ vi.mock("../../cloudflare-context", () => ({
|
|
|
17
19
|
},
|
|
18
20
|
},
|
|
19
21
|
ctx: { waitUntil: waitUntilMock },
|
|
22
|
+
cf: {
|
|
23
|
+
// @ts-expect-error - We define it here only for the test
|
|
24
|
+
continent: globalThis.continent,
|
|
25
|
+
},
|
|
20
26
|
}),
|
|
21
27
|
}));
|
|
22
28
|
describe("DOShardedTagCache", () => {
|
|
@@ -96,6 +102,80 @@ describe("DOShardedTagCache", () => {
|
|
|
96
102
|
expect(secondDOId?.replicaId).toBeGreaterThanOrEqual(1);
|
|
97
103
|
expect(secondDOId?.replicaId).toBeLessThanOrEqual(2);
|
|
98
104
|
});
|
|
105
|
+
it("should generate one doIds, but in the default region", () => {
|
|
106
|
+
const cache = shardedDOTagCache({
|
|
107
|
+
baseShardSize: 4,
|
|
108
|
+
shardReplication: {
|
|
109
|
+
numberOfSoftReplicas: 2,
|
|
110
|
+
numberOfHardReplicas: 2,
|
|
111
|
+
regionalReplication: {
|
|
112
|
+
defaultRegion: "enam",
|
|
113
|
+
},
|
|
114
|
+
},
|
|
115
|
+
});
|
|
116
|
+
const shardedTagCollection = cache.groupTagsByDO({
|
|
117
|
+
tags: ["tag1", "_N_T_/tag1"],
|
|
118
|
+
generateAllReplicas: false,
|
|
119
|
+
});
|
|
120
|
+
expect(shardedTagCollection.length).toBe(2);
|
|
121
|
+
const firstDOId = shardedTagCollection[0]?.doId;
|
|
122
|
+
const secondDOId = shardedTagCollection[1]?.doId;
|
|
123
|
+
expect(firstDOId?.shardId).toBe("tag-soft;shard-3");
|
|
124
|
+
expect(firstDOId?.region).toBe("enam");
|
|
125
|
+
expect(secondDOId?.shardId).toBe("tag-hard;shard-1");
|
|
126
|
+
expect(secondDOId?.region).toBe("enam");
|
|
127
|
+
// We still need to check if the last part is between the correct boundaries
|
|
128
|
+
expect(firstDOId?.replicaId).toBeGreaterThanOrEqual(1);
|
|
129
|
+
expect(firstDOId?.replicaId).toBeLessThanOrEqual(2);
|
|
130
|
+
expect(secondDOId?.replicaId).toBeGreaterThanOrEqual(1);
|
|
131
|
+
expect(secondDOId?.replicaId).toBeLessThanOrEqual(2);
|
|
132
|
+
});
|
|
133
|
+
it("should generate one doIds, but in the correct region", () => {
|
|
134
|
+
// @ts-expect-error - We define it here only for the test
|
|
135
|
+
globalThis.continent = "EU";
|
|
136
|
+
const cache = shardedDOTagCache({
|
|
137
|
+
baseShardSize: 4,
|
|
138
|
+
shardReplication: {
|
|
139
|
+
numberOfSoftReplicas: 2,
|
|
140
|
+
numberOfHardReplicas: 2,
|
|
141
|
+
regionalReplication: {
|
|
142
|
+
defaultRegion: "enam",
|
|
143
|
+
},
|
|
144
|
+
},
|
|
145
|
+
});
|
|
146
|
+
const shardedTagCollection = cache.groupTagsByDO({
|
|
147
|
+
tags: ["tag1", "_N_T_/tag1"],
|
|
148
|
+
generateAllReplicas: false,
|
|
149
|
+
});
|
|
150
|
+
expect(shardedTagCollection.length).toBe(2);
|
|
151
|
+
expect(shardedTagCollection[0]?.doId.region).toBe("weur");
|
|
152
|
+
expect(shardedTagCollection[1]?.doId.region).toBe("weur");
|
|
153
|
+
//@ts-expect-error - We need to reset the global variable
|
|
154
|
+
globalThis.continent = undefined;
|
|
155
|
+
});
|
|
156
|
+
it("should generate all the appropriate replicas in all the regions with enableRegionalReplication", () => {
|
|
157
|
+
const cache = shardedDOTagCache({
|
|
158
|
+
baseShardSize: 4,
|
|
159
|
+
shardReplication: {
|
|
160
|
+
numberOfSoftReplicas: 2,
|
|
161
|
+
numberOfHardReplicas: 2,
|
|
162
|
+
regionalReplication: {
|
|
163
|
+
defaultRegion: "enam",
|
|
164
|
+
},
|
|
165
|
+
},
|
|
166
|
+
});
|
|
167
|
+
const shardedTagCollection = cache.groupTagsByDO({
|
|
168
|
+
tags: ["tag1", "_N_T_/tag1"],
|
|
169
|
+
generateAllReplicas: true,
|
|
170
|
+
});
|
|
171
|
+
// 6 regions times 4 shards replica
|
|
172
|
+
expect(shardedTagCollection.length).toBe(24);
|
|
173
|
+
shardedTagCollection.forEach(({ doId }) => {
|
|
174
|
+
expect(AVAILABLE_REGIONS).toContain(doId.region);
|
|
175
|
+
// It should end with the region
|
|
176
|
+
expect(doId.key).toMatch(/tag-(soft|hard);shard-\d;replica-\d;region-(enam|weur|sam|afr|apac|oc)$/);
|
|
177
|
+
});
|
|
178
|
+
});
|
|
99
179
|
});
|
|
100
180
|
});
|
|
101
181
|
describe("hasBeenRevalidated", () => {
|
|
@@ -6,7 +6,7 @@ export declare function bundleServer(buildOpts: BuildOptions): Promise<void>;
|
|
|
6
6
|
/**
|
|
7
7
|
* This function applies patches required for the code to run on workers.
|
|
8
8
|
*/
|
|
9
|
-
export declare function updateWorkerBundledCode(workerOutputFile: string
|
|
9
|
+
export declare function updateWorkerBundledCode(workerOutputFile: string): Promise<void>;
|
|
10
10
|
/**
|
|
11
11
|
* Gets the path of the worker.js file generated by the build process
|
|
12
12
|
*
|
|
@@ -9,12 +9,12 @@ import { getOpenNextConfig } from "../../api/config.js";
|
|
|
9
9
|
import { patchVercelOgLibrary } from "./patches/ast/patch-vercel-og-library.js";
|
|
10
10
|
import { patchWebpackRuntime } from "./patches/ast/webpack-runtime.js";
|
|
11
11
|
import * as patches from "./patches/index.js";
|
|
12
|
-
import { inlineBuildId } from "./patches/plugins/build-id.js";
|
|
13
12
|
import { inlineDynamicRequires } from "./patches/plugins/dynamic-requires.js";
|
|
14
|
-
import { inlineEvalManifest } from "./patches/plugins/eval-manifest.js";
|
|
15
13
|
import { inlineFindDir } from "./patches/plugins/find-dir.js";
|
|
16
14
|
import { patchInstrumentation } from "./patches/plugins/instrumentation.js";
|
|
17
15
|
import { inlineLoadManifest } from "./patches/plugins/load-manifest.js";
|
|
16
|
+
import { patchNextServer } from "./patches/plugins/next-server.js";
|
|
17
|
+
import { patchResolveCache } from "./patches/plugins/open-next.js";
|
|
18
18
|
import { handleOptionalDependencies } from "./patches/plugins/optional-deps.js";
|
|
19
19
|
import { patchPagesRouterContext } from "./patches/plugins/pages-router-context.js";
|
|
20
20
|
import { patchDepdDeprecations } from "./patches/plugins/patch-depd-deprecations.js";
|
|
@@ -80,31 +80,28 @@ export async function bundleServer(buildOpts) {
|
|
|
80
80
|
handleOptionalDependencies(optionalDependencies),
|
|
81
81
|
patchInstrumentation(updater, buildOpts),
|
|
82
82
|
patchPagesRouterContext(buildOpts),
|
|
83
|
-
inlineEvalManifest(updater, buildOpts),
|
|
84
83
|
inlineFindDir(updater, buildOpts),
|
|
85
84
|
inlineLoadManifest(updater, buildOpts),
|
|
86
|
-
|
|
85
|
+
patchNextServer(updater, buildOpts),
|
|
87
86
|
patchDepdDeprecations(updater),
|
|
87
|
+
patchResolveCache(updater, buildOpts),
|
|
88
88
|
// Apply updater updates, must be the last plugin
|
|
89
89
|
updater.plugin,
|
|
90
90
|
],
|
|
91
91
|
external: ["./middleware/handler.mjs"],
|
|
92
92
|
alias: {
|
|
93
|
-
//
|
|
94
|
-
// but just to be safe we replace it with a module that re-exports the native fetch
|
|
95
|
-
// we do this to both save on bundle size (there isn't really any benefit in us shipping the node-fetch code)
|
|
96
|
-
// and also get rid of a warning in the terminal caused by the package (because it performs an === comparison with -0)
|
|
93
|
+
// Workers have `fetch` so the `node-fetch` polyfill is not needed
|
|
97
94
|
"next/dist/compiled/node-fetch": path.join(buildOpts.outputDir, "cloudflare-templates/shims/fetch.js"),
|
|
98
|
-
//
|
|
99
|
-
// eval("require")("bufferutil");
|
|
100
|
-
// eval("require")("utf-8-validate");
|
|
95
|
+
// Workers have builtin Web Sockets
|
|
101
96
|
"next/dist/compiled/ws": path.join(buildOpts.outputDir, "cloudflare-templates/shims/empty.js"),
|
|
102
|
-
//
|
|
103
|
-
//
|
|
104
|
-
//
|
|
97
|
+
// The toolbox optimizer pulls severals MB of dependencies (`caniuse-lite`, `terser`, `acorn`, ...)
|
|
98
|
+
// Drop it to optimize the code size
|
|
99
|
+
// See https://github.com/vercel/next.js/blob/6eb235c/packages/next/src/server/optimize-amp.ts
|
|
100
|
+
"next/dist/compiled/@ampproject/toolbox-optimizer": path.join(buildOpts.outputDir, "cloudflare-templates/shims/throw.js"),
|
|
101
|
+
// The edge runtime is not supported
|
|
105
102
|
"next/dist/compiled/edge-runtime": path.join(buildOpts.outputDir, "cloudflare-templates/shims/empty.js"),
|
|
106
|
-
// `@next/env` is
|
|
107
|
-
//
|
|
103
|
+
// `@next/env` is used by Next to load environment variables from files.
|
|
104
|
+
// OpenNext inlines the values at build time so this is not needed.
|
|
108
105
|
"@next/env": path.join(buildOpts.outputDir, "cloudflare-templates/shims/env.js"),
|
|
109
106
|
},
|
|
110
107
|
define: {
|
|
@@ -133,7 +130,7 @@ export async function bundleServer(buildOpts) {
|
|
|
133
130
|
platform: "node",
|
|
134
131
|
});
|
|
135
132
|
fs.writeFileSync(openNextServerBundle + ".meta.json", JSON.stringify(result.metafile, null, 2));
|
|
136
|
-
await updateWorkerBundledCode(openNextServerBundle
|
|
133
|
+
await updateWorkerBundledCode(openNextServerBundle);
|
|
137
134
|
const isMonorepo = monorepoRoot !== appPath;
|
|
138
135
|
if (isMonorepo) {
|
|
139
136
|
fs.writeFileSync(path.join(outputPath, "handler.mjs"), `export { handler } from "./${normalizePath(packagePath)}/handler.mjs";`);
|
|
@@ -143,28 +140,9 @@ export async function bundleServer(buildOpts) {
|
|
|
143
140
|
/**
|
|
144
141
|
* This function applies patches required for the code to run on workers.
|
|
145
142
|
*/
|
|
146
|
-
export async function updateWorkerBundledCode(workerOutputFile
|
|
143
|
+
export async function updateWorkerBundledCode(workerOutputFile) {
|
|
147
144
|
const code = await readFile(workerOutputFile, "utf8");
|
|
148
|
-
const patchedCode = await patchCodeWithValidations(code, [
|
|
149
|
-
["require", patches.patchRequire],
|
|
150
|
-
["cacheHandler", (code) => patches.patchCache(code, buildOpts)],
|
|
151
|
-
["composableCache", (code) => patches.patchComposableCache(code, buildOpts), { isOptional: true }],
|
|
152
|
-
[
|
|
153
|
-
"'require(this.middlewareManifestPath)'",
|
|
154
|
-
(code) => patches.inlineMiddlewareManifestRequire(code, buildOpts),
|
|
155
|
-
{ isOptional: true },
|
|
156
|
-
],
|
|
157
|
-
[
|
|
158
|
-
"`require.resolve` call",
|
|
159
|
-
// workers do not support dynamic require nor require.resolve
|
|
160
|
-
(code) => code.replace('require.resolve("./cache.cjs")', '"unused"'),
|
|
161
|
-
],
|
|
162
|
-
[
|
|
163
|
-
"`require.resolve composable cache` call",
|
|
164
|
-
// workers do not support dynamic require nor require.resolve
|
|
165
|
-
(code) => code.replace('require.resolve("./composable-cache.cjs")', '"unused"'),
|
|
166
|
-
],
|
|
167
|
-
]);
|
|
145
|
+
const patchedCode = await patchCodeWithValidations(code, [["require", patches.patchRequire]]);
|
|
168
146
|
await writeFile(workerOutputFile, patchedCode);
|
|
169
147
|
}
|
|
170
148
|
/**
|
|
@@ -7,6 +7,7 @@ export function compileDurableObjects(buildOpts) {
|
|
|
7
7
|
const entryPoints = [
|
|
8
8
|
_require.resolve("@opennextjs/cloudflare/durable-objects/queue"),
|
|
9
9
|
_require.resolve("@opennextjs/cloudflare/durable-objects/sharded-tag-cache"),
|
|
10
|
+
_require.resolve("@opennextjs/cloudflare/durable-objects/bucket-cache-purge"),
|
|
10
11
|
];
|
|
11
12
|
const { outputDir } = buildOpts;
|
|
12
13
|
const baseManifestPath = path.join(outputDir, "server-functions/default", getPackagePath(buildOpts), ".next");
|
|
@@ -10,9 +10,7 @@ import { copyMiddlewareResources, generateEdgeBundle } from "@opennextjs/aws/bui
|
|
|
10
10
|
import * as buildHelper from "@opennextjs/aws/build/helper.js";
|
|
11
11
|
import { installDependencies } from "@opennextjs/aws/build/installDeps.js";
|
|
12
12
|
import { applyCodePatches } from "@opennextjs/aws/build/patch/codePatcher.js";
|
|
13
|
-
import
|
|
14
|
-
// TODO: import from patches/index.js when https://github.com/opennextjs/opennextjs-aws/pull/827 is released
|
|
15
|
-
import { patchBackgroundRevalidation } from "@opennextjs/aws/build/patch/patches/patchBackgroundRevalidation.js";
|
|
13
|
+
import * as awsPatches from "@opennextjs/aws/build/patch/patches/index.js";
|
|
16
14
|
import logger from "@opennextjs/aws/logger.js";
|
|
17
15
|
import { minifyAll } from "@opennextjs/aws/minimize-js.js";
|
|
18
16
|
import { openNextEdgePlugins } from "@opennextjs/aws/plugins/edge.js";
|
|
@@ -133,13 +131,14 @@ async function generateBundle(name, options, fnOptions, codeCustomization) {
|
|
|
133
131
|
}
|
|
134
132
|
const additionalCodePatches = codeCustomization?.additionalCodePatches ?? [];
|
|
135
133
|
await applyCodePatches(options, tracedFiles, manifests, [
|
|
136
|
-
patchFetchCacheSetMissingWaitUntil,
|
|
137
|
-
patchFetchCacheForISR,
|
|
138
|
-
patchUnstableCacheForISR,
|
|
139
|
-
patchUseCacheForISR,
|
|
140
|
-
patchNextServer,
|
|
141
|
-
patchEnvVars,
|
|
142
|
-
patchBackgroundRevalidation,
|
|
134
|
+
awsPatches.patchFetchCacheSetMissingWaitUntil,
|
|
135
|
+
awsPatches.patchFetchCacheForISR,
|
|
136
|
+
awsPatches.patchUnstableCacheForISR,
|
|
137
|
+
awsPatches.patchUseCacheForISR,
|
|
138
|
+
awsPatches.patchNextServer,
|
|
139
|
+
awsPatches.patchEnvVars,
|
|
140
|
+
awsPatches.patchBackgroundRevalidation,
|
|
141
|
+
awsPatches.patchDropBabel,
|
|
143
142
|
// Cloudflare specific patches
|
|
144
143
|
patchResRevalidate,
|
|
145
144
|
patchUseCacheIO,
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Inline `loadManifest`
|
|
2
|
+
* Inline `loadManifest` and `evalManifest` from `load-manifest.js`
|
|
3
|
+
*
|
|
4
|
+
* They rely on `readFileSync` that is not supported by workerd.
|
|
3
5
|
*/
|
|
4
6
|
import { type BuildOptions } from "@opennextjs/aws/build/helper.js";
|
|
5
7
|
import type { ContentUpdater, Plugin } from "@opennextjs/aws/plugins/content-updater.js";
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Inline `loadManifest`
|
|
2
|
+
* Inline `loadManifest` and `evalManifest` from `load-manifest.js`
|
|
3
|
+
*
|
|
4
|
+
* They rely on `readFileSync` that is not supported by workerd.
|
|
3
5
|
*/
|
|
4
6
|
import { readFile } from "node:fs/promises";
|
|
5
7
|
import { join, posix, relative, sep } from "node:path";
|
|
@@ -16,21 +18,24 @@ export function inlineLoadManifest(updater, buildOpts) {
|
|
|
16
18
|
escape: false,
|
|
17
19
|
}),
|
|
18
20
|
contentFilter: /function loadManifest\(/,
|
|
19
|
-
callback: async ({ contents }) =>
|
|
21
|
+
callback: async ({ contents }) => {
|
|
22
|
+
contents = await patchCode(contents, await getLoadManifestRule(buildOpts));
|
|
23
|
+
contents = await patchCode(contents, await getEvalManifestRule(buildOpts));
|
|
24
|
+
return contents;
|
|
25
|
+
},
|
|
20
26
|
},
|
|
21
27
|
},
|
|
22
28
|
]);
|
|
23
29
|
}
|
|
24
|
-
async function
|
|
30
|
+
async function getLoadManifestRule(buildOpts) {
|
|
25
31
|
const { outputDir } = buildOpts;
|
|
26
32
|
const baseDir = join(outputDir, "server-functions/default", getPackagePath(buildOpts));
|
|
27
33
|
const dotNextDir = join(baseDir, ".next");
|
|
28
34
|
const manifests = await glob(join(dotNextDir, "**/*-manifest.json"), { windowsPathsNoEscape: true });
|
|
29
35
|
const returnManifests = (await Promise.all(manifests.map(async (manifest) => `
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
`))).join("\n");
|
|
36
|
+
if ($PATH.endsWith("${normalizePath("/" + relative(dotNextDir, manifest))}")) {
|
|
37
|
+
return ${await readFile(manifest, "utf-8")};
|
|
38
|
+
}`))).join("\n");
|
|
34
39
|
return {
|
|
35
40
|
rule: {
|
|
36
41
|
pattern: `
|
|
@@ -46,3 +51,40 @@ function loadManifest($PATH, $$$ARGS) {
|
|
|
46
51
|
}`,
|
|
47
52
|
};
|
|
48
53
|
}
|
|
54
|
+
async function getEvalManifestRule(buildOpts) {
|
|
55
|
+
const { outputDir } = buildOpts;
|
|
56
|
+
const baseDir = join(outputDir, "server-functions/default", getPackagePath(buildOpts), ".next");
|
|
57
|
+
const appDir = join(baseDir, "server/app");
|
|
58
|
+
const manifests = await glob(join(baseDir, "**/*_client-reference-manifest.js"), {
|
|
59
|
+
windowsPathsNoEscape: true,
|
|
60
|
+
});
|
|
61
|
+
const returnManifests = manifests
|
|
62
|
+
.map((manifest) => {
|
|
63
|
+
const endsWith = normalizePath(relative(baseDir, manifest));
|
|
64
|
+
const key = normalizePath("/" + relative(appDir, manifest)).replace("_client-reference-manifest.js", "");
|
|
65
|
+
return `
|
|
66
|
+
if ($PATH.endsWith("${endsWith}")) {
|
|
67
|
+
require(${JSON.stringify(manifest)});
|
|
68
|
+
return {
|
|
69
|
+
__RSC_MANIFEST: {
|
|
70
|
+
"${key}": globalThis.__RSC_MANIFEST["${key}"],
|
|
71
|
+
},
|
|
72
|
+
};
|
|
73
|
+
}`;
|
|
74
|
+
})
|
|
75
|
+
.join("\n");
|
|
76
|
+
return {
|
|
77
|
+
rule: {
|
|
78
|
+
pattern: `
|
|
79
|
+
function evalManifest($PATH, $$$ARGS) {
|
|
80
|
+
$$$_
|
|
81
|
+
}`,
|
|
82
|
+
},
|
|
83
|
+
fix: `
|
|
84
|
+
function evalManifest($PATH, $$$ARGS) {
|
|
85
|
+
$PATH = $PATH.replaceAll(${JSON.stringify(sep)}, ${JSON.stringify(posix.sep)});
|
|
86
|
+
${returnManifests}
|
|
87
|
+
throw new Error(\`Unexpected evalManifest(\${$PATH}) call!\`);
|
|
88
|
+
}`,
|
|
89
|
+
};
|
|
90
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Misc patches for `next-server.js`
|
|
3
|
+
*
|
|
4
|
+
* Note: we will probably need to revisit the patches when the Next adapter API lands
|
|
5
|
+
*
|
|
6
|
+
* - Inline `getBuildId` as it relies on `readFileSync` that is not supported by workerd
|
|
7
|
+
* - Inline the middleware manifest
|
|
8
|
+
* - Override the cache and composable cache handlers
|
|
9
|
+
*/
|
|
10
|
+
import { type BuildOptions } from "@opennextjs/aws/build/helper.js";
|
|
11
|
+
import type { ContentUpdater, Plugin } from "@opennextjs/aws/plugins/content-updater.js";
|
|
12
|
+
export declare function patchNextServer(updater: ContentUpdater, buildOpts: BuildOptions): Plugin;
|
|
13
|
+
export declare const buildIdRule = "\nrule:\n pattern:\n selector: method_definition\n context: \"class { getBuildId($$$PARAMS) { $$$_ } }\"\nfix: |-\n getBuildId($$$PARAMS) {\n return process.env.NEXT_BUILD_ID;\n }\n";
|
|
14
|
+
export declare function createMiddlewareManifestRule(manifest: unknown): string;
|
|
15
|
+
/**
|
|
16
|
+
* The cache handler used by Next.js is normally defined in the config file as a path. At runtime,
|
|
17
|
+
* Next.js would then do a dynamic require on a transformed version of the path to retrieve the
|
|
18
|
+
* cache handler and create a new instance of it.
|
|
19
|
+
*
|
|
20
|
+
* This is problematic in workerd due to the dynamic import of the file that is not known from
|
|
21
|
+
* build-time. Therefore, we have to manually override the default way that the cache handler is
|
|
22
|
+
* instantiated with a dynamic require that uses a string literal for the path.
|
|
23
|
+
*/
|
|
24
|
+
export declare function createCacheHandlerRule(handlerPath: string): string;
|
|
25
|
+
export declare function createComposableCacheHandlersRule(handlerPath: string): string;
|