2022-03-26 02:19:51 +01:00
|
|
|
import type { NextConfigComplete, PageRuntime } from '../server/config-shared'
|
|
|
|
|
2022-01-24 22:38:54 +01:00
|
|
|
import '../server/node-polyfill-fetch'
|
2021-12-21 16:13:45 +01:00
|
|
|
import chalk from 'next/dist/compiled/chalk'
|
2021-05-14 16:29:49 +02:00
|
|
|
import getGzipSize from 'next/dist/compiled/gzip-size'
|
2019-05-14 17:11:22 +02:00
|
|
|
import textTable from 'next/dist/compiled/text-table'
|
2019-04-10 18:22:10 +02:00
|
|
|
import path from 'path'
|
2021-05-14 16:29:49 +02:00
|
|
|
import { promises as fs } from 'fs'
|
2022-01-17 16:17:22 +01:00
|
|
|
import { isValidElementType } from 'next/dist/compiled/react-is'
|
2020-04-06 16:24:37 +02:00
|
|
|
import stripAnsi from 'next/dist/compiled/strip-ansi'
|
2020-06-09 22:16:23 +02:00
|
|
|
import {
|
|
|
|
Redirect,
|
|
|
|
Rewrite,
|
|
|
|
Header,
|
|
|
|
CustomRoutes,
|
|
|
|
} from '../lib/load-custom-routes'
|
2020-01-27 23:50:59 +01:00
|
|
|
import {
|
|
|
|
SSG_GET_INITIAL_PROPS_CONFLICT,
|
|
|
|
SERVER_PROPS_GET_INIT_PROPS_CONFLICT,
|
|
|
|
SERVER_PROPS_SSG_CONFLICT,
|
2022-01-04 01:47:18 +01:00
|
|
|
MIDDLEWARE_ROUTE,
|
2020-01-27 23:50:59 +01:00
|
|
|
} from '../lib/constants'
|
2019-05-01 22:31:08 +02:00
|
|
|
import prettyBytes from '../lib/pretty-bytes'
|
2021-06-30 11:43:31 +02:00
|
|
|
import { getRouteMatcher, getRouteRegex } from '../shared/lib/router/utils'
|
|
|
|
import { isDynamicRoute } from '../shared/lib/router/utils/is-dynamic'
|
|
|
|
import escapePathDelimiters from '../shared/lib/router/utils/escape-path-delimiters'
|
2019-12-12 17:20:24 +01:00
|
|
|
import { findPageFile } from '../server/lib/find-page-file'
|
2021-08-16 21:29:11 +02:00
|
|
|
import { GetStaticPaths, PageConfig } from 'next/types'
|
2021-11-09 18:03:20 +01:00
|
|
|
import {
|
|
|
|
denormalizePagePath,
|
|
|
|
normalizePagePath,
|
|
|
|
} from '../server/normalize-page-path'
|
2021-06-30 13:44:40 +02:00
|
|
|
import { BuildManifest } from '../server/get-page-files'
|
2020-06-30 06:06:39 +02:00
|
|
|
import { removePathTrailingSlash } from '../client/normalize-trailing-slash'
|
2020-12-28 23:04:51 +01:00
|
|
|
import { UnwrapPromise } from '../lib/coalesced-function'
|
2021-06-30 11:43:31 +02:00
|
|
|
import { normalizeLocalePath } from '../shared/lib/i18n/normalize-locale-path'
|
2021-01-11 21:50:17 +01:00
|
|
|
import * as Log from './output/log'
|
2021-06-30 13:44:40 +02:00
|
|
|
import { loadComponents } from '../server/load-components'
|
2021-09-13 15:49:29 +02:00
|
|
|
import { trace } from '../trace'
|
2021-08-03 02:38:42 +02:00
|
|
|
import { setHttpAgentOptions } from '../server/config'
|
2021-09-16 18:06:57 +02:00
|
|
|
import isError from '../lib/is-error'
|
2021-11-09 18:03:20 +01:00
|
|
|
import { recursiveDelete } from '../lib/recursive-delete'
|
|
|
|
import { Sema } from 'next/dist/compiled/async-sema'
|
2022-01-04 01:47:18 +01:00
|
|
|
import { MiddlewareManifest } from './webpack/plugins/middleware-plugin'
|
2019-04-10 05:15:35 +02:00
|
|
|
|
2021-11-02 22:03:29 +01:00
|
|
|
const { builtinModules } = require('module')
|
2021-11-06 12:27:40 +01:00
|
|
|
const RESERVED_PAGE = /^\/(_app|_error|_document|api(\/|$))/
|
2021-05-14 16:29:49 +02:00
|
|
|
const fileGzipStats: { [k: string]: Promise<number> | undefined } = {}
|
2019-12-14 07:39:59 +01:00
|
|
|
const fsStatGzip = (file: string) => {
|
2021-05-14 16:29:49 +02:00
|
|
|
const cached = fileGzipStats[file]
|
|
|
|
if (cached) return cached
|
|
|
|
return (fileGzipStats[file] = getGzipSize.file(file))
|
|
|
|
}
|
|
|
|
|
|
|
|
const fileSize = async (file: string) => (await fs.stat(file)).size
|
|
|
|
|
|
|
|
const fileStats: { [k: string]: Promise<number> | undefined } = {}
|
|
|
|
const fsStat = (file: string) => {
|
|
|
|
const cached = fileStats[file]
|
|
|
|
if (cached) return cached
|
|
|
|
return (fileStats[file] = fileSize(file))
|
2019-09-19 18:16:51 +02:00
|
|
|
}
|
2019-04-10 21:19:50 +02:00
|
|
|
|
2019-05-14 17:11:22 +02:00
|
|
|
export interface PageInfo {
|
2019-12-14 20:23:04 +01:00
|
|
|
isHybridAmp?: boolean
|
2019-05-14 17:11:22 +02:00
|
|
|
size: number
|
2020-01-09 19:49:52 +01:00
|
|
|
totalSize: number
|
2019-12-12 10:45:45 +01:00
|
|
|
static: boolean
|
|
|
|
isSsg: boolean
|
|
|
|
ssgPageRoutes: string[] | null
|
2020-08-04 10:42:18 +02:00
|
|
|
initialRevalidateSeconds: number | false
|
2021-07-16 11:21:44 +02:00
|
|
|
pageDuration: number | undefined
|
|
|
|
ssgPageDurations: number[] | undefined
|
2022-03-26 02:19:51 +01:00
|
|
|
runtime: PageRuntime
|
2019-05-01 22:31:08 +02:00
|
|
|
}
|
|
|
|
|
2019-12-12 17:20:24 +01:00
|
|
|
export async function printTreeView(
|
|
|
|
list: readonly string[],
|
2019-06-24 20:59:51 +02:00
|
|
|
pageInfos: Map<string, PageInfo>,
|
2019-12-12 17:20:24 +01:00
|
|
|
serverless: boolean,
|
2019-12-12 20:44:34 +01:00
|
|
|
{
|
|
|
|
distPath,
|
2020-01-09 19:49:52 +01:00
|
|
|
buildId,
|
2019-12-12 20:44:34 +01:00
|
|
|
pagesDir,
|
|
|
|
pageExtensions,
|
|
|
|
buildManifest,
|
2020-03-14 09:58:20 +01:00
|
|
|
useStatic404,
|
2021-05-14 16:29:49 +02:00
|
|
|
gzipSize = true,
|
2019-12-12 20:44:34 +01:00
|
|
|
}: {
|
|
|
|
distPath: string
|
2020-01-09 19:49:52 +01:00
|
|
|
buildId: string
|
2019-12-12 20:44:34 +01:00
|
|
|
pagesDir: string
|
|
|
|
pageExtensions: string[]
|
2020-06-14 14:49:46 +02:00
|
|
|
buildManifest: BuildManifest
|
2020-03-14 09:58:20 +01:00
|
|
|
useStatic404: boolean
|
2021-05-14 16:29:49 +02:00
|
|
|
gzipSize?: boolean
|
2019-12-12 20:44:34 +01:00
|
|
|
}
|
2019-05-14 17:11:22 +02:00
|
|
|
) {
|
|
|
|
const getPrettySize = (_size: number): string => {
|
|
|
|
const size = prettyBytes(_size)
|
2019-12-14 07:39:59 +01:00
|
|
|
// green for 0-130kb
|
|
|
|
if (_size < 130 * 1000) return chalk.green(size)
|
|
|
|
// yellow for 130-170kb
|
|
|
|
if (_size < 170 * 1000) return chalk.yellow(size)
|
|
|
|
// red for >= 170kb
|
2019-05-14 17:11:22 +02:00
|
|
|
return chalk.red.bold(size)
|
2019-05-01 22:31:08 +02:00
|
|
|
}
|
|
|
|
|
2021-07-16 11:21:44 +02:00
|
|
|
const MIN_DURATION = 300
|
|
|
|
const getPrettyDuration = (_duration: number): string => {
|
|
|
|
const duration = `${_duration} ms`
|
|
|
|
// green for 300-1000ms
|
|
|
|
if (_duration < 1000) return chalk.green(duration)
|
|
|
|
// yellow for 1000-2000ms
|
|
|
|
if (_duration < 2000) return chalk.yellow(duration)
|
|
|
|
// red for >= 2000ms
|
|
|
|
return chalk.red.bold(duration)
|
|
|
|
}
|
|
|
|
|
2020-03-18 09:47:39 +01:00
|
|
|
const getCleanName = (fileName: string) =>
|
|
|
|
fileName
|
|
|
|
// Trim off `static/`
|
|
|
|
.replace(/^static\//, '')
|
|
|
|
// Re-add `static/` for root files
|
|
|
|
.replace(/^<buildId>/, 'static')
|
|
|
|
// Remove file hash
|
2021-05-14 16:29:49 +02:00
|
|
|
.replace(/(?:^|[.-])([0-9a-z]{6})[0-9a-z]{14}(?=\.)/, '.$1')
|
2020-03-18 09:47:39 +01:00
|
|
|
|
2020-01-09 19:49:52 +01:00
|
|
|
const messages: [string, string, string][] = [
|
2020-05-18 21:24:37 +02:00
|
|
|
['Page', 'Size', 'First Load JS'].map((entry) =>
|
|
|
|
chalk.underline(entry)
|
|
|
|
) as [string, string, string],
|
2019-05-14 17:11:22 +02:00
|
|
|
]
|
|
|
|
|
2019-12-12 17:20:24 +01:00
|
|
|
const hasCustomApp = await findPageFile(pagesDir, '/_app', pageExtensions)
|
2022-04-05 21:46:17 +02:00
|
|
|
const hasCustomAppServer = await findPageFile(
|
|
|
|
pagesDir,
|
|
|
|
'/_app.server',
|
|
|
|
pageExtensions
|
|
|
|
)
|
2019-12-12 17:20:24 +01:00
|
|
|
|
2020-04-05 13:19:14 +02:00
|
|
|
pageInfos.set('/404', {
|
|
|
|
...(pageInfos.get('/404') || pageInfos.get('/_error')),
|
|
|
|
static: useStatic404,
|
|
|
|
} as any)
|
|
|
|
|
|
|
|
if (!list.includes('/404')) {
|
2020-03-14 09:58:20 +01:00
|
|
|
list = [...list, '/404']
|
|
|
|
}
|
|
|
|
|
2021-05-14 16:29:49 +02:00
|
|
|
const sizeData = await computeFromManifest(
|
|
|
|
buildManifest,
|
|
|
|
distPath,
|
|
|
|
gzipSize,
|
|
|
|
pageInfos
|
|
|
|
)
|
2020-03-18 09:47:39 +01:00
|
|
|
|
2021-10-25 00:09:47 +02:00
|
|
|
const usedSymbols = new Set()
|
|
|
|
|
2019-12-12 17:20:24 +01:00
|
|
|
const pageList = list
|
|
|
|
.slice()
|
|
|
|
.filter(
|
2020-05-18 21:24:37 +02:00
|
|
|
(e) =>
|
2019-12-12 17:20:24 +01:00
|
|
|
!(
|
|
|
|
e === '/_document' ||
|
2020-04-05 13:19:14 +02:00
|
|
|
e === '/_error' ||
|
2022-04-05 21:46:17 +02:00
|
|
|
(!hasCustomApp && e === '/_app') ||
|
|
|
|
(!hasCustomAppServer && e === '/_app.server')
|
2019-12-12 17:20:24 +01:00
|
|
|
)
|
|
|
|
)
|
2019-05-14 17:11:22 +02:00
|
|
|
.sort((a, b) => a.localeCompare(b))
|
2019-12-12 17:20:24 +01:00
|
|
|
|
|
|
|
pageList.forEach((item, i, arr) => {
|
2021-10-25 00:09:47 +02:00
|
|
|
const border =
|
2019-12-12 17:20:24 +01:00
|
|
|
i === 0
|
|
|
|
? arr.length === 1
|
|
|
|
? '─'
|
|
|
|
: '┌'
|
|
|
|
: i === arr.length - 1
|
|
|
|
? '└'
|
|
|
|
: '├'
|
|
|
|
|
|
|
|
const pageInfo = pageInfos.get(item)
|
2020-06-14 14:49:46 +02:00
|
|
|
const ampFirst = buildManifest.ampFirstPages.includes(item)
|
2021-07-16 11:21:44 +02:00
|
|
|
const totalDuration =
|
|
|
|
(pageInfo?.pageDuration || 0) +
|
|
|
|
(pageInfo?.ssgPageDurations?.reduce((a, b) => a + (b || 0), 0) || 0)
|
|
|
|
|
2021-10-25 00:09:47 +02:00
|
|
|
const symbol =
|
2022-04-05 21:46:17 +02:00
|
|
|
item === '/_app' || item === '/_app.server'
|
2021-10-25 00:09:47 +02:00
|
|
|
? ' '
|
|
|
|
: item.endsWith('/_middleware')
|
|
|
|
? 'ƒ'
|
|
|
|
: pageInfo?.static
|
|
|
|
? '○'
|
|
|
|
: pageInfo?.isSsg
|
|
|
|
? '●'
|
2022-03-26 02:19:51 +01:00
|
|
|
: pageInfo?.runtime === 'edge'
|
|
|
|
? 'ℇ'
|
2021-10-25 00:09:47 +02:00
|
|
|
: 'λ'
|
2021-10-26 18:50:56 +02:00
|
|
|
|
2021-10-25 00:09:47 +02:00
|
|
|
usedSymbols.add(symbol)
|
|
|
|
|
|
|
|
if (pageInfo?.initialRevalidateSeconds) usedSymbols.add('ISR')
|
|
|
|
|
2019-12-12 17:20:24 +01:00
|
|
|
messages.push([
|
2021-10-25 00:09:47 +02:00
|
|
|
`${border} ${symbol} ${
|
2020-08-04 10:42:18 +02:00
|
|
|
pageInfo?.initialRevalidateSeconds
|
|
|
|
? `${item} (ISR: ${pageInfo?.initialRevalidateSeconds} Seconds)`
|
|
|
|
: item
|
2021-07-16 11:21:44 +02:00
|
|
|
}${
|
|
|
|
totalDuration > MIN_DURATION
|
|
|
|
? ` (${getPrettyDuration(totalDuration)})`
|
|
|
|
: ''
|
2020-08-04 10:42:18 +02:00
|
|
|
}`,
|
2019-12-12 17:20:24 +01:00
|
|
|
pageInfo
|
2020-06-14 14:49:46 +02:00
|
|
|
? ampFirst
|
2019-12-12 17:20:24 +01:00
|
|
|
? chalk.cyan('AMP')
|
|
|
|
: pageInfo.size >= 0
|
2020-01-09 19:49:52 +01:00
|
|
|
? prettyBytes(pageInfo.size)
|
|
|
|
: ''
|
|
|
|
: '',
|
|
|
|
pageInfo
|
2020-06-14 14:49:46 +02:00
|
|
|
? ampFirst
|
2020-01-09 19:49:52 +01:00
|
|
|
? chalk.cyan('AMP')
|
|
|
|
: pageInfo.size >= 0
|
|
|
|
? getPrettySize(pageInfo.totalSize)
|
2019-12-12 17:20:24 +01:00
|
|
|
: ''
|
|
|
|
: '',
|
|
|
|
])
|
|
|
|
|
2020-03-18 09:47:39 +01:00
|
|
|
const uniqueCssFiles =
|
|
|
|
buildManifest.pages[item]?.filter(
|
2020-05-18 21:24:37 +02:00
|
|
|
(file) => file.endsWith('.css') && sizeData.uniqueFiles.includes(file)
|
2020-03-18 09:47:39 +01:00
|
|
|
) || []
|
|
|
|
|
|
|
|
if (uniqueCssFiles.length > 0) {
|
|
|
|
const contSymbol = i === arr.length - 1 ? ' ' : '├'
|
|
|
|
|
|
|
|
uniqueCssFiles.forEach((file, index, { length }) => {
|
|
|
|
const innerSymbol = index === length - 1 ? '└' : '├'
|
|
|
|
messages.push([
|
|
|
|
`${contSymbol} ${innerSymbol} ${getCleanName(file)}`,
|
|
|
|
prettyBytes(sizeData.sizeUniqueFiles[file]),
|
|
|
|
'',
|
|
|
|
])
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2020-01-08 17:30:53 +01:00
|
|
|
if (pageInfo?.ssgPageRoutes?.length) {
|
2019-12-12 17:20:24 +01:00
|
|
|
const totalRoutes = pageInfo.ssgPageRoutes.length
|
|
|
|
const contSymbol = i === arr.length - 1 ? ' ' : '├'
|
|
|
|
|
2021-07-16 11:21:44 +02:00
|
|
|
let routes: { route: string; duration: number; avgDuration?: number }[]
|
|
|
|
if (
|
|
|
|
pageInfo.ssgPageDurations &&
|
|
|
|
pageInfo.ssgPageDurations.some((d) => d > MIN_DURATION)
|
|
|
|
) {
|
|
|
|
const previewPages = totalRoutes === 8 ? 8 : Math.min(totalRoutes, 7)
|
|
|
|
const routesWithDuration = pageInfo.ssgPageRoutes
|
|
|
|
.map((route, idx) => ({
|
|
|
|
route,
|
|
|
|
duration: pageInfo.ssgPageDurations![idx] || 0,
|
|
|
|
}))
|
|
|
|
.sort(({ duration: a }, { duration: b }) =>
|
|
|
|
// Sort by duration
|
|
|
|
// keep too small durations in original order at the end
|
|
|
|
a <= MIN_DURATION && b <= MIN_DURATION ? 0 : b - a
|
|
|
|
)
|
|
|
|
routes = routesWithDuration.slice(0, previewPages)
|
|
|
|
const remainingRoutes = routesWithDuration.slice(previewPages)
|
|
|
|
if (remainingRoutes.length) {
|
|
|
|
const remaining = remainingRoutes.length
|
|
|
|
const avgDuration = Math.round(
|
|
|
|
remainingRoutes.reduce(
|
|
|
|
(total, { duration }) => total + duration,
|
|
|
|
0
|
|
|
|
) / remainingRoutes.length
|
|
|
|
)
|
|
|
|
routes.push({
|
|
|
|
route: `[+${remaining} more paths]`,
|
|
|
|
duration: 0,
|
|
|
|
avgDuration,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
const previewPages = totalRoutes === 4 ? 4 : Math.min(totalRoutes, 3)
|
|
|
|
routes = pageInfo.ssgPageRoutes
|
|
|
|
.slice(0, previewPages)
|
|
|
|
.map((route) => ({ route, duration: 0 }))
|
|
|
|
if (totalRoutes > previewPages) {
|
|
|
|
const remaining = totalRoutes - previewPages
|
|
|
|
routes.push({ route: `[+${remaining} more paths]`, duration: 0 })
|
|
|
|
}
|
2019-12-12 10:45:45 +01:00
|
|
|
}
|
2019-12-12 17:20:24 +01:00
|
|
|
|
2021-07-16 11:21:44 +02:00
|
|
|
routes.forEach(({ route, duration, avgDuration }, index, { length }) => {
|
2019-12-12 17:20:24 +01:00
|
|
|
const innerSymbol = index === length - 1 ? '└' : '├'
|
2021-07-16 11:21:44 +02:00
|
|
|
messages.push([
|
|
|
|
`${contSymbol} ${innerSymbol} ${route}${
|
|
|
|
duration > MIN_DURATION ? ` (${getPrettyDuration(duration)})` : ''
|
|
|
|
}${
|
|
|
|
avgDuration && avgDuration > MIN_DURATION
|
|
|
|
? ` (avg ${getPrettyDuration(avgDuration)})`
|
|
|
|
: ''
|
|
|
|
}`,
|
|
|
|
'',
|
|
|
|
'',
|
|
|
|
])
|
2019-12-12 17:20:24 +01:00
|
|
|
})
|
|
|
|
}
|
|
|
|
})
|
2019-05-01 22:31:08 +02:00
|
|
|
|
2020-03-18 09:47:39 +01:00
|
|
|
const sharedFilesSize = sizeData.sizeCommonFiles
|
|
|
|
const sharedFiles = sizeData.sizeCommonFile
|
2019-12-12 20:44:34 +01:00
|
|
|
|
2020-04-01 11:39:25 +02:00
|
|
|
messages.push([
|
|
|
|
'+ First Load JS shared by all',
|
|
|
|
getPrettySize(sharedFilesSize),
|
|
|
|
'',
|
|
|
|
])
|
2020-03-19 09:25:10 +01:00
|
|
|
const sharedFileKeys = Object.keys(sharedFiles)
|
|
|
|
const sharedCssFiles: string[] = []
|
|
|
|
;[
|
|
|
|
...sharedFileKeys
|
2020-05-18 21:24:37 +02:00
|
|
|
.filter((file) => {
|
2020-03-19 09:25:10 +01:00
|
|
|
if (file.endsWith('.css')) {
|
|
|
|
sharedCssFiles.push(file)
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
return true
|
|
|
|
})
|
2020-05-18 21:24:37 +02:00
|
|
|
.map((e) => e.replace(buildId, '<buildId>'))
|
2020-03-19 09:25:10 +01:00
|
|
|
.sort(),
|
2020-05-18 21:24:37 +02:00
|
|
|
...sharedCssFiles.map((e) => e.replace(buildId, '<buildId>')).sort(),
|
2020-03-19 09:25:10 +01:00
|
|
|
].forEach((fileName, index, { length }) => {
|
|
|
|
const innerSymbol = index === length - 1 ? '└' : '├'
|
|
|
|
|
|
|
|
const originalName = fileName.replace('<buildId>', buildId)
|
|
|
|
const cleanName = getCleanName(fileName)
|
|
|
|
|
|
|
|
messages.push([
|
|
|
|
` ${innerSymbol} ${cleanName}`,
|
|
|
|
prettyBytes(sharedFiles[originalName]),
|
|
|
|
'',
|
|
|
|
])
|
|
|
|
})
|
2019-12-12 20:44:34 +01:00
|
|
|
|
2019-05-14 17:11:22 +02:00
|
|
|
console.log(
|
|
|
|
textTable(messages, {
|
2020-01-09 19:49:52 +01:00
|
|
|
align: ['l', 'l', 'r'],
|
2020-05-18 21:24:37 +02:00
|
|
|
stringLength: (str) => stripAnsi(str).length,
|
2019-04-10 05:15:35 +02:00
|
|
|
})
|
2019-05-14 17:11:22 +02:00
|
|
|
)
|
2019-04-10 05:15:35 +02:00
|
|
|
|
2019-06-10 20:59:36 +02:00
|
|
|
console.log()
|
|
|
|
console.log(
|
|
|
|
textTable(
|
|
|
|
[
|
2021-10-25 00:09:47 +02:00
|
|
|
usedSymbols.has('ƒ') && [
|
|
|
|
'ƒ',
|
|
|
|
'(Middleware)',
|
|
|
|
`intercepts requests (uses ${chalk.cyan('_middleware')})`,
|
|
|
|
],
|
2021-10-26 18:50:56 +02:00
|
|
|
usedSymbols.has('ℇ') && [
|
|
|
|
'ℇ',
|
|
|
|
'(Streaming)',
|
|
|
|
`server-side renders with streaming (uses React 18 SSR streaming or Server Components)`,
|
|
|
|
],
|
2021-10-25 00:09:47 +02:00
|
|
|
usedSymbols.has('λ') && [
|
2019-12-12 10:45:45 +01:00
|
|
|
'λ',
|
|
|
|
serverless ? '(Lambda)' : '(Server)',
|
|
|
|
`server-side renders at runtime (uses ${chalk.cyan(
|
|
|
|
'getInitialProps'
|
2020-02-27 18:04:30 +01:00
|
|
|
)} or ${chalk.cyan('getServerSideProps')})`,
|
2019-12-12 10:45:45 +01:00
|
|
|
],
|
2021-10-25 00:09:47 +02:00
|
|
|
usedSymbols.has('○') && [
|
2019-12-12 10:45:45 +01:00
|
|
|
'○',
|
|
|
|
'(Static)',
|
|
|
|
'automatically rendered as static HTML (uses no initial props)',
|
|
|
|
],
|
2021-10-25 00:09:47 +02:00
|
|
|
usedSymbols.has('●') && [
|
2019-12-12 10:45:45 +01:00
|
|
|
'●',
|
|
|
|
'(SSG)',
|
|
|
|
`automatically generated as static HTML + JSON (uses ${chalk.cyan(
|
|
|
|
'getStaticProps'
|
|
|
|
)})`,
|
|
|
|
],
|
2021-10-25 00:09:47 +02:00
|
|
|
usedSymbols.has('ISR') && [
|
2020-08-04 10:42:18 +02:00
|
|
|
'',
|
|
|
|
'(ISR)',
|
|
|
|
`incremental static regeneration (uses revalidate in ${chalk.cyan(
|
|
|
|
'getStaticProps'
|
|
|
|
)})`,
|
|
|
|
],
|
2021-10-25 00:09:47 +02:00
|
|
|
].filter((x) => x) as [string, string, string][],
|
2019-06-10 20:59:36 +02:00
|
|
|
{
|
|
|
|
align: ['l', 'l', 'l'],
|
2020-05-18 21:24:37 +02:00
|
|
|
stringLength: (str) => stripAnsi(str).length,
|
2019-06-10 20:59:36 +02:00
|
|
|
}
|
|
|
|
)
|
|
|
|
)
|
|
|
|
|
2019-04-10 05:15:35 +02:00
|
|
|
console.log()
|
|
|
|
}
|
|
|
|
|
2019-11-26 10:33:47 +01:00
|
|
|
export function printCustomRoutes({
|
|
|
|
redirects,
|
|
|
|
rewrites,
|
2020-02-07 16:06:02 +01:00
|
|
|
headers,
|
2020-06-09 22:16:23 +02:00
|
|
|
}: CustomRoutes) {
|
2019-11-26 10:33:47 +01:00
|
|
|
const printRoutes = (
|
2020-02-07 16:06:02 +01:00
|
|
|
routes: Redirect[] | Rewrite[] | Header[],
|
|
|
|
type: 'Redirects' | 'Rewrites' | 'Headers'
|
2019-11-26 10:33:47 +01:00
|
|
|
) => {
|
|
|
|
const isRedirects = type === 'Redirects'
|
2020-02-07 16:06:02 +01:00
|
|
|
const isHeaders = type === 'Headers'
|
2019-11-26 10:33:47 +01:00
|
|
|
console.log(chalk.underline(type))
|
|
|
|
console.log()
|
|
|
|
|
2020-02-07 16:06:02 +01:00
|
|
|
/*
|
|
|
|
┌ source
|
|
|
|
├ permanent/statusCode
|
|
|
|
└ destination
|
|
|
|
*/
|
|
|
|
const routesStr = (routes as any[])
|
|
|
|
.map((route: { source: string }) => {
|
|
|
|
let routeStr = `┌ source: ${route.source}\n`
|
|
|
|
|
|
|
|
if (!isHeaders) {
|
|
|
|
const r = route as Rewrite
|
|
|
|
routeStr += `${isRedirects ? '├' : '└'} destination: ${
|
|
|
|
r.destination
|
|
|
|
}\n`
|
2019-11-26 10:33:47 +01:00
|
|
|
}
|
2020-02-07 16:06:02 +01:00
|
|
|
if (isRedirects) {
|
|
|
|
const r = route as Redirect
|
|
|
|
routeStr += `└ ${
|
|
|
|
r.statusCode
|
|
|
|
? `status: ${r.statusCode}`
|
|
|
|
: `permanent: ${r.permanent}`
|
|
|
|
}\n`
|
|
|
|
}
|
|
|
|
|
|
|
|
if (isHeaders) {
|
|
|
|
const r = route as Header
|
|
|
|
routeStr += `└ headers:\n`
|
|
|
|
|
|
|
|
for (let i = 0; i < r.headers.length; i++) {
|
|
|
|
const header = r.headers[i]
|
|
|
|
const last = i === headers.length - 1
|
|
|
|
|
|
|
|
routeStr += ` ${last ? '└' : '├'} ${header.key}: ${header.value}\n`
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return routeStr
|
|
|
|
})
|
|
|
|
.join('\n')
|
|
|
|
|
|
|
|
console.log(routesStr, '\n')
|
2019-11-26 10:33:47 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
if (redirects.length) {
|
|
|
|
printRoutes(redirects, 'Redirects')
|
|
|
|
}
|
2020-02-07 16:06:02 +01:00
|
|
|
if (headers.length) {
|
|
|
|
printRoutes(headers, 'Headers')
|
|
|
|
}
|
2021-03-26 16:19:48 +01:00
|
|
|
|
|
|
|
const combinedRewrites = [
|
|
|
|
...rewrites.beforeFiles,
|
|
|
|
...rewrites.afterFiles,
|
|
|
|
...rewrites.fallback,
|
|
|
|
]
|
|
|
|
if (combinedRewrites.length) {
|
|
|
|
printRoutes(combinedRewrites, 'Rewrites')
|
|
|
|
}
|
2019-11-26 10:33:47 +01:00
|
|
|
}
|
|
|
|
|
2019-12-12 20:44:34 +01:00
|
|
|
type ComputeManifestShape = {
|
|
|
|
commonFiles: string[]
|
2020-01-09 19:49:52 +01:00
|
|
|
uniqueFiles: string[]
|
2020-03-18 09:47:39 +01:00
|
|
|
sizeUniqueFiles: { [file: string]: number }
|
2019-12-12 20:44:34 +01:00
|
|
|
sizeCommonFile: { [file: string]: number }
|
|
|
|
sizeCommonFiles: number
|
|
|
|
}
|
|
|
|
|
2020-06-14 14:49:46 +02:00
|
|
|
let cachedBuildManifest: BuildManifest | undefined
|
2019-12-12 20:44:34 +01:00
|
|
|
|
|
|
|
let lastCompute: ComputeManifestShape | undefined
|
2019-12-14 20:23:04 +01:00
|
|
|
let lastComputePageInfo: boolean | undefined
|
2019-12-12 20:44:34 +01:00
|
|
|
|
2021-05-14 16:29:49 +02:00
|
|
|
export async function computeFromManifest(
|
2020-06-14 14:49:46 +02:00
|
|
|
manifest: BuildManifest,
|
2019-12-12 20:44:34 +01:00
|
|
|
distPath: string,
|
2021-05-14 16:29:49 +02:00
|
|
|
gzipSize: boolean = true,
|
2019-12-14 20:23:04 +01:00
|
|
|
pageInfos?: Map<string, PageInfo>
|
2019-12-12 20:44:34 +01:00
|
|
|
): Promise<ComputeManifestShape> {
|
|
|
|
if (
|
|
|
|
Object.is(cachedBuildManifest, manifest) &&
|
2019-12-14 20:23:04 +01:00
|
|
|
lastComputePageInfo === !!pageInfos
|
2019-12-12 20:44:34 +01:00
|
|
|
) {
|
|
|
|
return lastCompute!
|
|
|
|
}
|
|
|
|
|
|
|
|
let expected = 0
|
|
|
|
const files = new Map<string, number>()
|
2020-05-18 21:24:37 +02:00
|
|
|
Object.keys(manifest.pages).forEach((key) => {
|
2019-12-14 20:23:04 +01:00
|
|
|
if (pageInfos) {
|
2020-06-08 16:17:03 +02:00
|
|
|
const pageInfo = pageInfos.get(key)
|
2019-12-14 20:23:04 +01:00
|
|
|
// don't include AMP pages since they don't rely on shared bundles
|
2020-06-14 14:49:46 +02:00
|
|
|
// AMP First pages are not under the pageInfos key
|
|
|
|
if (pageInfo?.isHybridAmp) {
|
2019-12-14 20:23:04 +01:00
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-12-12 20:44:34 +01:00
|
|
|
++expected
|
2020-05-18 21:24:37 +02:00
|
|
|
manifest.pages[key].forEach((file) => {
|
2020-01-09 19:49:52 +01:00
|
|
|
if (key === '/_app') {
|
|
|
|
files.set(file, Infinity)
|
|
|
|
} else if (files.has(file)) {
|
2019-12-12 20:44:34 +01:00
|
|
|
files.set(file, files.get(file)! + 1)
|
|
|
|
} else {
|
|
|
|
files.set(file, 1)
|
|
|
|
}
|
|
|
|
})
|
|
|
|
})
|
|
|
|
|
2021-05-14 16:29:49 +02:00
|
|
|
const getSize = gzipSize ? fsStatGzip : fsStat
|
|
|
|
|
2019-12-12 20:44:34 +01:00
|
|
|
const commonFiles = [...files.entries()]
|
2020-01-09 19:49:52 +01:00
|
|
|
.filter(([, len]) => len === expected || len === Infinity)
|
|
|
|
.map(([f]) => f)
|
|
|
|
const uniqueFiles = [...files.entries()]
|
|
|
|
.filter(([, len]) => len === 1)
|
2019-12-12 20:44:34 +01:00
|
|
|
.map(([f]) => f)
|
|
|
|
|
2019-12-14 07:39:59 +01:00
|
|
|
let stats: [string, number][]
|
2019-12-12 20:44:34 +01:00
|
|
|
try {
|
|
|
|
stats = await Promise.all(
|
|
|
|
commonFiles.map(
|
2020-05-18 21:24:37 +02:00
|
|
|
async (f) =>
|
2021-05-14 16:29:49 +02:00
|
|
|
[f, await getSize(path.join(distPath, f))] as [string, number]
|
2019-12-12 20:44:34 +01:00
|
|
|
)
|
|
|
|
)
|
|
|
|
} catch (_) {
|
|
|
|
stats = []
|
|
|
|
}
|
|
|
|
|
2020-03-18 09:47:39 +01:00
|
|
|
let uniqueStats: [string, number][]
|
|
|
|
try {
|
|
|
|
uniqueStats = await Promise.all(
|
|
|
|
uniqueFiles.map(
|
2020-05-18 21:24:37 +02:00
|
|
|
async (f) =>
|
2021-05-14 16:29:49 +02:00
|
|
|
[f, await getSize(path.join(distPath, f))] as [string, number]
|
2020-03-18 09:47:39 +01:00
|
|
|
)
|
|
|
|
)
|
|
|
|
} catch (_) {
|
|
|
|
uniqueStats = []
|
|
|
|
}
|
|
|
|
|
2019-12-12 20:44:34 +01:00
|
|
|
lastCompute = {
|
|
|
|
commonFiles,
|
2020-01-09 19:49:52 +01:00
|
|
|
uniqueFiles,
|
2020-03-18 09:47:39 +01:00
|
|
|
sizeUniqueFiles: uniqueStats.reduce(
|
|
|
|
(obj, n) => Object.assign(obj, { [n[0]]: n[1] }),
|
|
|
|
{}
|
|
|
|
),
|
2019-12-12 20:44:34 +01:00
|
|
|
sizeCommonFile: stats.reduce(
|
2019-12-14 07:39:59 +01:00
|
|
|
(obj, n) => Object.assign(obj, { [n[0]]: n[1] }),
|
2019-12-12 20:44:34 +01:00
|
|
|
{}
|
|
|
|
),
|
2020-04-01 11:39:25 +02:00
|
|
|
sizeCommonFiles: stats.reduce((size, [f, stat]) => {
|
|
|
|
if (f.endsWith('.css')) return size
|
|
|
|
return size + stat
|
|
|
|
}, 0),
|
2019-12-12 20:44:34 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
cachedBuildManifest = manifest
|
2019-12-14 20:23:04 +01:00
|
|
|
lastComputePageInfo = !!pageInfos
|
2019-12-12 20:44:34 +01:00
|
|
|
return lastCompute!
|
|
|
|
}
|
|
|
|
|
2020-09-02 18:57:21 +02:00
|
|
|
export function difference<T>(main: T[] | Set<T>, sub: T[] | Set<T>): T[] {
|
2019-12-12 20:44:34 +01:00
|
|
|
const a = new Set(main)
|
|
|
|
const b = new Set(sub)
|
2020-05-18 21:24:37 +02:00
|
|
|
return [...a].filter((x) => !b.has(x))
|
2019-12-12 20:44:34 +01:00
|
|
|
}
|
|
|
|
|
2020-01-09 19:49:52 +01:00
|
|
|
function intersect<T>(main: T[], sub: T[]): T[] {
|
|
|
|
const a = new Set(main)
|
|
|
|
const b = new Set(sub)
|
2020-05-18 21:24:37 +02:00
|
|
|
return [...new Set([...a].filter((x) => b.has(x)))]
|
2020-01-09 19:49:52 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
function sum(a: number[]): number {
|
|
|
|
return a.reduce((size, stat) => size + stat, 0)
|
|
|
|
}
|
|
|
|
|
2020-04-01 11:39:25 +02:00
|
|
|
export async function getJsPageSizeInKb(
|
2019-05-01 22:31:08 +02:00
|
|
|
page: string,
|
|
|
|
distPath: string,
|
2021-05-14 16:29:49 +02:00
|
|
|
buildManifest: BuildManifest,
|
|
|
|
gzipSize: boolean = true,
|
|
|
|
computedManifestData?: ComputeManifestShape
|
2020-01-09 19:49:52 +01:00
|
|
|
): Promise<[number, number]> {
|
2021-05-14 16:29:49 +02:00
|
|
|
const data =
|
|
|
|
computedManifestData ||
|
|
|
|
(await computeFromManifest(buildManifest, distPath, gzipSize))
|
2020-01-09 19:49:52 +01:00
|
|
|
|
2020-11-18 19:30:00 +01:00
|
|
|
const fnFilterJs = (entry: string) => entry.endsWith('.js')
|
2020-01-09 19:49:52 +01:00
|
|
|
|
2020-06-11 10:57:24 +02:00
|
|
|
const pageFiles = (
|
|
|
|
buildManifest.pages[denormalizePagePath(page)] || []
|
2020-11-18 19:30:00 +01:00
|
|
|
).filter(fnFilterJs)
|
|
|
|
const appFiles = (buildManifest.pages['/_app'] || []).filter(fnFilterJs)
|
2020-01-09 19:49:52 +01:00
|
|
|
|
|
|
|
const fnMapRealPath = (dep: string) => `${distPath}/${dep}`
|
|
|
|
|
|
|
|
const allFilesReal = [...new Set([...pageFiles, ...appFiles])].map(
|
|
|
|
fnMapRealPath
|
|
|
|
)
|
|
|
|
const selfFilesReal = difference(
|
|
|
|
intersect(pageFiles, data.uniqueFiles),
|
|
|
|
data.commonFiles
|
|
|
|
).map(fnMapRealPath)
|
2019-12-12 20:44:34 +01:00
|
|
|
|
2021-05-14 16:29:49 +02:00
|
|
|
const getSize = gzipSize ? fsStatGzip : fsStat
|
|
|
|
|
2019-05-14 17:11:22 +02:00
|
|
|
try {
|
2020-01-09 19:49:52 +01:00
|
|
|
// Doesn't use `Promise.all`, as we'd double compute duplicate files. This
|
|
|
|
// function is memoized, so the second one will instantly resolve.
|
2021-05-14 16:29:49 +02:00
|
|
|
const allFilesSize = sum(await Promise.all(allFilesReal.map(getSize)))
|
|
|
|
const selfFilesSize = sum(await Promise.all(selfFilesReal.map(getSize)))
|
2020-06-11 10:57:24 +02:00
|
|
|
|
2020-01-09 19:49:52 +01:00
|
|
|
return [selfFilesSize, allFilesSize]
|
2019-05-14 17:11:22 +02:00
|
|
|
} catch (_) {}
|
2020-01-09 19:49:52 +01:00
|
|
|
return [-1, -1]
|
2019-05-01 22:31:08 +02:00
|
|
|
}
|
2019-05-22 18:36:53 +02:00
|
|
|
|
2020-02-24 22:36:59 +01:00
|
|
|
export async function buildStaticPaths(
|
|
|
|
page: string,
|
2020-10-07 23:11:01 +02:00
|
|
|
getStaticPaths: GetStaticPaths,
|
2021-10-22 01:04:40 +02:00
|
|
|
configFileName: string,
|
2020-10-07 23:11:01 +02:00
|
|
|
locales?: string[],
|
|
|
|
defaultLocale?: string
|
2020-08-04 17:10:31 +02:00
|
|
|
): Promise<
|
2020-12-28 21:08:58 +01:00
|
|
|
Omit<UnwrapPromise<ReturnType<GetStaticPaths>>, 'paths'> & {
|
|
|
|
paths: string[]
|
|
|
|
encodedPaths: string[]
|
|
|
|
}
|
2020-08-04 17:10:31 +02:00
|
|
|
> {
|
2020-02-24 22:36:59 +01:00
|
|
|
const prerenderPaths = new Set<string>()
|
2020-12-28 21:08:58 +01:00
|
|
|
const encodedPrerenderPaths = new Set<string>()
|
2020-02-24 22:36:59 +01:00
|
|
|
const _routeRegex = getRouteRegex(page)
|
|
|
|
const _routeMatcher = getRouteMatcher(_routeRegex)
|
|
|
|
|
|
|
|
// Get the default list of allowed params.
|
|
|
|
const _validParamKeys = Object.keys(_routeMatcher(page))
|
|
|
|
|
2020-10-27 10:43:15 +01:00
|
|
|
const staticPathsResult = await getStaticPaths({ locales, defaultLocale })
|
2020-02-24 22:36:59 +01:00
|
|
|
|
|
|
|
const expectedReturnVal =
|
2020-02-27 13:23:28 +01:00
|
|
|
`Expected: { paths: [], fallback: boolean }\n` +
|
2021-03-29 10:25:00 +02:00
|
|
|
`See here for more info: https://nextjs.org/docs/messages/invalid-getstaticpaths-value`
|
2020-02-24 22:36:59 +01:00
|
|
|
|
|
|
|
if (
|
|
|
|
!staticPathsResult ||
|
|
|
|
typeof staticPathsResult !== 'object' ||
|
|
|
|
Array.isArray(staticPathsResult)
|
|
|
|
) {
|
|
|
|
throw new Error(
|
2020-02-27 18:57:39 +01:00
|
|
|
`Invalid value returned from getStaticPaths in ${page}. Received ${typeof staticPathsResult} ${expectedReturnVal}`
|
2020-02-24 22:36:59 +01:00
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
const invalidStaticPathKeys = Object.keys(staticPathsResult).filter(
|
2020-05-18 21:24:37 +02:00
|
|
|
(key) => !(key === 'paths' || key === 'fallback')
|
2020-02-24 22:36:59 +01:00
|
|
|
)
|
|
|
|
|
|
|
|
if (invalidStaticPathKeys.length > 0) {
|
|
|
|
throw new Error(
|
2020-02-27 18:57:39 +01:00
|
|
|
`Extra keys returned from getStaticPaths in ${page} (${invalidStaticPathKeys.join(
|
2020-02-24 22:36:59 +01:00
|
|
|
', '
|
|
|
|
)}) ${expectedReturnVal}`
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
2020-08-04 17:10:31 +02:00
|
|
|
if (
|
|
|
|
!(
|
|
|
|
typeof staticPathsResult.fallback === 'boolean' ||
|
2020-10-27 05:01:37 +01:00
|
|
|
staticPathsResult.fallback === 'blocking'
|
2020-08-04 17:10:31 +02:00
|
|
|
)
|
|
|
|
) {
|
2020-02-27 13:23:28 +01:00
|
|
|
throw new Error(
|
2020-02-27 18:57:39 +01:00
|
|
|
`The \`fallback\` key must be returned from getStaticPaths in ${page}.\n` +
|
2020-02-27 13:23:28 +01:00
|
|
|
expectedReturnVal
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
2020-02-24 22:36:59 +01:00
|
|
|
const toPrerender = staticPathsResult.paths
|
|
|
|
|
|
|
|
if (!Array.isArray(toPrerender)) {
|
|
|
|
throw new Error(
|
2020-11-11 00:53:08 +01:00
|
|
|
`Invalid \`paths\` value returned from getStaticPaths in ${page}.\n` +
|
2020-02-24 22:36:59 +01:00
|
|
|
`\`paths\` must be an array of strings or objects of shape { params: [key: string]: string }`
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
2020-05-18 21:24:37 +02:00
|
|
|
toPrerender.forEach((entry) => {
|
2020-02-24 22:36:59 +01:00
|
|
|
// For a string-provided path, we must make sure it matches the dynamic
|
|
|
|
// route.
|
|
|
|
if (typeof entry === 'string') {
|
2020-06-30 06:06:39 +02:00
|
|
|
entry = removePathTrailingSlash(entry)
|
2020-10-07 23:11:01 +02:00
|
|
|
|
|
|
|
const localePathResult = normalizeLocalePath(entry, locales)
|
|
|
|
let cleanedEntry = entry
|
|
|
|
|
|
|
|
if (localePathResult.detectedLocale) {
|
2022-03-24 22:49:38 +01:00
|
|
|
cleanedEntry = entry.slice(localePathResult.detectedLocale.length + 1)
|
2020-10-07 23:11:01 +02:00
|
|
|
} else if (defaultLocale) {
|
|
|
|
entry = `/${defaultLocale}${entry}`
|
|
|
|
}
|
|
|
|
|
|
|
|
const result = _routeMatcher(cleanedEntry)
|
2020-02-24 22:36:59 +01:00
|
|
|
if (!result) {
|
|
|
|
throw new Error(
|
2021-01-06 11:27:32 +01:00
|
|
|
`The provided path \`${cleanedEntry}\` does not match the page: \`${page}\`.`
|
2020-02-24 22:36:59 +01:00
|
|
|
)
|
|
|
|
}
|
|
|
|
|
2020-12-28 21:08:58 +01:00
|
|
|
// If leveraging the string paths variant the entry should already be
|
|
|
|
// encoded so we decode the segments ensuring we only escape path
|
|
|
|
// delimiters
|
|
|
|
prerenderPaths.add(
|
|
|
|
entry
|
|
|
|
.split('/')
|
|
|
|
.map((segment) =>
|
|
|
|
escapePathDelimiters(decodeURIComponent(segment), true)
|
|
|
|
)
|
|
|
|
.join('/')
|
|
|
|
)
|
|
|
|
encodedPrerenderPaths.add(entry)
|
2020-02-24 22:36:59 +01:00
|
|
|
}
|
|
|
|
// For the object-provided path, we must make sure it specifies all
|
|
|
|
// required keys.
|
|
|
|
else {
|
2020-10-07 23:11:01 +02:00
|
|
|
const invalidKeys = Object.keys(entry).filter(
|
|
|
|
(key) => key !== 'params' && key !== 'locale'
|
|
|
|
)
|
|
|
|
|
2020-02-24 22:36:59 +01:00
|
|
|
if (invalidKeys.length) {
|
|
|
|
throw new Error(
|
2020-02-27 18:57:39 +01:00
|
|
|
`Additional keys were returned from \`getStaticPaths\` in page "${page}". ` +
|
2020-02-24 22:36:59 +01:00
|
|
|
`URL Parameters intended for this dynamic route must be nested under the \`params\` key, i.e.:` +
|
|
|
|
`\n\n\treturn { params: { ${_validParamKeys
|
2020-05-18 21:24:37 +02:00
|
|
|
.map((k) => `${k}: ...`)
|
2020-02-24 22:36:59 +01:00
|
|
|
.join(', ')} } }` +
|
|
|
|
`\n\nKeys that need to be moved: ${invalidKeys.join(', ')}.\n`
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
const { params = {} } = entry
|
|
|
|
let builtPage = page
|
2020-12-28 21:08:58 +01:00
|
|
|
let encodedBuiltPage = page
|
|
|
|
|
2020-05-18 21:24:37 +02:00
|
|
|
_validParamKeys.forEach((validParamKey) => {
|
2020-06-01 19:08:34 +02:00
|
|
|
const { repeat, optional } = _routeRegex.groups[validParamKey]
|
|
|
|
let paramValue = params[validParamKey]
|
|
|
|
if (
|
|
|
|
optional &&
|
|
|
|
params.hasOwnProperty(validParamKey) &&
|
|
|
|
(paramValue === null ||
|
|
|
|
paramValue === undefined ||
|
|
|
|
(paramValue as any) === false)
|
|
|
|
) {
|
|
|
|
paramValue = []
|
|
|
|
}
|
2020-02-24 22:36:59 +01:00
|
|
|
if (
|
|
|
|
(repeat && !Array.isArray(paramValue)) ||
|
|
|
|
(!repeat && typeof paramValue !== 'string')
|
|
|
|
) {
|
|
|
|
throw new Error(
|
|
|
|
`A required parameter (${validParamKey}) was not provided as ${
|
|
|
|
repeat ? 'an array' : 'a string'
|
2020-02-27 18:57:39 +01:00
|
|
|
} in getStaticPaths for ${page}`
|
2020-02-24 22:36:59 +01:00
|
|
|
)
|
|
|
|
}
|
2020-06-01 19:08:34 +02:00
|
|
|
let replaced = `[${repeat ? '...' : ''}${validParamKey}]`
|
|
|
|
if (optional) {
|
|
|
|
replaced = `[${replaced}]`
|
|
|
|
}
|
|
|
|
builtPage = builtPage
|
|
|
|
.replace(
|
|
|
|
replaced,
|
|
|
|
repeat
|
2020-12-28 21:08:58 +01:00
|
|
|
? (paramValue as string[])
|
|
|
|
.map((segment) => escapePathDelimiters(segment, true))
|
|
|
|
.join('/')
|
|
|
|
: escapePathDelimiters(paramValue as string, true)
|
|
|
|
)
|
|
|
|
.replace(/(?!^)\/$/, '')
|
|
|
|
|
|
|
|
encodedBuiltPage = encodedBuiltPage
|
|
|
|
.replace(
|
|
|
|
replaced,
|
|
|
|
repeat
|
|
|
|
? (paramValue as string[]).map(encodeURIComponent).join('/')
|
|
|
|
: encodeURIComponent(paramValue as string)
|
2020-06-01 19:08:34 +02:00
|
|
|
)
|
|
|
|
.replace(/(?!^)\/$/, '')
|
2020-02-24 22:36:59 +01:00
|
|
|
})
|
|
|
|
|
2020-10-07 23:11:01 +02:00
|
|
|
if (entry.locale && !locales?.includes(entry.locale)) {
|
|
|
|
throw new Error(
|
2021-10-22 01:04:40 +02:00
|
|
|
`Invalid locale returned from getStaticPaths for ${page}, the locale ${entry.locale} is not specified in ${configFileName}`
|
2020-10-07 23:11:01 +02:00
|
|
|
)
|
|
|
|
}
|
|
|
|
const curLocale = entry.locale || defaultLocale || ''
|
|
|
|
|
2020-12-28 21:08:58 +01:00
|
|
|
prerenderPaths.add(
|
2020-11-12 19:50:32 +01:00
|
|
|
`${curLocale ? `/${curLocale}` : ''}${
|
|
|
|
curLocale && builtPage === '/' ? '' : builtPage
|
|
|
|
}`
|
|
|
|
)
|
2020-12-28 21:08:58 +01:00
|
|
|
encodedPrerenderPaths.add(
|
|
|
|
`${curLocale ? `/${curLocale}` : ''}${
|
|
|
|
curLocale && encodedBuiltPage === '/' ? '' : encodedBuiltPage
|
|
|
|
}`
|
|
|
|
)
|
2020-02-24 22:36:59 +01:00
|
|
|
}
|
|
|
|
})
|
|
|
|
|
2020-12-28 21:08:58 +01:00
|
|
|
return {
|
|
|
|
paths: [...prerenderPaths],
|
|
|
|
fallback: staticPathsResult.fallback,
|
|
|
|
encodedPaths: [...encodedPrerenderPaths],
|
|
|
|
}
|
2020-02-24 22:36:59 +01:00
|
|
|
}
|
|
|
|
|
2019-09-24 10:50:04 +02:00
|
|
|
export async function isPageStatic(
|
|
|
|
page: string,
|
2021-03-03 20:20:48 +01:00
|
|
|
distDir: string,
|
|
|
|
serverless: boolean,
|
2021-10-22 01:04:40 +02:00
|
|
|
configFileName: string,
|
2020-10-07 23:11:01 +02:00
|
|
|
runtimeEnvConfig: any,
|
2021-08-03 02:38:42 +02:00
|
|
|
httpAgentOptions: NextConfigComplete['httpAgentOptions'],
|
2020-10-07 23:11:01 +02:00
|
|
|
locales?: string[],
|
2021-01-10 02:12:13 +01:00
|
|
|
defaultLocale?: string,
|
Telemetry-compatible tracing (#22713)
A number of changes here. I recommend viewing the diff with the <a href="?w=1">whitespace flag enabled</a>.
- OpenTelemetry is replaced with a custom and lightweight tracing solution.
- Three trace targets are currently supported: console, Zipkin, and NextJS.
- Tracing is now governed by environment variables rather than `--require instrument.js`.
+ `TRACE_TARGET`: one of `CONSOLE`, `ZIPKIN`, or `TELEMETRY`; defaults to `TELEMETRY` if unset or invalid.
+ `TRACE_ID`: an 8-byte hex-encoded value used as the Zipkin trace ID; if not provided, this value will be randomly generated and passed down to subprocesses.
Other sundry:
- I'm missing something, probably a setup step, with the Zipkin target. Traces are captured successfully, but you have to manually enter the Trace ID in order to view the trace - it doesn't show up in queries.
- I'm generally unhappy with [this commit](https://github.com/vercel/next.js/pull/22713/commits/235cedcb3ead76b630b4c8aa695f904489da2831). It is... untidy to provide a telemetry object via `setGlobal`, but I don't have a ready alternative. Is `distDir` strictly required when creating a new Telemetry object? I didn't dig too deep here.
As noted, there are a lot of changes, so it'd be great if a reviewer could:
- [ ] pull down the branch and try to break it
- [ ] check the Zipkin traces and identify possible regressions in the functionality
Closes #22570
Fixes #22574
2021-03-10 22:00:20 +01:00
|
|
|
parentId?: any
|
2019-09-24 10:50:04 +02:00
|
|
|
): Promise<{
|
2020-01-25 00:41:00 +01:00
|
|
|
isStatic?: boolean
|
2020-04-01 10:24:44 +02:00
|
|
|
isAmpOnly?: boolean
|
2019-09-24 10:50:04 +02:00
|
|
|
isHybridAmp?: boolean
|
2020-01-27 23:50:59 +01:00
|
|
|
hasServerProps?: boolean
|
2020-01-25 00:41:00 +01:00
|
|
|
hasStaticProps?: boolean
|
2020-12-28 21:08:58 +01:00
|
|
|
prerenderRoutes?: string[]
|
|
|
|
encodedPrerenderRoutes?: string[]
|
|
|
|
prerenderFallback?: boolean | 'blocking'
|
2020-11-11 16:46:48 +01:00
|
|
|
isNextImageImported?: boolean
|
2021-08-16 21:29:11 +02:00
|
|
|
traceIncludes?: string[]
|
|
|
|
traceExcludes?: string[]
|
2019-09-24 10:50:04 +02:00
|
|
|
}> {
|
Telemetry-compatible tracing (#22713)
A number of changes here. I recommend viewing the diff with the <a href="?w=1">whitespace flag enabled</a>.
- OpenTelemetry is replaced with a custom and lightweight tracing solution.
- Three trace targets are currently supported: console, Zipkin, and NextJS.
- Tracing is now governed by environment variables rather than `--require instrument.js`.
+ `TRACE_TARGET`: one of `CONSOLE`, `ZIPKIN`, or `TELEMETRY`; defaults to `TELEMETRY` if unset or invalid.
+ `TRACE_ID`: an 8-byte hex-encoded value used as the Zipkin trace ID; if not provided, this value will be randomly generated and passed down to subprocesses.
Other sundry:
- I'm missing something, probably a setup step, with the Zipkin target. Traces are captured successfully, but you have to manually enter the Trace ID in order to view the trace - it doesn't show up in queries.
- I'm generally unhappy with [this commit](https://github.com/vercel/next.js/pull/22713/commits/235cedcb3ead76b630b4c8aa695f904489da2831). It is... untidy to provide a telemetry object via `setGlobal`, but I don't have a ready alternative. Is `distDir` strictly required when creating a new Telemetry object? I didn't dig too deep here.
As noted, there are a lot of changes, so it'd be great if a reviewer could:
- [ ] pull down the branch and try to break it
- [ ] check the Zipkin traces and identify possible regressions in the functionality
Closes #22570
Fixes #22574
2021-03-10 22:00:20 +01:00
|
|
|
const isPageStaticSpan = trace('is-page-static-utils', parentId)
|
|
|
|
return isPageStaticSpan.traceAsyncFn(async () => {
|
|
|
|
try {
|
2021-06-30 11:43:31 +02:00
|
|
|
require('../shared/lib/runtime-config').setConfig(runtimeEnvConfig)
|
2021-08-03 02:38:42 +02:00
|
|
|
setHttpAgentOptions(httpAgentOptions)
|
2021-10-26 18:50:56 +02:00
|
|
|
|
2021-09-21 19:17:16 +02:00
|
|
|
const mod = await loadComponents(distDir, page, serverless)
|
|
|
|
const Comp = mod.Component
|
Telemetry-compatible tracing (#22713)
A number of changes here. I recommend viewing the diff with the <a href="?w=1">whitespace flag enabled</a>.
- OpenTelemetry is replaced with a custom and lightweight tracing solution.
- Three trace targets are currently supported: console, Zipkin, and NextJS.
- Tracing is now governed by environment variables rather than `--require instrument.js`.
+ `TRACE_TARGET`: one of `CONSOLE`, `ZIPKIN`, or `TELEMETRY`; defaults to `TELEMETRY` if unset or invalid.
+ `TRACE_ID`: an 8-byte hex-encoded value used as the Zipkin trace ID; if not provided, this value will be randomly generated and passed down to subprocesses.
Other sundry:
- I'm missing something, probably a setup step, with the Zipkin target. Traces are captured successfully, but you have to manually enter the Trace ID in order to view the trace - it doesn't show up in queries.
- I'm generally unhappy with [this commit](https://github.com/vercel/next.js/pull/22713/commits/235cedcb3ead76b630b4c8aa695f904489da2831). It is... untidy to provide a telemetry object via `setGlobal`, but I don't have a ready alternative. Is `distDir` strictly required when creating a new Telemetry object? I didn't dig too deep here.
As noted, there are a lot of changes, so it'd be great if a reviewer could:
- [ ] pull down the branch and try to break it
- [ ] check the Zipkin traces and identify possible regressions in the functionality
Closes #22570
Fixes #22574
2021-03-10 22:00:20 +01:00
|
|
|
|
|
|
|
if (!Comp || !isValidElementType(Comp) || typeof Comp === 'string') {
|
|
|
|
throw new Error('INVALID_DEFAULT_EXPORT')
|
|
|
|
}
|
|
|
|
|
|
|
|
const hasGetInitialProps = !!(Comp as any).getInitialProps
|
2021-09-21 19:17:16 +02:00
|
|
|
const hasStaticProps = !!mod.getStaticProps
|
|
|
|
const hasStaticPaths = !!mod.getStaticPaths
|
|
|
|
const hasServerProps = !!mod.getServerSideProps
|
|
|
|
const hasLegacyServerProps = !!(await mod.ComponentMod
|
|
|
|
.unstable_getServerProps)
|
|
|
|
const hasLegacyStaticProps = !!(await mod.ComponentMod
|
|
|
|
.unstable_getStaticProps)
|
|
|
|
const hasLegacyStaticPaths = !!(await mod.ComponentMod
|
|
|
|
.unstable_getStaticPaths)
|
|
|
|
const hasLegacyStaticParams = !!(await mod.ComponentMod
|
|
|
|
.unstable_getStaticParams)
|
Telemetry-compatible tracing (#22713)
A number of changes here. I recommend viewing the diff with the <a href="?w=1">whitespace flag enabled</a>.
- OpenTelemetry is replaced with a custom and lightweight tracing solution.
- Three trace targets are currently supported: console, Zipkin, and NextJS.
- Tracing is now governed by environment variables rather than `--require instrument.js`.
+ `TRACE_TARGET`: one of `CONSOLE`, `ZIPKIN`, or `TELEMETRY`; defaults to `TELEMETRY` if unset or invalid.
+ `TRACE_ID`: an 8-byte hex-encoded value used as the Zipkin trace ID; if not provided, this value will be randomly generated and passed down to subprocesses.
Other sundry:
- I'm missing something, probably a setup step, with the Zipkin target. Traces are captured successfully, but you have to manually enter the Trace ID in order to view the trace - it doesn't show up in queries.
- I'm generally unhappy with [this commit](https://github.com/vercel/next.js/pull/22713/commits/235cedcb3ead76b630b4c8aa695f904489da2831). It is... untidy to provide a telemetry object via `setGlobal`, but I don't have a ready alternative. Is `distDir` strictly required when creating a new Telemetry object? I didn't dig too deep here.
As noted, there are a lot of changes, so it'd be great if a reviewer could:
- [ ] pull down the branch and try to break it
- [ ] check the Zipkin traces and identify possible regressions in the functionality
Closes #22570
Fixes #22574
2021-03-10 22:00:20 +01:00
|
|
|
|
|
|
|
if (hasLegacyStaticParams) {
|
|
|
|
throw new Error(
|
|
|
|
`unstable_getStaticParams was replaced with getStaticPaths. Please update your code.`
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
if (hasLegacyStaticPaths) {
|
|
|
|
throw new Error(
|
|
|
|
`unstable_getStaticPaths was replaced with getStaticPaths. Please update your code.`
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
if (hasLegacyStaticProps) {
|
|
|
|
throw new Error(
|
|
|
|
`unstable_getStaticProps was replaced with getStaticProps. Please update your code.`
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
if (hasLegacyServerProps) {
|
|
|
|
throw new Error(
|
|
|
|
`unstable_getServerProps was replaced with getServerSideProps. Please update your code.`
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
// A page cannot be prerendered _and_ define a data requirement. That's
|
|
|
|
// contradictory!
|
|
|
|
if (hasGetInitialProps && hasStaticProps) {
|
|
|
|
throw new Error(SSG_GET_INITIAL_PROPS_CONFLICT)
|
|
|
|
}
|
|
|
|
|
|
|
|
if (hasGetInitialProps && hasServerProps) {
|
|
|
|
throw new Error(SERVER_PROPS_GET_INIT_PROPS_CONFLICT)
|
|
|
|
}
|
|
|
|
|
|
|
|
if (hasStaticProps && hasServerProps) {
|
|
|
|
throw new Error(SERVER_PROPS_SSG_CONFLICT)
|
|
|
|
}
|
|
|
|
|
|
|
|
const pageIsDynamic = isDynamicRoute(page)
|
|
|
|
// A page cannot have static parameters if it is not a dynamic page.
|
|
|
|
if (hasStaticProps && hasStaticPaths && !pageIsDynamic) {
|
|
|
|
throw new Error(
|
|
|
|
`getStaticPaths can only be used with dynamic pages, not '${page}'.` +
|
|
|
|
`\nLearn more: https://nextjs.org/docs/routing/dynamic-routes`
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
if (hasStaticProps && pageIsDynamic && !hasStaticPaths) {
|
|
|
|
throw new Error(
|
|
|
|
`getStaticPaths is required for dynamic SSG pages and is missing for '${page}'.` +
|
2021-03-29 10:25:00 +02:00
|
|
|
`\nRead more: https://nextjs.org/docs/messages/invalid-getstaticpaths-value`
|
Telemetry-compatible tracing (#22713)
A number of changes here. I recommend viewing the diff with the <a href="?w=1">whitespace flag enabled</a>.
- OpenTelemetry is replaced with a custom and lightweight tracing solution.
- Three trace targets are currently supported: console, Zipkin, and NextJS.
- Tracing is now governed by environment variables rather than `--require instrument.js`.
+ `TRACE_TARGET`: one of `CONSOLE`, `ZIPKIN`, or `TELEMETRY`; defaults to `TELEMETRY` if unset or invalid.
+ `TRACE_ID`: an 8-byte hex-encoded value used as the Zipkin trace ID; if not provided, this value will be randomly generated and passed down to subprocesses.
Other sundry:
- I'm missing something, probably a setup step, with the Zipkin target. Traces are captured successfully, but you have to manually enter the Trace ID in order to view the trace - it doesn't show up in queries.
- I'm generally unhappy with [this commit](https://github.com/vercel/next.js/pull/22713/commits/235cedcb3ead76b630b4c8aa695f904489da2831). It is... untidy to provide a telemetry object via `setGlobal`, but I don't have a ready alternative. Is `distDir` strictly required when creating a new Telemetry object? I didn't dig too deep here.
As noted, there are a lot of changes, so it'd be great if a reviewer could:
- [ ] pull down the branch and try to break it
- [ ] check the Zipkin traces and identify possible regressions in the functionality
Closes #22570
Fixes #22574
2021-03-10 22:00:20 +01:00
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
let prerenderRoutes: Array<string> | undefined
|
|
|
|
let encodedPrerenderRoutes: Array<string> | undefined
|
|
|
|
let prerenderFallback: boolean | 'blocking' | undefined
|
|
|
|
if (hasStaticProps && hasStaticPaths) {
|
|
|
|
;({
|
|
|
|
paths: prerenderRoutes,
|
|
|
|
fallback: prerenderFallback,
|
|
|
|
encodedPaths: encodedPrerenderRoutes,
|
|
|
|
} = await buildStaticPaths(
|
|
|
|
page,
|
2021-09-21 19:17:16 +02:00
|
|
|
mod.getStaticPaths!,
|
2021-10-22 01:04:40 +02:00
|
|
|
configFileName,
|
Telemetry-compatible tracing (#22713)
A number of changes here. I recommend viewing the diff with the <a href="?w=1">whitespace flag enabled</a>.
- OpenTelemetry is replaced with a custom and lightweight tracing solution.
- Three trace targets are currently supported: console, Zipkin, and NextJS.
- Tracing is now governed by environment variables rather than `--require instrument.js`.
+ `TRACE_TARGET`: one of `CONSOLE`, `ZIPKIN`, or `TELEMETRY`; defaults to `TELEMETRY` if unset or invalid.
+ `TRACE_ID`: an 8-byte hex-encoded value used as the Zipkin trace ID; if not provided, this value will be randomly generated and passed down to subprocesses.
Other sundry:
- I'm missing something, probably a setup step, with the Zipkin target. Traces are captured successfully, but you have to manually enter the Trace ID in order to view the trace - it doesn't show up in queries.
- I'm generally unhappy with [this commit](https://github.com/vercel/next.js/pull/22713/commits/235cedcb3ead76b630b4c8aa695f904489da2831). It is... untidy to provide a telemetry object via `setGlobal`, but I don't have a ready alternative. Is `distDir` strictly required when creating a new Telemetry object? I didn't dig too deep here.
As noted, there are a lot of changes, so it'd be great if a reviewer could:
- [ ] pull down the branch and try to break it
- [ ] check the Zipkin traces and identify possible regressions in the functionality
Closes #22570
Fixes #22574
2021-03-10 22:00:20 +01:00
|
|
|
locales,
|
|
|
|
defaultLocale
|
|
|
|
))
|
|
|
|
}
|
|
|
|
|
|
|
|
const isNextImageImported = (global as any).__NEXT_IMAGE_IMPORTED
|
2021-09-21 19:17:16 +02:00
|
|
|
const config: PageConfig = mod.pageConfig
|
Telemetry-compatible tracing (#22713)
A number of changes here. I recommend viewing the diff with the <a href="?w=1">whitespace flag enabled</a>.
- OpenTelemetry is replaced with a custom and lightweight tracing solution.
- Three trace targets are currently supported: console, Zipkin, and NextJS.
- Tracing is now governed by environment variables rather than `--require instrument.js`.
+ `TRACE_TARGET`: one of `CONSOLE`, `ZIPKIN`, or `TELEMETRY`; defaults to `TELEMETRY` if unset or invalid.
+ `TRACE_ID`: an 8-byte hex-encoded value used as the Zipkin trace ID; if not provided, this value will be randomly generated and passed down to subprocesses.
Other sundry:
- I'm missing something, probably a setup step, with the Zipkin target. Traces are captured successfully, but you have to manually enter the Trace ID in order to view the trace - it doesn't show up in queries.
- I'm generally unhappy with [this commit](https://github.com/vercel/next.js/pull/22713/commits/235cedcb3ead76b630b4c8aa695f904489da2831). It is... untidy to provide a telemetry object via `setGlobal`, but I don't have a ready alternative. Is `distDir` strictly required when creating a new Telemetry object? I didn't dig too deep here.
As noted, there are a lot of changes, so it'd be great if a reviewer could:
- [ ] pull down the branch and try to break it
- [ ] check the Zipkin traces and identify possible regressions in the functionality
Closes #22570
Fixes #22574
2021-03-10 22:00:20 +01:00
|
|
|
return {
|
2022-04-01 18:13:38 +02:00
|
|
|
isStatic: !hasStaticProps && !hasGetInitialProps && !hasServerProps,
|
Telemetry-compatible tracing (#22713)
A number of changes here. I recommend viewing the diff with the <a href="?w=1">whitespace flag enabled</a>.
- OpenTelemetry is replaced with a custom and lightweight tracing solution.
- Three trace targets are currently supported: console, Zipkin, and NextJS.
- Tracing is now governed by environment variables rather than `--require instrument.js`.
+ `TRACE_TARGET`: one of `CONSOLE`, `ZIPKIN`, or `TELEMETRY`; defaults to `TELEMETRY` if unset or invalid.
+ `TRACE_ID`: an 8-byte hex-encoded value used as the Zipkin trace ID; if not provided, this value will be randomly generated and passed down to subprocesses.
Other sundry:
- I'm missing something, probably a setup step, with the Zipkin target. Traces are captured successfully, but you have to manually enter the Trace ID in order to view the trace - it doesn't show up in queries.
- I'm generally unhappy with [this commit](https://github.com/vercel/next.js/pull/22713/commits/235cedcb3ead76b630b4c8aa695f904489da2831). It is... untidy to provide a telemetry object via `setGlobal`, but I don't have a ready alternative. Is `distDir` strictly required when creating a new Telemetry object? I didn't dig too deep here.
As noted, there are a lot of changes, so it'd be great if a reviewer could:
- [ ] pull down the branch and try to break it
- [ ] check the Zipkin traces and identify possible regressions in the functionality
Closes #22570
Fixes #22574
2021-03-10 22:00:20 +01:00
|
|
|
isHybridAmp: config.amp === 'hybrid',
|
|
|
|
isAmpOnly: config.amp === true,
|
|
|
|
prerenderRoutes,
|
|
|
|
prerenderFallback,
|
|
|
|
encodedPrerenderRoutes,
|
|
|
|
hasStaticProps,
|
|
|
|
hasServerProps,
|
|
|
|
isNextImageImported,
|
2021-08-16 21:29:11 +02:00
|
|
|
traceIncludes: config.unstable_includeFiles || [],
|
|
|
|
traceExcludes: config.unstable_excludeFiles || [],
|
Telemetry-compatible tracing (#22713)
A number of changes here. I recommend viewing the diff with the <a href="?w=1">whitespace flag enabled</a>.
- OpenTelemetry is replaced with a custom and lightweight tracing solution.
- Three trace targets are currently supported: console, Zipkin, and NextJS.
- Tracing is now governed by environment variables rather than `--require instrument.js`.
+ `TRACE_TARGET`: one of `CONSOLE`, `ZIPKIN`, or `TELEMETRY`; defaults to `TELEMETRY` if unset or invalid.
+ `TRACE_ID`: an 8-byte hex-encoded value used as the Zipkin trace ID; if not provided, this value will be randomly generated and passed down to subprocesses.
Other sundry:
- I'm missing something, probably a setup step, with the Zipkin target. Traces are captured successfully, but you have to manually enter the Trace ID in order to view the trace - it doesn't show up in queries.
- I'm generally unhappy with [this commit](https://github.com/vercel/next.js/pull/22713/commits/235cedcb3ead76b630b4c8aa695f904489da2831). It is... untidy to provide a telemetry object via `setGlobal`, but I don't have a ready alternative. Is `distDir` strictly required when creating a new Telemetry object? I didn't dig too deep here.
As noted, there are a lot of changes, so it'd be great if a reviewer could:
- [ ] pull down the branch and try to break it
- [ ] check the Zipkin traces and identify possible regressions in the functionality
Closes #22570
Fixes #22574
2021-03-10 22:00:20 +01:00
|
|
|
}
|
|
|
|
} catch (err) {
|
2021-09-16 18:06:57 +02:00
|
|
|
if (isError(err) && err.code === 'MODULE_NOT_FOUND') return {}
|
Telemetry-compatible tracing (#22713)
A number of changes here. I recommend viewing the diff with the <a href="?w=1">whitespace flag enabled</a>.
- OpenTelemetry is replaced with a custom and lightweight tracing solution.
- Three trace targets are currently supported: console, Zipkin, and NextJS.
- Tracing is now governed by environment variables rather than `--require instrument.js`.
+ `TRACE_TARGET`: one of `CONSOLE`, `ZIPKIN`, or `TELEMETRY`; defaults to `TELEMETRY` if unset or invalid.
+ `TRACE_ID`: an 8-byte hex-encoded value used as the Zipkin trace ID; if not provided, this value will be randomly generated and passed down to subprocesses.
Other sundry:
- I'm missing something, probably a setup step, with the Zipkin target. Traces are captured successfully, but you have to manually enter the Trace ID in order to view the trace - it doesn't show up in queries.
- I'm generally unhappy with [this commit](https://github.com/vercel/next.js/pull/22713/commits/235cedcb3ead76b630b4c8aa695f904489da2831). It is... untidy to provide a telemetry object via `setGlobal`, but I don't have a ready alternative. Is `distDir` strictly required when creating a new Telemetry object? I didn't dig too deep here.
As noted, there are a lot of changes, so it'd be great if a reviewer could:
- [ ] pull down the branch and try to break it
- [ ] check the Zipkin traces and identify possible regressions in the functionality
Closes #22570
Fixes #22574
2021-03-10 22:00:20 +01:00
|
|
|
throw err
|
2019-09-24 10:50:04 +02:00
|
|
|
}
|
Telemetry-compatible tracing (#22713)
A number of changes here. I recommend viewing the diff with the <a href="?w=1">whitespace flag enabled</a>.
- OpenTelemetry is replaced with a custom and lightweight tracing solution.
- Three trace targets are currently supported: console, Zipkin, and NextJS.
- Tracing is now governed by environment variables rather than `--require instrument.js`.
+ `TRACE_TARGET`: one of `CONSOLE`, `ZIPKIN`, or `TELEMETRY`; defaults to `TELEMETRY` if unset or invalid.
+ `TRACE_ID`: an 8-byte hex-encoded value used as the Zipkin trace ID; if not provided, this value will be randomly generated and passed down to subprocesses.
Other sundry:
- I'm missing something, probably a setup step, with the Zipkin target. Traces are captured successfully, but you have to manually enter the Trace ID in order to view the trace - it doesn't show up in queries.
- I'm generally unhappy with [this commit](https://github.com/vercel/next.js/pull/22713/commits/235cedcb3ead76b630b4c8aa695f904489da2831). It is... untidy to provide a telemetry object via `setGlobal`, but I don't have a ready alternative. Is `distDir` strictly required when creating a new Telemetry object? I didn't dig too deep here.
As noted, there are a lot of changes, so it'd be great if a reviewer could:
- [ ] pull down the branch and try to break it
- [ ] check the Zipkin traces and identify possible regressions in the functionality
Closes #22570
Fixes #22574
2021-03-10 22:00:20 +01:00
|
|
|
})
|
2019-05-22 18:36:53 +02:00
|
|
|
}
|
|
|
|
|
2020-10-14 11:55:42 +02:00
|
|
|
export async function hasCustomGetInitialProps(
|
2021-03-09 10:55:28 +01:00
|
|
|
page: string,
|
|
|
|
distDir: string,
|
|
|
|
isLikeServerless: boolean,
|
2020-05-27 07:31:57 +02:00
|
|
|
runtimeEnvConfig: any,
|
|
|
|
checkingApp: boolean
|
2020-10-14 11:55:42 +02:00
|
|
|
): Promise<boolean> {
|
2021-06-30 11:43:31 +02:00
|
|
|
require('../shared/lib/runtime-config').setConfig(runtimeEnvConfig)
|
2021-03-09 10:55:28 +01:00
|
|
|
|
|
|
|
const components = await loadComponents(distDir, page, isLikeServerless)
|
|
|
|
let mod = components.ComponentMod
|
2019-05-22 18:36:53 +02:00
|
|
|
|
2020-05-27 07:31:57 +02:00
|
|
|
if (checkingApp) {
|
2020-10-14 11:55:42 +02:00
|
|
|
mod = (await mod._app) || mod.default || mod
|
2019-05-22 18:36:53 +02:00
|
|
|
} else {
|
2020-05-27 07:31:57 +02:00
|
|
|
mod = mod.default || mod
|
2019-05-22 18:36:53 +02:00
|
|
|
}
|
2020-10-14 11:55:42 +02:00
|
|
|
mod = await mod
|
2019-05-22 18:36:53 +02:00
|
|
|
return mod.getInitialProps !== mod.origGetInitialProps
|
|
|
|
}
|
2020-05-20 20:44:39 +02:00
|
|
|
|
2021-03-09 10:55:28 +01:00
|
|
|
export async function getNamedExports(
|
|
|
|
page: string,
|
|
|
|
distDir: string,
|
|
|
|
isLikeServerless: boolean,
|
2020-05-20 20:44:39 +02:00
|
|
|
runtimeEnvConfig: any
|
2021-03-09 10:55:28 +01:00
|
|
|
): Promise<Array<string>> {
|
2021-06-30 11:43:31 +02:00
|
|
|
require('../shared/lib/runtime-config').setConfig(runtimeEnvConfig)
|
2021-03-09 10:55:28 +01:00
|
|
|
const components = await loadComponents(distDir, page, isLikeServerless)
|
|
|
|
let mod = components.ComponentMod
|
|
|
|
|
|
|
|
return Object.keys(mod)
|
2020-05-20 20:44:39 +02:00
|
|
|
}
|
2021-01-11 21:50:17 +01:00
|
|
|
|
|
|
|
export function detectConflictingPaths(
|
|
|
|
combinedPages: string[],
|
|
|
|
ssgPages: Set<string>,
|
|
|
|
additionalSsgPaths: Map<string, string[]>
|
|
|
|
) {
|
|
|
|
const conflictingPaths = new Map<
|
|
|
|
string,
|
|
|
|
Array<{
|
|
|
|
path: string
|
|
|
|
page: string
|
|
|
|
}>
|
|
|
|
>()
|
|
|
|
|
|
|
|
const dynamicSsgPages = [...ssgPages].filter((page) => isDynamicRoute(page))
|
|
|
|
|
|
|
|
additionalSsgPaths.forEach((paths, pathsPage) => {
|
|
|
|
paths.forEach((curPath) => {
|
|
|
|
const lowerPath = curPath.toLowerCase()
|
|
|
|
let conflictingPage = combinedPages.find(
|
|
|
|
(page) => page.toLowerCase() === lowerPath
|
|
|
|
)
|
|
|
|
|
|
|
|
if (conflictingPage) {
|
|
|
|
conflictingPaths.set(lowerPath, [
|
|
|
|
{ path: curPath, page: pathsPage },
|
|
|
|
{ path: conflictingPage, page: conflictingPage },
|
|
|
|
])
|
|
|
|
} else {
|
|
|
|
let conflictingPath: string | undefined
|
|
|
|
|
|
|
|
conflictingPage = dynamicSsgPages.find((page) => {
|
|
|
|
if (page === pathsPage) return false
|
|
|
|
|
|
|
|
conflictingPath = additionalSsgPaths
|
|
|
|
.get(page)
|
|
|
|
?.find((compPath) => compPath.toLowerCase() === lowerPath)
|
|
|
|
return conflictingPath
|
|
|
|
})
|
|
|
|
|
|
|
|
if (conflictingPage && conflictingPath) {
|
|
|
|
conflictingPaths.set(lowerPath, [
|
|
|
|
{ path: curPath, page: pathsPage },
|
|
|
|
{ path: conflictingPath, page: conflictingPage },
|
|
|
|
])
|
|
|
|
}
|
|
|
|
}
|
|
|
|
})
|
|
|
|
})
|
|
|
|
|
|
|
|
if (conflictingPaths.size > 0) {
|
|
|
|
let conflictingPathsOutput = ''
|
|
|
|
|
|
|
|
conflictingPaths.forEach((pathItems) => {
|
|
|
|
pathItems.forEach((pathItem, idx) => {
|
|
|
|
const isDynamic = pathItem.page !== pathItem.path
|
|
|
|
|
|
|
|
if (idx > 0) {
|
|
|
|
conflictingPathsOutput += 'conflicts with '
|
|
|
|
}
|
|
|
|
|
|
|
|
conflictingPathsOutput += `path: "${pathItem.path}"${
|
|
|
|
isDynamic ? ` from page: "${pathItem.page}" ` : ' '
|
|
|
|
}`
|
|
|
|
})
|
|
|
|
conflictingPathsOutput += '\n'
|
|
|
|
})
|
|
|
|
|
|
|
|
Log.error(
|
2021-10-06 05:22:46 +02:00
|
|
|
'Conflicting paths returned from getStaticPaths, paths must be unique per page.\n' +
|
2021-03-29 10:25:00 +02:00
|
|
|
'See more info here: https://nextjs.org/docs/messages/conflicting-ssg-paths\n\n' +
|
2021-01-11 21:50:17 +01:00
|
|
|
conflictingPathsOutput
|
|
|
|
)
|
|
|
|
process.exit(1)
|
|
|
|
}
|
|
|
|
}
|
2021-02-17 23:52:43 +01:00
|
|
|
|
2022-04-27 11:50:29 +02:00
|
|
|
/**
|
|
|
|
* With RSC we automatically add .server and .client to page extensions. This
|
|
|
|
* function allows to remove them for cases where we just need to strip out
|
|
|
|
* the actual extension keeping the .server and .client.
|
|
|
|
*/
|
|
|
|
export function withoutRSCExtensions(pageExtensions: string[]): string[] {
|
2021-10-26 18:50:56 +02:00
|
|
|
return pageExtensions.filter(
|
|
|
|
(ext) => !ext.startsWith('client.') && !ext.startsWith('server.')
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
export function isFlightPage(
|
|
|
|
nextConfig: NextConfigComplete,
|
2022-02-24 23:19:53 +01:00
|
|
|
filePath: string
|
2021-10-26 18:50:56 +02:00
|
|
|
): boolean {
|
2022-03-08 21:55:14 +01:00
|
|
|
if (!nextConfig.experimental.serverComponents) {
|
2021-10-26 18:50:56 +02:00
|
|
|
return false
|
2022-02-24 23:19:53 +01:00
|
|
|
}
|
2021-10-26 18:50:56 +02:00
|
|
|
|
2022-04-27 11:50:29 +02:00
|
|
|
const rawPageExtensions = withoutRSCExtensions(
|
2021-10-26 18:50:56 +02:00
|
|
|
nextConfig.pageExtensions || []
|
|
|
|
)
|
2022-02-24 23:19:53 +01:00
|
|
|
return rawPageExtensions.some((ext) => {
|
|
|
|
return filePath.endsWith(`.server.${ext}`)
|
2021-10-26 18:50:56 +02:00
|
|
|
})
|
|
|
|
}
|
2021-11-02 22:03:29 +01:00
|
|
|
|
|
|
|
export function getUnresolvedModuleFromError(
|
|
|
|
error: string
|
|
|
|
): string | undefined {
|
|
|
|
const moduleErrorRegex = new RegExp(
|
|
|
|
`Module not found: Can't resolve '(\\w+)'`
|
|
|
|
)
|
|
|
|
const [, moduleName] = error.match(moduleErrorRegex) || []
|
|
|
|
return builtinModules.find((item: string) => item === moduleName)
|
|
|
|
}
|
2021-11-06 12:27:40 +01:00
|
|
|
|
2021-11-09 18:03:20 +01:00
|
|
|
export async function copyTracedFiles(
|
|
|
|
dir: string,
|
|
|
|
distDir: string,
|
|
|
|
pageKeys: string[],
|
|
|
|
tracingRoot: string,
|
2022-01-04 01:47:18 +01:00
|
|
|
serverConfig: { [key: string]: any },
|
|
|
|
middlewareManifest: MiddlewareManifest
|
2021-11-09 18:03:20 +01:00
|
|
|
) {
|
|
|
|
const outputPath = path.join(distDir, 'standalone')
|
|
|
|
const copiedFiles = new Set()
|
|
|
|
await recursiveDelete(outputPath)
|
|
|
|
|
|
|
|
async function handleTraceFiles(traceFilePath: string) {
|
|
|
|
const traceData = JSON.parse(await fs.readFile(traceFilePath, 'utf8')) as {
|
|
|
|
files: string[]
|
|
|
|
}
|
|
|
|
const copySema = new Sema(10, { capacity: traceData.files.length })
|
|
|
|
const traceFileDir = path.dirname(traceFilePath)
|
|
|
|
|
|
|
|
await Promise.all(
|
|
|
|
traceData.files.map(async (relativeFile) => {
|
|
|
|
await copySema.acquire()
|
|
|
|
|
|
|
|
const tracedFilePath = path.join(traceFileDir, relativeFile)
|
|
|
|
const fileOutputPath = path.join(
|
|
|
|
outputPath,
|
|
|
|
path.relative(tracingRoot, tracedFilePath)
|
|
|
|
)
|
|
|
|
|
|
|
|
if (!copiedFiles.has(fileOutputPath)) {
|
|
|
|
copiedFiles.add(fileOutputPath)
|
|
|
|
|
|
|
|
await fs.mkdir(path.dirname(fileOutputPath), { recursive: true })
|
|
|
|
const symlink = await fs.readlink(tracedFilePath).catch(() => null)
|
|
|
|
|
|
|
|
if (symlink) {
|
|
|
|
console.log('symlink', path.relative(tracingRoot, symlink))
|
2022-04-15 14:35:47 +02:00
|
|
|
await fs.symlink(symlink, fileOutputPath)
|
2021-11-09 18:03:20 +01:00
|
|
|
} else {
|
|
|
|
await fs.copyFile(tracedFilePath, fileOutputPath)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
await copySema.release()
|
|
|
|
})
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
for (const page of pageKeys) {
|
2022-01-04 01:47:18 +01:00
|
|
|
if (MIDDLEWARE_ROUTE.test(page)) {
|
|
|
|
const { files } =
|
2022-01-06 17:20:43 +01:00
|
|
|
middlewareManifest.middleware[page.replace(/\/_middleware$/, '') || '/']
|
2022-01-04 01:47:18 +01:00
|
|
|
|
|
|
|
for (const file of files) {
|
|
|
|
const originalPath = path.join(distDir, file)
|
|
|
|
const fileOutputPath = path.join(
|
|
|
|
outputPath,
|
|
|
|
path.relative(tracingRoot, distDir),
|
|
|
|
file
|
|
|
|
)
|
|
|
|
await fs.mkdir(path.dirname(fileOutputPath), { recursive: true })
|
|
|
|
await fs.copyFile(originalPath, fileOutputPath)
|
|
|
|
}
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
2021-11-09 18:03:20 +01:00
|
|
|
const pageFile = path.join(
|
|
|
|
distDir,
|
|
|
|
'server',
|
|
|
|
'pages',
|
|
|
|
`${normalizePagePath(page)}.js`
|
|
|
|
)
|
|
|
|
const pageTraceFile = `${pageFile}.nft.json`
|
|
|
|
await handleTraceFiles(pageTraceFile)
|
|
|
|
}
|
|
|
|
await handleTraceFiles(path.join(distDir, 'next-server.js.nft.json'))
|
|
|
|
const serverOutputPath = path.join(
|
|
|
|
outputPath,
|
|
|
|
path.relative(tracingRoot, dir),
|
|
|
|
'server.js'
|
|
|
|
)
|
|
|
|
await fs.writeFile(
|
|
|
|
serverOutputPath,
|
|
|
|
`
|
|
|
|
process.env.NODE_ENV = 'production'
|
|
|
|
process.chdir(__dirname)
|
|
|
|
const NextServer = require('next/dist/server/next-server').default
|
|
|
|
const http = require('http')
|
|
|
|
const path = require('path')
|
|
|
|
|
2022-02-10 12:13:27 +01:00
|
|
|
// Make sure commands gracefully respect termination signals (e.g. from Docker)
|
|
|
|
process.on('SIGTERM', () => process.exit(0))
|
|
|
|
process.on('SIGINT', () => process.exit(0))
|
|
|
|
|
2022-01-04 01:47:18 +01:00
|
|
|
let handler
|
2021-11-09 18:03:20 +01:00
|
|
|
|
|
|
|
const server = http.createServer(async (req, res) => {
|
|
|
|
try {
|
|
|
|
await handler(req, res)
|
|
|
|
} catch (err) {
|
|
|
|
console.error(err);
|
|
|
|
res.statusCode = 500
|
|
|
|
res.end('internal server error')
|
|
|
|
}
|
|
|
|
})
|
2022-01-04 01:47:18 +01:00
|
|
|
const currentPort = parseInt(process.env.PORT, 10) || 3000
|
|
|
|
|
2021-11-09 18:03:20 +01:00
|
|
|
server.listen(currentPort, (err) => {
|
|
|
|
if (err) {
|
|
|
|
console.error("Failed to start server", err)
|
|
|
|
process.exit(1)
|
|
|
|
}
|
2022-01-04 01:47:18 +01:00
|
|
|
const addr = server.address()
|
|
|
|
const nextServer = new NextServer({
|
|
|
|
hostname: 'localhost',
|
|
|
|
port: currentPort,
|
|
|
|
dir: path.join(__dirname),
|
|
|
|
dev: false,
|
|
|
|
conf: ${JSON.stringify({
|
|
|
|
...serverConfig,
|
|
|
|
distDir: `./${path.relative(dir, distDir)}`,
|
|
|
|
})},
|
|
|
|
})
|
|
|
|
handler = nextServer.getRequestHandler()
|
|
|
|
|
2021-11-09 18:03:20 +01:00
|
|
|
console.log("Listening on port", currentPort)
|
|
|
|
})
|
|
|
|
`
|
|
|
|
)
|
|
|
|
}
|
2021-11-06 12:27:40 +01:00
|
|
|
export function isReservedPage(page: string) {
|
|
|
|
return RESERVED_PAGE.test(page)
|
|
|
|
}
|
|
|
|
|
|
|
|
export function isCustomErrorPage(page: string) {
|
|
|
|
return page === '/404' || page === '/500'
|
|
|
|
}
|