Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
28 changes: 28 additions & 0 deletions examples/e2e/app-router/app/isr-data-cache/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { unstable_cache } from "next/cache";

async function getTime() {
return new Date().toISOString();
}

const cachedTime = unstable_cache(getTime, { revalidate: false });

export const revalidate = 10;

export default async function ISR() {
const responseOpenNext = await fetch("https://opennext.js.org", {
cache: "force-cache",
});
const dateInOpenNext = responseOpenNext.headers.get("date");
const cachedTimeValue = await cachedTime();
const time = getTime();
return (
<div>
<h1>Date from from OpenNext</h1>
<p data-testid="fetched-date">Date from from OpenNext: {dateInOpenNext}</p>
<h1>Cached Time</h1>
<p data-testid="cached-date">Cached Time: {cachedTimeValue}</p>
<h1>Time</h1>
<p data-testid="time">Time: {time}</p>
</div>
);
}
37 changes: 37 additions & 0 deletions examples/e2e/app-router/e2e/isr.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,3 +61,40 @@ test("headers", async ({ page }) => {
await page.reload();
}
});

test("Incremental Static Regeneration with data cache", async ({ page }) => {
test.setTimeout(45000);
await page.goto("/isr-data-cache");

// Before doing anything else, we need to ensure that there was at least one revalidation, otherwise the first test will fail
// That's because the unstable_cache is not properly populated (build time generated key are different than runtime)
let tempTime = await page.getByTestId("time").textContent();
do {
await page.waitForTimeout(1000);
tempTime = await page.getByTestId("time").textContent();
await page.reload();
} while (tempTime === (await page.getByTestId("time").textContent()));

const originalFetchedDate = await page.getByTestId("fetched-date").textContent();
const originalCachedDate = await page.getByTestId("cached-date").textContent();
const originalTime = await page.getByTestId("time").textContent();
await page.reload();

let finalTime = originalTime;
let finalCachedDate = originalCachedDate;
let finalFetchedDate = originalFetchedDate;

// Wait 10 + 1 seconds for ISR to regenerate time
await page.waitForTimeout(11000);
do {
await page.waitForTimeout(2000);
finalTime = await page.getByTestId("time").textContent();
finalCachedDate = await page.getByTestId("cached-date").textContent();
finalFetchedDate = await page.getByTestId("fetched-date").textContent();
await page.reload();
} while (originalTime === finalTime);

expect(originalTime).not.toEqual(finalTime);
expect(originalCachedDate).toEqual(finalCachedDate);
expect(originalFetchedDate).toEqual(finalFetchedDate);
});
2 changes: 1 addition & 1 deletion examples/e2e/app-router/open-next.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,6 @@ import doQueue from "@opennextjs/cloudflare/durable-queue";

