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

feat(next): Support has match and locale option on middleware config #39257

Merged
merged 53 commits into from Aug 31, 2022
Merged
Show file tree
Hide file tree
Changes from 45 commits
Commits
Show all changes
53 commits
Select commit Hold shift + click to select a range
5edac48
support has match on middleware config
nkzawa Aug 2, 2022
75475a6
fix tests, revert getNamedMiddlewareRegex
nkzawa Aug 2, 2022
3a8d6d7
fix more tests
nkzawa Aug 2, 2022
e27e7f5
Merge branch 'canary' into middleware-has-match
nkzawa Aug 3, 2022
2a44308
fix for lint
nkzawa Aug 3, 2022
6ed85dc
fix a test
nkzawa Aug 3, 2022
a314fe0
increment middleware-manifest version
nkzawa Aug 3, 2022
bbe1426
Merge branch 'canary' into middleware-has-match
nkzawa Aug 4, 2022
234b5f1
Merge branch 'canary' into middleware-has-match
nkzawa Aug 4, 2022
db20496
Merge branch 'canary' into middleware-has-match
nkzawa Aug 8, 2022
cc3b703
Merge branch 'canary' into middleware-has-match
ijjk Aug 9, 2022
385b8b4
fix import
ijjk Aug 9, 2022
fe6cbe4
Migrate integration test to e2e
ijjk Aug 9, 2022
c76a2ea
improve matchesMiddleware, fix tests to check reload
nkzawa Aug 9, 2022
f7dbee4
Merge branch 'canary' into middleware-has-match
nkzawa Aug 10, 2022
2d8c33c
fix type
nkzawa Aug 10, 2022
75d332f
Merge branch 'canary' into middleware-has-match
nkzawa Aug 10, 2022
2d0c608
add support of locale and basePath options on middleware matcher
nkzawa Aug 9, 2022
f7bf097
Update test/e2e/middleware-custom-matchers-basepath/test/index.test.ts
nkzawa Aug 11, 2022
505096c
Update test/e2e/middleware-custom-matchers-basepath/test/index.test.ts
nkzawa Aug 11, 2022
f76c4ad
Merge branch 'canary' into middleware-has-match
nkzawa Aug 11, 2022
6aa12cb
fix the issue middleware is not called with basePath (#39428)
nkzawa Aug 11, 2022
5004a5a
Merge branch 'canary' into middleware-has-match
nkzawa Aug 12, 2022
9073ae3
improve type check
nkzawa Aug 12, 2022
0f211fa
invalidate routes after modifying as well
nkzawa Aug 12, 2022
7474ed2
use it.each
nkzawa Aug 12, 2022
f2c1cf1
Merge branch 'canary' into middleware-has-match
nkzawa Aug 15, 2022
1e4b122
revert edge-function routes
nkzawa Aug 15, 2022
53cb1fa
Merge branch 'canary' into middleware-has-match
nkzawa Aug 16, 2022
02cf24c
fix broken conflict fix
nkzawa Aug 16, 2022
cae0e22
Merge branch 'canary' into middleware-has-match
nkzawa Aug 17, 2022
19336f8
Merge branch 'canary' into middleware-has-match
nkzawa Aug 18, 2022
45d3a91
Merge branch 'canary' into middleware-has-match
nkzawa Aug 19, 2022
9306bd6
Merge branch 'canary' into middleware-has-match
nkzawa Aug 22, 2022
bf0e49c
fix basepath middleware test rewrite
nkzawa Aug 22, 2022
8b5ac93
fix middleware test rewrite url
nkzawa Aug 22, 2022
76c64e8
Merge branch 'canary' into middleware-has-match
nkzawa Aug 23, 2022
4dede19
disable tests that does not work on deploy
nkzawa Aug 23, 2022
b37e362
fix data path regex, disable some tests on deploy mode
nkzawa Aug 24, 2022
07efbce
disable more tests on deploy mode
nkzawa Aug 24, 2022
5158250
remove the basePath option from middleware matcher
nkzawa Aug 24, 2022
4039594
Merge branch 'canary' into middleware-has-match
nkzawa Aug 25, 2022
88c6362
add a test for _next/data
nkzawa Aug 25, 2022
e4b206c
fix for lint
nkzawa Aug 25, 2022
ee907c1
fix format
nkzawa Aug 25, 2022
5b2f6fd
Merge branch 'canary' into middleware-has-match
nkzawa Aug 26, 2022
315d8f9
Merge branch 'canary' into middleware-has-match
ijjk Aug 26, 2022
05cc1b9
Merge branch 'canary' into middleware-has-match
nkzawa Aug 29, 2022
d425bf9
Merge branch 'canary' into middleware-has-match
nkzawa Aug 30, 2022
0e37d56
Merge branch 'canary' into middleware-has-match
nkzawa Aug 31, 2022
f1942c1
Merge branch 'canary' into middleware-has-match
nkzawa Aug 31, 2022
cac69d7
separate getMiddlewareRouteMatcher
nkzawa Aug 31, 2022
14711cf
Merge branch 'canary' into middleware-has-match
ijjk Aug 31, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
104 changes: 56 additions & 48 deletions packages/next/build/analysis/get-page-static-info.ts
@@ -1,5 +1,6 @@
import { isServerRuntime } from '../../server/config-shared'
import type { NextConfig } from '../../server/config-shared'
import type { Middleware, RouteHas } from '../../lib/load-custom-routes'
import {
extractExportedConstValue,
UnsupportedValueError,
Expand All @@ -9,10 +10,17 @@ import { promises as fs } from 'fs'
import { tryToParsePath } from '../../lib/try-to-parse-path'
import * as Log from '../output/log'
import { SERVER_RUNTIME } from '../../lib/constants'
import { ServerRuntime } from '../../types'
import { ServerRuntime } from 'next/types'
import { checkCustomRoutes } from '../../lib/load-custom-routes'

interface MiddlewareConfig {
pathMatcher: RegExp
export interface MiddlewareConfig {
matchers: MiddlewareMatcher[]
}

export interface MiddlewareMatcher {
regexp: string
locale?: false
has?: RouteHas[]
}
ijjk marked this conversation as resolved.
Show resolved Hide resolved

export interface PageStaticInfo {
Expand Down Expand Up @@ -81,55 +89,63 @@ async function tryToReadFile(filePath: string, shouldThrow: boolean) {
}
}

function getMiddlewareRegExpStrings(
function getMiddlewareMatchers(
matcherOrMatchers: unknown,
nextConfig: NextConfig
): string[] {
): MiddlewareMatcher[] {
let matchers: unknown[] = []
if (Array.isArray(matcherOrMatchers)) {
return matcherOrMatchers.flatMap((matcher) =>
getMiddlewareRegExpStrings(matcher, nextConfig)
)
matchers = matcherOrMatchers
} else {
matchers.push(matcherOrMatchers)
}
const { i18n } = nextConfig

if (typeof matcherOrMatchers !== 'string') {
throw new Error(
'`matcher` must be a path matcher or an array of path matchers'
)
}
let routes = matchers.map(
(m) => (typeof m === 'string' ? { source: m } : m) as Middleware
)

let matcher: string = matcherOrMatchers
// check before we process the routes and after to ensure
// they are still valid
checkCustomRoutes(routes, 'middleware')
nkzawa marked this conversation as resolved.
Show resolved Hide resolved

if (!matcher.startsWith('/')) {
throw new Error('`matcher`: path matcher must start with /')
}
const isRoot = matcher === '/'
routes = routes.map((r) => {
let { source } = r

if (i18n?.locales) {
matcher = `/:nextInternalLocale([^/.]{1,})${isRoot ? '' : matcher}`
}
const isRoot = source === '/'

matcher = `/:nextData(_next/data/[^/]{1,})?${matcher}${
isRoot
? `(${nextConfig.i18n ? '|\\.json|' : ''}/?index|/?index\\.json)?`
: '(.json)?'
}`
if (i18n?.locales && r.locale !== false) {
source = `/:nextInternalLocale([^/.]{1,})${isRoot ? '' : source}`
}

if (nextConfig.basePath) {
matcher = `${nextConfig.basePath}${matcher}`
}
const parsedPage = tryToParsePath(matcher)
source = `/:nextData(_next/data/[^/]{1,})?${source}${
isRoot
? `(${nextConfig.i18n ? '|\\.json|' : ''}/?index|/?index\\.json)?`
: '(.json)?'
}`

if (parsedPage.error) {
throw new Error(`Invalid path matcher: ${matcher}`)
}
if (nextConfig.basePath) {
source = `${nextConfig.basePath}${source}`
}

const regexes = [parsedPage.regexStr].filter((x): x is string => !!x)
nkzawa marked this conversation as resolved.
Show resolved Hide resolved
if (regexes.length < 1) {
throw new Error("Can't parse matcher")
} else {
return regexes
}
return { ...r, source }
})

checkCustomRoutes(routes, 'middleware')

return routes.map((r) => {
const { source, ...rest } = r
const parsedPage = tryToParsePath(source)
nkzawa marked this conversation as resolved.
Show resolved Hide resolved

if (parsedPage.error || !parsedPage.regexStr) {
throw new Error(`Invalid source: ${source}`)
}
ijjk marked this conversation as resolved.
Show resolved Hide resolved

return {
...rest,
regexp: parsedPage.regexStr,
}
})
}

function getMiddlewareConfig(
Expand All @@ -139,15 +155,7 @@ function getMiddlewareConfig(
const result: Partial<MiddlewareConfig> = {}

if (config.matcher) {
result.pathMatcher = new RegExp(
getMiddlewareRegExpStrings(config.matcher, nextConfig).join('|')
)

if (result.pathMatcher.source.length > 4096) {
throw new Error(
`generated matcher config must be less than 4096 characters.`
)
}
result.matchers = getMiddlewareMatchers(config.matcher, nextConfig)
}

return result
Expand Down
24 changes: 14 additions & 10 deletions packages/next/build/entries.ts
Expand Up @@ -4,6 +4,10 @@ import type { EdgeSSRLoaderQuery } from './webpack/loaders/next-edge-ssr-loader'
import type { NextConfigComplete } from '../server/config-shared'
import type { ServerlessLoaderQuery } from './webpack/loaders/next-serverless-loader'
import type { webpack } from 'next/dist/compiled/webpack/webpack'
import type {
MiddlewareConfig,
MiddlewareMatcher,
} from './analysis/get-page-static-info'
import type { LoadedEnvFiles } from '@next/env'
import chalk from 'next/dist/compiled/chalk'
import { posix, join } from 'path'
Expand Down Expand Up @@ -42,6 +46,7 @@ import { normalizePathSep } from '../shared/lib/page-path/normalize-path-sep'
import { normalizePagePath } from '../shared/lib/page-path/normalize-page-path'
import { serverComponentRegex } from './webpack/loaders/utils'
import { ServerRuntime } from '../types'
import { encodeMatchers } from './webpack/loaders/next-middleware-loader'

type ObjectValue<T> = T extends { [key: string]: infer V } ? V : never

Expand Down Expand Up @@ -163,20 +168,17 @@ export function getEdgeServerEntry(opts: {
isServerComponent: boolean
page: string
pages: { [page: string]: string }
middleware?: { pathMatcher?: RegExp }
middleware?: Partial<MiddlewareConfig>
pagesType?: 'app' | 'pages' | 'root'
appDirLoader?: string
}) {
if (isMiddlewareFile(opts.page)) {
const loaderParams: MiddlewareLoaderOptions = {
absolutePagePath: opts.absolutePagePath,
page: opts.page,
// pathMatcher can have special characters that break the loader params
// parsing so we base64 encode/decode the string
matcherRegexp: Buffer.from(
(opts.middleware?.pathMatcher && opts.middleware.pathMatcher.source) ||
''
).toString('base64'),
matchers: opts.middleware?.matchers
? encodeMatchers(opts.middleware.matchers)
nkzawa marked this conversation as resolved.
Show resolved Hide resolved
: '',
}

return `next-middleware-loader?${stringify(loaderParams)}!`
Expand Down Expand Up @@ -347,7 +349,7 @@ export async function createEntrypoints(params: CreateEntrypointsParams) {
const server: webpack.EntryObject = {}
const client: webpack.EntryObject = {}
const nestedMiddleware: string[] = []
let middlewareRegex: string | undefined = undefined
let middlewareMatchers: MiddlewareMatcher[] | undefined = undefined

const getEntryHandler =
(mappings: Record<string, string>, pagesType: 'app' | 'pages' | 'root') =>
Expand Down Expand Up @@ -402,7 +404,9 @@ export async function createEntrypoints(params: CreateEntrypointsParams) {
})

if (isMiddlewareFile(page)) {
middlewareRegex = staticInfo.middleware?.pathMatcher?.source || '.*'
middlewareMatchers = staticInfo.middleware?.matchers ?? [
{ regexp: '.*' },
]

if (target === 'serverless') {
throw new MiddlewareInServerlessTargetError()
Expand Down Expand Up @@ -493,7 +497,7 @@ export async function createEntrypoints(params: CreateEntrypointsParams) {
client,
server,
edgeServer,
middlewareRegex,
middlewareMatchers,
}
}

Expand Down
2 changes: 1 addition & 1 deletion packages/next/build/index.ts
Expand Up @@ -849,7 +849,7 @@ export default async function build(
runWebpackSpan,
target,
appDir,
middlewareRegex: entrypoints.middlewareRegex,
middlewareMatchers: entrypoints.middlewareMatchers,
}

const configs = await runWebpackSpan
Expand Down
15 changes: 8 additions & 7 deletions packages/next/build/webpack-config.ts
Expand Up @@ -54,6 +54,7 @@ import type {
SWC_TARGET_TRIPLE,
} from './webpack/plugins/telemetry-plugin'
import type { Span } from '../trace'
import type { MiddlewareMatcher } from './analysis/get-page-static-info'
import { withoutRSCExtensions } from './utils'
import browserslist from 'next/dist/compiled/browserslist'
import loadJsConfig from './load-jsconfig'
Expand Down Expand Up @@ -90,7 +91,7 @@ export function getDefineEnv({
hasReactRoot,
isNodeServer,
isEdgeServer,
middlewareRegex,
middlewareMatchers,
hasServerComponents,
}: {
dev?: boolean
Expand All @@ -100,7 +101,7 @@ export function getDefineEnv({
hasReactRoot?: boolean
isNodeServer?: boolean
isEdgeServer?: boolean
middlewareRegex?: string
middlewareMatchers?: MiddlewareMatcher[]
config: NextConfigComplete
hasServerComponents?: boolean
}) {
Expand Down Expand Up @@ -144,8 +145,8 @@ export function getDefineEnv({
isEdgeServer ? 'edge' : 'nodejs'
),
}),
'process.env.__NEXT_MIDDLEWARE_REGEX': JSON.stringify(
middlewareRegex || ''
'process.env.__NEXT_MIDDLEWARE_MATCHERS': JSON.stringify(
middlewareMatchers || []
),
'process.env.__NEXT_MANUAL_CLIENT_BASE_PATH': JSON.stringify(
config.experimental.manualClientBasePath
Expand Down Expand Up @@ -510,7 +511,7 @@ export default async function getBaseWebpackConfig(
runWebpackSpan,
target = COMPILER_NAMES.server,
appDir,
middlewareRegex,
middlewareMatchers,
}: {
buildId: string
config: NextConfigComplete
Expand All @@ -525,7 +526,7 @@ export default async function getBaseWebpackConfig(
runWebpackSpan: Span
target?: string
appDir?: string
middlewareRegex?: string
middlewareMatchers?: MiddlewareMatcher[]
}
): Promise<webpack.Configuration> {
const isClient = compilerType === COMPILER_NAMES.client
Expand Down Expand Up @@ -1660,7 +1661,7 @@ export default async function getBaseWebpackConfig(
hasReactRoot,
isNodeServer,
isEdgeServer,
middlewareRegex,
middlewareMatchers,
hasServerComponents,
})
),
Expand Down
@@ -1,3 +1,4 @@
import type { MiddlewareMatcher } from '../../analysis/get-page-static-info'
import { webpack } from 'next/dist/compiled/webpack/webpack'

/**
Expand Down Expand Up @@ -25,7 +26,7 @@ export interface RouteMeta {

export interface EdgeMiddlewareMeta {
page: string
matcherRegexp?: string
matchers?: MiddlewareMatcher[]
}

export interface EdgeSSRMeta {
Expand Down
24 changes: 17 additions & 7 deletions packages/next/build/webpack/loaders/next-middleware-loader.ts
@@ -1,27 +1,37 @@
import type { MiddlewareMatcher } from '../../analysis/get-page-static-info'
import { getModuleBuildInfo } from './get-module-build-info'
import { stringifyRequest } from '../stringify-request'
import { MIDDLEWARE_LOCATION_REGEXP } from '../../../lib/constants'

export type MiddlewareLoaderOptions = {
absolutePagePath: string
page: string
matcherRegexp?: string
matchers?: string
}

// matchers can have special characters that break the loader params
// parsing so we base64 encode/decode the string
export function encodeMatchers(matchers: MiddlewareMatcher[]) {
return Buffer.from(JSON.stringify(matchers)).toString('base64')
}

export function decodeMatchers(encodedMatchers: string) {
return JSON.parse(
Buffer.from(encodedMatchers, 'base64').toString()
) as MiddlewareMatcher[]
}

export default function middlewareLoader(this: any) {
const {
absolutePagePath,
page,
matcherRegexp: base64MatcherRegex,
matchers: encodedMatchers,
}: MiddlewareLoaderOptions = this.getOptions()
const matcherRegexp = Buffer.from(
base64MatcherRegex || '',
'base64'
).toString()
const matchers = encodedMatchers ? decodeMatchers(encodedMatchers) : undefined
const stringifiedPagePath = stringifyRequest(this, absolutePagePath)
const buildInfo = getModuleBuildInfo(this._module)
buildInfo.nextEdgeMiddleware = {
matcherRegexp,
matchers,
page:
page.replace(new RegExp(`/${MIDDLEWARE_LOCATION_REGEXP}$`), '') || '/',
}
Expand Down