@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.
Files changed (60) hide show
  1. package/README.md +1 -64
  2. package/dist/api/{get-cloudflare-context.d.ts → cloudflare-context.d.ts} +8 -1
  3. package/dist/api/cloudflare-context.js +114 -0
  4. package/dist/api/index.d.ts +1 -1
  5. package/dist/api/index.js +1 -1
  6. package/dist/api/kvCache.d.ts +0 -5
  7. package/dist/api/kvCache.js +15 -28
  8. package/dist/cli/build/build.d.ts +1 -1
  9. package/dist/cli/build/build.js +2 -12
  10. package/dist/cli/build/bundle-server.d.ts +4 -5
  11. package/dist/cli/build/bundle-server.js +44 -37
  12. package/dist/cli/build/open-next/compile-env-files.d.ts +1 -1
  13. package/dist/cli/build/open-next/compile-env-files.js +2 -2
  14. package/dist/cli/build/patches/ast/optional-deps.d.ts +6 -3
  15. package/dist/cli/build/patches/ast/optional-deps.js +4 -4
  16. package/dist/cli/build/patches/ast/util.d.ts +15 -4
  17. package/dist/cli/build/patches/ast/util.js +16 -5
  18. package/dist/cli/build/patches/ast/vercel-og.d.ts +23 -0
  19. package/dist/cli/build/patches/ast/vercel-og.js +58 -0
  20. package/dist/cli/build/patches/ast/vercel-og.spec.d.ts +1 -0
  21. package/dist/cli/build/patches/ast/vercel-og.spec.js +22 -0
  22. package/dist/cli/build/patches/investigated/copy-package-cli-files.d.ts +4 -3
  23. package/dist/cli/build/patches/investigated/copy-package-cli-files.js +8 -5
  24. package/dist/cli/build/patches/investigated/index.d.ts +1 -0
  25. package/dist/cli/build/patches/investigated/index.js +1 -0
  26. package/dist/cli/build/patches/investigated/patch-cache.d.ts +2 -2
  27. package/dist/cli/build/patches/investigated/patch-cache.js +7 -6
  28. package/dist/cli/build/patches/investigated/patch-require.d.ts +1 -4
  29. package/dist/cli/build/patches/investigated/patch-require.js +1 -4
  30. package/dist/cli/build/patches/investigated/patch-vercel-og-library.d.ts +7 -0
  31. package/dist/cli/build/patches/investigated/patch-vercel-og-library.js +39 -0
  32. package/dist/cli/build/patches/investigated/patch-vercel-og-library.spec.d.ts +1 -0
  33. package/dist/cli/build/patches/investigated/patch-vercel-og-library.spec.js +50 -0
  34. package/dist/cli/build/patches/investigated/update-webpack-chunks-file/index.d.ts +2 -5
  35. package/dist/cli/build/patches/investigated/update-webpack-chunks-file/index.js +6 -6
  36. package/dist/cli/build/patches/plugins/require-page.d.ts +6 -0
  37. package/dist/cli/build/patches/plugins/require-page.js +70 -0
  38. package/dist/cli/build/patches/plugins/wrangler-external.d.ts +20 -0
  39. package/dist/cli/build/patches/plugins/wrangler-external.js +36 -0
  40. package/dist/cli/build/patches/to-investigate/index.d.ts +0 -1
  41. package/dist/cli/build/patches/to-investigate/index.js +0 -1
  42. package/dist/cli/build/patches/to-investigate/inline-eval-manifest.d.ts +2 -2
  43. package/dist/cli/build/patches/to-investigate/inline-eval-manifest.js +21 -17
  44. package/dist/cli/build/patches/to-investigate/inline-middleware-manifest-require.d.ts +2 -2
  45. package/dist/cli/build/patches/to-investigate/inline-middleware-manifest-require.js +4 -2
  46. package/dist/cli/build/patches/to-investigate/patch-find-dir.d.ts +3 -6
  47. package/dist/cli/build/patches/to-investigate/patch-find-dir.js +11 -11
  48. package/dist/cli/build/patches/to-investigate/patch-read-file.d.ts +3 -3
  49. package/dist/cli/build/patches/to-investigate/patch-read-file.js +15 -16
  50. package/dist/cli/build/patches/to-investigate/wrangler-deps.d.ts +2 -2
  51. package/dist/cli/build/patches/to-investigate/wrangler-deps.js +36 -67
  52. package/dist/cli/build/utils/create-config-files.d.ts +1 -1
  53. package/dist/cli/project-options.d.ts +7 -0
  54. package/dist/cli/project-options.js +1 -0
  55. package/package.json +4 -4
  56. package/dist/api/get-cloudflare-context.js +0 -42
  57. package/dist/cli/build/patches/to-investigate/inline-next-require.d.ts +0 -6
  58. package/dist/cli/build/patches/to-investigate/inline-next-require.js +0 -36
  59. package/dist/cli/config.d.ts +0 -42
  60. package/dist/cli/config.js +0 -92
