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

[edge] serialize custom config to middleware-manifest #40881

Merged
merged 11 commits into from Sep 27, 2022
22 changes: 21 additions & 1 deletion packages/next/build/analysis/get-page-static-info.ts
Expand Up @@ -31,6 +31,7 @@ export interface PageStaticInfo {
ssg?: boolean
ssr?: boolean
rsc?: RSCModuleType
userConfig: Record<string, unknown> | undefined
middleware?: Partial<MiddlewareConfig>
}

Expand Down Expand Up @@ -239,6 +240,23 @@ function warnAboutUnsupportedValue(
warnedUnsupportedValueMap.set(pageFilePath, true)
}

function getUserConfig(
config: any,
middlewareConfig: Partial<MiddlewareConfig>
) {
if (!config) return

const userConfig = config && { ...config }
if (userConfig) {
delete userConfig.runtime
if (middlewareConfig.matchers) {
delete userConfig.matcher
Copy link
Contributor

Choose a reason for hiding this comment

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

suggestion: how about throwing an error like you can't specify both "matcher" and "matchers" instead of discarding matcher here silently?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is not a validation, I just don’t want to serialize the matcher again (it already exists in the middleware manifest)

}
}
Schniz marked this conversation as resolved.
Show resolved Hide resolved

return userConfig
}

