diff --git a/.prettierrc b/.prettierrc index b7976e08c..0adf1369d 100644 --- a/.prettierrc +++ b/.prettierrc @@ -1,7 +1,8 @@ { - "printWidth": 80, - "singleQuote": false, - "semi": true, - "useTabs": true, - "trailingComma": "es5" + "printWidth": 110, + "singleQuote": false, + "semi": true, + "useTabs": false, + "tabWidth": 2, + "trailingComma": "es5" } diff --git a/.vscode/settings.json b/.vscode/settings.json index 1295ee63d..ad92582bd 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,3 +1,3 @@ { - "editor.formatOnSave": true + "editor.formatOnSave": true } diff --git a/TODO.md b/TODO.md index 6749b79f2..1e96a5f06 100644 --- a/TODO.md +++ b/TODO.md @@ -19,10 +19,10 @@ DONE: ```typescript /** @type {import('next').NextConfig} */ const nextConfig = { - output: "standalone", - experimental: { - serverMinification: false, - }, + output: "standalone", + experimental: { + serverMinification: false, + }, }; export default nextConfig; @@ -70,3 +70,42 @@ DONE: ```sh SKIP_NEXT_APP_BUILD=1 node /path/to/poc-next/builder/dist/index.mjs && npx wrangler dev ``` + +## Open next [example app](https://github.com/sst/open-next/tree/main/example) + +Changes: + +- App: Patch the next.config.js +- App: Update package.json + +```text + "scripts": { + "dev": "next dev", + ... + }, + "dependencies": { + "next": "^14.2.11", + "next-auth": "latest", + ... + }, + "devDependencies": { + "node-url": "npm:url@^0.11.4", + "wrangler": "^3.77.0" + ... + } +``` + +- wrangler, update bundle.ts (based on 3.76.0) + +```js + //l 354 + conditions: [], + platform: "node", +``` + +The conditions (`export const BUILD_CONDITIONS = ["workerd", "worker", "browser"];`) +would pull browser files that are not traced by nft. + +- Build the app + +- Use the updated wrangler `pnpm --filter wrangler start dev -c /path/to/open-next/example/wrangler.toml` diff --git a/builder/package.json b/builder/package.json index f42b4019b..60d37121e 100644 --- a/builder/package.json +++ b/builder/package.json @@ -1,19 +1,19 @@ { - "name": "builder", - "scripts": { - "build": "tsup", - "build:watch": "tsup --watch src" - }, - "bin": "dist/index.mjs", - "files": [ - "dist" - ], - "devDependencies": { - "@types/node": "^22.2.0", - "esbuild": "^0.23.0", - "glob": "^11.0.0", - "next": "14.2.5", - "tsup": "^8.2.4", - "typescript": "^5.5.4" - } + "name": "builder", + "scripts": { + "build": "tsup", + "build:watch": "tsup --watch src" + }, + "bin": "dist/index.mjs", + "files": [ + "dist" + ], + "devDependencies": { + "@types/node": "^22.2.0", + "esbuild": "^0.23.0", + "glob": "^11.0.0", + "next": "14.2.5", + "tsup": "^8.2.4", + "typescript": "^5.5.4" + } } diff --git a/builder/src/args.ts b/builder/src/args.ts index dda1b21fe..440335702 100644 --- a/builder/src/args.ts +++ b/builder/src/args.ts @@ -3,61 +3,51 @@ import { parseArgs } from "node:util"; import { resolve } from "node:path"; export function getArgs(): { - skipBuild: boolean; - outputDir?: string; + skipBuild: boolean; + outputDir?: string; } { - const { - values: { skipBuild, output }, - } = parseArgs({ - options: { - skipBuild: { - type: "boolean", - short: "s", - default: false, - }, - output: { - type: "string", - short: "o", - }, - }, - allowPositionals: false, - }); + const { + values: { skipBuild, output }, + } = parseArgs({ + options: { + skipBuild: { + type: "boolean", + short: "s", + default: false, + }, + output: { + type: "string", + short: "o", + }, + }, + allowPositionals: false, + }); - const outputDir = output ? resolve(output) : undefined; + const outputDir = output ? resolve(output) : undefined; - if (outputDir) { - assertDirArg(outputDir, "output", true); - } + if (outputDir) { + assertDirArg(outputDir, "output", true); + } - return { - outputDir, - skipBuild: - skipBuild || - ["1", "true", "yes"].includes(String(process.env.SKIP_NEXT_APP_BUILD)), - }; + return { + outputDir, + skipBuild: skipBuild || ["1", "true", "yes"].includes(String(process.env.SKIP_NEXT_APP_BUILD)), + }; } function assertDirArg(path: string, argName?: string, make?: boolean) { - let dirStats: Stats; - try { - dirStats = statSync(path); - } catch { - if (!make) { - throw new Error( - `Error: the provided${ - argName ? ` "${argName}"` : "" - } input is not a valid path` - ); - } - mkdirSync(path); - return; - } + let dirStats: Stats; + try { + dirStats = statSync(path); + } catch { + if (!make) { + throw new Error(`Error: the provided${argName ? ` "${argName}"` : ""} input is not a valid path`); + } + mkdirSync(path); + return; + } - if (!dirStats.isDirectory()) { - throw new Error( - `Error: the provided${ - argName ? ` "${argName}"` : "" - } input is not a directory` - ); - } + if (!dirStats.isDirectory()) { + throw new Error(`Error: the provided${argName ? ` "${argName}"` : ""} input is not a directory`); + } } diff --git a/builder/src/build/build-next-app.ts b/builder/src/build/build-next-app.ts index 309a9db08..3097e83ed 100644 --- a/builder/src/build/build-next-app.ts +++ b/builder/src/build/build-next-app.ts @@ -8,26 +8,24 @@ import { execSync } from "node:child_process"; * @param nextAppDir the directory of the app to build */ export function buildNextjsApp(nextAppDir: string): void { - runNextBuildCommand("pnpm", nextAppDir); + runNextBuildCommand("pnpm", nextAppDir); } // equivalent to: https://github.com/sst/open-next/blob/f61b0e94/packages/open-next/src/build.ts#L175-L186 function runNextBuildCommand( - // let's keep things simple and just support only pnpm for now - packager: "pnpm" /*"npm" | "yarn" | "pnpm" | "bun"*/, - nextAppDir: string + // let's keep things simple and just support only pnpm for now + packager: "pnpm" /*"npm" | "yarn" | "pnpm" | "bun"*/, + nextAppDir: string ) { - const command = ["bun", "npm"].includes(packager) - ? `${packager} next build` - : `${packager} next build`; - execSync(command, { - stdio: "inherit", - cwd: nextAppDir, - env: { - ...process.env, - // equivalent to: https://github.com/sst/open-next/blob/f61b0e9/packages/open-next/src/build.ts#L168-L173 - // Equivalent to setting `output: "standalone"` in next.config.js - NEXT_PRIVATE_STANDALONE: "true", - }, - }); + const command = ["bun", "npm"].includes(packager) ? `${packager} next build` : `${packager} next build`; + execSync(command, { + stdio: "inherit", + cwd: nextAppDir, + env: { + ...process.env, + // equivalent to: https://github.com/sst/open-next/blob/f61b0e9/packages/open-next/src/build.ts#L168-L173 + // Equivalent to setting `output: "standalone"` in next.config.js + NEXT_PRIVATE_STANDALONE: "true", + }, + }); } diff --git a/builder/src/build/build-worker/index.ts b/builder/src/build/build-worker/index.ts index 5ff7b6320..6e0a572db 100644 --- a/builder/src/build/build-worker/index.ts +++ b/builder/src/build/build-worker/index.ts @@ -20,75 +20,74 @@ import { patchWranglerDeps } from "./patches/to-investigate/wrangler-deps"; * @param nextjsAppPaths */ export async function buildWorker( - outputDir: string, - nextjsAppPaths: NextjsAppPaths, - templateSrcDir: string + outputDir: string, + nextjsAppPaths: NextjsAppPaths, + templateSrcDir: string ): Promise { - const templateDir = copyTemplates(templateSrcDir, nextjsAppPaths); - - const workerEntrypoint = `${templateDir}/worker.ts`; - const workerOutputFile = `${outputDir}/index.mjs`; - const nextConfigStr = - readFileSync(nextjsAppPaths.standaloneAppDir + "/server.js", "utf8")?.match( - /const nextConfig = ({.+?})\n/ - )?.[1] ?? {}; - - console.log(`\x1b[35m⚙️ Bundling the worker file...\n\x1b[0m`); - - patchWranglerDeps(nextjsAppPaths); - updateWebpackChunksFile(nextjsAppPaths); - - await build({ - entryPoints: [workerEntrypoint], - bundle: true, - outfile: workerOutputFile, - format: "esm", - target: "esnext", - minify: false, - plugins: [createFixRequiresESBuildPlugin(templateDir)], - alias: { - // Note: we apply an empty shim to next/dist/compiled/ws because it generates two `eval`s: - // eval("require")("bufferutil"); - // eval("require")("utf-8-validate"); - "next/dist/compiled/ws": `${templateDir}/shims/empty.ts`, - // Note: we apply an empty shim to next/dist/compiled/edge-runtime since (amongst others) it generated the following `eval`: - // eval(getModuleCode)(module, module.exports, throwingRequire, params.context, ...Object.values(params.scopedContext)); - // which comes from https://github.com/vercel/edge-runtime/blob/6e96b55f/packages/primitives/src/primitives/load.js#L57-L63 - // QUESTION: Why did I encountered this but mhart didn't? - "next/dist/compiled/edge-runtime": `${templateDir}/shims/empty.ts`, - // `@next/env` is a library Next.js uses for loading dotenv files, for obvious reasons we need to stub it here - // source: https://github.com/vercel/next.js/tree/0ac10d79720/packages/next-env - "@next/env": `${templateDir}/shims/env.ts`, - }, - define: { - // config file used by Next.js, see: https://github.com/vercel/next.js/blob/68a7128/packages/next/src/build/utils.ts#L2137-L2139 - "process.env.__NEXT_PRIVATE_STANDALONE_CONFIG": - JSON.stringify(nextConfigStr), - // Next.js tried to access __dirname so we need to define it - __dirname: '""', - // Note: we need the __non_webpack_require__ variable declared as it is used by next-server: - // https://github.com/vercel/next.js/blob/be0c3283/packages/next/src/server/next-server.ts#L116-L119 - __non_webpack_require__: "require", - // The next.js server can run in minimal mode: https://github.com/vercel/next.js/blob/aa90fe9bb/packages/next/src/server/base-server.ts#L510-L511 - // this avoids some extra (/problematic) `require` calls, such as here: https://github.com/vercel/next.js/blob/aa90fe9bb/packages/next/src/server/next-server.ts#L1259 - // that's wht we enable it - "process.env.NEXT_PRIVATE_MINIMAL_MODE": "true", - // Ask mhart if he can explain why the `define`s below are necessary - "process.env.NEXT_RUNTIME": '"nodejs"', - "process.env.NODE_ENV": '"production"', - "process.env.NEXT_MINIMAL": "true", - }, - // We need to set platform to node so that esbuild doesn't complain about the node imports - platform: "node", - banner: { - js: ` + const templateDir = copyTemplates(templateSrcDir, nextjsAppPaths); + + const workerEntrypoint = `${templateDir}/worker.ts`; + const workerOutputFile = `${outputDir}/index.mjs`; + const nextConfigStr = + readFileSync(nextjsAppPaths.standaloneAppDir + "/server.js", "utf8")?.match( + /const nextConfig = ({.+?})\n/ + )?.[1] ?? {}; + + console.log(`\x1b[35m⚙️ Bundling the worker file...\n\x1b[0m`); + + patchWranglerDeps(nextjsAppPaths); + updateWebpackChunksFile(nextjsAppPaths); + + await build({ + entryPoints: [workerEntrypoint], + bundle: true, + outfile: workerOutputFile, + format: "esm", + target: "esnext", + minify: false, + plugins: [createFixRequiresESBuildPlugin(templateDir)], + alias: { + // Note: we apply an empty shim to next/dist/compiled/ws because it generates two `eval`s: + // eval("require")("bufferutil"); + // eval("require")("utf-8-validate"); + "next/dist/compiled/ws": `${templateDir}/shims/empty.ts`, + // Note: we apply an empty shim to next/dist/compiled/edge-runtime since (amongst others) it generated the following `eval`: + // eval(getModuleCode)(module, module.exports, throwingRequire, params.context, ...Object.values(params.scopedContext)); + // which comes from https://github.com/vercel/edge-runtime/blob/6e96b55f/packages/primitives/src/primitives/load.js#L57-L63 + // QUESTION: Why did I encountered this but mhart didn't? + "next/dist/compiled/edge-runtime": `${templateDir}/shims/empty.ts`, + // `@next/env` is a library Next.js uses for loading dotenv files, for obvious reasons we need to stub it here + // source: https://github.com/vercel/next.js/tree/0ac10d79720/packages/next-env + "@next/env": `${templateDir}/shims/env.ts`, + }, + define: { + // config file used by Next.js, see: https://github.com/vercel/next.js/blob/68a7128/packages/next/src/build/utils.ts#L2137-L2139 + "process.env.__NEXT_PRIVATE_STANDALONE_CONFIG": JSON.stringify(nextConfigStr), + // Next.js tried to access __dirname so we need to define it + __dirname: '""', + // Note: we need the __non_webpack_require__ variable declared as it is used by next-server: + // https://github.com/vercel/next.js/blob/be0c3283/packages/next/src/server/next-server.ts#L116-L119 + __non_webpack_require__: "require", + // The next.js server can run in minimal mode: https://github.com/vercel/next.js/blob/aa90fe9bb/packages/next/src/server/base-server.ts#L510-L511 + // this avoids some extra (/problematic) `require` calls, such as here: https://github.com/vercel/next.js/blob/aa90fe9bb/packages/next/src/server/next-server.ts#L1259 + // that's wht we enable it + "process.env.NEXT_PRIVATE_MINIMAL_MODE": "true", + // Ask mhart if he can explain why the `define`s below are necessary + "process.env.NEXT_RUNTIME": '"nodejs"', + "process.env.NODE_ENV": '"production"', + "process.env.NEXT_MINIMAL": "true", + }, + // We need to set platform to node so that esbuild doesn't complain about the node imports + platform: "node", + banner: { + js: ` ${ - /* + /* `__dirname` is used by unbundled js files (which don't inherit the `__dirname` present in the `define` field) so we also need to set it on the global scope Note: this was hit in the `next/dist/compiled/@opentelemetry/api` module */ "" - } + } globalThis.__dirname ??= ""; // Do not crash on cache not supported @@ -113,21 +112,17 @@ const CustomRequest = class extends globalThis.Request { globalThis.Request = CustomRequest; Request = globalThis.Request; `, - }, - }); + }, + }); - await updateWorkerBundledCode(workerOutputFile, nextjsAppPaths); + await updateWorkerBundledCode(workerOutputFile, nextjsAppPaths); - console.log(`\x1b[35m⚙️ Copying asset files...\n\x1b[0m`); - await cp( - `${nextjsAppPaths.dotNextDir}/static`, - `${outputDir}/assets/_next/static`, - { - recursive: true, - } - ); + console.log(`\x1b[35m⚙️ Copying asset files...\n\x1b[0m`); + await cp(`${nextjsAppPaths.dotNextDir}/static`, `${outputDir}/assets/_next/static`, { + recursive: true, + }); - console.log(`\x1b[35mWorker saved in \`${workerOutputFile}\` 🚀\n\x1b[0m`); + console.log(`\x1b[35mWorker saved in \`${workerOutputFile}\` 🚀\n\x1b[0m`); } /** @@ -139,21 +134,21 @@ Request = globalThis.Request; * @param nextjsAppPaths */ async function updateWorkerBundledCode( - workerOutputFile: string, - nextjsAppPaths: NextjsAppPaths + workerOutputFile: string, + nextjsAppPaths: NextjsAppPaths ): Promise { - const originalCode = await readFile(workerOutputFile, "utf8"); + const originalCode = await readFile(workerOutputFile, "utf8"); - let patchedCode = originalCode; + let patchedCode = originalCode; - patchedCode = patchRequire(patchedCode); - patchedCode = patchReadFile(patchedCode, nextjsAppPaths); - patchedCode = patchUrl(patchedCode); - patchedCode = inlineNextRequire(patchedCode, nextjsAppPaths); - patchedCode = patchFindDir(patchedCode, nextjsAppPaths); - patchedCode = inlineEvalManifest(patchedCode, nextjsAppPaths); + patchedCode = patchRequire(patchedCode); + patchedCode = patchReadFile(patchedCode, nextjsAppPaths); + patchedCode = patchUrl(patchedCode); + patchedCode = inlineNextRequire(patchedCode, nextjsAppPaths); + patchedCode = patchFindDir(patchedCode, nextjsAppPaths); + patchedCode = inlineEvalManifest(patchedCode, nextjsAppPaths); - await writeFile(workerOutputFile, patchedCode); + await writeFile(workerOutputFile, patchedCode); } /** @@ -165,51 +160,51 @@ async function updateWorkerBundledCode( * so this shows that not everything that's needed to deploy the application is in the output directory... */ async function updateWebpackChunksFile(nextjsAppPaths: NextjsAppPaths) { - console.log("# updateWebpackChunksFile"); - const webpackRuntimeFile = `${nextjsAppPaths.standaloneAppServerDir}/webpack-runtime.js`; + console.log("# updateWebpackChunksFile"); + const webpackRuntimeFile = `${nextjsAppPaths.standaloneAppServerDir}/webpack-runtime.js`; - console.log({ webpackRuntimeFile }); + console.log({ webpackRuntimeFile }); - const fileContent = readFileSync(webpackRuntimeFile, "utf-8"); + const fileContent = readFileSync(webpackRuntimeFile, "utf-8"); - const chunks = readdirSync(`${nextjsAppPaths.standaloneAppServerDir}/chunks`) - .filter((chunk) => /^\d+\.js$/.test(chunk)) - .map((chunk) => { - console.log(` - chunk ${chunk}`); - return chunk.replace(/\.js$/, ""); - }); + const chunks = readdirSync(`${nextjsAppPaths.standaloneAppServerDir}/chunks`) + .filter((chunk) => /^\d+\.js$/.test(chunk)) + .map((chunk) => { + console.log(` - chunk ${chunk}`); + return chunk.replace(/\.js$/, ""); + }); - const updatedFileContent = fileContent.replace( - "__webpack_require__.f.require = (chunkId, promises) => {", - `__webpack_require__.f.require = (chunkId, promises) => { + const updatedFileContent = fileContent.replace( + "__webpack_require__.f.require = (chunkId, promises) => {", + `__webpack_require__.f.require = (chunkId, promises) => { if (installedChunks[chunkId]) return; ${chunks - .map( - (chunk) => ` + .map( + (chunk) => ` if (chunkId === ${chunk}) { installChunk(require("./chunks/${chunk}.js")); return; } ` - ) - .join("\n")} + ) + .join("\n")} ` - ); + ); - writeFileSync(webpackRuntimeFile, updatedFileContent); + writeFileSync(webpackRuntimeFile, updatedFileContent); } function createFixRequiresESBuildPlugin(templateDir: string): Plugin { - return { - name: "replaceRelative", - setup(build) { - // Note: we (empty) shim require-hook modules as they generate problematic code that uses requires - build.onResolve({ filter: /^\.\/require-hook$/ }, (args) => ({ - path: `${templateDir}/shims/empty.ts`, - })); - build.onResolve({ filter: /\.\/lib\/node-fs-methods$/ }, (args) => ({ - path: `${templateDir}/shims/node-fs.ts`, - })); - }, - }; + return { + name: "replaceRelative", + setup(build) { + // Note: we (empty) shim require-hook modules as they generate problematic code that uses requires + build.onResolve({ filter: /^\.\/require-hook$/ }, (args) => ({ + path: `${templateDir}/shims/empty.ts`, + })); + build.onResolve({ filter: /\.\/lib\/node-fs-methods$/ }, (args) => ({ + path: `${templateDir}/shims/node-fs.ts`, + })); + }, + }; } diff --git a/builder/src/build/build-worker/patches/investigated/copy-templates.ts b/builder/src/build/build-worker/patches/investigated/copy-templates.ts index 93424ca20..fb0bb2083 100644 --- a/builder/src/build/build-worker/patches/investigated/copy-templates.ts +++ b/builder/src/build/build-worker/patches/investigated/copy-templates.ts @@ -9,12 +9,9 @@ import { cpSync } from "node:fs"; * to resolve to files in the the node_module of the standalone app.= */ export function copyTemplates(srcDir: string, nextjsAppPaths: NextjsAppPaths) { - console.log("# copyTemplates"); - const destDir = path.join( - nextjsAppPaths.standaloneAppDir, - "node_modules/cf/templates" - ); + console.log("# copyTemplates"); + const destDir = path.join(nextjsAppPaths.standaloneAppDir, "node_modules/cf/templates"); - cpSync(srcDir, destDir, { recursive: true }); - return destDir; + cpSync(srcDir, destDir, { recursive: true }); + return destDir; } diff --git a/builder/src/build/build-worker/patches/investigated/patch-require.ts b/builder/src/build/build-worker/patches/investigated/patch-require.ts index d9a7c0e92..3771b0883 100644 --- a/builder/src/build/build-worker/patches/investigated/patch-require.ts +++ b/builder/src/build/build-worker/patches/investigated/patch-require.ts @@ -5,8 +5,6 @@ * James on Aug 29: `module.createRequire()` is planned. */ export function patchRequire(code: string): string { - console.log("# patchRequire"); - return code - .replace(/__require\d?\(/g, "require(") - .replace(/__require\d?\./g, "require."); + console.log("# patchRequire"); + return code.replace(/__require\d?\(/g, "require(").replace(/__require\d?\./g, "require."); } diff --git a/builder/src/build/build-worker/patches/investigated/patch-url.ts b/builder/src/build/build-worker/patches/investigated/patch-url.ts index 09d86eab8..ca2bc77f8 100644 --- a/builder/src/build/build-worker/patches/investigated/patch-url.ts +++ b/builder/src/build/build-worker/patches/investigated/patch-url.ts @@ -5,10 +5,10 @@ * Hopefully this should not be necessary after this unenv PR lands: https://github.com/unjs/unenv/pull/292 */ export function patchUrl(code: string): string { - console.log("# patchUrl"); - return code.replace( - / ([a-zA-Z0-9_]+) = require\("url"\);/g, - ` $1 = require("url"); + console.log("# patchUrl"); + return code.replace( + / ([a-zA-Z0-9_]+) = require\("url"\);/g, + ` $1 = require("url"); const nodeUrl = require("node-url"); $1.parse = nodeUrl.parse.bind(nodeUrl); $1.format = nodeUrl.format.bind(nodeUrl); @@ -17,5 +17,5 @@ export function patchUrl(code: string): string { return new URL("file://" + path); } ` - ); + ); } diff --git a/builder/src/build/build-worker/patches/to-investigate/inline-eval-manifest.ts b/builder/src/build/build-worker/patches/to-investigate/inline-eval-manifest.ts index d49794d43..58d8ddebb 100644 --- a/builder/src/build/build-worker/patches/to-investigate/inline-eval-manifest.ts +++ b/builder/src/build/build-worker/patches/to-investigate/inline-eval-manifest.ts @@ -8,39 +8,33 @@ import { NextjsAppPaths } from "../../../../nextjs-paths"; * Note: we could/should probably just patch readFileSync here or something, but here the issue is that after the readFileSync call * 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) */ -export function inlineEvalManifest( - code: string, - nextjsAppPaths: NextjsAppPaths -): string { - console.log("# inlineEvalManifest"); - const manifestJss = globSync( - `${nextjsAppPaths.standaloneAppDotNextDir}/**/*_client-reference-manifest.js` - ).map((file) => file.replace(`${nextjsAppPaths.standaloneAppDir}/`, "")); - return code.replace( - /function evalManifest\((.+?), .+?\) {/, - `$& +export function inlineEvalManifest(code: string, nextjsAppPaths: NextjsAppPaths): string { + console.log("# inlineEvalManifest"); + const manifestJss = globSync( + `${nextjsAppPaths.standaloneAppDotNextDir}/**/*_client-reference-manifest.js` + ).map((file) => file.replace(`${nextjsAppPaths.standaloneAppDir}/`, "")); + return code.replace( + /function evalManifest\((.+?), .+?\) {/, + `$& ${manifestJss - .map( - (manifestJs) => ` + .map( + (manifestJs) => ` if ($1.endsWith("${manifestJs}")) { require("${nextjsAppPaths.standaloneAppDir}/${manifestJs}"); return { __RSC_MANIFEST: { "${manifestJs - .replace(".next/server/app", "") - .replace( - "_client-reference-manifest.js", - "" - )}": globalThis.__RSC_MANIFEST["${manifestJs - .replace(".next/server/app", "") - .replace("_client-reference-manifest.js", "")}"], + .replace(".next/server/app", "") + .replace("_client-reference-manifest.js", "")}": globalThis.__RSC_MANIFEST["${manifestJs + .replace(".next/server/app", "") + .replace("_client-reference-manifest.js", "")}"], }, }; } ` - ) - .join("\n")} + ) + .join("\n")} throw new Error("Unknown evalManifest: " + $1); ` - ); + ); } diff --git a/builder/src/build/build-worker/patches/to-investigate/inline-next-require.ts b/builder/src/build/build-worker/patches/to-investigate/inline-next-require.ts index 44221f358..48467f8f4 100644 --- a/builder/src/build/build-worker/patches/to-investigate/inline-next-require.ts +++ b/builder/src/build/build-worker/patches/to-investigate/inline-next-require.ts @@ -5,56 +5,48 @@ import { NextjsAppPaths } from "../../../../nextjs-paths"; * The following avoid various Next.js specific files `require`d at runtime since we can just read * and inline their content during build time */ -export function inlineNextRequire( - code: string, - nextjsAppPaths: NextjsAppPaths -) { - console.log("# inlineNextRequire"); - const pagesManifestFile = `${nextjsAppPaths.standaloneAppServerDir}/pages-manifest.json`; - const appPathsManifestFile = `${nextjsAppPaths.standaloneAppServerDir}/app-paths-manifest.json`; +export function inlineNextRequire(code: string, nextjsAppPaths: NextjsAppPaths) { + console.log("# inlineNextRequire"); + const pagesManifestFile = `${nextjsAppPaths.standaloneAppServerDir}/pages-manifest.json`; + const appPathsManifestFile = `${nextjsAppPaths.standaloneAppServerDir}/app-paths-manifest.json`; - const pagesManifestFiles = existsSync(pagesManifestFile) - ? Object.values(JSON.parse(readFileSync(pagesManifestFile, "utf-8"))).map( - (file) => ".next/server/" + file - ) - : []; - const appPathsManifestFiles = existsSync(appPathsManifestFile) - ? Object.values( - JSON.parse(readFileSync(appPathsManifestFile, "utf-8")) - ).map((file) => ".next/server/" + file) - : []; - const allManifestFiles = pagesManifestFiles.concat(appPathsManifestFiles); + const pagesManifestFiles = existsSync(pagesManifestFile) + ? Object.values(JSON.parse(readFileSync(pagesManifestFile, "utf-8"))).map( + (file) => ".next/server/" + file + ) + : []; + const appPathsManifestFiles = existsSync(appPathsManifestFile) + ? Object.values(JSON.parse(readFileSync(appPathsManifestFile, "utf-8"))).map( + (file) => ".next/server/" + file + ) + : []; + const allManifestFiles = pagesManifestFiles.concat(appPathsManifestFiles); - const htmlPages = allManifestFiles.filter((file) => file.endsWith(".html")); - const pageModules = allManifestFiles.filter((file) => file.endsWith(".js")); + const htmlPages = allManifestFiles.filter((file) => file.endsWith(".html")); + const pageModules = allManifestFiles.filter((file) => file.endsWith(".js")); - return code.replace( - /const pagePath = getPagePath\(.+?\);/, - `$& + return code.replace( + /const pagePath = getPagePath\(.+?\);/, + `$& ${htmlPages - .map( - (htmlPage) => ` + .map( + (htmlPage) => ` if (pagePath.endsWith("${htmlPage}")) { - return ${JSON.stringify( - readFileSync( - `${nextjsAppPaths.standaloneAppDir}/${htmlPage}`, - "utf-8" - ) - )}; + return ${JSON.stringify(readFileSync(`${nextjsAppPaths.standaloneAppDir}/${htmlPage}`, "utf-8"))}; } ` - ) - .join("\n")} + ) + .join("\n")} ${pageModules - .map( - (module) => ` + .map( + (module) => ` if (pagePath.endsWith("${module}")) { return require("${nextjsAppPaths.standaloneAppDir}/${module}"); } ` - ) - .join("\n")} + ) + .join("\n")} throw new Error("Unknown pagePath: " + pagePath); ` - ); + ); } diff --git a/builder/src/build/build-worker/patches/to-investigate/patch-find-dir.ts b/builder/src/build/build-worker/patches/to-investigate/patch-find-dir.ts index 9b4f3c4a0..b1d3cb2ed 100644 --- a/builder/src/build/build-worker/patches/to-investigate/patch-find-dir.ts +++ b/builder/src/build/build-worker/patches/to-investigate/patch-find-dir.ts @@ -7,23 +7,16 @@ import { existsSync } from "node:fs"; * (usage source: https://github.com/vercel/next.js/blob/ba995993/packages/next/src/server/next-server.ts#L450-L451) * Note: `findDir` uses `fs.existsSync` under the hood, so patching that should be enough to make this work */ -export function patchFindDir( - code: string, - nextjsAppPaths: NextjsAppPaths -): string { - console.log("# patchFindDir"); - return code.replace( - "function findDir(dir, name) {", - `function findDir(dir, name) { +export function patchFindDir(code: string, nextjsAppPaths: NextjsAppPaths): string { + console.log("# patchFindDir"); + return code.replace( + "function findDir(dir, name) {", + `function findDir(dir, name) { if (dir.endsWith(".next/server")) { - if (name === "app") return ${existsSync( - `${nextjsAppPaths.standaloneAppServerDir}/app` - )}; - if (name === "pages") return ${existsSync( - `${nextjsAppPaths.standaloneAppServerDir}/pages` - )}; + if (name === "app") return ${existsSync(`${nextjsAppPaths.standaloneAppServerDir}/app`)}; + if (name === "pages") return ${existsSync(`${nextjsAppPaths.standaloneAppServerDir}/pages`)}; } throw new Error("Unknown findDir call: " + dir + " " + name); ` - ); + ); } diff --git a/builder/src/build/build-worker/patches/to-investigate/patch-read-file.ts b/builder/src/build/build-worker/patches/to-investigate/patch-read-file.ts index 35c6e1458..6c9a4e0e9 100644 --- a/builder/src/build/build-worker/patches/to-investigate/patch-read-file.ts +++ b/builder/src/build/build-worker/patches/to-investigate/patch-read-file.ts @@ -2,51 +2,40 @@ import { readFileSync } from "node:fs"; import { globSync } from "glob"; import { NextjsAppPaths } from "../../../../nextjs-paths"; -export function patchReadFile( - code: string, - nextjsAppPaths: NextjsAppPaths -): string { - console.log("# patchReadFile"); - // The next-server code gets the buildId from the filesystem, resulting in a `[unenv] fs.readFileSync is not implemented yet!` error - // so we add an early return to the `getBuildId` function so that the `readyFileSync` is never encountered - // (source: https://github.com/vercel/next.js/blob/15aeb92efb34c09a36/packages/next/src/server/next-server.ts#L438-L451) - // Note: we could/should probably just patch readFileSync here or something! - code = code.replace( - "getBuildId() {", - `getBuildId() { - return ${JSON.stringify( - readFileSync( - `${nextjsAppPaths.standaloneAppDotNextDir}/BUILD_ID`, - "utf-8" - ) - )}; +export function patchReadFile(code: string, nextjsAppPaths: NextjsAppPaths): string { + console.log("# patchReadFile"); + // The next-server code gets the buildId from the filesystem, resulting in a `[unenv] fs.readFileSync is not implemented yet!` error + // so we add an early return to the `getBuildId` function so that the `readyFileSync` is never encountered + // (source: https://github.com/vercel/next.js/blob/15aeb92efb34c09a36/packages/next/src/server/next-server.ts#L438-L451) + // Note: we could/should probably just patch readFileSync here or something! + code = code.replace( + "getBuildId() {", + `getBuildId() { + return ${JSON.stringify(readFileSync(`${nextjsAppPaths.standaloneAppDotNextDir}/BUILD_ID`, "utf-8"))}; ` - ); + ); - // Same as above, the next-server code loads the manifests with `readyFileSync` and we want to avoid that - // (source: https://github.com/vercel/next.js/blob/15aeb92e/packages/next/src/server/load-manifest.ts#L34-L56) - // Note: we could/should probably just patch readFileSync here or something! - const manifestJsons = globSync( - `${nextjsAppPaths.standaloneAppDotNextDir}/**/*-manifest.json` - ).map((file) => file.replace(nextjsAppPaths.standaloneAppDir + "/", "")); - code = code.replace( - /function loadManifest\((.+?), .+?\) {/, - `$& + // Same as above, the next-server code loads the manifests with `readyFileSync` and we want to avoid that + // (source: https://github.com/vercel/next.js/blob/15aeb92e/packages/next/src/server/load-manifest.ts#L34-L56) + // Note: we could/should probably just patch readFileSync here or something! + const manifestJsons = globSync(`${nextjsAppPaths.standaloneAppDotNextDir}/**/*-manifest.json`).map((file) => + file.replace(nextjsAppPaths.standaloneAppDir + "/", "") + ); + code = code.replace( + /function loadManifest\((.+?), .+?\) {/, + `$& ${manifestJsons - .map( - (manifestJson) => ` + .map( + (manifestJson) => ` if ($1.endsWith("${manifestJson}")) { - return ${readFileSync( - `${nextjsAppPaths.standaloneAppDir}/${manifestJson}`, - "utf-8" - )}; + return ${readFileSync(`${nextjsAppPaths.standaloneAppDir}/${manifestJson}`, "utf-8")}; } ` - ) - .join("\n")} + ) + .join("\n")} throw new Error("Unknown loadManifest: " + $1); ` - ); + ); - return code; + return code; } diff --git a/builder/src/build/build-worker/patches/to-investigate/wrangler-deps.ts b/builder/src/build/build-worker/patches/to-investigate/wrangler-deps.ts index ac875161a..93a9991e2 100644 --- a/builder/src/build/build-worker/patches/to-investigate/wrangler-deps.ts +++ b/builder/src/build/build-worker/patches/to-investigate/wrangler-deps.ts @@ -3,60 +3,57 @@ import fs, { writeFileSync } from "node:fs"; import { NextjsAppPaths } from "../../../../nextjs-paths"; export function patchWranglerDeps(paths: NextjsAppPaths) { - console.log("# patchWranglerDeps"); - - console.log({ base: paths.standaloneAppDotNextDir }); - - // Patch .next/standalone/node_modules/next/dist/compiled/next-server/pages.runtime.prod.js - // - // Remove the need for an alias in wrangler.toml: - // - // [alias] - // # critters is `require`d from `pages.runtime.prod.js` when running wrangler dev, so we need to stub it out - // "critters" = "./.next/standalone/node_modules/cf/templates/shims/empty.ts" - const pagesRuntimeFile = path.join( - paths.standaloneAppDir, - "node_modules", - "next", - "dist", - "compiled", - "next-server", - "pages.runtime.prod.js" - ); - - const patchedPagesRuntime = fs - .readFileSync(pagesRuntimeFile, "utf-8") - .replace(`e.exports=require("critters")`, `e.exports={}`); - - fs.writeFileSync(pagesRuntimeFile, patchedPagesRuntime); - - // Patch .next/standalone/node_modules/next/dist/server/lib/trace/tracer.js - // - // Remove the need for an alias in wrangler.toml: - // - // [alias] - // # @opentelemetry/api is `require`d when running wrangler dev, so we need to stub it out - // # IMPORTANT: we shim @opentelemetry/api to the throwing shim so that it will throw right away, this is so that we throw inside the - // # try block here: https://github.com/vercel/next.js/blob/9e8266a7/packages/next/src/server/lib/trace/tracer.ts#L27-L31 - // # causing the code to require the 'next/dist/compiled/@opentelemetry/api' module instead (which properly works) - // #"@opentelemetry/api" = "./.next/standalone/node_modules/cf/templates/shims/throw.ts" - const tracerFile = path.join( - paths.standaloneAppDir, - "node_modules", - "next", - "dist", - "server", - "lib", - "trace", - "tracer.js" - ); - - const pacthedTracer = fs - .readFileSync(tracerFile, "utf-8") - .replaceAll( - /\w+\s*=\s*require\([^/]*opentelemetry.*\)/g, - `throw new Error("@opentelemetry/api")` - ); - - writeFileSync(tracerFile, pacthedTracer); + console.log("# patchWranglerDeps"); + + console.log({ base: paths.standaloneAppDotNextDir }); + + // Patch .next/standalone/node_modules/next/dist/compiled/next-server/pages.runtime.prod.js + // + // Remove the need for an alias in wrangler.toml: + // + // [alias] + // # critters is `require`d from `pages.runtime.prod.js` when running wrangler dev, so we need to stub it out + // "critters" = "./.next/standalone/node_modules/cf/templates/shims/empty.ts" + const pagesRuntimeFile = path.join( + paths.standaloneAppDir, + "node_modules", + "next", + "dist", + "compiled", + "next-server", + "pages.runtime.prod.js" + ); + + const patchedPagesRuntime = fs + .readFileSync(pagesRuntimeFile, "utf-8") + .replace(`e.exports=require("critters")`, `e.exports={}`); + + fs.writeFileSync(pagesRuntimeFile, patchedPagesRuntime); + + // Patch .next/standalone/node_modules/next/dist/server/lib/trace/tracer.js + // + // Remove the need for an alias in wrangler.toml: + // + // [alias] + // # @opentelemetry/api is `require`d when running wrangler dev, so we need to stub it out + // # IMPORTANT: we shim @opentelemetry/api to the throwing shim so that it will throw right away, this is so that we throw inside the + // # try block here: https://github.com/vercel/next.js/blob/9e8266a7/packages/next/src/server/lib/trace/tracer.ts#L27-L31 + // # causing the code to require the 'next/dist/compiled/@opentelemetry/api' module instead (which properly works) + // #"@opentelemetry/api" = "./.next/standalone/node_modules/cf/templates/shims/throw.ts" + const tracerFile = path.join( + paths.standaloneAppDir, + "node_modules", + "next", + "dist", + "server", + "lib", + "trace", + "tracer.js" + ); + + const pacthedTracer = fs + .readFileSync(tracerFile, "utf-8") + .replaceAll(/\w+\s*=\s*require\([^/]*opentelemetry.*\)/g, `throw new Error("@opentelemetry/api")`); + + writeFileSync(tracerFile, pacthedTracer); } diff --git a/builder/src/build/build-worker/templates/shims/node-fs.ts b/builder/src/build/build-worker/templates/shims/node-fs.ts index bc833ee9b..9ac156eb8 100644 --- a/builder/src/build/build-worker/templates/shims/node-fs.ts +++ b/builder/src/build/build-worker/templates/shims/node-fs.ts @@ -1,73 +1,69 @@ // https://github.com/vercel/next.js/blob/canary/packages/next/src/server/lib/node-fs-methods.ts export const nodeFs = { - existsSync, - readFile, - readFileSync, - writeFile, - mkdir, - stat, + existsSync, + readFile, + readFileSync, + writeFile, + mkdir, + stat, }; const FILES = new Map(); const MTIME = Date.now(); function existsSync(path: string) { - console.log( - "existsSync", - path, - new Error().stack?.split("\n").slice(1).join("\n") - ); - return FILES.has(path); + console.log("existsSync", path, new Error().stack?.split("\n").slice(1).join("\n")); + return FILES.has(path); } async function readFile(path: string, options: unknown): Promise { - console.log( - "readFile", - { path, options } - // new Error().stack.split("\n").slice(1).join("\n"), - ); - if (!FILES.has(path)) { - throw new Error(path + "does not exist"); - } - return FILES.get(path); + console.log( + "readFile", + { path, options } + // new Error().stack.split("\n").slice(1).join("\n"), + ); + if (!FILES.has(path)) { + throw new Error(path + "does not exist"); + } + return FILES.get(path); } function readFileSync(path: string, options: unknown) { - console.log( - "readFileSync", - { path, options } - // new Error().stack.split("\n").slice(1).join("\n"), - ); - if (!FILES.has(path)) { - throw new Error(path + "does not exist"); - } - return FILES.get(path); + console.log( + "readFileSync", + { path, options } + // new Error().stack.split("\n").slice(1).join("\n"), + ); + if (!FILES.has(path)) { + throw new Error(path + "does not exist"); + } + return FILES.get(path); } async function writeFile(file: string, data: unknown) { - console.log( - "writeFile", - { file, data } - // new Error().stack.split("\n").slice(1).join("\n"), - ); - FILES.set(file, data); - return true; + console.log( + "writeFile", + { file, data } + // new Error().stack.split("\n").slice(1).join("\n"), + ); + FILES.set(file, data); + return true; } async function mkdir(dir: string) { - console.log( - "mkdir", - dir - //new Error().stack.split("\n").slice(1).join("\n"), - ); + console.log( + "mkdir", + dir + //new Error().stack.split("\n").slice(1).join("\n"), + ); } async function stat(file: string) { - console.log( - "stat", - file - // new Error().stack.split("\n").slice(1).join("\n"), - ); - return { mtime: new Date(MTIME) }; + console.log( + "stat", + file + // new Error().stack.split("\n").slice(1).join("\n"), + ); + return { mtime: new Date(MTIME) }; } diff --git a/builder/src/build/build-worker/templates/worker.ts b/builder/src/build/build-worker/templates/worker.ts index 84541b945..157719582 100644 --- a/builder/src/build/build-worker/templates/worker.ts +++ b/builder/src/build/build-worker/templates/worker.ts @@ -1,93 +1,81 @@ import { Readable } from "node:stream"; import type { NextConfig } from "next"; -import { - NodeNextRequest, - NodeNextResponse, -} from "next/dist/server/base-http/node"; +import { NodeNextRequest, NodeNextResponse } from "next/dist/server/base-http/node"; import { createRequestResponseMocks } from "next/dist/server/lib/mock-request"; -import NextNodeServer, { - NodeRequestHandler, -} from "next/dist/server/next-server"; +import NextNodeServer, { NodeRequestHandler } from "next/dist/server/next-server"; /** * Injected at build time * (we practically follow what Next.js does here: https://github.com/vercel/next.js/blob/68a7128/packages/next/src/build/utils.ts#L2137-L2139) */ -const nextConfig: NextConfig = JSON.parse( - process.env.__NEXT_PRIVATE_STANDALONE_CONFIG ?? "{}" -); +const nextConfig: NextConfig = JSON.parse(process.env.__NEXT_PRIVATE_STANDALONE_CONFIG ?? "{}"); let requestHandler: NodeRequestHandler | null = null; export default { - async fetch(request: Request, env: any, ctx: any) { - if (requestHandler == null) { - globalThis.process.env = { ...globalThis.process.env, ...env }; - requestHandler = new NextNodeServer({ - conf: { ...nextConfig, env }, - customServer: false, - dev: false, - dir: "", - }).getRequestHandler(); - } + async fetch(request: Request, env: any, ctx: any) { + if (requestHandler == null) { + globalThis.process.env = { ...globalThis.process.env, ...env }; + requestHandler = new NextNodeServer({ + conf: { ...nextConfig, env }, + customServer: false, + dev: false, + dir: "", + }).getRequestHandler(); + } - const url = new URL(request.url); + const url = new URL(request.url); - if (url.pathname === "/_next/image") { - let imageUrl = - url.searchParams.get("url") ?? - "https://developers.cloudflare.com/_astro/logo.BU9hiExz.svg"; - if (imageUrl.startsWith("/")) { - imageUrl = new URL(imageUrl, request.url).href; - } - return fetch(imageUrl, { cf: { cacheEverything: true } } as any); - } + if (url.pathname === "/_next/image") { + let imageUrl = + url.searchParams.get("url") ?? "https://developers.cloudflare.com/_astro/logo.BU9hiExz.svg"; + if (imageUrl.startsWith("/")) { + imageUrl = new URL(imageUrl, request.url).href; + } + return fetch(imageUrl, { cf: { cacheEverything: true } } as any); + } - const resBody = new TransformStream(); - const writer = resBody.writable.getWriter(); + const resBody = new TransformStream(); + const writer = resBody.writable.getWriter(); - const reqBodyNodeStream = request.body - ? Readable.fromWeb(request.body as any) - : undefined; + const reqBodyNodeStream = request.body ? Readable.fromWeb(request.body as any) : undefined; - const { req, res } = createRequestResponseMocks({ - method: request.method, - url: url.href.slice(url.origin.length), - headers: Object.fromEntries([...request.headers]), - bodyReadable: reqBodyNodeStream, - resWriter: (chunk) => { - writer.write(chunk).catch(console.error); - return true; - }, - }); + const { req, res } = createRequestResponseMocks({ + method: request.method, + url: url.href.slice(url.origin.length), + headers: Object.fromEntries([...request.headers]), + bodyReadable: reqBodyNodeStream, + resWriter: (chunk) => { + writer.write(chunk).catch(console.error); + return true; + }, + }); - let headPromiseResolve: any = null; - const headPromise = new Promise((resolve) => { - headPromiseResolve = resolve; - }); - res.flushHeaders = () => headPromiseResolve?.(); + let headPromiseResolve: any = null; + const headPromise = new Promise((resolve) => { + headPromiseResolve = resolve; + }); + res.flushHeaders = () => headPromiseResolve?.(); - if (reqBodyNodeStream != null) { - const origPush = reqBodyNodeStream.push; - reqBodyNodeStream.push = (chunk: any) => { - req.push(chunk); - return origPush.call(reqBodyNodeStream, chunk); - }; - } + if (reqBodyNodeStream != null) { + const origPush = reqBodyNodeStream.push; + reqBodyNodeStream.push = (chunk: any) => { + req.push(chunk); + return origPush.call(reqBodyNodeStream, chunk); + }; + } - ctx.waitUntil((res as any).hasStreamed.then(() => writer.close())); + ctx.waitUntil((res as any).hasStreamed.then(() => writer.close())); - ctx.waitUntil( - requestHandler(new NodeNextRequest(req), new NodeNextResponse(res)) - ); + ctx.waitUntil(requestHandler(new NodeNextRequest(req), new NodeNextResponse(res))); - await Promise.race([res.headPromise, headPromise]); + await Promise.race([res.headPromise, headPromise]); - return new Response(resBody.readable, { - status: res.statusCode, - headers: (res as any).headers, - }); - }, + return new Response(resBody.readable, { + status: res.statusCode, + headers: (res as any).headers, + }); + }, }; diff --git a/builder/src/build/index.ts b/builder/src/build/index.ts index 19801be7e..aa8be9892 100644 --- a/builder/src/build/index.ts +++ b/builder/src/build/index.ts @@ -17,48 +17,42 @@ const SAVE_DIR = ".save.next"; * @param opts.outputDir the directory where to save the output (defaults to the app's directory) * @param opts.skipBuild boolean indicating whether the Next.js build should be skipped (i.e. if the `.next` dir is already built) */ -export async function build( - inputNextAppDir: string, - opts: BuildOptions -): Promise { - if (!opts.skipBuild) { - // Build the next app and save a copy in .save.next - buildNextjsApp(inputNextAppDir); - rmSync(`${inputNextAppDir}/${SAVE_DIR}`, { - recursive: true, - force: true, - }); - cpSync(`${inputNextAppDir}/.next`, `${inputNextAppDir}/${SAVE_DIR}`, { - recursive: true, - }); - } else { - // Skip the next build and restore the copy from .next.save - rmSync(`${inputNextAppDir}/.next`, { recursive: true, force: true }); - cpSync(`${inputNextAppDir}/${SAVE_DIR}`, `${inputNextAppDir}/.next`, { - recursive: true, - }); - } - - const outputDir = `${opts.outputDir ?? inputNextAppDir}/.worker-next`; - await cleanDirectory(outputDir); - - const nextjsAppPaths = getNextjsAppPaths(inputNextAppDir); - - const templateDir = path.join( - path.dirname(fileURLToPath(import.meta.url)), - "templates" - ); - - console.log({ outputDir, nextjsAppPaths, templateDir }); - - await buildWorker(outputDir, nextjsAppPaths, templateDir); +export async function build(inputNextAppDir: string, opts: BuildOptions): Promise { + if (!opts.skipBuild) { + // Build the next app and save a copy in .save.next + buildNextjsApp(inputNextAppDir); + rmSync(`${inputNextAppDir}/${SAVE_DIR}`, { + recursive: true, + force: true, + }); + cpSync(`${inputNextAppDir}/.next`, `${inputNextAppDir}/${SAVE_DIR}`, { + recursive: true, + }); + } else { + // Skip the next build and restore the copy from .next.save + rmSync(`${inputNextAppDir}/.next`, { recursive: true, force: true }); + cpSync(`${inputNextAppDir}/${SAVE_DIR}`, `${inputNextAppDir}/.next`, { + recursive: true, + }); + } + + const outputDir = `${opts.outputDir ?? inputNextAppDir}/.worker-next`; + await cleanDirectory(outputDir); + + const nextjsAppPaths = getNextjsAppPaths(inputNextAppDir); + + const templateDir = path.join(path.dirname(fileURLToPath(import.meta.url)), "templates"); + + console.log({ outputDir, nextjsAppPaths, templateDir }); + + await buildWorker(outputDir, nextjsAppPaths, templateDir); } type BuildOptions = { - skipBuild: boolean; - outputDir?: string; + skipBuild: boolean; + outputDir?: string; }; async function cleanDirectory(path: string): Promise { - return await rm(path, { recursive: true, force: true }); + return await rm(path, { recursive: true, force: true }); } diff --git a/builder/src/index.ts b/builder/src/index.ts index fb6985ea8..df2e8df37 100644 --- a/builder/src/index.ts +++ b/builder/src/index.ts @@ -7,16 +7,14 @@ const inputNextAppDir = resolve("."); console.log({ inputNextAppDir }); -if ( - !["js", "cjs", "mjs", "ts"].some((ext) => existsSync(`./next.config.${ext}`)) -) { - // TODO: we can add more validation later - throw new Error("Error: Not in a Next.js app project"); +if (!["js", "cjs", "mjs", "ts"].some((ext) => existsSync(`./next.config.${ext}`))) { + // TODO: we can add more validation later + throw new Error("Error: Not in a Next.js app project"); } const { skipBuild, outputDir } = getArgs(); await build(inputNextAppDir, { - outputDir, - skipBuild: !!skipBuild, + outputDir, + skipBuild: !!skipBuild, }); diff --git a/builder/src/nextjs-paths.ts b/builder/src/nextjs-paths.ts index 530ea32d7..999330cc6 100644 --- a/builder/src/nextjs-paths.ts +++ b/builder/src/nextjs-paths.ts @@ -7,26 +7,26 @@ import path, { relative } from "node:path"; * NOTE: WIP, we still need to discern which paths are relevant here! */ export type NextjsAppPaths = { - appDir: string; - /** - * The path to the application's `.next` directory (where `next build` saves the build output) - */ - dotNextDir: string; - - /** - * The path to the application standalone directory (where `next build` saves the standalone app when standalone mode is used) - */ - standaloneAppDir: string; - - /** - * the path to the `.next` directory specific to the standalone application - */ - standaloneAppDotNextDir: string; - - /** - * the path to the `server` directory specific to the standalone application - */ - standaloneAppServerDir: string; + appDir: string; + /** + * The path to the application's `.next` directory (where `next build` saves the build output) + */ + dotNextDir: string; + + /** + * The path to the application standalone directory (where `next build` saves the standalone app when standalone mode is used) + */ + standaloneAppDir: string; + + /** + * the path to the `.next` directory specific to the standalone application + */ + standaloneAppDotNextDir: string; + + /** + * the path to the `server` directory specific to the standalone application + */ + standaloneAppServerDir: string; }; /** @@ -36,32 +36,32 @@ export type NextjsAppPaths = { * @returns the various paths. */ export function getNextjsAppPaths(nextAppDir: string): NextjsAppPaths { - const dotNextDir = getDotNextDirPath(nextAppDir); + const dotNextDir = getDotNextDirPath(nextAppDir); - const appPath = getNextjsApplicationPath(dotNextDir).replace(/\/$/, ""); + const appPath = getNextjsApplicationPath(dotNextDir).replace(/\/$/, ""); - const standaloneAppDir = path.join(dotNextDir, "standalone", appPath); + const standaloneAppDir = path.join(dotNextDir, "standalone", appPath); - return { - appDir: nextAppDir, - dotNextDir, - standaloneAppDir, - standaloneAppDotNextDir: path.join(standaloneAppDir, ".next"), - standaloneAppServerDir: path.join(standaloneAppDir, ".next", "server"), - }; + return { + appDir: nextAppDir, + dotNextDir, + standaloneAppDir, + standaloneAppDotNextDir: path.join(standaloneAppDir, ".next"), + standaloneAppServerDir: path.join(standaloneAppDir, ".next", "server"), + }; } function getDotNextDirPath(nextAppDir: string): string { - const dotNextDirPath = `${nextAppDir}/.next`; + const dotNextDirPath = `${nextAppDir}/.next`; - try { - const dirStats = statSync(dotNextDirPath); - if (!dirStats.isDirectory()) throw new Error(); - } catch { - throw new Error(`Error: \`.next\` directory not found!`); - } + try { + const dirStats = statSync(dotNextDirPath); + if (!dirStats.isDirectory()) throw new Error(); + } catch { + throw new Error(`Error: \`.next\` directory not found!`); + } - return dotNextDirPath; + return dotNextDirPath; } /** @@ -74,32 +74,30 @@ function getDotNextDirPath(nextAppDir: string): string { * and the function here given the `dotNextDir` returns `next-apps/api` */ function getNextjsApplicationPath(dotNextDir: string): string { - const serverPath = findServerParentPath(dotNextDir); - - if (!serverPath) { - throw new Error( - `Unexpected Error: no \`.next/server\` folder could be found in \`${serverPath}\`` - ); - } - - return relative(`${dotNextDir}/standalone`, serverPath); - - function findServerParentPath(path: string): string | undefined { - try { - if (statSync(`${path}/.next/server`).isDirectory()) { - return path; - } - } catch {} - - const files = readdirSync(path); - - for (const file of files) { - if (statSync(`${path}/${file}`).isDirectory()) { - const dirServerPath = findServerParentPath(`${path}/${file}`); - if (dirServerPath) { - return dirServerPath; - } - } - } - } + const serverPath = findServerParentPath(dotNextDir); + + if (!serverPath) { + throw new Error(`Unexpected Error: no \`.next/server\` folder could be found in \`${serverPath}\``); + } + + return relative(`${dotNextDir}/standalone`, serverPath); + + function findServerParentPath(path: string): string | undefined { + try { + if (statSync(`${path}/.next/server`).isDirectory()) { + return path; + } + } catch {} + + const files = readdirSync(path); + + for (const file of files) { + if (statSync(`${path}/${file}`).isDirectory()) { + const dirServerPath = findServerParentPath(`${path}/${file}`); + if (dirServerPath) { + return dirServerPath; + } + } + } + } } diff --git a/builder/tsconfig.json b/builder/tsconfig.json index b8a5328ab..ff6d97309 100644 --- a/builder/tsconfig.json +++ b/builder/tsconfig.json @@ -1,11 +1,11 @@ { - "compilerOptions": { - "target": "ESNext", - "module": "ESNext", - "moduleResolution": "Bundler", - "esModuleInterop": true, - "forceConsistentCasingInFileNames": true, - "strict": true, - "skipLibCheck": true - } + "compilerOptions": { + "target": "ESNext", + "module": "ESNext", + "moduleResolution": "Bundler", + "esModuleInterop": true, + "forceConsistentCasingInFileNames": true, + "strict": true, + "skipLibCheck": true + } } diff --git a/builder/tsup.config.ts b/builder/tsup.config.ts index 1651b204a..8d1ff8478 100644 --- a/builder/tsup.config.ts +++ b/builder/tsup.config.ts @@ -2,17 +2,15 @@ import { cp } from "fs/promises"; import { defineConfig } from "tsup"; export default defineConfig({ - entry: ["src/index.ts"], - outDir: "dist", - dts: true, - format: ["esm"], - platform: "node", - external: ["esbuild"], - onSuccess: async () => { - await cp( - `${__dirname}/src/build/build-worker/templates`, - `${__dirname}/dist/templates`, - { recursive: true } - ); - }, + entry: ["src/index.ts"], + outDir: "dist", + dts: true, + format: ["esm"], + platform: "node", + external: ["esbuild"], + onSuccess: async () => { + await cp(`${__dirname}/src/build/build-worker/templates`, `${__dirname}/dist/templates`, { + recursive: true, + }); + }, }); diff --git a/examples/api/app/api/hello/route.ts b/examples/api/app/api/hello/route.ts index b4028c87f..16825a038 100644 --- a/examples/api/app/api/hello/route.ts +++ b/examples/api/app/api/hello/route.ts @@ -1,12 +1,12 @@ import { headers } from "next/headers"; export async function GET() { - // Note: we use headers just so that the route is not built as a static one - const headersList = headers(); - const sayHi = !!headersList.get("should-say-hi"); - return new Response(sayHi ? "Hi World!" : "Hello World!"); + // Note: we use headers just so that the route is not built as a static one + const headersList = headers(); + const sayHi = !!headersList.get("should-say-hi"); + return new Response(sayHi ? "Hi World!" : "Hello World!"); } export async function POST(request: Request) { - return new Response(`Hello post-World! body=${await request.text()}`); + return new Response(`Hello post-World! body=${await request.text()}`); } diff --git a/examples/api/app/layout.js b/examples/api/app/layout.js index d3cded23e..175f2b0a4 100644 --- a/examples/api/app/layout.js +++ b/examples/api/app/layout.js @@ -1,12 +1,12 @@ export const metadata = { - title: "API hello-world", - description: "a simple api hello-world app", + title: "API hello-world", + description: "a simple api hello-world app", }; export default function RootLayout({ children }) { - return ( - - {children} - - ); + return ( + + {children} + + ); } diff --git a/examples/api/app/page.js b/examples/api/app/page.js index 730e6bfee..e2d065ac7 100644 --- a/examples/api/app/page.js +++ b/examples/api/app/page.js @@ -1,13 +1,13 @@ export default function Home() { - return ( -
-

