Skip to content

Commit 15ce996

Browse files
refactor: use the traced file copy from OpenNext
Co-authored-by: Dario Piotrowicz <[email protected]>
1 parent 8de2c04 commit 15ce996

18 files changed

+453
-634
lines changed

packages/cloudflare/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@
7373
"dependencies": {
7474
"@ast-grep/napi": "^0.34.1",
7575
"@dotenvx/dotenvx": "catalog:",
76-
"@opennextjs/aws": "https://pkg.pr.new/@opennextjs/aws@704",
76+
"@opennextjs/aws": "https://pkg.pr.new/@opennextjs/aws@712",
7777
"enquirer": "^2.4.1",
7878
"glob": "catalog:",
7979
"ts-morph": "catalog:",

packages/cloudflare/src/cli/build/build.ts

Lines changed: 3 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
1-
import { cpSync } from "node:fs";
21
import { createRequire } from "node:module";
3-
import { dirname, join } from "node:path";
2+
import { dirname } from "node:path";
43

54
import { buildNextjsApp, setStandaloneBuildMode } from "@opennextjs/aws/build/buildNextApp.js";
65
import { compileCache } from "@opennextjs/aws/build/compileCache.js";
@@ -11,8 +10,7 @@ import * as buildHelper from "@opennextjs/aws/build/helper.js";
1110
import { printHeader, showWarningOnWindows } from "@opennextjs/aws/build/utils.js";
1211
import logger from "@opennextjs/aws/logger.js";
1312

14-
import type { ProjectOptions } from "../config.js";
15-
import { containsDotNextDir, getConfig } from "../config.js";
13+
import type { ProjectOptions } from "../project-options.js";
1614
import { bundleServer } from "./bundle-server.js";
1715
import { compileEnvFiles } from "./open-next/compile-env-files.js";
1816
import { copyCacheAssets } from "./open-next/copyCacheAssets.js";
@@ -69,10 +67,6 @@ export async function build(projectOpts: ProjectOptions): Promise<void> {
6967
buildNextjsApp(options);
7068
}
7169

72-
if (!containsDotNextDir(projectOpts.sourceDir)) {
73-
throw new Error(`.next folder not found in ${projectOpts.sourceDir}`);
74-
}
75-
7670
// Generate deployable bundle
7771
printHeader("Generating bundle");
7872
buildHelper.initOutputDir(options);
@@ -95,14 +89,7 @@ export async function build(projectOpts: ProjectOptions): Promise<void> {
9589

9690
await createServerBundle(options);
9791

98-
// TODO: drop this copy.
99-
// Copy the .next directory to the output directory so it can be mutated.
100-
cpSync(join(projectOpts.sourceDir, ".next"), join(projectOpts.outputDir, ".next"), { recursive: true });
101-
102-
const projConfig = getConfig(projectOpts);
103-
104-
// TODO: rely on options only.
105-
await bundleServer(projConfig, options);
92+
await bundleServer(options);
10693

10794
if (!projectOpts.skipWranglerConfigCheck) {
10895
await createWranglerConfigIfNotExistent(projectOpts);

packages/cloudflare/src/cli/build/bundle-server.ts

Lines changed: 39 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,10 @@ import path from "node:path";
44
import { fileURLToPath } from "node:url";
55

66
import { Lang, parse } from "@ast-grep/napi";
7-
import type { BuildOptions } from "@opennextjs/aws/build/helper.js";
7+
import { type BuildOptions, getPackagePath } from "@opennextjs/aws/build/helper.js";
88
import { getCrossPlatformPathRegex } from "@opennextjs/aws/utils/regex.js";
99
import { build, Plugin } from "esbuild";
1010

11-
import { Config } from "../config.js";
1211
import { patchOptionalDependencies } from "./patches/ast/optional-deps.js";
1312
import * as patches from "./patches/index.js";
1413
import { normalizePath, patchCodeWithValidations } from "./utils/index.js";
@@ -19,22 +18,25 @@ const packageDistDir = path.join(path.dirname(fileURLToPath(import.meta.url)), "
1918
/**
2019
* Bundle the Open Next server.
2120
*/
22-
export async function bundleServer(config: Config, openNextOptions: BuildOptions): Promise<void> {
23-
patches.copyPackageCliFiles(packageDistDir, config, openNextOptions);
24-
25-
const nextConfigStr =
26-
fs
27-
.readFileSync(path.join(config.paths.output.standaloneApp, "server.js"), "utf8")
28-
?.match(/const nextConfig = ({.+?})\n/)?.[1] ?? {};
21+
export async function bundleServer(buildOpts: BuildOptions): Promise<void> {
22+
patches.copyPackageCliFiles(packageDistDir, buildOpts);
23+
24+
const { appPath, outputDir, monorepoRoot } = buildOpts;
25+
const serverFiles = path.join(
26+
outputDir,
27+
"server-functions/default",
28+
getPackagePath(buildOpts),
29+
".next/required-server-files.json"
30+
);
31+
const nextConfig = JSON.parse(fs.readFileSync(serverFiles, "utf-8")).config;
2932

3033
console.log(`\x1b[35m⚙️ Bundling the OpenNext server...\n\x1b[0m`);
3134

32-
patches.patchWranglerDeps(config);
33-
patches.updateWebpackChunksFile(config);
35+
patches.patchWranglerDeps(buildOpts);
36+
patches.updateWebpackChunksFile(buildOpts);
3437

35-
const { appBuildOutputPath, appPath, outputDir, monorepoRoot } = openNextOptions;
3638
const outputPath = path.join(outputDir, "server-functions", "default");
37-
const packagePath = path.relative(monorepoRoot, appBuildOutputPath);
39+
const packagePath = getPackagePath(buildOpts);
3840
const openNextServer = path.join(outputPath, packagePath, `index.mjs`);
3941
const openNextServerBundle = path.join(outputPath, packagePath, `handler.mjs`);
4042

@@ -45,25 +47,28 @@ export async function bundleServer(config: Config, openNextOptions: BuildOptions
4547
format: "esm",
4648
target: "esnext",
4749
minify: false,
48-
plugins: [createFixRequiresESBuildPlugin(config)],
50+
plugins: [createFixRequiresESBuildPlugin(buildOpts)],
4951
external: ["./middleware/handler.mjs", "caniuse-lite"],
5052
alias: {
5153
// Note: we apply an empty shim to next/dist/compiled/ws because it generates two `eval`s:
5254
// eval("require")("bufferutil");
5355
// eval("require")("utf-8-validate");
54-
"next/dist/compiled/ws": path.join(config.paths.internal.templates, "shims", "empty.js"),
56+
"next/dist/compiled/ws": path.join(buildOpts.outputDir, "cloudflare-templates/shims/empty.js"),
5557
// Note: we apply an empty shim to next/dist/compiled/edge-runtime since (amongst others) it generated the following `eval`:
5658
// eval(getModuleCode)(module, module.exports, throwingRequire, params.context, ...Object.values(params.scopedContext));
5759
// which comes from https://github.com/vercel/edge-runtime/blob/6e96b55f/packages/primitives/src/primitives/load.js#L57-L63
5860
// QUESTION: Why did I encountered this but mhart didn't?
59-
"next/dist/compiled/edge-runtime": path.join(config.paths.internal.templates, "shims", "empty.js"),
61+
"next/dist/compiled/edge-runtime": path.join(
62+
buildOpts.outputDir,
63+
"cloudflare-templates/shims/empty.js"
64+
),
6065
// `@next/env` is a library Next.js uses for loading dotenv files, for obvious reasons we need to stub it here
6166
// source: https://github.com/vercel/next.js/tree/0ac10d79720/packages/next-env
62-
"@next/env": path.join(config.paths.internal.templates, "shims", "env.js"),
67+
"@next/env": path.join(buildOpts.outputDir, "cloudflare-templates/shims/env.js"),
6368
},
6469
define: {
6570
// config file used by Next.js, see: https://github.com/vercel/next.js/blob/68a7128/packages/next/src/build/utils.ts#L2137-L2139
66-
"process.env.__NEXT_PRIVATE_STANDALONE_CONFIG": JSON.stringify(nextConfigStr),
71+
"process.env.__NEXT_PRIVATE_STANDALONE_CONFIG": `${JSON.stringify(nextConfig)}`,
6772
// Next.js tried to access __dirname so we need to define it
6873
__dirname: '""',
6974
// Note: we need the __non_webpack_require__ variable declared as it is used by next-server:
@@ -117,7 +122,7 @@ globalThis.__BUILD_TIMESTAMP_MS__ = ${Date.now()};
117122
},
118123
});
119124

120-
await updateWorkerBundledCode(openNextServerBundle, config, openNextOptions);
125+
await updateWorkerBundledCode(openNextServerBundle, buildOpts);
121126

122127
const isMonorepo = monorepoRoot !== appPath;
123128
if (isMonorepo) {
@@ -127,35 +132,26 @@ globalThis.__BUILD_TIMESTAMP_MS__ = ${Date.now()};
127132
);
128133
}
129134

130-
console.log(`\x1b[35mWorker saved in \`${getOutputWorkerPath(openNextOptions)}\` 🚀\n\x1b[0m`);
135+
console.log(`\x1b[35mWorker saved in \`${getOutputWorkerPath(buildOpts)}\` 🚀\n\x1b[0m`);
131136
}
132137

133138
/**
134-
* This function applies string replacements on the bundled worker code necessary to get it to run in workerd
135-
*
136-
* Needless to say all the logic in this function is something we should avoid as much as possible!
137-
*
138-
* @param workerOutputFile
139-
* @param config
139+
* This function applies patches required for the code to run on workers.
140140
*/
141-
async function updateWorkerBundledCode(
142-
workerOutputFile: string,
143-
config: Config,
144-
openNextOptions: BuildOptions
145-
): Promise<void> {
141+
async function updateWorkerBundledCode(workerOutputFile: string, buildOpts: BuildOptions): Promise<void> {
146142
const code = await readFile(workerOutputFile, "utf8");
147143

148144
const patchedCode = await patchCodeWithValidations(code, [
149145
["require", patches.patchRequire],
150-
["`buildId` function", (code) => patches.patchBuildId(code, config)],
151-
["`loadManifest` function", (code) => patches.patchLoadManifest(code, config)],
152-
["next's require", (code) => patches.inlineNextRequire(code, config)],
153-
["`findDir` function", (code) => patches.patchFindDir(code, config)],
154-
["`evalManifest` function", (code) => patches.inlineEvalManifest(code, config)],
155-
["cacheHandler", (code) => patches.patchCache(code, openNextOptions)],
146+
["`buildId` function", (code) => patches.patchBuildId(code, buildOpts)],
147+
["`loadManifest` function", (code) => patches.patchLoadManifest(code, buildOpts)],
148+
["next's require", (code) => patches.inlineNextRequire(code, buildOpts)],
149+
["`findDir` function", (code) => patches.patchFindDir(code, buildOpts)],
150+
["`evalManifest` function", (code) => patches.inlineEvalManifest(code, buildOpts)],
151+
["cacheHandler", (code) => patches.patchCache(code, buildOpts)],
156152
[
157153
"'require(this.middlewareManifestPath)'",
158-
(code) => patches.inlineMiddlewareManifestRequire(code, config),
154+
(code) => patches.inlineMiddlewareManifestRequire(code, buildOpts),
159155
],
160156
["exception bubbling", patches.patchExceptionBubbling],
161157
["`loadInstrumentationModule` function", patches.patchLoadInstrumentationModule],
@@ -185,15 +181,15 @@ async function updateWorkerBundledCode(
185181
await writeFile(workerOutputFile, bundle.commitEdits(edits));
186182
}
187183

188-
function createFixRequiresESBuildPlugin(config: Config): Plugin {
184+
function createFixRequiresESBuildPlugin(options: BuildOptions): Plugin {
189185
return {
190186
name: "replaceRelative",
191187
setup(build) {
192188
// Note: we (empty) shim require-hook modules as they generate problematic code that uses requires
193189
build.onResolve(
194190
{ filter: getCrossPlatformPathRegex(String.raw`^\./require-hook$`, { escape: false }) },
195191
() => ({
196-
path: path.join(config.paths.internal.templates, "shims", "empty.js"),
192+
path: path.join(options.outputDir, "cloudflare-templates/shims/empty.js"),
197193
})
198194
);
199195
},
@@ -203,9 +199,9 @@ function createFixRequiresESBuildPlugin(config: Config): Plugin {
203199
/**
204200
* Gets the path of the worker.js file generated by the build process
205201
*
206-
* @param openNextOptions the open-next build options
202+
* @param buildOpts the open-next build options
207203
* @returns the path of the worker.js file that the build process generates
208204
*/
209-
export function getOutputWorkerPath(openNextOptions: BuildOptions): string {
210-
return path.join(openNextOptions.outputDir, "worker.js");
205+
export function getOutputWorkerPath(buildOpts: BuildOptions): string {
206+
return path.join(buildOpts.outputDir, "worker.js");
211207
}

packages/cloudflare/src/cli/build/open-next/compile-env-files.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,11 @@ import { extractProjectEnvVars } from "../utils/index.js";
88
/**
99
* Compiles the values extracted from the project's env files to the output directory for use in the worker.
1010
*/
11-
export function compileEnvFiles(options: BuildOptions) {
11+
export function compileEnvFiles(buildOpts: BuildOptions) {
1212
["production", "development", "test"].forEach((mode) =>
1313
fs.appendFileSync(
14-
path.join(options.outputDir, `.env.mjs`),
15-
`export const ${mode} = ${JSON.stringify(extractProjectEnvVars(mode, options))};\n`
14+
path.join(buildOpts.outputDir, `.env.mjs`),
15+
`export const ${mode} = ${JSON.stringify(extractProjectEnvVars(mode, buildOpts))};\n`
1616
)
1717
);
1818
}

packages/cloudflare/src/cli/build/patches/investigated/copy-package-cli-files.ts

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,21 +3,21 @@ import path from "node:path";
33

44
import type { BuildOptions } from "@opennextjs/aws/build/helper.js";
55

6-
import { Config } from "../../../config.js";
76
import { getOutputWorkerPath } from "../../bundle-server.js";
87

98
/**
10-
* Copies the template files present in the cloudflare adapter package into the standalone node_modules folder
9+
* Copies
10+
* - the template files present in the cloudflare adapter package to `.open-next/cloudflare-templates`
11+
* - `worker.js` to `.open-next/`
1112
*/
12-
export function copyPackageCliFiles(packageDistDir: string, config: Config, openNextOptions: BuildOptions) {
13+
export function copyPackageCliFiles(packageDistDir: string, buildOpts: BuildOptions) {
1314
console.log("# copyPackageTemplateFiles");
14-
const sourceDir = path.join(packageDistDir, "cli");
15-
const destinationDir = path.join(config.paths.internal.package, "cli");
15+
const sourceDir = path.join(packageDistDir, "cli/templates");
1616

17+
const destinationDir = path.join(buildOpts.outputDir, "cloudflare-templates");
18+
19+
fs.mkdirSync(destinationDir, { recursive: true });
1720
fs.cpSync(sourceDir, destinationDir, { recursive: true });
1821

19-
fs.copyFileSync(
20-
path.join(packageDistDir, "cli", "templates", "worker.js"),
21-
getOutputWorkerPath(openNextOptions)
22-
);
22+
fs.copyFileSync(path.join(packageDistDir, "cli/templates/worker.js"), getOutputWorkerPath(buildOpts));
2323
}

packages/cloudflare/src/cli/build/patches/investigated/patch-cache.ts

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import path from "node:path";
22

3-
import type { BuildOptions } from "@opennextjs/aws/build/helper.js";
3+
import { type BuildOptions, getPackagePath } from "@opennextjs/aws/build/helper.js";
44

55
import { normalizePath } from "../../utils/index.js";
66

@@ -15,17 +15,17 @@ import { normalizePath } from "../../utils/index.js";
1515
* build-time. Therefore, we have to manually override the default way that the cache handler is
1616
* instantiated with a dynamic require that uses a string literal for the path.
1717
*/
18-
export async function patchCache(code: string, openNextOptions: BuildOptions): Promise<string> {
19-
const { appBuildOutputPath, outputDir, monorepoRoot } = openNextOptions;
18+
export async function patchCache(code: string, buildOpts: BuildOptions): Promise<string> {
19+
const { outputDir } = buildOpts;
2020

2121
// TODO: switch to cache.mjs
22-
const outputPath = path.join(outputDir, "server-functions", "default");
23-
const packagePath = path.relative(monorepoRoot, appBuildOutputPath);
24-
const cacheFile = path.join(outputPath, packagePath, "cache.cjs");
22+
const outputPath = path.join(outputDir, "server-functions/default");
23+
const cacheFile = path.join(outputPath, getPackagePath(buildOpts), "cache.cjs");
2524

2625
return code.replace(
2726
"const { cacheHandler } = this.nextConfig;",
28-
`const cacheHandler = null;
27+
`
28+
const cacheHandler = null;
2929
CacheHandler = require('${normalizePath(cacheFile)}').default;
3030
`
3131
);

packages/cloudflare/src/cli/build/patches/investigated/patch-require.ts

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,5 @@
11
/**
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`
63
*/
74
export function patchRequire(code: string): string {
85
return code.replace(/__require\d?\(/g, "require(").replace(/__require\d?\./g, "require.");

packages/cloudflare/src/cli/build/patches/investigated/update-webpack-chunks-file/index.ts

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,30 @@
11
import { readdirSync, readFileSync, writeFileSync } from "node:fs";
22
import { join } from "node:path";
33

4-
import { Config } from "../../../../config.js";
4+
import { type BuildOptions, getPackagePath } from "@opennextjs/aws/build/helper.js";
5+
56
import { getUpdatedWebpackChunksFileContent } from "./get-updated-webpack-chunks-file-content.js";
67

78
/**
89
* Fixes the webpack-runtime.js file by removing its webpack dynamic requires.
9-
*
10-
* 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`
11-
* so this shows that not everything that's needed to deploy the application is in the output directory...
1210
*/
13-
export async function updateWebpackChunksFile(config: Config) {
11+
export async function updateWebpackChunksFile(buildOpts: BuildOptions) {
1412
console.log("# updateWebpackChunksFile");
15-
const webpackRuntimeFile = join(config.paths.output.standaloneAppServer, "webpack-runtime.js");
13+
14+
const { outputDir } = buildOpts;
15+
16+
const dotNextServerDir = join(
17+
outputDir,
18+
"server-functions/default",
19+
getPackagePath(buildOpts),
20+
".next/server"
21+
);
22+
23+
const webpackRuntimeFile = join(dotNextServerDir, "webpack-runtime.js");
1624

1725
const fileContent = readFileSync(webpackRuntimeFile, "utf-8");
1826

19-
const chunks = readdirSync(join(config.paths.output.standaloneAppServer, "chunks"))
27+
const chunks = readdirSync(join(dotNextServerDir, "chunks"))
2028
.filter((chunk) => /^\d+\.js$/.test(chunk))
2129
.map((chunk) => {
2230
console.log(` - chunk ${chunk}`);

0 commit comments

Comments
 (0)