2022-04-27 11:50:29 +02:00
|
|
|
import type { ClientPagesLoaderOptions } from './webpack/loaders/next-client-pages-loader'
|
|
|
|
import type { MiddlewareLoaderOptions } from './webpack/loaders/next-middleware-loader'
|
2022-06-26 23:01:26 +02:00
|
|
|
import type { EdgeSSRLoaderQuery } from './webpack/loaders/next-edge-ssr-loader'
|
2022-05-20 14:24:00 +02:00
|
|
|
import type { NextConfigComplete } from '../server/config-shared'
|
2022-06-27 03:02:24 +02:00
|
|
|
import type { ServerRuntime } from '../server/config-shared'
|
2022-04-27 11:50:29 +02:00
|
|
|
import type { ServerlessLoaderQuery } from './webpack/loaders/next-serverless-loader'
|
2022-03-26 02:19:51 +01:00
|
|
|
import type { webpack5 } from 'next/dist/compiled/webpack/webpack'
|
2022-04-27 11:50:29 +02:00
|
|
|
import type { LoadedEnvFiles } from '@next/env'
|
2021-12-21 16:13:45 +01:00
|
|
|
import chalk from 'next/dist/compiled/chalk'
|
2020-07-09 14:31:06 +02:00
|
|
|
import { posix, join } from 'path'
|
2019-05-29 13:57:26 +02:00
|
|
|
import { stringify } from 'querystring'
|
2022-05-03 12:37:23 +02:00
|
|
|
import {
|
|
|
|
API_ROUTE,
|
|
|
|
DOT_NEXT_ALIAS,
|
|
|
|
PAGES_DIR_ALIAS,
|
2022-05-19 17:46:21 +02:00
|
|
|
ROOT_DIR_ALIAS,
|
2022-05-25 11:46:26 +02:00
|
|
|
APP_DIR_ALIAS,
|
2022-06-27 03:02:24 +02:00
|
|
|
SERVER_RUNTIME,
|
2022-05-03 12:37:23 +02:00
|
|
|
} from '../lib/constants'
|
|
|
|
import {
|
|
|
|
CLIENT_STATIC_FILES_RUNTIME_AMP,
|
|
|
|
CLIENT_STATIC_FILES_RUNTIME_MAIN,
|
|
|
|
CLIENT_STATIC_FILES_RUNTIME_MAIN_ROOT,
|
|
|
|
CLIENT_STATIC_FILES_RUNTIME_REACT_REFRESH,
|
|
|
|
EDGE_RUNTIME_WEBPACK,
|
|
|
|
} from '../shared/lib/constants'
|
2021-06-30 13:44:40 +02:00
|
|
|
import { __ApiPreviewProps } from '../server/api-utils'
|
2022-01-20 22:25:44 +01:00
|
|
|
import { isTargetLikeServerless } from '../server/utils'
|
2019-09-11 20:26:10 +02:00
|
|
|
import { warn } from './output/log'
|
2022-06-08 16:10:05 +02:00
|
|
|
import {
|
|
|
|
isMiddlewareFile,
|
|
|
|
isMiddlewareFilename,
|
|
|
|
isServerComponentPage,
|
2022-06-08 18:51:26 +02:00
|
|
|
NestedMiddlewareError,
|
2022-06-19 15:17:18 +02:00
|
|
|
MiddlewareInServerlessTargetError,
|
2022-06-08 16:10:05 +02:00
|
|
|
} from './utils'
|
2022-05-20 14:24:00 +02:00
|
|
|
import { getPageStaticInfo } from './analysis/get-page-static-info'
|
2022-04-30 13:19:27 +02:00
|
|
|
import { normalizePathSep } from '../shared/lib/page-path/normalize-path-sep'
|
|
|
|
import { normalizePagePath } from '../shared/lib/page-path/normalize-page-path'
|
2022-05-13 19:48:53 +02:00
|
|
|
import { serverComponentRegex } from './webpack/loaders/utils'
|
2019-01-08 23:10:32 +01:00
|
|
|
|
2021-10-20 19:52:11 +02:00
|
|
|
type ObjectValue<T> = T extends { [key: string]: infer V } ? V : never
|
2019-01-08 23:10:32 +01:00
|
|
|
|
2022-04-27 11:50:29 +02:00
|
|
|
/**
|
2022-05-20 20:07:20 +02:00
|
|
|
* For a given page path removes the provided extensions.
|
2022-04-27 11:50:29 +02:00
|
|
|
*/
|
2022-05-05 13:15:32 +02:00
|
|
|
export function getPageFromPath(pagePath: string, pageExtensions: string[]) {
|
2022-05-03 12:37:23 +02:00
|
|
|
let page = normalizePathSep(
|
2022-05-20 20:07:20 +02:00
|
|
|
pagePath.replace(new RegExp(`\\.+(${pageExtensions.join('|')})$`), '')
|
2022-05-03 12:37:23 +02:00
|
|
|
)
|
|
|
|
|
2022-05-05 13:15:32 +02:00
|
|
|
page = page.replace(/\/index$/, '')
|
2022-04-27 11:50:29 +02:00
|
|
|
|
2022-02-06 02:28:42 +01:00
|
|
|
return page === '' ? '/' : page
|
|
|
|
}
|
|
|
|
|
2022-04-27 11:50:29 +02:00
|
|
|
export function createPagesMapping({
|
|
|
|
hasServerComponents,
|
|
|
|
isDev,
|
|
|
|
pageExtensions,
|
|
|
|
pagePaths,
|
2022-05-19 17:46:21 +02:00
|
|
|
pagesType,
|
2022-04-27 11:50:29 +02:00
|
|
|
}: {
|
|
|
|
hasServerComponents: boolean
|
|
|
|
isDev: boolean
|
|
|
|
pageExtensions: string[]
|
|
|
|
pagePaths: string[]
|
2022-05-25 11:46:26 +02:00
|
|
|
pagesType: 'pages' | 'root' | 'app'
|
2022-04-27 11:50:29 +02:00
|
|
|
}): { [page: string]: string } {
|
|
|
|
const previousPages: { [key: string]: string } = {}
|
|
|
|
const pages = pagePaths.reduce<{ [key: string]: string }>(
|
|
|
|
(result, pagePath) => {
|
|
|
|
// Do not process .d.ts files inside the `pages` folder
|
|
|
|
if (pagePath.endsWith('.d.ts') && pageExtensions.includes('ts')) {
|
|
|
|
return result
|
|
|
|
}
|
2022-01-01 18:16:03 +01:00
|
|
|
|
2022-05-05 13:15:32 +02:00
|
|
|
const pageKey = getPageFromPath(pagePath, pageExtensions)
|
2022-02-06 02:28:42 +01:00
|
|
|
|
2022-04-27 11:50:29 +02:00
|
|
|
// Assume that if there's a Client Component, that there is
|
|
|
|
// a matching Server Component that will map to the page.
|
|
|
|
// so we will not process it
|
2022-02-06 02:28:42 +01:00
|
|
|
if (hasServerComponents && /\.client$/.test(pageKey)) {
|
2021-10-26 18:50:56 +02:00
|
|
|
return result
|
|
|
|
}
|
|
|
|
|
2019-09-11 20:26:10 +02:00
|
|
|
if (pageKey in result) {
|
|
|
|
warn(
|
|
|
|
`Duplicate page detected. ${chalk.cyan(
|
|
|
|
join('pages', previousPages[pageKey])
|
|
|
|
)} and ${chalk.cyan(
|
|
|
|
join('pages', pagePath)
|
|
|
|
)} both resolve to ${chalk.cyan(pageKey)}.`
|
|
|
|
)
|
|
|
|
} else {
|
|
|
|
previousPages[pageKey] = pagePath
|
|
|
|
}
|
2022-04-27 11:50:29 +02:00
|
|
|
|
2022-05-19 17:46:21 +02:00
|
|
|
result[pageKey] = normalizePathSep(
|
|
|
|
join(
|
|
|
|
pagesType === 'pages'
|
|
|
|
? PAGES_DIR_ALIAS
|
2022-05-25 11:46:26 +02:00
|
|
|
: pagesType === 'app'
|
|
|
|
? APP_DIR_ALIAS
|
2022-05-19 17:46:21 +02:00
|
|
|
: ROOT_DIR_ALIAS,
|
|
|
|
pagePath
|
|
|
|
)
|
|
|
|
)
|
2019-05-29 13:57:26 +02:00
|
|
|
return result
|
|
|
|
},
|
|
|
|
{}
|
|
|
|
)
|
2019-01-08 23:10:32 +01:00
|
|
|
|
2022-05-19 17:46:21 +02:00
|
|
|
if (pagesType !== 'pages') {
|
2022-05-03 12:37:23 +02:00
|
|
|
return pages
|
|
|
|
}
|
|
|
|
|
2021-10-07 01:46:46 +02:00
|
|
|
if (isDev) {
|
2022-04-27 11:50:29 +02:00
|
|
|
delete pages['/_app']
|
|
|
|
delete pages['/_error']
|
|
|
|
delete pages['/_document']
|
2021-08-19 10:12:12 +02:00
|
|
|
}
|
2019-01-08 23:10:32 +01:00
|
|
|
|
2022-05-19 17:46:21 +02:00
|
|
|
// In development we always alias these to allow Webpack to fallback to
|
|
|
|
// the correct source file so that HMR can work properly when a file is
|
|
|
|
// added or removed.
|
2022-04-27 11:50:29 +02:00
|
|
|
const root = isDev ? PAGES_DIR_ALIAS : 'next/dist/pages'
|
2022-05-19 17:46:21 +02:00
|
|
|
|
2022-04-27 11:50:29 +02:00
|
|
|
return {
|
|
|
|
'/_app': `${root}/_app`,
|
|
|
|
'/_error': `${root}/_error`,
|
|
|
|
'/_document': `${root}/_document`,
|
|
|
|
...pages,
|
|
|
|
}
|
2019-01-08 23:10:32 +01:00
|
|
|
}
|
|
|
|
|
2022-04-27 11:50:29 +02:00
|
|
|
interface CreateEntrypointsParams {
|
|
|
|
buildId: string
|
|
|
|
config: NextConfigComplete
|
|
|
|
envFiles: LoadedEnvFiles
|
2022-04-05 23:51:47 +02:00
|
|
|
isDev?: boolean
|
2022-04-27 11:50:29 +02:00
|
|
|
pages: { [page: string]: string }
|
|
|
|
pagesDir: string
|
|
|
|
previewMode: __ApiPreviewProps
|
2022-05-19 17:46:21 +02:00
|
|
|
rootDir: string
|
|
|
|
rootPaths?: Record<string, string>
|
2022-04-27 11:50:29 +02:00
|
|
|
target: 'server' | 'serverless' | 'experimental-serverless-trace'
|
2022-05-25 11:46:26 +02:00
|
|
|
appDir?: string
|
|
|
|
appPaths?: Record<string, string>
|
2022-05-07 15:37:14 +02:00
|
|
|
pageExtensions: string[]
|
2022-04-27 11:50:29 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
export function getEdgeServerEntry(opts: {
|
|
|
|
absolutePagePath: string
|
|
|
|
buildId: string
|
|
|
|
bundlePath: string
|
|
|
|
config: NextConfigComplete
|
|
|
|
isDev: boolean
|
2022-05-13 19:48:53 +02:00
|
|
|
isServerComponent: boolean
|
2022-04-27 11:50:29 +02:00
|
|
|
page: string
|
|
|
|
pages: { [page: string]: string }
|
2022-06-03 18:35:44 +02:00
|
|
|
middleware?: { pathMatcher?: RegExp }
|
2022-05-13 19:48:53 +02:00
|
|
|
}) {
|
2022-06-08 16:10:05 +02:00
|
|
|
if (isMiddlewareFile(opts.page)) {
|
2022-04-27 11:50:29 +02:00
|
|
|
const loaderParams: MiddlewareLoaderOptions = {
|
|
|
|
absolutePagePath: opts.absolutePagePath,
|
|
|
|
page: opts.page,
|
2022-06-23 16:13:40 +02:00
|
|
|
// 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'),
|
2022-04-27 11:50:29 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
return `next-middleware-loader?${stringify(loaderParams)}!`
|
|
|
|
}
|
|
|
|
|
2022-06-13 20:17:44 +02:00
|
|
|
if (opts.page.startsWith('/api/')) {
|
|
|
|
const loaderParams: MiddlewareLoaderOptions = {
|
|
|
|
absolutePagePath: opts.absolutePagePath,
|
|
|
|
page: opts.page,
|
|
|
|
}
|
|
|
|
|
|
|
|
return `next-edge-function-loader?${stringify(loaderParams)}!`
|
|
|
|
}
|
|
|
|
|
2022-06-26 23:01:26 +02:00
|
|
|
const loaderParams: EdgeSSRLoaderQuery = {
|
2022-04-27 11:50:29 +02:00
|
|
|
absolute500Path: opts.pages['/500'] || '',
|
|
|
|
absoluteAppPath: opts.pages['/_app'],
|
|
|
|
absoluteDocumentPath: opts.pages['/_document'],
|
|
|
|
absoluteErrorPath: opts.pages['/_error'],
|
|
|
|
absolutePagePath: opts.absolutePagePath,
|
|
|
|
buildId: opts.buildId,
|
|
|
|
dev: opts.isDev,
|
2022-05-02 15:07:21 +02:00
|
|
|
isServerComponent: isServerComponentPage(
|
|
|
|
opts.config,
|
|
|
|
opts.absolutePagePath
|
|
|
|
),
|
2022-04-27 11:50:29 +02:00
|
|
|
page: opts.page,
|
|
|
|
stringifiedConfig: JSON.stringify(opts.config),
|
|
|
|
}
|
|
|
|
|
2022-05-13 19:48:53 +02:00
|
|
|
return {
|
2022-06-26 23:01:26 +02:00
|
|
|
import: `next-edge-ssr-loader?${stringify(loaderParams)}!`,
|
2022-05-13 19:48:53 +02:00
|
|
|
layer: opts.isServerComponent ? 'sc_server' : undefined,
|
|
|
|
}
|
2022-04-27 11:50:29 +02:00
|
|
|
}
|
|
|
|
|
2022-05-25 11:46:26 +02:00
|
|
|
export function getAppEntry(opts: {
|
2022-05-13 19:48:53 +02:00
|
|
|
name: string
|
2022-05-07 15:37:14 +02:00
|
|
|
pagePath: string
|
2022-05-25 11:46:26 +02:00
|
|
|
appDir: string
|
2022-05-07 15:37:14 +02:00
|
|
|
pageExtensions: string[]
|
|
|
|
}) {
|
2022-05-16 11:46:45 +02:00
|
|
|
return {
|
2022-05-25 11:46:26 +02:00
|
|
|
import: `next-app-loader?${stringify(opts)}!`,
|
2022-05-16 11:46:45 +02:00
|
|
|
layer: 'sc_server',
|
|
|
|
}
|
2022-05-05 22:42:22 +02:00
|
|
|
}
|
|
|
|
|
2022-04-27 11:50:29 +02:00
|
|
|
export function getServerlessEntry(opts: {
|
|
|
|
absolutePagePath: string
|
|
|
|
buildId: string
|
|
|
|
config: NextConfigComplete
|
|
|
|
envFiles: LoadedEnvFiles
|
|
|
|
page: string
|
|
|
|
previewMode: __ApiPreviewProps
|
|
|
|
pages: { [page: string]: string }
|
|
|
|
}): ObjectValue<webpack5.EntryObject> {
|
|
|
|
const loaderParams: ServerlessLoaderQuery = {
|
|
|
|
absolute404Path: opts.pages['/404'] || '',
|
|
|
|
absoluteAppPath: opts.pages['/_app'],
|
|
|
|
absoluteDocumentPath: opts.pages['/_document'],
|
|
|
|
absoluteErrorPath: opts.pages['/_error'],
|
|
|
|
absolutePagePath: opts.absolutePagePath,
|
|
|
|
assetPrefix: opts.config.assetPrefix,
|
|
|
|
basePath: opts.config.basePath,
|
|
|
|
buildId: opts.buildId,
|
|
|
|
canonicalBase: opts.config.amp.canonicalBase || '',
|
2019-01-08 23:10:32 +01:00
|
|
|
distDir: DOT_NEXT_ALIAS,
|
2022-04-27 11:50:29 +02:00
|
|
|
generateEtags: opts.config.generateEtags ? 'true' : '',
|
|
|
|
i18n: opts.config.i18n ? JSON.stringify(opts.config.i18n) : '',
|
2020-06-30 23:33:37 +02:00
|
|
|
// base64 encode to make sure contents don't break webpack URL loading
|
2022-04-27 11:50:29 +02:00
|
|
|
loadedEnvFiles: Buffer.from(JSON.stringify(opts.envFiles)).toString(
|
2020-06-30 23:33:37 +02:00
|
|
|
'base64'
|
|
|
|
),
|
2022-04-27 11:50:29 +02:00
|
|
|
page: opts.page,
|
|
|
|
poweredByHeader: opts.config.poweredByHeader ? 'true' : '',
|
|
|
|
previewProps: JSON.stringify(opts.previewMode),
|
|
|
|
runtimeConfig:
|
|
|
|
Object.keys(opts.config.publicRuntimeConfig).length > 0 ||
|
|
|
|
Object.keys(opts.config.serverRuntimeConfig).length > 0
|
|
|
|
? JSON.stringify({
|
|
|
|
publicRuntimeConfig: opts.config.publicRuntimeConfig,
|
|
|
|
serverRuntimeConfig: opts.config.serverRuntimeConfig,
|
|
|
|
})
|
|
|
|
: '',
|
|
|
|
}
|
|
|
|
|
|
|
|
return `next-serverless-loader?${stringify(loaderParams)}!`
|
|
|
|
}
|
|
|
|
|
|
|
|
export function getClientEntry(opts: {
|
|
|
|
absolutePagePath: string
|
|
|
|
page: string
|
|
|
|
}) {
|
|
|
|
const loaderOptions: ClientPagesLoaderOptions = {
|
|
|
|
absolutePagePath: opts.absolutePagePath,
|
|
|
|
page: opts.page,
|
2019-01-08 23:10:32 +01:00
|
|
|
}
|
|
|
|
|
2022-04-27 11:50:29 +02:00
|
|
|
const pageLoader = `next-client-pages-loader?${stringify(loaderOptions)}!`
|
|
|
|
|
|
|
|
// Make sure next/router is a dependency of _app or else chunk splitting
|
|
|
|
// might cause the router to not be able to load causing hydration
|
|
|
|
// to fail
|
|
|
|
return opts.page === '/_app'
|
|
|
|
? [pageLoader, require.resolve('../client/router')]
|
|
|
|
: pageLoader
|
|
|
|
}
|
|
|
|
|
|
|
|
export async function createEntrypoints(params: CreateEntrypointsParams) {
|
2022-05-07 15:37:14 +02:00
|
|
|
const {
|
|
|
|
config,
|
|
|
|
pages,
|
|
|
|
pagesDir,
|
|
|
|
isDev,
|
2022-05-19 17:46:21 +02:00
|
|
|
rootDir,
|
|
|
|
rootPaths,
|
2022-05-07 15:37:14 +02:00
|
|
|
target,
|
2022-05-25 11:46:26 +02:00
|
|
|
appDir,
|
|
|
|
appPaths,
|
2022-05-07 15:37:14 +02:00
|
|
|
pageExtensions,
|
|
|
|
} = params
|
2022-04-27 11:50:29 +02:00
|
|
|
const edgeServer: webpack5.EntryObject = {}
|
|
|
|
const server: webpack5.EntryObject = {}
|
|
|
|
const client: webpack5.EntryObject = {}
|
2022-06-08 18:51:26 +02:00
|
|
|
const nestedMiddleware: string[] = []
|
2022-06-14 18:50:05 +02:00
|
|
|
let middlewareRegex: string | undefined = undefined
|
2022-04-27 11:50:29 +02:00
|
|
|
|
2022-05-03 12:37:23 +02:00
|
|
|
const getEntryHandler =
|
2022-05-25 11:46:26 +02:00
|
|
|
(mappings: Record<string, string>, pagesType: 'app' | 'pages' | 'root') =>
|
2022-05-03 12:37:23 +02:00
|
|
|
async (page: string) => {
|
2022-03-08 21:55:14 +01:00
|
|
|
const bundleFile = normalizePagePath(page)
|
2022-07-08 12:08:37 +02:00
|
|
|
const clientBundlePath = posix.join(pagesType, bundleFile)
|
2022-05-19 17:46:21 +02:00
|
|
|
const serverBundlePath =
|
|
|
|
pagesType === 'pages'
|
|
|
|
? posix.join('pages', bundleFile)
|
2022-05-25 11:46:26 +02:00
|
|
|
: pagesType === 'app'
|
|
|
|
? posix.join('app', bundleFile)
|
2022-05-19 17:46:21 +02:00
|
|
|
: bundleFile.slice(1)
|
2022-05-13 19:48:53 +02:00
|
|
|
const absolutePagePath = mappings[page]
|
2022-05-03 12:37:23 +02:00
|
|
|
|
|
|
|
// Handle paths that have aliases
|
|
|
|
const pageFilePath = (() => {
|
|
|
|
if (absolutePagePath.startsWith(PAGES_DIR_ALIAS)) {
|
|
|
|
return absolutePagePath.replace(PAGES_DIR_ALIAS, pagesDir)
|
|
|
|
}
|
|
|
|
|
2022-05-25 11:46:26 +02:00
|
|
|
if (absolutePagePath.startsWith(APP_DIR_ALIAS) && appDir) {
|
|
|
|
return absolutePagePath.replace(APP_DIR_ALIAS, appDir)
|
2022-05-03 12:37:23 +02:00
|
|
|
}
|
|
|
|
|
2022-05-19 17:46:21 +02:00
|
|
|
if (absolutePagePath.startsWith(ROOT_DIR_ALIAS)) {
|
|
|
|
return absolutePagePath.replace(ROOT_DIR_ALIAS, rootDir)
|
|
|
|
}
|
|
|
|
|
2022-05-03 12:37:23 +02:00
|
|
|
return require.resolve(absolutePagePath)
|
|
|
|
})()
|
2019-05-11 15:32:38 +02:00
|
|
|
|
2022-05-19 17:46:21 +02:00
|
|
|
/**
|
|
|
|
* When we find a middleware file that is not in the ROOT_DIR we fail.
|
|
|
|
* There is no need to check on `dev` as this should only happen when
|
|
|
|
* building for production.
|
|
|
|
*/
|
|
|
|
if (
|
|
|
|
!absolutePagePath.startsWith(ROOT_DIR_ALIAS) &&
|
|
|
|
/[\\\\/]_middleware$/.test(page)
|
|
|
|
) {
|
2022-06-08 18:51:26 +02:00
|
|
|
nestedMiddleware.push(page)
|
2022-05-19 17:46:21 +02:00
|
|
|
}
|
|
|
|
|
2022-05-13 19:48:53 +02:00
|
|
|
const isServerComponent = serverComponentRegex.test(absolutePagePath)
|
2022-06-09 16:58:10 +02:00
|
|
|
const isInsideAppDir = appDir && absolutePagePath.startsWith(appDir)
|
2022-05-13 19:48:53 +02:00
|
|
|
|
2022-05-20 14:24:00 +02:00
|
|
|
const staticInfo = await getPageStaticInfo({
|
|
|
|
nextConfig: config,
|
|
|
|
pageFilePath,
|
|
|
|
isDev,
|
2022-06-03 18:35:44 +02:00
|
|
|
page,
|
2022-05-20 14:24:00 +02:00
|
|
|
})
|
|
|
|
|
2022-06-14 18:50:05 +02:00
|
|
|
if (isMiddlewareFile(page)) {
|
|
|
|
middlewareRegex = staticInfo.middleware?.pathMatcher?.source || '.*'
|
2022-06-19 15:17:18 +02:00
|
|
|
|
|
|
|
if (target === 'serverless') {
|
|
|
|
throw new MiddlewareInServerlessTargetError()
|
|
|
|
}
|
2022-06-14 18:50:05 +02:00
|
|
|
}
|
|
|
|
|
2022-04-27 11:50:29 +02:00
|
|
|
runDependingOnPageType({
|
|
|
|
page,
|
2022-05-20 14:24:00 +02:00
|
|
|
pageRuntime: staticInfo.runtime,
|
2022-04-27 11:50:29 +02:00
|
|
|
onClient: () => {
|
2022-06-09 16:58:10 +02:00
|
|
|
if (isServerComponent || isInsideAppDir) {
|
2022-05-13 19:48:53 +02:00
|
|
|
// We skip the initial entries for server component pages and let the
|
|
|
|
// server compiler inject them instead.
|
|
|
|
} else {
|
|
|
|
client[clientBundlePath] = getClientEntry({
|
|
|
|
absolutePagePath: mappings[page],
|
|
|
|
page,
|
|
|
|
})
|
|
|
|
}
|
2022-04-27 11:50:29 +02:00
|
|
|
},
|
|
|
|
onServer: () => {
|
2022-05-25 11:46:26 +02:00
|
|
|
if (pagesType === 'app' && appDir) {
|
|
|
|
server[serverBundlePath] = getAppEntry({
|
2022-05-13 19:48:53 +02:00
|
|
|
name: serverBundlePath,
|
2022-05-05 22:42:22 +02:00
|
|
|
pagePath: mappings[page],
|
2022-05-25 11:46:26 +02:00
|
|
|
appDir,
|
2022-05-07 15:37:14 +02:00
|
|
|
pageExtensions,
|
2022-05-05 22:42:22 +02:00
|
|
|
})
|
|
|
|
} else if (isTargetLikeServerless(target)) {
|
2022-04-27 11:50:29 +02:00
|
|
|
if (page !== '/_app' && page !== '/_document') {
|
|
|
|
server[serverBundlePath] = getServerlessEntry({
|
|
|
|
...params,
|
2022-05-03 12:37:23 +02:00
|
|
|
absolutePagePath: mappings[page],
|
2022-04-27 11:50:29 +02:00
|
|
|
page,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
} else {
|
2022-05-13 19:48:53 +02:00
|
|
|
server[serverBundlePath] = isServerComponent
|
|
|
|
? {
|
|
|
|
import: mappings[page],
|
|
|
|
layer: 'sc_server',
|
|
|
|
}
|
|
|
|
: [mappings[page]]
|
2022-04-27 11:50:29 +02:00
|
|
|
}
|
|
|
|
},
|
|
|
|
onEdgeServer: () => {
|
|
|
|
edgeServer[serverBundlePath] = getEdgeServerEntry({
|
|
|
|
...params,
|
2022-05-03 12:37:23 +02:00
|
|
|
absolutePagePath: mappings[page],
|
2022-04-27 11:50:29 +02:00
|
|
|
bundlePath: clientBundlePath,
|
|
|
|
isDev: false,
|
2022-05-13 19:48:53 +02:00
|
|
|
isServerComponent,
|
2022-04-27 11:50:29 +02:00
|
|
|
page,
|
2022-06-03 18:35:44 +02:00
|
|
|
middleware: staticInfo?.middleware,
|
2022-04-27 11:50:29 +02:00
|
|
|
})
|
|
|
|
},
|
|
|
|
})
|
2022-05-03 12:37:23 +02:00
|
|
|
}
|
|
|
|
|
2022-05-25 11:46:26 +02:00
|
|
|
if (appDir && appPaths) {
|
|
|
|
const entryHandler = getEntryHandler(appPaths, 'app')
|
|
|
|
await Promise.all(Object.keys(appPaths).map(entryHandler))
|
2022-05-03 12:37:23 +02:00
|
|
|
}
|
2022-05-19 17:46:21 +02:00
|
|
|
if (rootPaths) {
|
|
|
|
await Promise.all(
|
|
|
|
Object.keys(rootPaths).map(getEntryHandler(rootPaths, 'root'))
|
|
|
|
)
|
|
|
|
}
|
|
|
|
await Promise.all(Object.keys(pages).map(getEntryHandler(pages, 'pages')))
|
2019-01-08 23:10:32 +01:00
|
|
|
|
2022-06-08 18:51:26 +02:00
|
|
|
if (nestedMiddleware.length > 0) {
|
|
|
|
throw new NestedMiddlewareError(nestedMiddleware, rootDir, pagesDir)
|
|
|
|
}
|
|
|
|
|
2019-01-08 23:10:32 +01:00
|
|
|
return {
|
|
|
|
client,
|
2019-05-29 13:57:26 +02:00
|
|
|
server,
|
2022-02-08 14:16:46 +01:00
|
|
|
edgeServer,
|
2022-06-14 18:50:05 +02:00
|
|
|
middlewareRegex,
|
2019-01-08 23:10:32 +01:00
|
|
|
}
|
|
|
|
}
|
2021-09-17 21:20:09 +02:00
|
|
|
|
2022-04-27 11:50:29 +02:00
|
|
|
export function runDependingOnPageType<T>(params: {
|
|
|
|
onClient: () => T
|
|
|
|
onEdgeServer: () => T
|
|
|
|
onServer: () => T
|
|
|
|
page: string
|
2022-06-27 03:02:24 +02:00
|
|
|
pageRuntime: ServerRuntime
|
2022-04-27 11:50:29 +02:00
|
|
|
}) {
|
2022-06-08 16:10:05 +02:00
|
|
|
if (isMiddlewareFile(params.page)) {
|
2022-06-24 20:50:49 +02:00
|
|
|
return { edgeServer: params.onEdgeServer() }
|
2022-04-27 11:50:29 +02:00
|
|
|
} else if (params.page.match(API_ROUTE)) {
|
2022-06-27 03:02:24 +02:00
|
|
|
return params.pageRuntime === SERVER_RUNTIME.edge
|
2022-06-24 20:50:49 +02:00
|
|
|
? { edgeServer: params.onEdgeServer() }
|
|
|
|
: { server: params.onServer() }
|
2022-04-27 11:50:29 +02:00
|
|
|
} else if (params.page === '/_document') {
|
2022-06-24 20:50:49 +02:00
|
|
|
return { server: params.onServer() }
|
2022-04-27 11:50:29 +02:00
|
|
|
} else if (
|
|
|
|
params.page === '/_app' ||
|
|
|
|
params.page === '/_error' ||
|
|
|
|
params.page === '/404' ||
|
|
|
|
params.page === '/500'
|
|
|
|
) {
|
2022-06-24 20:50:49 +02:00
|
|
|
return { client: params.onClient(), server: params.onServer() }
|
2022-04-27 11:50:29 +02:00
|
|
|
} else {
|
2022-06-27 03:02:24 +02:00
|
|
|
return params.pageRuntime === SERVER_RUNTIME.edge
|
2022-06-24 20:50:49 +02:00
|
|
|
? { client: params.onClient(), edgeServer: params.onEdgeServer() }
|
|
|
|
: { client: params.onClient(), server: params.onServer() }
|
2022-04-27 11:50:29 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-10-20 19:52:11 +02:00
|
|
|
export function finalizeEntrypoint({
|
|
|
|
name,
|
2022-04-27 11:50:29 +02:00
|
|
|
compilerType,
|
2021-10-20 19:52:11 +02:00
|
|
|
value,
|
2022-05-13 19:48:53 +02:00
|
|
|
isServerComponent,
|
2022-06-01 13:52:57 +02:00
|
|
|
appDir,
|
2021-10-20 19:52:11 +02:00
|
|
|
}: {
|
2022-04-27 11:50:29 +02:00
|
|
|
compilerType?: 'client' | 'server' | 'edge-server'
|
2021-10-20 19:52:11 +02:00
|
|
|
name: string
|
|
|
|
value: ObjectValue<webpack5.EntryObject>
|
2022-05-13 19:48:53 +02:00
|
|
|
isServerComponent?: boolean
|
2022-06-01 13:52:57 +02:00
|
|
|
appDir?: boolean
|
2021-10-20 19:52:11 +02:00
|
|
|
}): ObjectValue<webpack5.EntryObject> {
|
|
|
|
const entry =
|
|
|
|
typeof value !== 'object' || Array.isArray(value)
|
|
|
|
? { import: value }
|
|
|
|
: value
|
|
|
|
|
feat: build edge functions with node.js modules and fail at runtime (#38234)
## What's in there?
The Edge runtime [does not support Node.js modules](https://edge-runtime.vercel.app/features/available-apis#unsupported-apis).
When building Next.js application, we currently fail the build when detecting node.js module imported from middleware.
This is an blocker for using code that is conditionally loading node.js modules (based on platform/env detection), as @cramforce reported.
This PR implements a new strategy where:
- we can build such middleware/Edge API route code **with a warning**
- we fail at run time, with graceful errors in dev (console & react-dev-overlay error)
- we fail at run time, with console errors in production
## How to test?
All cases are covered with integration tests.
To try them live, create a simple app with a page, a `middleware.js` file and a `pages/api/route.js`file.
Here are iconic examples:
### node.js modules
```js
// middleware.js
import { NextResponse } from 'next/server'
// static
import { basename } from 'path'
export default async function middleware() {
// dynamic
const { basename } = await import('path')
basename()
return NextResponse.next()
}
export const config = { matcher: '/' }
```
```js
// pags/api/route.js
// static
import { isAbsolute } from 'path'
export default async function handle() {
// dynamic
const { isAbsolute } = await import('path')
return Response.json({ useNodeModule: isAbsolute('/test') })
}
export const config = { runtime: 'experimental-edge' }
```
Desired error (+ source code highlight in dev):
> The edge runtime does not support Node.js 'path' module
Learn More: https://nextjs.org/docs/messages/node-module-in-edge-runtime
Desired warning at build time:
> A Node.js module is loaded ('path' at line 2) which is not supported in the Edge Runtime.
Learn More: https://nextjs.org/docs/messages/node-module-in-edge-runtime
- [x] in dev middleware, static, shows desired error on stderr
- [x] in dev route, static, shows desired error on stderr
- [x] in dev middleware, dynamic, shows desired error on stderr
- [x] in dev route, dynamic, shows desired error on stderr
- [x] in dev middleware, static, shows desired error on react error overlay
- [x] in dev route, static, shows desired error on react error overlay
- [x] in dev middleware, dynamic, shows desired error on react error overlay
- [x] in dev route, dynamic, shows desired error on react error overlay
- [x] builds middleware successfully, shows build warning, shows desired error on stderr on call
- [x] builds route successfully, shows build warning, shows desired error on stderr on call
### 3rd party modules not found
```js
// middleware.js
import { NextResponse } from 'next/server'
// static
import Unknown from 'unknown'
export default async function middleware() {
// dynamic
const Unknown = await import('unknown')
new Unknown()
return NextResponse.next()
}
```
export const config = { matcher: '/' }
```
```js
// pags/api/route.js
// static
import Unknown from 'unknown'
export default async function handle() {
// dynamic
const Unknown = await import('unknown')
return Response.json({ use3rdPartyModule: Unknown() })
}
export const config = { runtime: 'experimental-edge' }
```
Desired error (+ source code highlight in dev):
> Module not found: Can't resolve 'does-not-exist'
Learn More: https://nextjs.org/docs/messages/module-not-found
- [x] in dev middleware, static, shows desired error on stderr
- [x] in dev route, static, shows desired error on stderr
- [x] in dev middleware, dynamic, shows desired error on stderr
- [x] in dev route, dynamic, shows desired error on stderr
- [x] in dev middleware, static, shows desired error on react error overlay
- [x] in dev route, static, shows desired error on react error overlay
- [x] in dev middleware, dynamic, shows desired error on react error overlay
- [x] in dev route, dynamic, shows desired error on react error overlay
- [x] fails to build middleware, with desired error on stderr
- [x] fails to build route, with desired error on stderr
### unused node.js modules
```js
// middleware.js
import { NextResponse } from 'next/server'
export default async function middleware() {
if (process.exit) {
const { basename } = await import('path')
basename()
}
return NextResponse.next()
}
```
```js
// pags/api/route.js
export default async function handle() {
if (process.exit) {
const { basename } = await import('path')
basename()
}
return Response.json({ useNodeModule: false })
}
export const config = { runtime: 'experimental-edge' }
```
Desired warning at build time:
> A Node.js module is loaded ('path' at line 2) which is not supported in the Edge Runtime.
Learn More: https://nextjs.org/docs/messages/node-module-in-edge-runtime
- [x] invoke middleware in dev with no error
- [x] invoke route in dev with no error
- [x] builds successfully, shows build warning, invoke middleware with no error
- [x] builds successfully, shows build warning, invoke api-route with no error
## Notes to reviewers
The strategy to implement this feature is to leverages webpack [externals](https://webpack.js.org/configuration/externals/#externals) and run a global `__unsupported_module()` function when using a node.js module from edge function's code.
For the record, I tried using [webpack resolve.fallback](https://webpack.js.org/configuration/resolve/#resolvefallback) and [Webpack.IgnorePlugin](https://webpack.js.org/plugins/ignore-plugin/) but they do not allow throwing proper errors at runtime that would contain the loaded module name for reporting.
`__unsupported_module()` is defined in `EdgeRuntime`, and returns a proxy that's throw on use (whether it's property access, function call, new operator... synchronous & promise-based styles).
However there's an issue with error reporting: webpack does not includes the import lines in the generated sourcemaps, preventing from displaying useful errors.
I extended our middleware-plugin to supplement the sourcemaps (when analyzing edge function code, it saves which module is imported from which file, together with line/column/source)
The react-dev-overlay was adapted to look for this additional information when the caught error relates to modules, instead of looking at sourcemaps.
I removed the previous mechanism (built by @nkzawa ) which caught webpack errors at built time to change the displayed error message (files `next/build/index.js`, `next/build/utils.ts` and `wellknown-errors-plugin`)
2022-07-06 22:54:44 +02:00
|
|
|
const isApi = name.startsWith('pages/api/')
|
2022-04-27 11:50:29 +02:00
|
|
|
if (compilerType === 'server') {
|
2021-10-20 19:52:11 +02:00
|
|
|
return {
|
|
|
|
publicPath: isApi ? '' : undefined,
|
|
|
|
runtime: isApi ? 'webpack-api-runtime' : 'webpack-runtime',
|
2022-05-13 19:48:53 +02:00
|
|
|
layer: isApi ? 'api' : isServerComponent ? 'sc_server' : undefined,
|
2021-10-20 19:52:11 +02:00
|
|
|
...entry,
|
2021-10-06 17:40:01 +02:00
|
|
|
}
|
2021-10-20 19:52:11 +02:00
|
|
|
}
|
|
|
|
|
2022-04-27 11:50:29 +02:00
|
|
|
if (compilerType === 'edge-server') {
|
|
|
|
return {
|
feat: build edge functions with node.js modules and fail at runtime (#38234)
## What's in there?
The Edge runtime [does not support Node.js modules](https://edge-runtime.vercel.app/features/available-apis#unsupported-apis).
When building Next.js application, we currently fail the build when detecting node.js module imported from middleware.
This is an blocker for using code that is conditionally loading node.js modules (based on platform/env detection), as @cramforce reported.
This PR implements a new strategy where:
- we can build such middleware/Edge API route code **with a warning**
- we fail at run time, with graceful errors in dev (console & react-dev-overlay error)
- we fail at run time, with console errors in production
## How to test?
All cases are covered with integration tests.
To try them live, create a simple app with a page, a `middleware.js` file and a `pages/api/route.js`file.
Here are iconic examples:
### node.js modules
```js
// middleware.js
import { NextResponse } from 'next/server'
// static
import { basename } from 'path'
export default async function middleware() {
// dynamic
const { basename } = await import('path')
basename()
return NextResponse.next()
}
export const config = { matcher: '/' }
```
```js
// pags/api/route.js
// static
import { isAbsolute } from 'path'
export default async function handle() {
// dynamic
const { isAbsolute } = await import('path')
return Response.json({ useNodeModule: isAbsolute('/test') })
}
export const config = { runtime: 'experimental-edge' }
```
Desired error (+ source code highlight in dev):
> The edge runtime does not support Node.js 'path' module
Learn More: https://nextjs.org/docs/messages/node-module-in-edge-runtime
Desired warning at build time:
> A Node.js module is loaded ('path' at line 2) which is not supported in the Edge Runtime.
Learn More: https://nextjs.org/docs/messages/node-module-in-edge-runtime
- [x] in dev middleware, static, shows desired error on stderr
- [x] in dev route, static, shows desired error on stderr
- [x] in dev middleware, dynamic, shows desired error on stderr
- [x] in dev route, dynamic, shows desired error on stderr
- [x] in dev middleware, static, shows desired error on react error overlay
- [x] in dev route, static, shows desired error on react error overlay
- [x] in dev middleware, dynamic, shows desired error on react error overlay
- [x] in dev route, dynamic, shows desired error on react error overlay
- [x] builds middleware successfully, shows build warning, shows desired error on stderr on call
- [x] builds route successfully, shows build warning, shows desired error on stderr on call
### 3rd party modules not found
```js
// middleware.js
import { NextResponse } from 'next/server'
// static
import Unknown from 'unknown'
export default async function middleware() {
// dynamic
const Unknown = await import('unknown')
new Unknown()
return NextResponse.next()
}
```
export const config = { matcher: '/' }
```
```js
// pags/api/route.js
// static
import Unknown from 'unknown'
export default async function handle() {
// dynamic
const Unknown = await import('unknown')
return Response.json({ use3rdPartyModule: Unknown() })
}
export const config = { runtime: 'experimental-edge' }
```
Desired error (+ source code highlight in dev):
> Module not found: Can't resolve 'does-not-exist'
Learn More: https://nextjs.org/docs/messages/module-not-found
- [x] in dev middleware, static, shows desired error on stderr
- [x] in dev route, static, shows desired error on stderr
- [x] in dev middleware, dynamic, shows desired error on stderr
- [x] in dev route, dynamic, shows desired error on stderr
- [x] in dev middleware, static, shows desired error on react error overlay
- [x] in dev route, static, shows desired error on react error overlay
- [x] in dev middleware, dynamic, shows desired error on react error overlay
- [x] in dev route, dynamic, shows desired error on react error overlay
- [x] fails to build middleware, with desired error on stderr
- [x] fails to build route, with desired error on stderr
### unused node.js modules
```js
// middleware.js
import { NextResponse } from 'next/server'
export default async function middleware() {
if (process.exit) {
const { basename } = await import('path')
basename()
}
return NextResponse.next()
}
```
```js
// pags/api/route.js
export default async function handle() {
if (process.exit) {
const { basename } = await import('path')
basename()
}
return Response.json({ useNodeModule: false })
}
export const config = { runtime: 'experimental-edge' }
```
Desired warning at build time:
> A Node.js module is loaded ('path' at line 2) which is not supported in the Edge Runtime.
Learn More: https://nextjs.org/docs/messages/node-module-in-edge-runtime
- [x] invoke middleware in dev with no error
- [x] invoke route in dev with no error
- [x] builds successfully, shows build warning, invoke middleware with no error
- [x] builds successfully, shows build warning, invoke api-route with no error
## Notes to reviewers
The strategy to implement this feature is to leverages webpack [externals](https://webpack.js.org/configuration/externals/#externals) and run a global `__unsupported_module()` function when using a node.js module from edge function's code.
For the record, I tried using [webpack resolve.fallback](https://webpack.js.org/configuration/resolve/#resolvefallback) and [Webpack.IgnorePlugin](https://webpack.js.org/plugins/ignore-plugin/) but they do not allow throwing proper errors at runtime that would contain the loaded module name for reporting.
`__unsupported_module()` is defined in `EdgeRuntime`, and returns a proxy that's throw on use (whether it's property access, function call, new operator... synchronous & promise-based styles).
However there's an issue with error reporting: webpack does not includes the import lines in the generated sourcemaps, preventing from displaying useful errors.
I extended our middleware-plugin to supplement the sourcemaps (when analyzing edge function code, it saves which module is imported from which file, together with line/column/source)
The react-dev-overlay was adapted to look for this additional information when the caught error relates to modules, instead of looking at sourcemaps.
I removed the previous mechanism (built by @nkzawa ) which caught webpack errors at built time to change the displayed error message (files `next/build/index.js`, `next/build/utils.ts` and `wellknown-errors-plugin`)
2022-07-06 22:54:44 +02:00
|
|
|
layer: isMiddlewareFilename(name) || isApi ? 'middleware' : undefined,
|
2022-04-27 11:50:29 +02:00
|
|
|
library: { name: ['_ENTRIES', `middleware_[name]`], type: 'assign' },
|
|
|
|
runtime: EDGE_RUNTIME_WEBPACK,
|
2022-01-10 18:41:53 +01:00
|
|
|
asyncChunks: false,
|
2021-10-20 19:52:11 +02:00
|
|
|
...entry,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (
|
2022-04-27 11:50:29 +02:00
|
|
|
// Client special cases
|
2021-10-20 19:52:11 +02:00
|
|
|
name !== 'polyfills' &&
|
2022-05-03 12:37:23 +02:00
|
|
|
name !== CLIENT_STATIC_FILES_RUNTIME_MAIN &&
|
|
|
|
name !== CLIENT_STATIC_FILES_RUNTIME_MAIN_ROOT &&
|
|
|
|
name !== CLIENT_STATIC_FILES_RUNTIME_AMP &&
|
|
|
|
name !== CLIENT_STATIC_FILES_RUNTIME_REACT_REFRESH
|
2021-10-20 19:52:11 +02:00
|
|
|
) {
|
2022-07-12 18:32:27 +02:00
|
|
|
// TODO-APP: this is a temporary fix. @shuding is going to change the handling of server components
|
2022-06-01 13:52:57 +02:00
|
|
|
if (appDir && entry.import.includes('flight')) {
|
|
|
|
return {
|
|
|
|
dependOn: CLIENT_STATIC_FILES_RUNTIME_MAIN_ROOT,
|
|
|
|
...entry,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-10-20 19:52:11 +02:00
|
|
|
return {
|
|
|
|
dependOn:
|
2021-10-06 17:40:01 +02:00
|
|
|
name.startsWith('pages/') && name !== 'pages/_app'
|
|
|
|
? 'pages/_app'
|
2022-06-01 13:52:57 +02:00
|
|
|
: CLIENT_STATIC_FILES_RUNTIME_MAIN,
|
2021-10-20 19:52:11 +02:00
|
|
|
...entry,
|
2021-09-17 21:20:09 +02:00
|
|
|
}
|
|
|
|
}
|
2021-10-20 19:52:11 +02:00
|
|
|
|
|
|
|
return entry
|
2021-09-17 21:20:09 +02:00
|
|
|
}
|