765 lines
40 KiB
JavaScript
765 lines
40 KiB
JavaScript
"use strict";
|
|
Object.defineProperty(exports, "__esModule", {
|
|
value: true
|
|
});
|
|
Object.defineProperty(exports, "default", {
|
|
enumerable: true,
|
|
get: function() {
|
|
return _default1;
|
|
}
|
|
});
|
|
const _constants = require("../../../../shared/lib/constants");
|
|
const _entryconstants = require("../../../../shared/lib/entry-constants");
|
|
const _path = /*#__PURE__*/ _interop_require_default(require("path"));
|
|
const _picocolors = require("../../../../lib/picocolors");
|
|
const _getmodulebuildinfo = require("../get-module-build-info");
|
|
const _verifyrootlayout = require("../../../../lib/verify-root-layout");
|
|
const _log = /*#__PURE__*/ _interop_require_wildcard(require("../../../output/log"));
|
|
const _constants1 = require("../../../../lib/constants");
|
|
const _discover = require("../metadata/discover");
|
|
const _fs = require("fs");
|
|
const _isapprouteroute = require("../../../../lib/is-app-route-route");
|
|
const _apppathnamenormalizer = require("../../../../server/normalizers/built/app/app-pathname-normalizer");
|
|
const _utils = require("../../../utils");
|
|
const _loadentrypoint = require("../../../load-entrypoint");
|
|
const _segment = require("../../../../shared/lib/segment");
|
|
const _getfilesindir = require("../../../../lib/get-files-in-dir");
|
|
const _default = require("../../../../client/components/builtin/default");
|
|
const _defaultnull = require("../../../../client/components/builtin/default-null");
|
|
const _createapproutecode = require("./create-app-route-code");
|
|
const _missingdefaultparallelrouteerror = require("../../../../shared/lib/errors/missing-default-parallel-route-error");
|
|
const _interceptionroutes = require("../../../../shared/lib/router/utils/interception-routes");
|
|
const _normalizepathsep = require("../../../../shared/lib/page-path/normalize-path-sep");
|
|
const _installbindings = require("../../../swc/install-bindings");
|
|
function _interop_require_default(obj) {
|
|
return obj && obj.__esModule ? obj : {
|
|
default: obj
|
|
};
|
|
}
|
|
function _getRequireWildcardCache(nodeInterop) {
|
|
if (typeof WeakMap !== "function") return null;
|
|
var cacheBabelInterop = new WeakMap();
|
|
var cacheNodeInterop = new WeakMap();
|
|
return (_getRequireWildcardCache = function(nodeInterop) {
|
|
return nodeInterop ? cacheNodeInterop : cacheBabelInterop;
|
|
})(nodeInterop);
|
|
}
|
|
function _interop_require_wildcard(obj, nodeInterop) {
|
|
if (!nodeInterop && obj && obj.__esModule) {
|
|
return obj;
|
|
}
|
|
if (obj === null || typeof obj !== "object" && typeof obj !== "function") {
|
|
return {
|
|
default: obj
|
|
};
|
|
}
|
|
var cache = _getRequireWildcardCache(nodeInterop);
|
|
if (cache && cache.has(obj)) {
|
|
return cache.get(obj);
|
|
}
|
|
var newObj = {
|
|
__proto__: null
|
|
};
|
|
var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor;
|
|
for(var key in obj){
|
|
if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) {
|
|
var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null;
|
|
if (desc && (desc.get || desc.set)) {
|
|
Object.defineProperty(newObj, key, desc);
|
|
} else {
|
|
newObj[key] = obj[key];
|
|
}
|
|
}
|
|
}
|
|
newObj.default = obj;
|
|
if (cache) {
|
|
cache.set(obj, newObj);
|
|
}
|
|
return newObj;
|
|
}
|
|
const HTTP_ACCESS_FALLBACKS = {
|
|
'not-found': 'not-found',
|
|
forbidden: 'forbidden',
|
|
unauthorized: 'unauthorized'
|
|
};
|
|
const defaultHTTPAccessFallbackPaths = {
|
|
'not-found': 'next/dist/client/components/builtin/not-found.js',
|
|
forbidden: 'next/dist/client/components/builtin/forbidden.js',
|
|
unauthorized: 'next/dist/client/components/builtin/unauthorized.js'
|
|
};
|
|
const FILE_TYPES = {
|
|
layout: 'layout',
|
|
template: 'template',
|
|
error: 'error',
|
|
loading: 'loading',
|
|
'global-error': 'global-error',
|
|
'global-not-found': 'global-not-found',
|
|
...HTTP_ACCESS_FALLBACKS
|
|
};
|
|
const GLOBAL_ERROR_FILE_TYPE = 'global-error';
|
|
const GLOBAL_NOT_FOUND_FILE_TYPE = 'global-not-found';
|
|
const PAGE_SEGMENT = 'page$';
|
|
const PARALLEL_VIRTUAL_SEGMENT = 'slot$';
|
|
const defaultGlobalErrorPath = 'next/dist/client/components/builtin/global-error.js';
|
|
const defaultNotFoundPath = 'next/dist/client/components/builtin/not-found.js';
|
|
const defaultEmptyStubPath = 'next/dist/client/components/builtin/empty-stub.js';
|
|
const defaultLayoutPath = 'next/dist/client/components/builtin/layout.js';
|
|
const defaultGlobalNotFoundPath = 'next/dist/client/components/builtin/global-not-found.js';
|
|
const appErrorPath = 'next/dist/client/components/builtin/app-error.js';
|
|
const normalizeParallelKey = (key)=>key.startsWith('@') ? key.slice(1) : key;
|
|
const isCatchAllSegment = (segment)=>segment.startsWith('[...') || segment.startsWith('[[...');
|
|
const isDirectory = async (pathname)=>{
|
|
try {
|
|
const stat = await _fs.promises.stat(pathname);
|
|
return stat.isDirectory();
|
|
} catch (err) {
|
|
return false;
|
|
}
|
|
};
|
|
async function createTreeCodeFromPath(pagePath, { page, resolveDir, resolver, resolveParallelSegments, hasChildRoutesForSegment, metadataResolver, pageExtensions, basePath, collectedDeclarations, isGlobalNotFoundEnabled }) {
|
|
const splittedPath = pagePath.split(/[\\/]/, 1);
|
|
const isNotFoundRoute = page === _entryconstants.UNDERSCORE_NOT_FOUND_ROUTE_ENTRY;
|
|
const isAppErrorRoute = page === _entryconstants.UNDERSCORE_GLOBAL_ERROR_ROUTE_ENTRY;
|
|
const isDefaultNotFound = (0, _utils.isAppBuiltinPage)(pagePath);
|
|
const appDirPrefix = isDefaultNotFound ? _constants1.APP_DIR_ALIAS : splittedPath[0];
|
|
let rootLayout;
|
|
let globalError = defaultGlobalErrorPath;
|
|
let globalNotFound = defaultNotFoundPath;
|
|
async function resolveAdjacentParallelSegments(segmentPath) {
|
|
const absoluteSegmentPath = resolveDir(`${appDirPrefix}${segmentPath}`);
|
|
if (!absoluteSegmentPath) {
|
|
return [];
|
|
}
|
|
const segmentIsDirectory = await isDirectory(absoluteSegmentPath);
|
|
if (!segmentIsDirectory) {
|
|
return [];
|
|
}
|
|
// We need to resolve all parallel routes in this level.
|
|
const files = await _fs.promises.opendir(absoluteSegmentPath);
|
|
const parallelSegments = [
|
|
'children'
|
|
];
|
|
for await (const dirent of files){
|
|
// Make sure name starts with "@" and is a directory.
|
|
if (dirent.isDirectory() && dirent.name.charCodeAt(0) === 64) {
|
|
parallelSegments.push(dirent.name);
|
|
}
|
|
}
|
|
return parallelSegments;
|
|
}
|
|
async function createSubtreePropsFromSegmentPath(segments, nestedCollectedDeclarations) {
|
|
const segmentPath = segments.join('/');
|
|
// Existing tree are the children of the current segment
|
|
const props = {};
|
|
// Root layer could be 1st layer of normal routes
|
|
const isRootLayer = segments.length === 0;
|
|
const isRootLayoutOrRootPage = segments.length <= 1;
|
|
// We need to resolve all parallel routes in this level.
|
|
const parallelSegments = [];
|
|
if (isRootLayer) {
|
|
parallelSegments.push([
|
|
'children',
|
|
''
|
|
]);
|
|
} else {
|
|
parallelSegments.push(...resolveParallelSegments(segmentPath));
|
|
}
|
|
let metadata = null;
|
|
const routerDirPath = `${appDirPrefix}${segmentPath}`;
|
|
const resolvedRouteDir = resolveDir(routerDirPath);
|
|
if (resolvedRouteDir && // Do not collect metadata for app-error route as it's for generating pure static 500.html
|
|
!(0, _normalizepathsep.normalizePathSep)(pagePath).endsWith(appErrorPath)) {
|
|
metadata = await (0, _discover.createStaticMetadataFromRoute)(resolvedRouteDir, {
|
|
basePath,
|
|
segment: segmentPath,
|
|
metadataResolver,
|
|
isRootLayoutOrRootPage,
|
|
pageExtensions
|
|
});
|
|
}
|
|
for (const [parallelKey, parallelSegment] of parallelSegments){
|
|
// if parallelSegment is the page segment (ie, `page$` and not ['page$']), it gets loaded into the __PAGE__ slot
|
|
// as it's the page for the current route.
|
|
if (parallelSegment === PAGE_SEGMENT) {
|
|
const matchedPagePath = `${appDirPrefix}${segmentPath}${parallelKey === 'children' ? '' : `/${parallelKey}`}/page`;
|
|
const resolvedPagePath = await resolver(matchedPagePath);
|
|
if (resolvedPagePath) {
|
|
const varName = `page${nestedCollectedDeclarations.length}`;
|
|
nestedCollectedDeclarations.push([
|
|
varName,
|
|
resolvedPagePath
|
|
]);
|
|
// Use '' for segment as it's the page. There can't be a segment called '' so this is the safest way to add it.
|
|
props[normalizeParallelKey(parallelKey)] = `['${_segment.PAGE_SEGMENT_KEY}', {}, {
|
|
page: [${varName}, ${JSON.stringify(resolvedPagePath)}],
|
|
${(0, _discover.createMetadataExportsCode)(metadata)}
|
|
}]`;
|
|
continue;
|
|
} else {
|
|
throw Object.defineProperty(new Error(`Can't resolve ${matchedPagePath}`), "__NEXT_ERROR_CODE", {
|
|
value: "E711",
|
|
enumerable: false,
|
|
configurable: true
|
|
});
|
|
}
|
|
}
|
|
// if the parallelSegment was not matched to the __PAGE__ slot, then it's a parallel route at this level.
|
|
// the code below recursively traverses the parallel slots directory to match the corresponding __PAGE__ for each parallel slot
|
|
// while also filling in layout/default/etc files into the loader tree at each segment level.
|
|
const subSegmentPath = [
|
|
...segments
|
|
];
|
|
if (parallelKey !== 'children') {
|
|
// A `children` parallel key should have already been processed in the above segment
|
|
// So we exclude it when constructing the subsegment path for the remaining segment levels
|
|
subSegmentPath.push(parallelKey);
|
|
}
|
|
const normalizedParallelSegment = Array.isArray(parallelSegment) ? parallelSegment[0] : parallelSegment;
|
|
if (normalizedParallelSegment !== PAGE_SEGMENT && normalizedParallelSegment !== PARALLEL_VIRTUAL_SEGMENT) {
|
|
// If we don't have a page segment, nor a special $children marker, it means we need to traverse the next directory
|
|
// (ie, `normalizedParallelSegment` would correspond with the folder that contains the next level of pages/layout/etc)
|
|
// we push it to the subSegmentPath so that we can fill in the loader tree for that segment.
|
|
subSegmentPath.push(normalizedParallelSegment);
|
|
}
|
|
const parallelSegmentPath = subSegmentPath.join('/');
|
|
// Fill in the loader tree for all of the special files types (layout, default, etc) at this level
|
|
// `page` is not included here as it's added above.
|
|
const filePathEntries = await Promise.all(Object.values(FILE_TYPES).map(async (file)=>{
|
|
return [
|
|
file,
|
|
await resolver(`${appDirPrefix}${// TODO-APP: parallelSegmentPath sometimes ends in `/` but sometimes it doesn't. This should be consistent.
|
|
parallelSegmentPath.endsWith('/') ? parallelSegmentPath : parallelSegmentPath + '/'}${file}`)
|
|
];
|
|
}));
|
|
const filePaths = new Map(filePathEntries);
|
|
// Only resolve global-* convention files at the root layer
|
|
if (isRootLayer) {
|
|
const resolvedGlobalErrorPath = await resolver(`${appDirPrefix}/${GLOBAL_ERROR_FILE_TYPE}`);
|
|
if (resolvedGlobalErrorPath) {
|
|
globalError = resolvedGlobalErrorPath;
|
|
}
|
|
// Add global-error to root layer's filePaths, so that it's always available,
|
|
// by default it's the built-in global-error.js
|
|
filePaths.set(GLOBAL_ERROR_FILE_TYPE, globalError);
|
|
// TODO(global-not-found): remove this flag assertion condition
|
|
// once global-not-found is stable
|
|
if (isGlobalNotFoundEnabled) {
|
|
const resolvedGlobalNotFoundPath = await resolver(`${appDirPrefix}/${GLOBAL_NOT_FOUND_FILE_TYPE}`);
|
|
if (resolvedGlobalNotFoundPath) {
|
|
globalNotFound = resolvedGlobalNotFoundPath;
|
|
}
|
|
// Add global-not-found to root layer's filePaths, so that it's always available,
|
|
// by default it's the built-in global-not-found.js
|
|
filePaths.set(GLOBAL_NOT_FOUND_FILE_TYPE, globalNotFound);
|
|
}
|
|
}
|
|
let definedFilePaths = Array.from(filePaths.entries()).filter(([, filePath])=>filePath !== undefined);
|
|
// Add default access fallback as root fallback if not present
|
|
const existedConventionNames = new Set(definedFilePaths.map(([type])=>type));
|
|
// If the first layer is a group route, we treat it as root layer
|
|
const isFirstLayerGroupRoute = segments.length === 1 && subSegmentPath.filter((seg)=>(0, _segment.isGroupSegment)(seg)).length === 1;
|
|
if (isRootLayer || isFirstLayerGroupRoute) {
|
|
const accessFallbackTypes = Object.keys(defaultHTTPAccessFallbackPaths);
|
|
for (const type of accessFallbackTypes){
|
|
const hasRootFallbackFile = await resolver(`${appDirPrefix}/${FILE_TYPES[type]}`);
|
|
const hasLayerFallbackFile = existedConventionNames.has(type);
|
|
// If you already have a root access error fallback, don't insert default access error boundary to group routes root
|
|
if (// Is treated as root layout and without boundary
|
|
!(hasRootFallbackFile && isFirstLayerGroupRoute) && // Does not have a fallback boundary file
|
|
!hasLayerFallbackFile) {
|
|
const defaultFallbackPath = defaultHTTPAccessFallbackPaths[type];
|
|
if (!(isDefaultNotFound && type === 'not-found')) {
|
|
definedFilePaths.push([
|
|
type,
|
|
defaultFallbackPath
|
|
]);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (!rootLayout) {
|
|
var _definedFilePaths_find;
|
|
const layoutPath = (_definedFilePaths_find = definedFilePaths.find(([type])=>type === 'layout')) == null ? void 0 : _definedFilePaths_find[1];
|
|
rootLayout = layoutPath;
|
|
// When `global-not-found` is disabled, we insert a default layout if
|
|
// root layout is presented. This logic and the default layout will be removed
|
|
// once `global-not-found` is stabilized.
|
|
if (!isGlobalNotFoundEnabled && isDefaultNotFound && !layoutPath && !rootLayout) {
|
|
rootLayout = defaultLayoutPath;
|
|
definedFilePaths.push([
|
|
'layout',
|
|
rootLayout
|
|
]);
|
|
}
|
|
}
|
|
let parallelSegmentKey = Array.isArray(parallelSegment) ? parallelSegment[0] : parallelSegment;
|
|
// normalize the parallel segment key to remove any special markers that we inserted in the
|
|
// earlier logic (such as children$ and page$). These should never appear in the loader tree, and
|
|
// should instead be the corresponding segment keys (ie `__PAGE__`) or the `children` parallel route.
|
|
parallelSegmentKey = parallelSegmentKey === PARALLEL_VIRTUAL_SEGMENT ? '(slot)' : parallelSegmentKey === PAGE_SEGMENT ? _segment.PAGE_SEGMENT_KEY : parallelSegmentKey;
|
|
const normalizedParallelKey = normalizeParallelKey(parallelKey);
|
|
let subtreeCode;
|
|
// If it's root not found page, set not-found boundary as children page
|
|
if (isNotFoundRoute) {
|
|
if (normalizedParallelKey === 'children') {
|
|
var _definedFilePaths_find1;
|
|
const matchedGlobalNotFound = isGlobalNotFoundEnabled ? ((_definedFilePaths_find1 = definedFilePaths.find(([type])=>type === GLOBAL_NOT_FOUND_FILE_TYPE)) == null ? void 0 : _definedFilePaths_find1[1]) ?? defaultGlobalNotFoundPath : undefined;
|
|
// If custom global-not-found.js is defined, use global-not-found.js
|
|
if (matchedGlobalNotFound) {
|
|
const varName = `notFound${nestedCollectedDeclarations.length}`;
|
|
nestedCollectedDeclarations.push([
|
|
varName,
|
|
matchedGlobalNotFound
|
|
]);
|
|
const layoutName = `layout${nestedCollectedDeclarations.length}`;
|
|
nestedCollectedDeclarations.push([
|
|
layoutName,
|
|
defaultEmptyStubPath
|
|
]);
|
|
subtreeCode = `{
|
|
children: [${JSON.stringify(_constants.UNDERSCORE_NOT_FOUND_ROUTE)}, {
|
|
children: ['${_segment.PAGE_SEGMENT_KEY}', {}, {
|
|
layout: [
|
|
${varName},
|
|
${JSON.stringify(matchedGlobalNotFound)}
|
|
],
|
|
page: [
|
|
${layoutName},
|
|
${JSON.stringify(defaultEmptyStubPath)}
|
|
]
|
|
}]
|
|
}, {}]
|
|
}`;
|
|
} else {
|
|
var _definedFilePaths_find2;
|
|
// If custom not-found.js is found, use it and layout to compose the page,
|
|
// and fallback to built-in not-found component if doesn't exist.
|
|
const notFoundPath = ((_definedFilePaths_find2 = definedFilePaths.find(([type])=>type === 'not-found')) == null ? void 0 : _definedFilePaths_find2[1]) ?? defaultNotFoundPath;
|
|
const varName = `notFound${nestedCollectedDeclarations.length}`;
|
|
nestedCollectedDeclarations.push([
|
|
varName,
|
|
notFoundPath
|
|
]);
|
|
subtreeCode = `{
|
|
children: [${JSON.stringify(_constants.UNDERSCORE_NOT_FOUND_ROUTE.slice(1))}, {
|
|
children: ['${_segment.PAGE_SEGMENT_KEY}', {}, {
|
|
page: [
|
|
${varName},
|
|
${JSON.stringify(notFoundPath)}
|
|
]
|
|
}]
|
|
}, {}]
|
|
}`;
|
|
}
|
|
}
|
|
}
|
|
// If it's app-error route, set app-error as children page
|
|
if (isAppErrorRoute) {
|
|
const varName = `appError${nestedCollectedDeclarations.length}`;
|
|
nestedCollectedDeclarations.push([
|
|
varName,
|
|
appErrorPath
|
|
]);
|
|
subtreeCode = `{
|
|
children: [${JSON.stringify(_constants.UNDERSCORE_GLOBAL_ERROR_ROUTE.slice(1))}, {
|
|
children: ['${_segment.PAGE_SEGMENT_KEY}', {}, {
|
|
page: [
|
|
${varName},
|
|
${JSON.stringify(appErrorPath)}
|
|
]
|
|
}]
|
|
}, {}]
|
|
}`;
|
|
}
|
|
// For 404 route
|
|
// if global-not-found is in definedFilePaths, remove root layout for /_not-found,
|
|
// and change it to global-not-found route.
|
|
// TODO: remove this once global-not-found is stable.
|
|
if (isNotFoundRoute && isGlobalNotFoundEnabled) {
|
|
var _definedFilePaths_find3;
|
|
definedFilePaths = definedFilePaths.filter(([type])=>type !== 'layout');
|
|
// Replace the layout to global-not-found
|
|
definedFilePaths.push([
|
|
'layout',
|
|
((_definedFilePaths_find3 = definedFilePaths.find(([type])=>type === GLOBAL_NOT_FOUND_FILE_TYPE)) == null ? void 0 : _definedFilePaths_find3[1]) ?? defaultGlobalNotFoundPath
|
|
]);
|
|
}
|
|
if (isAppErrorRoute) {
|
|
definedFilePaths = definedFilePaths.filter(([type])=>type !== 'layout');
|
|
}
|
|
const modulesCode = `{
|
|
${definedFilePaths.map(([file, filePath])=>{
|
|
const varName = `module${nestedCollectedDeclarations.length}`;
|
|
nestedCollectedDeclarations.push([
|
|
varName,
|
|
filePath
|
|
]);
|
|
return `'${file}': [${varName}, ${JSON.stringify(filePath)}],`;
|
|
}).join('\n')}
|
|
${(0, _discover.createMetadataExportsCode)(metadata)}
|
|
}`;
|
|
if (!subtreeCode) {
|
|
const { treeCode: pageSubtreeCode } = await createSubtreePropsFromSegmentPath(subSegmentPath, nestedCollectedDeclarations);
|
|
subtreeCode = pageSubtreeCode;
|
|
}
|
|
props[normalizedParallelKey] = `[
|
|
'${parallelSegmentKey}',
|
|
${subtreeCode},
|
|
${modulesCode}
|
|
]`;
|
|
}
|
|
const adjacentParallelSegments = await resolveAdjacentParallelSegments(segmentPath);
|
|
for (const adjacentParallelSegment of adjacentParallelSegments){
|
|
if (!props[normalizeParallelKey(adjacentParallelSegment)]) {
|
|
const actualSegment = adjacentParallelSegment === 'children' ? '' : `/${adjacentParallelSegment}`;
|
|
// Use the default path if it's found, otherwise if it's a children
|
|
// slot, then use the fallback (which triggers a `notFound()`). If this
|
|
// isn't a children slot, then throw an error, as it produces a silent
|
|
// 404 if we'd used the fallback.
|
|
const fullSegmentPath = `${appDirPrefix}${segmentPath}${actualSegment}`;
|
|
let defaultPath = await resolver(`${fullSegmentPath}/default`);
|
|
if (!defaultPath) {
|
|
if (adjacentParallelSegment === 'children') {
|
|
// When we host applications on Vercel, the status code affects the
|
|
// underlying behavior of the route, which when we are missing the
|
|
// children slot of an interception route, will yield a full 404
|
|
// response for the RSC request instead. For this reason, we expect
|
|
// that if a default file is missing when we're rendering an
|
|
// interception route, we instead always render null for the default
|
|
// slot to avoid the full 404 response.
|
|
if ((0, _interceptionroutes.isInterceptionRouteAppPath)(page)) {
|
|
defaultPath = _defaultnull.PARALLEL_ROUTE_DEFAULT_NULL_PATH;
|
|
} else {
|
|
defaultPath = _default.PARALLEL_ROUTE_DEFAULT_PATH;
|
|
}
|
|
} else {
|
|
// Check if we're inside a catch-all route (i.e., the parallel route is a child
|
|
// of a catch-all segment). Only skip validation if the slot is UNDER a catch-all.
|
|
// For example:
|
|
// /[...catchAll]/@slot - isInsideCatchAll = true (skip validation) ✓
|
|
// /@slot/[...catchAll] - isInsideCatchAll = false (require default) ✓
|
|
// The catch-all provides fallback behavior, so default.js is not required.
|
|
const isInsideCatchAll = segments.some(isCatchAllSegment);
|
|
// Check if this is a leaf segment (no child routes).
|
|
// Leaf segments don't need default.js because there are no child routes
|
|
// that could cause the parallel slot to unmatch. For example:
|
|
// /repo-overview/@slot/page with no child routes - isLeafSegment = true (skip validation) ✓
|
|
// /repo-overview/@slot/page with /repo-overview/child/page - isLeafSegment = false (require default) ✓
|
|
// This also handles route groups correctly by filtering them out.
|
|
const isLeafSegment = !hasChildRoutesForSegment(segmentPath);
|
|
if (!isInsideCatchAll && !isLeafSegment) {
|
|
// Replace internal webpack alias with user-facing directory name
|
|
const userFacingPath = fullSegmentPath.replace(_constants1.APP_DIR_ALIAS, 'app');
|
|
throw new _missingdefaultparallelrouteerror.MissingDefaultParallelRouteError(userFacingPath, adjacentParallelSegment);
|
|
}
|
|
defaultPath = _default.PARALLEL_ROUTE_DEFAULT_PATH;
|
|
}
|
|
}
|
|
const varName = `default${nestedCollectedDeclarations.length}`;
|
|
nestedCollectedDeclarations.push([
|
|
varName,
|
|
defaultPath
|
|
]);
|
|
props[normalizeParallelKey(adjacentParallelSegment)] = `[
|
|
'${_segment.DEFAULT_SEGMENT_KEY}',
|
|
{},
|
|
{
|
|
defaultPage: [${varName}, ${JSON.stringify(defaultPath)}],
|
|
}
|
|
]`;
|
|
}
|
|
}
|
|
return {
|
|
treeCode: `{
|
|
${Object.entries(props).map(([key, value])=>`${key}: ${value}`).join(',\n')}
|
|
}`
|
|
};
|
|
}
|
|
const { treeCode } = await createSubtreePropsFromSegmentPath([], collectedDeclarations);
|
|
return {
|
|
treeCode: `${treeCode}.children;`,
|
|
rootLayout,
|
|
globalError,
|
|
globalNotFound
|
|
};
|
|
}
|
|
function createAbsolutePath(appDir, pathToTurnAbsolute) {
|
|
return pathToTurnAbsolute// Replace all POSIX path separators with the current OS path separator
|
|
.replace(/\//g, _path.default.sep).replace(/^private-next-app-dir/, appDir);
|
|
}
|
|
const filesInDirMapMap = new WeakMap();
|
|
const nextAppLoader = async function nextAppLoader() {
|
|
// install native bindings early so they are always available.
|
|
// When run by webpack, next will have already done this, so this will be fast,
|
|
// but if run by turbopack in a subprocess it is required. In that case we cannot pass the
|
|
// `useWasmBinary` flag, but that is ok since turbopack doesn't currently support wasm.
|
|
await (0, _installbindings.installBindings)();
|
|
const loaderOptions = this.getOptions();
|
|
const { name, appDir, appPaths, pagePath, pageExtensions, rootDir, tsconfigPath, isDev, nextConfigOutput, preferredRegion, basePath, middlewareConfig: middlewareConfigBase64 } = loaderOptions;
|
|
const isGlobalNotFoundEnabled = !!loaderOptions.isGlobalNotFoundEnabled;
|
|
// Update FILE_TYPES on the very top-level of the loader
|
|
if (!isGlobalNotFoundEnabled) {
|
|
// @ts-expect-error this delete is only necessary while experimental
|
|
delete FILE_TYPES['global-not-found'];
|
|
}
|
|
const buildInfo = (0, _getmodulebuildinfo.getModuleBuildInfo)(this._module);
|
|
const collectedDeclarations = [];
|
|
// Use the page from loaderOptions directly instead of deriving it from name.
|
|
// The name (bundlePath) may have been normalized with normalizePagePath()
|
|
// which is designed for Pages Router and incorrectly duplicates /index paths
|
|
// (e.g., /index/page -> /index/index/page). The page parameter contains the
|
|
// correct unnormalized value.
|
|
const page = loaderOptions.page;
|
|
const middlewareConfig = JSON.parse(Buffer.from(middlewareConfigBase64, 'base64').toString());
|
|
buildInfo.route = {
|
|
page,
|
|
absolutePagePath: createAbsolutePath(appDir, pagePath),
|
|
preferredRegion,
|
|
middlewareConfig,
|
|
relatedModules: []
|
|
};
|
|
const extensions = typeof pageExtensions === 'string' ? [
|
|
pageExtensions
|
|
] : pageExtensions.map((extension)=>`.${extension}`);
|
|
const normalizedAppPaths = typeof appPaths === 'string' ? [
|
|
appPaths
|
|
] : appPaths || [];
|
|
const resolveParallelSegments = (pathname)=>{
|
|
const matched = {};
|
|
let existingChildrenPath;
|
|
for (const appPath of normalizedAppPaths){
|
|
if (appPath.startsWith(pathname + '/')) {
|
|
const rest = appPath.slice(pathname.length + 1).split('/');
|
|
// It is the actual page, mark it specially.
|
|
if (rest.length === 1 && rest[0] === 'page') {
|
|
existingChildrenPath = appPath;
|
|
matched.children = PAGE_SEGMENT;
|
|
continue;
|
|
}
|
|
const isParallelRoute = rest[0].startsWith('@');
|
|
if (isParallelRoute) {
|
|
if (rest.length === 2 && rest[1] === 'page') {
|
|
// We found a parallel route at this level. We don't want to mark it explicitly as the page segment,
|
|
// as that should be matched to the `children` slot. Instead, we use an array, to signal to `createSubtreePropsFromSegmentPath`
|
|
// that it needs to recursively fill in the loader tree code for the parallel route at the appropriate levels.
|
|
matched[rest[0]] = [
|
|
PAGE_SEGMENT
|
|
];
|
|
continue;
|
|
}
|
|
// If it was a parallel route but we weren't able to find the page segment (ie, maybe the page is nested further)
|
|
// we first insert a special marker to ensure that we still process layout/default/etc at the slot level prior to continuing
|
|
// on to the page segment.
|
|
matched[rest[0]] = [
|
|
PARALLEL_VIRTUAL_SEGMENT,
|
|
...rest.slice(1)
|
|
];
|
|
continue;
|
|
}
|
|
if (existingChildrenPath && matched.children !== rest[0]) {
|
|
// If we get here, it means we already set a `page` segment earlier in the loop,
|
|
// meaning we already matched a page to the `children` parallel segment.
|
|
const isIncomingParallelPage = appPath.includes('@');
|
|
const hasCurrentParallelPage = existingChildrenPath.includes('@');
|
|
if (isIncomingParallelPage) {
|
|
continue;
|
|
} else if (!hasCurrentParallelPage && !isIncomingParallelPage) {
|
|
// Both the current `children` and the incoming `children` are regular pages.
|
|
throw Object.defineProperty(new Error(`You cannot have two parallel pages that resolve to the same path. Please check ${existingChildrenPath} and ${appPath}. Refer to the route group docs for more information: https://nextjs.org/docs/app/building-your-application/routing/route-groups`), "__NEXT_ERROR_CODE", {
|
|
value: "E28",
|
|
enumerable: false,
|
|
configurable: true
|
|
});
|
|
}
|
|
}
|
|
existingChildrenPath = appPath;
|
|
matched.children = rest[0];
|
|
}
|
|
}
|
|
return Object.entries(matched);
|
|
};
|
|
const hasChildRoutesForSegment = (segmentPath)=>{
|
|
const pathPrefix = segmentPath ? `${segmentPath}/` : '';
|
|
for (const appPath of normalizedAppPaths){
|
|
if (appPath.startsWith(pathPrefix)) {
|
|
var _routeSegments_;
|
|
const rest = appPath.slice(pathPrefix.length).split('/');
|
|
// Filter out route groups to get the actual route segments
|
|
// Route groups (e.g., "(group)") don't contribute to the URL path
|
|
const routeSegments = rest.filter((segment)=>!(0, _segment.isGroupSegment)(segment));
|
|
// If it's just 'page' at this level, skip (not a child route)
|
|
if (routeSegments.length === 1 && routeSegments[0] === 'page') {
|
|
continue;
|
|
}
|
|
// If the first segment (after filtering route groups) is a parallel route, skip
|
|
if ((_routeSegments_ = routeSegments[0]) == null ? void 0 : _routeSegments_.startsWith('@')) {
|
|
continue;
|
|
}
|
|
// If we have more than just 'page', then there are child routes
|
|
// Examples:
|
|
// ['child', 'page'] -> true (has child route)
|
|
// ['page'] -> false (already filtered above)
|
|
// ['grandchild', 'deeper', 'page'] -> true (has nested child routes)
|
|
if (routeSegments.length > 1 || routeSegments.length === 1 && routeSegments[0] !== 'page') {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
};
|
|
const resolveDir = (pathToResolve)=>{
|
|
return createAbsolutePath(appDir, pathToResolve);
|
|
};
|
|
const resolveAppRoute = (pathToResolve)=>{
|
|
return createAbsolutePath(appDir, pathToResolve);
|
|
};
|
|
// Cached checker to see if a file exists in a given directory.
|
|
// This can be more efficient than checking them with `fs.stat` one by one
|
|
// because all the thousands of files are likely in a few possible directories.
|
|
// Note that it should only be cached for this compilation, not globally.
|
|
const fileExistsInDirectory = async (dirname, fileName)=>{
|
|
// I don't think we should ever hit this code path, but if we do we should handle it gracefully.
|
|
if (this._compilation === undefined) {
|
|
try {
|
|
return (await (0, _getfilesindir.getFilesInDir)(dirname).catch(()=>new Set())).has(fileName);
|
|
} catch (e) {
|
|
return false;
|
|
}
|
|
}
|
|
const map = filesInDirMapMap.get(this._compilation) || new Map();
|
|
if (!filesInDirMapMap.has(this._compilation)) {
|
|
filesInDirMapMap.set(this._compilation, map);
|
|
}
|
|
if (!map.has(dirname)) {
|
|
map.set(dirname, (0, _getfilesindir.getFilesInDir)(dirname).catch(()=>new Set()));
|
|
}
|
|
return (await map.get(dirname) || new Set()).has(fileName);
|
|
};
|
|
const resolver = async (pathname)=>{
|
|
const absolutePath = createAbsolutePath(appDir, pathname);
|
|
const filenameIndex = absolutePath.lastIndexOf(_path.default.sep);
|
|
const dirname = absolutePath.slice(0, filenameIndex);
|
|
const filename = absolutePath.slice(filenameIndex + 1);
|
|
let result;
|
|
for (const ext of extensions){
|
|
const absolutePathWithExtension = `${absolutePath}${ext}`;
|
|
if (!result && await fileExistsInDirectory(dirname, `${filename}${ext}`)) {
|
|
result = absolutePathWithExtension;
|
|
}
|
|
// Call `addMissingDependency` for all files even if they didn't match,
|
|
// because they might be added or removed during development.
|
|
this.addMissingDependency(absolutePathWithExtension);
|
|
}
|
|
return result;
|
|
};
|
|
const metadataResolver = async (dirname, filename, exts)=>{
|
|
const absoluteDir = createAbsolutePath(appDir, dirname);
|
|
let result;
|
|
for (const ext of exts){
|
|
// Compared to `resolver` above the exts do not have the `.` included already, so it's added here.
|
|
const filenameWithExt = `${filename}.${ext}`;
|
|
const absolutePathWithExtension = `${absoluteDir}${_path.default.sep}${filenameWithExt}`;
|
|
if (!result && await fileExistsInDirectory(dirname, filenameWithExt)) {
|
|
result = absolutePathWithExtension;
|
|
}
|
|
// Call `addMissingDependency` for all files even if they didn't match,
|
|
// because they might be added or removed during development.
|
|
this.addMissingDependency(absolutePathWithExtension);
|
|
}
|
|
return result;
|
|
};
|
|
if ((0, _isapprouteroute.isAppRouteRoute)(name)) {
|
|
return (0, _createapproutecode.createAppRouteCode)({
|
|
appDir,
|
|
// TODO: investigate if the local `page` is the same as the loaderOptions.page
|
|
page: loaderOptions.page,
|
|
name,
|
|
pagePath,
|
|
resolveAppRoute,
|
|
pageExtensions,
|
|
nextConfigOutput
|
|
});
|
|
}
|
|
let treeCodeResult = await createTreeCodeFromPath(pagePath, {
|
|
page,
|
|
resolveDir,
|
|
resolver,
|
|
metadataResolver,
|
|
resolveParallelSegments,
|
|
hasChildRoutesForSegment,
|
|
loaderContext: this,
|
|
pageExtensions,
|
|
basePath,
|
|
collectedDeclarations,
|
|
isGlobalNotFoundEnabled
|
|
});
|
|
const isGlobalNotFoundPath = page === _entryconstants.UNDERSCORE_NOT_FOUND_ROUTE_ENTRY && !!treeCodeResult.globalNotFound && isGlobalNotFoundEnabled;
|
|
const isAppErrorRoute = page === _entryconstants.UNDERSCORE_GLOBAL_ERROR_ROUTE_ENTRY;
|
|
if (!treeCodeResult.rootLayout && !isGlobalNotFoundPath && !isAppErrorRoute) {
|
|
if (!isDev) {
|
|
// If we're building and missing a root layout, exit the build
|
|
_log.error(`${(0, _picocolors.bold)(pagePath.replace(`${_constants1.APP_DIR_ALIAS}/`, ''))} doesn't have a root layout. To fix this error, make sure every page has a root layout.`);
|
|
process.exit(1);
|
|
} else {
|
|
var _filesInDirMapMap_get;
|
|
// In dev we'll try to create a root layout
|
|
const [createdRootLayout, rootLayoutPath] = await (0, _verifyrootlayout.verifyRootLayout)({
|
|
appDir: appDir,
|
|
dir: rootDir,
|
|
tsconfigPath: tsconfigPath,
|
|
pagePath,
|
|
pageExtensions
|
|
});
|
|
if (!createdRootLayout) {
|
|
let message = `${(0, _picocolors.bold)(pagePath.replace(`${_constants1.APP_DIR_ALIAS}/`, ''))} doesn't have a root layout. `;
|
|
if (rootLayoutPath) {
|
|
var _this__compiler;
|
|
message += `We tried to create ${(0, _picocolors.bold)(_path.default.relative(((_this__compiler = this._compiler) == null ? void 0 : _this__compiler.context) ?? '', rootLayoutPath))} for you but something went wrong.`;
|
|
} else {
|
|
message += 'To fix this error, make sure every page has a root layout.';
|
|
}
|
|
throw Object.defineProperty(new Error(message), "__NEXT_ERROR_CODE", {
|
|
value: "E394",
|
|
enumerable: false,
|
|
configurable: true
|
|
});
|
|
}
|
|
// Clear fs cache, get the new result with the created root layout.
|
|
if (this._compilation) (_filesInDirMapMap_get = filesInDirMapMap.get(this._compilation)) == null ? void 0 : _filesInDirMapMap_get.clear();
|
|
treeCodeResult = await createTreeCodeFromPath(pagePath, {
|
|
page,
|
|
resolveDir,
|
|
resolver,
|
|
metadataResolver,
|
|
resolveParallelSegments,
|
|
hasChildRoutesForSegment,
|
|
loaderContext: this,
|
|
pageExtensions,
|
|
basePath,
|
|
collectedDeclarations,
|
|
isGlobalNotFoundEnabled
|
|
});
|
|
}
|
|
}
|
|
const pathname = new _apppathnamenormalizer.AppPathnameNormalizer().normalize(page);
|
|
// Prefer to modify next/src/server/app-render/entry-base.ts since this is shared with Turbopack.
|
|
// Any changes to this code should be reflected in Turbopack's app_source.rs and/or app-renderer.tsx as well.
|
|
const code = await (0, _loadentrypoint.loadEntrypoint)('app-page', {
|
|
VAR_DEFINITION_PAGE: page,
|
|
VAR_DEFINITION_PATHNAME: pathname,
|
|
VAR_MODULE_GLOBAL_ERROR: treeCodeResult.globalError
|
|
}, {
|
|
tree: treeCodeResult.treeCode,
|
|
__next_app_require__: '__webpack_require__',
|
|
// all modules are in the entry chunk, so we never actually need to load chunks in webpack
|
|
__next_app_load_chunk__: '() => Promise.resolve()'
|
|
});
|
|
// Lazily evaluate the imported modules in the generated code
|
|
const header = collectedDeclarations.map(([varName, modulePath])=>{
|
|
return `const ${varName} = () => import(/* webpackMode: "eager" */ ${JSON.stringify(modulePath)});\n`;
|
|
}).join('');
|
|
return header + code;
|
|
};
|
|
const _default1 = nextAppLoader;
|
|
|
|
//# sourceMappingURL=index.js.map
|