@@ -7,7 +7,7 @@ export type RuleConfig = NapiConfig & {
7
7
  fix?: string;
8
8
  };
9
9
  /**
10
- * Returns the `Edit`s for an ast-grep rule in yaml format
10
+ * Returns the `Edit`s and `Match`es for an ast-grep rule in yaml format
11
11
  *
12
12
  * The rule must have a `fix` to rewrite the matched node.
13
13
  *
@@ -16,11 +16,22 @@ export type RuleConfig = NapiConfig & {
16
16
  * @param rule The rule. Either a yaml string or an instance of `RuleConfig`
17
17
  * @param root The root node
18
18
  * @param once only apply once
19
- * @returns A list of edits.
19
+ * @returns A list of edits and a list of matches.
20
20
  */
21
- export declare function getRuleEdits(rule: string | RuleConfig, root: SgNode, { once }?: {
21
+ export declare function applyRule(rule: string | RuleConfig, root: SgNode, { once }?: {
22
22
  once?: boolean | undefined;
23
- }): Edit[];
23
+ }): {
24
+ edits: Edit[];
25
+ matches: SgNode<import("@ast-grep/napi/types/staticTypes").TypesMap, import("@ast-grep/napi/types/staticTypes").Kinds<import("@ast-grep/napi/types/staticTypes").TypesMap>>[];
26
+ };
27
+ /**
28
+ * Parse a file and obtain its root.
29
+ *
30
+ * @param path The file path
31
+ * @param lang The language to parse. Defaults to TypeScript.
32
+ * @returns The root for the file.
33
+ */
34
+ export declare function parseFile(path: string, lang?: Lang): SgNode<import("@ast-grep/napi/types/staticTypes").TypesMap, import("@ast-grep/napi/types/staticTypes").Kinds<import("@ast-grep/napi/types/staticTypes").TypesMap>>;
24
35
  /**
25
36
  * Patches the code from by applying the rule.
26
37
  *
@@ -1,7 +1,8 @@
1
+ import { readFileSync } from "node:fs";
1
2
  import { Lang, parse } from "@ast-grep/napi";
2
3
  import yaml from "yaml";
3
4
  /**
4
- * Returns the `Edit`s for an ast-grep rule in yaml format
5
+ * Returns the `Edit`s and `Match`es for an ast-grep rule in yaml format
5
6
  *
6
7
  * The rule must have a `fix` to rewrite the matched node.
7
8
  *
@@ -10,9 +11,9 @@ import yaml from "yaml";
10
11
  * @param rule The rule. Either a yaml string or an instance of `RuleConfig`
11
12
  * @param root The root node
12
13
  * @param once only apply once
13
- * @returns A list of edits.
14
+ * @returns A list of edits and a list of matches.
14
15
  */
15
- export function getRuleEdits(rule, root, { once = false } = {}) {
16
+ export function applyRule(rule, root, { once = false } = {}) {
16
17
  const ruleConfig = typeof rule === "string" ? yaml.parse(rule) : rule;
17
18
  if (ruleConfig.transform) {
18
19
  throw new Error("transform is not supported");
@@ -33,7 +34,17 @@ export function getRuleEdits(rule, root, { once = false } = {}) {
33
34
  .join(""))
34
35
  .replace(/\$([A-Z0-9_]+)/g, (m, name) => match.getMatch(name)?.text() ?? m)));
35
36
  });
36
- return edits;
37
+ return { edits, matches };
38
+ }
39
+ /**
40
+ * Parse a file and obtain its root.
41
+ *
42
+ * @param path The file path
43
+ * @param lang The language to parse. Defaults to TypeScript.
44
+ * @returns The root for the file.
45
+ */
46
+ export function parseFile(path, lang = Lang.TypeScript) {
47
+ return parse(lang, readFileSync(path, { encoding: "utf-8" })).root();
37
48
  }
38
49
  /**
39
50
  * Patches the code from by applying the rule.
@@ -49,6 +60,6 @@ export function getRuleEdits(rule, root, { once = false } = {}) {
49
60
  */
50
61
  export function patchCode(code, rule, { lang = Lang.TypeScript, once = false } = {}) {
51
62
  const node = parse(lang, code).root();
52
- const edits = getRuleEdits(rule, node, { once });
63
+ const { edits } = applyRule(rule, node, { once });
53
64
  return node.commitEdits(edits);
54
65
  }
