Skip to content

Commit

Permalink
Re-introduce Edge API Endpoints (#37481)
Browse files Browse the repository at this point in the history
* Re-introduce Edge API Endpoints

This reverts commit 210fa39, and
re-introduces Edge API endpoints as a possible runtime selection in API
endpoints.

This is done by exporting a `config` object:

```ts
export config = { runtime: 'edge' }
```

Note: `'edge'` will probably change into `'experimental-edge'` to show
that this is experimental and the API might change in the future.

* Support `experimental-edge`, but allow `edge` too

Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
  • Loading branch information
Schniz and kodiakhq[bot] authored Jun 13, 2022
1 parent 0742099 commit b62bb97
Show file tree
Hide file tree
Showing 13 changed files with 291 additions and 40 deletions.
27 changes: 21 additions & 6 deletions packages/next/build/analysis/get-page-static-info.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { parseModule } from './parse-module'
import { promises as fs } from 'fs'
import { tryToParsePath } from '../../lib/try-to-parse-path'
import { isMiddlewareFile } from '../utils'
import * as Log from '../output/log'

interface MiddlewareConfig {
pathMatcher: RegExp
Expand Down Expand Up @@ -38,12 +39,16 @@ export async function getPageStaticInfo(params: {
const { ssg, ssr } = checkExports(swcAST)
const config = tryToExtractExportedConstValue(swcAST, 'config') || {}

const runtime =
config?.runtime === 'edge'
? 'edge'
: ssr || ssg
? config?.runtime || nextConfig.experimental?.runtime
: undefined
let runtime = ['experimental-edge', 'edge'].includes(config?.runtime)
? 'edge'
: ssr || ssg
? config?.runtime || nextConfig.experimental?.runtime
: undefined

if (runtime === 'experimental-edge' || runtime === 'edge') {
warnAboutExperimentalEdgeApiFunctions()
runtime = 'edge'
}

const middlewareConfig =
isMiddlewareFile(params.page!) && getMiddlewareConfig(config)
Expand Down Expand Up @@ -174,3 +179,13 @@ function getMiddlewareRegExpStrings(matcherOrMatchers: unknown): string[] {
return regexes
}
}

function warnAboutExperimentalEdgeApiFunctions() {
if (warnedAboutExperimentalEdgeApiFunctions) {
return
}
Log.warn(`You are using an experimental edge runtime, the API might change.`)
warnedAboutExperimentalEdgeApiFunctions = true
}

let warnedAboutExperimentalEdgeApiFunctions = false
13 changes: 12 additions & 1 deletion packages/next/build/entries.ts
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,15 @@ export function getEdgeServerEntry(opts: {
return `next-middleware-loader?${stringify(loaderParams)}!`
}

if (opts.page.startsWith('/api/')) {
const loaderParams: MiddlewareLoaderOptions = {
absolutePagePath: opts.absolutePagePath,
page: opts.page,
}

return `next-edge-function-loader?${stringify(loaderParams)}!`
}

const loaderParams: MiddlewareSSRLoaderQuery = {
absolute500Path: opts.pages['/500'] || '',
absoluteAppPath: opts.pages['/_app'],
Expand Down Expand Up @@ -421,7 +430,9 @@ export function runDependingOnPageType<T>(params: {
if (isMiddlewareFile(params.page)) {
return [params.onEdgeServer()]
} else if (params.page.match(API_ROUTE)) {
return [params.onServer()]
return params.pageRuntime === 'edge'
? [params.onEdgeServer()]
: [params.onServer()]
} else if (params.page === '/_document') {
return [params.onServer()]
} else if (
Expand Down
6 changes: 1 addition & 5 deletions packages/next/build/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,6 @@ import {
getUnresolvedModuleFromError,
copyTracedFiles,
isReservedPage,
isCustomErrorPage,
isServerComponentPage,
isMiddlewareFile,
} from './utils'
Expand Down Expand Up @@ -1256,10 +1255,7 @@ export default async function build(
isHybridAmp,
ssgPageRoutes,
initialRevalidateSeconds: false,
runtime:
!isReservedPage(page) && !isCustomErrorPage(page)
? pageRuntime
: undefined,
runtime: pageRuntime,
pageDuration: undefined,
ssgPageDurations: undefined,
})
Expand Down
1 change: 1 addition & 0 deletions packages/next/build/webpack-config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1230,6 +1230,7 @@ export default async function getBaseWebpackConfig(
'next-flight-client-entry-loader',
'noop-loader',
'next-middleware-loader',
'next-edge-function-loader',
'next-middleware-ssr-loader',
'next-middleware-wasm-loader',
'next-app-loader',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { webpack5 } from 'next/dist/compiled/webpack/webpack'
export function getModuleBuildInfo(webpackModule: webpack5.Module) {
return webpackModule.buildInfo as {
nextEdgeMiddleware?: EdgeMiddlewareMeta
nextEdgeApiFunction?: EdgeMiddlewareMeta
nextEdgeSSR?: EdgeSSRMeta
nextUsedEnvVars?: Set<string>
nextWasmMiddlewareBinding?: WasmBinding
Expand Down
43 changes: 43 additions & 0 deletions packages/next/build/webpack/loaders/next-edge-function-loader.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { getModuleBuildInfo } from './get-module-build-info'
import { stringifyRequest } from '../stringify-request'

export type EdgeFunctionLoaderOptions = {
absolutePagePath: string
page: string
}

export default function middlewareLoader(this: any) {
const { absolutePagePath, page }: EdgeFunctionLoaderOptions =
this.getOptions()
const stringifiedPagePath = stringifyRequest(this, absolutePagePath)
const buildInfo = getModuleBuildInfo(this._module)
buildInfo.nextEdgeApiFunction = {
page: page || '/',
}

return `
import { adapter } from 'next/dist/server/web/adapter'
// The condition is true when the "process" module is provided
if (process !== global.process) {
// prefer local process but global.process has correct "env"
process.env = global.process.env;
global.process = process;
}
var mod = require(${stringifiedPagePath})
var handler = mod.middleware || mod.default;
if (typeof handler !== 'function') {
throw new Error('The Edge Function "pages${page}" must export a \`default\` function');
}
export default function (opts) {
return adapter({
...opts,
page: ${JSON.stringify(page)},
handler,
})
}
`
}
40 changes: 27 additions & 13 deletions packages/next/build/webpack/plugins/middleware-plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,24 +14,26 @@ import {
NEXT_CLIENT_SSR_ENTRY_SUFFIX,
} from '../../../shared/lib/constants'

interface EdgeFunctionDefinition {
env: string[]
files: string[]
name: string
page: string
regexp: string
wasm?: WasmBinding[]
}

export interface MiddlewareManifest {
version: 1
sortedMiddleware: string[]
clientInfo: [location: string, isSSR: boolean][]
middleware: {
[page: string]: {
env: string[]
files: string[]
name: string
page: string
regexp: string
wasm?: WasmBinding[]
}
}
middleware: { [page: string]: EdgeFunctionDefinition }
functions: { [page: string]: EdgeFunctionDefinition }
}

interface EntryMetadata {
edgeMiddleware?: EdgeMiddlewareMeta
edgeApiFunction?: EdgeMiddlewareMeta
edgeSSR?: EdgeSSRMeta
env: Set<string>
wasmBindings: Set<WasmBinding>
Expand All @@ -42,6 +44,7 @@ const middlewareManifest: MiddlewareManifest = {
sortedMiddleware: [],
clientInfo: [],
middleware: {},
functions: {},
version: 1,
}

Expand Down Expand Up @@ -349,6 +352,8 @@ function getExtractMetadata(params: {
entryMetadata.edgeSSR = buildInfo.nextEdgeSSR
} else if (buildInfo?.nextEdgeMiddleware) {
entryMetadata.edgeMiddleware = buildInfo.nextEdgeMiddleware
} else if (buildInfo?.nextEdgeApiFunction) {
entryMetadata.edgeApiFunction = buildInfo.nextEdgeApiFunction
}

/**
Expand Down Expand Up @@ -425,24 +430,33 @@ function getCreateAssets(params: {

// There should always be metadata for the entrypoint.
const metadata = metadataByEntry.get(entrypoint.name)
const page = metadata?.edgeMiddleware?.page || metadata?.edgeSSR?.page
const page =
metadata?.edgeMiddleware?.page ||
metadata?.edgeSSR?.page ||
metadata?.edgeApiFunction?.page
if (!page) {
continue
}

const { namedRegex } = getNamedMiddlewareRegex(page, {
catchAll: !metadata.edgeSSR,
catchAll: !metadata.edgeSSR && !metadata.edgeApiFunction,
})
const regexp = metadata?.edgeMiddleware?.matcherRegexp || namedRegex

middlewareManifest.middleware[page] = {
const edgeFunctionDefinition: EdgeFunctionDefinition = {
env: Array.from(metadata.env),
files: getEntryFiles(entrypoint.getFiles(), metadata),
name: entrypoint.name,
page: page,
regexp,
wasm: Array.from(metadata.wasmBindings),
}

if (metadata.edgeApiFunction /* || metadata.edgeSSR */) {
middlewareManifest.functions[page] = edgeFunctionDefinition
} else {
middlewareManifest.middleware[page] = edgeFunctionDefinition
}
}

middlewareManifest.sortedMiddleware = getSortedRoutes(
Expand Down
2 changes: 1 addition & 1 deletion packages/next/server/body-streams.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ function requestToBodyStream(request: IncomingMessage): BodyStream {
return transform.readable as unknown as ReadableStream<Uint8Array>
}

function bodyStreamToNodeStream(bodyStream: BodyStream): Readable {
export function bodyStreamToNodeStream(bodyStream: BodyStream): Readable {
const reader = bodyStream.getReader()
return Readable.from(
(async function* () {
Expand Down
4 changes: 3 additions & 1 deletion packages/next/server/dev/next-dev-server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -363,7 +363,9 @@ export default class DevServer extends Server {
onClient: () => {},
onServer: () => {},
onEdgeServer: () => {
routedMiddleware.push(pageName)
if (!pageName.startsWith('/api/')) {
routedMiddleware.push(pageName)
}
ssrMiddleware.add(pageName)
},
})
Expand Down
Loading

0 comments on commit b62bb97

Please sign in to comment.