Skip to content

Commit 9303340

Browse files
committed
Copy templates to standalone app
1 parent cccf357 commit 9303340

File tree

12 files changed

+111
-30
lines changed

12 files changed

+111
-30
lines changed

TODO.txt

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
TODO:
2+
3+
- [] copy the template folders
4+
- [] figure out the assets
5+
- [] dependency graph
6+
7+
DONE:
8+
9+
10+
-----
11+
12+
wrangler alias
13+
14+
-> % cp -r .next/standalone/node_modules/.pnpm/[email protected]_@[email protected][email protected][email protected][email protected]/node_modules/next .next/standalone/next/api/node_modules/
15+
-> % cp -r .next/standalone/node_modules/.pnpm/[email protected]/node_modules/react .next/standalone/next/api/node_modules/
16+
17+
find -L . -type l -ls
18+
19+
symlinks -rv
20+
21+
----
22+
23+
Need to add "node-url": "npm:url@^0.11.4"
24+
25+
---
26+

builder/src/build/build-worker/index.ts

Lines changed: 28 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -3,20 +3,15 @@ import { build, Plugin } from "esbuild";
33
import { readdirSync, readFileSync, writeFileSync } from "node:fs";
44
import { cp, readFile, writeFile } from "node:fs/promises";
55

6-
import { resolve } from "node:path";
7-
86
import { patchRequire } from "./patches/investigated/patchRequire";
97
import { patchUrl } from "./patches/investigated/patchUrl";
8+
import { copyTemplates } from "./patches/investigated/copyTemplates";
109

1110
import { patchReadFile } from "./patches/to-investigate/patchReadFile";
1211
import { patchFindDir } from "./patches/to-investigate/patchFindDir";
1312
import { inlineNextRequire } from "./patches/to-investigate/inlineNextRequire";
1413
import { inlineEvalManifest } from "./patches/to-investigate/inlineEvalManifest";
1514

