From 97d79811000d487da7671e2768fcd365654bb286 Mon Sep 17 00:00:00 2001 From: James Anderson Date: Wed, 10 Jul 2024 12:38:11 +0100 Subject: [PATCH] support prerendered api functions (#826) --- .changeset/quiet-glasses-buy.md | 5 ++ .../processVercelFunctions/configs.ts | 26 +++++++-- packages/next-on-pages/src/utils/fs.ts | 16 +++--- .../next-on-pages/tests/src/utils/fs.test.ts | 56 +++++++++++-------- 4 files changed, 66 insertions(+), 37 deletions(-) create mode 100644 .changeset/quiet-glasses-buy.md diff --git a/.changeset/quiet-glasses-buy.md b/.changeset/quiet-glasses-buy.md new file mode 100644 index 000000000..cab02f5ed --- /dev/null +++ b/.changeset/quiet-glasses-buy.md @@ -0,0 +1,5 @@ +--- +'@cloudflare/next-on-pages': patch +--- + +Support for pre-rendered `API` functions. diff --git a/packages/next-on-pages/src/buildApplication/processVercelFunctions/configs.ts b/packages/next-on-pages/src/buildApplication/processVercelFunctions/configs.ts index 82bc1c663..a7aa5bf65 100644 --- a/packages/next-on-pages/src/buildApplication/processVercelFunctions/configs.ts +++ b/packages/next-on-pages/src/buildApplication/processVercelFunctions/configs.ts @@ -1,10 +1,11 @@ import { join, relative } from 'node:path'; +import type { PathInfo } from '../../utils'; import { addLeadingSlash, formatRoutePath, getRouteOverrides, normalizePath, - readDirectories, + readFilesAndDirectories, readJsonFile, } from '../../utils'; @@ -25,7 +26,9 @@ export async function collectFunctionConfigsRecursively( ignoredFunctions: new Map(), }, ): Promise { - const dirs = await readDirectories(baseDir); + const paths = await readFilesAndDirectories(baseDir); + const dirs = paths.filter(path => path.isDirectory); + const files = paths.filter(path => !path.isDirectory); for (const { path } of dirs) { if (path.endsWith('.func')) { @@ -36,10 +39,14 @@ export async function collectFunctionConfigsRecursively( normalizePath(relative(configs.functionsDir, path)), ); - if ( + const isPrerenderedIsrFunc = config?.operationType?.toLowerCase() === 'isr' && - !path.endsWith('.action.func') - ) { + !path.endsWith('.action.func'); + const isPrerenderedApiFunc = + config?.operationType?.toLowerCase() === 'api' && + checkPrerenderConfigExists(path, files); + + if (isPrerenderedIsrFunc || isPrerenderedApiFunc) { configs.prerenderedFunctions.set(path, { relativePath, config }); } else if (config?.runtime?.toLowerCase() === 'edge') { const formattedPathName = formatRoutePath(relativePath); @@ -61,6 +68,15 @@ export async function collectFunctionConfigsRecursively( return configs; } +function checkPrerenderConfigExists(funcPath: string, files: PathInfo[]) { + const prerenderConfigPath = funcPath.replace( + /\.func$/, + '.prerender-config.json', + ); + + return files.find(({ path }) => path === prerenderConfigPath); +} + export type CollectedFunctions = { functionsDir: string; edgeFunctions: Map; diff --git a/packages/next-on-pages/src/utils/fs.ts b/packages/next-on-pages/src/utils/fs.ts index 85f7423ec..e0bf18df6 100644 --- a/packages/next-on-pages/src/utils/fs.ts +++ b/packages/next-on-pages/src/utils/fs.ts @@ -129,18 +129,18 @@ export async function copyFileWithDir(sourceFile: string, destFile: string) { } /** - * Reads all directories in a directory. + * Reads all files and directories in a directory. * - * @param path Path to read directories from. - * @returns Array of all directories in a directory. + * @param path Path to read from. + * @returns Array of all files and directories in a directory. */ -export async function readDirectories( +export async function readFilesAndDirectories( basePath: string, -): Promise { +): Promise { try { const files = await readdir(basePath, { withFileTypes: true }); - const dirs = await Promise.all( + const paths = await Promise.all( files.map(async file => { const path = normalizePath(join(basePath, file.name)); const isSymbolicLink = file.isSymbolicLink(); @@ -151,13 +151,13 @@ export async function readDirectories( }), ); - return dirs.filter(file => file.isDirectory); + return paths; } catch { return []; } } -type DirectoryInfo = { +export type PathInfo = { name: string; path: string; isDirectory: boolean; diff --git a/packages/next-on-pages/tests/src/utils/fs.test.ts b/packages/next-on-pages/tests/src/utils/fs.test.ts index 60de57ad6..ea8906df9 100644 --- a/packages/next-on-pages/tests/src/utils/fs.test.ts +++ b/packages/next-on-pages/tests/src/utils/fs.test.ts @@ -3,7 +3,7 @@ import { copyFileWithDir, getFileHash, normalizePath, - readDirectories, + readFilesAndDirectories, readJsonFile, readPathsRecursively, validateDir, @@ -183,7 +183,7 @@ describe('copyFileWithDir', () => { }); }); -describe('readDirectories', () => { +describe('readFilesAndDirectories', () => { beforeAll(() => { mockFs({ root: { @@ -193,6 +193,7 @@ describe('readDirectories', () => { }, 'index.func': { 'index.js': 'index-js-code' }, 'home.func': { 'index.js': 'home-js-code' }, + 'test.js': 'test-js-code', }, }, }); @@ -200,32 +201,39 @@ describe('readDirectories', () => { afterAll(() => mockFs.restore()); - test('should read all directories inside the provided folder', async () => { - const dirs = await readDirectories('root/functions'); + test('should read all files and directories inside the provided folder', async () => { + const dirs = await readFilesAndDirectories('root/functions'); - expect(dirs.length).toEqual(3); - expect(dirs[0]).toEqual({ - name: '(route-group)', - path: 'root/functions/(route-group)', - isDirectory: true, - isSymbolicLink: false, - }); - expect(dirs[1]).toEqual({ - name: 'home.func', - path: 'root/functions/home.func', - isDirectory: true, - isSymbolicLink: false, - }); - expect(dirs[2]).toEqual({ - name: 'index.func', - path: 'root/functions/index.func', - isDirectory: true, - isSymbolicLink: false, - }); + expect(dirs).toEqual([ + { + name: '(route-group)', + path: 'root/functions/(route-group)', + isDirectory: true, + isSymbolicLink: false, + }, + { + name: 'home.func', + path: 'root/functions/home.func', + isDirectory: true, + isSymbolicLink: false, + }, + { + name: 'index.func', + path: 'root/functions/index.func', + isDirectory: true, + isSymbolicLink: false, + }, + { + name: 'test.js', + path: 'root/functions/test.js', + isDirectory: false, + isSymbolicLink: false, + }, + ]); }); test('invalid directory returns empty array', async () => { - const dirs = await readDirectories('invalid-root/functions'); + const dirs = await readFilesAndDirectories('invalid-root/functions'); expect(dirs).toEqual([]); });