Skip to content
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

Re-introduce Edge API Endpoints #37481

Merged
merged 7 commits into from
Jun 13, 2022
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
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)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Supporting both 'experimental-edge' and 'edge', to keep compatibility with the RSC tests. WDYT @shuding @cramforce

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Works for me. I mostly care about what we document.

? '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