@@ -0,0 +1,23 @@
1
+ import { SgNode } from "@ast-grep/napi";
2
+ export declare const vercelOgImportRule = "\nrule:\n pattern: $NODE\n kind: string\n regex: \"next/dist/compiled/@vercel/og/index\\\\.node\\\\.js\"\ninside:\n kind: arguments\n inside:\n kind: call_expression\n stopBy: end\n has:\n field: function\n regex: \"import\"\n\nfix: |-\n \"next/dist/compiled/@vercel/og/index.edge.js\"\n";
3
+ /**
4
+ * Patches Node.js imports for the library to be Edge imports.
5
+ *
6
+ * @param root Root node.
7
+ * @returns Results of applying the rule.
8
+ */
9
+ export declare function patchVercelOgImport(root: SgNode): {
10
+ edits: import("@ast-grep/napi").Edit[];
11
+ 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>>[];
12
+ };
13
+ export declare const vercelOgFallbackFontRule = "\nrule:\n kind: variable_declaration\n all:\n - has:\n kind: variable_declarator\n has:\n kind: identifier\n regex: ^fallbackFont$\n - has:\n kind: call_expression\n pattern: fetch(new URL(\"$PATH\", $$$REST))\n stopBy: end\n\nfix: |-\n async function getFallbackFont() {\n // .bin is used so that a loader does not need to be configured for .ttf files\n return (await import(\"$PATH.bin\")).default;\n }\n\n var fallbackFont = getFallbackFont();\n";
14
+ /**
15
+ * Patches the default font fetching to use a .bin import.
16
+ *
17
+ * @param root Root node.
18
+ * @returns Results of applying the rule.
19
+ */
20
+ export declare function patchVercelOgFallbackFont(root: SgNode): {
21
+ edits: import("@ast-grep/napi").Edit[];
22
+ 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>>[];
23
+ };
@@ -0,0 +1,58 @@
1
+ import { applyRule } from "./util.js";
2
+ export const vercelOgImportRule = `
3
+ rule:
4
+ pattern: $NODE
5
+ kind: string
6
+ regex: "next/dist/compiled/@vercel/og/index\\\\.node\\\\.js"
7
+ inside:
8
+ kind: arguments
9
+ inside:
10
+ kind: call_expression
11
+ stopBy: end
12
+ has:
13
+ field: function
14
+ regex: "import"
15
+
16
+ fix: |-
17
+ "next/dist/compiled/@vercel/og/index.edge.js"
18
+ `;
19
+ /**
20
+ * Patches Node.js imports for the library to be Edge imports.
21
+ *
22
+ * @param root Root node.
23
+ * @returns Results of applying the rule.
24
+ */
25
+ export function patchVercelOgImport(root) {
26
+ return applyRule(vercelOgImportRule, root);
27
+ }
28
+ export const vercelOgFallbackFontRule = `
29
+ rule:
30
+ kind: variable_declaration
31
+ all:
32
+ - has:
33
+ kind: variable_declarator
34
+ has:
35
+ kind: identifier
36
+ regex: ^fallbackFont$
37
+ - has:
38
+ kind: call_expression
39
+ pattern: fetch(new URL("$PATH", $$$REST))
40
+ stopBy: end
41
+
42
+ fix: |-
43
+ async function getFallbackFont() {
44
+ // .bin is used so that a loader does not need to be configured for .ttf files
45
+ return (await import("$PATH.bin")).default;
46
+ }
47
+
48
+ var fallbackFont = getFallbackFont();
49
+ `;
50
+ /**
51
+ * Patches the default font fetching to use a .bin import.
52
+ *
53
+ * @param root Root node.
54
+ * @returns Results of applying the rule.
55
+ */
56
+ export function patchVercelOgFallbackFont(root) {
57
+ return applyRule(vercelOgFallbackFontRule, root);
58
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,22 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { patchCode } from "./util";
3
+ import { vercelOgFallbackFontRule, vercelOgImportRule } from "./vercel-og";
4
+ describe("vercelOgImportRule", () => {
5
+ it("should rewrite a node import to an edge import", () => {
6
+ const code = `e.exports=import("next/dist/compiled/@vercel/og/index.node.js")`;
7
+ expect(patchCode(code, vercelOgImportRule)).toMatchInlineSnapshot(`"e.exports=import("next/dist/compiled/@vercel/og/index.edge.js")"`);
8
+ });
9
+ });
10
+ describe("vercelOgFallbackFontRule", () => {
11
+ it("should replace a fetch call for a font with an import", () => {
12
+ const code = `var fallbackFont = fetch(new URL("./noto-sans-v27-latin-regular.ttf", import.meta.url)).then((res) => res.arrayBuffer());`;
13
+ expect(patchCode(code, vercelOgFallbackFontRule)).toMatchInlineSnapshot(`
14
+ "async function getFallbackFont() {
15
+ // .bin is used so that a loader does not need to be configured for .ttf files
16
+ return (await import("./noto-sans-v27-latin-regular.ttf.bin")).default;
17
+ }
18
+
19
+ var fallbackFont = getFallbackFont();"
20
+ `);
21
+ });
22
+ });
@@ -1,6 +1,7 @@
1
1
  import type { BuildOptions } from "@opennextjs/aws/build/helper.js";
2
- import { Config } from "../../../config.js";
3
2
  /**
4
- * Copies the template files present in the cloudflare adapter package into the standalone node_modules folder
3
+ * Copies
4
+ * - the template files present in the cloudflare adapter package to `.open-next/cloudflare-templates`
5
+ * - `worker.js` to `.open-next/`
5
6
  */
6
- export declare function copyPackageCliFiles(packageDistDir: string, config: Config, openNextOptions: BuildOptions): void;
7
+ export declare function copyPackageCliFiles(packageDistDir: string, buildOpts: BuildOptions): void;
@@ -2,12 +2,15 @@ import fs from "node:fs";
2
2
  import path from "node:path";
3
3
  import { getOutputWorkerPath } from "../../bundle-server.js";
4
4
  /**
5
- * Copies the template files present in the cloudflare adapter package into the standalone node_modules folder
5
+ * Copies
6
+ * - the template files present in the cloudflare adapter package to `.open-next/cloudflare-templates`
7
+ * - `worker.js` to `.open-next/`
6
8
  */
7
- export function copyPackageCliFiles(packageDistDir, config, openNextOptions) {
9
+ export function copyPackageCliFiles(packageDistDir, buildOpts) {
8
10
  console.log("# copyPackageTemplateFiles");
9
- const sourceDir = path.join(packageDistDir, "cli");
10
- const destinationDir = path.join(config.paths.internal.package, "cli");
11
+ const sourceDir = path.join(packageDistDir, "cli/templates");
12
+ const destinationDir = path.join(buildOpts.outputDir, "cloudflare-templates");
13
+ fs.mkdirSync(destinationDir, { recursive: true });
11
14
  fs.cpSync(sourceDir, destinationDir, { recursive: true });
12
- fs.copyFileSync(path.join(packageDistDir, "cli", "templates", "worker.js"), getOutputWorkerPath(openNextOptions));
15
+ fs.copyFileSync(path.join(packageDistDir, "cli/templates/worker.js"), getOutputWorkerPath(buildOpts));
13
16
  }
@@ -1,4 +1,5 @@
1
1
  export * from "./copy-package-cli-files.js";
2
2
  export * from "./patch-cache.js";
3
3
  export * from "./patch-require.js";
4
+ export * from "./patch-vercel-og-library.js";
4
5
  export * from "./update-webpack-chunks-file/index.js";
@@ -1,4 +1,5 @@
1
1
  export * from "./copy-package-cli-files.js";
2
2
  export * from "./patch-cache.js";
3
3
  export * from "./patch-require.js";
4
+ export * from "./patch-vercel-og-library.js";
4
5
  export * from "./update-webpack-chunks-file/index.js";
@@ -1,4 +1,4 @@
1
- import type { BuildOptions } from "@opennextjs/aws/build/helper.js";
1
+ import { type BuildOptions } from "@opennextjs/aws/build/helper.js";
2
2
  /**
3
3
  * Sets up the OpenNext cache handler in a Next.js build.
4
4
  *
@@ -10,4 +10,4 @@ import type { BuildOptions } from "@opennextjs/aws/build/helper.js";
10
10
  * build-time. Therefore, we have to manually override the default way that the cache handler is
11
11
  * instantiated with a dynamic require that uses a string literal for the path.
12
12
  */
13
- export declare function patchCache(code: string, openNextOptions: BuildOptions): Promise<string>;
13
+ export declare function patchCache(code: string, buildOpts: BuildOptions): Promise<string>;
@@ -1,4 +1,5 @@
1
1
  import path from "node:path";
2
+ import { getPackagePath } from "@opennextjs/aws/build/helper.js";
2
3
  import { normalizePath } from "../../utils/index.js";
3
4
  /**
4
5
  * Sets up the OpenNext cache handler in a Next.js build.
@@ -11,13 +12,13 @@ import { normalizePath } from "../../utils/index.js";
11
12
  * build-time. Therefore, we have to manually override the default way that the cache handler is
12
13
  * instantiated with a dynamic require that uses a string literal for the path.
13
14
  */
14
- export async function patchCache(code, openNextOptions) {
15
- const { appBuildOutputPath, outputDir, monorepoRoot } = openNextOptions;
15
+ export async function patchCache(code, buildOpts) {
16
+ const { outputDir } = buildOpts;
16
17
  // TODO: switch to cache.mjs
17
- const outputPath = path.join(outputDir, "server-functions", "default");
18
- const packagePath = path.relative(monorepoRoot, appBuildOutputPath);
19
- const cacheFile = path.join(outputPath, packagePath, "cache.cjs");
20
- return code.replace("const { cacheHandler } = this.nextConfig;", `const cacheHandler = null;
18
+ const outputPath = path.join(outputDir, "server-functions/default");
19
+ const cacheFile = path.join(outputPath, getPackagePath(buildOpts), "cache.cjs");
20
+ return code.replace("const { cacheHandler } = this.nextConfig;", `
21
+ const cacheHandler = null;
21
22
  CacheHandler = require('${normalizePath(cacheFile)}').default;
22
23
  `);
23
24
  }
@@ -1,7 +1,4 @@
1
1
  /**
2
- * ESBuild does not support CJS format
3
- * See https://github.com/evanw/esbuild/issues/1921 and linked issues
4
- * Some of the solutions are based on `module.createRequire()` not implemented in workerd.
5
- * James on Aug 29: `module.createRequire()` is planned.
2
+ * Replaces webpack `__require` with actual `require`
6
3
  */
7
4
  export declare function patchRequire(code: string): string;
@@ -1,8 +1,5 @@
1
1
  /**
2
- * ESBuild does not support CJS format
3
- * See https://github.com/evanw/esbuild/issues/1921 and linked issues
4
- * Some of the solutions are based on `module.createRequire()` not implemented in workerd.
5
- * James on Aug 29: `module.createRequire()` is planned.
2
+ * Replaces webpack `__require` with actual `require`
6
3
  */
7
4
  export function patchRequire(code) {
8
5
  return code.replace(/__require\d?\(/g, "require(").replace(/__require\d?\./g, "require.");
@@ -0,0 +1,7 @@
1
+ import type { BuildOptions } from "@opennextjs/aws/build/helper.js";
2
+ /**
3
+ * Patches the usage of @vercel/og to be compatible with Cloudflare Workers.
4
+ *
5
+ * @param buildOpts Build options.
6
+ */
7
+ export declare function patchVercelOgLibrary(buildOpts: BuildOptions): void;
@@ -0,0 +1,39 @@
1
+ import { copyFileSync, existsSync, readFileSync, renameSync, writeFileSync } from "node:fs";
2
+ import path from "node:path";
3
+ import { getPackagePath } from "@opennextjs/aws/build/helper.js";
4
+ import { globSync } from "glob";
5
+ import { parseFile } from "../ast/util.js";
6
+ import { patchVercelOgFallbackFont, patchVercelOgImport } from "../ast/vercel-og.js";
7
+ /**
8
+ * Patches the usage of @vercel/og to be compatible with Cloudflare Workers.
9
+ *
10
+ * @param buildOpts Build options.
11
+ */
12
+ export function patchVercelOgLibrary(buildOpts) {
13
+ const { appBuildOutputPath, outputDir } = buildOpts;
14
+ const packagePath = path.join(outputDir, "server-functions/default", getPackagePath(buildOpts));
15
+ for (const traceInfoPath of globSync(path.join(appBuildOutputPath, ".next/server/**/*.nft.json"))) {
16
+ const traceInfo = JSON.parse(readFileSync(traceInfoPath, { encoding: "utf8" }));
17
+ const tracedNodePath = traceInfo.files.find((p) => p.endsWith("@vercel/og/index.node.js"));
18
+ if (!tracedNodePath)
19
+ continue;
20
+ const outputDir = path.join(packagePath, "node_modules/next/dist/compiled/@vercel/og");
21
+ const outputEdgePath = path.join(outputDir, "index.edge.js");
22
+ // Ensure the edge version is available in the OpenNext node_modules.
23
+ if (!existsSync(outputEdgePath)) {
24
+ const tracedEdgePath = path.join(path.dirname(traceInfoPath), tracedNodePath.replace("index.node.js", "index.edge.js"));
25
+ copyFileSync(tracedEdgePath, outputEdgePath);
26
+ // Change font fetches in the library to use imports.
27
+ const node = parseFile(outputEdgePath);
28
+ const { edits, matches } = patchVercelOgFallbackFont(node);
29
+ writeFileSync(outputEdgePath, node.commitEdits(edits));
30
+ const fontFileName = matches[0].getMatch("PATH").text();
31
+ renameSync(path.join(outputDir, fontFileName), path.join(outputDir, `${fontFileName}.bin`));
32
+ }
33
+ // Change node imports for the library to edge imports.
34
+ const routeFilePath = traceInfoPath.replace(appBuildOutputPath, packagePath).replace(".nft.json", "");
35
+ const node = parseFile(routeFilePath);
36
+ const { edits } = patchVercelOgImport(node);
37
+ writeFileSync(routeFilePath, node.commitEdits(edits));
38
+ }
39
+ }
@@ -0,0 +1,50 @@
1
+ import { mkdirSync, readdirSync, readFileSync, writeFileSync } from "node:fs";
2
+ import path from "node:path";
3
+ import mockFs from "mock-fs";
4
+ import { afterAll, beforeAll, describe, expect, it } from "vitest";
5
+ import { patchVercelOgLibrary } from "./patch-vercel-og-library";
6
+ const nodeModulesVercelOgDir = "node_modules/.pnpm/next@14.2.11/node_modules/next/dist/compiled/@vercel/og";
7
+ const nextServerOgNftPath = "examples/api/.next/server/app/og/route.js.nft.json";
8
+ const openNextFunctionDir = "examples/api/.open-next/server-functions/default/examples/api";
9
+ const openNextOgRoutePath = path.join(openNextFunctionDir, ".next/server/app/og/route.js");
10
+ const openNextVercelOgDir = path.join(openNextFunctionDir, "node_modules/next/dist/compiled/@vercel/og");
11
+ const buildOpts = {
12
+ appBuildOutputPath: "examples/api",
13
+ monorepoRoot: "",
14
+ outputDir: "examples/api/.open-next",
15
+ };
16
+ describe("patchVercelOgLibrary", () => {
17
+ beforeAll(() => {
18
+ mockFs();
19
+ mkdirSync(nodeModulesVercelOgDir, { recursive: true });
20
+ mkdirSync(path.dirname(nextServerOgNftPath), { recursive: true });
21
+ mkdirSync(path.dirname(openNextOgRoutePath), { recursive: true });
22
+ mkdirSync(openNextVercelOgDir, { recursive: true });
23
+ writeFileSync(nextServerOgNftPath, JSON.stringify({ version: 1, files: [`../../../../../../${nodeModulesVercelOgDir}/index.node.js`] }));
24
+ writeFileSync(path.join(nodeModulesVercelOgDir, "index.edge.js"), `var fallbackFont = fetch(new URL("./noto-sans-v27-latin-regular.ttf", import.meta.url)).then((res) => res.arrayBuffer());`);
25
+ writeFileSync(openNextOgRoutePath, `e.exports=import("next/dist/compiled/@vercel/og/index.node.js")`);
26
+ writeFileSync(path.join(openNextVercelOgDir, "index.node.js"), "");
27
+ writeFileSync(path.join(openNextVercelOgDir, "noto-sans-v27-latin-regular.ttf"), "");
28
+ });
29
+ afterAll(() => mockFs.restore());
30
+ it("should patch the open-next files correctly", () => {
31
+ patchVercelOgLibrary(buildOpts);
32
+ expect(readdirSync(openNextVercelOgDir)).toMatchInlineSnapshot(`
33
+ [
34
+ "index.edge.js",
35
+ "index.node.js",
36
+ "noto-sans-v27-latin-regular.ttf.bin",
37
+ ]
38
+ `);
39
+ expect(readFileSync(path.join(openNextVercelOgDir, "index.edge.js"), { encoding: "utf-8" }))
40
+ .toMatchInlineSnapshot(`
41
+ "async function getFallbackFont() {
42
+ // .bin is used so that a loader does not need to be configured for .ttf files
43
+ return (await import("./noto-sans-v27-latin-regular.ttf.bin")).default;
44
+ }
45
+
46
+ var fallbackFont = getFallbackFont();"
47
+ `);
48
+ expect(readFileSync(openNextOgRoutePath, { encoding: "utf-8" })).toMatchInlineSnapshot(`"e.exports=import("next/dist/compiled/@vercel/og/index.edge.js")"`);
49
+ });
50
+ });
@@ -1,8 +1,5 @@
1
- import { Config } from "../../../../config.js";
1
+ import { type BuildOptions } from "@opennextjs/aws/build/helper.js";
2
2
  /**
3
3
  * Fixes the webpack-runtime.js file by removing its webpack dynamic requires.
4
- *
5
- * This hack is particularly bad as it indicates that files inside the output directory still get a hold of files from the outside: `${nextjsAppPaths.standaloneAppServerDir}/webpack-runtime.js`
6
- * so this shows that not everything that's needed to deploy the application is in the output directory...
7
4
  */
8
- export declare function updateWebpackChunksFile(config: Config): Promise<void>;
5
+ export declare function updateWebpackChunksFile(buildOpts: BuildOptions): Promise<void>;
@@ -1,17 +1,17 @@
1
1
  import { readdirSync, readFileSync, writeFileSync } from "node:fs";
2
2
  import { join } from "node:path";
3
+ import { getPackagePath } from "@opennextjs/aws/build/helper.js";
3
4
  import { getUpdatedWebpackChunksFileContent } from "./get-updated-webpack-chunks-file-content.js";
4
5
  /**
5
6
  * Fixes the webpack-runtime.js file by removing its webpack dynamic requires.
6
- *
7
- * This hack is particularly bad as it indicates that files inside the output directory still get a hold of files from the outside: `${nextjsAppPaths.standaloneAppServerDir}/webpack-runtime.js`
8
- * so this shows that not everything that's needed to deploy the application is in the output directory...
9
7
  */
10
- export async function updateWebpackChunksFile(config) {
8
+ export async function updateWebpackChunksFile(buildOpts) {
11
9
  console.log("# updateWebpackChunksFile");
12
- const webpackRuntimeFile = join(config.paths.output.standaloneAppServer, "webpack-runtime.js");
10
+ const { outputDir } = buildOpts;
11
+ const dotNextServerDir = join(outputDir, "server-functions/default", getPackagePath(buildOpts), ".next/server");
12
+ const webpackRuntimeFile = join(dotNextServerDir, "webpack-runtime.js");
13
13
  const fileContent = readFileSync(webpackRuntimeFile, "utf-8");
14
- const chunks = readdirSync(join(config.paths.output.standaloneAppServer, "chunks"))
14
+ const chunks = readdirSync(join(dotNextServerDir, "chunks"))
15
15
  .filter((chunk) => /^\d+\.js$/.test(chunk))
16
16
  .map((chunk) => {
17
17
  console.log(` - chunk ${chunk}`);
@@ -0,0 +1,6 @@
1
+ import { type BuildOptions } from "@opennextjs/aws/build/helper.js";
2
+ import type { PluginBuild } from "esbuild";
3
+ export default function inlineRequirePagePlugin(buildOpts: BuildOptions): {
4
+ name: string;
5
+ setup: (build: PluginBuild) => Promise<void>;
6
+ };
@@ -0,0 +1,70 @@
1
+ import { existsSync, readFileSync } from "node:fs";
2
+ import { readFile } from "node:fs/promises";
3
+ import { join } from "node:path";
4
+ import { getPackagePath } from "@opennextjs/aws/build/helper.js";
5
+ import { getCrossPlatformPathRegex } from "@opennextjs/aws/utils/regex.js";
6
+ import { patchCode } from "../ast/util.js";
7
+ export default function inlineRequirePagePlugin(buildOpts) {
8
+ return {
9
+ name: "inline-require-page",
10
+ setup: async (build) => {
11
+ build.onLoad({
12
+ filter: getCrossPlatformPathRegex(String.raw `/next/dist/server/require\.js$`, { escape: false }),
13
+ }, async ({ path }) => {
14
+ const jsCode = await readFile(path, "utf8");
15
+ if (/function requirePage\(/.test(jsCode)) {
16
+ return { contents: patchCode(jsCode, getRule(buildOpts)) };
17
+ }
18
+ });
19
+ },
20
+ };
21
+ }
22
+ function getRule(buildOpts) {
23
+ const { outputDir } = buildOpts;
24
+ const serverDir = join(outputDir, "server-functions/default", getPackagePath(buildOpts), ".next/server");
25
+ const pagesManifestFile = join(serverDir, "pages-manifest.json");
26
+ const appPathsManifestFile = join(serverDir, "app-paths-manifest.json");
27
+ const pagesManifests = existsSync(pagesManifestFile)
28
+ ? Object.values(JSON.parse(readFileSync(pagesManifestFile, "utf-8")))
29
+ : [];
30
+ const appPathsManifests = existsSync(appPathsManifestFile)
31
+ ? Object.values(JSON.parse(readFileSync(appPathsManifestFile, "utf-8")))
32
+ : [];
33
+ const manifests = pagesManifests.concat(appPathsManifests);
34
+ const htmlFiles = manifests.filter((file) => file.endsWith(".html"));
35
+ const jsFiles = manifests.filter((file) => file.endsWith(".js"));
36
+ // Inline fs access and dynamic require that are not supported by workerd.
37
+ const fnBody = `
38
+ // html
39
+ ${htmlFiles
40
+ .map((file) => `if (pagePath.endsWith("${file}")) {
41
+ return ${JSON.stringify(readFileSync(join(serverDir, file), "utf-8"))};
42
+ }`)
43
+ .join("\n")}
44
+ // js
45
+ process.env.__NEXT_PRIVATE_RUNTIME_TYPE = isAppPath ? 'app' : 'pages';
46
+ try {
47
+ ${jsFiles
48
+ .map((file) => `if (pagePath.endsWith("${file}")) {
49
+ return require(${JSON.stringify(join(serverDir, file))});
50
+ }`)
51
+ .join("\n")}
52
+ } finally {
53
+ process.env.__NEXT_PRIVATE_RUNTIME_TYPE = '';
54
+ }
55
+ `;
56
+ return {
57
+ rule: {
58
+ pattern: `
59
+ function requirePage($PAGE, $DIST_DIR, $IS_APPP_ATH) {
60
+ const $_ = getPagePath($$$ARGS);
61
+ $$$_BODY
62
+ }`,
63
+ },
64
+ fix: `
65
+ function requirePage($PAGE, $DIST_DIR, $IS_APPP_ATH) {
66
+ const pagePath = getPagePath($$$ARGS);
67
+ ${fnBody}
68
+ }`,
69
+ };
70
+ }
@@ -0,0 +1,20 @@
1
+ /**
2
+ * ESBuild plugin to mark files bundled by wrangler as external.
3
+ *
4
+ * `.wasm` and `.bin` will ultimately be bundled by wrangler.
5
+ * We should only mark them as external in the adapter.
6
+ *
7
+ * However simply marking them as external would copy the import path to the bundle,
8
+ * i.e. `import("./file.wasm?module")` and given than the bundle is generated in a
9
+ * different location than the input files, the relative path would not be valid.
10
+ *
11
+ * This ESBuild plugin convert relative paths to absolute paths so that they are
12
+ * still valid from inside the bundle.
13
+ *
14
+ * ref: https://developers.cloudflare.com/workers/wrangler/bundling/
15
+ */
16
+ import type { PluginBuild } from "esbuild";
17
+ export default function setWranglerExternal(): {
18
+ name: string;
19
+ setup: (build: PluginBuild) => Promise<void>;
20
+ };
@@ -0,0 +1,36 @@
1
+ /**
2
+ * ESBuild plugin to mark files bundled by wrangler as external.
3
+ *
4
+ * `.wasm` and `.bin` will ultimately be bundled by wrangler.
5
+ * We should only mark them as external in the adapter.
6
+ *
7
+ * However simply marking them as external would copy the import path to the bundle,
8
+ * i.e. `import("./file.wasm?module")` and given than the bundle is generated in a
9
+ * different location than the input files, the relative path would not be valid.
10
+ *
11
+ * This ESBuild plugin convert relative paths to absolute paths so that they are
12
+ * still valid from inside the bundle.
13
+ *
14
+ * ref: https://developers.cloudflare.com/workers/wrangler/bundling/
15
+ */
16
+ import { dirname, resolve } from "node:path";
17
+ export default function setWranglerExternal() {
18
+ return {
19
+ name: "wrangler-externals",
20
+ setup: async (build) => {
21
+ const namespace = "wrangler-externals-plugin";
22
+ build.onResolve({ filter: /(\.bin|\.wasm\?module)$/ }, ({ path, importer }) => {
23
+ return {
24
+ path: resolve(dirname(importer), path),
25
+ namespace,
26
+ external: true,
27
+ };
28
+ });
29
+ build.onLoad({ filter: /.*/, namespace }, async ({ path }) => {
30
+ return {
31
+ contents: `export * from '${path}';`,
32
+ };
33
+ });
34
+ },
35
+ };
36
+ }
@@ -1,6 +1,5 @@
1
1
  export * from "./inline-eval-manifest.js";
2
2
  export * from "./inline-middleware-manifest-require.js";
3
- export * from "./inline-next-require.js";
4
3
  export * from "./patch-exception-bubbling.js";
5
4
  export * from "./patch-find-dir.js";
6
5
  export * from "./patch-load-instrumentation-module.js";
@@ -1,6 +1,5 @@
1
1
  export * from "./inline-eval-manifest.js";
2
2
  export * from "./inline-middleware-manifest-require.js";
3
- export * from "./inline-next-require.js";
4
3
  export * from "./patch-exception-bubbling.js";
5
4
  export * from "./patch-find-dir.js";
6
5
  export * from "./patch-load-instrumentation-module.js";
@@ -1,4 +1,4 @@
1
- import { Config } from "../../../config.js";
1
+ import { type BuildOptions } from "@opennextjs/aws/build/helper.js";
2
2
  /**
3
3
  * `evalManifest` relies on readFileSync so we need to patch the function so that it instead returns the content of the manifest files
4
4
  * which are known at build time
@@ -6,4 +6,4 @@ import { Config } from "../../../config.js";
6
6
  * Note: we could/should probably just patch readFileSync here or something, but here the issue is that after the readFileSync call
7
7
  * there is a vm `runInNewContext` call which we also don't support (source: https://github.com/vercel/next.js/blob/b1e32c5d1f/packages/next/src/server/load-manifest.ts#L88)
8
8
  */
9
- export declare function inlineEvalManifest(code: string, config: Config): string;
9
+ export declare function inlineEvalManifest(code: string, buildOpts: BuildOptions): string;