@opennextjs/cloudflare 0.2.0 → 0.3.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/README.md +48 -38
- package/dist/api/{get-cloudflare-context.d.mts → get-cloudflare-context.d.ts} +4 -4
- package/dist/api/get-cloudflare-context.js +39 -0
- package/dist/api/index.d.ts +1 -0
- package/dist/api/index.js +1 -0
- package/dist/api/kvCache.d.ts +27 -0
- package/dist/api/kvCache.js +121 -0
- package/dist/cli/args.d.ts +5 -0
- package/dist/cli/args.js +48 -0
- package/dist/cli/build/bundle-server.d.ts +6 -0
- package/dist/cli/build/bundle-server.js +188 -0
- package/dist/cli/build/index.d.ts +9 -0
- package/dist/cli/build/index.js +123 -0
- package/dist/cli/build/open-next/compile-env-files.d.ts +5 -0
- package/dist/cli/build/open-next/compile-env-files.js +9 -0
- package/dist/cli/build/open-next/copyCacheAssets.d.ts +2 -0
- package/dist/cli/build/open-next/copyCacheAssets.js +10 -0
- package/dist/cli/build/open-next/createServerBundle.d.ts +2 -0
- package/dist/cli/build/open-next/createServerBundle.js +216 -0
- package/dist/cli/build/patches/index.d.ts +2 -0
- package/dist/cli/build/patches/index.js +2 -0
- package/dist/cli/build/patches/investigated/copy-package-cli-files.d.ts +6 -0
- package/dist/cli/build/patches/investigated/copy-package-cli-files.js +12 -0
- package/dist/cli/build/patches/investigated/index.d.ts +4 -0
- package/dist/cli/build/patches/investigated/index.js +4 -0
- package/dist/cli/build/patches/investigated/patch-cache.d.ts +13 -0
- package/dist/cli/build/patches/investigated/patch-cache.js +22 -0
- package/dist/cli/build/patches/investigated/patch-require.d.ts +7 -0
- package/dist/cli/build/patches/investigated/patch-require.js +9 -0
- package/dist/cli/build/patches/investigated/update-webpack-chunks-file/get-chunk-installation-identifiers.d.ts +13 -0
- package/dist/cli/build/patches/investigated/update-webpack-chunks-file/get-chunk-installation-identifiers.js +82 -0
- package/dist/cli/build/patches/investigated/update-webpack-chunks-file/get-chunk-installation-identifiers.test.d.ts +1 -0
- package/dist/cli/build/patches/investigated/update-webpack-chunks-file/get-chunk-installation-identifiers.test.js +20 -0
- package/dist/cli/build/patches/investigated/update-webpack-chunks-file/get-file-content-with-updated-webpack-f-require-code.d.ts +19 -0
- package/dist/cli/build/patches/investigated/update-webpack-chunks-file/get-file-content-with-updated-webpack-f-require-code.js +76 -0
- package/dist/cli/build/patches/investigated/update-webpack-chunks-file/get-file-content-with-updated-webpack-f-require-code.test.d.ts +1 -0
- package/dist/cli/build/patches/investigated/update-webpack-chunks-file/get-file-content-with-updated-webpack-f-require-code.test.js +23 -0
- package/dist/cli/build/patches/investigated/update-webpack-chunks-file/get-updated-webpack-chunks-file-content.d.ts +14 -0
- package/dist/cli/build/patches/investigated/update-webpack-chunks-file/get-updated-webpack-chunks-file-content.js +22 -0
- package/dist/cli/build/patches/investigated/update-webpack-chunks-file/get-updated-webpack-chunks-file-content.test.d.ts +1 -0
- package/dist/cli/build/patches/investigated/update-webpack-chunks-file/get-updated-webpack-chunks-file-content.test.js +15 -0
- package/dist/cli/build/patches/investigated/update-webpack-chunks-file/index.d.ts +8 -0
- package/dist/cli/build/patches/investigated/update-webpack-chunks-file/index.js +22 -0
- package/dist/cli/build/patches/to-investigate/index.d.ts +8 -0
- package/dist/cli/build/patches/to-investigate/index.js +8 -0
- package/dist/cli/build/patches/to-investigate/inline-eval-manifest.d.ts +9 -0
- package/dist/cli/build/patches/to-investigate/inline-eval-manifest.js +32 -0
- package/dist/cli/build/patches/to-investigate/inline-middleware-manifest-require.d.ts +6 -0
- package/dist/cli/build/patches/to-investigate/inline-middleware-manifest-require.js +13 -0
- package/dist/cli/build/patches/to-investigate/inline-next-require.d.ts +6 -0
- package/dist/cli/build/patches/to-investigate/inline-next-require.js +36 -0
- package/dist/cli/build/patches/to-investigate/patch-exception-bubbling.d.ts +7 -0
- package/dist/cli/build/patches/to-investigate/patch-exception-bubbling.js +9 -0
- package/dist/cli/build/patches/to-investigate/patch-find-dir.d.ts +8 -0
- package/dist/cli/build/patches/to-investigate/patch-find-dir.js +21 -0
- package/dist/cli/build/patches/to-investigate/patch-load-instrumentation-module.d.ts +14 -0
- package/dist/cli/build/patches/to-investigate/patch-load-instrumentation-module.js +34 -0
- package/dist/cli/build/patches/to-investigate/patch-read-file.d.ts +3 -0
- package/dist/cli/build/patches/to-investigate/patch-read-file.js +29 -0
- package/dist/cli/build/patches/to-investigate/wrangler-deps.d.ts +2 -0
- package/dist/cli/build/patches/to-investigate/wrangler-deps.js +54 -0
- package/dist/cli/build/utils/extract-project-env-vars.d.ts +18 -0
- package/dist/cli/build/utils/extract-project-env-vars.js +32 -0
- package/dist/cli/build/utils/extract-project-env-vars.spec.d.ts +1 -0
- package/dist/cli/build/utils/extract-project-env-vars.spec.js +57 -0
- package/dist/cli/build/utils/index.d.ts +3 -0
- package/dist/cli/build/utils/index.js +3 -0
- package/dist/cli/build/utils/normalize-path.d.ts +1 -0
- package/dist/cli/build/utils/normalize-path.js +4 -0
- package/dist/cli/build/utils/read-paths-recursively.d.ts +7 -0
- package/dist/cli/build/utils/read-paths-recursively.js +20 -0
- package/dist/cli/build/utils/ts-parse-file.d.ts +8 -0
- package/dist/cli/build/utils/ts-parse-file.js +12 -0
- package/dist/cli/config.d.ts +41 -0
- package/dist/cli/config.js +92 -0
- package/dist/cli/index.d.ts +2 -0
- package/dist/cli/index.js +12 -0
- package/dist/cli/templates/shims/empty.d.ts +2 -0
- package/dist/cli/templates/shims/env.d.ts +1 -0
- package/dist/cli/templates/shims/env.js +1 -0
- package/dist/cli/templates/shims/node-fs.d.ts +17 -0
- package/dist/cli/templates/shims/node-fs.js +51 -0
- package/dist/cli/templates/shims/throw.d.ts +0 -0
- package/dist/cli/templates/shims/{throw.ts → throw.js} +1 -0
- package/dist/cli/templates/worker.d.ts +5 -0
- package/dist/cli/templates/worker.js +67 -0
- package/package.json +29 -12
- package/dist/api/chunk-VTBEIZPQ.mjs +0 -32
- package/dist/api/get-cloudflare-context.mjs +0 -6
- package/dist/api/index.d.mts +0 -1
- package/dist/api/index.mjs +0 -6
- package/dist/cli/constants/incremental-cache.ts +0 -8
- package/dist/cli/index.mjs +0 -7424
- package/dist/cli/templates/cache-handler/index.ts +0 -1
- package/dist/cli/templates/cache-handler/open-next-cache-handler.ts +0 -148
- package/dist/cli/templates/cache-handler/utils.ts +0 -41
- package/dist/cli/templates/shims/env.ts +0 -1
- package/dist/cli/templates/shims/node-fs.ts +0 -69
- package/dist/cli/templates/worker.ts +0 -156
- /package/dist/cli/templates/shims/{empty.ts → empty.js} +0 -0
package/README.md
CHANGED
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
Deploy Next.js apps to Cloudflare!
|
|
4
4
|
|
|
5
|
-
[OpenNext for Cloudflare](https://opennext.js.org/cloudflare) is Cloudflare specific adapter that enables deployment of Next.js applications to Cloudflare.
|
|
5
|
+
[OpenNext for Cloudflare](https://opennext.js.org/cloudflare) is a Cloudflare specific adapter that enables deployment of Next.js applications to Cloudflare.
|
|
6
6
|
|
|
7
|
-
##
|
|
7
|
+
## Get started
|
|
8
8
|
|
|
9
9
|
You can use [`create-next-app`](https://nextjs.org/docs/pages/api-reference/cli/create-next-app) to start a new application or take an existing Next.js application and deploy it to Cloudflare using the following few steps:
|
|
10
10
|
|
|
@@ -16,9 +16,9 @@ You can use [`create-next-app`](https://nextjs.org/docs/pages/api-reference/cli/
|
|
|
16
16
|
npm add -D wrangler@latest @opennextjs/cloudflare
|
|
17
17
|
# or
|
|
18
18
|
pnpm add -D wrangler@latest @opennextjs/cloudflare
|
|
19
|
-
#
|
|
19
|
+
# or
|
|
20
20
|
yarn add -D wrangler@latest @opennextjs/cloudflare
|
|
21
|
-
#
|
|
21
|
+
# or
|
|
22
22
|
bun add -D wrangler@latest @opennextjs/cloudflare
|
|
23
23
|
```
|
|
24
24
|
|
|
@@ -27,39 +27,49 @@ You can use [`create-next-app`](https://nextjs.org/docs/pages/api-reference/cli/
|
|
|
27
27
|
```toml
|
|
28
28
|
#:schema node_modules/wrangler/config-schema.json
|
|
29
29
|
name = "<your-app-name>"
|
|
30
|
-
main = ".
|
|
30
|
+
main = ".open-next/worker.js"
|
|
31
31
|
|
|
32
32
|
compatibility_date = "2024-09-23"
|
|
33
33
|
compatibility_flags = ["nodejs_compat"]
|
|
34
34
|
|
|
35
35
|
# Use the new Workers + Assets to host the static frontend files
|
|
36
|
-
assets = { directory = ".
|
|
37
|
-
```
|
|
38
|
-
|
|
39
|
-
You can enable Incremental Static Regeneration ([ISR](https://nextjs.org/docs/app/building-your-application/data-fetching/incremental-static-regeneration)) by adding a KV binding named `NEXT_CACHE_WORKERS_KV` to your `wrangler.toml`:
|
|
40
|
-
|
|
41
|
-
- Create the binding
|
|
42
|
-
|
|
43
|
-
```bash
|
|
44
|
-
npx wrangler kv namespace create NEXT_CACHE_WORKERS_KV
|
|
45
|
-
# or
|
|
46
|
-
pnpm wrangler kv namespace create NEXT_CACHE_WORKERS_KV
|
|
47
|
-
# or
|
|
48
|
-
yarn wrangler kv namespace create NEXT_CACHE_WORKERS_KV
|
|
49
|
-
# or
|
|
50
|
-
bun wrangler kv namespace create NEXT_CACHE_WORKERS_KV
|
|
51
|
-
```
|
|
52
|
-
|
|
53
|
-
- Paste the snippet to your `wrangler.toml`:
|
|
54
|
-
|
|
55
|
-
```bash
|
|
56
|
-
[[kv_namespaces]]
|
|
57
|
-
binding = "NEXT_CACHE_WORKERS_KV"
|
|
58
|
-
id = "..."
|
|
36
|
+
assets = { directory = ".open-next/assets", binding = "ASSETS" }
|
|
59
37
|
```
|
|
60
38
|
|
|
61
|
-
|
|
62
|
-
|
|
39
|
+
- add a `open-next.config.ts` at the root of your project:
|
|
40
|
+
|
|
41
|
+
```ts
|
|
42
|
+
import type { OpenNextConfig } from "open-next/types/open-next";
|
|
43
|
+
|
|
44
|
+
const config: OpenNextConfig = {
|
|
45
|
+
default: {
|
|
46
|
+
override: {
|
|
47
|
+
wrapper: "cloudflare-node",
|
|
48
|
+
converter: "edge",
|
|
49
|
+
// Unused implementation
|
|
50
|
+
incrementalCache: "dummy",
|
|
51
|
+
tagCache: "dummy",
|
|
52
|
+
queue: "dummy",
|
|
53
|
+
},
|
|
54
|
+
},
|
|
55
|
+
|
|
56
|
+
middleware: {
|
|
57
|
+
external: true,
|
|
58
|
+
override: {
|
|
59
|
+
wrapper: "cloudflare-edge",
|
|
60
|
+
converter: "edge",
|
|
61
|
+
proxyExternalRequest: "fetch",
|
|
62
|
+
},
|
|
63
|
+
},
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
export default config;
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
## Known issues
|
|
70
|
+
|
|
71
|
+
- `▲ [WARNING] Suspicious assignment to defined constant "process.env.NODE_ENV" [assign-to-define]` can safely be ignored
|
|
72
|
+
- Maybe more, still experimental...
|
|
63
73
|
|
|
64
74
|
## Local development
|
|
65
75
|
|
|
@@ -72,13 +82,13 @@ Run the following commands to preview the production build of your application l
|
|
|
72
82
|
- build the app and adapt it for Cloudflare
|
|
73
83
|
|
|
74
84
|
```bash
|
|
75
|
-
npx cloudflare
|
|
85
|
+
npx opennextjs-cloudflare
|
|
76
86
|
# or
|
|
77
|
-
pnpm cloudflare
|
|
87
|
+
pnpm opennextjs-cloudflare
|
|
78
88
|
# or
|
|
79
|
-
yarn cloudflare
|
|
89
|
+
yarn opennextjs-cloudflare
|
|
80
90
|
# or
|
|
81
|
-
bun cloudflare
|
|
91
|
+
bun opennextjs-cloudflare
|
|
82
92
|
```
|
|
83
93
|
|
|
84
94
|
- Preview the app in Wrangler
|
|
@@ -100,11 +110,11 @@ Deploy your application to production with the following:
|
|
|
100
110
|
- build the app and adapt it for Cloudflare
|
|
101
111
|
|
|
102
112
|
```bash
|
|
103
|
-
npx cloudflare && npx wrangler deploy
|
|
113
|
+
npx opennextjs-cloudflare && npx wrangler deploy
|
|
104
114
|
# or
|
|
105
|
-
pnpm cloudflare && pnpm wrangler deploy
|
|
115
|
+
pnpm opennextjs-cloudflare && pnpm wrangler deploy
|
|
106
116
|
# or
|
|
107
|
-
yarn cloudflare && yarn wrangler deploy
|
|
117
|
+
yarn opennextjs-cloudflare && yarn wrangler deploy
|
|
108
118
|
# or
|
|
109
|
-
bun cloudflare && bun wrangler deploy
|
|
119
|
+
bun opennextjs-cloudflare && bun wrangler deploy
|
|
110
120
|
```
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
declare global {
|
|
2
2
|
interface CloudflareEnv {
|
|
3
|
+
NEXT_CACHE_WORKERS_KV?: KVNamespace;
|
|
4
|
+
ASSETS?: Fetcher;
|
|
3
5
|
}
|
|
4
6
|
}
|
|
5
|
-
type CloudflareContext<CfProperties extends Record<string, unknown> = IncomingRequestCfProperties, Context = ExecutionContext> = {
|
|
7
|
+
export type CloudflareContext<CfProperties extends Record<string, unknown> = IncomingRequestCfProperties, Context = ExecutionContext> = {
|
|
6
8
|
/**
|
|
7
9
|
* the worker's [bindings](https://developers.cloudflare.com/workers/runtime-apis/bindings/)
|
|
8
10
|
*/
|
|
@@ -21,6 +23,4 @@ type CloudflareContext<CfProperties extends Record<string, unknown> = IncomingRe
|
|
|
21
23
|
*
|
|
22
24
|
* @returns the cloudflare context
|
|
23
25
|
*/
|
|
24
|
-
declare function getCloudflareContext<CfProperties extends Record<string, unknown> = IncomingRequestCfProperties, Context = ExecutionContext>(): Promise<CloudflareContext<CfProperties, Context>>;
|
|
25
|
-
|
|
26
|
-
export { type CloudflareContext, getCloudflareContext };
|
|
26
|
+
export declare function getCloudflareContext<CfProperties extends Record<string, unknown> = IncomingRequestCfProperties, Context = ExecutionContext>(): Promise<CloudflareContext<CfProperties, Context>>;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
// Note: this symbol needs to be kept in sync with the one used in `src/cli/templates/worker.ts`
|
|
2
|
+
const cloudflareContextSymbol = Symbol.for("__cloudflare-context__");
|
|
3
|
+
/**
|
|
4
|
+
* Utility to get the current Cloudflare context
|
|
5
|
+
*
|
|
6
|
+
* @returns the cloudflare context
|
|
7
|
+
*/
|
|
8
|
+
export async function getCloudflareContext() {
|
|
9
|
+
const global = globalThis;
|
|
10
|
+
const cloudflareContext = global[cloudflareContextSymbol];
|
|
11
|
+
if (!cloudflareContext) {
|
|
12
|
+
// the cloudflare context is initialized by the worker and is always present in production/preview,
|
|
13
|
+
// so, it not being present means that the application is running under `next dev`
|
|
14
|
+
return getCloudflareContextInNextDev();
|
|
15
|
+
}
|
|
16
|
+
return cloudflareContext;
|
|
17
|
+
}
|
|
18
|
+
const cloudflareContextInNextDevSymbol = Symbol.for("__next-dev/cloudflare-context__");
|
|
19
|
+
/**
|
|
20
|
+
* Gets a local proxy version of the cloudflare context (created using `getPlatformProxy`) when
|
|
21
|
+
* running in the standard next dev server (via `next dev`)
|
|
22
|
+
*
|
|
23
|
+
* @returns the local proxy version of the cloudflare context
|
|
24
|
+
*/
|
|
25
|
+
async function getCloudflareContextInNextDev() {
|
|
26
|
+
const global = globalThis;
|
|
27
|
+
if (!global[cloudflareContextInNextDevSymbol]) {
|
|
28
|
+
// Note: we never want wrangler to be bundled in the Next.js app, that's why the import below looks like it does
|
|
29
|
+
const { getPlatformProxy } = await import(
|
|
30
|
+
/* webpackIgnore: true */ `${"__wrangler".replaceAll("_", "")}`);
|
|
31
|
+
const { env, cf, ctx } = await getPlatformProxy();
|
|
32
|
+
global[cloudflareContextInNextDevSymbol] = {
|
|
33
|
+
env,
|
|
34
|
+
cf: cf,
|
|
35
|
+
ctx: ctx,
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
return global[cloudflareContextInNextDevSymbol];
|
|
39
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./get-cloudflare-context.js";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./get-cloudflare-context.js";
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { KVNamespace } from "@cloudflare/workers-types";
|
|
2
|
+
import type { CacheValue, IncrementalCache, WithLastModified } from "@opennextjs/aws/types/overrides";
|
|
3
|
+
export declare const CACHE_ASSET_DIR = "cnd-cgi/_next_cache";
|
|
4
|
+
export declare const STATUS_DELETED = 1;
|
|
5
|
+
/**
|
|
6
|
+
* Open Next cache based on cloudflare KV and Assets.
|
|
7
|
+
*
|
|
8
|
+
* Note: The class is instantiated outside of the request context.
|
|
9
|
+
* The cloudflare context and process.env are not initialzed yet
|
|
10
|
+
* when the constructor is called.
|
|
11
|
+
*/
|
|
12
|
+
declare class Cache implements IncrementalCache {
|
|
13
|
+
readonly name = "cloudflare-kv";
|
|
14
|
+
protected initialized: boolean;
|
|
15
|
+
protected kv: KVNamespace | undefined;
|
|
16
|
+
protected assets: Fetcher | undefined;
|
|
17
|
+
get<IsFetch extends boolean = false>(key: string, isFetch?: IsFetch): Promise<WithLastModified<CacheValue<IsFetch>>>;
|
|
18
|
+
set<IsFetch extends boolean = false>(key: string, value: CacheValue<IsFetch>, isFetch?: IsFetch): Promise<void>;
|
|
19
|
+
delete(key: string): Promise<void>;
|
|
20
|
+
protected getKVKey(key: string, isFetch?: boolean): string;
|
|
21
|
+
protected getAssetUrl(key: string, isFetch?: boolean): string;
|
|
22
|
+
protected debug(...args: unknown[]): void;
|
|
23
|
+
protected getBuildId(): string;
|
|
24
|
+
protected init(): Promise<void>;
|
|
25
|
+
}
|
|
26
|
+
declare const _default: Cache;
|
|
27
|
+
export default _default;
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import { IgnorableError, RecoverableError } from "@opennextjs/aws/utils/error.js";
|
|
2
|
+
import { getCloudflareContext } from "./get-cloudflare-context.js";
|
|
3
|
+
export const CACHE_ASSET_DIR = "cnd-cgi/_next_cache";
|
|
4
|
+
export const STATUS_DELETED = 1;
|
|
5
|
+
/**
|
|
6
|
+
* Open Next cache based on cloudflare KV and Assets.
|
|
7
|
+
*
|
|
8
|
+
* Note: The class is instantiated outside of the request context.
|
|
9
|
+
* The cloudflare context and process.env are not initialzed yet
|
|
10
|
+
* when the constructor is called.
|
|
11
|
+
*/
|
|
12
|
+
class Cache {
|
|
13
|
+
name = "cloudflare-kv";
|
|
14
|
+
initialized = false;
|
|
15
|
+
kv;
|
|
16
|
+
assets;
|
|
17
|
+
async get(key, isFetch) {
|
|
18
|
+
if (!this.initialized) {
|
|
19
|
+
await this.init();
|
|
20
|
+
}
|
|
21
|
+
if (!(this.kv || this.assets)) {
|
|
22
|
+
throw new IgnorableError(`No KVNamespace nor Fetcher`);
|
|
23
|
+
}
|
|
24
|
+
this.debug(`Get ${key}`);
|
|
25
|
+
try {
|
|
26
|
+
let entry = null;
|
|
27
|
+
if (this.kv) {
|
|
28
|
+
this.debug(`- From KV`);
|
|
29
|
+
const kvKey = this.getKVKey(key, isFetch);
|
|
30
|
+
entry = await this.kv.get(kvKey, "json");
|
|
31
|
+
if (entry?.status === STATUS_DELETED) {
|
|
32
|
+
return {};
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
if (!entry && this.assets) {
|
|
36
|
+
this.debug(`- From Assets`);
|
|
37
|
+
const url = this.getAssetUrl(key, isFetch);
|
|
38
|
+
const response = await this.assets.fetch(url);
|
|
39
|
+
if (response.ok) {
|
|
40
|
+
// TODO: consider populating KV with the asset value if faster.
|
|
41
|
+
// This could be optional as KV writes are $$.
|
|
42
|
+
// See https://github.com/opennextjs/opennextjs-cloudflare/pull/194#discussion_r1893166026
|
|
43
|
+
entry = {
|
|
44
|
+
value: await response.json(),
|
|
45
|
+
// __BUILD_TIMESTAMP_MS__ is injected by ESBuild.
|
|
46
|
+
lastModified: globalThis.__BUILD_TIMESTAMP_MS__,
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
this.debug(entry ? `-> hit` : `-> miss`);
|
|
51
|
+
return { value: entry?.value, lastModified: entry?.lastModified };
|
|
52
|
+
}
|
|
53
|
+
catch {
|
|
54
|
+
throw new RecoverableError(`Failed to get cache [${key}]`);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
async set(key, value, isFetch) {
|
|
58
|
+
if (!this.initialized) {
|
|
59
|
+
await this.init();
|
|
60
|
+
}
|
|
61
|
+
if (!this.kv) {
|
|
62
|
+
throw new IgnorableError(`No KVNamespace`);
|
|
63
|
+
}
|
|
64
|
+
this.debug(`Set ${key}`);
|
|
65
|
+
try {
|
|
66
|
+
const kvKey = this.getKVKey(key, isFetch);
|
|
67
|
+
// Note: We can not set a TTL as we might fallback to assets,
|
|
68
|
+
// still removing old data (old BUILD_ID) could help avoiding
|
|
69
|
+
// the cache growing too big.
|
|
70
|
+
await this.kv.put(kvKey, JSON.stringify({
|
|
71
|
+
value,
|
|
72
|
+
// Note: `Date.now()` returns the time of the last IO rather than the actual time.
|
|
73
|
+
// See https://developers.cloudflare.com/workers/reference/security-model/
|
|
74
|
+
lastModified: Date.now(),
|
|
75
|
+
}));
|
|
76
|
+
}
|
|
77
|
+
catch {
|
|
78
|
+
throw new RecoverableError(`Failed to set cache [${key}]`);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
async delete(key) {
|
|
82
|
+
if (!this.initialized) {
|
|
83
|
+
await this.init();
|
|
84
|
+
}
|
|
85
|
+
if (!this.kv) {
|
|
86
|
+
throw new IgnorableError(`No KVNamespace`);
|
|
87
|
+
}
|
|
88
|
+
this.debug(`Delete ${key}`);
|
|
89
|
+
try {
|
|
90
|
+
const kvKey = this.getKVKey(key, /* isFetch= */ false);
|
|
91
|
+
// Do not delete the key as we would then fallback to the assets.
|
|
92
|
+
await this.kv.put(kvKey, JSON.stringify({ status: STATUS_DELETED }));
|
|
93
|
+
}
|
|
94
|
+
catch {
|
|
95
|
+
throw new RecoverableError(`Failed to delete cache [${key}]`);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
getKVKey(key, isFetch) {
|
|
99
|
+
return `${this.getBuildId()}/${key}.${isFetch ? "fetch" : "cache"}`;
|
|
100
|
+
}
|
|
101
|
+
getAssetUrl(key, isFetch) {
|
|
102
|
+
return isFetch
|
|
103
|
+
? `http://assets.local/${CACHE_ASSET_DIR}/__fetch/${this.getBuildId()}/${key}`
|
|
104
|
+
: `http://assets.local/${CACHE_ASSET_DIR}/${this.getBuildId()}/${key}.cache`;
|
|
105
|
+
}
|
|
106
|
+
debug(...args) {
|
|
107
|
+
if (process.env.NEXT_PRIVATE_DEBUG_CACHE) {
|
|
108
|
+
console.log(`[Cache ${this.name}] `, ...args);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
getBuildId() {
|
|
112
|
+
return process.env.NEXT_BUILD_ID ?? "no-build-id";
|
|
113
|
+
}
|
|
114
|
+
async init() {
|
|
115
|
+
const env = (await getCloudflareContext()).env;
|
|
116
|
+
this.kv = env.NEXT_CACHE_WORKERS_KV;
|
|
117
|
+
this.assets = env.ASSETS;
|
|
118
|
+
this.initialized = true;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
export default new Cache();
|
package/dist/cli/args.js
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { mkdirSync, statSync } from "node:fs";
|
|
2
|
+
import { resolve } from "node:path";
|
|
3
|
+
import { parseArgs } from "node:util";
|
|
4
|
+
export function getArgs() {
|
|
5
|
+
const { skipBuild, output, noMinify } = parseArgs({
|
|
6
|
+
options: {
|
|
7
|
+
skipBuild: {
|
|
8
|
+
type: "boolean",
|
|
9
|
+
short: "s",
|
|
10
|
+
default: false,
|
|
11
|
+
},
|
|
12
|
+
output: {
|
|
13
|
+
type: "string",
|
|
14
|
+
short: "o",
|
|
15
|
+
},
|
|
16
|
+
noMinify: {
|
|
17
|
+
type: "boolean",
|
|
18
|
+
default: false,
|
|
19
|
+
},
|
|
20
|
+
},
|
|
21
|
+
allowPositionals: false,
|
|
22
|
+
}).values;
|
|
23
|
+
const outputDir = output ? resolve(output) : undefined;
|
|
24
|
+
if (outputDir) {
|
|
25
|
+
assertDirArg(outputDir, "output", true);
|
|
26
|
+
}
|
|
27
|
+
return {
|
|
28
|
+
outputDir,
|
|
29
|
+
skipNextBuild: skipBuild || ["1", "true", "yes"].includes(String(process.env.SKIP_NEXT_APP_BUILD)),
|
|
30
|
+
minify: !noMinify,
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
function assertDirArg(path, argName, make) {
|
|
34
|
+
let dirStats;
|
|
35
|
+
try {
|
|
36
|
+
dirStats = statSync(path);
|
|
37
|
+
}
|
|
38
|
+
catch {
|
|
39
|
+
if (!make) {
|
|
40
|
+
throw new Error(`Error: the provided${argName ? ` "${argName}"` : ""} input is not a valid path`);
|
|
41
|
+
}
|
|
42
|
+
mkdirSync(path);
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
if (!dirStats.isDirectory()) {
|
|
46
|
+
throw new Error(`Error: the provided${argName ? ` "${argName}"` : ""} input is not a directory`);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import { readFile, writeFile } from "node:fs/promises";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
5
|
+
import { build } from "esbuild";
|
|
6
|
+
import * as patches from "./patches/index.js";
|
|
7
|
+
/** The dist directory of the Cloudflare adapter package */
|
|
8
|
+
const packageDistDir = path.join(path.dirname(fileURLToPath(import.meta.url)), "../..");
|
|
9
|
+
/**
|
|
10
|
+
* Bundle the Open Next server.
|
|
11
|
+
*/
|
|
12
|
+
export async function bundleServer(config, openNextOptions) {
|
|
13
|
+
patches.copyPackageCliFiles(packageDistDir, config, openNextOptions);
|
|
14
|
+
const nextConfigStr = fs
|
|
15
|
+
.readFileSync(path.join(config.paths.output.standaloneApp, "server.js"), "utf8")
|
|
16
|
+
?.match(/const nextConfig = ({.+?})\n/)?.[1] ?? {};
|
|
17
|
+
console.log(`\x1b[35m⚙️ Bundling the OpenNext server...\n\x1b[0m`);
|
|
18
|
+
patches.patchWranglerDeps(config);
|
|
19
|
+
patches.updateWebpackChunksFile(config);
|
|
20
|
+
const { appBuildOutputPath, appPath, outputDir, monorepoRoot } = openNextOptions;
|
|
21
|
+
const outputPath = path.join(outputDir, "server-functions", "default");
|
|
22
|
+
const packagePath = path.relative(monorepoRoot, appBuildOutputPath);
|
|
23
|
+
const openNextServer = path.join(outputPath, packagePath, `index.mjs`);
|
|
24
|
+
const openNextServerBundle = path.join(outputPath, packagePath, `handler.mjs`);
|
|
25
|
+
await build({
|
|
26
|
+
entryPoints: [openNextServer],
|
|
27
|
+
bundle: true,
|
|
28
|
+
outfile: openNextServerBundle,
|
|
29
|
+
format: "esm",
|
|
30
|
+
target: "esnext",
|
|
31
|
+
minify: false,
|
|
32
|
+
plugins: [createFixRequiresESBuildPlugin(config)],
|
|
33
|
+
external: ["./middleware/handler.mjs"],
|
|
34
|
+
alias: {
|
|
35
|
+
// Note: we apply an empty shim to next/dist/compiled/ws because it generates two `eval`s:
|
|
36
|
+
// eval("require")("bufferutil");
|
|
37
|
+
// eval("require")("utf-8-validate");
|
|
38
|
+
"next/dist/compiled/ws": path.join(config.paths.internal.templates, "shims", "empty.js"),
|
|
39
|
+
// Note: we apply an empty shim to next/dist/compiled/edge-runtime since (amongst others) it generated the following `eval`:
|
|
40
|
+
// eval(getModuleCode)(module, module.exports, throwingRequire, params.context, ...Object.values(params.scopedContext));
|
|
41
|
+
// which comes from https://github.com/vercel/edge-runtime/blob/6e96b55f/packages/primitives/src/primitives/load.js#L57-L63
|
|
42
|
+
// QUESTION: Why did I encountered this but mhart didn't?
|
|
43
|
+
"next/dist/compiled/edge-runtime": path.join(config.paths.internal.templates, "shims", "empty.js"),
|
|
44
|
+
// `@next/env` is a library Next.js uses for loading dotenv files, for obvious reasons we need to stub it here
|
|
45
|
+
// source: https://github.com/vercel/next.js/tree/0ac10d79720/packages/next-env
|
|
46
|
+
"@next/env": path.join(config.paths.internal.templates, "shims", "env.js"),
|
|
47
|
+
},
|
|
48
|
+
define: {
|
|
49
|
+
// config file used by Next.js, see: https://github.com/vercel/next.js/blob/68a7128/packages/next/src/build/utils.ts#L2137-L2139
|
|
50
|
+
"process.env.__NEXT_PRIVATE_STANDALONE_CONFIG": JSON.stringify(nextConfigStr),
|
|
51
|
+
// Next.js tried to access __dirname so we need to define it
|
|
52
|
+
__dirname: '""',
|
|
53
|
+
// Note: we need the __non_webpack_require__ variable declared as it is used by next-server:
|
|
54
|
+
// https://github.com/vercel/next.js/blob/be0c3283/packages/next/src/server/next-server.ts#L116-L119
|
|
55
|
+
__non_webpack_require__: "require",
|
|
56
|
+
// Ask mhart if he can explain why the `define`s below are necessary
|
|
57
|
+
"process.env.NEXT_RUNTIME": '"nodejs"',
|
|
58
|
+
"process.env.NODE_ENV": '"production"',
|
|
59
|
+
"process.env.NEXT_MINIMAL": "true",
|
|
60
|
+
},
|
|
61
|
+
// We need to set platform to node so that esbuild doesn't complain about the node imports
|
|
62
|
+
platform: "node",
|
|
63
|
+
banner: {
|
|
64
|
+
js: `
|
|
65
|
+
// __dirname is used by unbundled js files (which don't inherit the __dirname present in the define field)
|
|
66
|
+
// so we also need to set it on the global scope
|
|
67
|
+
// Note: this was hit in the next/dist/compiled/@opentelemetry/api module
|
|
68
|
+
globalThis.__dirname ??= "";
|
|
69
|
+
|
|
70
|
+
// Do not crash on cache not supported
|
|
71
|
+
// https://github.com/cloudflare/workerd/pull/2434
|
|
72
|
+
// compatibility flag "cache_option_enabled" -> does not support "force-cache"
|
|
73
|
+
const curFetch = globalThis.fetch;
|
|
74
|
+
globalThis.fetch = (input, init) => {
|
|
75
|
+
if (init) {
|
|
76
|
+
delete init.cache;
|
|
77
|
+
}
|
|
78
|
+
return curFetch(input, init);
|
|
79
|
+
};
|
|
80
|
+
import __cf_stream from 'node:stream';
|
|
81
|
+
fetch = globalThis.fetch;
|
|
82
|
+
const CustomRequest = class extends globalThis.Request {
|
|
83
|
+
constructor(input, init) {
|
|
84
|
+
if (init) {
|
|
85
|
+
init = {
|
|
86
|
+
...init,
|
|
87
|
+
cache: undefined,
|
|
88
|
+
// https://github.com/cloudflare/workerd/issues/2746
|
|
89
|
+
// https://github.com/cloudflare/workerd/issues/3245
|
|
90
|
+
body: init.body instanceof __cf_stream.Readable ? ReadableStream.from(init.body) : init.body,
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
super(input, init);
|
|
94
|
+
}
|
|
95
|
+
};
|
|
96
|
+
globalThis.Request = CustomRequest;
|
|
97
|
+
Request = globalThis.Request;
|
|
98
|
+
// Makes the edge converter returns either a Response or a Request.
|
|
99
|
+
globalThis.__dangerous_ON_edge_converter_returns_request = true;
|
|
100
|
+
globalThis.__BUILD_TIMESTAMP_MS__ = ${Date.now()};
|
|
101
|
+
`,
|
|
102
|
+
},
|
|
103
|
+
});
|
|
104
|
+
await updateWorkerBundledCode(openNextServerBundle, config, openNextOptions);
|
|
105
|
+
const isMonorepo = monorepoRoot !== appPath;
|
|
106
|
+
if (isMonorepo) {
|
|
107
|
+
const packagePosixPath = packagePath.split(path.sep).join(path.posix.sep);
|
|
108
|
+
fs.writeFileSync(path.join(outputPath, "handler.mjs"), `export * from "./${packagePosixPath}/handler.mjs";`);
|
|
109
|
+
}
|
|
110
|
+
console.log(`\x1b[35mWorker saved in \`${openNextServerBundle}\` 🚀\n\x1b[0m`);
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* This function applies string replacements on the bundled worker code necessary to get it to run in workerd
|
|
114
|
+
*
|
|
115
|
+
* Needless to say all the logic in this function is something we should avoid as much as possible!
|
|
116
|
+
*
|
|
117
|
+
* @param workerOutputFile
|
|
118
|
+
* @param config
|
|
119
|
+
*/
|
|
120
|
+
async function updateWorkerBundledCode(workerOutputFile, config, openNextOptions) {
|
|
121
|
+
const code = await readFile(workerOutputFile, "utf8");
|
|
122
|
+
const patchedCode = await patchCodeWithValidations(code, [
|
|
123
|
+
["require", patches.patchRequire],
|
|
124
|
+
["`buildId` function", (code) => patches.patchBuildId(code, config)],
|
|
125
|
+
["`loadManifest` function", (code) => patches.patchLoadManifest(code, config)],
|
|
126
|
+
["next's require", (code) => patches.inlineNextRequire(code, config)],
|
|
127
|
+
["`findDir` function", (code) => patches.patchFindDir(code, config)],
|
|
128
|
+
["`evalManifest` function", (code) => patches.inlineEvalManifest(code, config)],
|
|
129
|
+
["cacheHandler", (code) => patches.patchCache(code, openNextOptions)],
|
|
130
|
+
[
|
|
131
|
+
"'require(this.middlewareManifestPath)'",
|
|
132
|
+
(code) => patches.inlineMiddlewareManifestRequire(code, config),
|
|
133
|
+
],
|
|
134
|
+
["exception bubbling", patches.patchExceptionBubbling],
|
|
135
|
+
["`loadInstrumentationModule` function", patches.patchLoadInstrumentationModule],
|
|
136
|
+
[
|
|
137
|
+
"`patchAsyncStorage` call",
|
|
138
|
+
(code) => code
|
|
139
|
+
// TODO: implement for cf (possibly in @opennextjs/aws)
|
|
140
|
+
.replace("patchAsyncStorage();", "//patchAsyncStorage();"),
|
|
141
|
+
],
|
|
142
|
+
[
|
|
143
|
+
'`eval("require")` calls',
|
|
144
|
+
(code) => code.replaceAll('eval("require")', "require"),
|
|
145
|
+
{ isOptional: true },
|
|
146
|
+
],
|
|
147
|
+
[
|
|
148
|
+
"`require.resolve` call",
|
|
149
|
+
// workers do not support dynamic require nor require.resolve
|
|
150
|
+
(code) => code.replace('require.resolve("./cache.cjs")', '"unused"'),
|
|
151
|
+
],
|
|
152
|
+
]);
|
|
153
|
+
await writeFile(workerOutputFile, patchedCode);
|
|
154
|
+
}
|
|
155
|
+
function createFixRequiresESBuildPlugin(config) {
|
|
156
|
+
return {
|
|
157
|
+
name: "replaceRelative",
|
|
158
|
+
setup(build) {
|
|
159
|
+
// Note: we (empty) shim require-hook modules as they generate problematic code that uses requires
|
|
160
|
+
build.onResolve({ filter: /^\.\/require-hook$/ }, () => ({
|
|
161
|
+
path: path.join(config.paths.internal.templates, "shims", "empty.js"),
|
|
162
|
+
}));
|
|
163
|
+
},
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* Applies multiple code patches in order to a given piece of code, at each step it validates that the code
|
|
168
|
+
* has actually been patched/changed, if not an error is thrown
|
|
169
|
+
*
|
|
170
|
+
* @param code the code to apply the patches to
|
|
171
|
+
* @param patches array of tuples, containing a string indicating the target of the patching (for logging) and
|
|
172
|
+
* a patching function that takes a string (pre-patch code) and returns a string (post-patch code)
|
|
173
|
+
* @returns the patched code
|
|
174
|
+
*/
|
|
175
|
+
async function patchCodeWithValidations(code, patches) {
|
|
176
|
+
console.log(`Applying code patches:`);
|
|
177
|
+
let patchedCode = code;
|
|
178
|
+
for (const [target, patchFunction, opts] of patches) {
|
|
179
|
+
console.log(` - patching ${target}`);
|
|
180
|
+
const prePatchCode = patchedCode;
|
|
181
|
+
patchedCode = await patchFunction(patchedCode);
|
|
182
|
+
if (!opts?.isOptional && prePatchCode === patchedCode) {
|
|
183
|
+
throw new Error(`Failed to patch ${target}`);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
console.log(`All ${patches.length} patches applied\n`);
|
|
187
|
+
return patchedCode;
|
|
188
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { ProjectOptions } from "../config.js";
|
|
2
|
+
/**
|
|
3
|
+
* Builds the application in a format that can be passed to workerd
|
|
4
|
+
*
|
|
5
|
+
* It saves the output in a `.worker-next` directory
|
|
6
|
+
*
|
|
7
|
+
* @param projectOpts The options for the project
|
|
8
|
+
*/
|
|
9
|
+
export declare function build(projectOpts: ProjectOptions): Promise<void>;
|