this repo has no description
0
fork

Configure Feed

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

feat: turbopack support (#983)

Co-authored-by: Nicolas Dorseuil <nicolas@gitbook.io>

authored by

Victor Berchet
Nicolas Dorseuil
and committed by
GitHub
68aed260 aefcb03a

+116 -16
+5
.changeset/silver-phones-brush.md
··· 1 + --- 2 + "@opennextjs/cloudflare": minor 3 + --- 4 + 5 + feat: turbopack support
+2 -2
examples/common/config-e2e.ts
··· 27 27 if (isCI) { 28 28 // Do not build on CI - there is a preceding build step 29 29 command = `pnpm preview:worker -- --port ${port} --inspector-port ${inspectorPort} ${env}`; 30 - timeout = 200_000; 30 + timeout = 800_000; 31 31 } else { 32 - timeout = 500_000; 32 + timeout = 800_000; 33 33 command = `pnpm preview -- --port ${port} --inspector-port ${inspectorPort} ${env}`; 34 34 } 35 35 } else {
+1 -1
examples/e2e/app-router/package.json
··· 5 5 "scripts": { 6 6 "openbuild": "node ../../packages/open-next/dist/index.js build --streaming --build-command \"npx turbo build\"", 7 7 "dev": "next dev --turbopack --port 3001", 8 - "build": "next build", 8 + "build": "next build --turbopack", 9 9 "start": "next start --port 3001", 10 10 "lint": "next lint", 11 11 "clean": "rm -rf .turbo node_modules .next .open-next",
+7 -11
packages/cloudflare/src/cli/build/bundle-server.ts
··· 50 50 copyPackageCliFiles(packageDistDir, buildOpts); 51 51 52 52 const { appPath, outputDir, monorepoRoot, debug } = buildOpts; 53 - const baseManifestPath = path.join( 54 - outputDir, 55 - "server-functions/default", 56 - getPackagePath(buildOpts), 57 - ".next" 58 - ); 59 - const serverFiles = path.join(baseManifestPath, "required-server-files.json"); 53 + const dotNextPath = path.join(outputDir, "server-functions/default", getPackagePath(buildOpts), ".next"); 54 + const serverFiles = path.join(dotNextPath, "required-server-files.json"); 60 55 const nextConfig = JSON.parse(fs.readFileSync(serverFiles, "utf-8")).config; 56 + 57 + const useTurbopack = fs.existsSync(path.join(dotNextPath, "server/chunks/[turbopack]_runtime.js")); 61 58 62 59 console.log(`\x1b[35m⚙️ Bundling the OpenNext server...\n\x1b[0m`); 63 60 ··· 141 138 // Note: we need the __non_webpack_require__ variable declared as it is used by next-server: 142 139 // https://github.com/vercel/next.js/blob/be0c3283/packages/next/src/server/next-server.ts#L116-L119 143 140 __non_webpack_require__: "require", 141 + // The 2 following defines are used to reduce the bundle size by removing unnecessary code 142 + // Next uses different precompiled renderers (i.e. `app-page.runtime.prod.js`) based on if you use `TURBOPACK` or some experimental React features 143 + ...(useTurbopack ? {} : { "process.env.TURBOPACK": "false" }), 144 144 // We make sure that environment variables that Next.js expects are properly defined 145 145 "process.env.NEXT_RUNTIME": '"nodejs"', 146 146 "process.env.NODE_ENV": '"production"', 147 - // The 2 following defines are used to reduce the bundle size by removing unnecessary code 148 - // Next uses different precompiled renderers (i.e. `app-page.runtime.prod.js`) based on if you use `TURBOPACK` or some experimental React features 149 - // Turbopack is not supported for build at the moment, so we disable it 150 - "process.env.TURBOPACK": "false", 151 147 // This define should be safe to use for Next 14.2+, earlier versions (13.5 and less) will cause trouble 152 148 "process.env.__NEXT_EXPERIMENTAL_REACT": `${needsExperimentalReact(nextConfig)}`, 153 149 // Fix `res.validate` in Next 15.4 (together with the `route-module` patch)
+2
packages/cloudflare/src/cli/build/open-next/createServerBundle.ts
··· 27 27 28 28 import { getOpenNextConfig } from "../../../api/config.js"; 29 29 import { patchResRevalidate } from "../patches/plugins/res-revalidate.js"; 30 + import { patchTurbopackRuntime } from "../patches/plugins/turbopack.js"; 30 31 import { patchUseCacheIO } from "../patches/plugins/use-cache.js"; 31 32 import { normalizePath } from "../utils/index.js"; 32 33 import { copyWorkerdPackages } from "../utils/workerd.js"; ··· 210 211 // Cloudflare specific patches 211 212 patchResRevalidate, 212 213 patchUseCacheIO, 214 + patchTurbopackRuntime, 213 215 ...additionalCodePatches, 214 216 ]); 215 217
+10 -2
packages/cloudflare/src/cli/build/patches/ast/patch-vercel-og-library.ts
··· 24 24 for (const traceInfoPath of globSync(path.join(appBuildOutputPath, ".next/server/**/*.nft.json"), { 25 25 windowsPathsNoEscape: true, 26 26 })) { 27 + let edgeFilePatched = false; 28 + 27 29 const traceInfo: TraceInfo = JSON.parse(readFileSync(traceInfoPath, { encoding: "utf8" })); 28 30 const tracedNodePath = traceInfo.files.find((p) => p.endsWith("@vercel/og/index.node.js")); 29 31 ··· 40 42 ); 41 43 42 44 copyFileSync(tracedEdgePath, outputEdgePath); 45 + } 43 46 47 + if (!edgeFilePatched) { 48 + edgeFilePatched = true; 44 49 // Change font fetches in the library to use imports. 45 50 const node = parseFile(outputEdgePath); 46 51 const { edits, matches } = patchVercelOgFallbackFont(node); 47 52 writeFileSync(outputEdgePath, node.commitEdits(edits)); 48 53 49 - const fontFileName = matches[0]!.getMatch("PATH")!.text(); 50 - renameSync(path.join(outputDir, fontFileName), path.join(outputDir, `${fontFileName}.bin`)); 54 + if (matches.length > 0) { 55 + const fontFileName = matches[0]!.getMatch("PATH")!.text(); 56 + renameSync(path.join(outputDir, fontFileName), path.join(outputDir, `${fontFileName}.bin`)); 57 + } 51 58 } 52 59 53 60 // Change node imports for the library to edge imports. 61 + // This is only useful when turbopack is not used to bundle the function. 54 62 const routeFilePath = traceInfoPath.replace(appBuildOutputPath, packagePath).replace(".nft.json", ""); 55 63 56 64 const node = parseFile(routeFilePath);
+89
packages/cloudflare/src/cli/build/patches/plugins/turbopack.ts
··· 1 + import { patchCode } from "@opennextjs/aws/build/patch/astCodePatcher.js"; 2 + import type { CodePatcher } from "@opennextjs/aws/build/patch/codePatcher.js"; 3 + import { getCrossPlatformPathRegex } from "@opennextjs/aws/utils/regex.js"; 4 + 5 + const inlineChunksRule = ` 6 + rule: 7 + kind: call_expression 8 + pattern: require(resolved) 9 + fix: 10 + requireChunk(chunkPath) 11 + `; 12 + 13 + export const patchTurbopackRuntime: CodePatcher = { 14 + name: "inline-turbopack-chunks", 15 + patches: [ 16 + { 17 + versions: ">=15.0.0", 18 + pathFilter: getCrossPlatformPathRegex(String.raw`\[turbopack\]_runtime\.js$`, { 19 + escape: false, 20 + }), 21 + contentFilter: /loadRuntimeChunkPath/, 22 + patchCode: async ({ code, tracedFiles }) => { 23 + let patched = patchCode(code, inlineExternalImportRule); 24 + patched = patchCode(patched, inlineChunksRule); 25 + 26 + return `${patched}\n${inlineChunksFn(tracedFiles)}`; 27 + }, 28 + }, 29 + ], 30 + }; 31 + 32 + function getInlinableChunks(tracedFiles: string[]): string[] { 33 + const chunks = new Set<string>(); 34 + for (const file of tracedFiles) { 35 + if (file === "[turbopack]_runtime.js") { 36 + continue; 37 + } 38 + if (file.includes(".next/server/chunks/")) { 39 + chunks.add(file); 40 + } 41 + } 42 + return Array.from(chunks); 43 + } 44 + 45 + function inlineChunksFn(tracedFiles: string[]) { 46 + // From the outputs, we extract every chunks 47 + const chunks = getInlinableChunks(tracedFiles); 48 + return ` 49 + function requireChunk(chunkPath) { 50 + switch(chunkPath) { 51 + ${chunks 52 + .map( 53 + (chunk) => 54 + ` case "${ 55 + // we only want the path after /path/to/.next/ 56 + chunk.replace(/.*\/\.next\//, "") 57 + }": return require("${chunk}");` 58 + ) 59 + .join("\n")} 60 + default: 61 + throw new Error(\`Not found \${chunkPath}\`); 62 + } 63 + } 64 + `; 65 + } 66 + 67 + // Turbopack imports `og` via `externalImport`. 68 + // We patch it to: 69 + // - add the explicit path so that the file is inlined by wrangler 70 + // - use the edge version of the module instead of the node version. 71 + // 72 + // Modules that are not inlined (no added to the switch), would generate an error similar to: 73 + // Failed to load external module path/to/module: Error: No such module "path/to/module" 74 + const inlineExternalImportRule = ` 75 + rule: 76 + pattern: "$RAW = await import($ID)" 77 + inside: 78 + regex: "externalImport" 79 + kind: function_declaration 80 + stopBy: end 81 + fix: |- 82 + switch ($ID) { 83 + case "next/dist/compiled/@vercel/og/index.node.js": 84 + $RAW = await import("next/dist/compiled/@vercel/og/index.edge.js"); 85 + break; 86 + default: 87 + $RAW = await import($ID); 88 + } 89 + `;