Skip to content

Commit 8f946a9

Browse files
Merge branch 'main' into fix-dynamic-api-routes
2 parents d19f422 + 9195192 commit 8f946a9

File tree

9 files changed

+439
-234
lines changed

9 files changed

+439
-234
lines changed

package-lock.json

+346-208
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@
5353
"@babel/preset-env": "^7.15.8",
5454
"@babel/preset-typescript": "^7.16.0",
5555
"@delucis/if-env": "^1.1.2",
56-
"@netlify/build": "^29.11.8",
56+
"@netlify/build": "^29.12.3",
5757
"@netlify/eslint-config-node": "^7.0.1",
5858
"@testing-library/cypress": "^9.0.0",
5959
"@types/fs-extra": "^9.0.13",

packages/runtime/package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@
3737
},
3838
"devDependencies": {
3939
"@delucis/if-env": "^1.1.2",
40-
"@netlify/build": "^29.11.8",
40+
"@netlify/build": "^29.12.3",
4141
"@types/fs-extra": "^9.0.13",
4242
"@types/jest": "^27.4.1",
4343
"@types/merge-stream": "^1.1.2",

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/files.ts

+4-4
Original file line numberDiff line numberDiff line change
@@ -404,19 +404,19 @@ const baseServerReplacements: Array<[string, string]> = [
404404
const nextServerReplacements: Array<[string, string]> = [
405405
[
406406
`getMiddlewareManifest() {\n if (this.minimalMode) return null;`,
407-
`getMiddlewareManifest() {\n if (this.minimalMode || (process.env.NEXT_DISABLE_NETLIFY_EDGE !== 'true' && process.env.NEXT_DISABLE_NETLIFY_EDGE !== '1')) return null;`,
407+
`getMiddlewareManifest() {\n if (this.minimalMode || (process.env.NETLIFY && process.env.NEXT_DISABLE_NETLIFY_EDGE !== 'true' && process.env.NEXT_DISABLE_NETLIFY_EDGE !== '1')) return null;`,
408408
],
409409
[
410410
`generateCatchAllMiddlewareRoute(devReady) {\n if (this.minimalMode) return []`,
411-
`generateCatchAllMiddlewareRoute(devReady) {\n if (this.minimalMode || (process.env.NEXT_DISABLE_NETLIFY_EDGE !== 'true' && process.env.NEXT_DISABLE_NETLIFY_EDGE !== '1')) return [];`,
411+
`generateCatchAllMiddlewareRoute(devReady) {\n if (this.minimalMode || (process.env.NETLIFY && process.env.NEXT_DISABLE_NETLIFY_EDGE !== 'true' && process.env.NEXT_DISABLE_NETLIFY_EDGE !== '1')) return [];`,
412412
],
413413
[
414414
`generateCatchAllMiddlewareRoute() {\n if (this.minimalMode) return undefined;`,
415-
`generateCatchAllMiddlewareRoute() {\n if (this.minimalMode || (process.env.NEXT_DISABLE_NETLIFY_EDGE !== 'true' && process.env.NEXT_DISABLE_NETLIFY_EDGE !== '1')) return undefined;`,
415+
`generateCatchAllMiddlewareRoute() {\n if (this.minimalMode || (process.env.NETLIFY && process.env.NEXT_DISABLE_NETLIFY_EDGE !== 'true' && process.env.NEXT_DISABLE_NETLIFY_EDGE !== '1')) return undefined;`,
416416
],
417417
[
418418
`getMiddlewareManifest() {\n if (this.minimalMode) {`,
419-
`getMiddlewareManifest() {\n if (!this.minimalMode && (process.env.NEXT_DISABLE_NETLIFY_EDGE === 'true' || process.env.NEXT_DISABLE_NETLIFY_EDGE === '1')) {`,
419+
`getMiddlewareManifest() {\n if (!this.minimalMode && (process.env.NETLIFY && process.env.NEXT_DISABLE_NETLIFY_EDGE === 'true' || process.env.NEXT_DISABLE_NETLIFY_EDGE === '1')) {`,
420420
],
421421
]
422422

packages/runtime/src/helpers/functions.ts

+50-13
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,9 @@ import {
1616
HANDLER_FUNCTION_TITLE,
1717
ODB_FUNCTION_TITLE,
1818
IMAGE_FUNCTION_TITLE,
19+
API_FUNCTION_TITLE,
20+
API_FUNCTION_NAME,
21+
LAMBDA_WARNING_SIZE,
1922
} from '../constants'
2023
import { getApiHandler } from '../templates/getApiHandler'
2124
import { getHandler } from '../templates/getHandler'
@@ -31,6 +34,7 @@ import { getFunctionNameForPage } from './utils'
3134

3235
export interface ApiRouteConfig {
3336
functionName: string
37+
functionTitle?: string
3438
route: string
3539
config: ApiConfig
3640
compiled: string
@@ -39,6 +43,7 @@ export interface ApiRouteConfig {
3943

4044
export interface APILambda {
4145
functionName: string
46+
functionTitle: string
4247
routes: ApiRouteConfig[]
4348
includedFiles: string[]
4449
type?: ApiRouteType
@@ -60,7 +65,7 @@ export const generateFunctions = async (
6065
: undefined
6166

6267
for (const apiLambda of apiLambdas) {
63-
const { functionName, routes, type, includedFiles } = apiLambda
68+
const { functionName, functionTitle, routes, type, includedFiles } = apiLambda
6469

6570
const apiHandlerSource = getApiHandler({
6671
// most api lambdas serve multiple routes, but scheduled functions need to be in separate lambdas.
@@ -102,6 +107,8 @@ export const generateFunctions = async (
102107
})
103108
await writeFile(join(functionsDir, functionName, 'pages.js'), resolverSource)
104109

110+
await writeFunctionConfiguration({ functionName, functionTitle, functionsDir })
111+
105112
const nfInternalFiles = await glob(join(functionsDir, functionName, '**'))
106113
includedFiles.push(...nfInternalFiles)
107114
}
@@ -128,7 +135,7 @@ export const generateFunctions = async (
128135
join(__dirname, '..', '..', 'lib', 'templates', 'handlerUtils.js'),
129136
join(functionsDir, functionName, 'handlerUtils.js'),
130137
)
131-
writeFunctionConfiguration({ functionName, functionTitle, functionsDir })
138+
await writeFunctionConfiguration({ functionName, functionTitle, functionsDir })
132139
}
133140

134141
await writeHandler(HANDLER_FUNCTION_NAME, HANDLER_FUNCTION_TITLE, false)
@@ -314,16 +321,14 @@ const getBundleWeight = async (patterns: string[]) => {
314321
return sum(sizes.flat(1))
315322
}
316323

317-
const MB = 1024 * 1024
318-
319324
export const getAPILambdas = async (
320325
publish: string,
321326
baseDir: string,
322327
pageExtensions: string[],
323328
): Promise<APILambda[]> => {
324329
const commonDependencies = await getAPIPRouteCommonDependencies(publish)
325330

326-
const threshold = 50 * MB - (await getBundleWeight(commonDependencies))
331+
const threshold = LAMBDA_WARNING_SIZE - (await getBundleWeight(commonDependencies))
327332

328333
const apiRoutes = await getApiRouteConfigs(publish, baseDir, pageExtensions)
329334

@@ -334,12 +339,41 @@ export const getAPILambdas = async (
334339

335340
const bins = pack(weighedRoutes, threshold)
336341

337-
return bins.map((bin, index) => ({
338-
functionName: bin.length === 1 ? bin[0].functionName : `api-${index}`,
339-
routes: bin,
340-
includedFiles: [...commonDependencies, ...routes.flatMap((route) => route.includedFiles)],
341-
type,
342-
}))
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+
})
343377
}
344378

345379
const standardFunctions = apiRoutes.filter(
@@ -366,7 +400,7 @@ export const getAPILambdas = async (
366400
export const getApiRouteConfigs = async (
367401
publish: string,
368402
appDir: string,
369-
pageExtensions: string[],
403+
pageExtensions?: string[],
370404
): Promise<Array<ApiRouteConfig>> => {
371405
const pages = await readJSON(join(publish, 'server', 'pages-manifest.json'))
372406
const apiRoutes = Object.keys(pages).filter((page) => page.startsWith('/api/'))
@@ -381,6 +415,7 @@ export const getApiRouteConfigs = async (
381415
const config = await extractConfigFromFile(filePath, appDir)
382416

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

385420
const compiled = pages[apiRoute]
386421
const compiledPath = join(publish, 'server', compiled)
@@ -390,6 +425,7 @@ export const getApiRouteConfigs = async (
390425

391426
return {
392427
functionName,
428+
functionTitle,
393429
route: apiRoute,
394430
config,
395431
compiled,
@@ -405,7 +441,7 @@ export const getApiRouteConfigs = async (
405441
export const getExtendedApiRouteConfigs = async (
406442
publish: string,
407443
appDir: string,
408-
pageExtensions: string[],
444+
pageExtensions?: string[],
409445
): Promise<Array<ApiRouteConfig>> => {
410446
const settledApiRoutes = await getApiRouteConfigs(publish, appDir, pageExtensions)
411447

@@ -415,6 +451,7 @@ export const getExtendedApiRouteConfigs = async (
415451

416452
export const packSingleFunction = (func: ApiRouteConfig): APILambda => ({
417453
functionName: func.functionName,
454+
functionTitle: func.functionTitle,
418455
includedFiles: func.includedFiles,
419456
routes: [func],
420457
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)