.
This commit is contained in:
+224
@@ -0,0 +1,224 @@
|
||||
function isExportDeclaration(node) {
|
||||
return node.type === 'ExportDeclaration';
|
||||
}
|
||||
function isVariableDeclaration(node) {
|
||||
return node.type === 'VariableDeclaration';
|
||||
}
|
||||
function isIdentifier(node) {
|
||||
return node.type === 'Identifier';
|
||||
}
|
||||
function isBooleanLiteral(node) {
|
||||
return node.type === 'BooleanLiteral';
|
||||
}
|
||||
function isNullLiteral(node) {
|
||||
return node.type === 'NullLiteral';
|
||||
}
|
||||
function isStringLiteral(node) {
|
||||
return node.type === 'StringLiteral';
|
||||
}
|
||||
function isNumericLiteral(node) {
|
||||
return node.type === 'NumericLiteral';
|
||||
}
|
||||
function isArrayExpression(node) {
|
||||
return node.type === 'ArrayExpression';
|
||||
}
|
||||
function isObjectExpression(node) {
|
||||
return node.type === 'ObjectExpression';
|
||||
}
|
||||
function isKeyValueProperty(node) {
|
||||
return node.type === 'KeyValueProperty';
|
||||
}
|
||||
function isRegExpLiteral(node) {
|
||||
return node.type === 'RegExpLiteral';
|
||||
}
|
||||
function isTemplateLiteral(node) {
|
||||
return node.type === 'TemplateLiteral';
|
||||
}
|
||||
function isTsSatisfiesExpression(node) {
|
||||
return node.type === 'TsSatisfiesExpression';
|
||||
}
|
||||
/** Formats a path array like `["config", "runtime", "[0]", "value"]` → `"config.runtime[0].value"` */ function formatCodePath(paths) {
|
||||
if (!paths) return undefined;
|
||||
let codePath = '';
|
||||
for (const path of paths){
|
||||
if (path[0] === '[') {
|
||||
// "array" + "[0]"
|
||||
codePath += path;
|
||||
} else if (codePath === '') {
|
||||
codePath = path;
|
||||
} else {
|
||||
// "object" + ".key"
|
||||
codePath += `.${path}`;
|
||||
}
|
||||
}
|
||||
return codePath;
|
||||
}
|
||||
function extractValue(node, path) {
|
||||
if (isNullLiteral(node)) {
|
||||
return {
|
||||
value: null
|
||||
};
|
||||
} else if (isBooleanLiteral(node)) {
|
||||
// e.g. true / false
|
||||
return {
|
||||
value: node.value
|
||||
};
|
||||
} else if (isStringLiteral(node)) {
|
||||
// e.g. "abc"
|
||||
return {
|
||||
value: node.value
|
||||
};
|
||||
} else if (isNumericLiteral(node)) {
|
||||
// e.g. 123
|
||||
return {
|
||||
value: node.value
|
||||
};
|
||||
} else if (isRegExpLiteral(node)) {
|
||||
// e.g. /abc/i
|
||||
return {
|
||||
value: new RegExp(node.pattern, node.flags)
|
||||
};
|
||||
} else if (isIdentifier(node)) {
|
||||
switch(node.value){
|
||||
case 'undefined':
|
||||
return {
|
||||
value: undefined
|
||||
};
|
||||
default:
|
||||
return {
|
||||
unsupported: `Unknown identifier "${node.value}"`,
|
||||
path: formatCodePath(path)
|
||||
};
|
||||
}
|
||||
} else if (isArrayExpression(node)) {
|
||||
// e.g. [1, 2, 3]
|
||||
const arr = [];
|
||||
for(let i = 0, len = node.elements.length; i < len; i++){
|
||||
const elem = node.elements[i];
|
||||
if (elem) {
|
||||
if (elem.spread) {
|
||||
// e.g. [ ...a ]
|
||||
return {
|
||||
unsupported: 'Unsupported spread operator in the Array Expression',
|
||||
path: formatCodePath(path)
|
||||
};
|
||||
}
|
||||
const result = extractValue(elem.expression, path && [
|
||||
...path,
|
||||
`[${i}]`
|
||||
]);
|
||||
if ('unsupported' in result) return result;
|
||||
arr.push(result.value);
|
||||
} else {
|
||||
// e.g. [1, , 2]
|
||||
// ^^
|
||||
arr.push(undefined);
|
||||
}
|
||||
}
|
||||
return {
|
||||
value: arr
|
||||
};
|
||||
} else if (isObjectExpression(node)) {
|
||||
// e.g. { a: 1, b: 2 }
|
||||
const obj = {};
|
||||
for (const prop of node.properties){
|
||||
if (!isKeyValueProperty(prop)) {
|
||||
// e.g. { ...a }
|
||||
return {
|
||||
unsupported: 'Unsupported spread operator in the Object Expression',
|
||||
path: formatCodePath(path)
|
||||
};
|
||||
}
|
||||
let key;
|
||||
if (isIdentifier(prop.key)) {
|
||||
// e.g. { a: 1, b: 2 }
|
||||
key = prop.key.value;
|
||||
} else if (isStringLiteral(prop.key)) {
|
||||
// e.g. { "a": 1, "b": 2 }
|
||||
key = prop.key.value;
|
||||
} else {
|
||||
return {
|
||||
unsupported: `Unsupported key type "${prop.key.type}" in the Object Expression`,
|
||||
path: formatCodePath(path)
|
||||
};
|
||||
}
|
||||
const result = extractValue(prop.value, path && [
|
||||
...path,
|
||||
key
|
||||
]);
|
||||
if ('unsupported' in result) return result;
|
||||
obj[key] = result.value;
|
||||
}
|
||||
return {
|
||||
value: obj
|
||||
};
|
||||
} else if (isTemplateLiteral(node)) {
|
||||
// e.g. `abc`
|
||||
if (node.expressions.length !== 0) {
|
||||
// TODO: should we add support for `${'e'}d${'g'}'e'`?
|
||||
return {
|
||||
unsupported: 'Unsupported template literal with expressions',
|
||||
path: formatCodePath(path)
|
||||
};
|
||||
}
|
||||
// When TemplateLiteral has 0 expressions, the length of quasis is always 1.
|
||||
// Because when parsing TemplateLiteral, the parser yields the first quasi,
|
||||
// then the first expression, then the next quasi, then the next expression, etc.,
|
||||
// until the last quasi.
|
||||
// Thus if there is no expression, the parser ends at the frst and also last quasis
|
||||
//
|
||||
// A "cooked" interpretation where backslashes have special meaning, while a
|
||||
// "raw" interpretation where backslashes do not have special meaning
|
||||
// https://exploringjs.com/impatient-js/ch_template-literals.html#template-strings-cooked-vs-raw
|
||||
const [{ cooked, raw }] = node.quasis;
|
||||
return {
|
||||
value: cooked ?? raw
|
||||
};
|
||||
} else if (isTsSatisfiesExpression(node)) {
|
||||
return extractValue(node.expression);
|
||||
} else {
|
||||
return {
|
||||
unsupported: `Unsupported node type "${node.type}"`,
|
||||
path: formatCodePath(path)
|
||||
};
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Extracts the value of an exported const variable named `exportedName`
|
||||
* (e.g. "export const config = { runtime: 'edge' }") from swc's AST.
|
||||
* The value must be one of (or returns unsupported):
|
||||
* - string
|
||||
* - boolean
|
||||
* - number
|
||||
* - null
|
||||
* - undefined
|
||||
* - array containing values listed in this list
|
||||
* - object containing values listed in this list
|
||||
*
|
||||
* Returns null if the declaration is not found.
|
||||
* Returns { unsupported, path? } if the value contains unsupported nodes.
|
||||
*/ export function extractExportedConstValue(module, exportedName) {
|
||||
if (!module) return null;
|
||||
for (const moduleItem of module.body){
|
||||
if (!isExportDeclaration(moduleItem)) {
|
||||
continue;
|
||||
}
|
||||
const declaration = moduleItem.declaration;
|
||||
if (!isVariableDeclaration(declaration)) {
|
||||
continue;
|
||||
}
|
||||
if (declaration.kind !== 'const') {
|
||||
continue;
|
||||
}
|
||||
for (const decl of declaration.declarations){
|
||||
if (isIdentifier(decl.id) && decl.id.value === exportedName && decl.init) {
|
||||
return extractValue(decl.init, [
|
||||
exportedName
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
//# sourceMappingURL=extract-const-value.js.map
|
||||
+1
File diff suppressed because one or more lines are too long
+558
@@ -0,0 +1,558 @@
|
||||
import { readFileSync } from 'fs';
|
||||
import { relative } from 'path';
|
||||
import { LRUCache } from '../../server/lib/lru-cache';
|
||||
import { extractExportedConstValue } from './extract-const-value';
|
||||
import { parseModule } from './parse-module';
|
||||
import * as Log from '../output/log';
|
||||
import { SERVER_RUNTIME, MIDDLEWARE_FILENAME, PROXY_FILENAME } from '../../lib/constants';
|
||||
import { tryToParsePath } from '../../lib/try-to-parse-path';
|
||||
import { isAPIRoute } from '../../lib/is-api-route';
|
||||
import { isEdgeRuntime } from '../../lib/is-edge-runtime';
|
||||
import { RSC_MODULE_TYPES } from '../../shared/lib/constants';
|
||||
import { PAGE_TYPES } from '../../lib/page-types';
|
||||
import { AppSegmentConfigSchemaKeys, parseAppSegmentConfig } from '../segment-config/app/app-segment-config';
|
||||
import { reportZodError } from '../../shared/lib/zod';
|
||||
import { PagesSegmentConfigSchemaKeys, parsePagesSegmentConfig } from '../segment-config/pages/pages-segment-config';
|
||||
import { MiddlewareConfigInputSchema, SourceSchema } from '../segment-config/middleware/middleware-config';
|
||||
import { normalizeAppPath } from '../../shared/lib/router/utils/app-paths';
|
||||
import { normalizePagePath } from '../../shared/lib/page-path/normalize-page-path';
|
||||
import { isProxyFile } from '../utils';
|
||||
const PARSE_PATTERN = /(?<!(_jsx|jsx-))runtime|preferredRegion|getStaticProps|getServerSideProps|generateStaticParams|export const|generateImageMetadata|generateSitemaps|middleware|proxy/;
|
||||
const CLIENT_MODULE_LABEL = /\/\* __next_internal_client_entry_do_not_use__ ([^ ]*) (cjs|auto) \*\//;
|
||||
// Match JSON object that may contain nested objects (for loc info)
|
||||
// The JSON ends right before the closing " */"
|
||||
const ACTION_MODULE_LABEL = /\/\* __next_internal_action_entry_do_not_use__ (\{.*\}) \*\//;
|
||||
const CLIENT_DIRECTIVE = 'use client';
|
||||
const SERVER_ACTION_DIRECTIVE = 'use server';
|
||||
export function getRSCModuleInformation(source, isReactServerLayer) {
|
||||
const actionsJson = source.match(ACTION_MODULE_LABEL);
|
||||
// Parse action metadata - supports both old format (string) and new format (object with loc)
|
||||
const parsedActionsMeta = actionsJson ? JSON.parse(actionsJson[1]) : undefined;
|
||||
const clientInfoMatch = source.match(CLIENT_MODULE_LABEL);
|
||||
const isClientRef = !!clientInfoMatch;
|
||||
if (!isReactServerLayer) {
|
||||
return {
|
||||
type: RSC_MODULE_TYPES.client,
|
||||
actionIds: parsedActionsMeta,
|
||||
isClientRef
|
||||
};
|
||||
}
|
||||
const clientRefsString = clientInfoMatch == null ? void 0 : clientInfoMatch[1];
|
||||
const clientRefs = clientRefsString ? clientRefsString.split(',') : [];
|
||||
const clientEntryType = clientInfoMatch == null ? void 0 : clientInfoMatch[2];
|
||||
const type = clientInfoMatch ? RSC_MODULE_TYPES.client : RSC_MODULE_TYPES.server;
|
||||
return {
|
||||
type,
|
||||
actionIds: parsedActionsMeta,
|
||||
clientRefs,
|
||||
clientEntryType,
|
||||
isClientRef
|
||||
};
|
||||
}
|
||||
/**
|
||||
* 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 ...>`
|
||||
* - Modules with `export const runtime = ...`
|
||||
*/ function checkExports(ast, expectedExports, page) {
|
||||
const exportsSet = new Set([
|
||||
'getStaticProps',
|
||||
'getServerSideProps',
|
||||
'generateImageMetadata',
|
||||
'generateSitemaps',
|
||||
'generateStaticParams'
|
||||
]);
|
||||
if (!Array.isArray(ast == null ? void 0 : ast.body)) {
|
||||
return {};
|
||||
}
|
||||
try {
|
||||
let getStaticProps = false;
|
||||
let getServerSideProps = false;
|
||||
let generateImageMetadata = false;
|
||||
let generateSitemaps = false;
|
||||
let generateStaticParams = false;
|
||||
let exports = new Set();
|
||||
let directives = new Set();
|
||||
let hasLeadingNonDirectiveNode = false;
|
||||
for (const node of ast.body){
|
||||
var _node_declaration, _node_declaration1, _node_declaration_identifier, _node_declaration2;
|
||||
// There should be no non-string literals nodes before directives
|
||||
if (node.type === 'ExpressionStatement' && node.expression.type === 'StringLiteral') {
|
||||
if (!hasLeadingNonDirectiveNode) {
|
||||
const directive = node.expression.value;
|
||||
if (CLIENT_DIRECTIVE === directive) {
|
||||
directives.add('client');
|
||||
}
|
||||
if (SERVER_ACTION_DIRECTIVE === directive) {
|
||||
directives.add('server');
|
||||
}
|
||||
}
|
||||
} else {
|
||||
hasLeadingNonDirectiveNode = true;
|
||||
}
|
||||
if (node.type === 'ExportDeclaration' && ((_node_declaration = node.declaration) == null ? void 0 : _node_declaration.type) === 'VariableDeclaration') {
|
||||
var _node_declaration3;
|
||||
for (const declaration of (_node_declaration3 = node.declaration) == null ? void 0 : _node_declaration3.declarations){
|
||||
if (expectedExports.includes(declaration.id.value)) {
|
||||
exports.add(declaration.id.value);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (node.type === 'ExportDeclaration' && ((_node_declaration1 = node.declaration) == null ? void 0 : _node_declaration1.type) === 'FunctionDeclaration' && exportsSet.has((_node_declaration_identifier = node.declaration.identifier) == null ? void 0 : _node_declaration_identifier.value)) {
|
||||
const id = node.declaration.identifier.value;
|
||||
getServerSideProps = id === 'getServerSideProps';
|
||||
getStaticProps = id === 'getStaticProps';
|
||||
generateImageMetadata = id === 'generateImageMetadata';
|
||||
generateSitemaps = id === 'generateSitemaps';
|
||||
generateStaticParams = id === 'generateStaticParams';
|
||||
}
|
||||
if (node.type === 'ExportDeclaration' && ((_node_declaration2 = node.declaration) == null ? void 0 : _node_declaration2.type) === 'VariableDeclaration') {
|
||||
var _node_declaration_declarations_, _node_declaration4;
|
||||
const id = (_node_declaration4 = node.declaration) == null ? void 0 : (_node_declaration_declarations_ = _node_declaration4.declarations[0]) == null ? void 0 : _node_declaration_declarations_.id.value;
|
||||
if (exportsSet.has(id)) {
|
||||
getServerSideProps = id === 'getServerSideProps';
|
||||
getStaticProps = id === 'getStaticProps';
|
||||
generateImageMetadata = id === 'generateImageMetadata';
|
||||
generateSitemaps = id === 'generateSitemaps';
|
||||
generateStaticParams = id === 'generateStaticParams';
|
||||
}
|
||||
}
|
||||
if (node.type === 'ExportNamedDeclaration') {
|
||||
for (const specifier of node.specifiers){
|
||||
var _specifier_orig;
|
||||
if (specifier.type === 'ExportSpecifier' && ((_specifier_orig = specifier.orig) == null ? void 0 : _specifier_orig.type) === 'Identifier') {
|
||||
const value = specifier.orig.value;
|
||||
if (!getServerSideProps && value === 'getServerSideProps') {
|
||||
getServerSideProps = true;
|
||||
}
|
||||
if (!getStaticProps && value === 'getStaticProps') {
|
||||
getStaticProps = true;
|
||||
}
|
||||
if (!generateImageMetadata && value === 'generateImageMetadata') {
|
||||
generateImageMetadata = true;
|
||||
}
|
||||
if (!generateSitemaps && value === 'generateSitemaps') {
|
||||
generateSitemaps = true;
|
||||
}
|
||||
if (!generateStaticParams && value === 'generateStaticParams') {
|
||||
generateStaticParams = true;
|
||||
}
|
||||
if (expectedExports.includes(value) && !exports.has(value)) {
|
||||
// An export was found that was actually a re-export, and not a
|
||||
// literal value. We should warn here.
|
||||
Log.warn(`Next.js can't recognize the exported \`${value}\` field in "${page}", it may be re-exported from another file. The default config will be used instead.`);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return {
|
||||
getStaticProps,
|
||||
getServerSideProps,
|
||||
generateImageMetadata,
|
||||
generateSitemaps,
|
||||
generateStaticParams,
|
||||
directives,
|
||||
exports
|
||||
};
|
||||
} catch {}
|
||||
return {};
|
||||
}
|
||||
function validateMiddlewareProxyExports({ ast, page, pageFilePath, isDev }) {
|
||||
// Check if this is middleware/proxy
|
||||
const isMiddleware = page === `/${MIDDLEWARE_FILENAME}` || page === `/src/${MIDDLEWARE_FILENAME}`;
|
||||
const isProxy = page === `/${PROXY_FILENAME}` || page === `/src/${PROXY_FILENAME}`;
|
||||
if (!isMiddleware && !isProxy) {
|
||||
return;
|
||||
}
|
||||
if (!ast || !Array.isArray(ast.body)) {
|
||||
return;
|
||||
}
|
||||
const fileName = isProxy ? PROXY_FILENAME : MIDDLEWARE_FILENAME;
|
||||
// Parse AST to get export info (since checkExports doesn't return middleware/proxy info)
|
||||
let hasDefaultExport = false;
|
||||
let hasMiddlewareExport = false;
|
||||
let hasProxyExport = false;
|
||||
for (const node of ast.body){
|
||||
var _node_declaration, _node_declaration1;
|
||||
if (node.type === 'ExportDefaultDeclaration' || node.type === 'ExportDefaultExpression') {
|
||||
hasDefaultExport = true;
|
||||
}
|
||||
if (node.type === 'ExportDeclaration' && ((_node_declaration = node.declaration) == null ? void 0 : _node_declaration.type) === 'FunctionDeclaration') {
|
||||
var _node_declaration_identifier;
|
||||
const id = (_node_declaration_identifier = node.declaration.identifier) == null ? void 0 : _node_declaration_identifier.value;
|
||||
if (id === 'middleware') {
|
||||
hasMiddlewareExport = true;
|
||||
}
|
||||
if (id === 'proxy') {
|
||||
hasProxyExport = true;
|
||||
}
|
||||
}
|
||||
if (node.type === 'ExportDeclaration' && ((_node_declaration1 = node.declaration) == null ? void 0 : _node_declaration1.type) === 'VariableDeclaration') {
|
||||
var _node_declaration_declarations_, _node_declaration2;
|
||||
const id = (_node_declaration2 = node.declaration) == null ? void 0 : (_node_declaration_declarations_ = _node_declaration2.declarations[0]) == null ? void 0 : _node_declaration_declarations_.id.value;
|
||||
if (id === 'middleware') {
|
||||
hasMiddlewareExport = true;
|
||||
}
|
||||
if (id === 'proxy') {
|
||||
hasProxyExport = true;
|
||||
}
|
||||
}
|
||||
if (node.type === 'ExportNamedDeclaration') {
|
||||
for (const specifier of node.specifiers){
|
||||
var _specifier_orig;
|
||||
if (specifier.type === 'ExportSpecifier' && ((_specifier_orig = specifier.orig) == null ? void 0 : _specifier_orig.type) === 'Identifier') {
|
||||
// Use the exported name if it exists (for aliased exports like `export { foo as proxy }`),
|
||||
// otherwise fall back to the original name (for simple re-exports like `export { proxy }`)
|
||||
const exportedIdentifier = specifier.exported || specifier.orig;
|
||||
const value = exportedIdentifier.value;
|
||||
if (value === 'middleware') {
|
||||
hasMiddlewareExport = true;
|
||||
}
|
||||
if (value === 'proxy') {
|
||||
hasProxyExport = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
const hasValidExport = hasDefaultExport || isMiddleware && hasMiddlewareExport || isProxy && hasProxyExport;
|
||||
const relativePath = relative(process.cwd(), pageFilePath);
|
||||
const resolvedPath = relativePath.startsWith('.') ? relativePath : `./${relativePath}`;
|
||||
if (!hasValidExport) {
|
||||
const message = `The file "${resolvedPath}" must export a function, either as a default export or as a named "${fileName}" export.\n` + `This function is what Next.js runs for every request handled by this ${fileName === 'proxy' ? 'proxy (previously called middleware)' : 'middleware'}.\n\n` + `Why this happens:\n` + (isProxy ? "- You are migrating from `middleware` to `proxy`, but haven't updated the exported function.\n" : '') + `- The file exists but doesn't export a function.\n` + `- The export is not a function (e.g., an object or constant).\n` + `- There's a syntax error preventing the export from being recognized.\n\n` + `To fix it:\n` + `- Ensure this file has either a default or "${fileName}" function export.\n\n` + `Learn more: https://nextjs.org/docs/messages/middleware-to-proxy`;
|
||||
if (isDev) {
|
||||
// errorOnce as proxy/middleware runs per request including multiple
|
||||
// internal _next/ routes and spams logs.
|
||||
Log.errorOnce(message);
|
||||
} else {
|
||||
throw Object.defineProperty(new Error(message), "__NEXT_ERROR_CODE", {
|
||||
value: "E903",
|
||||
enumerable: false,
|
||||
configurable: true
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
function tryToReadFile(filePath, shouldThrow) {
|
||||
try {
|
||||
return readFileSync(filePath, {
|
||||
encoding: 'utf8'
|
||||
});
|
||||
} catch (error) {
|
||||
if (shouldThrow) {
|
||||
error.message = `Next.js ERROR: Failed to read file ${filePath}:\n${error.message}`;
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
/**
|
||||
* @internal - required to exclude zod types from the build
|
||||
*/ export function getMiddlewareMatchers(matcherOrMatchers, nextConfig) {
|
||||
const matchers = Array.isArray(matcherOrMatchers) ? matcherOrMatchers : [
|
||||
matcherOrMatchers
|
||||
];
|
||||
const { i18n } = nextConfig;
|
||||
return matchers.map((matcher)=>{
|
||||
matcher = typeof matcher === 'string' ? {
|
||||
source: matcher
|
||||
} : matcher;
|
||||
const originalSource = matcher.source;
|
||||
let { source, ...rest } = matcher;
|
||||
const isRoot = source === '/';
|
||||
if ((i18n == null ? void 0 : i18n.locales) && matcher.locale !== false) {
|
||||
source = `/:nextInternalLocale((?!_next/)[^/.]{1,})${isRoot ? '' : source}`;
|
||||
}
|
||||
source = `/:nextData(_next/data/[^/]{1,})?${source}${isRoot ? `(${nextConfig.i18n ? '|\\.json|' : ''}/?index|/?index\\.json)?` : '{(\\.json)}?'}`;
|
||||
if (nextConfig.basePath) {
|
||||
source = `${nextConfig.basePath}${source}`;
|
||||
}
|
||||
// Validate that the source is still.
|
||||
const result = SourceSchema.safeParse(source);
|
||||
if (!result.success) {
|
||||
reportZodError('Failed to parse middleware source', result.error);
|
||||
// We need to exit here because middleware being built occurs before we
|
||||
// finish setting up the server. Exiting here is the only way to ensure
|
||||
// that we don't hang.
|
||||
process.exit(1);
|
||||
}
|
||||
return {
|
||||
...rest,
|
||||
// We know that parsed.regexStr is not undefined because we already
|
||||
// checked that the source is valid.
|
||||
regexp: tryToParsePath(result.data).regexStr,
|
||||
originalSource: originalSource || source
|
||||
};
|
||||
});
|
||||
}
|
||||
function parseMiddlewareConfig(page, rawConfig, nextConfig) {
|
||||
// If there's no config to parse, then return nothing.
|
||||
if (typeof rawConfig !== 'object' || !rawConfig) return {};
|
||||
const input = MiddlewareConfigInputSchema.safeParse(rawConfig);
|
||||
if (!input.success) {
|
||||
reportZodError(`${page} contains invalid middleware config`, input.error);
|
||||
// We need to exit here because middleware being built occurs before we
|
||||
// finish setting up the server. Exiting here is the only way to ensure
|
||||
// that we don't hang.
|
||||
process.exit(1);
|
||||
}
|
||||
const config = {};
|
||||
if (input.data.matcher) {
|
||||
config.matchers = getMiddlewareMatchers(input.data.matcher, nextConfig);
|
||||
}
|
||||
if (input.data.unstable_allowDynamic) {
|
||||
config.unstable_allowDynamic = Array.isArray(input.data.unstable_allowDynamic) ? input.data.unstable_allowDynamic : [
|
||||
input.data.unstable_allowDynamic
|
||||
];
|
||||
}
|
||||
if (input.data.regions) {
|
||||
config.regions = input.data.regions;
|
||||
}
|
||||
return config;
|
||||
}
|
||||
const apiRouteWarnings = new LRUCache(250);
|
||||
function warnAboutExperimentalEdge(apiRoute) {
|
||||
if (process.env.NODE_ENV === 'production' && process.env.NEXT_PRIVATE_BUILD_WORKER === '1') {
|
||||
return;
|
||||
}
|
||||
if (apiRoute && apiRouteWarnings.has(apiRoute)) {
|
||||
return;
|
||||
}
|
||||
Log.warn(apiRoute ? `${apiRoute} provided runtime 'experimental-edge'. It can be updated to 'edge' instead.` : `You are using an experimental edge runtime, the API might change.`);
|
||||
if (apiRoute) {
|
||||
apiRouteWarnings.set(apiRoute, 1);
|
||||
}
|
||||
}
|
||||
let hadUnsupportedValue = false;
|
||||
const warnedUnsupportedValueMap = new LRUCache(250, ()=>1);
|
||||
function warnAboutUnsupportedValue(pageFilePath, page, result) {
|
||||
hadUnsupportedValue = true;
|
||||
const isProductionBuild = process.env.NODE_ENV === 'production';
|
||||
if (// we only log for the server compilation so it's not
|
||||
// duplicated due to webpack build worker having fresh
|
||||
// module scope for each compiler
|
||||
process.env.NEXT_COMPILER_NAME !== 'server' || isProductionBuild && warnedUnsupportedValueMap.has(pageFilePath)) {
|
||||
return;
|
||||
}
|
||||
warnedUnsupportedValueMap.set(pageFilePath, true);
|
||||
const message = `Next.js can't recognize the exported \`config\` field in ` + (page ? `route "${page}"` : `"${pageFilePath}"`) + ':\n' + result.unsupported + (result.path ? ` at "${result.path}"` : '') + '.\n' + 'Read More - https://nextjs.org/docs/messages/invalid-page-config';
|
||||
// for a build we use `Log.error` instead of throwing
|
||||
// so that all errors can be logged before exiting the process
|
||||
if (isProductionBuild) {
|
||||
Log.error(message);
|
||||
} else {
|
||||
throw Object.defineProperty(new Error(message), "__NEXT_ERROR_CODE", {
|
||||
value: "E1013",
|
||||
enumerable: false,
|
||||
configurable: true
|
||||
});
|
||||
}
|
||||
}
|
||||
export async function getAppPageStaticInfo({ pageFilePath, nextConfig, isDev, page }) {
|
||||
const content = tryToReadFile(pageFilePath, !isDev);
|
||||
if (!content || !PARSE_PATTERN.test(content)) {
|
||||
return {
|
||||
type: PAGE_TYPES.APP,
|
||||
config: undefined,
|
||||
runtime: undefined,
|
||||
preferredRegion: undefined,
|
||||
maxDuration: undefined,
|
||||
hadUnsupportedValue: false
|
||||
};
|
||||
}
|
||||
const ast = await parseModule(pageFilePath, content);
|
||||
validateMiddlewareProxyExports({
|
||||
ast,
|
||||
page,
|
||||
pageFilePath,
|
||||
isDev
|
||||
});
|
||||
const { generateStaticParams, generateImageMetadata, generateSitemaps, exports, directives } = checkExports(ast, AppSegmentConfigSchemaKeys, page);
|
||||
const { type: rsc } = getRSCModuleInformation(content, true);
|
||||
const exportedConfig = {};
|
||||
if (exports) {
|
||||
for (const property of exports){
|
||||
const result = extractExportedConstValue(ast, property);
|
||||
if (result !== null && 'unsupported' in result) {
|
||||
warnAboutUnsupportedValue(pageFilePath, page, result);
|
||||
} else if (result !== null) {
|
||||
exportedConfig[property] = result.value;
|
||||
}
|
||||
}
|
||||
}
|
||||
const configResult = extractExportedConstValue(ast, 'config');
|
||||
if (configResult !== null && 'unsupported' in configResult) {
|
||||
warnAboutUnsupportedValue(pageFilePath, page, configResult);
|
||||
} else if (configResult !== null) {
|
||||
exportedConfig.config = configResult.value;
|
||||
}
|
||||
const route = normalizeAppPath(page);
|
||||
const config = parseAppSegmentConfig(exportedConfig, route);
|
||||
// Prevent edge runtime and generateStaticParams in the same file.
|
||||
if (isEdgeRuntime(config.runtime) && generateStaticParams) {
|
||||
throw Object.defineProperty(new Error(`Page "${page}" cannot use both \`export const runtime = 'edge'\` and export \`generateStaticParams\`.`), "__NEXT_ERROR_CODE", {
|
||||
value: "E42",
|
||||
enumerable: false,
|
||||
configurable: true
|
||||
});
|
||||
}
|
||||
// Prevent use client and generateStaticParams in the same file.
|
||||
if ((directives == null ? void 0 : directives.has('client')) && generateStaticParams) {
|
||||
throw Object.defineProperty(new Error(`Page "${page}" cannot use both "use client" and export function "generateStaticParams()".`), "__NEXT_ERROR_CODE", {
|
||||
value: "E475",
|
||||
enumerable: false,
|
||||
configurable: true
|
||||
});
|
||||
}
|
||||
// Prevent use client and unstable_instant in the same file.
|
||||
if ((directives == null ? void 0 : directives.has('client')) && 'unstable_instant' in config) {
|
||||
throw Object.defineProperty(new Error(`Page "${page}" cannot export "unstable_instant" from a Client Component module. To use this API, convert this module to a Server Component by removing the "use client" directive.`), "__NEXT_ERROR_CODE", {
|
||||
value: "E1075",
|
||||
enumerable: false,
|
||||
configurable: true
|
||||
});
|
||||
}
|
||||
if ('unstable_instant' in config && !nextConfig.cacheComponents) {
|
||||
throw Object.defineProperty(new Error(`Page "${page}" cannot use \`export const unstable_instant = ...\` without enabling \`cacheComponents\`.`), "__NEXT_ERROR_CODE", {
|
||||
value: "E990",
|
||||
enumerable: false,
|
||||
configurable: true
|
||||
});
|
||||
}
|
||||
// Prevent unstable_dynamicStaleTime in layouts.
|
||||
if ('unstable_dynamicStaleTime' in config) {
|
||||
const isLayout = /\/layout\.[^/]+$/.test(pageFilePath);
|
||||
if (isLayout) {
|
||||
throw Object.defineProperty(new Error(`"${page}" cannot use \`export const unstable_dynamicStaleTime\`. This config is only supported in page files, not layouts.`), "__NEXT_ERROR_CODE", {
|
||||
value: "E1137",
|
||||
enumerable: false,
|
||||
configurable: true
|
||||
});
|
||||
}
|
||||
}
|
||||
// Prevent combining unstable_dynamicStaleTime and unstable_instant.
|
||||
if ('unstable_dynamicStaleTime' in config && 'unstable_instant' in config) {
|
||||
throw Object.defineProperty(new Error(`Page "${page}" cannot use both \`export const unstable_dynamicStaleTime\` and \`export const unstable_instant\`.`), "__NEXT_ERROR_CODE", {
|
||||
value: "E1136",
|
||||
enumerable: false,
|
||||
configurable: true
|
||||
});
|
||||
}
|
||||
return {
|
||||
type: PAGE_TYPES.APP,
|
||||
rsc,
|
||||
generateImageMetadata,
|
||||
generateSitemaps,
|
||||
generateStaticParams,
|
||||
config,
|
||||
middleware: parseMiddlewareConfig(page, exportedConfig.config, nextConfig),
|
||||
runtime: config.runtime,
|
||||
preferredRegion: config.preferredRegion,
|
||||
maxDuration: config.maxDuration,
|
||||
hadUnsupportedValue
|
||||
};
|
||||
}
|
||||
export async function getPagesPageStaticInfo({ pageFilePath, nextConfig, isDev, page }) {
|
||||
var _config_config, _config_config1, _config_config2;
|
||||
const content = tryToReadFile(pageFilePath, !isDev);
|
||||
if (!content || !PARSE_PATTERN.test(content)) {
|
||||
return {
|
||||
type: PAGE_TYPES.PAGES,
|
||||
config: undefined,
|
||||
runtime: undefined,
|
||||
preferredRegion: undefined,
|
||||
maxDuration: undefined,
|
||||
hadUnsupportedValue: false
|
||||
};
|
||||
}
|
||||
const ast = await parseModule(pageFilePath, content);
|
||||
validateMiddlewareProxyExports({
|
||||
ast,
|
||||
page,
|
||||
pageFilePath,
|
||||
isDev
|
||||
});
|
||||
const { getServerSideProps, getStaticProps, exports } = checkExports(ast, PagesSegmentConfigSchemaKeys, page);
|
||||
const { type: rsc } = getRSCModuleInformation(content, true);
|
||||
const exportedConfig = {};
|
||||
if (exports) {
|
||||
for (const property of exports){
|
||||
const result = extractExportedConstValue(ast, property);
|
||||
if (result !== null && 'unsupported' in result) {
|
||||
warnAboutUnsupportedValue(pageFilePath, page, result);
|
||||
} else if (result !== null) {
|
||||
exportedConfig[property] = result.value;
|
||||
}
|
||||
}
|
||||
}
|
||||
const configResult = extractExportedConstValue(ast, 'config');
|
||||
if (configResult !== null && 'unsupported' in configResult) {
|
||||
warnAboutUnsupportedValue(pageFilePath, page, configResult);
|
||||
} else if (configResult !== null) {
|
||||
exportedConfig.config = configResult.value;
|
||||
}
|
||||
// Validate the config.
|
||||
const route = normalizePagePath(page);
|
||||
const config = parsePagesSegmentConfig(exportedConfig, route);
|
||||
const isAnAPIRoute = isAPIRoute(route);
|
||||
let resolvedRuntime = config.runtime ?? ((_config_config = config.config) == null ? void 0 : _config_config.runtime);
|
||||
if (isProxyFile(page) && resolvedRuntime) {
|
||||
const relativePath = relative(process.cwd(), pageFilePath);
|
||||
const resolvedPath = relativePath.startsWith('.') ? relativePath : `./${relativePath}`;
|
||||
const message = `Route segment config is not allowed in Proxy file at "${resolvedPath}". Proxy always runs on Node.js runtime. Learn more: https://nextjs.org/docs/messages/middleware-to-proxy`;
|
||||
if (isDev) {
|
||||
// errorOnce as proxy/middleware runs per request including multiple
|
||||
// internal _next/ routes and spams logs.
|
||||
Log.errorOnce(message);
|
||||
resolvedRuntime = SERVER_RUNTIME.nodejs;
|
||||
} else {
|
||||
throw Object.defineProperty(new Error(message), "__NEXT_ERROR_CODE", {
|
||||
value: "E1031",
|
||||
enumerable: false,
|
||||
configurable: true
|
||||
});
|
||||
}
|
||||
}
|
||||
if (resolvedRuntime === SERVER_RUNTIME.experimentalEdge) {
|
||||
warnAboutExperimentalEdge(isAnAPIRoute ? page : null);
|
||||
}
|
||||
if (!isProxyFile(page) && resolvedRuntime === SERVER_RUNTIME.edge && page && !isAnAPIRoute) {
|
||||
const message = `Page ${page} provided runtime 'edge', the edge runtime for rendering is currently experimental. Use runtime 'experimental-edge' instead.`;
|
||||
if (isDev) {
|
||||
Log.error(message);
|
||||
} else {
|
||||
throw Object.defineProperty(new Error(message), "__NEXT_ERROR_CODE", {
|
||||
value: "E1015",
|
||||
enumerable: false,
|
||||
configurable: true
|
||||
});
|
||||
}
|
||||
}
|
||||
return {
|
||||
type: PAGE_TYPES.PAGES,
|
||||
getStaticProps,
|
||||
getServerSideProps,
|
||||
rsc,
|
||||
config,
|
||||
middleware: parseMiddlewareConfig(page, exportedConfig.config, nextConfig),
|
||||
runtime: resolvedRuntime,
|
||||
preferredRegion: (_config_config1 = config.config) == null ? void 0 : _config_config1.regions,
|
||||
maxDuration: config.maxDuration ?? ((_config_config2 = config.config) == null ? void 0 : _config_config2.maxDuration),
|
||||
hadUnsupportedValue
|
||||
};
|
||||
}
|
||||
/**
|
||||
* 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) {
|
||||
if (params.pageType === PAGE_TYPES.APP) {
|
||||
return getAppPageStaticInfo(params);
|
||||
}
|
||||
return getPagesPageStaticInfo(params);
|
||||
}
|
||||
|
||||
//# sourceMappingURL=get-page-static-info.js.map
|
||||
+1
File diff suppressed because one or more lines are too long
+13
@@ -0,0 +1,13 @@
|
||||
import { LRUCache } from '../../server/lib/lru-cache';
|
||||
import { withPromiseCache } from '../../lib/with-promise-cache';
|
||||
import { createHash } from 'crypto';
|
||||
import { parse } from '../swc';
|
||||
/**
|
||||
* Parses a module with SWC using an LRU cache where the parsed module will
|
||||
* be indexed by a sha of its content holding up to 500 entries.
|
||||
*/ export const parseModule = withPromiseCache(new LRUCache(500), async (filename, content)=>parse(content, {
|
||||
isModule: 'unknown',
|
||||
filename
|
||||
}).catch(()=>null), (_, content)=>createHash('sha1').update(content).digest('hex'));
|
||||
|
||||
//# sourceMappingURL=parse-module.js.map
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"sources":["../../../../src/build/analysis/parse-module.ts"],"sourcesContent":["import { LRUCache } from '../../server/lib/lru-cache'\nimport { withPromiseCache } from '../../lib/with-promise-cache'\nimport { createHash } from 'crypto'\nimport { parse } from '../swc'\n\n/**\n * Parses a module with SWC using an LRU cache where the parsed module will\n * be indexed by a sha of its content holding up to 500 entries.\n */\nexport const parseModule = withPromiseCache(\n new LRUCache<any>(500),\n async (filename: string, content: string) =>\n parse(content, { isModule: 'unknown', filename }).catch(() => null),\n (_, content) => createHash('sha1').update(content).digest('hex')\n)\n"],"names":["LRUCache","withPromiseCache","createHash","parse","parseModule","filename","content","isModule","catch","_","update","digest"],"mappings":"AAAA,SAASA,QAAQ,QAAQ,6BAA4B;AACrD,SAASC,gBAAgB,QAAQ,+BAA8B;AAC/D,SAASC,UAAU,QAAQ,SAAQ;AACnC,SAASC,KAAK,QAAQ,SAAQ;AAE9B;;;CAGC,GACD,OAAO,MAAMC,cAAcH,iBACzB,IAAID,SAAc,MAClB,OAAOK,UAAkBC,UACvBH,MAAMG,SAAS;QAAEC,UAAU;QAAWF;IAAS,GAAGG,KAAK,CAAC,IAAM,OAChE,CAACC,GAAGH,UAAYJ,WAAW,QAAQQ,MAAM,CAACJ,SAASK,MAAM,CAAC,QAC3D","ignoreList":[0]}
|
||||
Reference in New Issue
Block a user