Skip to content

feat: provide display name for split api routes #2155

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Jun 12, 2023
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
2 changes: 2 additions & 0 deletions packages/runtime/src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,13 @@ import destr from 'destr'

export const HANDLER_FUNCTION_NAME = '___netlify-handler'
export const ODB_FUNCTION_NAME = '___netlify-odb-handler'
export const API_FUNCTION_NAME = '___netlify-api-handler'
export const IMAGE_FUNCTION_NAME = '_ipx'
export const NEXT_PLUGIN_NAME = '@netlify/next-runtime'
export const NEXT_PLUGIN = '@netlify/plugin-nextjs'
export const HANDLER_FUNCTION_TITLE = 'Next.js SSR handler'
export const ODB_FUNCTION_TITLE = 'Next.js ISR handler'
export const API_FUNCTION_TITLE = 'Next.js API handler'
export const IMAGE_FUNCTION_TITLE = 'next/image handler'
// These are paths in .next that shouldn't be publicly accessible
export const HIDDEN_PATHS = destr(process.env.NEXT_KEEP_METADATA_FILES)
Expand Down
58 changes: 48 additions & 10 deletions packages/runtime/src/helpers/functions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ import {
HANDLER_FUNCTION_TITLE,
ODB_FUNCTION_TITLE,
IMAGE_FUNCTION_TITLE,
API_FUNCTION_TITLE,
API_FUNCTION_NAME,
LAMBDA_WARNING_SIZE,
} from '../constants'
import { getApiHandler } from '../templates/getApiHandler'
Expand All @@ -32,6 +34,7 @@ import { getFunctionNameForPage } from './utils'