/**
* For a given pageFilePath and nextConfig, if the config supports it, this
* function will read the file and return the runtime that should be used.
Expand All @@ -256,7 +274,7 @@ export async function getPageStaticInfo(params: {

const fileContent = (await tryToReadFile(pageFilePath, !isDev)) || ''
if (
/runtime|getStaticProps|getServerSideProps|matcher|unstable_allowDynamic/.test(
/runtime|getStaticProps|getServerSideProps|matcher|unstable_allowDynamic|export (let|var|const) config/.test(
Schniz marked this conversation as resolved.
Show resolved Hide resolved
fileContent
)
) {
Expand Down Expand Up @@ -315,6 +333,7 @@ export async function getPageStaticInfo(params: {
ssr,
ssg,
rsc,
userConfig: getUserConfig(config, middlewareConfig),
...(middlewareConfig && { middleware: middlewareConfig }),
...(runtime && { runtime }),
}
Expand All @@ -325,5 +344,6 @@ export async function getPageStaticInfo(params: {
ssg: false,
rsc: RSC_MODULE_TYPES.server,
runtime: nextConfig.experimental?.runtime,
userConfig: undefined,
}
}
6 changes: 6 additions & 0 deletions packages/next/build/entries.ts
Expand Up @@ -166,6 +166,8 @@ export function getEdgeServerEntry(opts: {
middleware?: Partial<MiddlewareConfig>
pagesType?: 'app' | 'pages' | 'root'
appDirLoader?: string
// TODO(schniz): mark as optional
Schniz marked this conversation as resolved.
Show resolved Hide resolved
userConfig: Record<string, unknown> | undefined
}) {
if (isMiddlewareFile(opts.page)) {
const loaderParams: MiddlewareLoaderOptions = {
Expand All @@ -175,6 +177,7 @@ export function getEdgeServerEntry(opts: {
matchers: opts.middleware?.matchers
? encodeMatchers(opts.middleware.matchers)
: '',
...(opts.userConfig && { userConfig: JSON.stringify(opts.userConfig) }),
}

return `next-middleware-loader?${stringify(loaderParams)}!`
Expand All @@ -185,6 +188,7 @@ export function getEdgeServerEntry(opts: {
absolutePagePath: opts.absolutePagePath,
page: opts.page,
rootDir: opts.rootDir,
...(opts.userConfig && { userConfig: JSON.stringify(opts.userConfig) }),
}

return `next-edge-function-loader?${stringify(loaderParams)}!`
Expand All @@ -205,6 +209,7 @@ export function getEdgeServerEntry(opts: {
appDirLoader: Buffer.from(opts.appDirLoader || '').toString('base64'),
sriEnabled: !opts.isDev && !!opts.config.experimental.sri?.algorithm,
hasFontLoaders: !!opts.config.experimental.fontLoaders,
...(opts.userConfig && { userConfig: JSON.stringify(opts.userConfig) }),
}

return {
Expand Down Expand Up @@ -493,6 +498,7 @@ export async function createEntrypoints(params: CreateEntrypointsParams) {
middleware: staticInfo?.middleware,
pagesType,
appDirLoader,
userConfig: staticInfo?.userConfig,
Copy link
Member

Choose a reason for hiding this comment

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

For consistency I think we may want to have this be config and only add expected config fields which are defined in our type definition.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I kept the entire config so we will be able to serialize what we want in MiddlewarePlugin, but changed it to serialize only regions. Does that make sense? :pray_parrot:

})
},
})
Expand Down
Expand Up @@ -14,6 +14,7 @@ export function getModuleBuildInfo(webpackModule: webpack.Module) {
nextEdgeApiFunction?: EdgeMiddlewareMeta
nextEdgeSSR?: EdgeSSRMeta
nextUsedEnvVars?: Set<string>
nextUserConfig?: Record<string, unknown> | undefined
nextWasmMiddlewareBinding?: AssetBinding
nextAssetMiddlewareBinding?: AssetBinding
usingIndirectEval?: boolean | Set<string>
Expand Down
10 changes: 8 additions & 2 deletions packages/next/build/webpack/loaders/next-edge-function-loader.ts
Expand Up @@ -5,13 +5,19 @@ export type EdgeFunctionLoaderOptions = {
absolutePagePath: string
page: string
rootDir: string
userConfig?: string
}

export default function middlewareLoader(this: any) {
const { absolutePagePath, page, rootDir }: EdgeFunctionLoaderOptions =
this.getOptions()
const {
absolutePagePath,
page,
rootDir,
userConfig,
}: EdgeFunctionLoaderOptions = this.getOptions()
const stringifiedPagePath = stringifyRequest(this, absolutePagePath)
const buildInfo = getModuleBuildInfo(this._module)
buildInfo.nextUserConfig = userConfig ? JSON.parse(userConfig) : undefined
buildInfo.nextEdgeApiFunction = {
page: page || '/',
}
Expand Down
Expand Up @@ -16,6 +16,7 @@ export type EdgeSSRLoaderQuery = {
pagesType?: 'app' | 'pages' | 'root'
sriEnabled: boolean
hasFontLoaders: boolean
userConfig?: string
}

export default async function edgeSSRLoader(this: any) {
Expand All @@ -34,6 +35,7 @@ export default async function edgeSSRLoader(this: any) {
pagesType,
sriEnabled,
hasFontLoaders,
userConfig,
} = this.getOptions()

const appDirLoader = Buffer.from(
Expand All @@ -48,6 +50,7 @@ export default async function edgeSSRLoader(this: any) {
page: page,
isAppDir,
}
buildInfo.nextUserConfig = userConfig ? JSON.parse(userConfig) : undefined
buildInfo.route = {
page,
absolutePagePath,
Expand Down
3 changes: 3 additions & 0 deletions packages/next/build/webpack/loaders/next-middleware-loader.ts
Expand Up @@ -8,6 +8,7 @@ export type MiddlewareLoaderOptions = {
page: string
rootDir: string
matchers?: string
userConfig?: string
}

// matchers can have special characters that break the loader params
Expand All @@ -28,10 +29,12 @@ export default function middlewareLoader(this: any) {
page,
rootDir,
matchers: encodedMatchers,
userConfig,
}: MiddlewareLoaderOptions = this.getOptions()
const matchers = encodedMatchers ? decodeMatchers(encodedMatchers) : undefined
const stringifiedPagePath = stringifyRequest(this, absolutePagePath)
const buildInfo = getModuleBuildInfo(this._module)
buildInfo.nextUserConfig = userConfig ? JSON.parse(userConfig) : undefined
buildInfo.nextEdgeMiddleware = {
matchers,
page:
Expand Down
10 changes: 10 additions & 0 deletions packages/next/build/webpack/plugins/middleware-plugin.ts
Expand Up @@ -36,6 +36,8 @@ export interface EdgeFunctionDefinition {
matchers: MiddlewareMatcher[]
wasm?: AssetBinding[]
assets?: AssetBinding[]
// TODO(schniz): mark as optional
userConfig: Record<string, unknown> | undefined
}

export interface MiddlewareManifest {
Expand All @@ -49,6 +51,8 @@ interface EntryMetadata {
edgeMiddleware?: EdgeMiddlewareMeta
edgeApiFunction?: EdgeMiddlewareMeta
edgeSSR?: EdgeSSRMeta
// TODO(schniz): mark as optional
userConfig: Record<string, unknown> | undefined
env: Set<string>
wasmBindings: Map<string, string>
assetBindings: Map<string, string>
Expand Down Expand Up @@ -170,6 +174,7 @@ function getCreateAssets(params: {
name: entrypoint.name,
page: page,
matchers,
userConfig: metadata.userConfig,
wasm: Array.from(metadata.wasmBindings, ([name, filePath]) => ({
name,
filePath,
Expand Down Expand Up @@ -675,6 +680,7 @@ function getExtractMetadata(params: {
env: new Set<string>(),
wasmBindings: new Map(),
assetBindings: new Map(),
userConfig: undefined,
}

for (const module of modules) {
Expand Down Expand Up @@ -759,6 +765,10 @@ function getExtractMetadata(params: {
}
}

if (buildInfo?.nextUserConfig) {
entryMetadata.userConfig = buildInfo.nextUserConfig
}

/**
* If the module is a WASM module we read the binding information and
* append it to the entry wasm bindings.
Expand Down
4 changes: 3 additions & 1 deletion packages/next/server/dev/hot-reloader.ts
Expand Up @@ -46,6 +46,7 @@ import { getProperError } from '../../lib/is-error'
import ws from 'next/dist/compiled/ws'
import { promises as fs } from 'fs'
import { getPageStaticInfo } from '../../build/analysis/get-page-static-info'
import type { PageStaticInfo } from '../../build/analysis/get-page-static-info'
import { UnwrapPromise } from '../../lib/coalesced-function'

function diff(a: Set<any>, b: Set<any>) {
Expand Down Expand Up @@ -596,7 +597,7 @@ export default class HotReloader {
}

const isAppPath = !!this.appDir && bundlePath.startsWith('app/')
const staticInfo = isEntry
const staticInfo: Partial<PageStaticInfo> = isEntry
? await getPageStaticInfo({
pageFilePath: entryData.absolutePagePath,
nextConfig: this.config,
Expand Down Expand Up @@ -644,6 +645,7 @@ export default class HotReloader {
pages: this.pagesMapping,
isServerComponent,
appDirLoader,
userConfig: undefined,
pagesType: isAppPath ? 'app' : undefined,
}),
appDir: this.config.experimental.appDir,
Expand Down
2 changes: 2 additions & 0 deletions test/e2e/middleware-general/app/middleware.js
Expand Up @@ -2,6 +2,8 @@
import { NextRequest, NextResponse, URLPattern } from 'next/server'
import magicValue from 'shared-package'

export const config = { customConfig: true }

const PATTERNS = [
[
new URLPattern({ pathname: '/:locale/:id' }),
Expand Down
@@ -1,6 +1,6 @@
import { NextResponse } from 'next/server'

export const config = { runtime: 'experimental-edge' }
export const config = { runtime: 'experimental-edge', custom: 'config' }

/**
* @param {import('next/server').NextRequest}
Expand Down
14 changes: 14 additions & 0 deletions test/e2e/middleware-general/test/index.test.ts
Expand Up @@ -125,10 +125,24 @@ describe('Middleware Runtime', () => {
matchers: [{ regexp: '^/.*$' }],
wasm: [],
assets: [],
userConfig: {
customConfig: true,
},
},
})
})

it('should have the custom config in the manifest', async () => {
const manifest = await fs.readJSON(
join(next.testDir, '.next/server/middleware-manifest.json')
)

expect(manifest.functions['/api/edge-search-params']).toHaveProperty(
'userConfig',
{ custom: 'config' }
)
})

it('should have correct files in manifest', async () => {
const manifest = await fs.readJSON(
join(next.testDir, '.next/server/middleware-manifest.json')
Expand Down