- This application doesn't have a UI, just a single api route (running in - the node.js runtime): - - /api/hello - -

-
- ); + return ( +
+

+ This application doesn't have a UI, just a single api route (running in the node.js{" "} + runtime): + + /api/hello + +

+
+ ); } diff --git a/examples/api/e2e-tests/base.spec.ts b/examples/api/e2e-tests/base.spec.ts index 31d488fbe..fbd771dae 100644 --- a/examples/api/e2e-tests/base.spec.ts +++ b/examples/api/e2e-tests/base.spec.ts @@ -1,16 +1,16 @@ import { test, expect } from "@playwright/test"; test("the application's noop index page is visible and it allows navigating to the hello-world api route", async ({ - page, + page, }) => { - await page.goto("/"); - await expect(page.getByText("This application doesn't have")).toBeVisible(); - await page.getByRole("link", { name: "/api/hello" }).click(); - await expect(page.getByText("Hello World!")).toBeVisible(); + await page.goto("/"); + await expect(page.getByText("This application doesn't have")).toBeVisible(); + await page.getByRole("link", { name: "/api/hello" }).click(); + await expect(page.getByText("Hello World!")).toBeVisible(); }); test("the hello-world api route works as intended", async ({ page }) => { - const res = await page.request.get("/api/hello"); - expect(res.headers()["content-type"]).toContain("text/plain"); - expect(await res.text()).toEqual("Hello World!"); + const res = await page.request.get("/api/hello"); + expect(res.headers()["content-type"]).toContain("text/plain"); + expect(await res.text()).toEqual("Hello World!"); }); diff --git a/examples/api/next.config.mjs b/examples/api/next.config.mjs index e256af5a0..f9a6d1d00 100644 --- a/examples/api/next.config.mjs +++ b/examples/api/next.config.mjs @@ -1,11 +1,11 @@ /** @type {import('next').NextConfig} */ const nextConfig = { - output: "standalone", - experimental: { - // IMPORTANT: this option is necessary for the chunks hack since that relies on the webpack-runtime.js file not being minified - // (since we regex-replace relying on specific variable names) - serverMinification: false, - }, + output: "standalone", + experimental: { + // IMPORTANT: this option is necessary for the chunks hack since that relies on the webpack-runtime.js file not being minified + // (since we regex-replace relying on specific variable names) + serverMinification: false, + }, }; export default nextConfig; diff --git a/examples/api/package.json b/examples/api/package.json index a805484e4..8059d8e32 100644 --- a/examples/api/package.json +++ b/examples/api/package.json @@ -1,27 +1,27 @@ { - "name": "api", - "version": "0.1.0", - "private": true, - "scripts": { - "dev": "next dev", - "build": "next build", - "start": "next start", - "lint": "next lint", - "build:worker": "builder", - "dev:worker": "wrangler dev --port 8770", - "preview:worker": "pnpm build:worker && pnpm dev:worker", - "e2e": "playwright test" - }, - "dependencies": { - "next": "14.2.5", - "react": "^18", - "react-dom": "^18" - }, - "devDependencies": { - "builder": "workspace:*", - "@playwright/test": "1.47.0", - "@types/node": "^22.2.0", - "node-url": "npm:url@^0.11.4", - "wrangler": "3.77.0" - } + "name": "api", + "version": "0.1.0", + "private": true, + "scripts": { + "dev": "next dev", + "build": "next build", + "start": "next start", + "lint": "next lint", + "build:worker": "builder", + "dev:worker": "wrangler dev --port 8770", + "preview:worker": "pnpm build:worker && pnpm dev:worker", + "e2e": "playwright test" + }, + "dependencies": { + "next": "14.2.5", + "react": "^18", + "react-dom": "^18" + }, + "devDependencies": { + "builder": "workspace:*", + "@playwright/test": "1.47.0", + "@types/node": "^22.2.0", + "node-url": "npm:url@^0.11.4", + "wrangler": "3.77.0" + } } diff --git a/examples/api/playwright.config.ts b/examples/api/playwright.config.ts index 3186acd66..548ad14f8 100644 --- a/examples/api/playwright.config.ts +++ b/examples/api/playwright.config.ts @@ -13,68 +13,68 @@ declare var process: { env: Record }; * See https://playwright.dev/docs/test-configuration. */ export default defineConfig({ - testDir: "./e2e-tests", - /* Run tests in files in parallel */ - fullyParallel: true, - /* Fail the build on CI if you accidentally left test.only in the source code. */ - forbidOnly: !!process.env.CI, - /* Retry on CI only */ - retries: process.env.CI ? 2 : 0, - /* Opt out of parallel tests on CI. */ - workers: process.env.CI ? 1 : undefined, - /* Reporter to use. See https://playwright.dev/docs/test-reporters */ - reporter: "html", - /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */ - use: { - /* Base URL to use in actions like `await page.goto('/')`. */ - baseURL: "http://localhost:8770", + testDir: "./e2e-tests", + /* Run tests in files in parallel */ + fullyParallel: true, + /* Fail the build on CI if you accidentally left test.only in the source code. */ + forbidOnly: !!process.env.CI, + /* Retry on CI only */ + retries: process.env.CI ? 2 : 0, + /* Opt out of parallel tests on CI. */ + workers: process.env.CI ? 1 : undefined, + /* Reporter to use. See https://playwright.dev/docs/test-reporters */ + reporter: "html", + /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */ + use: { + /* Base URL to use in actions like `await page.goto('/')`. */ + baseURL: "http://localhost:8770", - /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */ - trace: "on-first-retry", - }, + /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */ + trace: "on-first-retry", + }, - /* Configure projects for major browsers */ - projects: [ - { - name: "chromium", - use: { ...devices["Desktop Chrome"] }, - }, + /* Configure projects for major browsers */ + projects: [ + { + name: "chromium", + use: { ...devices["Desktop Chrome"] }, + }, - { - name: "firefox", - use: { ...devices["Desktop Firefox"] }, - }, + { + name: "firefox", + use: { ...devices["Desktop Firefox"] }, + }, - { - name: "webkit", - use: { ...devices["Desktop Safari"] }, - }, + { + name: "webkit", + use: { ...devices["Desktop Safari"] }, + }, - /* Test against mobile viewports. */ - // { - // name: 'Mobile Chrome', - // use: { ...devices['Pixel 5'] }, - // }, - // { - // name: 'Mobile Safari', - // use: { ...devices['iPhone 12'] }, - // }, + /* Test against mobile viewports. */ + // { + // name: 'Mobile Chrome', + // use: { ...devices['Pixel 5'] }, + // }, + // { + // name: 'Mobile Safari', + // use: { ...devices['iPhone 12'] }, + // }, - /* Test against branded browsers. */ - // { - // name: 'Microsoft Edge', - // use: { ...devices['Desktop Edge'], channel: 'msedge' }, - // }, - // { - // name: 'Google Chrome', - // use: { ...devices['Desktop Chrome'], channel: 'chrome' }, - // }, - ], + /* Test against branded browsers. */ + // { + // name: 'Microsoft Edge', + // use: { ...devices['Desktop Edge'], channel: 'msedge' }, + // }, + // { + // name: 'Google Chrome', + // use: { ...devices['Desktop Chrome'], channel: 'chrome' }, + // }, + ], - /* Run your local dev server before starting the tests */ - webServer: { - command: "pnpm preview:worker", - url: "http://localhost:8770", - reuseExistingServer: !process.env.CI, - }, + /* Run your local dev server before starting the tests */ + webServer: { + command: "pnpm preview:worker", + url: "http://localhost:8770", + reuseExistingServer: !process.env.CI, + }, }); diff --git a/examples/api/tsconfig.json b/examples/api/tsconfig.json index 174c9f3c8..2f2891475 100644 --- a/examples/api/tsconfig.json +++ b/examples/api/tsconfig.json @@ -1,23 +1,23 @@ { - "compilerOptions": { - "lib": ["dom", "dom.iterable", "esnext"], - "allowJs": true, - "skipLibCheck": true, - "strict": false, - "noEmit": true, - "incremental": true, - "module": "esnext", - "esModuleInterop": true, - "moduleResolution": "node", - "resolveJsonModule": true, - "isolatedModules": true, - "jsx": "preserve", - "plugins": [ - { - "name": "next" - } - ] - }, - "include": ["next-env.d.ts", ".next/types/**/*.ts", "**/*.ts", "**/*.tsx"], - "exclude": ["node_modules"] + "compilerOptions": { + "lib": ["dom", "dom.iterable", "esnext"], + "allowJs": true, + "skipLibCheck": true, + "strict": false, + "noEmit": true, + "incremental": true, + "module": "esnext", + "esModuleInterop": true, + "moduleResolution": "node", + "resolveJsonModule": true, + "isolatedModules": true, + "jsx": "preserve", + "plugins": [ + { + "name": "next" + } + ] + }, + "include": ["next-env.d.ts", ".next/types/**/*.ts", "**/*.ts", "**/*.tsx"], + "exclude": ["node_modules"] } diff --git a/examples/create-next-app/.eslintrc.json b/examples/create-next-app/.eslintrc.json index a885bd832..372241854 100644 --- a/examples/create-next-app/.eslintrc.json +++ b/examples/create-next-app/.eslintrc.json @@ -1,3 +1,3 @@ { - "extends": ["next/core-web-vitals", "next/typescript"] + "extends": ["next/core-web-vitals", "next/typescript"] } diff --git a/examples/create-next-app/e2e/base.spec.ts b/examples/create-next-app/e2e/base.spec.ts index e7d642882..202ff471c 100644 --- a/examples/create-next-app/e2e/base.spec.ts +++ b/examples/create-next-app/e2e/base.spec.ts @@ -1,8 +1,6 @@ import { test, expect } from "@playwright/test"; -test("the index page of the application shows the Next.js logo", async ({ - page, -}) => { - await page.goto("/"); - await expect(page.getByAltText("Next.js logo")).toBeVisible(); +test("the index page of the application shows the Next.js logo", async ({ page }) => { + await page.goto("/"); + await expect(page.getByAltText("Next.js logo")).toBeVisible(); }); diff --git a/examples/create-next-app/e2e/playwright.config.ts b/examples/create-next-app/e2e/playwright.config.ts index e46860874..a0c91ba6f 100644 --- a/examples/create-next-app/e2e/playwright.config.ts +++ b/examples/create-next-app/e2e/playwright.config.ts @@ -13,68 +13,68 @@ declare const process: { env: Record }; * See https://playwright.dev/docs/test-configuration. */ export default defineConfig({ - testDir: "./", - /* Run tests in files in parallel */ - fullyParallel: true, - /* Fail the build on CI if you accidentally left test.only in the source code. */ - forbidOnly: !!process.env.CI, - /* Retry on CI only */ - retries: process.env.CI ? 2 : 0, - /* Opt out of parallel tests on CI. */ - workers: process.env.CI ? 1 : undefined, - /* Reporter to use. See https://playwright.dev/docs/test-reporters */ - reporter: "html", - /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */ - use: { - /* Base URL to use in actions like `await page.goto('/')`. */ - baseURL: "http://localhost:8771", + testDir: "./", + /* Run tests in files in parallel */ + fullyParallel: true, + /* Fail the build on CI if you accidentally left test.only in the source code. */ + forbidOnly: !!process.env.CI, + /* Retry on CI only */ + retries: process.env.CI ? 2 : 0, + /* Opt out of parallel tests on CI. */ + workers: process.env.CI ? 1 : undefined, + /* Reporter to use. See https://playwright.dev/docs/test-reporters */ + reporter: "html", + /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */ + use: { + /* Base URL to use in actions like `await page.goto('/')`. */ + baseURL: "http://localhost:8771", - /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */ - trace: "on-first-retry", - }, + /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */ + trace: "on-first-retry", + }, - /* Configure projects for major browsers */ - projects: [ - { - name: "chromium", - use: { ...devices["Desktop Chrome"] }, - }, + /* Configure projects for major browsers */ + projects: [ + { + name: "chromium", + use: { ...devices["Desktop Chrome"] }, + }, - { - name: "firefox", - use: { ...devices["Desktop Firefox"] }, - }, + { + name: "firefox", + use: { ...devices["Desktop Firefox"] }, + }, - { - name: "webkit", - use: { ...devices["Desktop Safari"] }, - }, + { + name: "webkit", + use: { ...devices["Desktop Safari"] }, + }, - /* Test against mobile viewports. */ - // { - // name: 'Mobile Chrome', - // use: { ...devices['Pixel 5'] }, - // }, - // { - // name: 'Mobile Safari', - // use: { ...devices['iPhone 12'] }, - // }, + /* Test against mobile viewports. */ + // { + // name: 'Mobile Chrome', + // use: { ...devices['Pixel 5'] }, + // }, + // { + // name: 'Mobile Safari', + // use: { ...devices['iPhone 12'] }, + // }, - /* Test against branded browsers. */ - // { - // name: 'Microsoft Edge', - // use: { ...devices['Desktop Edge'], channel: 'msedge' }, - // }, - // { - // name: 'Google Chrome', - // use: { ...devices['Desktop Chrome'], channel: 'chrome' }, - // }, - ], + /* Test against branded browsers. */ + // { + // name: 'Microsoft Edge', + // use: { ...devices['Desktop Edge'], channel: 'msedge' }, + // }, + // { + // name: 'Google Chrome', + // use: { ...devices['Desktop Chrome'], channel: 'chrome' }, + // }, + ], - /* Run your local dev server before starting the tests */ - webServer: { - command: "pnpm preview:worker", - url: "http://localhost:8771", - reuseExistingServer: !process.env.CI, - }, + /* Run your local dev server before starting the tests */ + webServer: { + command: "pnpm preview:worker", + url: "http://localhost:8771", + reuseExistingServer: !process.env.CI, + }, }); diff --git a/examples/create-next-app/next.config.mjs b/examples/create-next-app/next.config.mjs index fdd022727..52316df54 100644 --- a/examples/create-next-app/next.config.mjs +++ b/examples/create-next-app/next.config.mjs @@ -1,9 +1,9 @@ /** @type {import('next').NextConfig} */ const nextConfig = { - output: "standalone", - experimental: { - serverMinification: false, - }, + output: "standalone", + experimental: { + serverMinification: false, + }, }; export default nextConfig; diff --git a/examples/create-next-app/package.json b/examples/create-next-app/package.json index e9dfbd62f..412e3e8ce 100644 --- a/examples/create-next-app/package.json +++ b/examples/create-next-app/package.json @@ -1,34 +1,34 @@ { - "name": "create-next-app", - "version": "0.1.0", - "private": true, - "scripts": { - "dev": "next dev", - "build": "next build", - "start": "next start", - "lint": "next lint", - "build:worker": "builder", - "dev:worker": "wrangler dev --port 8771", - "preview:worker": "pnpm build:worker && pnpm dev:worker", - "e2e": "playwright test -c e2e/playwright.config.ts" - }, - "dependencies": { - "react": "^18", - "react-dom": "^18", - "next": "14.2.11" - }, - "devDependencies": { - "builder": "workspace:*", - "@playwright/test": "1.47.0", - "@types/node": "^20", - "@types/react": "^18", - "@types/react-dom": "^18", - "eslint": "^8", - "eslint-config-next": "14.2.11", - "postcss": "^8", - "node-url": "npm:url@^0.11.4", - "tailwindcss": "^3.4.1", - "typescript": "^5", - "wrangler": "^3.77.0" - } + "name": "create-next-app", + "version": "0.1.0", + "private": true, + "scripts": { + "dev": "next dev", + "build": "next build", + "start": "next start", + "lint": "next lint", + "build:worker": "builder", + "dev:worker": "wrangler dev --port 8771", + "preview:worker": "pnpm build:worker && pnpm dev:worker", + "e2e": "playwright test -c e2e/playwright.config.ts" + }, + "dependencies": { + "react": "^18", + "react-dom": "^18", + "next": "14.2.11" + }, + "devDependencies": { + "builder": "workspace:*", + "@playwright/test": "1.47.0", + "@types/node": "^20", + "@types/react": "^18", + "@types/react-dom": "^18", + "eslint": "^8", + "eslint-config-next": "14.2.11", + "postcss": "^8", + "node-url": "npm:url@^0.11.4", + "tailwindcss": "^3.4.1", + "typescript": "^5", + "wrangler": "^3.77.0" + } } diff --git a/examples/create-next-app/postcss.config.mjs b/examples/create-next-app/postcss.config.mjs index f6c3605a8..1a69fd2a4 100644 --- a/examples/create-next-app/postcss.config.mjs +++ b/examples/create-next-app/postcss.config.mjs @@ -1,8 +1,8 @@ /** @type {import('postcss-load-config').Config} */ const config = { - plugins: { - tailwindcss: {}, - }, + plugins: { + tailwindcss: {}, + }, }; export default config; diff --git a/examples/create-next-app/src/app/globals.css b/examples/create-next-app/src/app/globals.css index 1a4fd67aa..13d40b892 100644 --- a/examples/create-next-app/src/app/globals.css +++ b/examples/create-next-app/src/app/globals.css @@ -3,25 +3,25 @@ @tailwind utilities; :root { - --background: #ffffff; - --foreground: #171717; + --background: #ffffff; + --foreground: #171717; } @media (prefers-color-scheme: dark) { - :root { - --background: #0a0a0a; - --foreground: #ededed; - } + :root { + --background: #0a0a0a; + --foreground: #ededed; + } } body { - color: var(--foreground); - background: var(--background); - font-family: Arial, Helvetica, sans-serif; + color: var(--foreground); + background: var(--background); + font-family: Arial, Helvetica, sans-serif; } @layer utilities { - .text-balance { - text-wrap: balance; - } + .text-balance { + text-wrap: balance; + } } diff --git a/examples/create-next-app/src/app/layout.tsx b/examples/create-next-app/src/app/layout.tsx index 659596efd..480872490 100644 --- a/examples/create-next-app/src/app/layout.tsx +++ b/examples/create-next-app/src/app/layout.tsx @@ -3,33 +3,29 @@ import localFont from "next/font/local"; import "./globals.css"; const geistSans = localFont({ - src: "./fonts/GeistVF.woff", - variable: "--font-geist-sans", - weight: "100 900", + src: "./fonts/GeistVF.woff", + variable: "--font-geist-sans", + weight: "100 900", }); const geistMono = localFont({ - src: "./fonts/GeistMonoVF.woff", - variable: "--font-geist-mono", - weight: "100 900", + src: "./fonts/GeistMonoVF.woff", + variable: "--font-geist-mono", + weight: "100 900", }); export const metadata: Metadata = { - title: "Create Next App", - description: "Generated by create next app", + title: "Create Next App", + description: "Generated by create next app", }; export default function RootLayout({ - children, + children, }: Readonly<{ - children: React.ReactNode; + children: React.ReactNode; }>) { - return ( - - - {children} - - - ); + return ( + + {children} + + ); } diff --git a/examples/create-next-app/src/app/page.tsx b/examples/create-next-app/src/app/page.tsx index e54f8b516..1179ed2af 100644 --- a/examples/create-next-app/src/app/page.tsx +++ b/examples/create-next-app/src/app/page.tsx @@ -1,101 +1,95 @@ import Image from "next/image"; export default function Home() { - return ( -
-
- Next.js logo -
    -
  1. - Get started by editing{" "} - - src/app/page.tsx - - . -
  2. -
  3. Save and see your changes instantly.
  4. -
+ return ( +
+
+ Next.js logo +
    +
  1. + Get started by editing{" "} + + src/app/page.tsx + + . +
  2. +
  3. Save and see your changes instantly.
  4. +
- -
- -
- ); + +
+ +
+ ); } diff --git a/examples/create-next-app/tailwind.config.ts b/examples/create-next-app/tailwind.config.ts index 45e6dc974..021c39379 100644 --- a/examples/create-next-app/tailwind.config.ts +++ b/examples/create-next-app/tailwind.config.ts @@ -1,19 +1,19 @@ import type { Config } from "tailwindcss"; const config: Config = { - content: [ - "./src/pages/**/*.{js,ts,jsx,tsx,mdx}", - "./src/components/**/*.{js,ts,jsx,tsx,mdx}", - "./src/app/**/*.{js,ts,jsx,tsx,mdx}", - ], - theme: { - extend: { - colors: { - background: "var(--background)", - foreground: "var(--foreground)", - }, - }, - }, - plugins: [], + content: [ + "./src/pages/**/*.{js,ts,jsx,tsx,mdx}", + "./src/components/**/*.{js,ts,jsx,tsx,mdx}", + "./src/app/**/*.{js,ts,jsx,tsx,mdx}", + ], + theme: { + extend: { + colors: { + background: "var(--background)", + foreground: "var(--foreground)", + }, + }, + }, + plugins: [], }; export default config; diff --git a/examples/create-next-app/tsconfig.json b/examples/create-next-app/tsconfig.json index dcfa651d6..7b2858930 100644 --- a/examples/create-next-app/tsconfig.json +++ b/examples/create-next-app/tsconfig.json @@ -1,26 +1,26 @@ { - "compilerOptions": { - "lib": ["dom", "dom.iterable", "esnext"], - "allowJs": true, - "skipLibCheck": true, - "strict": true, - "noEmit": true, - "esModuleInterop": true, - "module": "esnext", - "moduleResolution": "bundler", - "resolveJsonModule": true, - "isolatedModules": true, - "jsx": "preserve", - "incremental": true, - "plugins": [ - { - "name": "next" - } - ], - "paths": { - "@/*": ["./src/*"] - } - }, - "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], - "exclude": ["node_modules"] + "compilerOptions": { + "lib": ["dom", "dom.iterable", "esnext"], + "allowJs": true, + "skipLibCheck": true, + "strict": true, + "noEmit": true, + "esModuleInterop": true, + "module": "esnext", + "moduleResolution": "bundler", + "resolveJsonModule": true, + "isolatedModules": true, + "jsx": "preserve", + "incremental": true, + "plugins": [ + { + "name": "next" + } + ], + "paths": { + "@/*": ["./src/*"] + } + }, + "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], + "exclude": ["node_modules"] } diff --git a/package.json b/package.json index a4271aa5e..a07e0b81f 100644 --- a/package.json +++ b/package.json @@ -1,16 +1,16 @@ { - "name": "poc-next", - "version": "0.0.0.0", - "private": true, - "devDependencies": { - "prettier": "3.3.3", - "@playwright/test": "1.47.0" - }, - "scripts": { - "prettier:check": "prettier --check .", - "prettier:fix": "prettier --write .", - "postinstall": "pnpm --filter builder build", - "install-playwright": "playwright install --with-deps", - "e2e": "pnpm -r e2e" - } + "name": "poc-next", + "version": "0.0.0.0", + "private": true, + "devDependencies": { + "prettier": "3.3.3", + "@playwright/test": "1.47.0" + }, + "scripts": { + "prettier:check": "prettier --check .", + "prettier:fix": "prettier --write .", + "postinstall": "pnpm --filter builder build", + "install-playwright": "playwright install --with-deps", + "e2e": "pnpm -r e2e" + } }