export interface ApiRouteConfig {
functionName: string
functionTitle?: string
route: string
config: ApiConfig
compiled: string
Expand All @@ -40,6 +43,7 @@ export interface ApiRouteConfig {

export interface APILambda {
functionName: string
functionTitle: string
routes: ApiRouteConfig[]
includedFiles: string[]
type?: ApiRouteType
Expand All @@ -61,7 +65,7 @@ export const generateFunctions = async (
: undefined

for (const apiLambda of apiLambdas) {
const { functionName, routes, type, includedFiles } = apiLambda
const { functionName, functionTitle, routes, type, includedFiles } = apiLambda

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

await writeFunctionConfiguration({ functionName, functionTitle, functionsDir })

const nfInternalFiles = await glob(join(functionsDir, functionName, '**'))
includedFiles.push(...nfInternalFiles)
}
Expand All @@ -129,7 +135,7 @@ export const generateFunctions = async (
join(__dirname, '..', '..', 'lib', 'templates', 'handlerUtils.js'),
join(functionsDir, functionName, 'handlerUtils.js'),
)
writeFunctionConfiguration({ functionName, functionTitle, functionsDir })
await writeFunctionConfiguration({ functionName, functionTitle, functionsDir })
}

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

const bins = pack(weighedRoutes, threshold)

return bins.map((bin, index) => ({
functionName: bin.length === 1 ? bin[0].functionName : `api-${index}`,
routes: bin,
includedFiles: [...commonDependencies, ...routes.flatMap((route) => route.includedFiles)],
type,
}))
return bins.map((bin) => {
if (bin.length === 1) {
const [func] = bin
const { functionName, functionTitle, config, includedFiles } = func
return {
functionName,
functionTitle,
routes: [func],
includedFiles: [...commonDependencies, ...includedFiles],
type: config.type,
}
}

const includedFiles = [...commonDependencies, ...bin.flatMap((route) => route.includedFiles)]
const nonSingletonBins = bins.filter((b) => b.length > 1)
if (nonSingletonBins.length === 1) {
return {
functionName: API_FUNCTION_NAME,
functionTitle: API_FUNCTION_TITLE,
includedFiles,
routes: bin,
type,
}
}

const indexInNonSingletonBins = nonSingletonBins.indexOf(bin)

return {
functionName: `${API_FUNCTION_NAME}-${indexInNonSingletonBins + 1}`,
functionTitle: `${API_FUNCTION_TITLE} ${indexInNonSingletonBins + 1}/${nonSingletonBins.length}`,
includedFiles,
routes: bin,
type,
}
})
}

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

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

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

return {
functionName,
functionTitle,
route: apiRoute,
config,
compiled,
Expand All @@ -404,7 +441,7 @@ export const getApiRouteConfigs = async (
export const getExtendedApiRouteConfigs = async (
publish: string,
appDir: string,
pageExtensions: string[],
pageExtensions?: string[],
): Promise<Array<ApiRouteConfig>> => {
const settledApiRoutes = await getApiRouteConfigs(publish, appDir, pageExtensions)

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

export const packSingleFunction = (func: ApiRouteConfig): APILambda => ({
functionName: func.functionName,
functionTitle: func.functionTitle,
includedFiles: func.includedFiles,
routes: [func],
type: func.config.type,
Expand Down
12 changes: 6 additions & 6 deletions test/__snapshots__/index.spec.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -2109,17 +2109,17 @@ Array [
Object {
"from": "/api/enterPreview",
"status": 200,
"to": "/.netlify/functions/api-0",
"to": "/.netlify/functions/___netlify-api-handler",
},
Object {
"from": "/api/exitPreview",
"status": 200,
"to": "/.netlify/functions/api-0",
"to": "/.netlify/functions/___netlify-api-handler",
},
Object {
"from": "/api/hello",
"status": 200,
"to": "/.netlify/functions/api-0",
"to": "/.netlify/functions/___netlify-api-handler",
},
Object {
"from": "/api/hello-background",
Expand All @@ -2134,17 +2134,17 @@ Array [
Object {
"from": "/api/revalidate",
"status": 200,
"to": "/.netlify/functions/api-0",
"to": "/.netlify/functions/___netlify-api-handler",
},
Object {
"from": "/api/shows/:id",
"status": 200,
"to": "/.netlify/functions/api-0",
"to": "/.netlify/functions/___netlify-api-handler",
},
Object {
"from": "/api/shows/:params/*",
"status": 200,
"to": "/.netlify/functions/api-0",
"to": "/.netlify/functions/___netlify-api-handler",
},
Object {
"force": false,
Expand Down
13 changes: 12 additions & 1 deletion test/helpers/functions.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,13 @@ import { describeCwdTmpDir, moveNextDist } from '../test-utils'
describeCwdTmpDir('api route file analysis', () => {
it('extracts correct route configs from source files', async () => {
await moveNextDist()
const configs = await getApiRouteConfigs('.next', process.cwd(), ['js', 'jsx', 'ts', 'tsx'])
const configs = await getApiRouteConfigs('.next', process.cwd())
// Using a Set means the order doesn't matter
expect(new Set(configs.map(({ includedFiles, ...rest }) => rest))).toEqual(
new Set([
{
functionName: '_api_og-handler',
functionTitle: 'Next.js API handler /api/og',
compiled: 'pages/api/og.js',
config: {
runtime: 'edge',
Expand All @@ -18,48 +19,56 @@ describeCwdTmpDir('api route file analysis', () => {
},
{
functionName: '_api_enterPreview-handler',
functionTitle: 'Next.js API handler /api/enterPreview',
compiled: 'pages/api/enterPreview.js',
config: {},
route: '/api/enterPreview',
},
{
functionName: '_api_exitPreview-handler',
functionTitle: 'Next.js API handler /api/exitPreview',
compiled: 'pages/api/exitPreview.js',
config: {},
route: '/api/exitPreview',
},
{
functionName: '_api_hello-handler',
functionTitle: 'Next.js API handler /api/hello',
compiled: 'pages/api/hello.js',
config: {},
route: '/api/hello',
},
{
functionName: '_api_shows_params-SPLAT-handler',
functionTitle: 'Next.js API handler /api/shows/[...params]',
compiled: 'pages/api/shows/[...params].js',
config: {},
route: '/api/shows/[...params]',
},
{
functionName: '_api_shows_id-PARAM-handler',
functionTitle: 'Next.js API handler /api/shows/[id]',
compiled: 'pages/api/shows/[id].js',
config: {},
route: '/api/shows/[id]',
},
{
functionName: '_api_hello-background-background',
functionTitle: 'Next.js API handler /api/hello-background',
compiled: 'pages/api/hello-background.js',
config: { type: 'experimental-background' },
route: '/api/hello-background',
},
{
functionName: '_api_hello-scheduled-handler',
functionTitle: 'Next.js API handler /api/hello-scheduled',
compiled: 'pages/api/hello-scheduled.js',
config: { schedule: '@hourly', type: 'experimental-scheduled' },
route: '/api/hello-scheduled',
},
{
functionName: '_api_revalidate-handler',
functionTitle: 'Next.js API handler /api/revalidate',
compiled: 'pages/api/revalidate.js',
config: {},
route: '/api/revalidate',
Expand All @@ -76,12 +85,14 @@ describeCwdTmpDir('api route file analysis', () => {
new Set([
{
functionName: '_api_hello-background-background',
functionTitle: 'Next.js API handler /api/hello-background',
compiled: 'pages/api/hello-background.js',
config: { type: 'experimental-background' },
route: '/api/hello-background',
},
{
functionName: '_api_hello-scheduled-handler',
functionTitle: 'Next.js API handler /api/hello-scheduled',
compiled: 'pages/api/hello-scheduled.js',
config: { schedule: '@hourly', type: 'experimental-scheduled' },
route: '/api/hello-scheduled',
Expand Down
17 changes: 17 additions & 0 deletions test/index.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -738,6 +738,23 @@ describe('onBuild()', () => {
expect(netlifyConfig.functions['_api_*'].node_bundler).toEqual('nft')
})

it('provides displayname for split api routes', async () => {
await moveNextDist()
await nextRuntime.onBuild(defaultArgs)

const functionsManifest = await readJson(
path.join('.netlify', 'functions-internal', '___netlify-api-handler', '___netlify-api-handler.json'),
)

expect(functionsManifest).toEqual({
config: {
generator: '@netlify/next-runtime@unknown',
name: 'Next.js API handler',
},
version: 1,
})
})

// eslint-disable-next-line jest/expect-expect
it('works when `relativeAppDir` is undefined', async () => {
await moveNextDist()
Expand Down