2022-05-20 14:24:00 +02:00
|
|
|
import type { PageRuntime } from '../../server/config-shared'
|
|
|
|
import type { NextConfig } from '../../server/config-shared'
|
|
|
|
import { tryToExtractExportedConstValue } from './extract-const-value'
|
|
|
|
import { parseModule } from './parse-module'
|
|
|
|
import { promises as fs } from 'fs'
|
2022-06-03 18:35:44 +02:00
|
|
|
import { tryToParsePath } from '../../lib/try-to-parse-path'
|
2022-06-13 20:17:44 +02:00
|
|
|
import * as Log from '../output/log'
|
2022-06-03 18:35:44 +02:00
|
|
|
|
|
|
|
interface MiddlewareConfig {
|
|
|
|
pathMatcher: RegExp
|
|
|
|
}
|
2022-05-20 14:24:00 +02:00
|
|
|
|
|
|
|
export interface PageStaticInfo {
|
|
|
|
runtime?: PageRuntime
|
|
|
|
ssg?: boolean
|
|
|
|
ssr?: boolean
|
2022-06-03 18:35:44 +02:00
|
|
|
middleware?: Partial<MiddlewareConfig>
|
2022-05-20 14:24:00 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* For a given pageFilePath and nextConfig, if the config supports it, this
|
|
|
|
* function will read the file and return the runtime that should be used.
|
|
|
|
* It will look into the file content only if the page *requires* a runtime
|
|
|
|
* to be specified, that is, when gSSP or gSP is used.
|
|
|
|
* Related discussion: https://github.com/vercel/next.js/discussions/34179
|
|
|
|
*/
|
|
|
|
export async function getPageStaticInfo(params: {
|
|
|
|
nextConfig: Partial<NextConfig>
|
|
|
|
pageFilePath: string
|
|
|
|
isDev?: boolean
|
2022-06-03 18:35:44 +02:00
|
|
|
page?: string
|
2022-05-20 14:24:00 +02:00
|
|
|
}): Promise<PageStaticInfo> {
|
|
|
|
const { isDev, pageFilePath, nextConfig } = params
|
|
|
|
|
|
|
|
const fileContent = (await tryToReadFile(pageFilePath, !isDev)) || ''
|
2022-06-03 18:35:44 +02:00
|
|
|
if (/runtime|getStaticProps|getServerSideProps|matcher/.test(fileContent)) {
|
2022-05-20 14:24:00 +02:00
|
|
|
const swcAST = await parseModule(pageFilePath, fileContent)
|
|
|
|
const { ssg, ssr } = checkExports(swcAST)
|
|
|
|
const config = tryToExtractExportedConstValue(swcAST, 'config') || {}
|
|
|
|
|
2022-06-13 20:17:44 +02:00
|
|
|
let runtime = ['experimental-edge', 'edge'].includes(config?.runtime)
|
|
|
|
? 'edge'
|
|
|
|
: ssr || ssg
|
|
|
|
? config?.runtime || nextConfig.experimental?.runtime
|
|
|
|
: undefined
|
|
|
|
|
|
|
|
if (runtime === 'experimental-edge' || runtime === 'edge') {
|
|
|
|
warnAboutExperimentalEdgeApiFunctions()
|
|
|
|
runtime = 'edge'
|
|
|
|
}
|
2022-05-20 14:24:00 +02:00
|
|
|
|
2022-06-14 05:07:40 +02:00
|
|
|
const middlewareConfig = getMiddlewareConfig(config)
|
2022-06-03 18:35:44 +02:00
|
|
|
|
|
|
|
return {
|
|
|
|
ssr,
|
|
|
|
ssg,
|
|
|
|
...(middlewareConfig && { middleware: middlewareConfig }),
|
|
|
|
...(runtime && { runtime }),
|
2022-05-20 14:24:00 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return { ssr: false, ssg: false }
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Receives a parsed AST from SWC and checks if it belongs to a module that
|
|
|
|
* requires a runtime to be specified. Those are:
|
|
|
|
* - Modules with `export function getStaticProps | getServerSideProps`
|
|
|
|
* - Modules with `export { getStaticProps | getServerSideProps } <from ...>`
|
|
|
|
*/
|
|
|
|
function checkExports(swcAST: any) {
|
|
|
|
if (Array.isArray(swcAST?.body)) {
|
|
|
|
try {
|
|
|
|
for (const node of swcAST.body) {
|
|
|
|
if (
|
|
|
|
node.type === 'ExportDeclaration' &&
|
|
|
|
node.declaration?.type === 'FunctionDeclaration' &&
|
|
|
|
['getStaticProps', 'getServerSideProps'].includes(
|
|
|
|
node.declaration.identifier?.value
|
|
|
|
)
|
|
|
|
) {
|
|
|
|
return {
|
|
|
|
ssg: node.declaration.identifier.value === 'getStaticProps',
|
|
|
|
ssr: node.declaration.identifier.value === 'getServerSideProps',
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (node.type === 'ExportNamedDeclaration') {
|
|
|
|
const values = node.specifiers.map(
|
|
|
|
(specifier: any) =>
|
|
|
|
specifier.type === 'ExportSpecifier' &&
|
|
|
|
specifier.orig?.type === 'Identifier' &&
|
|
|
|
specifier.orig?.value
|
|
|
|
)
|
|
|
|
|
|
|
|
return {
|
|
|
|
ssg: values.some((value: any) =>
|
|
|
|
['getStaticProps'].includes(value)
|
|
|
|
),
|
|
|
|
ssr: values.some((value: any) =>
|
|
|
|
['getServerSideProps'].includes(value)
|
|
|
|
),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} catch (err) {}
|
|
|
|
}
|
|
|
|
|
|
|
|
return { ssg: false, ssr: false }
|
|
|
|
}
|
|
|
|
|
|
|
|
async function tryToReadFile(filePath: string, shouldThrow: boolean) {
|
|
|
|
try {
|
|
|
|
return await fs.readFile(filePath, {
|
|
|
|
encoding: 'utf8',
|
|
|
|
})
|
|
|
|
} catch (error) {
|
|
|
|
if (shouldThrow) {
|
|
|
|
throw error
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2022-06-03 18:35:44 +02:00
|
|
|
|
|
|
|
function getMiddlewareConfig(config: any): Partial<MiddlewareConfig> {
|
|
|
|
const result: Partial<MiddlewareConfig> = {}
|
|
|
|
|
|
|
|
if (config.matcher) {
|
|
|
|
result.pathMatcher = new RegExp(
|
|
|
|
getMiddlewareRegExpStrings(config.matcher).join('|')
|
|
|
|
)
|
|
|
|
|
|
|
|
if (result.pathMatcher.source.length > 4096) {
|
|
|
|
throw new Error(
|
|
|
|
`generated matcher config must be less than 4096 characters.`
|
|
|
|
)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return result
|
|
|
|
}
|
|
|
|
|
|
|
|
function getMiddlewareRegExpStrings(matcherOrMatchers: unknown): string[] {
|
|
|
|
if (Array.isArray(matcherOrMatchers)) {
|
|
|
|
return matcherOrMatchers.flatMap((x) => getMiddlewareRegExpStrings(x))
|
|
|
|
}
|
|
|
|
|
|
|
|
if (typeof matcherOrMatchers !== 'string') {
|
|
|
|
throw new Error(
|
|
|
|
'`matcher` must be a path matcher or an array of path matchers'
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
let matcher: string = matcherOrMatchers
|
|
|
|
|
|
|
|
if (!matcher.startsWith('/')) {
|
|
|
|
throw new Error('`matcher`: path matcher must start with /')
|
|
|
|
}
|
|
|
|
|
|
|
|
const parsedPage = tryToParsePath(matcher)
|
|
|
|
if (parsedPage.error) {
|
|
|
|
throw new Error(`Invalid path matcher: ${matcher}`)
|
|
|
|
}
|
|
|
|
|
2022-06-14 18:50:05 +02:00
|
|
|
const regexes = [parsedPage.regexStr].filter((x): x is string => !!x)
|
|
|
|
if (regexes.length < 1) {
|
2022-06-03 18:35:44 +02:00
|
|
|
throw new Error("Can't parse matcher")
|
|
|
|
} else {
|
|
|
|
return regexes
|
|
|
|
}
|
|
|
|
}
|
2022-06-13 20:17:44 +02:00
|
|
|
|
|
|
|
function warnAboutExperimentalEdgeApiFunctions() {
|
|
|
|
if (warnedAboutExperimentalEdgeApiFunctions) {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
Log.warn(`You are using an experimental edge runtime, the API might change.`)
|
|
|
|
warnedAboutExperimentalEdgeApiFunctions = true
|
|
|
|
}
|
|
|
|
|
|
|
|
let warnedAboutExperimentalEdgeApiFunctions = false
|