export default defineCloudflareConfig({
incrementalCache: kvIncrementalCache,
tagCache: shardedTagCache({ numberOfShards: 12, regionalCache: true }),
tagCache: shardedTagCache({ numberOfShards: 12 }),
queue: doQueue,
});
6 changes: 2 additions & 4 deletions packages/cloudflare/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -71,12 +71,10 @@
"vitest": "catalog:"
},
"dependencies": {
"@ast-grep/napi": "^0.36.1",
"@dotenvx/dotenvx": "catalog:",
"@opennextjs/aws": "https://pkg.pr.new/@opennextjs/aws@778",
"@opennextjs/aws": "^3.5.3",
"enquirer": "^2.4.1",
"glob": "catalog:",
"yaml": "^2.7.0"
"glob": "catalog:"
},
"peerDependencies": {
"wrangler": "catalog:"
Expand Down
4 changes: 2 additions & 2 deletions packages/cloudflare/src/cli/build/build.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,8 +92,8 @@ export async function build(
}

function ensureNextjsVersionSupported(options: buildHelper.BuildOptions) {
if (buildHelper.compareSemver(options.nextVersion, "14.0.0") < 0) {
logger.error("Next.js version unsupported, please upgrade to version 14 or greater.");
if (buildHelper.compareSemver(options.nextVersion, "<", "14.2.0")) {
logger.error("Next.js version unsupported, please upgrade to version 14.2 or greater.");
process.exit(1);
}
}
20 changes: 5 additions & 15 deletions packages/cloudflare/src/cli/build/bundle-server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,15 @@ import path from "node:path";
import { fileURLToPath } from "node:url";

import { type BuildOptions, getPackagePath } from "@opennextjs/aws/build/helper.js";
import { build } from "esbuild";
import { ContentUpdater } from "@opennextjs/aws/plugins/content-updater.js";
import { build, type Plugin } from "esbuild";

import { patchVercelOgLibrary } from "./patches/ast/patch-vercel-og-library.js";
import { patchWebpackRuntime } from "./patches/ast/webpack-runtime.js";
import * as patches from "./patches/index.js";
import { inlineBuildId } from "./patches/plugins/build-id.js";
import { ContentUpdater } from "./patches/plugins/content-updater.js";
import { inlineDynamicRequires } from "./patches/plugins/dynamic-requires.js";
import { inlineEvalManifest } from "./patches/plugins/eval-manifest.js";
import { patchFetchCacheSetMissingWaitUntil } from "./patches/plugins/fetch-cache-wait-until.js";
import { inlineFindDir } from "./patches/plugins/find-dir.js";
import { patchInstrumentation } from "./patches/plugins/instrumentation.js";
import { inlineLoadManifest } from "./patches/plugins/load-manifest.js";
Expand Down Expand Up @@ -68,7 +67,7 @@ export async function bundleServer(buildOpts: BuildOptions): Promise<void> {
const openNextServer = path.join(outputPath, packagePath, `index.mjs`);
const openNextServerBundle = path.join(outputPath, packagePath, `handler.mjs`);

const updater = new ContentUpdater();
const updater = new ContentUpdater(buildOpts);

const result = await build({
entryPoints: [openNextServer],
Expand All @@ -95,16 +94,15 @@ export async function bundleServer(buildOpts: BuildOptions): Promise<void> {
fixRequire(updater),
handleOptionalDependencies(optionalDependencies),
patchInstrumentation(updater, buildOpts),
patchFetchCacheSetMissingWaitUntil(updater),
inlineEvalManifest(updater, buildOpts),
inlineFindDir(updater, buildOpts),
inlineLoadManifest(updater, buildOpts),
inlineBuildId(updater),
patchDepdDeprecations(updater),
patchNextMinimal(updater),
// Apply updater updaters, must be the last plugin
// Apply updater updates, must be the last plugin
updater.plugin,
],
] as Plugin[],
external: ["./middleware/handler.mjs"],
alias: {
// Note: it looks like node-fetch is actually not necessary for us, so we could replace it with an empty shim
Expand All @@ -119,7 +117,6 @@ export async function bundleServer(buildOpts: BuildOptions): Promise<void> {
// Note: we apply an empty shim to next/dist/compiled/edge-runtime since (amongst others) it generated the following `eval`:
// eval(getModuleCode)(module, module.exports, throwingRequire, params.context, ...Object.values(params.scopedContext));
// which comes from https://github.com/vercel/edge-runtime/blob/6e96b55f/packages/primitives/src/primitives/load.js#L57-L63
// QUESTION: Why did I encountered this but mhart didn't?
"next/dist/compiled/edge-runtime": path.join(
buildOpts.outputDir,
"cloudflare-templates/shims/empty.js"
Expand Down Expand Up @@ -220,13 +217,6 @@ export async function updateWorkerBundledCode(
"'require(this.middlewareManifestPath)'",
(code) => patches.inlineMiddlewareManifestRequire(code, buildOpts),
],
[
"`patchAsyncStorage` call",
(code) =>
code
// TODO: implement for cf (possibly in @opennextjs/aws)
.replace("patchAsyncStorage();", "//patchAsyncStorage();"),
],
[
"`require.resolve` call",
// workers do not support dynamic require nor require.resolve
Expand Down
48 changes: 40 additions & 8 deletions packages/cloudflare/src/cli/build/open-next/createServerBundle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,18 +10,38 @@ import { copyTracedFiles } from "@opennextjs/aws/build/copyTracedFiles.js";
import { generateEdgeBundle } from "@opennextjs/aws/build/edge/createEdgeBundle.js";
import * as buildHelper from "@opennextjs/aws/build/helper.js";
import { installDependencies } from "@opennextjs/aws/build/installDeps.js";
import type { CodePatcher } from "@opennextjs/aws/build/patch/codePatcher.js";
import { applyCodePatches } from "@opennextjs/aws/build/patch/codePatcher.js";
import {
patchFetchCacheForISR,
patchUnstableCacheForISR,
} from "@opennextjs/aws/build/patch/patchFetchCacheISR.js";
import { patchFetchCacheSetMissingWaitUntil } from "@opennextjs/aws/build/patch/patchFetchCacheWaitUntil.js";
import logger from "@opennextjs/aws/logger.js";
import { minifyAll } from "@opennextjs/aws/minimize-js.js";
import type { ContentUpdater } from "@opennextjs/aws/plugins/content-updater.js";
import { openNextEdgePlugins } from "@opennextjs/aws/plugins/edge.js";
import { openNextExternalMiddlewarePlugin } from "@opennextjs/aws/plugins/externalMiddleware.js";
import { openNextReplacementPlugin } from "@opennextjs/aws/plugins/replacement.js";
import { openNextResolvePlugin } from "@opennextjs/aws/plugins/resolve.js";
import type { FunctionOptions, SplittedFunctionOptions } from "@opennextjs/aws/types/open-next.js";
import { getCrossPlatformPathRegex } from "@opennextjs/aws/utils/regex.js";
import type { Plugin } from "esbuild";

import { normalizePath } from "../utils/index.js";

export async function createServerBundle(options: buildHelper.BuildOptions) {
interface CodeCustomization {
// These patches are meant to apply on user and next generated code
additionalCodePatches?: CodePatcher[];
// These plugins are meant to apply during the esbuild bundling process.
// This will only apply to OpenNext code.
additionalPlugins?: (contentUpdater: ContentUpdater) => Plugin[];
}

export async function createServerBundle(
options: buildHelper.BuildOptions,
codeCustomization?: CodeCustomization
) {
const { config } = options;
const foundRoutes = new Set<string>();
// Get all functions to build
Expand All @@ -39,7 +59,7 @@ export async function createServerBundle(options: buildHelper.BuildOptions) {
if (fnOptions.runtime === "edge") {
await generateEdgeBundle(name, options, fnOptions);
} else {
await generateBundle(name, options, fnOptions);
await generateBundle(name, options, fnOptions, codeCustomization);
}
});

Expand Down Expand Up @@ -102,7 +122,8 @@ export async function createServerBundle(options: buildHelper.BuildOptions) {
async function generateBundle(
name: string,
options: buildHelper.BuildOptions,
fnOptions: SplittedFunctionOptions
fnOptions: SplittedFunctionOptions,
codeCustomization?: CodeCustomization
) {
const { appPath, appBuildOutputPath, config, outputDir, monorepoRoot } = options;
logger.info(`Building server function: ${name}...`);
Expand Down Expand Up @@ -151,27 +172,37 @@ async function generateBundle(
buildHelper.copyEnvFile(appBuildOutputPath, packagePath, outputPath);

// Copy all necessary traced files
await copyTracedFiles({
const { tracedFiles, manifests } = await copyTracedFiles({
buildOutputPath: appBuildOutputPath,
packagePath,
outputDir: outputPath,
routes: fnOptions.routes ?? ["app/page.tsx"],
bundledNextServer: isBundled,
});

const additionalCodePatches = codeCustomization?.additionalCodePatches ?? [];

await applyCodePatches(options, tracedFiles, manifests, [
patchFetchCacheSetMissingWaitUntil,
patchFetchCacheForISR,
patchUnstableCacheForISR,
...additionalCodePatches,
]);

// Build Lambda code
// note: bundle in OpenNext package b/c the adapter relies on the
// "serverless-http" package which is not a dependency in user's
// Next.js app.

const disableNextPrebundledReact =
buildHelper.compareSemver(options.nextVersion, "13.5.1") >= 0 ||
buildHelper.compareSemver(options.nextVersion, "13.4.1") <= 0;
buildHelper.compareSemver(options.nextVersion, ">=", "13.5.1") ||
buildHelper.compareSemver(options.nextVersion, "<=", "13.4.1");

const overrides = fnOptions.override ?? {};

const isBefore13413 = buildHelper.compareSemver(options.nextVersion, "13.4.13") <= 0;
const isAfter141 = buildHelper.compareSemver(options.nextVersion, "14.1") >= 0;
const isBefore13413 = buildHelper.compareSemver(options.nextVersion, "<=", "13.4.13");
const isAfter141 = buildHelper.compareSemver(options.nextVersion, ">=", "14.1");
const isAfter142 = buildHelper.compareSemver(options.nextVersion, ">=", "14.2");

const disableRouting = isBefore13413 || config.middleware?.external;

Expand All @@ -182,6 +213,7 @@ async function generateBundle(
deletes: [
...(disableNextPrebundledReact ? ["applyNextjsPrebundledReact"] : []),
...(disableRouting ? ["withRouting"] : []),
...(isAfter142 ? ["patchAsyncStorage"] : []),
],
}),
openNextReplacementPlugin({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@ import path from "node:path";

import type { BuildOptions } from "@opennextjs/aws/build/helper.js";
import { getPackagePath } from "@opennextjs/aws/build/helper.js";
import { parseFile } from "@opennextjs/aws/build/patch/astCodePatcher.js";
import { globSync } from "glob";

import { parseFile } from "./util.js";
import { patchVercelOgFallbackFont, patchVercelOgImport } from "./vercel-og.js";

type TraceInfo = { version: number; files: string[] };
Expand Down
65 changes: 0 additions & 65 deletions packages/cloudflare/src/cli/build/patches/ast/util.spec.ts

This file was deleted.

Loading