this repo has no description
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

format

+945 -1041
+6 -5
.prettierrc
··· 1 1 { 2 - "printWidth": 80, 3 - "singleQuote": false, 4 - "semi": true, 5 - "useTabs": true, 6 - "trailingComma": "es5" 2 + "printWidth": 110, 3 + "singleQuote": false, 4 + "semi": true, 5 + "useTabs": false, 6 + "tabWidth": 2, 7 + "trailingComma": "es5" 7 8 }
+1 -1
.vscode/settings.json
··· 1 1 { 2 - "editor.formatOnSave": true 2 + "editor.formatOnSave": true 3 3 }
+4 -4
TODO.md
··· 19 19 ```typescript 20 20 /** @type {import('next').NextConfig} */ 21 21 const nextConfig = { 22 - output: "standalone", 23 - experimental: { 24 - serverMinification: false, 25 - }, 22 + output: "standalone", 23 + experimental: { 24 + serverMinification: false, 25 + }, 26 26 }; 27 27 28 28 export default nextConfig;
+17 -17
builder/package.json
··· 1 1 { 2 - "name": "builder", 3 - "scripts": { 4 - "build": "tsup", 5 - "build:watch": "tsup --watch src" 6 - }, 7 - "bin": "dist/index.mjs", 8 - "files": [ 9 - "dist" 10 - ], 11 - "devDependencies": { 12 - "@types/node": "^22.2.0", 13 - "esbuild": "^0.23.0", 14 - "glob": "^11.0.0", 15 - "next": "14.2.5", 16 - "tsup": "^8.2.4", 17 - "typescript": "^5.5.4" 18 - } 2 + "name": "builder", 3 + "scripts": { 4 + "build": "tsup", 5 + "build:watch": "tsup --watch src" 6 + }, 7 + "bin": "dist/index.mjs", 8 + "files": [ 9 + "dist" 10 + ], 11 + "devDependencies": { 12 + "@types/node": "^22.2.0", 13 + "esbuild": "^0.23.0", 14 + "glob": "^11.0.0", 15 + "next": "14.2.5", 16 + "tsup": "^8.2.4", 17 + "typescript": "^5.5.4" 18 + } 19 19 }
+39 -49
builder/src/args.ts
··· 3 3 import { resolve } from "node:path"; 4 4 5 5 export function getArgs(): { 6 - skipBuild: boolean; 7 - outputDir?: string; 6 + skipBuild: boolean; 7 + outputDir?: string; 8 8 } { 9 - const { 10 - values: { skipBuild, output }, 11 - } = parseArgs({ 12 - options: { 13 - skipBuild: { 14 - type: "boolean", 15 - short: "s", 16 - default: false, 17 - }, 18 - output: { 19 - type: "string", 20 - short: "o", 21 - }, 22 - }, 23 - allowPositionals: false, 24 - }); 9 + const { 10 + values: { skipBuild, output }, 11 + } = parseArgs({ 12 + options: { 13 + skipBuild: { 14 + type: "boolean", 15 + short: "s", 16 + default: false, 17 + }, 18 + output: { 19 + type: "string", 20 + short: "o", 21 + }, 22 + }, 23 + allowPositionals: false, 24 + }); 25 25 26 - const outputDir = output ? resolve(output) : undefined; 26 + const outputDir = output ? resolve(output) : undefined; 27 27 28 - if (outputDir) { 29 - assertDirArg(outputDir, "output", true); 30 - } 28 + if (outputDir) { 29 + assertDirArg(outputDir, "output", true); 30 + } 31 31 32 - return { 33 - outputDir, 34 - skipBuild: 35 - skipBuild || 36 - ["1", "true", "yes"].includes(String(process.env.SKIP_NEXT_APP_BUILD)), 37 - }; 32 + return { 33 + outputDir, 34 + skipBuild: skipBuild || ["1", "true", "yes"].includes(String(process.env.SKIP_NEXT_APP_BUILD)), 35 + }; 38 36 } 39 37 40 38 function assertDirArg(path: string, argName?: string, make?: boolean) { 41 - let dirStats: Stats; 42 - try { 43 - dirStats = statSync(path); 44 - } catch { 45 - if (!make) { 46 - throw new Error( 47 - `Error: the provided${ 48 - argName ? ` "${argName}"` : "" 49 - } input is not a valid path` 50 - ); 51 - } 52 - mkdirSync(path); 53 - return; 54 - } 39 + let dirStats: Stats; 40 + try { 41 + dirStats = statSync(path); 42 + } catch { 43 + if (!make) { 44 + throw new Error(`Error: the provided${argName ? ` "${argName}"` : ""} input is not a valid path`); 45 + } 46 + mkdirSync(path); 47 + return; 48 + } 55 49 56 - if (!dirStats.isDirectory()) { 57 - throw new Error( 58 - `Error: the provided${ 59 - argName ? ` "${argName}"` : "" 60 - } input is not a directory` 61 - ); 62 - } 50 + if (!dirStats.isDirectory()) { 51 + throw new Error(`Error: the provided${argName ? ` "${argName}"` : ""} input is not a directory`); 52 + } 63 53 }
+15 -17
builder/src/build/build-next-app.ts
··· 8 8 * @param nextAppDir the directory of the app to build 9 9 */ 10 10 export function buildNextjsApp(nextAppDir: string): void { 11 - runNextBuildCommand("pnpm", nextAppDir); 11 + runNextBuildCommand("pnpm", nextAppDir); 12 12 } 13 13 14 14 // equivalent to: https://github.com/sst/open-next/blob/f61b0e94/packages/open-next/src/build.ts#L175-L186 15 15 function runNextBuildCommand( 16 - // let's keep things simple and just support only pnpm for now 17 - packager: "pnpm" /*"npm" | "yarn" | "pnpm" | "bun"*/, 18 - nextAppDir: string 16 + // let's keep things simple and just support only pnpm for now 17 + packager: "pnpm" /*"npm" | "yarn" | "pnpm" | "bun"*/, 18 + nextAppDir: string 19 19 ) { 20 - const command = ["bun", "npm"].includes(packager) 21 - ? `${packager} next build` 22 - : `${packager} next build`; 23 - execSync(command, { 24 - stdio: "inherit", 25 - cwd: nextAppDir, 26 - env: { 27 - ...process.env, 28 - // equivalent to: https://github.com/sst/open-next/blob/f61b0e9/packages/open-next/src/build.ts#L168-L173 29 - // Equivalent to setting `output: "standalone"` in next.config.js 30 - NEXT_PRIVATE_STANDALONE: "true", 31 - }, 32 - }); 20 + const command = ["bun", "npm"].includes(packager) ? `${packager} next build` : `${packager} next build`; 21 + execSync(command, { 22 + stdio: "inherit", 23 + cwd: nextAppDir, 24 + env: { 25 + ...process.env, 26 + // equivalent to: https://github.com/sst/open-next/blob/f61b0e9/packages/open-next/src/build.ts#L168-L173 27 + // Equivalent to setting `output: "standalone"` in next.config.js 28 + NEXT_PRIVATE_STANDALONE: "true", 29 + }, 30 + }); 33 31 }
+108 -113
builder/src/build/build-worker/index.ts
··· 20 20 * @param nextjsAppPaths 21 21 */ 22 22 export async function buildWorker( 23 - outputDir: string, 24 - nextjsAppPaths: NextjsAppPaths, 25 - templateSrcDir: string 23 + outputDir: string, 24 + nextjsAppPaths: NextjsAppPaths, 25 + templateSrcDir: string 26 26 ): Promise<void> { 27 - const templateDir = copyTemplates(templateSrcDir, nextjsAppPaths); 27 + const templateDir = copyTemplates(templateSrcDir, nextjsAppPaths); 28 28 29 - const workerEntrypoint = `${templateDir}/worker.ts`; 30 - const workerOutputFile = `${outputDir}/index.mjs`; 31 - const nextConfigStr = 32 - readFileSync(nextjsAppPaths.standaloneAppDir + "/server.js", "utf8")?.match( 33 - /const nextConfig = ({.+?})\n/ 34 - )?.[1] ?? {}; 29 + const workerEntrypoint = `${templateDir}/worker.ts`; 30 + const workerOutputFile = `${outputDir}/index.mjs`; 31 + const nextConfigStr = 32 + readFileSync(nextjsAppPaths.standaloneAppDir + "/server.js", "utf8")?.match( 33 + /const nextConfig = ({.+?})\n/ 34 + )?.[1] ?? {}; 35 35 36 - console.log(`\x1b[35m⚙️ Bundling the worker file...\n\x1b[0m`); 36 + console.log(`\x1b[35m⚙️ Bundling the worker file...\n\x1b[0m`); 37 37 38 - patchWranglerDeps(nextjsAppPaths); 39 - updateWebpackChunksFile(nextjsAppPaths); 38 + patchWranglerDeps(nextjsAppPaths); 39 + updateWebpackChunksFile(nextjsAppPaths); 40 40 41 - await build({ 42 - entryPoints: [workerEntrypoint], 43 - bundle: true, 44 - outfile: workerOutputFile, 45 - format: "esm", 46 - target: "esnext", 47 - minify: false, 48 - plugins: [createFixRequiresESBuildPlugin(templateDir)], 49 - alias: { 50 - // Note: we apply an empty shim to next/dist/compiled/ws because it generates two `eval`s: 51 - // eval("require")("bufferutil"); 52 - // eval("require")("utf-8-validate"); 53 - "next/dist/compiled/ws": `${templateDir}/shims/empty.ts`, 54 - // Note: we apply an empty shim to next/dist/compiled/edge-runtime since (amongst others) it generated the following `eval`: 55 - // eval(getModuleCode)(module, module.exports, throwingRequire, params.context, ...Object.values(params.scopedContext)); 56 - // which comes from https://github.com/vercel/edge-runtime/blob/6e96b55f/packages/primitives/src/primitives/load.js#L57-L63 57 - // QUESTION: Why did I encountered this but mhart didn't? 58 - "next/dist/compiled/edge-runtime": `${templateDir}/shims/empty.ts`, 59 - // `@next/env` is a library Next.js uses for loading dotenv files, for obvious reasons we need to stub it here 60 - // source: https://github.com/vercel/next.js/tree/0ac10d79720/packages/next-env 61 - "@next/env": `${templateDir}/shims/env.ts`, 62 - }, 63 - define: { 64 - // config file used by Next.js, see: https://github.com/vercel/next.js/blob/68a7128/packages/next/src/build/utils.ts#L2137-L2139 65 - "process.env.__NEXT_PRIVATE_STANDALONE_CONFIG": 66 - JSON.stringify(nextConfigStr), 67 - // Next.js tried to access __dirname so we need to define it 68 - __dirname: '""', 69 - // Note: we need the __non_webpack_require__ variable declared as it is used by next-server: 70 - // https://github.com/vercel/next.js/blob/be0c3283/packages/next/src/server/next-server.ts#L116-L119 71 - __non_webpack_require__: "require", 72 - // 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 73 - // 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 74 - // that's wht we enable it 75 - "process.env.NEXT_PRIVATE_MINIMAL_MODE": "true", 76 - // Ask mhart if he can explain why the `define`s below are necessary 77 - "process.env.NEXT_RUNTIME": '"nodejs"', 78 - "process.env.NODE_ENV": '"production"', 79 - "process.env.NEXT_MINIMAL": "true", 80 - }, 81 - // We need to set platform to node so that esbuild doesn't complain about the node imports 82 - platform: "node", 83 - banner: { 84 - js: ` 41 + await build({ 42 + entryPoints: [workerEntrypoint], 43 + bundle: true, 44 + outfile: workerOutputFile, 45 + format: "esm", 46 + target: "esnext", 47 + minify: false, 48 + plugins: [createFixRequiresESBuildPlugin(templateDir)], 49 + alias: { 50 + // Note: we apply an empty shim to next/dist/compiled/ws because it generates two `eval`s: 51 + // eval("require")("bufferutil"); 52 + // eval("require")("utf-8-validate"); 53 + "next/dist/compiled/ws": `${templateDir}/shims/empty.ts`, 54 + // Note: we apply an empty shim to next/dist/compiled/edge-runtime since (amongst others) it generated the following `eval`: 55 + // eval(getModuleCode)(module, module.exports, throwingRequire, params.context, ...Object.values(params.scopedContext)); 56 + // which comes from https://github.com/vercel/edge-runtime/blob/6e96b55f/packages/primitives/src/primitives/load.js#L57-L63 57 + // QUESTION: Why did I encountered this but mhart didn't? 58 + "next/dist/compiled/edge-runtime": `${templateDir}/shims/empty.ts`, 59 + // `@next/env` is a library Next.js uses for loading dotenv files, for obvious reasons we need to stub it here 60 + // source: https://github.com/vercel/next.js/tree/0ac10d79720/packages/next-env 61 + "@next/env": `${templateDir}/shims/env.ts`, 62 + }, 63 + define: { 64 + // config file used by Next.js, see: https://github.com/vercel/next.js/blob/68a7128/packages/next/src/build/utils.ts#L2137-L2139 65 + "process.env.__NEXT_PRIVATE_STANDALONE_CONFIG": JSON.stringify(nextConfigStr), 66 + // Next.js tried to access __dirname so we need to define it 67 + __dirname: '""', 68 + // Note: we need the __non_webpack_require__ variable declared as it is used by next-server: 69 + // https://github.com/vercel/next.js/blob/be0c3283/packages/next/src/server/next-server.ts#L116-L119 70 + __non_webpack_require__: "require", 71 + // 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 72 + // 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 73 + // that's wht we enable it 74 + "process.env.NEXT_PRIVATE_MINIMAL_MODE": "true", 75 + // Ask mhart if he can explain why the `define`s below are necessary 76 + "process.env.NEXT_RUNTIME": '"nodejs"', 77 + "process.env.NODE_ENV": '"production"', 78 + "process.env.NEXT_MINIMAL": "true", 79 + }, 80 + // We need to set platform to node so that esbuild doesn't complain about the node imports 81 + platform: "node", 82 + banner: { 83 + js: ` 85 84 ${ 86 - /* 85 + /* 87 86 `__dirname` is used by unbundled js files (which don't inherit the `__dirname` present in the `define` field) 88 87 so we also need to set it on the global scope 89 88 Note: this was hit in the `next/dist/compiled/@opentelemetry/api` module 90 89 */ "" 91 - } 90 + } 92 91 globalThis.__dirname ??= ""; 93 92 94 93 // Do not crash on cache not supported ··· 113 112 globalThis.Request = CustomRequest; 114 113 Request = globalThis.Request; 115 114 `, 116 - }, 117 - }); 115 + }, 116 + }); 118 117 119 - await updateWorkerBundledCode(workerOutputFile, nextjsAppPaths); 118 + await updateWorkerBundledCode(workerOutputFile, nextjsAppPaths); 120 119 121 - console.log(`\x1b[35m⚙️ Copying asset files...\n\x1b[0m`); 122 - await cp( 123 - `${nextjsAppPaths.dotNextDir}/static`, 124 - `${outputDir}/assets/_next/static`, 125 - { 126 - recursive: true, 127 - } 128 - ); 120 + console.log(`\x1b[35m⚙️ Copying asset files...\n\x1b[0m`); 121 + await cp(`${nextjsAppPaths.dotNextDir}/static`, `${outputDir}/assets/_next/static`, { 122 + recursive: true, 123 + }); 129 124 130 - console.log(`\x1b[35mWorker saved in \`${workerOutputFile}\` 🚀\n\x1b[0m`); 125 + console.log(`\x1b[35mWorker saved in \`${workerOutputFile}\` 🚀\n\x1b[0m`); 131 126 } 132 127 133 128 /** ··· 139 134 * @param nextjsAppPaths 140 135 */ 141 136 async function updateWorkerBundledCode( 142 - workerOutputFile: string, 143 - nextjsAppPaths: NextjsAppPaths 137 + workerOutputFile: string, 138 + nextjsAppPaths: NextjsAppPaths 144 139 ): Promise<void> { 145 - const originalCode = await readFile(workerOutputFile, "utf8"); 140 + const originalCode = await readFile(workerOutputFile, "utf8"); 146 141 147 - let patchedCode = originalCode; 142 + let patchedCode = originalCode; 148 143 149 - patchedCode = patchRequire(patchedCode); 150 - patchedCode = patchReadFile(patchedCode, nextjsAppPaths); 151 - patchedCode = patchUrl(patchedCode); 152 - patchedCode = inlineNextRequire(patchedCode, nextjsAppPaths); 153 - patchedCode = patchFindDir(patchedCode, nextjsAppPaths); 154 - patchedCode = inlineEvalManifest(patchedCode, nextjsAppPaths); 144 + patchedCode = patchRequire(patchedCode); 145 + patchedCode = patchReadFile(patchedCode, nextjsAppPaths); 146 + patchedCode = patchUrl(patchedCode); 147 + patchedCode = inlineNextRequire(patchedCode, nextjsAppPaths); 148 + patchedCode = patchFindDir(patchedCode, nextjsAppPaths); 149 + patchedCode = inlineEvalManifest(patchedCode, nextjsAppPaths); 155 150 156 - await writeFile(workerOutputFile, patchedCode); 151 + await writeFile(workerOutputFile, patchedCode); 157 152 } 158 153 159 154 /** ··· 165 160 * so this shows that not everything that's needed to deploy the application is in the output directory... 166 161 */ 167 162 async function updateWebpackChunksFile(nextjsAppPaths: NextjsAppPaths) { 168 - console.log("# updateWebpackChunksFile"); 169 - const webpackRuntimeFile = `${nextjsAppPaths.standaloneAppServerDir}/webpack-runtime.js`; 163 + console.log("# updateWebpackChunksFile"); 164 + const webpackRuntimeFile = `${nextjsAppPaths.standaloneAppServerDir}/webpack-runtime.js`; 170 165 171 - console.log({ webpackRuntimeFile }); 166 + console.log({ webpackRuntimeFile }); 172 167 173 - const fileContent = readFileSync(webpackRuntimeFile, "utf-8"); 168 + const fileContent = readFileSync(webpackRuntimeFile, "utf-8"); 174 169 175 - const chunks = readdirSync(`${nextjsAppPaths.standaloneAppServerDir}/chunks`) 176 - .filter((chunk) => /^\d+\.js$/.test(chunk)) 177 - .map((chunk) => { 178 - console.log(` - chunk ${chunk}`); 179 - return chunk.replace(/\.js$/, ""); 180 - }); 170 + const chunks = readdirSync(`${nextjsAppPaths.standaloneAppServerDir}/chunks`) 171 + .filter((chunk) => /^\d+\.js$/.test(chunk)) 172 + .map((chunk) => { 173 + console.log(` - chunk ${chunk}`); 174 + return chunk.replace(/\.js$/, ""); 175 + }); 181 176 182 - const updatedFileContent = fileContent.replace( 183 - "__webpack_require__.f.require = (chunkId, promises) => {", 184 - `__webpack_require__.f.require = (chunkId, promises) => { 177 + const updatedFileContent = fileContent.replace( 178 + "__webpack_require__.f.require = (chunkId, promises) => {", 179 + `__webpack_require__.f.require = (chunkId, promises) => { 185 180 if (installedChunks[chunkId]) return; 186 181 ${chunks 187 - .map( 188 - (chunk) => ` 182 + .map( 183 + (chunk) => ` 189 184 if (chunkId === ${chunk}) { 190 185 installChunk(require("./chunks/${chunk}.js")); 191 186 return; 192 187 } 193 188 ` 194 - ) 195 - .join("\n")} 189 + ) 190 + .join("\n")} 196 191 ` 197 - ); 192 + ); 198 193 199 - writeFileSync(webpackRuntimeFile, updatedFileContent); 194 + writeFileSync(webpackRuntimeFile, updatedFileContent); 200 195 } 201 196 202 197 function createFixRequiresESBuildPlugin(templateDir: string): Plugin { 203 - return { 204 - name: "replaceRelative", 205 - setup(build) { 206 - // Note: we (empty) shim require-hook modules as they generate problematic code that uses requires 207 - build.onResolve({ filter: /^\.\/require-hook$/ }, (args) => ({ 208 - path: `${templateDir}/shims/empty.ts`, 209 - })); 210 - build.onResolve({ filter: /\.\/lib\/node-fs-methods$/ }, (args) => ({ 211 - path: `${templateDir}/shims/node-fs.ts`, 212 - })); 213 - }, 214 - }; 198 + return { 199 + name: "replaceRelative", 200 + setup(build) { 201 + // Note: we (empty) shim require-hook modules as they generate problematic code that uses requires 202 + build.onResolve({ filter: /^\.\/require-hook$/ }, (args) => ({ 203 + path: `${templateDir}/shims/empty.ts`, 204 + })); 205 + build.onResolve({ filter: /\.\/lib\/node-fs-methods$/ }, (args) => ({ 206 + path: `${templateDir}/shims/node-fs.ts`, 207 + })); 208 + }, 209 + }; 215 210 }
+4 -7
builder/src/build/build-worker/patches/investigated/copy-templates.ts
··· 9 9 * to resolve to files in the the node_module of the standalone app.= 10 10 */ 11 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 - ); 12 + console.log("# copyTemplates"); 13 + const destDir = path.join(nextjsAppPaths.standaloneAppDir, "node_modules/cf/templates"); 17 14 18 - cpSync(srcDir, destDir, { recursive: true }); 19 - return destDir; 15 + cpSync(srcDir, destDir, { recursive: true }); 16 + return destDir; 20 17 }
+2 -4
builder/src/build/build-worker/patches/investigated/patch-require.ts
··· 5 5 * James on Aug 29: `module.createRequire()` is planned. 6 6 */ 7 7 export function patchRequire(code: string): string { 8 - console.log("# patchRequire"); 9 - return code 10 - .replace(/__require\d?\(/g, "require(") 11 - .replace(/__require\d?\./g, "require."); 8 + console.log("# patchRequire"); 9 + return code.replace(/__require\d?\(/g, "require(").replace(/__require\d?\./g, "require."); 12 10 }
+5 -5
builder/src/build/build-worker/patches/investigated/patch-url.ts
··· 5 5 * Hopefully this should not be necessary after this unenv PR lands: https://github.com/unjs/unenv/pull/292 6 6 */ 7 7 export function patchUrl(code: string): string { 8 - console.log("# patchUrl"); 9 - return code.replace( 10 - / ([a-zA-Z0-9_]+) = require\("url"\);/g, 11 - ` $1 = require("url"); 8 + console.log("# patchUrl"); 9 + return code.replace( 10 + / ([a-zA-Z0-9_]+) = require\("url"\);/g, 11 + ` $1 = require("url"); 12 12 const nodeUrl = require("node-url"); 13 13 $1.parse = nodeUrl.parse.bind(nodeUrl); 14 14 $1.format = nodeUrl.format.bind(nodeUrl); ··· 17 17 return new URL("file://" + path); 18 18 } 19 19 ` 20 - ); 20 + ); 21 21 }
+17 -23
builder/src/build/build-worker/patches/to-investigate/inline-eval-manifest.ts
··· 8 8 * Note: we could/should probably just patch readFileSync here or something, but here the issue is that after the readFileSync call 9 9 * 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) 10 10 */ 11 - export function inlineEvalManifest( 12 - code: string, 13 - nextjsAppPaths: NextjsAppPaths 14 - ): string { 15 - console.log("# inlineEvalManifest"); 16 - const manifestJss = globSync( 17 - `${nextjsAppPaths.standaloneAppDotNextDir}/**/*_client-reference-manifest.js` 18 - ).map((file) => file.replace(`${nextjsAppPaths.standaloneAppDir}/`, "")); 19 - return code.replace( 20 - /function evalManifest\((.+?), .+?\) {/, 21 - `$& 11 + export function inlineEvalManifest(code: string, nextjsAppPaths: NextjsAppPaths): string { 12 + console.log("# inlineEvalManifest"); 13 + const manifestJss = globSync( 14 + `${nextjsAppPaths.standaloneAppDotNextDir}/**/*_client-reference-manifest.js` 15 + ).map((file) => file.replace(`${nextjsAppPaths.standaloneAppDir}/`, "")); 16 + return code.replace( 17 + /function evalManifest\((.+?), .+?\) {/, 18 + `$& 22 19 ${manifestJss 23 - .map( 24 - (manifestJs) => ` 20 + .map( 21 + (manifestJs) => ` 25 22 if ($1.endsWith("${manifestJs}")) { 26 23 require("${nextjsAppPaths.standaloneAppDir}/${manifestJs}"); 27 24 return { 28 25 __RSC_MANIFEST: { 29 26 "${manifestJs 30 - .replace(".next/server/app", "") 31 - .replace( 32 - "_client-reference-manifest.js", 33 - "" 34 - )}": globalThis.__RSC_MANIFEST["${manifestJs 35 - .replace(".next/server/app", "") 36 - .replace("_client-reference-manifest.js", "")}"], 27 + .replace(".next/server/app", "") 28 + .replace("_client-reference-manifest.js", "")}": globalThis.__RSC_MANIFEST["${manifestJs 29 + .replace(".next/server/app", "") 30 + .replace("_client-reference-manifest.js", "")}"], 37 31 }, 38 32 }; 39 33 } 40 34 ` 41 - ) 42 - .join("\n")} 35 + ) 36 + .join("\n")} 43 37 throw new Error("Unknown evalManifest: " + $1); 44 38 ` 45 - ); 39 + ); 46 40 }
+30 -38
builder/src/build/build-worker/patches/to-investigate/inline-next-require.ts
··· 5 5 * The following avoid various Next.js specific files `require`d at runtime since we can just read 6 6 * and inline their content during build time 7 7 */ 8 - export function inlineNextRequire( 9 - code: string, 10 - nextjsAppPaths: NextjsAppPaths 11 - ) { 12 - console.log("# inlineNextRequire"); 13 - const pagesManifestFile = `${nextjsAppPaths.standaloneAppServerDir}/pages-manifest.json`; 14 - const appPathsManifestFile = `${nextjsAppPaths.standaloneAppServerDir}/app-paths-manifest.json`; 8 + export function inlineNextRequire(code: string, nextjsAppPaths: NextjsAppPaths) { 9 + console.log("# inlineNextRequire"); 10 + const pagesManifestFile = `${nextjsAppPaths.standaloneAppServerDir}/pages-manifest.json`; 11 + const appPathsManifestFile = `${nextjsAppPaths.standaloneAppServerDir}/app-paths-manifest.json`; 15 12 16 - const pagesManifestFiles = existsSync(pagesManifestFile) 17 - ? Object.values(JSON.parse(readFileSync(pagesManifestFile, "utf-8"))).map( 18 - (file) => ".next/server/" + file 19 - ) 20 - : []; 21 - const appPathsManifestFiles = existsSync(appPathsManifestFile) 22 - ? Object.values( 23 - JSON.parse(readFileSync(appPathsManifestFile, "utf-8")) 24 - ).map((file) => ".next/server/" + file) 25 - : []; 26 - const allManifestFiles = pagesManifestFiles.concat(appPathsManifestFiles); 13 + const pagesManifestFiles = existsSync(pagesManifestFile) 14 + ? Object.values(JSON.parse(readFileSync(pagesManifestFile, "utf-8"))).map( 15 + (file) => ".next/server/" + file 16 + ) 17 + : []; 18 + const appPathsManifestFiles = existsSync(appPathsManifestFile) 19 + ? Object.values(JSON.parse(readFileSync(appPathsManifestFile, "utf-8"))).map( 20 + (file) => ".next/server/" + file 21 + ) 22 + : []; 23 + const allManifestFiles = pagesManifestFiles.concat(appPathsManifestFiles); 27 24 28 - const htmlPages = allManifestFiles.filter((file) => file.endsWith(".html")); 29 - const pageModules = allManifestFiles.filter((file) => file.endsWith(".js")); 25 + const htmlPages = allManifestFiles.filter((file) => file.endsWith(".html")); 26 + const pageModules = allManifestFiles.filter((file) => file.endsWith(".js")); 30 27 31 - return code.replace( 32 - /const pagePath = getPagePath\(.+?\);/, 33 - `$& 28 + return code.replace( 29 + /const pagePath = getPagePath\(.+?\);/, 30 + `$& 34 31 ${htmlPages 35 - .map( 36 - (htmlPage) => ` 32 + .map( 33 + (htmlPage) => ` 37 34 if (pagePath.endsWith("${htmlPage}")) { 38 - return ${JSON.stringify( 39 - readFileSync( 40 - `${nextjsAppPaths.standaloneAppDir}/${htmlPage}`, 41 - "utf-8" 42 - ) 43 - )}; 35 + return ${JSON.stringify(readFileSync(`${nextjsAppPaths.standaloneAppDir}/${htmlPage}`, "utf-8"))}; 44 36 } 45 37 ` 46 - ) 47 - .join("\n")} 38 + ) 39 + .join("\n")} 48 40 ${pageModules 49 - .map( 50 - (module) => ` 41 + .map( 42 + (module) => ` 51 43 if (pagePath.endsWith("${module}")) { 52 44 return require("${nextjsAppPaths.standaloneAppDir}/${module}"); 53 45 } 54 46 ` 55 - ) 56 - .join("\n")} 47 + ) 48 + .join("\n")} 57 49 throw new Error("Unknown pagePath: " + pagePath); 58 50 ` 59 - ); 51 + ); 60 52 }
+8 -15
builder/src/build/build-worker/patches/to-investigate/patch-find-dir.ts
··· 7 7 * (usage source: https://github.com/vercel/next.js/blob/ba995993/packages/next/src/server/next-server.ts#L450-L451) 8 8 * Note: `findDir` uses `fs.existsSync` under the hood, so patching that should be enough to make this work 9 9 */ 10 - export function patchFindDir( 11 - code: string, 12 - nextjsAppPaths: NextjsAppPaths 13 - ): string { 14 - console.log("# patchFindDir"); 15 - return code.replace( 16 - "function findDir(dir, name) {", 17 - `function findDir(dir, name) { 10 + export function patchFindDir(code: string, nextjsAppPaths: NextjsAppPaths): string { 11 + console.log("# patchFindDir"); 12 + return code.replace( 13 + "function findDir(dir, name) {", 14 + `function findDir(dir, name) { 18 15 if (dir.endsWith(".next/server")) { 19 - if (name === "app") return ${existsSync( 20 - `${nextjsAppPaths.standaloneAppServerDir}/app` 21 - )}; 22 - if (name === "pages") return ${existsSync( 23 - `${nextjsAppPaths.standaloneAppServerDir}/pages` 24 - )}; 16 + if (name === "app") return ${existsSync(`${nextjsAppPaths.standaloneAppServerDir}/app`)}; 17 + if (name === "pages") return ${existsSync(`${nextjsAppPaths.standaloneAppServerDir}/pages`)}; 25 18 } 26 19 throw new Error("Unknown findDir call: " + dir + " " + name); 27 20 ` 28 - ); 21 + ); 29 22 }
+27 -38
builder/src/build/build-worker/patches/to-investigate/patch-read-file.ts
··· 2 2 import { globSync } from "glob"; 3 3 import { NextjsAppPaths } from "../../../../nextjs-paths"; 4 4 5 - export function patchReadFile( 6 - code: string, 7 - nextjsAppPaths: NextjsAppPaths 8 - ): string { 9 - console.log("# patchReadFile"); 10 - // The next-server code gets the buildId from the filesystem, resulting in a `[unenv] fs.readFileSync is not implemented yet!` error 11 - // so we add an early return to the `getBuildId` function so that the `readyFileSync` is never encountered 12 - // (source: https://github.com/vercel/next.js/blob/15aeb92efb34c09a36/packages/next/src/server/next-server.ts#L438-L451) 13 - // Note: we could/should probably just patch readFileSync here or something! 14 - code = code.replace( 15 - "getBuildId() {", 16 - `getBuildId() { 17 - return ${JSON.stringify( 18 - readFileSync( 19 - `${nextjsAppPaths.standaloneAppDotNextDir}/BUILD_ID`, 20 - "utf-8" 21 - ) 22 - )}; 5 + export function patchReadFile(code: string, nextjsAppPaths: NextjsAppPaths): string { 6 + console.log("# patchReadFile"); 7 + // The next-server code gets the buildId from the filesystem, resulting in a `[unenv] fs.readFileSync is not implemented yet!` error 8 + // so we add an early return to the `getBuildId` function so that the `readyFileSync` is never encountered 9 + // (source: https://github.com/vercel/next.js/blob/15aeb92efb34c09a36/packages/next/src/server/next-server.ts#L438-L451) 10 + // Note: we could/should probably just patch readFileSync here or something! 11 + code = code.replace( 12 + "getBuildId() {", 13 + `getBuildId() { 14 + return ${JSON.stringify(readFileSync(`${nextjsAppPaths.standaloneAppDotNextDir}/BUILD_ID`, "utf-8"))}; 23 15 ` 24 - ); 16 + ); 25 17 26 - // Same as above, the next-server code loads the manifests with `readyFileSync` and we want to avoid that 27 - // (source: https://github.com/vercel/next.js/blob/15aeb92e/packages/next/src/server/load-manifest.ts#L34-L56) 28 - // Note: we could/should probably just patch readFileSync here or something! 29 - const manifestJsons = globSync( 30 - `${nextjsAppPaths.standaloneAppDotNextDir}/**/*-manifest.json` 31 - ).map((file) => file.replace(nextjsAppPaths.standaloneAppDir + "/", "")); 32 - code = code.replace( 33 - /function loadManifest\((.+?), .+?\) {/, 34 - `$& 18 + // Same as above, the next-server code loads the manifests with `readyFileSync` and we want to avoid that 19 + // (source: https://github.com/vercel/next.js/blob/15aeb92e/packages/next/src/server/load-manifest.ts#L34-L56) 20 + // Note: we could/should probably just patch readFileSync here or something! 21 + const manifestJsons = globSync(`${nextjsAppPaths.standaloneAppDotNextDir}/**/*-manifest.json`).map((file) => 22 + file.replace(nextjsAppPaths.standaloneAppDir + "/", "") 23 + ); 24 + code = code.replace( 25 + /function loadManifest\((.+?), .+?\) {/, 26 + `$& 35 27 ${manifestJsons 36 - .map( 37 - (manifestJson) => ` 28 + .map( 29 + (manifestJson) => ` 38 30 if ($1.endsWith("${manifestJson}")) { 39 - return ${readFileSync( 40 - `${nextjsAppPaths.standaloneAppDir}/${manifestJson}`, 41 - "utf-8" 42 - )}; 31 + return ${readFileSync(`${nextjsAppPaths.standaloneAppDir}/${manifestJson}`, "utf-8")}; 43 32 } 44 33 ` 45 - ) 46 - .join("\n")} 34 + ) 35 + .join("\n")} 47 36 throw new Error("Unknown loadManifest: " + $1); 48 37 ` 49 - ); 38 + ); 50 39 51 - return code; 40 + return code; 52 41 }
+46 -49
builder/src/build/build-worker/patches/to-investigate/wrangler-deps.ts
··· 3 3 import { NextjsAppPaths } from "../../../../nextjs-paths"; 4 4 5 5 export function patchWranglerDeps(paths: NextjsAppPaths) { 6 - console.log("# patchWranglerDeps"); 6 + console.log("# patchWranglerDeps"); 7 7 8 - console.log({ base: paths.standaloneAppDotNextDir }); 8 + console.log({ base: paths.standaloneAppDotNextDir }); 9 9 10 - // Patch .next/standalone/node_modules/next/dist/compiled/next-server/pages.runtime.prod.js 11 - // 12 - // Remove the need for an alias in wrangler.toml: 13 - // 14 - // [alias] 15 - // # critters is `require`d from `pages.runtime.prod.js` when running wrangler dev, so we need to stub it out 16 - // "critters" = "./.next/standalone/node_modules/cf/templates/shims/empty.ts" 17 - const pagesRuntimeFile = path.join( 18 - paths.standaloneAppDir, 19 - "node_modules", 20 - "next", 21 - "dist", 22 - "compiled", 23 - "next-server", 24 - "pages.runtime.prod.js" 25 - ); 10 + // Patch .next/standalone/node_modules/next/dist/compiled/next-server/pages.runtime.prod.js 11 + // 12 + // Remove the need for an alias in wrangler.toml: 13 + // 14 + // [alias] 15 + // # critters is `require`d from `pages.runtime.prod.js` when running wrangler dev, so we need to stub it out 16 + // "critters" = "./.next/standalone/node_modules/cf/templates/shims/empty.ts" 17 + const pagesRuntimeFile = path.join( 18 + paths.standaloneAppDir, 19 + "node_modules", 20 + "next", 21 + "dist", 22 + "compiled", 23 + "next-server", 24 + "pages.runtime.prod.js" 25 + ); 26 26 27 - const patchedPagesRuntime = fs 28 - .readFileSync(pagesRuntimeFile, "utf-8") 29 - .replace(`e.exports=require("critters")`, `e.exports={}`); 27 + const patchedPagesRuntime = fs 28 + .readFileSync(pagesRuntimeFile, "utf-8") 29 + .replace(`e.exports=require("critters")`, `e.exports={}`); 30 30 31 - fs.writeFileSync(pagesRuntimeFile, patchedPagesRuntime); 31 + fs.writeFileSync(pagesRuntimeFile, patchedPagesRuntime); 32 32 33 - // Patch .next/standalone/node_modules/next/dist/server/lib/trace/tracer.js 34 - // 35 - // Remove the need for an alias in wrangler.toml: 36 - // 37 - // [alias] 38 - // # @opentelemetry/api is `require`d when running wrangler dev, so we need to stub it out 39 - // # IMPORTANT: we shim @opentelemetry/api to the throwing shim so that it will throw right away, this is so that we throw inside the 40 - // # try block here: https://github.com/vercel/next.js/blob/9e8266a7/packages/next/src/server/lib/trace/tracer.ts#L27-L31 41 - // # causing the code to require the 'next/dist/compiled/@opentelemetry/api' module instead (which properly works) 42 - // #"@opentelemetry/api" = "./.next/standalone/node_modules/cf/templates/shims/throw.ts" 43 - const tracerFile = path.join( 44 - paths.standaloneAppDir, 45 - "node_modules", 46 - "next", 47 - "dist", 48 - "server", 49 - "lib", 50 - "trace", 51 - "tracer.js" 52 - ); 33 + // Patch .next/standalone/node_modules/next/dist/server/lib/trace/tracer.js 34 + // 35 + // Remove the need for an alias in wrangler.toml: 36 + // 37 + // [alias] 38 + // # @opentelemetry/api is `require`d when running wrangler dev, so we need to stub it out 39 + // # IMPORTANT: we shim @opentelemetry/api to the throwing shim so that it will throw right away, this is so that we throw inside the 40 + // # try block here: https://github.com/vercel/next.js/blob/9e8266a7/packages/next/src/server/lib/trace/tracer.ts#L27-L31 41 + // # causing the code to require the 'next/dist/compiled/@opentelemetry/api' module instead (which properly works) 42 + // #"@opentelemetry/api" = "./.next/standalone/node_modules/cf/templates/shims/throw.ts" 43 + const tracerFile = path.join( 44 + paths.standaloneAppDir, 45 + "node_modules", 46 + "next", 47 + "dist", 48 + "server", 49 + "lib", 50 + "trace", 51 + "tracer.js" 52 + ); 53 53 54 - const pacthedTracer = fs 55 - .readFileSync(tracerFile, "utf-8") 56 - .replaceAll( 57 - /\w+\s*=\s*require\([^/]*opentelemetry.*\)/g, 58 - `throw new Error("@opentelemetry/api")` 59 - ); 54 + const pacthedTracer = fs 55 + .readFileSync(tracerFile, "utf-8") 56 + .replaceAll(/\w+\s*=\s*require\([^/]*opentelemetry.*\)/g, `throw new Error("@opentelemetry/api")`); 60 57 61 - writeFileSync(tracerFile, pacthedTracer); 58 + writeFileSync(tracerFile, pacthedTracer); 62 59 }
+44 -48
builder/src/build/build-worker/templates/shims/node-fs.ts
··· 1 1 // https://github.com/vercel/next.js/blob/canary/packages/next/src/server/lib/node-fs-methods.ts 2 2 3 3 export const nodeFs = { 4 - existsSync, 5 - readFile, 6 - readFileSync, 7 - writeFile, 8 - mkdir, 9 - stat, 4 + existsSync, 5 + readFile, 6 + readFileSync, 7 + writeFile, 8 + mkdir, 9 + stat, 10 10 }; 11 11 12 12 const FILES = new Map<string, unknown>(); 13 13 const MTIME = Date.now(); 14 14 15 15 function existsSync(path: string) { 16 - console.log( 17 - "existsSync", 18 - path, 19 - new Error().stack?.split("\n").slice(1).join("\n") 20 - ); 21 - return FILES.has(path); 16 + console.log("existsSync", path, new Error().stack?.split("\n").slice(1).join("\n")); 17 + return FILES.has(path); 22 18 } 23 19 24 20 async function readFile(path: string, options: unknown): Promise<any> { 25 - console.log( 26 - "readFile", 27 - { path, options } 28 - // new Error().stack.split("\n").slice(1).join("\n"), 29 - ); 30 - if (!FILES.has(path)) { 31 - throw new Error(path + "does not exist"); 32 - } 33 - return FILES.get(path); 21 + console.log( 22 + "readFile", 23 + { path, options } 24 + // new Error().stack.split("\n").slice(1).join("\n"), 25 + ); 26 + if (!FILES.has(path)) { 27 + throw new Error(path + "does not exist"); 28 + } 29 + return FILES.get(path); 34 30 } 35 31 36 32 function readFileSync(path: string, options: unknown) { 37 - console.log( 38 - "readFileSync", 39 - { path, options } 40 - // new Error().stack.split("\n").slice(1).join("\n"), 41 - ); 42 - if (!FILES.has(path)) { 43 - throw new Error(path + "does not exist"); 44 - } 45 - return FILES.get(path); 33 + console.log( 34 + "readFileSync", 35 + { path, options } 36 + // new Error().stack.split("\n").slice(1).join("\n"), 37 + ); 38 + if (!FILES.has(path)) { 39 + throw new Error(path + "does not exist"); 40 + } 41 + return FILES.get(path); 46 42 } 47 43 48 44 async function writeFile(file: string, data: unknown) { 49 - console.log( 50 - "writeFile", 51 - { file, data } 52 - // new Error().stack.split("\n").slice(1).join("\n"), 53 - ); 54 - FILES.set(file, data); 55 - return true; 45 + console.log( 46 + "writeFile", 47 + { file, data } 48 + // new Error().stack.split("\n").slice(1).join("\n"), 49 + ); 50 + FILES.set(file, data); 51 + return true; 56 52 } 57 53 58 54 async function mkdir(dir: string) { 59 - console.log( 60 - "mkdir", 61 - dir 62 - //new Error().stack.split("\n").slice(1).join("\n"), 63 - ); 55 + console.log( 56 + "mkdir", 57 + dir 58 + //new Error().stack.split("\n").slice(1).join("\n"), 59 + ); 64 60 } 65 61 66 62 async function stat(file: string) { 67 - console.log( 68 - "stat", 69 - file 70 - // new Error().stack.split("\n").slice(1).join("\n"), 71 - ); 72 - return { mtime: new Date(MTIME) }; 63 + console.log( 64 + "stat", 65 + file 66 + // new Error().stack.split("\n").slice(1).join("\n"), 67 + ); 68 + return { mtime: new Date(MTIME) }; 73 69 }
+55 -67
builder/src/build/build-worker/templates/worker.ts
··· 1 1 import { Readable } from "node:stream"; 2 2 3 3 import type { NextConfig } from "next"; 4 - import { 5 - NodeNextRequest, 6 - NodeNextResponse, 7 - } from "next/dist/server/base-http/node"; 4 + import { NodeNextRequest, NodeNextResponse } from "next/dist/server/base-http/node"; 8 5 import { createRequestResponseMocks } from "next/dist/server/lib/mock-request"; 9 - import NextNodeServer, { 10 - NodeRequestHandler, 11 - } from "next/dist/server/next-server"; 6 + import NextNodeServer, { NodeRequestHandler } from "next/dist/server/next-server"; 12 7 13 8 /** 14 9 * Injected at build time 15 10 * (we practically follow what Next.js does here: 16 11 https://github.com/vercel/next.js/blob/68a7128/packages/next/src/build/utils.ts#L2137-L2139) 17 12 */ 18 - const nextConfig: NextConfig = JSON.parse( 19 - process.env.__NEXT_PRIVATE_STANDALONE_CONFIG ?? "{}" 20 - ); 13 + const nextConfig: NextConfig = JSON.parse(process.env.__NEXT_PRIVATE_STANDALONE_CONFIG ?? "{}"); 21 14 22 15 let requestHandler: NodeRequestHandler | null = null; 23 16 24 17 export default { 25 - async fetch(request: Request, env: any, ctx: any) { 26 - if (requestHandler == null) { 27 - globalThis.process.env = { ...globalThis.process.env, ...env }; 28 - requestHandler = new NextNodeServer({ 29 - conf: { ...nextConfig, env }, 30 - customServer: false, 31 - dev: false, 32 - dir: "", 33 - }).getRequestHandler(); 34 - } 18 + async fetch(request: Request, env: any, ctx: any) { 19 + if (requestHandler == null) { 20 + globalThis.process.env = { ...globalThis.process.env, ...env }; 21 + requestHandler = new NextNodeServer({ 22 + conf: { ...nextConfig, env }, 23 + customServer: false, 24 + dev: false, 25 + dir: "", 26 + }).getRequestHandler(); 27 + } 35 28 36 - const url = new URL(request.url); 29 + const url = new URL(request.url); 37 30 38 - if (url.pathname === "/_next/image") { 39 - let imageUrl = 40 - url.searchParams.get("url") ?? 41 - "https://developers.cloudflare.com/_astro/logo.BU9hiExz.svg"; 42 - if (imageUrl.startsWith("/")) { 43 - imageUrl = new URL(imageUrl, request.url).href; 44 - } 45 - return fetch(imageUrl, { cf: { cacheEverything: true } } as any); 46 - } 31 + if (url.pathname === "/_next/image") { 32 + let imageUrl = 33 + url.searchParams.get("url") ?? "https://developers.cloudflare.com/_astro/logo.BU9hiExz.svg"; 34 + if (imageUrl.startsWith("/")) { 35 + imageUrl = new URL(imageUrl, request.url).href; 36 + } 37 + return fetch(imageUrl, { cf: { cacheEverything: true } } as any); 38 + } 47 39 48 - const resBody = new TransformStream(); 49 - const writer = resBody.writable.getWriter(); 40 + const resBody = new TransformStream(); 41 + const writer = resBody.writable.getWriter(); 50 42 51 - const reqBodyNodeStream = request.body 52 - ? Readable.fromWeb(request.body as any) 53 - : undefined; 43 + const reqBodyNodeStream = request.body ? Readable.fromWeb(request.body as any) : undefined; 54 44 55 - const { req, res } = createRequestResponseMocks({ 56 - method: request.method, 57 - url: url.href.slice(url.origin.length), 58 - headers: Object.fromEntries([...request.headers]), 59 - bodyReadable: reqBodyNodeStream, 60 - resWriter: (chunk) => { 61 - writer.write(chunk).catch(console.error); 62 - return true; 63 - }, 64 - }); 45 + const { req, res } = createRequestResponseMocks({ 46 + method: request.method, 47 + url: url.href.slice(url.origin.length), 48 + headers: Object.fromEntries([...request.headers]), 49 + bodyReadable: reqBodyNodeStream, 50 + resWriter: (chunk) => { 51 + writer.write(chunk).catch(console.error); 52 + return true; 53 + }, 54 + }); 65 55 66 - let headPromiseResolve: any = null; 67 - const headPromise = new Promise<void>((resolve) => { 68 - headPromiseResolve = resolve; 69 - }); 70 - res.flushHeaders = () => headPromiseResolve?.(); 56 + let headPromiseResolve: any = null; 57 + const headPromise = new Promise<void>((resolve) => { 58 + headPromiseResolve = resolve; 59 + }); 60 + res.flushHeaders = () => headPromiseResolve?.(); 71 61 72 - if (reqBodyNodeStream != null) { 73 - const origPush = reqBodyNodeStream.push; 74 - reqBodyNodeStream.push = (chunk: any) => { 75 - req.push(chunk); 76 - return origPush.call(reqBodyNodeStream, chunk); 77 - }; 78 - } 62 + if (reqBodyNodeStream != null) { 63 + const origPush = reqBodyNodeStream.push; 64 + reqBodyNodeStream.push = (chunk: any) => { 65 + req.push(chunk); 66 + return origPush.call(reqBodyNodeStream, chunk); 67 + }; 68 + } 79 69 80 - ctx.waitUntil((res as any).hasStreamed.then(() => writer.close())); 70 + ctx.waitUntil((res as any).hasStreamed.then(() => writer.close())); 81 71 82 - ctx.waitUntil( 83 - requestHandler(new NodeNextRequest(req), new NodeNextResponse(res)) 84 - ); 72 + ctx.waitUntil(requestHandler(new NodeNextRequest(req), new NodeNextResponse(res))); 85 73 86 - await Promise.race([res.headPromise, headPromise]); 74 + await Promise.race([res.headPromise, headPromise]); 87 75 88 - return new Response(resBody.readable, { 89 - status: res.statusCode, 90 - headers: (res as any).headers, 91 - }); 92 - }, 76 + return new Response(resBody.readable, { 77 + status: res.statusCode, 78 + headers: (res as any).headers, 79 + }); 80 + }, 93 81 };
+27 -33
builder/src/build/index.ts
··· 17 17 * @param opts.outputDir the directory where to save the output (defaults to the app's directory) 18 18 * @param opts.skipBuild boolean indicating whether the Next.js build should be skipped (i.e. if the `.next` dir is already built) 19 19 */ 20 - export async function build( 21 - inputNextAppDir: string, 22 - opts: BuildOptions 23 - ): Promise<void> { 24 - if (!opts.skipBuild) { 25 - // Build the next app and save a copy in .save.next 26 - buildNextjsApp(inputNextAppDir); 27 - rmSync(`${inputNextAppDir}/${SAVE_DIR}`, { 28 - recursive: true, 29 - force: true, 30 - }); 31 - cpSync(`${inputNextAppDir}/.next`, `${inputNextAppDir}/${SAVE_DIR}`, { 32 - recursive: true, 33 - }); 34 - } else { 35 - // Skip the next build and restore the copy from .next.save 36 - rmSync(`${inputNextAppDir}/.next`, { recursive: true, force: true }); 37 - cpSync(`${inputNextAppDir}/${SAVE_DIR}`, `${inputNextAppDir}/.next`, { 38 - recursive: true, 39 - }); 40 - } 20 + export async function build(inputNextAppDir: string, opts: BuildOptions): Promise<void> { 21 + if (!opts.skipBuild) { 22 + // Build the next app and save a copy in .save.next 23 + buildNextjsApp(inputNextAppDir); 24 + rmSync(`${inputNextAppDir}/${SAVE_DIR}`, { 25 + recursive: true, 26 + force: true, 27 + }); 28 + cpSync(`${inputNextAppDir}/.next`, `${inputNextAppDir}/${SAVE_DIR}`, { 29 + recursive: true, 30 + }); 31 + } else { 32 + // Skip the next build and restore the copy from .next.save 33 + rmSync(`${inputNextAppDir}/.next`, { recursive: true, force: true }); 34 + cpSync(`${inputNextAppDir}/${SAVE_DIR}`, `${inputNextAppDir}/.next`, { 35 + recursive: true, 36 + }); 37 + } 41 38 42 - const outputDir = `${opts.outputDir ?? inputNextAppDir}/.worker-next`; 43 - await cleanDirectory(outputDir); 39 + const outputDir = `${opts.outputDir ?? inputNextAppDir}/.worker-next`; 40 + await cleanDirectory(outputDir); 44 41 45 - const nextjsAppPaths = getNextjsAppPaths(inputNextAppDir); 42 + const nextjsAppPaths = getNextjsAppPaths(inputNextAppDir); 46 43 47 - const templateDir = path.join( 48 - path.dirname(fileURLToPath(import.meta.url)), 49 - "templates" 50 - ); 44 + const templateDir = path.join(path.dirname(fileURLToPath(import.meta.url)), "templates"); 51 45 52 - console.log({ outputDir, nextjsAppPaths, templateDir }); 46 + console.log({ outputDir, nextjsAppPaths, templateDir }); 53 47 54 - await buildWorker(outputDir, nextjsAppPaths, templateDir); 48 + await buildWorker(outputDir, nextjsAppPaths, templateDir); 55 49 } 56 50 57 51 type BuildOptions = { 58 - skipBuild: boolean; 59 - outputDir?: string; 52 + skipBuild: boolean; 53 + outputDir?: string; 60 54 }; 61 55 62 56 async function cleanDirectory(path: string): Promise<void> { 63 - return await rm(path, { recursive: true, force: true }); 57 + return await rm(path, { recursive: true, force: true }); 64 58 }
+5 -7
builder/src/index.ts
··· 7 7 8 8 console.log({ inputNextAppDir }); 9 9 10 - if ( 11 - !["js", "cjs", "mjs", "ts"].some((ext) => existsSync(`./next.config.${ext}`)) 12 - ) { 13 - // TODO: we can add more validation later 14 - throw new Error("Error: Not in a Next.js app project"); 10 + if (!["js", "cjs", "mjs", "ts"].some((ext) => existsSync(`./next.config.${ext}`))) { 11 + // TODO: we can add more validation later 12 + throw new Error("Error: Not in a Next.js app project"); 15 13 } 16 14 17 15 const { skipBuild, outputDir } = getArgs(); 18 16 19 17 await build(inputNextAppDir, { 20 - outputDir, 21 - skipBuild: !!skipBuild, 18 + outputDir, 19 + skipBuild: !!skipBuild, 22 20 });
+56 -58
builder/src/nextjs-paths.ts
··· 7 7 * NOTE: WIP, we still need to discern which paths are relevant here! 8 8 */ 9 9 export type NextjsAppPaths = { 10 - appDir: string; 11 - /** 12 - * The path to the application's `.next` directory (where `next build` saves the build output) 13 - */ 14 - dotNextDir: string; 10 + appDir: string; 11 + /** 12 + * The path to the application's `.next` directory (where `next build` saves the build output) 13 + */ 14 + dotNextDir: string; 15 15 16 - /** 17 - * The path to the application standalone directory (where `next build` saves the standalone app when standalone mode is used) 18 - */ 19 - standaloneAppDir: string; 16 + /** 17 + * The path to the application standalone directory (where `next build` saves the standalone app when standalone mode is used) 18 + */ 19 + standaloneAppDir: string; 20 20 21 - /** 22 - * the path to the `.next` directory specific to the standalone application 23 - */ 24 - standaloneAppDotNextDir: string; 21 + /** 22 + * the path to the `.next` directory specific to the standalone application 23 + */ 24 + standaloneAppDotNextDir: string; 25 25 26 - /** 27 - * the path to the `server` directory specific to the standalone application 28 - */ 29 - standaloneAppServerDir: string; 26 + /** 27 + * the path to the `server` directory specific to the standalone application 28 + */ 29 + standaloneAppServerDir: string; 30 30 }; 31 31 32 32 /** ··· 36 36 * @returns the various paths. 37 37 */ 38 38 export function getNextjsAppPaths(nextAppDir: string): NextjsAppPaths { 39 - const dotNextDir = getDotNextDirPath(nextAppDir); 39 + const dotNextDir = getDotNextDirPath(nextAppDir); 40 40 41 - const appPath = getNextjsApplicationPath(dotNextDir).replace(/\/$/, ""); 41 + const appPath = getNextjsApplicationPath(dotNextDir).replace(/\/$/, ""); 42 42 43 - const standaloneAppDir = path.join(dotNextDir, "standalone", appPath); 43 + const standaloneAppDir = path.join(dotNextDir, "standalone", appPath); 44 44 45 - return { 46 - appDir: nextAppDir, 47 - dotNextDir, 48 - standaloneAppDir, 49 - standaloneAppDotNextDir: path.join(standaloneAppDir, ".next"), 50 - standaloneAppServerDir: path.join(standaloneAppDir, ".next", "server"), 51 - }; 45 + return { 46 + appDir: nextAppDir, 47 + dotNextDir, 48 + standaloneAppDir, 49 + standaloneAppDotNextDir: path.join(standaloneAppDir, ".next"), 50 + standaloneAppServerDir: path.join(standaloneAppDir, ".next", "server"), 51 + }; 52 52 } 53 53 54 54 function getDotNextDirPath(nextAppDir: string): string { 55 - const dotNextDirPath = `${nextAppDir}/.next`; 55 + const dotNextDirPath = `${nextAppDir}/.next`; 56 56 57 - try { 58 - const dirStats = statSync(dotNextDirPath); 59 - if (!dirStats.isDirectory()) throw new Error(); 60 - } catch { 61 - throw new Error(`Error: \`.next\` directory not found!`); 62 - } 57 + try { 58 + const dirStats = statSync(dotNextDirPath); 59 + if (!dirStats.isDirectory()) throw new Error(); 60 + } catch { 61 + throw new Error(`Error: \`.next\` directory not found!`); 62 + } 63 63 64 - return dotNextDirPath; 64 + return dotNextDirPath; 65 65 } 66 66 67 67 /** ··· 74 74 * and the function here given the `dotNextDir` returns `next-apps/api` 75 75 */ 76 76 function getNextjsApplicationPath(dotNextDir: string): string { 77 - const serverPath = findServerParentPath(dotNextDir); 77 + const serverPath = findServerParentPath(dotNextDir); 78 78 79 - if (!serverPath) { 80 - throw new Error( 81 - `Unexpected Error: no \`.next/server\` folder could be found in \`${serverPath}\`` 82 - ); 83 - } 79 + if (!serverPath) { 80 + throw new Error(`Unexpected Error: no \`.next/server\` folder could be found in \`${serverPath}\``); 81 + } 84 82 85 - return relative(`${dotNextDir}/standalone`, serverPath); 83 + return relative(`${dotNextDir}/standalone`, serverPath); 86 84 87 - function findServerParentPath(path: string): string | undefined { 88 - try { 89 - if (statSync(`${path}/.next/server`).isDirectory()) { 90 - return path; 91 - } 92 - } catch {} 85 + function findServerParentPath(path: string): string | undefined { 86 + try { 87 + if (statSync(`${path}/.next/server`).isDirectory()) { 88 + return path; 89 + } 90 + } catch {} 93 91 94 - const files = readdirSync(path); 92 + const files = readdirSync(path); 95 93 96 - for (const file of files) { 97 - if (statSync(`${path}/${file}`).isDirectory()) { 98 - const dirServerPath = findServerParentPath(`${path}/${file}`); 99 - if (dirServerPath) { 100 - return dirServerPath; 101 - } 102 - } 103 - } 104 - } 94 + for (const file of files) { 95 + if (statSync(`${path}/${file}`).isDirectory()) { 96 + const dirServerPath = findServerParentPath(`${path}/${file}`); 97 + if (dirServerPath) { 98 + return dirServerPath; 99 + } 100 + } 101 + } 102 + } 105 103 }
+9 -9
builder/tsconfig.json
··· 1 1 { 2 - "compilerOptions": { 3 - "target": "ESNext", 4 - "module": "ESNext", 5 - "moduleResolution": "Bundler", 6 - "esModuleInterop": true, 7 - "forceConsistentCasingInFileNames": true, 8 - "strict": true, 9 - "skipLibCheck": true 10 - } 2 + "compilerOptions": { 3 + "target": "ESNext", 4 + "module": "ESNext", 5 + "moduleResolution": "Bundler", 6 + "esModuleInterop": true, 7 + "forceConsistentCasingInFileNames": true, 8 + "strict": true, 9 + "skipLibCheck": true 10 + } 11 11 }
+11 -13
builder/tsup.config.ts
··· 2 2 import { defineConfig } from "tsup"; 3 3 4 4 export default defineConfig({ 5 - entry: ["src/index.ts"], 6 - outDir: "dist", 7 - dts: true, 8 - format: ["esm"], 9 - platform: "node", 10 - external: ["esbuild"], 11 - onSuccess: async () => { 12 - await cp( 13 - `${__dirname}/src/build/build-worker/templates`, 14 - `${__dirname}/dist/templates`, 15 - { recursive: true } 16 - ); 17 - }, 5 + entry: ["src/index.ts"], 6 + outDir: "dist", 7 + dts: true, 8 + format: ["esm"], 9 + platform: "node", 10 + external: ["esbuild"], 11 + onSuccess: async () => { 12 + await cp(`${__dirname}/src/build/build-worker/templates`, `${__dirname}/dist/templates`, { 13 + recursive: true, 14 + }); 15 + }, 18 16 });
+5 -5
examples/api/app/api/hello/route.ts
··· 1 1 import { headers } from "next/headers"; 2 2 3 3 export async function GET() { 4 - // Note: we use headers just so that the route is not built as a static one 5 - const headersList = headers(); 6 - const sayHi = !!headersList.get("should-say-hi"); 7 - return new Response(sayHi ? "Hi World!" : "Hello World!"); 4 + // Note: we use headers just so that the route is not built as a static one 5 + const headersList = headers(); 6 + const sayHi = !!headersList.get("should-say-hi"); 7 + return new Response(sayHi ? "Hi World!" : "Hello World!"); 8 8 } 9 9 10 10 export async function POST(request: Request) { 11 - return new Response(`Hello post-World! body=${await request.text()}`); 11 + return new Response(`Hello post-World! body=${await request.text()}`); 12 12 }
+7 -7
examples/api/app/layout.js
··· 1 1 export const metadata = { 2 - title: "API hello-world", 3 - description: "a simple api hello-world app", 2 + title: "API hello-world", 3 + description: "a simple api hello-world app", 4 4 }; 5 5 6 6 export default function RootLayout({ children }) { 7 - return ( 8 - <html lang="en"> 9 - <body>{children}</body> 10 - </html> 11 - ); 7 + return ( 8 + <html lang="en"> 9 + <body>{children}</body> 10 + </html> 11 + ); 12 12 }
+11 -11
examples/api/app/page.js
··· 1 1 export default function Home() { 2 - return ( 3 - <main> 4 - <p> 5 - This application doesn't have a UI, just a single api route (running in 6 - the <code>node.js</code> runtime): 7 - <a href="/api/hello"> 8 - <code>/api/hello</code> 9 - </a> 10 - </p> 11 - </main> 12 - ); 2 + return ( 3 + <main> 4 + <p> 5 + This application doesn't have a UI, just a single api route (running in the <code>node.js</code>{" "} 6 + runtime): 7 + <a href="/api/hello"> 8 + <code>/api/hello</code> 9 + </a> 10 + </p> 11 + </main> 12 + ); 13 13 }
+8 -8
examples/api/e2e-tests/base.spec.ts
··· 1 1 import { test, expect } from "@playwright/test"; 2 2 3 3 test("the application's noop index page is visible and it allows navigating to the hello-world api route", async ({ 4 - page, 4 + page, 5 5 }) => { 6 - await page.goto("/"); 7 - await expect(page.getByText("This application doesn't have")).toBeVisible(); 8 - await page.getByRole("link", { name: "/api/hello" }).click(); 9 - await expect(page.getByText("Hello World!")).toBeVisible(); 6 + await page.goto("/"); 7 + await expect(page.getByText("This application doesn't have")).toBeVisible(); 8 + await page.getByRole("link", { name: "/api/hello" }).click(); 9 + await expect(page.getByText("Hello World!")).toBeVisible(); 10 10 }); 11 11 12 12 test("the hello-world api route works as intended", async ({ page }) => { 13 - const res = await page.request.get("/api/hello"); 14 - expect(res.headers()["content-type"]).toContain("text/plain"); 15 - expect(await res.text()).toEqual("Hello World!"); 13 + const res = await page.request.get("/api/hello"); 14 + expect(res.headers()["content-type"]).toContain("text/plain"); 15 + expect(await res.text()).toEqual("Hello World!"); 16 16 });
+6 -6
examples/api/next.config.mjs
··· 1 1 /** @type {import('next').NextConfig} */ 2 2 const nextConfig = { 3 - output: "standalone", 4 - experimental: { 5 - // IMPORTANT: this option is necessary for the chunks hack since that relies on the webpack-runtime.js file not being minified 6 - // (since we regex-replace relying on specific variable names) 7 - serverMinification: false, 8 - }, 3 + output: "standalone", 4 + experimental: { 5 + // IMPORTANT: this option is necessary for the chunks hack since that relies on the webpack-runtime.js file not being minified 6 + // (since we regex-replace relying on specific variable names) 7 + serverMinification: false, 8 + }, 9 9 }; 10 10 11 11 export default nextConfig;
+25 -25
examples/api/package.json
··· 1 1 { 2 - "name": "api", 3 - "version": "0.1.0", 4 - "private": true, 5 - "scripts": { 6 - "dev": "next dev", 7 - "build": "next build", 8 - "start": "next start", 9 - "lint": "next lint", 10 - "build:worker": "builder", 11 - "dev:worker": "wrangler dev --port 8770", 12 - "preview:worker": "pnpm build:worker && pnpm dev:worker", 13 - "e2e": "playwright test" 14 - }, 15 - "dependencies": { 16 - "next": "14.2.5", 17 - "react": "^18", 18 - "react-dom": "^18" 19 - }, 20 - "devDependencies": { 21 - "builder": "workspace:*", 22 - "@playwright/test": "1.47.0", 23 - "@types/node": "^22.2.0", 24 - "node-url": "npm:url@^0.11.4", 25 - "wrangler": "3.77.0" 26 - } 2 + "name": "api", 3 + "version": "0.1.0", 4 + "private": true, 5 + "scripts": { 6 + "dev": "next dev", 7 + "build": "next build", 8 + "start": "next start", 9 + "lint": "next lint", 10 + "build:worker": "builder", 11 + "dev:worker": "wrangler dev --port 8770", 12 + "preview:worker": "pnpm build:worker && pnpm dev:worker", 13 + "e2e": "playwright test" 14 + }, 15 + "dependencies": { 16 + "next": "14.2.5", 17 + "react": "^18", 18 + "react-dom": "^18" 19 + }, 20 + "devDependencies": { 21 + "builder": "workspace:*", 22 + "@playwright/test": "1.47.0", 23 + "@types/node": "^22.2.0", 24 + "node-url": "npm:url@^0.11.4", 25 + "wrangler": "3.77.0" 26 + } 27 27 }
+57 -57
examples/api/playwright.config.ts
··· 13 13 * See https://playwright.dev/docs/test-configuration. 14 14 */ 15 15 export default defineConfig({ 16 - testDir: "./e2e-tests", 17 - /* Run tests in files in parallel */ 18 - fullyParallel: true, 19 - /* Fail the build on CI if you accidentally left test.only in the source code. */ 20 - forbidOnly: !!process.env.CI, 21 - /* Retry on CI only */ 22 - retries: process.env.CI ? 2 : 0, 23 - /* Opt out of parallel tests on CI. */ 24 - workers: process.env.CI ? 1 : undefined, 25 - /* Reporter to use. See https://playwright.dev/docs/test-reporters */ 26 - reporter: "html", 27 - /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */ 28 - use: { 29 - /* Base URL to use in actions like `await page.goto('/')`. */ 30 - baseURL: "http://localhost:8770", 16 + testDir: "./e2e-tests", 17 + /* Run tests in files in parallel */ 18 + fullyParallel: true, 19 + /* Fail the build on CI if you accidentally left test.only in the source code. */ 20 + forbidOnly: !!process.env.CI, 21 + /* Retry on CI only */ 22 + retries: process.env.CI ? 2 : 0, 23 + /* Opt out of parallel tests on CI. */ 24 + workers: process.env.CI ? 1 : undefined, 25 + /* Reporter to use. See https://playwright.dev/docs/test-reporters */ 26 + reporter: "html", 27 + /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */ 28 + use: { 29 + /* Base URL to use in actions like `await page.goto('/')`. */ 30 + baseURL: "http://localhost:8770", 31 31 32 - /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */ 33 - trace: "on-first-retry", 34 - }, 32 + /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */ 33 + trace: "on-first-retry", 34 + }, 35 35 36 - /* Configure projects for major browsers */ 37 - projects: [ 38 - { 39 - name: "chromium", 40 - use: { ...devices["Desktop Chrome"] }, 41 - }, 36 + /* Configure projects for major browsers */ 37 + projects: [ 38 + { 39 + name: "chromium", 40 + use: { ...devices["Desktop Chrome"] }, 41 + }, 42 42 43 - { 44 - name: "firefox", 45 - use: { ...devices["Desktop Firefox"] }, 46 - }, 43 + { 44 + name: "firefox", 45 + use: { ...devices["Desktop Firefox"] }, 46 + }, 47 47 48 - { 49 - name: "webkit", 50 - use: { ...devices["Desktop Safari"] }, 51 - }, 48 + { 49 + name: "webkit", 50 + use: { ...devices["Desktop Safari"] }, 51 + }, 52 52 53 - /* Test against mobile viewports. */ 54 - // { 55 - // name: 'Mobile Chrome', 56 - // use: { ...devices['Pixel 5'] }, 57 - // }, 58 - // { 59 - // name: 'Mobile Safari', 60 - // use: { ...devices['iPhone 12'] }, 61 - // }, 53 + /* Test against mobile viewports. */ 54 + // { 55 + // name: 'Mobile Chrome', 56 + // use: { ...devices['Pixel 5'] }, 57 + // }, 58 + // { 59 + // name: 'Mobile Safari', 60 + // use: { ...devices['iPhone 12'] }, 61 + // }, 62 62 63 - /* Test against branded browsers. */ 64 - // { 65 - // name: 'Microsoft Edge', 66 - // use: { ...devices['Desktop Edge'], channel: 'msedge' }, 67 - // }, 68 - // { 69 - // name: 'Google Chrome', 70 - // use: { ...devices['Desktop Chrome'], channel: 'chrome' }, 71 - // }, 72 - ], 63 + /* Test against branded browsers. */ 64 + // { 65 + // name: 'Microsoft Edge', 66 + // use: { ...devices['Desktop Edge'], channel: 'msedge' }, 67 + // }, 68 + // { 69 + // name: 'Google Chrome', 70 + // use: { ...devices['Desktop Chrome'], channel: 'chrome' }, 71 + // }, 72 + ], 73 73 74 - /* Run your local dev server before starting the tests */ 75 - webServer: { 76 - command: "pnpm preview:worker", 77 - url: "http://localhost:8770", 78 - reuseExistingServer: !process.env.CI, 79 - }, 74 + /* Run your local dev server before starting the tests */ 75 + webServer: { 76 + command: "pnpm preview:worker", 77 + url: "http://localhost:8770", 78 + reuseExistingServer: !process.env.CI, 79 + }, 80 80 });
+21 -21
examples/api/tsconfig.json
··· 1 1 { 2 - "compilerOptions": { 3 - "lib": ["dom", "dom.iterable", "esnext"], 4 - "allowJs": true, 5 - "skipLibCheck": true, 6 - "strict": false, 7 - "noEmit": true, 8 - "incremental": true, 9 - "module": "esnext", 10 - "esModuleInterop": true, 11 - "moduleResolution": "node", 12 - "resolveJsonModule": true, 13 - "isolatedModules": true, 14 - "jsx": "preserve", 15 - "plugins": [ 16 - { 17 - "name": "next" 18 - } 19 - ] 20 - }, 21 - "include": ["next-env.d.ts", ".next/types/**/*.ts", "**/*.ts", "**/*.tsx"], 22 - "exclude": ["node_modules"] 2 + "compilerOptions": { 3 + "lib": ["dom", "dom.iterable", "esnext"], 4 + "allowJs": true, 5 + "skipLibCheck": true, 6 + "strict": false, 7 + "noEmit": true, 8 + "incremental": true, 9 + "module": "esnext", 10 + "esModuleInterop": true, 11 + "moduleResolution": "node", 12 + "resolveJsonModule": true, 13 + "isolatedModules": true, 14 + "jsx": "preserve", 15 + "plugins": [ 16 + { 17 + "name": "next" 18 + } 19 + ] 20 + }, 21 + "include": ["next-env.d.ts", ".next/types/**/*.ts", "**/*.ts", "**/*.tsx"], 22 + "exclude": ["node_modules"] 23 23 }
+1 -1
examples/create-next-app/.eslintrc.json
··· 1 1 { 2 - "extends": ["next/core-web-vitals", "next/typescript"] 2 + "extends": ["next/core-web-vitals", "next/typescript"] 3 3 }
+3 -5
examples/create-next-app/e2e/base.spec.ts
··· 1 1 import { test, expect } from "@playwright/test"; 2 2 3 - test("the index page of the application shows the Next.js logo", async ({ 4 - page, 5 - }) => { 6 - await page.goto("/"); 7 - await expect(page.getByAltText("Next.js logo")).toBeVisible(); 3 + test("the index page of the application shows the Next.js logo", async ({ page }) => { 4 + await page.goto("/"); 5 + await expect(page.getByAltText("Next.js logo")).toBeVisible(); 8 6 });
+57 -57
examples/create-next-app/e2e/playwright.config.ts
··· 13 13 * See https://playwright.dev/docs/test-configuration. 14 14 */ 15 15 export default defineConfig({ 16 - testDir: "./", 17 - /* Run tests in files in parallel */ 18 - fullyParallel: true, 19 - /* Fail the build on CI if you accidentally left test.only in the source code. */ 20 - forbidOnly: !!process.env.CI, 21 - /* Retry on CI only */ 22 - retries: process.env.CI ? 2 : 0, 23 - /* Opt out of parallel tests on CI. */ 24 - workers: process.env.CI ? 1 : undefined, 25 - /* Reporter to use. See https://playwright.dev/docs/test-reporters */ 26 - reporter: "html", 27 - /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */ 28 - use: { 29 - /* Base URL to use in actions like `await page.goto('/')`. */ 30 - baseURL: "http://localhost:8771", 16 + testDir: "./", 17 + /* Run tests in files in parallel */ 18 + fullyParallel: true, 19 + /* Fail the build on CI if you accidentally left test.only in the source code. */ 20 + forbidOnly: !!process.env.CI, 21 + /* Retry on CI only */ 22 + retries: process.env.CI ? 2 : 0, 23 + /* Opt out of parallel tests on CI. */ 24 + workers: process.env.CI ? 1 : undefined, 25 + /* Reporter to use. See https://playwright.dev/docs/test-reporters */ 26 + reporter: "html", 27 + /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */ 28 + use: { 29 + /* Base URL to use in actions like `await page.goto('/')`. */ 30 + baseURL: "http://localhost:8771", 31 31 32 - /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */ 33 - trace: "on-first-retry", 34 - }, 32 + /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */ 33 + trace: "on-first-retry", 34 + }, 35 35 36 - /* Configure projects for major browsers */ 37 - projects: [ 38 - { 39 - name: "chromium", 40 - use: { ...devices["Desktop Chrome"] }, 41 - }, 36 + /* Configure projects for major browsers */ 37 + projects: [ 38 + { 39 + name: "chromium", 40 + use: { ...devices["Desktop Chrome"] }, 41 + }, 42 42 43 - { 44 - name: "firefox", 45 - use: { ...devices["Desktop Firefox"] }, 46 - }, 43 + { 44 + name: "firefox", 45 + use: { ...devices["Desktop Firefox"] }, 46 + }, 47 47 48 - { 49 - name: "webkit", 50 - use: { ...devices["Desktop Safari"] }, 51 - }, 48 + { 49 + name: "webkit", 50 + use: { ...devices["Desktop Safari"] }, 51 + }, 52 52 53 - /* Test against mobile viewports. */ 54 - // { 55 - // name: 'Mobile Chrome', 56 - // use: { ...devices['Pixel 5'] }, 57 - // }, 58 - // { 59 - // name: 'Mobile Safari', 60 - // use: { ...devices['iPhone 12'] }, 61 - // }, 53 + /* Test against mobile viewports. */ 54 + // { 55 + // name: 'Mobile Chrome', 56 + // use: { ...devices['Pixel 5'] }, 57 + // }, 58 + // { 59 + // name: 'Mobile Safari', 60 + // use: { ...devices['iPhone 12'] }, 61 + // }, 62 62 63 - /* Test against branded browsers. */ 64 - // { 65 - // name: 'Microsoft Edge', 66 - // use: { ...devices['Desktop Edge'], channel: 'msedge' }, 67 - // }, 68 - // { 69 - // name: 'Google Chrome', 70 - // use: { ...devices['Desktop Chrome'], channel: 'chrome' }, 71 - // }, 72 - ], 63 + /* Test against branded browsers. */ 64 + // { 65 + // name: 'Microsoft Edge', 66 + // use: { ...devices['Desktop Edge'], channel: 'msedge' }, 67 + // }, 68 + // { 69 + // name: 'Google Chrome', 70 + // use: { ...devices['Desktop Chrome'], channel: 'chrome' }, 71 + // }, 72 + ], 73 73 74 - /* Run your local dev server before starting the tests */ 75 - webServer: { 76 - command: "pnpm preview:worker", 77 - url: "http://localhost:8771", 78 - reuseExistingServer: !process.env.CI, 79 - }, 74 + /* Run your local dev server before starting the tests */ 75 + webServer: { 76 + command: "pnpm preview:worker", 77 + url: "http://localhost:8771", 78 + reuseExistingServer: !process.env.CI, 79 + }, 80 80 });
+4 -4
examples/create-next-app/next.config.mjs
··· 1 1 /** @type {import('next').NextConfig} */ 2 2 const nextConfig = { 3 - output: "standalone", 4 - experimental: { 5 - serverMinification: false, 6 - }, 3 + output: "standalone", 4 + experimental: { 5 + serverMinification: false, 6 + }, 7 7 }; 8 8 9 9 export default nextConfig;
+32 -32
examples/create-next-app/package.json
··· 1 1 { 2 - "name": "create-next-app", 3 - "version": "0.1.0", 4 - "private": true, 5 - "scripts": { 6 - "dev": "next dev", 7 - "build": "next build", 8 - "start": "next start", 9 - "lint": "next lint", 10 - "build:worker": "builder", 11 - "dev:worker": "wrangler dev --port 8771", 12 - "preview:worker": "pnpm build:worker && pnpm dev:worker", 13 - "e2e": "playwright test -c e2e/playwright.config.ts" 14 - }, 15 - "dependencies": { 16 - "react": "^18", 17 - "react-dom": "^18", 18 - "next": "14.2.11" 19 - }, 20 - "devDependencies": { 21 - "builder": "workspace:*", 22 - "@playwright/test": "1.47.0", 23 - "@types/node": "^20", 24 - "@types/react": "^18", 25 - "@types/react-dom": "^18", 26 - "eslint": "^8", 27 - "eslint-config-next": "14.2.11", 28 - "postcss": "^8", 29 - "node-url": "npm:url@^0.11.4", 30 - "tailwindcss": "^3.4.1", 31 - "typescript": "^5", 32 - "wrangler": "^3.77.0" 33 - } 2 + "name": "create-next-app", 3 + "version": "0.1.0", 4 + "private": true, 5 + "scripts": { 6 + "dev": "next dev", 7 + "build": "next build", 8 + "start": "next start", 9 + "lint": "next lint", 10 + "build:worker": "builder", 11 + "dev:worker": "wrangler dev --port 8771", 12 + "preview:worker": "pnpm build:worker && pnpm dev:worker", 13 + "e2e": "playwright test -c e2e/playwright.config.ts" 14 + }, 15 + "dependencies": { 16 + "react": "^18", 17 + "react-dom": "^18", 18 + "next": "14.2.11" 19 + }, 20 + "devDependencies": { 21 + "builder": "workspace:*", 22 + "@playwright/test": "1.47.0", 23 + "@types/node": "^20", 24 + "@types/react": "^18", 25 + "@types/react-dom": "^18", 26 + "eslint": "^8", 27 + "eslint-config-next": "14.2.11", 28 + "postcss": "^8", 29 + "node-url": "npm:url@^0.11.4", 30 + "tailwindcss": "^3.4.1", 31 + "typescript": "^5", 32 + "wrangler": "^3.77.0" 33 + } 34 34 }
+3 -3
examples/create-next-app/postcss.config.mjs
··· 1 1 /** @type {import('postcss-load-config').Config} */ 2 2 const config = { 3 - plugins: { 4 - tailwindcss: {}, 5 - }, 3 + plugins: { 4 + tailwindcss: {}, 5 + }, 6 6 }; 7 7 8 8 export default config;
+12 -12
examples/create-next-app/src/app/globals.css
··· 3 3 @tailwind utilities; 4 4 5 5 :root { 6 - --background: #ffffff; 7 - --foreground: #171717; 6 + --background: #ffffff; 7 + --foreground: #171717; 8 8 } 9 9 10 10 @media (prefers-color-scheme: dark) { 11 - :root { 12 - --background: #0a0a0a; 13 - --foreground: #ededed; 14 - } 11 + :root { 12 + --background: #0a0a0a; 13 + --foreground: #ededed; 14 + } 15 15 } 16 16 17 17 body { 18 - color: var(--foreground); 19 - background: var(--background); 20 - font-family: Arial, Helvetica, sans-serif; 18 + color: var(--foreground); 19 + background: var(--background); 20 + font-family: Arial, Helvetica, sans-serif; 21 21 } 22 22 23 23 @layer utilities { 24 - .text-balance { 25 - text-wrap: balance; 26 - } 24 + .text-balance { 25 + text-wrap: balance; 26 + } 27 27 }
+15 -19
examples/create-next-app/src/app/layout.tsx
··· 3 3 import "./globals.css"; 4 4 5 5 const geistSans = localFont({ 6 - src: "./fonts/GeistVF.woff", 7 - variable: "--font-geist-sans", 8 - weight: "100 900", 6 + src: "./fonts/GeistVF.woff", 7 + variable: "--font-geist-sans", 8 + weight: "100 900", 9 9 }); 10 10 const geistMono = localFont({ 11 - src: "./fonts/GeistMonoVF.woff", 12 - variable: "--font-geist-mono", 13 - weight: "100 900", 11 + src: "./fonts/GeistMonoVF.woff", 12 + variable: "--font-geist-mono", 13 + weight: "100 900", 14 14 }); 15 15 16 16 export const metadata: Metadata = { 17 - title: "Create Next App", 18 - description: "Generated by create next app", 17 + title: "Create Next App", 18 + description: "Generated by create next app", 19 19 }; 20 20 21 21 export default function RootLayout({ 22 - children, 22 + children, 23 23 }: Readonly<{ 24 - children: React.ReactNode; 24 + children: React.ReactNode; 25 25 }>) { 26 - return ( 27 - <html lang="en"> 28 - <body 29 - className={`${geistSans.variable} ${geistMono.variable} antialiased`} 30 - > 31 - {children} 32 - </body> 33 - </html> 34 - ); 26 + return ( 27 + <html lang="en"> 28 + <body className={`${geistSans.variable} ${geistMono.variable} antialiased`}>{children}</body> 29 + </html> 30 + ); 35 31 }
+90 -96
examples/create-next-app/src/app/page.tsx
··· 1 1 import Image from "next/image"; 2 2 3 3 export default function Home() { 4 - return ( 5 - <div className="grid grid-rows-[20px_1fr_20px] items-center justify-items-center min-h-screen p-8 pb-20 gap-16 sm:p-20 font-[family-name:var(--font-geist-sans)]"> 6 - <main className="flex flex-col gap-8 row-start-2 items-center sm:items-start"> 7 - <Image 8 - className="dark:invert" 9 - src="https://nextjs.org/icons/next.svg" 10 - alt="Next.js logo" 11 - width={180} 12 - height={38} 13 - priority 14 - /> 15 - <ol className="list-inside list-decimal text-sm text-center sm:text-left font-[family-name:var(--font-geist-mono)]"> 16 - <li className="mb-2"> 17 - Get started by editing{" "} 18 - <code className="bg-black/[.05] dark:bg-white/[.06] px-1 py-0.5 rounded font-semibold"> 19 - src/app/page.tsx 20 - </code> 21 - . 22 - </li> 23 - <li>Save and see your changes instantly.</li> 24 - </ol> 4 + return ( 5 + <div className="grid grid-rows-[20px_1fr_20px] items-center justify-items-center min-h-screen p-8 pb-20 gap-16 sm:p-20 font-[family-name:var(--font-geist-sans)]"> 6 + <main className="flex flex-col gap-8 row-start-2 items-center sm:items-start"> 7 + <Image 8 + className="dark:invert" 9 + src="https://nextjs.org/icons/next.svg" 10 + alt="Next.js logo" 11 + width={180} 12 + height={38} 13 + priority 14 + /> 15 + <ol className="list-inside list-decimal text-sm text-center sm:text-left font-[family-name:var(--font-geist-mono)]"> 16 + <li className="mb-2"> 17 + Get started by editing{" "} 18 + <code className="bg-black/[.05] dark:bg-white/[.06] px-1 py-0.5 rounded font-semibold"> 19 + src/app/page.tsx 20 + </code> 21 + . 22 + </li> 23 + <li>Save and see your changes instantly.</li> 24 + </ol> 25 25 26 - <div className="flex gap-4 items-center flex-col sm:flex-row"> 27 - <a 28 - className="rounded-full border border-solid border-transparent transition-colors flex items-center justify-center bg-foreground text-background gap-2 hover:bg-[#383838] dark:hover:bg-[#ccc] text-sm sm:text-base h-10 sm:h-12 px-4 sm:px-5" 29 - href="https://vercel.com/new?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app" 30 - target="_blank" 31 - rel="noopener noreferrer" 32 - > 33 - <Image 34 - className="dark:invert" 35 - src="https://nextjs.org/icons/vercel.svg" 36 - alt="Vercel logomark" 37 - width={20} 38 - height={20} 39 - /> 40 - Deploy now 41 - </a> 42 - <a 43 - className="rounded-full border border-solid border-black/[.08] dark:border-white/[.145] transition-colors flex items-center justify-center hover:bg-[#f2f2f2] dark:hover:bg-[#1a1a1a] hover:border-transparent text-sm sm:text-base h-10 sm:h-12 px-4 sm:px-5 sm:min-w-44" 44 - href="https://nextjs.org/docs?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app" 45 - target="_blank" 46 - rel="noopener noreferrer" 47 - > 48 - Read our docs 49 - </a> 50 - </div> 51 - </main> 52 - <footer className="row-start-3 flex gap-6 flex-wrap items-center justify-center"> 53 - <a 54 - className="flex items-center gap-2 hover:underline hover:underline-offset-4" 55 - href="https://nextjs.org/learn?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app" 56 - target="_blank" 57 - rel="noopener noreferrer" 58 - > 59 - <Image 60 - aria-hidden 61 - src="https://nextjs.org/icons/file.svg" 62 - alt="File icon" 63 - width={16} 64 - height={16} 65 - /> 66 - Learn 67 - </a> 68 - <a 69 - className="flex items-center gap-2 hover:underline hover:underline-offset-4" 70 - href="https://vercel.com/templates?framework=next.js&utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app" 71 - target="_blank" 72 - rel="noopener noreferrer" 73 - > 74 - <Image 75 - aria-hidden 76 - src="https://nextjs.org/icons/window.svg" 77 - alt="Window icon" 78 - width={16} 79 - height={16} 80 - /> 81 - Examples 82 - </a> 83 - <a 84 - className="flex items-center gap-2 hover:underline hover:underline-offset-4" 85 - href="https://nextjs.org?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app" 86 - target="_blank" 87 - rel="noopener noreferrer" 88 - > 89 - <Image 90 - aria-hidden 91 - src="https://nextjs.org/icons/globe.svg" 92 - alt="Globe icon" 93 - width={16} 94 - height={16} 95 - /> 96 - Go to nextjs.org → 97 - </a> 98 - </footer> 99 - </div> 100 - ); 26 + <div className="flex gap-4 items-center flex-col sm:flex-row"> 27 + <a 28 + className="rounded-full border border-solid border-transparent transition-colors flex items-center justify-center bg-foreground text-background gap-2 hover:bg-[#383838] dark:hover:bg-[#ccc] text-sm sm:text-base h-10 sm:h-12 px-4 sm:px-5" 29 + href="https://vercel.com/new?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app" 30 + target="_blank" 31 + rel="noopener noreferrer" 32 + > 33 + <Image 34 + className="dark:invert" 35 + src="https://nextjs.org/icons/vercel.svg" 36 + alt="Vercel logomark" 37 + width={20} 38 + height={20} 39 + /> 40 + Deploy now 41 + </a> 42 + <a 43 + className="rounded-full border border-solid border-black/[.08] dark:border-white/[.145] transition-colors flex items-center justify-center hover:bg-[#f2f2f2] dark:hover:bg-[#1a1a1a] hover:border-transparent text-sm sm:text-base h-10 sm:h-12 px-4 sm:px-5 sm:min-w-44" 44 + href="https://nextjs.org/docs?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app" 45 + target="_blank" 46 + rel="noopener noreferrer" 47 + > 48 + Read our docs 49 + </a> 50 + </div> 51 + </main> 52 + <footer className="row-start-3 flex gap-6 flex-wrap items-center justify-center"> 53 + <a 54 + className="flex items-center gap-2 hover:underline hover:underline-offset-4" 55 + href="https://nextjs.org/learn?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app" 56 + target="_blank" 57 + rel="noopener noreferrer" 58 + > 59 + <Image aria-hidden src="https://nextjs.org/icons/file.svg" alt="File icon" width={16} height={16} /> 60 + Learn 61 + </a> 62 + <a 63 + className="flex items-center gap-2 hover:underline hover:underline-offset-4" 64 + href="https://vercel.com/templates?framework=next.js&utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app" 65 + target="_blank" 66 + rel="noopener noreferrer" 67 + > 68 + <Image 69 + aria-hidden 70 + src="https://nextjs.org/icons/window.svg" 71 + alt="Window icon" 72 + width={16} 73 + height={16} 74 + /> 75 + Examples 76 + </a> 77 + <a 78 + className="flex items-center gap-2 hover:underline hover:underline-offset-4" 79 + href="https://nextjs.org?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app" 80 + target="_blank" 81 + rel="noopener noreferrer" 82 + > 83 + <Image 84 + aria-hidden 85 + src="https://nextjs.org/icons/globe.svg" 86 + alt="Globe icon" 87 + width={16} 88 + height={16} 89 + /> 90 + Go to nextjs.org → 91 + </a> 92 + </footer> 93 + </div> 94 + ); 101 95 }
+14 -14
examples/create-next-app/tailwind.config.ts
··· 1 1 import type { Config } from "tailwindcss"; 2 2 3 3 const config: Config = { 4 - content: [ 5 - "./src/pages/**/*.{js,ts,jsx,tsx,mdx}", 6 - "./src/components/**/*.{js,ts,jsx,tsx,mdx}", 7 - "./src/app/**/*.{js,ts,jsx,tsx,mdx}", 8 - ], 9 - theme: { 10 - extend: { 11 - colors: { 12 - background: "var(--background)", 13 - foreground: "var(--foreground)", 14 - }, 15 - }, 16 - }, 17 - plugins: [], 4 + content: [ 5 + "./src/pages/**/*.{js,ts,jsx,tsx,mdx}", 6 + "./src/components/**/*.{js,ts,jsx,tsx,mdx}", 7 + "./src/app/**/*.{js,ts,jsx,tsx,mdx}", 8 + ], 9 + theme: { 10 + extend: { 11 + colors: { 12 + background: "var(--background)", 13 + foreground: "var(--foreground)", 14 + }, 15 + }, 16 + }, 17 + plugins: [], 18 18 }; 19 19 export default config;
+24 -24
examples/create-next-app/tsconfig.json
··· 1 1 { 2 - "compilerOptions": { 3 - "lib": ["dom", "dom.iterable", "esnext"], 4 - "allowJs": true, 5 - "skipLibCheck": true, 6 - "strict": true, 7 - "noEmit": true, 8 - "esModuleInterop": true, 9 - "module": "esnext", 10 - "moduleResolution": "bundler", 11 - "resolveJsonModule": true, 12 - "isolatedModules": true, 13 - "jsx": "preserve", 14 - "incremental": true, 15 - "plugins": [ 16 - { 17 - "name": "next" 18 - } 19 - ], 20 - "paths": { 21 - "@/*": ["./src/*"] 22 - } 23 - }, 24 - "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], 25 - "exclude": ["node_modules"] 2 + "compilerOptions": { 3 + "lib": ["dom", "dom.iterable", "esnext"], 4 + "allowJs": true, 5 + "skipLibCheck": true, 6 + "strict": true, 7 + "noEmit": true, 8 + "esModuleInterop": true, 9 + "module": "esnext", 10 + "moduleResolution": "bundler", 11 + "resolveJsonModule": true, 12 + "isolatedModules": true, 13 + "jsx": "preserve", 14 + "incremental": true, 15 + "plugins": [ 16 + { 17 + "name": "next" 18 + } 19 + ], 20 + "paths": { 21 + "@/*": ["./src/*"] 22 + } 23 + }, 24 + "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], 25 + "exclude": ["node_modules"] 26 26 }
+14 -14
package.json
··· 1 1 { 2 - "name": "poc-next", 3 - "version": "0.0.0.0", 4 - "private": true, 5 - "devDependencies": { 6 - "prettier": "3.3.3", 7 - "@playwright/test": "1.47.0" 8 - }, 9 - "scripts": { 10 - "prettier:check": "prettier --check .", 11 - "prettier:fix": "prettier --write .", 12 - "postinstall": "pnpm --filter builder build", 13 - "install-playwright": "playwright install --with-deps", 14 - "e2e": "pnpm -r e2e" 15 - } 2 + "name": "poc-next", 3 + "version": "0.0.0.0", 4 + "private": true, 5 + "devDependencies": { 6 + "prettier": "3.3.3", 7 + "@playwright/test": "1.47.0" 8 + }, 9 + "scripts": { 10 + "prettier:check": "prettier --check .", 11 + "prettier:fix": "prettier --write .", 12 + "postinstall": "pnpm --filter builder build", 13 + "install-playwright": "playwright install --with-deps", 14 + "e2e": "pnpm -r e2e" 15 + } 16 16 }