Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/silver-phones-brush.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@opennextjs/cloudflare": minor
---

feat: turbopack support
4 changes: 2 additions & 2 deletions examples/common/config-e2e.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,9 @@ export function configurePlaywright(
if (isCI) {
// Do not build on CI - there is a preceding build step
command = `pnpm preview:worker -- --port ${port} --inspector-port ${inspectorPort} ${env}`;
timeout = 200_000;
timeout = 800_000;
} else {
timeout = 500_000;
timeout = 800_000;
command = `pnpm preview -- --port ${port} --inspector-port ${inspectorPort} ${env}`;
}
} else {
Expand Down
2 changes: 1 addition & 1 deletion examples/e2e/app-router/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
"scripts": {
"openbuild": "node ../../packages/open-next/dist/index.js build --streaming --build-command \"npx turbo build\"",
"dev": "next dev --turbopack --port 3001",
"build": "next build",
"build": "next build --turbopack",
"start": "next start --port 3001",
"lint": "next lint",
"clean": "rm -rf .turbo node_modules .next .open-next",
Expand Down
4 changes: 0 additions & 4 deletions packages/cloudflare/src/cli/build/bundle-server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -144,10 +144,6 @@ export async function bundleServer(buildOpts: BuildOptions, projectOpts: Project
// We make sure that environment variables that Next.js expects are properly defined
"process.env.NEXT_RUNTIME": '"nodejs"',
"process.env.NODE_ENV": '"production"',
// The 2 following defines are used to reduce the bundle size by removing unnecessary code
// Next uses different precompiled renderers (i.e. `app-page.runtime.prod.js`) based on if you use `TURBOPACK` or some experimental React features
// Turbopack is not supported for build at the moment, so we disable it
"process.env.TURBOPACK": "false",
// This define should be safe to use for Next 14.2+, earlier versions (13.5 and less) will cause trouble
"process.env.__NEXT_EXPERIMENTAL_REACT": `${needsExperimentalReact(nextConfig)}`,
// Fix `res.validate` in Next 15.4 (together with the `route-module` patch)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import type { Plugin } from "esbuild";

import { getOpenNextConfig } from "../../../api/config.js";
import { patchResRevalidate } from "../patches/plugins/res-revalidate.js";
import { patchTurbopackRuntime } from "../patches/plugins/turbopack.js";
import { patchUseCacheIO } from "../patches/plugins/use-cache.js";
import { normalizePath } from "../utils/index.js";
import { copyWorkerdPackages } from "../utils/workerd.js";
Expand Down Expand Up @@ -210,6 +211,7 @@ async function generateBundle(
// Cloudflare specific patches
patchResRevalidate,
patchUseCacheIO,
patchTurbopackRuntime,
...additionalCodePatches,
]);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ export function patchVercelOgLibrary(buildOpts: BuildOptions) {
for (const traceInfoPath of globSync(path.join(appBuildOutputPath, ".next/server/**/*.nft.json"), {
windowsPathsNoEscape: true,
})) {
let edgeFilePatched = false;

const traceInfo: TraceInfo = JSON.parse(readFileSync(traceInfoPath, { encoding: "utf8" }));
const tracedNodePath = traceInfo.files.find((p) => p.endsWith("@vercel/og/index.node.js"));

Expand All @@ -40,17 +42,23 @@ export function patchVercelOgLibrary(buildOpts: BuildOptions) {
);

copyFileSync(tracedEdgePath, outputEdgePath);
}

if (!edgeFilePatched) {
edgeFilePatched = true;
// Change font fetches in the library to use imports.
const node = parseFile(outputEdgePath);
const { edits, matches } = patchVercelOgFallbackFont(node);
writeFileSync(outputEdgePath, node.commitEdits(edits));

const fontFileName = matches[0]!.getMatch("PATH")!.text();
renameSync(path.join(outputDir, fontFileName), path.join(outputDir, `${fontFileName}.bin`));
if (matches.length > 0) {
const fontFileName = matches[0]!.getMatch("PATH")!.text();
renameSync(path.join(outputDir, fontFileName), path.join(outputDir, `${fontFileName}.bin`));
}
}

// Change node imports for the library to edge imports.
// This is only useful when tubopack is not used to bundle the function.
const routeFilePath = traceInfoPath.replace(appBuildOutputPath, packagePath).replace(".nft.json", "");

const node = parseFile(routeFilePath);
Expand Down
86 changes: 86 additions & 0 deletions packages/cloudflare/src/cli/build/patches/plugins/turbopack.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
import { patchCode } from "@opennextjs/aws/build/patch/astCodePatcher.js";
import type { CodePatcher } from "@opennextjs/aws/build/patch/codePatcher.js";
import { getCrossPlatformPathRegex } from "@opennextjs/aws/utils/regex.js";

const inlineChunksRule = `
rule:
kind: call_expression
pattern: require(resolved)
fix:
requireChunk(chunkPath)
`;

export const patchTurbopackRuntime: CodePatcher = {
name: "inline-turbopack-chunks",
patches: [
{
versions: ">=15.0.0",
pathFilter: getCrossPlatformPathRegex(String.raw`\[turbopack\]_runtime\.js$`, {
escape: false,
}),
contentFilter: /loadRuntimeChunkPath/,
patchCode: async ({ code, tracedFiles }) => {
let patched = patchCode(code, inlineExternalImportRule);
patched = patchCode(patched, inlineChunksRule);

return `${patched}\n${inlineChunksFn(tracedFiles)}`;
},
},
],
};

function getInlinableChunks(tracedFiles: string[]): string[] {
const chunks = new Set<string>();
for (const file of tracedFiles) {
if (file === "[turbopack]_runtime.js") {
continue;
}
if (file.includes(".next/server/chunks/")) {
chunks.add(file);
}
}
return Array.from(chunks);
}

function inlineChunksFn(tracedFiles: string[]) {
// From the outputs, we extract every chunks
const chunks = getInlinableChunks(tracedFiles);
return `
function requireChunk(chunkPath) {
switch(chunkPath) {
${chunks
.map(
(chunk) =>
` case "${
// we only want the path after /path/to/.next/
chunk.replace(/.*\/\.next\//, "")
}": return require("${chunk}");`
)
.join("\n")}
default:
throw new Error(\`Not found \${chunkPath}\`);
}
}
`;
}

// Turbopack imports `og` via `externalImport`.
// We patch it to:
// - add the explicit path so that the file is inlined by wrangler
// - use the edge version of the module instead of the node version.
const inlineExternalImportRule = `
rule:
pattern: "$RAW = await import($ID)"
inside:
regex: "externalImport"
kind: function_declaration
stopBy: end
fix: |-
switch ($ID) {
case "next/dist/compiled/@vercel/og/index.node.js":
$RAW = await import("next/dist/compiled/@vercel/og/index.edge.js");
break;
default:
$RAW = await import($ID);
}
`;
Loading