@opennextjs/cloudflare 0.3.10 → 0.4.1
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 +1 -64
- package/dist/api/{get-cloudflare-context.d.ts → cloudflare-context.d.ts} +8 -1
- package/dist/api/cloudflare-context.js +114 -0
- package/dist/api/index.d.ts +1 -1
- package/dist/api/index.js +1 -1
- package/dist/api/kvCache.d.ts +0 -5
- package/dist/api/kvCache.js +15 -28
- package/dist/cli/build/build.d.ts +1 -1
- package/dist/cli/build/build.js +2 -12
- package/dist/cli/build/bundle-server.d.ts +4 -5
- package/dist/cli/build/bundle-server.js +44 -37
- package/dist/cli/build/open-next/compile-env-files.d.ts +1 -1
- package/dist/cli/build/open-next/compile-env-files.js +2 -2
- package/dist/cli/build/patches/ast/optional-deps.d.ts +6 -3
- package/dist/cli/build/patches/ast/optional-deps.js +4 -4
- package/dist/cli/build/patches/ast/util.d.ts +15 -4
- package/dist/cli/build/patches/ast/util.js +16 -5
- package/dist/cli/build/patches/ast/vercel-og.d.ts +23 -0
- package/dist/cli/build/patches/ast/vercel-og.js +58 -0
- package/dist/cli/build/patches/ast/vercel-og.spec.d.ts +1 -0
- package/dist/cli/build/patches/ast/vercel-og.spec.js +22 -0
- package/dist/cli/build/patches/investigated/copy-package-cli-files.d.ts +4 -3
- package/dist/cli/build/patches/investigated/copy-package-cli-files.js +8 -5
- package/dist/cli/build/patches/investigated/index.d.ts +1 -0
- package/dist/cli/build/patches/investigated/index.js +1 -0
- package/dist/cli/build/patches/investigated/patch-cache.d.ts +2 -2
- package/dist/cli/build/patches/investigated/patch-cache.js +7 -6
- package/dist/cli/build/patches/investigated/patch-require.d.ts +1 -4
- package/dist/cli/build/patches/investigated/patch-require.js +1 -4
- package/dist/cli/build/patches/investigated/patch-vercel-og-library.d.ts +7 -0
- package/dist/cli/build/patches/investigated/patch-vercel-og-library.js +39 -0
- package/dist/cli/build/patches/investigated/patch-vercel-og-library.spec.d.ts +1 -0
- package/dist/cli/build/patches/investigated/patch-vercel-og-library.spec.js +50 -0
- package/dist/cli/build/patches/investigated/update-webpack-chunks-file/index.d.ts +2 -5
- package/dist/cli/build/patches/investigated/update-webpack-chunks-file/index.js +6 -6
- package/dist/cli/build/patches/plugins/require-page.d.ts +6 -0
- package/dist/cli/build/patches/plugins/require-page.js +70 -0
- package/dist/cli/build/patches/plugins/wrangler-external.d.ts +20 -0
- package/dist/cli/build/patches/plugins/wrangler-external.js +36 -0
- package/dist/cli/build/patches/to-investigate/index.d.ts +0 -1
- package/dist/cli/build/patches/to-investigate/index.js +0 -1
- package/dist/cli/build/patches/to-investigate/inline-eval-manifest.d.ts +2 -2
- package/dist/cli/build/patches/to-investigate/inline-eval-manifest.js +21 -17
- package/dist/cli/build/patches/to-investigate/inline-middleware-manifest-require.d.ts +2 -2
- package/dist/cli/build/patches/to-investigate/inline-middleware-manifest-require.js +4 -2
- package/dist/cli/build/patches/to-investigate/patch-find-dir.d.ts +3 -6
- package/dist/cli/build/patches/to-investigate/patch-find-dir.js +11 -11
- package/dist/cli/build/patches/to-investigate/patch-read-file.d.ts +3 -3
- package/dist/cli/build/patches/to-investigate/patch-read-file.js +15 -16
- package/dist/cli/build/patches/to-investigate/wrangler-deps.d.ts +2 -2
- package/dist/cli/build/patches/to-investigate/wrangler-deps.js +36 -67
- package/dist/cli/build/utils/create-config-files.d.ts +1 -1
- package/dist/cli/project-options.d.ts +7 -0
- package/dist/cli/project-options.js +1 -0
- package/package.json +4 -4
- package/dist/api/get-cloudflare-context.js +0 -42
- package/dist/cli/build/patches/to-investigate/inline-next-require.d.ts +0 -6
- package/dist/cli/build/patches/to-investigate/inline-next-require.js +0 -36
- package/dist/cli/config.d.ts +0 -42
- package/dist/cli/config.js +0 -92
package/README.md
CHANGED
|
@@ -6,70 +6,7 @@ Deploy Next.js apps to Cloudflare!
|
|
|
6
6
|
|
|
7
7
|
## Get started
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
## Configure your app
|
|
12
|
-
|
|
13
|
-
- add the following `devDependencies` to the `package.json`:
|
|
14
|
-
|
|
15
|
-
```bash
|
|
16
|
-
npm add -D wrangler@latest @opennextjs/cloudflare
|
|
17
|
-
# or
|
|
18
|
-
pnpm add -D wrangler@latest @opennextjs/cloudflare
|
|
19
|
-
# or
|
|
20
|
-
yarn add -D wrangler@latest @opennextjs/cloudflare
|
|
21
|
-
# or
|
|
22
|
-
bun add -D wrangler@latest @opennextjs/cloudflare
|
|
23
|
-
```
|
|
24
|
-
|
|
25
|
-
- add a `wrangler.toml` at the root of your project
|
|
26
|
-
|
|
27
|
-
```toml
|
|
28
|
-
#:schema node_modules/wrangler/config-schema.json
|
|
29
|
-
name = "<your-app-name>"
|
|
30
|
-
main = ".open-next/worker.js"
|
|
31
|
-
|
|
32
|
-
compatibility_date = "2024-09-23"
|
|
33
|
-
compatibility_flags = ["nodejs_compat"]
|
|
34
|
-
|
|
35
|
-
# Use the new Workers + Assets to host the static frontend files
|
|
36
|
-
assets = { directory = ".open-next/assets", binding = "ASSETS" }
|
|
37
|
-
```
|
|
38
|
-
|
|
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...
|
|
9
|
+
To get started with the adapter visit the [official get started documentation](https://opennext.js.org/cloudflare/get-started).
|
|
73
10
|
|
|
74
11
|
## Local development
|
|
75
12
|
|
|
@@ -23,4 +23,11 @@ export type CloudflareContext<CfProperties extends Record<string, unknown> = Inc
|
|
|
23
23
|
*
|
|
24
24
|
* @returns the cloudflare context
|
|
25
25
|
*/
|
|
26
|
-
export declare function getCloudflareContext<CfProperties extends Record<string, unknown> = IncomingRequestCfProperties, Context = ExecutionContext>():
|
|
26
|
+
export declare function getCloudflareContext<CfProperties extends Record<string, unknown> = IncomingRequestCfProperties, Context = ExecutionContext>(): CloudflareContext<CfProperties, Context>;
|
|
27
|
+
/**
|
|
28
|
+
* Performs some initial setup to integrate as best as possible the local Next.js dev server (run via `next dev`)
|
|
29
|
+
* with the open-next Cloudflare adapter
|
|
30
|
+
*
|
|
31
|
+
* Note: this function should only be called inside the Next.js config file, and although async it doesn't need to be `await`ed
|
|
32
|
+
*/
|
|
33
|
+
export declare function initOpenNextCloudflareForDev(): Promise<void>;
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Symbol used as an index in the global scope to set and retrieve the Cloudflare context
|
|
3
|
+
*
|
|
4
|
+
* This is used both in production (in the actual built worker) and in development (`next dev`)
|
|
5
|
+
*
|
|
6
|
+
* Note: this symbol needs to be kept in sync with the one used in `src/cli/templates/worker.ts`
|
|
7
|
+
*/
|
|
8
|
+
const cloudflareContextSymbol = Symbol.for("__cloudflare-context__");
|
|
9
|
+
/**
|
|
10
|
+
* Utility to get the current Cloudflare context
|
|
11
|
+
*
|
|
12
|
+
* @returns the cloudflare context
|
|
13
|
+
*/
|
|
14
|
+
export function getCloudflareContext() {
|
|
15
|
+
const global = globalThis;
|
|
16
|
+
const cloudflareContext = global[cloudflareContextSymbol];
|
|
17
|
+
if (!cloudflareContext) {
|
|
18
|
+
// the cloudflare context is initialized by the worker and is always present in production/preview
|
|
19
|
+
// during local development (`next dev`) it might be missing only if the developers hasn't called
|
|
20
|
+
// the `initOpenNextCloudflareForDev` function in their Next.js config file
|
|
21
|
+
const getContextFunctionName = getCloudflareContext.name;
|
|
22
|
+
const initFunctionName = initOpenNextCloudflareForDev.name;
|
|
23
|
+
throw new Error(`\n\n\`${getContextFunctionName}\` has been called during development without having called` +
|
|
24
|
+
` the \`${initFunctionName}\` function inside the Next.js config file.\n\n` +
|
|
25
|
+
`In order to use \`${getContextFunctionName}\` import and call ${initFunctionName} in the Next.js config file.\n\n` +
|
|
26
|
+
"Example: \n ```\n // next.config.mjs\n\n" +
|
|
27
|
+
` import { ${initFunctionName} } from "@opennextjs/cloudflare";\n\n` +
|
|
28
|
+
` ${initFunctionName}();\n\n` +
|
|
29
|
+
" /** @type {import('next').NextConfig} */\n" +
|
|
30
|
+
" const nextConfig = {};\n" +
|
|
31
|
+
" export default nextConfig;\n" +
|
|
32
|
+
" ```\n" +
|
|
33
|
+
"\n(note: currently middlewares in Next.js are always run using the edge runtime)\n\n");
|
|
34
|
+
}
|
|
35
|
+
return cloudflareContext;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Performs some initial setup to integrate as best as possible the local Next.js dev server (run via `next dev`)
|
|
39
|
+
* with the open-next Cloudflare adapter
|
|
40
|
+
*
|
|
41
|
+
* Note: this function should only be called inside the Next.js config file, and although async it doesn't need to be `await`ed
|
|
42
|
+
*/
|
|
43
|
+
export async function initOpenNextCloudflareForDev() {
|
|
44
|
+
const shouldInitializationRun = shouldContextInitializationRun();
|
|
45
|
+
if (!shouldInitializationRun)
|
|
46
|
+
return;
|
|
47
|
+
const context = await getCloudflareContextFromWrangler();
|
|
48
|
+
addCloudflareContextToNodejsGlobal(context);
|
|
49
|
+
await monkeyPatchVmModuleEdgeContext(context);
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Next dev server imports the config file twice (in two different processes, making it hard to track),
|
|
53
|
+
* this causes the initialization to run twice as well, to keep things clean, not allocate extra
|
|
54
|
+
* resources (i.e. instantiate two miniflare instances) and avoid extra potential logs, it would be best
|
|
55
|
+
* to run the initialization only once, this function is used to try to make it so that it does, it returns
|
|
56
|
+
* a flag which indicates if the initialization should run in the current process or not.
|
|
57
|
+
*
|
|
58
|
+
* @returns boolean indicating if the initialization should run
|
|
59
|
+
*/
|
|
60
|
+
function shouldContextInitializationRun() {
|
|
61
|
+
// via debugging we've seen that AsyncLocalStorage is only set in one of the
|
|
62
|
+
// two processes so we're using it as the differentiator between the two
|
|
63
|
+
const AsyncLocalStorage = globalThis["AsyncLocalStorage"];
|
|
64
|
+
return !!AsyncLocalStorage;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Adds the cloudflare context to the global scope in which the Next.js dev node.js process runs in, enabling
|
|
68
|
+
* future calls to `getCloudflareContext` to retrieve and return such context
|
|
69
|
+
*
|
|
70
|
+
* @param cloudflareContext the cloudflare context to add to the node.sj global scope
|
|
71
|
+
*/
|
|
72
|
+
function addCloudflareContextToNodejsGlobal(cloudflareContext) {
|
|
73
|
+
const global = globalThis;
|
|
74
|
+
global[cloudflareContextSymbol] = cloudflareContext;
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Next.js uses the Node.js vm module's `runInContext()` function to evaluate edge functions
|
|
78
|
+
* in a runtime context that tries to simulate as accurately as possible the actual production runtime
|
|
79
|
+
* behavior, see: https://github.com/vercel/next.js/blob/9a1cd3/packages/next/src/server/web/sandbox/context.ts#L525-L527
|
|
80
|
+
*
|
|
81
|
+
* This function monkey-patches the Node.js `vm` module to override the `runInContext()` function so that the
|
|
82
|
+
* cloudflare context is added to the runtime context's global scope before edge functions are evaluated
|
|
83
|
+
*
|
|
84
|
+
* @param cloudflareContext the cloudflare context to patch onto the "edge" runtime context global scope
|
|
85
|
+
*/
|
|
86
|
+
async function monkeyPatchVmModuleEdgeContext(cloudflareContext) {
|
|
87
|
+
const require = (await import(/* webpackIgnore: true */ `${"__module".replaceAll("_", "")}`)).default.createRequire(import.meta.url);
|
|
88
|
+
// eslint-disable-next-line unicorn/prefer-node-protocol -- the `next dev` compiler doesn't accept the node prefix
|
|
89
|
+
const vmModule = require("vm");
|
|
90
|
+
const originalRunInContext = vmModule.runInContext.bind(vmModule);
|
|
91
|
+
vmModule.runInContext = (code, contextifiedObject, options) => {
|
|
92
|
+
const runtimeContext = contextifiedObject;
|
|
93
|
+
runtimeContext[cloudflareContextSymbol] ??= cloudflareContext;
|
|
94
|
+
return originalRunInContext(code, contextifiedObject, options);
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Gets a cloudflare context object from wrangler
|
|
99
|
+
*
|
|
100
|
+
* @returns the cloudflare context ready for use
|
|
101
|
+
*/
|
|
102
|
+
async function getCloudflareContextFromWrangler() {
|
|
103
|
+
// Note: we never want wrangler to be bundled in the Next.js app, that's why the import below looks like it does
|
|
104
|
+
const { getPlatformProxy } = await import(/* webpackIgnore: true */ `${"__wrangler".replaceAll("_", "")}`);
|
|
105
|
+
const { env, cf, ctx } = await getPlatformProxy({
|
|
106
|
+
// This allows the selection of a wrangler environment while running in next dev mode
|
|
107
|
+
environment: process.env.NEXT_DEV_WRANGLER_ENV,
|
|
108
|
+
});
|
|
109
|
+
return {
|
|
110
|
+
env,
|
|
111
|
+
cf: cf,
|
|
112
|
+
ctx: ctx,
|
|
113
|
+
};
|
|
114
|
+
}
|
package/dist/api/index.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export * from "./
|
|
1
|
+
export * from "./cloudflare-context.js";
|
package/dist/api/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export * from "./
|
|
1
|
+
export * from "./cloudflare-context.js";
|
package/dist/api/kvCache.d.ts
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import type { KVNamespace } from "@cloudflare/workers-types";
|
|
2
1
|
import type { CacheValue, IncrementalCache, WithLastModified } from "@opennextjs/aws/types/overrides";
|
|
3
2
|
export declare const CACHE_ASSET_DIR = "cnd-cgi/_next_cache";
|
|
4
3
|
export declare const STATUS_DELETED = 1;
|
|
@@ -11,9 +10,6 @@ export declare const STATUS_DELETED = 1;
|
|
|
11
10
|
*/
|
|
12
11
|
declare class Cache implements IncrementalCache {
|
|
13
12
|
readonly name = "cloudflare-kv";
|
|
14
|
-
protected initialized: boolean;
|
|
15
|
-
protected kv: KVNamespace | undefined;
|
|
16
|
-
protected assets: Fetcher | undefined;
|
|
17
13
|
get<IsFetch extends boolean = false>(key: string, isFetch?: IsFetch): Promise<WithLastModified<CacheValue<IsFetch>>>;
|
|
18
14
|
set<IsFetch extends boolean = false>(key: string, value: CacheValue<IsFetch>, isFetch?: IsFetch): Promise<void>;
|
|
19
15
|
delete(key: string): Promise<void>;
|
|
@@ -21,7 +17,6 @@ declare class Cache implements IncrementalCache {
|
|
|
21
17
|
protected getAssetUrl(key: string, isFetch?: boolean): string;
|
|
22
18
|
protected debug(...args: unknown[]): void;
|
|
23
19
|
protected getBuildId(): string;
|
|
24
|
-
protected init(): Promise<void>;
|
|
25
20
|
}
|
|
26
21
|
declare const _default: Cache;
|
|
27
22
|
export default _default;
|
package/dist/api/kvCache.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { IgnorableError, RecoverableError } from "@opennextjs/aws/utils/error.js";
|
|
2
|
-
import { getCloudflareContext } from "./
|
|
2
|
+
import { getCloudflareContext } from "./cloudflare-context.js";
|
|
3
3
|
export const CACHE_ASSET_DIR = "cnd-cgi/_next_cache";
|
|
4
4
|
export const STATUS_DELETED = 1;
|
|
5
5
|
/**
|
|
@@ -11,31 +11,28 @@ export const STATUS_DELETED = 1;
|
|
|
11
11
|
*/
|
|
12
12
|
class Cache {
|
|
13
13
|
name = "cloudflare-kv";
|
|
14
|
-
initialized = false;
|
|
15
|
-
kv;
|
|
16
|
-
assets;
|
|
17
14
|
async get(key, isFetch) {
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
if (!(
|
|
15
|
+
const cfEnv = getCloudflareContext().env;
|
|
16
|
+
const kv = cfEnv.NEXT_CACHE_WORKERS_KV;
|
|
17
|
+
const assets = cfEnv.ASSETS;
|
|
18
|
+
if (!(kv || assets)) {
|
|
22
19
|
throw new IgnorableError(`No KVNamespace nor Fetcher`);
|
|
23
20
|
}
|
|
24
21
|
this.debug(`Get ${key}`);
|
|
25
22
|
try {
|
|
26
23
|
let entry = null;
|
|
27
|
-
if (
|
|
24
|
+
if (kv) {
|
|
28
25
|
this.debug(`- From KV`);
|
|
29
26
|
const kvKey = this.getKVKey(key, isFetch);
|
|
30
|
-
entry = await
|
|
27
|
+
entry = await kv.get(kvKey, "json");
|
|
31
28
|
if (entry?.status === STATUS_DELETED) {
|
|
32
29
|
return {};
|
|
33
30
|
}
|
|
34
31
|
}
|
|
35
|
-
if (!entry &&
|
|
32
|
+
if (!entry && assets) {
|
|
36
33
|
this.debug(`- From Assets`);
|
|
37
34
|
const url = this.getAssetUrl(key, isFetch);
|
|
38
|
-
const response = await
|
|
35
|
+
const response = await assets.fetch(url);
|
|
39
36
|
if (response.ok) {
|
|
40
37
|
// TODO: consider populating KV with the asset value if faster.
|
|
41
38
|
// This could be optional as KV writes are $$.
|
|
@@ -55,10 +52,8 @@ class Cache {
|
|
|
55
52
|
}
|
|
56
53
|
}
|
|
57
54
|
async set(key, value, isFetch) {
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
}
|
|
61
|
-
if (!this.kv) {
|
|
55
|
+
const kv = getCloudflareContext().env.NEXT_CACHE_WORKERS_KV;
|
|
56
|
+
if (!kv) {
|
|
62
57
|
throw new IgnorableError(`No KVNamespace`);
|
|
63
58
|
}
|
|
64
59
|
this.debug(`Set ${key}`);
|
|
@@ -67,7 +62,7 @@ class Cache {
|
|
|
67
62
|
// Note: We can not set a TTL as we might fallback to assets,
|
|
68
63
|
// still removing old data (old BUILD_ID) could help avoiding
|
|
69
64
|
// the cache growing too big.
|
|
70
|
-
await
|
|
65
|
+
await kv.put(kvKey, JSON.stringify({
|
|
71
66
|
value,
|
|
72
67
|
// Note: `Date.now()` returns the time of the last IO rather than the actual time.
|
|
73
68
|
// See https://developers.cloudflare.com/workers/reference/security-model/
|
|
@@ -79,17 +74,15 @@ class Cache {
|
|
|
79
74
|
}
|
|
80
75
|
}
|
|
81
76
|
async delete(key) {
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
}
|
|
85
|
-
if (!this.kv) {
|
|
77
|
+
const kv = getCloudflareContext().env.NEXT_CACHE_WORKERS_KV;
|
|
78
|
+
if (!kv) {
|
|
86
79
|
throw new IgnorableError(`No KVNamespace`);
|
|
87
80
|
}
|
|
88
81
|
this.debug(`Delete ${key}`);
|
|
89
82
|
try {
|
|
90
83
|
const kvKey = this.getKVKey(key, /* isFetch= */ false);
|
|
91
84
|
// Do not delete the key as we would then fallback to the assets.
|
|
92
|
-
await
|
|
85
|
+
await kv.put(kvKey, JSON.stringify({ status: STATUS_DELETED }));
|
|
93
86
|
}
|
|
94
87
|
catch {
|
|
95
88
|
throw new RecoverableError(`Failed to delete cache [${key}]`);
|
|
@@ -111,11 +104,5 @@ class Cache {
|
|
|
111
104
|
getBuildId() {
|
|
112
105
|
return process.env.NEXT_BUILD_ID ?? "no-build-id";
|
|
113
106
|
}
|
|
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
107
|
}
|
|
121
108
|
export default new Cache();
|
package/dist/cli/build/build.js
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
import { cpSync } from "node:fs";
|
|
2
1
|
import { createRequire } from "node:module";
|
|
3
|
-
import { dirname
|
|
2
|
+
import { dirname } from "node:path";
|
|
4
3
|
import { buildNextjsApp, setStandaloneBuildMode } from "@opennextjs/aws/build/buildNextApp.js";
|
|
5
4
|
import { compileCache } from "@opennextjs/aws/build/compileCache.js";
|
|
6
5
|
import { compileOpenNextConfig } from "@opennextjs/aws/build/compileConfig.js";
|
|
@@ -9,7 +8,6 @@ import { createMiddleware } from "@opennextjs/aws/build/createMiddleware.js";
|
|
|
9
8
|
import * as buildHelper from "@opennextjs/aws/build/helper.js";
|
|
10
9
|
import { printHeader, showWarningOnWindows } from "@opennextjs/aws/build/utils.js";
|
|
11
10
|
import logger from "@opennextjs/aws/logger.js";
|
|
12
|
-
import { containsDotNextDir, getConfig } from "../config.js";
|
|
13
11
|
import { bundleServer } from "./bundle-server.js";
|
|
14
12
|
import { compileEnvFiles } from "./open-next/compile-env-files.js";
|
|
15
13
|
import { copyCacheAssets } from "./open-next/copyCacheAssets.js";
|
|
@@ -52,9 +50,6 @@ export async function build(projectOpts) {
|
|
|
52
50
|
setStandaloneBuildMode(options);
|
|
53
51
|
buildNextjsApp(options);
|
|
54
52
|
}
|
|
55
|
-
if (!containsDotNextDir(projectOpts.sourceDir)) {
|
|
56
|
-
throw new Error(`.next folder not found in ${projectOpts.sourceDir}`);
|
|
57
|
-
}
|
|
58
53
|
// Generate deployable bundle
|
|
59
54
|
printHeader("Generating bundle");
|
|
60
55
|
buildHelper.initOutputDir(options);
|
|
@@ -70,12 +65,7 @@ export async function build(projectOpts) {
|
|
|
70
65
|
copyCacheAssets(options);
|
|
71
66
|
}
|
|
72
67
|
await createServerBundle(options);
|
|
73
|
-
|
|
74
|
-
// Copy the .next directory to the output directory so it can be mutated.
|
|
75
|
-
cpSync(join(projectOpts.sourceDir, ".next"), join(projectOpts.outputDir, ".next"), { recursive: true });
|
|
76
|
-
const projConfig = getConfig(projectOpts);
|
|
77
|
-
// TODO: rely on options only.
|
|
78
|
-
await bundleServer(projConfig, options);
|
|
68
|
+
await bundleServer(options);
|
|
79
69
|
if (!projectOpts.skipWranglerConfigCheck) {
|
|
80
70
|
await createWranglerConfigIfNotExistent(projectOpts);
|
|
81
71
|
}
|
|
@@ -1,13 +1,12 @@
|
|
|
1
|
-
import type
|
|
2
|
-
import { Config } from "../config.js";
|
|
1
|
+
import { type BuildOptions } from "@opennextjs/aws/build/helper.js";
|
|
3
2
|
/**
|
|
4
3
|
* Bundle the Open Next server.
|
|
5
4
|
*/
|
|
6
|
-
export declare function bundleServer(
|
|
5
|
+
export declare function bundleServer(buildOpts: BuildOptions): Promise<void>;
|
|
7
6
|
/**
|
|
8
7
|
* Gets the path of the worker.js file generated by the build process
|
|
9
8
|
*
|
|
10
|
-
* @param
|
|
9
|
+
* @param buildOpts the open-next build options
|
|
11
10
|
* @returns the path of the worker.js file that the build process generates
|
|
12
11
|
*/
|
|
13
|
-
export declare function getOutputWorkerPath(
|
|
12
|
+
export declare function getOutputWorkerPath(buildOpts: BuildOptions): string;
|
|
@@ -3,27 +3,30 @@ import { readFile, writeFile } from "node:fs/promises";
|
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import { fileURLToPath } from "node:url";
|
|
5
5
|
import { Lang, parse } from "@ast-grep/napi";
|
|
6
|
+
import { getPackagePath } from "@opennextjs/aws/build/helper.js";
|
|
6
7
|
import { getCrossPlatformPathRegex } from "@opennextjs/aws/utils/regex.js";
|
|
7
8
|
import { build } from "esbuild";
|
|
8
9
|
import { patchOptionalDependencies } from "./patches/ast/optional-deps.js";
|
|
9
10
|
import * as patches from "./patches/index.js";
|
|
11
|
+
import inlineRequirePagePlugin from "./patches/plugins/require-page.js";
|
|
12
|
+
import setWranglerExternal from "./patches/plugins/wrangler-external.js";
|
|
10
13
|
import { normalizePath, patchCodeWithValidations } from "./utils/index.js";
|
|
11
14
|
/** The dist directory of the Cloudflare adapter package */
|
|
12
15
|
const packageDistDir = path.join(path.dirname(fileURLToPath(import.meta.url)), "../..");
|
|
13
16
|
/**
|
|
14
17
|
* Bundle the Open Next server.
|
|
15
18
|
*/
|
|
16
|
-
export async function bundleServer(
|
|
17
|
-
patches.copyPackageCliFiles(packageDistDir,
|
|
18
|
-
const
|
|
19
|
-
|
|
20
|
-
|
|
19
|
+
export async function bundleServer(buildOpts) {
|
|
20
|
+
patches.copyPackageCliFiles(packageDistDir, buildOpts);
|
|
21
|
+
const { appPath, outputDir, monorepoRoot } = buildOpts;
|
|
22
|
+
const serverFiles = path.join(outputDir, "server-functions/default", getPackagePath(buildOpts), ".next/required-server-files.json");
|
|
23
|
+
const nextConfig = JSON.parse(fs.readFileSync(serverFiles, "utf-8")).config;
|
|
21
24
|
console.log(`\x1b[35m⚙️ Bundling the OpenNext server...\n\x1b[0m`);
|
|
22
|
-
patches.patchWranglerDeps(
|
|
23
|
-
patches.updateWebpackChunksFile(
|
|
24
|
-
|
|
25
|
+
patches.patchWranglerDeps(buildOpts);
|
|
26
|
+
await patches.updateWebpackChunksFile(buildOpts);
|
|
27
|
+
patches.patchVercelOgLibrary(buildOpts);
|
|
25
28
|
const outputPath = path.join(outputDir, "server-functions", "default");
|
|
26
|
-
const packagePath =
|
|
29
|
+
const packagePath = getPackagePath(buildOpts);
|
|
27
30
|
const openNextServer = path.join(outputPath, packagePath, `index.mjs`);
|
|
28
31
|
const openNextServerBundle = path.join(outputPath, packagePath, `handler.mjs`);
|
|
29
32
|
await build({
|
|
@@ -33,25 +36,35 @@ export async function bundleServer(config, openNextOptions) {
|
|
|
33
36
|
format: "esm",
|
|
34
37
|
target: "esnext",
|
|
35
38
|
minify: false,
|
|
36
|
-
plugins: [
|
|
37
|
-
|
|
39
|
+
plugins: [
|
|
40
|
+
createFixRequiresESBuildPlugin(buildOpts),
|
|
41
|
+
inlineRequirePagePlugin(buildOpts),
|
|
42
|
+
setWranglerExternal(),
|
|
43
|
+
],
|
|
44
|
+
external: [
|
|
45
|
+
"./middleware/handler.mjs",
|
|
46
|
+
// Next optional dependencies.
|
|
47
|
+
"caniuse-lite",
|
|
48
|
+
"jimp",
|
|
49
|
+
"probe-image-size",
|
|
50
|
+
],
|
|
38
51
|
alias: {
|
|
39
52
|
// Note: we apply an empty shim to next/dist/compiled/ws because it generates two `eval`s:
|
|
40
53
|
// eval("require")("bufferutil");
|
|
41
54
|
// eval("require")("utf-8-validate");
|
|
42
|
-
"next/dist/compiled/ws": path.join(
|
|
55
|
+
"next/dist/compiled/ws": path.join(buildOpts.outputDir, "cloudflare-templates/shims/empty.js"),
|
|
43
56
|
// Note: we apply an empty shim to next/dist/compiled/edge-runtime since (amongst others) it generated the following `eval`:
|
|
44
57
|
// eval(getModuleCode)(module, module.exports, throwingRequire, params.context, ...Object.values(params.scopedContext));
|
|
45
58
|
// which comes from https://github.com/vercel/edge-runtime/blob/6e96b55f/packages/primitives/src/primitives/load.js#L57-L63
|
|
46
59
|
// QUESTION: Why did I encountered this but mhart didn't?
|
|
47
|
-
"next/dist/compiled/edge-runtime": path.join(
|
|
60
|
+
"next/dist/compiled/edge-runtime": path.join(buildOpts.outputDir, "cloudflare-templates/shims/empty.js"),
|
|
48
61
|
// `@next/env` is a library Next.js uses for loading dotenv files, for obvious reasons we need to stub it here
|
|
49
62
|
// source: https://github.com/vercel/next.js/tree/0ac10d79720/packages/next-env
|
|
50
|
-
"@next/env": path.join(
|
|
63
|
+
"@next/env": path.join(buildOpts.outputDir, "cloudflare-templates/shims/env.js"),
|
|
51
64
|
},
|
|
52
65
|
define: {
|
|
53
66
|
// config file used by Next.js, see: https://github.com/vercel/next.js/blob/68a7128/packages/next/src/build/utils.ts#L2137-L2139
|
|
54
|
-
"process.env.__NEXT_PRIVATE_STANDALONE_CONFIG": JSON.stringify(
|
|
67
|
+
"process.env.__NEXT_PRIVATE_STANDALONE_CONFIG": `${JSON.stringify(nextConfig)}`,
|
|
55
68
|
// Next.js tried to access __dirname so we need to define it
|
|
56
69
|
__dirname: '""',
|
|
57
70
|
// Note: we need the __non_webpack_require__ variable declared as it is used by next-server:
|
|
@@ -104,34 +117,28 @@ globalThis.__BUILD_TIMESTAMP_MS__ = ${Date.now()};
|
|
|
104
117
|
`,
|
|
105
118
|
},
|
|
106
119
|
});
|
|
107
|
-
await updateWorkerBundledCode(openNextServerBundle,
|
|
120
|
+
await updateWorkerBundledCode(openNextServerBundle, buildOpts);
|
|
108
121
|
const isMonorepo = monorepoRoot !== appPath;
|
|
109
122
|
if (isMonorepo) {
|
|
110
123
|
fs.writeFileSync(path.join(outputPath, "handler.mjs"), `export * from "./${normalizePath(packagePath)}/handler.mjs";`);
|
|
111
124
|
}
|
|
112
|
-
console.log(`\x1b[35mWorker saved in \`${getOutputWorkerPath(
|
|
125
|
+
console.log(`\x1b[35mWorker saved in \`${getOutputWorkerPath(buildOpts)}\` 🚀\n\x1b[0m`);
|
|
113
126
|
}
|
|
114
127
|
/**
|
|
115
|
-
* This function applies
|
|
116
|
-
*
|
|
117
|
-
* Needless to say all the logic in this function is something we should avoid as much as possible!
|
|
118
|
-
*
|
|
119
|
-
* @param workerOutputFile
|
|
120
|
-
* @param config
|
|
128
|
+
* This function applies patches required for the code to run on workers.
|
|
121
129
|
*/
|
|
122
|
-
async function updateWorkerBundledCode(workerOutputFile,
|
|
130
|
+
async function updateWorkerBundledCode(workerOutputFile, buildOpts) {
|
|
123
131
|
const code = await readFile(workerOutputFile, "utf8");
|
|
124
132
|
const patchedCode = await patchCodeWithValidations(code, [
|
|
125
133
|
["require", patches.patchRequire],
|
|
126
|
-
["`buildId` function", (code) => patches.patchBuildId(code,
|
|
127
|
-
["`loadManifest` function", (code) => patches.patchLoadManifest(code,
|
|
128
|
-
["
|
|
129
|
-
["`
|
|
130
|
-
["
|
|
131
|
-
["cacheHandler", (code) => patches.patchCache(code, openNextOptions)],
|
|
134
|
+
["`buildId` function", (code) => patches.patchBuildId(code, buildOpts)],
|
|
135
|
+
["`loadManifest` function", (code) => patches.patchLoadManifest(code, buildOpts)],
|
|
136
|
+
["`findDir` function", (code) => patches.patchFindDir(code, buildOpts)],
|
|
137
|
+
["`evalManifest` function", (code) => patches.inlineEvalManifest(code, buildOpts)],
|
|
138
|
+
["cacheHandler", (code) => patches.patchCache(code, buildOpts)],
|
|
132
139
|
[
|
|
133
140
|
"'require(this.middlewareManifestPath)'",
|
|
134
|
-
(code) => patches.inlineMiddlewareManifestRequire(code,
|
|
141
|
+
(code) => patches.inlineMiddlewareManifestRequire(code, buildOpts),
|
|
135
142
|
],
|
|
136
143
|
["exception bubbling", patches.patchExceptionBubbling],
|
|
137
144
|
["`loadInstrumentationModule` function", patches.patchLoadInstrumentationModule],
|
|
@@ -153,16 +160,16 @@ async function updateWorkerBundledCode(workerOutputFile, config, openNextOptions
|
|
|
153
160
|
],
|
|
154
161
|
]);
|
|
155
162
|
const bundle = parse(Lang.TypeScript, patchedCode).root();
|
|
156
|
-
const edits = patchOptionalDependencies(bundle);
|
|
163
|
+
const { edits } = patchOptionalDependencies(bundle);
|
|
157
164
|
await writeFile(workerOutputFile, bundle.commitEdits(edits));
|
|
158
165
|
}
|
|
159
|
-
function createFixRequiresESBuildPlugin(
|
|
166
|
+
function createFixRequiresESBuildPlugin(options) {
|
|
160
167
|
return {
|
|
161
168
|
name: "replaceRelative",
|
|
162
169
|
setup(build) {
|
|
163
170
|
// Note: we (empty) shim require-hook modules as they generate problematic code that uses requires
|
|
164
171
|
build.onResolve({ filter: getCrossPlatformPathRegex(String.raw `^\./require-hook$`, { escape: false }) }, () => ({
|
|
165
|
-
path: path.join(
|
|
172
|
+
path: path.join(options.outputDir, "cloudflare-templates/shims/empty.js"),
|
|
166
173
|
}));
|
|
167
174
|
},
|
|
168
175
|
};
|
|
@@ -170,9 +177,9 @@ function createFixRequiresESBuildPlugin(config) {
|
|
|
170
177
|
/**
|
|
171
178
|
* Gets the path of the worker.js file generated by the build process
|
|
172
179
|
*
|
|
173
|
-
* @param
|
|
180
|
+
* @param buildOpts the open-next build options
|
|
174
181
|
* @returns the path of the worker.js file that the build process generates
|
|
175
182
|
*/
|
|
176
|
-
export function getOutputWorkerPath(
|
|
177
|
-
return path.join(
|
|
183
|
+
export function getOutputWorkerPath(buildOpts) {
|
|
184
|
+
return path.join(buildOpts.outputDir, "worker.js");
|
|
178
185
|
}
|
|
@@ -2,4 +2,4 @@ import { BuildOptions } from "@opennextjs/aws/build/helper.js";
|
|
|
2
2
|
/**
|
|
3
3
|
* Compiles the values extracted from the project's env files to the output directory for use in the worker.
|
|
4
4
|
*/
|
|
5
|
-
export declare function compileEnvFiles(
|
|
5
|
+
export declare function compileEnvFiles(buildOpts: BuildOptions): void;
|
|
@@ -4,6 +4,6 @@ import { extractProjectEnvVars } from "../utils/index.js";
|
|
|
4
4
|
/**
|
|
5
5
|
* Compiles the values extracted from the project's env files to the output directory for use in the worker.
|
|
6
6
|
*/
|
|
7
|
-
export function compileEnvFiles(
|
|
8
|
-
["production", "development", "test"].forEach((mode) => fs.appendFileSync(path.join(
|
|
7
|
+
export function compileEnvFiles(buildOpts) {
|
|
8
|
+
["production", "development", "test"].forEach((mode) => fs.appendFileSync(path.join(buildOpts.outputDir, `.env.mjs`), `export const ${mode} = ${JSON.stringify(extractProjectEnvVars(mode, buildOpts))};\n`));
|
|
9
9
|
}
|
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
import { type SgNode } from "@ast-grep/napi";
|
|
2
2
|
/**
|
|
3
|
-
*
|
|
3
|
+
* Handles optional dependencies.
|
|
4
4
|
*
|
|
5
5
|
* A top level `require(optionalDep)` would throw when the dep is not installed.
|
|
6
6
|
*
|
|
7
7
|
* So we wrap `require(optionalDep)` in a try/catch (if not already present).
|
|
8
8
|
*/
|
|
9
|
-
export declare const optionalDepRule = "\nrule:\n pattern: $$$LHS = require($$$REQ)\n has:\n pattern: $MOD\n kind: string_fragment\n stopBy: end\n regex: ^caniuse-lite(/|$)\n not:\n inside:\n kind: try_statement\n stopBy: end\n\nfix: |-\n try {\n $$$LHS = require($$$REQ);\n } catch {\n throw new Error('The optional dependency \"$MOD\" is not installed');\n }\n";
|
|
10
|
-
export declare function patchOptionalDependencies(root: SgNode):
|
|
9
|
+
export declare const optionalDepRule = "\nrule:\n pattern: $$$LHS = require($$$REQ)\n has:\n pattern: $MOD\n kind: string_fragment\n stopBy: end\n regex: ^(caniuse-lite|jimp|probe-image-size)(/|$)\n not:\n inside:\n kind: try_statement\n stopBy: end\n\nfix: |-\n try {\n $$$LHS = require($$$REQ);\n } catch {\n throw new Error('The optional dependency \"$MOD\" is not installed');\n }\n";
|
|
10
|
+
export declare function patchOptionalDependencies(root: SgNode): {
|
|
11
|
+
edits: import("@ast-grep/napi").Edit[];
|
|
12
|
+
matches: SgNode<import("@ast-grep/napi/types/staticTypes.js").TypesMap, import("@ast-grep/napi/types/staticTypes.js").Kinds<import("@ast-grep/napi/types/staticTypes.js").TypesMap>>[];
|
|
13
|
+
};
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { applyRule } from "./util.js";
|
|
2
2
|
/**
|
|
3
|
-
*
|
|
3
|
+
* Handles optional dependencies.
|
|
4
4
|
*
|
|
5
5
|
* A top level `require(optionalDep)` would throw when the dep is not installed.
|
|
6
6
|
*
|
|
@@ -13,7 +13,7 @@ rule:
|
|
|
13
13
|
pattern: $MOD
|
|
14
14
|
kind: string_fragment
|
|
15
15
|
stopBy: end
|
|
16
|
-
regex: ^caniuse-lite(/|$)
|
|
16
|
+
regex: ^(caniuse-lite|jimp|probe-image-size)(/|$)
|
|
17
17
|
not:
|
|
18
18
|
inside:
|
|
19
19
|
kind: try_statement
|
|
@@ -27,5 +27,5 @@ fix: |-
|
|
|
27
27
|
}
|
|
28
28
|
`;
|
|
29
29
|
export function patchOptionalDependencies(root) {
|
|
30
|
-
return
|
|
30
|
+
return applyRule(optionalDepRule, root);
|
|
31
31
|
}
|