16-
import * as url from "url";
17-
18-
const __dirname = url.fileURLToPath(new URL(".", import.meta.url));
19-
2015
/**
2116
* Using the Next.js build output in the `.next` directory builds a workerd compatible output
2217
*
@@ -25,11 +20,12 @@ const __dirname = url.fileURLToPath(new URL(".", import.meta.url));
2520
*/
2621
export async function buildWorker(
2722
outputDir: string,
28-
nextjsAppPaths: NextjsAppPaths
23+
nextjsAppPaths: NextjsAppPaths,
24+
templateSrcDir: string
2925
): Promise<void> {
30-
const repoRoot = resolve(`${__dirname}/../..`);
26+
const templateDir = copyTemplates(templateSrcDir, nextjsAppPaths);
3127

32-
const workerEntrypoint = `${__dirname}/templates/worker.ts`;
28+
const workerEntrypoint = `${templateDir}/worker.ts`;
3329
const workerOutputFile = `${outputDir}/index.mjs`;
3430
const nextConfigStr =
3531
readFileSync(nextjsAppPaths.standaloneAppDir + "/server.js", "utf8")?.match(
@@ -44,27 +40,27 @@ export async function buildWorker(
4440
format: "esm",
4541
target: "esnext",
4642
minify: false,
47-
plugins: [fixRequiresESBuildPlugin],
43+
plugins: [createFixRequiresESBuildPlugin(templateDir)],
4844
alias: {
4945
// Note: we apply an empty shim to next/dist/compiled/ws because it generates two `eval`s:
5046
// eval("require")("bufferutil");
5147
// eval("require")("utf-8-validate");
52-
"next/dist/compiled/ws": `${__dirname}/templates/shims/empty.ts`,
48+
"next/dist/compiled/ws": `${templateDir}/shims/empty.ts`,
5349
// Note: we apply an empty shim to next/dist/compiled/edge-runtime since (amongst others) it generated the following `eval`:
5450
// eval(getModuleCode)(module, module.exports, throwingRequire, params.context, ...Object.values(params.scopedContext));
5551
// which comes from https://github.com/vercel/edge-runtime/blob/6e96b55f/packages/primitives/src/primitives/load.js#L57-L63
5652
// QUESTION: Why did I encountered this but mhart didn't?
57-
"next/dist/compiled/edge-runtime": `${__dirname}/templates/shims/empty.ts`,
53+
"next/dist/compiled/edge-runtime": `${templateDir}/shims/empty.ts`,
5854
// Note: we need to stub out `@opentelemetry/api` as that is problematic and doesn't get properly bundled...
59-
critters: `${__dirname}/templates/shims/empty.ts`,
55+
critters: `${templateDir}/shims/empty.ts`,
6056
// Note: we need to stub out `@opentelemetry/api` as it is problematic
6157
// IMPORTANT: we shim @opentelemetry/api to the throwing shim so that it will throw right away, this is so that we throw inside the
6258
// try block here: https://github.com/vercel/next.js/blob/9e8266a7/packages/next/src/server/lib/trace/tracer.ts#L27-L31
6359
// causing the code to require the 'next/dist/compiled/@opentelemetry/api' module instead (which properly works)
64-
"@opentelemetry/api": `${__dirname}/templates/shims/throw.ts`,
60+
"@opentelemetry/api": `${templateDir}/shims/throw.ts`,
6561
// `@next/env` is a library Next.js uses for loading dotenv files, for obvious reasons we need to stub it here
6662
// source: https://github.com/vercel/next.js/tree/0ac10d79720/packages/next-env
67-
"@next/env": `${__dirname}/templates/shims/env.ts`,
63+
"@next/env": `${templateDir}/shims/env.ts`,
6864
},
6965
define: {
7066
// config file used by Next.js, see: https://github.com/vercel/next.js/blob/68a7128/packages/next/src/build/utils.ts#L2137-L2139
@@ -147,13 +143,19 @@ async function updateWorkerBundledCode(
147143
* so this shows that not everything that's needed to deploy the application is in the output directory...
148144
*/
149145
async function updateWebpackChunksFile(nextjsAppPaths: NextjsAppPaths) {
146+
console.log("# updateWebpackChunksFile");
150147
const webpackRuntimeFile = `${nextjsAppPaths.standaloneAppServerDir}/webpack-runtime.js`;
151148

149+
console.log({ webpackRuntimeFile });
150+
152151
const fileContent = readFileSync(webpackRuntimeFile, "utf-8");
153152

154153
const chunks = readdirSync(`${nextjsAppPaths.standaloneAppServerDir}/chunks`)
155154
.filter((chunk) => /^\d+\.js$/.test(chunk))
156-
.map((chunk) => chunk.replace(/\.js$/, ""));
155+
.map((chunk) => {
156+
console.log(` - chunk ${chunk}`);
157+
return chunk.replace(/\.js$/, "");
158+
});
157159

158160
const updatedFileContent = fileContent.replace(
159161
"__webpack_require__.f.require = (chunkId, promises) => {",
@@ -175,12 +177,14 @@ async function updateWebpackChunksFile(nextjsAppPaths: NextjsAppPaths) {
175177
writeFileSync(webpackRuntimeFile, updatedFileContent);
176178
}
177179

178-
const fixRequiresESBuildPlugin: Plugin = {
179-
name: "replaceRelative",
180-
setup(build) {
181-
// Note: we (empty) shim require-hook modules as they generate problematic code that uses requires
182-
build.onResolve({ filter: /^\.\/require-hook$/ }, (args) => ({
183-
path: `${__dirname}/templates/shims/empty.ts`,
184-
}));
185-
},
180+
function createFixRequiresESBuildPlugin(templateDir: string): Plugin {
181+
return {
182+
name: "replaceRelative",
183+
setup(build) {
184+
// Note: we (empty) shim require-hook modules as they generate problematic code that uses requires
185+
build.onResolve({ filter: /^\.\/require-hook$/ }, (args) => ({
186+
path: `${templateDir}/shims/empty.ts`,
187+
}));
188+
},
189+
};
186190
};
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import path from "node:path";
2+
import { NextjsAppPaths } from "builder/src/nextjsPaths";
3+
import { cpSync, mkdirSync } from "node:fs";
4+
5+
/**
6+
* Copy templates in the standalone folder.
7+
*
8+
* We need to have the template files locally to referenced package paths
9+
* to resolve to files in the the node_module of the standalone app.=
10+
*/
11+
export function copyTemplates(srcDir: string, nextjsAppPaths: NextjsAppPaths) {
12+
console.log("# copyTemplates");
13+
const destDir = path.join(
14+
nextjsAppPaths.standaloneAppDir,
15+
"node_modules/cf/templates"
16+
);
17+
18+
cpSync(srcDir, destDir, { recursive: true });
19+
return destDir;
20+
}

builder/src/build/build-worker/patches/investigated/patchRequire.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
* James on Aug 29: `module.createRequire()` is planned.
66
*/
77
export function patchRequire(code: string): string {
8+
console.log("# patchRequire");
89
return code
910
.replace(/__require\d?\(/g, "require(")
1011
.replace(/__require\d?\./g, "require.");

builder/src/build/build-worker/patches/investigated/patchUrl.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
* Hopefully this should not be necessary after this unenv PR lands: https://github.com/unjs/unenv/pull/292
66
*/
77
export function patchUrl(code: string): string {
8+
console.log("# patchUrl");
89
return code.replace(
910
/ ([a-zA-Z0-9_]+) = require\("url"\);/g,
1011
` $1 = require("url");

builder/src/build/build-worker/patches/to-investigate/inlineEvalManifest.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ export function inlineEvalManifest(
1212
code: string,
1313
nextjsAppPaths: NextjsAppPaths
1414
): string {
15+
console.log("# inlineEvalManifest");
1516
const manifestJss = globSync(
1617
`${nextjsAppPaths.standaloneAppDotNextDir}/**/*_client-reference-manifest.js`
1718
).map((file) => file.replace(`${nextjsAppPaths.standaloneAppDir}/`, ""));

builder/src/build/build-worker/patches/to-investigate/inlineNextRequire.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ export function inlineNextRequire(
99
code: string,
1010
nextjsAppPaths: NextjsAppPaths
1111
) {
12+
console.log("# inlineNextRequire");
1213
const pagesManifestFile = `${nextjsAppPaths.standaloneAppServerDir}/pages-manifest.json`;
1314
const appPathsManifestFile = `${nextjsAppPaths.standaloneAppServerDir}/app-paths-manifest.json`;
1415

builder/src/build/build-worker/patches/to-investigate/patchFindDir.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ export function patchFindDir(
1111
code: string,
1212
nextjsAppPaths: NextjsAppPaths
1313
): string {
14+
console.log("# patchFindDir");
1415
return code.replace(
1516
"function findDir(dir, name) {",
1617
`function findDir(dir, name) {

builder/src/build/build-worker/patches/to-investigate/patchReadFile.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ export function patchReadFile(
66
code: string,
77
nextjsAppPaths: NextjsAppPaths
88
): string {
9+
console.log("# patchReadFile");
910
// The next-server code gets the buildId from the filesystem, resulting in a `[unenv] fs.readFileSync is not implemented yet!` error
1011
// so we add an early return to the `getBuildId` function so that the `readyFileSync` is never encountered
1112
// (source: https://github.com/vercel/next.js/blob/15aeb92efb34c09a36/packages/next/src/server/next-server.ts#L438-L451)

builder/src/build/index.ts

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@ import { rm } from "node:fs/promises";
22
import { buildNextjsApp } from "./build-next-app";
33
import { buildWorker } from "./build-worker";
44
import { getNextjsAppPaths } from "../nextjsPaths";
5+
import path from "node:path";
6+
import { fileURLToPath } from "node:url";
7+
import { cpSync, rmSync } from "node:fs";
58

69
/**
710
* Builds the application in a format that can be passed to workerd
@@ -17,14 +20,32 @@ export async function build(
1720
opts: BuildOptions
1821
): Promise<void> {
1922
if (!opts.skipBuild) {
23+
// Build the next app and save a copy in .next.save
2024
buildNextjsApp(inputNextAppDir);
25+
cpSync(`${inputNextAppDir}/.next`, `${inputNextAppDir}/save.next`, {
26+
recursive: true,
27+
});
28+
} else {
29+
// Skip the next build and restore the copy from .next.save
30+
rmSync(`${inputNextAppDir}/.next`, { recursive: true, force: true });
31+
cpSync(`${inputNextAppDir}/save.next`, `${inputNextAppDir}/.next`, {
32+
recursive: true,
33+
});
2134
}
2235

2336
const outputDir = `${opts.outputDir ?? inputNextAppDir}/.worker-next`;
24-
cleanDirectory(outputDir);
37+
await cleanDirectory(outputDir);
2538

2639
const nextjsAppPaths = getNextjsAppPaths(inputNextAppDir);
27-
await buildWorker(outputDir, nextjsAppPaths);
40+
41+
const templateDir = path.join(
42+
path.dirname(fileURLToPath(import.meta.url)),
43+
"templates"
44+
);
45+
46+
console.log({ outputDir, nextjsAppPaths, templateDir });
47+
48+
await buildWorker(outputDir, nextjsAppPaths, templateDir);
2849
}
2950

3051
type BuildOptions = {

0 commit comments

Comments
 (0)