Skip to content

Commit 10250a0

Browse files
authored
feat: provide display name for split api routes (#2155)
* feat: provide display name for split api routes * fix: functions test
1 parent eb30624 commit 10250a0

File tree

5 files changed

+85
-17
lines changed

5 files changed

+85
-17
lines changed

packages/runtime/src/constants.ts

+2
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,13 @@ import destr from 'destr'
22

33
export const HANDLER_FUNCTION_NAME = '___netlify-handler'
44
export const ODB_FUNCTION_NAME = '___netlify-odb-handler'
5+
export const API_FUNCTION_NAME = '___netlify-api-handler'
56
export const IMAGE_FUNCTION_NAME = '_ipx'
67
export const NEXT_PLUGIN_NAME = '@netlify/next-runtime'
78
export const NEXT_PLUGIN = '@netlify/plugin-nextjs'
89
export const HANDLER_FUNCTION_TITLE = 'Next.js SSR handler'
910
export const ODB_FUNCTION_TITLE = 'Next.js ISR handler'
11+
export const API_FUNCTION_TITLE = 'Next.js API handler'
1012
export const IMAGE_FUNCTION_TITLE = 'next/image handler'
1113
// These are paths in .next that shouldn't be publicly accessible
1214
export const HIDDEN_PATHS = destr(process.env.NEXT_KEEP_METADATA_FILES)

packages/runtime/src/helpers/functions.ts

+48-10
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ import {
1616
HANDLER_FUNCTION_TITLE,
1717
ODB_FUNCTION_TITLE,
1818
IMAGE_FUNCTION_TITLE,
19+
API_FUNCTION_TITLE,
20+
API_FUNCTION_NAME,
1921
LAMBDA_WARNING_SIZE,
2022
} from '../constants'
2123
import { getApiHandler } from '../templates/getApiHandler'
@@ -32,6 +34,7 @@ import { getFunctionNameForPage } from './utils'
3234

3335
export interface ApiRouteConfig {
3436
functionName: string
37+
functionTitle?: string
3538
route: string
3639
config: ApiConfig
3740
compiled: string
@@ -40,6 +43,7 @@ export interface ApiRouteConfig {
4043

4144
export interface APILambda {
4245
functionName: string
46+
functionTitle: string
4347
routes: ApiRouteConfig[]
4448
includedFiles: string[]
4549
type?: ApiRouteType
@@ -61,7 +65,7 @@ export const generateFunctions = async (
6165
: undefined
6266

6367
for (const apiLambda of apiLambdas) {
64-
const { functionName, routes, type, includedFiles } = apiLambda
68+
const { functionName, functionTitle, routes, type, includedFiles } = apiLambda
6569

6670
const apiHandlerSource = getApiHandler({
6771
// most api lambdas serve multiple routes, but scheduled functions need to be in separate lambdas.
@@ -103,6 +107,8 @@ export const generateFunctions = async (
103107
})
104108
await writeFile(join(functionsDir, functionName, 'pages.js'), resolverSource)
105109

110+
await writeFunctionConfiguration({ functionName, functionTitle, functionsDir })
111+
106112
const nfInternalFiles = await glob(join(functionsDir, functionName, '**'))
107113
includedFiles.push(...nfInternalFiles)
108114
}
@@ -129,7 +135,7 @@ export const generateFunctions = async (
129135
join(__dirname, '..', '..', 'lib', 'templates', 'handlerUtils.js'),
130136
join(functionsDir, functionName, 'handlerUtils.js'),
131137
)
132-
writeFunctionConfiguration({ functionName, functionTitle, functionsDir })
138+
await writeFunctionConfiguration({ functionName, functionTitle, functionsDir })
133139
}
134140

135141
await writeHandler(HANDLER_FUNCTION_NAME, HANDLER_FUNCTION_TITLE, false)
@@ -333,12 +339,41 @@ export const getAPILambdas = async (
333339

334340
const bins = pack(weighedRoutes, threshold)
335341

336-
return bins.map((bin, index) => ({
337-
functionName: bin.length === 1 ? bin[0].functionName : `api-${index}`,
338-
routes: bin,
339-
includedFiles: [...commonDependencies, ...routes.flatMap((route) => route.includedFiles)],
340-
type,
341-
}))
342+
return bins.map((bin) => {
343+
if (bin.length === 1) {
344+
const [func] = bin
345+
const { functionName, functionTitle, config, includedFiles } = func
346+
return {
347+
functionName,
348+
functionTitle,
349+
routes: [func],
350+
includedFiles: [...commonDependencies, ...includedFiles],
351+
type: config.type,
352+
}
353+
}
354+
355+
const includedFiles = [...commonDependencies, ...bin.flatMap((route) => route.includedFiles)]
356+
const nonSingletonBins = bins.filter((b) => b.length > 1)
357+
if (nonSingletonBins.length === 1) {
358+
return {
359+
functionName: API_FUNCTION_NAME,
360+
functionTitle: API_FUNCTION_TITLE,
361+
includedFiles,
362+
routes: bin,
363+
type,
364+
}
365+
}
366+
367+
const indexInNonSingletonBins = nonSingletonBins.indexOf(bin)
368+
369+
return {
370+
functionName: `${API_FUNCTION_NAME}-${indexInNonSingletonBins + 1}`,
371+
functionTitle: `${API_FUNCTION_TITLE} ${indexInNonSingletonBins + 1}/${nonSingletonBins.length}`,
372+
includedFiles,
373+
routes: bin,
374+
type,
375+
}
376+
})
342377
}
343378

344379
const standardFunctions = apiRoutes.filter(
@@ -365,7 +400,7 @@ export const getAPILambdas = async (
365400
export const getApiRouteConfigs = async (
366401
publish: string,
367402
appDir: string,
368-
pageExtensions: string[],
403+
pageExtensions?: string[],
369404
): Promise<Array<ApiRouteConfig>> => {
370405
const pages = await readJSON(join(publish, 'server', 'pages-manifest.json'))
371406
const apiRoutes = Object.keys(pages).filter((page) => page.startsWith('/api/'))
@@ -380,6 +415,7 @@ export const getApiRouteConfigs = async (
380415
const config = await extractConfigFromFile(filePath, appDir)
381416

382417
const functionName = getFunctionNameForPage(apiRoute, config.type === ApiRouteType.BACKGROUND)
418+
const functionTitle = `${API_FUNCTION_TITLE} ${apiRoute}`
383419

384420
const compiled = pages[apiRoute]
385421
const compiledPath = join(publish, 'server', compiled)
@@ -389,6 +425,7 @@ export const getApiRouteConfigs = async (
389425

390426
return {
391427
functionName,
428+
functionTitle,
392429
route: apiRoute,
393430
config,
394431
compiled,
@@ -404,7 +441,7 @@ export const getApiRouteConfigs = async (
404441
export const getExtendedApiRouteConfigs = async (
405442
publish: string,
406443
appDir: string,
407-
pageExtensions: string[],
444+
pageExtensions?: string[],
408445
): Promise<Array<ApiRouteConfig>> => {
409446
const settledApiRoutes = await getApiRouteConfigs(publish, appDir, pageExtensions)
410447

@@ -414,6 +451,7 @@ export const getExtendedApiRouteConfigs = async (
414451

415452
export const packSingleFunction = (func: ApiRouteConfig): APILambda => ({
416453
functionName: func.functionName,
454+
functionTitle: func.functionTitle,
417455
includedFiles: func.includedFiles,
418456
routes: [func],
419457
type: func.config.type,

test/__snapshots__/index.spec.ts.snap

+6-6
Original file line numberDiff line numberDiff line change
@@ -2109,17 +2109,17 @@ Array [
21092109
Object {
21102110
"from": "/api/enterPreview",
21112111
"status": 200,
2112-
"to": "/.netlify/functions/api-0",
2112+
"to": "/.netlify/functions/___netlify-api-handler",
21132113
},
21142114
Object {
21152115
"from": "/api/exitPreview",
21162116
"status": 200,
2117-
"to": "/.netlify/functions/api-0",
2117+
"to": "/.netlify/functions/___netlify-api-handler",
21182118
},
21192119
Object {
21202120
"from": "/api/hello",
21212121
"status": 200,
2122-
"to": "/.netlify/functions/api-0",
2122+
"to": "/.netlify/functions/___netlify-api-handler",
21232123
},
21242124
Object {
21252125
"from": "/api/hello-background",
@@ -2134,17 +2134,17 @@ Array [
21342134
Object {
21352135
"from": "/api/revalidate",
21362136
"status": 200,
2137-
"to": "/.netlify/functions/api-0",
2137+
"to": "/.netlify/functions/___netlify-api-handler",
21382138
},
21392139
Object {
21402140
"from": "/api/shows/:id",
21412141
"status": 200,
2142-
"to": "/.netlify/functions/api-0",
2142+
"to": "/.netlify/functions/___netlify-api-handler",
21432143
},
21442144
Object {
21452145
"from": "/api/shows/:params/*",
21462146
"status": 200,
2147-
"to": "/.netlify/functions/api-0",
2147+
"to": "/.netlify/functions/___netlify-api-handler",
21482148
},
21492149
Object {
21502150
"force": false,

test/helpers/functions.spec.ts

+12-1
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,13 @@ import { describeCwdTmpDir, moveNextDist } from '../test-utils'
44
describeCwdTmpDir('api route file analysis', () => {
55
it('extracts correct route configs from source files', async () => {
66
await moveNextDist()
7-
const configs = await getApiRouteConfigs('.next', process.cwd(), ['js', 'jsx', 'ts', 'tsx'])
7+
const configs = await getApiRouteConfigs('.next', process.cwd())
88
// Using a Set means the order doesn't matter
99
expect(new Set(configs.map(({ includedFiles, ...rest }) => rest))).toEqual(
1010
new Set([
1111
{
1212
functionName: '_api_og-handler',
13+
functionTitle: 'Next.js API handler /api/og',
1314
compiled: 'pages/api/og.js',
1415
config: {
1516
runtime: 'edge',
@@ -18,48 +19,56 @@ describeCwdTmpDir('api route file analysis', () => {
1819
},
1920
{
2021
functionName: '_api_enterPreview-handler',
22+
functionTitle: 'Next.js API handler /api/enterPreview',
2123
compiled: 'pages/api/enterPreview.js',
2224
config: {},
2325
route: '/api/enterPreview',
2426
},
2527
{
2628
functionName: '_api_exitPreview-handler',
29+
functionTitle: 'Next.js API handler /api/exitPreview',
2730
compiled: 'pages/api/exitPreview.js',
2831
config: {},
2932
route: '/api/exitPreview',
3033
},
3134
{
3235
functionName: '_api_hello-handler',
36+
functionTitle: 'Next.js API handler /api/hello',
3337
compiled: 'pages/api/hello.js',
3438
config: {},
3539
route: '/api/hello',
3640
},
3741
{
3842
functionName: '_api_shows_params-SPLAT-handler',
43+
functionTitle: 'Next.js API handler /api/shows/[...params]',
3944
compiled: 'pages/api/shows/[...params].js',
4045
config: {},
4146
route: '/api/shows/[...params]',
4247
},
4348
{
4449
functionName: '_api_shows_id-PARAM-handler',
50+
functionTitle: 'Next.js API handler /api/shows/[id]',
4551
compiled: 'pages/api/shows/[id].js',
4652
config: {},
4753
route: '/api/shows/[id]',
4854
},
4955
{
5056
functionName: '_api_hello-background-background',
57+
functionTitle: 'Next.js API handler /api/hello-background',
5158
compiled: 'pages/api/hello-background.js',
5259
config: { type: 'experimental-background' },
5360
route: '/api/hello-background',
5461
},
5562
{
5663
functionName: '_api_hello-scheduled-handler',
64+
functionTitle: 'Next.js API handler /api/hello-scheduled',
5765
compiled: 'pages/api/hello-scheduled.js',
5866
config: { schedule: '@hourly', type: 'experimental-scheduled' },
5967
route: '/api/hello-scheduled',
6068
},
6169
{
6270
functionName: '_api_revalidate-handler',
71+
functionTitle: 'Next.js API handler /api/revalidate',
6372
compiled: 'pages/api/revalidate.js',
6473
config: {},
6574
route: '/api/revalidate',
@@ -76,12 +85,14 @@ describeCwdTmpDir('api route file analysis', () => {
7685
new Set([
7786
{
7887
functionName: '_api_hello-background-background',
88+
functionTitle: 'Next.js API handler /api/hello-background',
7989
compiled: 'pages/api/hello-background.js',
8090
config: { type: 'experimental-background' },
8191
route: '/api/hello-background',
8292
},
8393
{
8494
functionName: '_api_hello-scheduled-handler',
95+
functionTitle: 'Next.js API handler /api/hello-scheduled',
8596
compiled: 'pages/api/hello-scheduled.js',
8697
config: { schedule: '@hourly', type: 'experimental-scheduled' },
8798
route: '/api/hello-scheduled',

test/index.spec.ts

+17
Original file line numberDiff line numberDiff line change
@@ -738,6 +738,23 @@ describe('onBuild()', () => {
738738
expect(netlifyConfig.functions['_api_*'].node_bundler).toEqual('nft')
739739
})
740740

741+
it('provides displayname for split api routes', async () => {
742+
await moveNextDist()
743+
await nextRuntime.onBuild(defaultArgs)
744+
745+
const functionsManifest = await readJson(
746+
path.join('.netlify', 'functions-internal', '___netlify-api-handler', '___netlify-api-handler.json'),
747+
)
748+
749+
expect(functionsManifest).toEqual({
750+
config: {
751+
generator: '@netlify/next-runtime@unknown',
752+
name: 'Next.js API handler',
753+
},
754+
version: 1,
755+
})
756+
})
757+
741758
// eslint-disable-next-line jest/expect-expect
742759
it('works when `relativeAppDir` is undefined', async () => {
743760
await moveNextDist()

0 commit comments

Comments
 (0)