UNPKG

next

Version:

The React Framework

943 lines • 51.7 kB
"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 _apppaths = require("../../../../shared/lib/router/utils/app-paths");
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 isDynamicSegment = (segment)=>segment.startsWith('[') && segment.endsWith(']');
const isDirectory = async (pathname)=>{
    try {
        const stat = await _fs.promises.stat(pathname);
        return stat.isDirectory();
    } catch (err) {
        return false;
    }
};
const containsPageOrDefaultMapMap = new WeakMap();
const hasDeclaredChildrenSlotMapMap = new WeakMap();
async function createTreeCodeFromPath(pagePath, { page, resolveDir, resolver, loaderContext, resolveParallelSegments, hasChildRoutesForSegment, getStaticSiblingSegments, metadataResolver, pageExtensions, basePath, collectedDeclarations, isGlobalNotFoundEnabled, explicitParallelRouteChildren, strictRouteMatching, isFinalRouteMatcher, isDev }) {
    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;
    const pageOrDefaultFileNames = new Set(pageExtensions.flatMap((extension)=>[
            `page.${extension}`,
            `default.${extension}`
        ]));
    const compilation = loaderContext._compilation;
    const containsPageOrDefaultCache = compilation ? containsPageOrDefaultMapMap.get(compilation) ?? new Map() : new Map();
    const hasDeclaredChildrenSlotCache = compilation ? hasDeclaredChildrenSlotMapMap.get(compilation) ?? new Map() : new Map();
    if (compilation) {
        containsPageOrDefaultMapMap.set(compilation, containsPageOrDefaultCache);
        hasDeclaredChildrenSlotMapMap.set(compilation, hasDeclaredChildrenSlotCache);
    }
    function containsPageOrDefault(absoluteDirectoryPath) {
        // Context dependencies are tracked per loader module, while the scan
        // result is shared by every app loader in this compilation. Register the
        // dependency even when another module already populated the cache.
        loaderContext.addContextDependency(absoluteDirectoryPath);
        let result = containsPageOrDefaultCache.get(absoluteDirectoryPath);
        if (result) return result;
        result = (async ()=>{
            let files;
            try {
                files = await _fs.promises.opendir(absoluteDirectoryPath);
            } catch  {
                return false;
            }
            for await (const dirent of files){
                // A layout is only structure. Keep tracing through every route branch,
                // including named slots, until we find content that can fill a slot.
                if (dirent.isFile() && pageOrDefaultFileNames.has(dirent.name)) {
                    return true;
                }
                if (dirent.isDirectory() && !dirent.name.startsWith('_') && await containsPageOrDefault(_path.default.join(absoluteDirectoryPath, dirent.name))) {
                    return true;
                }
            }
            return false;
        })();
        containsPageOrDefaultCache.set(absoluteDirectoryPath, result);
        return result;
    }
    function hasDeclaredChildrenSlot(absoluteDirectoryPath) {
        // See containsPageOrDefault. Every loader module that consumes this
        // shared result must invalidate when the directory contents change.
        loaderContext.addContextDependency(absoluteDirectoryPath);
        let result = hasDeclaredChildrenSlotCache.get(absoluteDirectoryPath);
        if (result) return result;
        result = (async ()=>{
            let files;
            try {
                files = await _fs.promises.opendir(absoluteDirectoryPath);
            } catch  {
                return false;
            }
            for await (const dirent of files){
                if (dirent.isFile() && pageOrDefaultFileNames.has(dirent.name)) {
                    return true;
                }
                if (!dirent.isDirectory() || dirent.name.startsWith('_') || dirent.name.startsWith('@')) {
                    continue;
                }
                // A direct named slot belongs to this layout and does not declare its
                // children. Once an ordinary branch is entered, named slots below it
                // can provide that branch's actual route targets.
                const subdirectory = _path.default.join(absoluteDirectoryPath, dirent.name);
                if (await containsPageOrDefault(subdirectory)) {
                    return true;
                }
            }
            return false;
        })();
        hasDeclaredChildrenSlotCache.set(absoluteDirectoryPath, result);
        return result;
    }
    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` is the ordinary route branch, not an implicit slot. Keep the
        // legacy fallback available as an opt-out, but otherwise only add it when
        // the filesystem actually declares ordinary route content.
        if (!explicitParallelRouteChildren || await hasDeclaredChildrenSlot(absoluteSegmentPath)) {
            parallelSegments.push('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 = {};
        const interceptingParallelKeys = new Set();
        let containsBuiltinNotFoundDefault = false;
        // 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
                    ]);
                    if ((0, _interceptionroutes.isInterceptionRouteAppPath)(matchedPagePath)) {
                        interceptingParallelKeys.add(normalizeParallelKey(parallelKey));
                    }
                    // 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 new Error(`Can't resolve ${matchedPagePath}`);
                }
            }
            // 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);
            // Resolve global-* convention files at the root layer
            if (isRootLayer) {
                const resolvedGlobalErrorPath = await resolver(`${appDirPrefix}/${GLOBAL_ERROR_FILE_TYPE}`);
                if (resolvedGlobalErrorPath) {
                    globalError = resolvedGlobalErrorPath;
                }
                // 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);
                }
            }
            // Add global-error to ALL layers' filePaths, so that it's always available.
            // By default it's the built-in global-error.js, or user's custom one if defined.
            filePaths.set(GLOBAL_ERROR_FILE_TYPE, globalError);
            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 || parallelSegmentKey === PAGE_SEGMENT ? '(__SLOT__)' : 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, containsInterception: subtreeContainsInterception, containsBuiltinNotFoundDefault: subtreeContainsBuiltinNotFoundDefault } = await createSubtreePropsFromSegmentPath(subSegmentPath, nestedCollectedDeclarations);
                subtreeCode = pageSubtreeCode;
                containsBuiltinNotFoundDefault ||= subtreeContainsBuiltinNotFoundDefault;
                if (subtreeContainsInterception) {
                    interceptingParallelKeys.add(normalizedParallelKey);
                }
            }
            // Compute static siblings for dynamic segments. In dev mode, routes are
            // compiled on-demand so we don't know all siblings; pass null.
            const staticSiblingsCode = isDev ? 'null' : `${JSON.stringify(getStaticSiblingSegments(parallelSegmentPath))}`;
            props[normalizedParallelKey] = `[
        '${parallelSegmentKey}',
        ${subtreeCode},
        ${modulesCode},
        ${staticSiblingsCode}
      ]`;
        }
        const adjacentParallelSegments = await resolveAdjacentParallelSegments(segmentPath);
        // This is the level whose parallel child contains the interception match,
        // rather than a layout inside the newly matched interception subtree.
        // Only the former is a partial update of an already active slot owner.
        const isInterceptionHost = !(0, _interceptionroutes.isInterceptionRouteAppPath)(segmentPath) && interceptingParallelKeys.size > 0;
        function setSyntheticDefault(key, defaultPath) {
            if (defaultPath === _default.PARALLEL_ROUTE_DEFAULT_PATH) {
                containsBuiltinNotFoundDefault = true;
            }
            const varName = `default${nestedCollectedDeclarations.length}`;
            nestedCollectedDeclarations.push([
                varName,
                defaultPath
            ]);
            props[key] = `[
        '${_segment.DEFAULT_SEGMENT_KEY}',
        {},
        {
          defaultPage: [${varName}, ${JSON.stringify(defaultPath)}],
        }
      ]`;
        }
        if (isInterceptionHost) {
            // A host may have produced a normal match for another slot while the
            // tree was being assembled. The interception match takes precedence:
            // every other slot owned at this level is retained. Its synthetic
            // `__DEFAULT__` branch renders null if evaluated; a user-authored
            // default is not the meaning of this partial update.
            for (const adjacentParallelSegment of adjacentParallelSegments){
                const normalizedKey = normalizeParallelKey(adjacentParallelSegment);
                if (!interceptingParallelKeys.has(normalizedKey)) {
                    setSyntheticDefault(normalizedKey, _defaultnull.PARALLEL_ROUTE_DEFAULT_NULL_PATH);
                }
            }
        }
        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 a built-in fallback. With explicit children slots this
                // can only be reached for a children slot declared by ordinary route
                // content; layouts composed only from named slots omit children.
                const fullSegmentPath = `${appDirPrefix}${segmentPath}${actualSegment}`;
                let defaultPath = await resolver(`${fullSegmentPath}/default`);
                if (!defaultPath) {
                    if (adjacentParallelSegment === 'children') {
                        // Legacy slot discovery can synthesize children inside an
                        // interception subtree even when no ordinary route declares it.
                        // Explicit children detection omits that structural child; this
                        // fallback remains for applications that disable the flag.
                        defaultPath = (0, _interceptionroutes.isInterceptionRouteAppPath)(page) ? _defaultnull.PARALLEL_ROUTE_DEFAULT_NULL_PATH : _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;
                    }
                }
                setSyntheticDefault(normalizeParallelKey(adjacentParallelSegment), defaultPath);
            }
        }
        return {
            treeCode: `{
        ${Object.entries(props).map(([key, value])=>`${key}: ${value}`).join(',\n')}
      }`,
            containsInterception: (0, _interceptionroutes.isInterceptionRouteAppPath)(segmentPath) || interceptingParallelKeys.size > 0,
            containsBuiltinNotFoundDefault
        };
    }
    const { treeCode, containsBuiltinNotFoundDefault } = await createSubtreePropsFromSegmentPath([], collectedDeclarations);
    if (strictRouteMatching && isFinalRouteMatcher && !(0, _interceptionroutes.isInterceptionRouteAppPath)(page) && containsBuiltinNotFoundDefault && !isNotFoundRoute && !isAppErrorRoute) {
        // A retained ordinary matcher must be able to construct its complete
        // route tree. An interception tree is a partial update and may
        // intentionally contain synthetic slots.
        throw new Error(`Invariant: strict route matching retained the incomplete route matcher ${page}`);
    }
    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, allNormalizedAppPaths: allNormalizedAppPathsOption, pagePath, pageExtensions, rootDir, tsconfigPath, isDev, nextConfigOutput, preferredRegion, basePath, middlewareConfig: middlewareConfigBase64 } = loaderOptions;
    const isGlobalNotFoundEnabled = !!loaderOptions.isGlobalNotFoundEnabled;
    const explicitParallelRouteChildren = !!loaderOptions.explicitParallelRouteChildren;
    const strictRouteMatching = !!loaderOptions.strictRouteMatching;
    const isFinalRouteMatcher = !!loaderOptions.isFinalRouteMatcher;
    // 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 || [];
    // All normalized app paths for computing static siblings across route groups
    const allNormalizedAppPaths = allNormalizedAppPathsOption ?? [];
    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 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`);
                    }
                }
                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;
    };
    /**
   * For a given segment path (in file system space, e.g., "(group)/products/[id]"),
   * find all static sibling segments at the same URL path level.
   *
   * This accounts for route groups - siblings may exist in different parts of the
   * file system tree but at the same URL level.
   *
   * For example:
   *   /app/(marketing)/products/sale/page.tsx -> /products/sale
   *   /app/(shop)/products/[id]/page.tsx -> /products/[id]
   *
   * When called with "(shop)/products/[id]", this would return ['sale'].
   *
   * TODO: This function, along with resolveParallelSegments and
   * hasChildRoutesForSegment, repeatedly scans normalizedAppPaths. A more
   * optimal approach would build an intermediate tree structure first
   * (representing the URL namespace with route groups collapsed), then derive
   * all this information in a single pass. The Turbopack implementation
   * already uses a more tree-oriented approach (DirectoryTree ->
   * AppPageLoaderTree), so this is less urgent to refactor given Turbopack is
   * the canonical implementation going forward.
   */ const getStaticSiblingSegments = (segmentPath)=>{
        // Normalize the current path to URL space
        // Add a trailing /page so normalizeAppPath strips it properly
        const currentUrlPath = (0, _apppaths.normalizeAppPath)(segmentPath + '/page');
        const currentUrlSegments = currentUrlPath.split('/').filter(Boolean);
        // If the path is empty (root level), there are no siblings
        if (currentUrlSegments.length === 0) {
            return [];
        }
        const currentSegment = currentUrlSegments[currentUrlSegments.length - 1];
        const parentUrlPath = currentUrlSegments.length === 1 ? '/' : '/' + currentUrlSegments.slice(0, -1).join('/');
        // The URL level at which we're looking for siblings (0-indexed)
        const siblingLevel = currentUrlSegments.length - 1;
        // Only compute siblings for dynamic segments
        if (!isDynamicSegment(currentSegment)) {
            return [];
        }
        // Use a Set to avoid duplicates (multiple paths may share the same sibling segment)
        const siblings = new Set();
        for (const appPath of allNormalizedAppPaths){
            // Normalize each path to URL space (strip route groups, parallel routes, and /page suffix)
            const urlPath = (0, _apppaths.normalizeAppPath)(appPath);
            const urlSegments = urlPath.split('/').filter(Boolean);
            // Path must have at least enough segments to reach the sibling level
            if (urlSegments.length <= siblingLevel) {
                continue;
            }
            // Check if the parent path matches (all segments before the sibling level)
            const pathParent = siblingLevel === 0 ? '/' : '/' + urlSegments.slice(0, siblingLevel).join('/');
            if (pathParent !== parentUrlPath) {
                continue;
            }
            // Get the segment at the same level as the current segment
            const segmentAtLevel = urlSegments[siblingLevel];
            // Check if this is a sibling: different segment and static
            if (segmentAtLevel !== currentSegment && !isDynamicSegment(segmentAtLevel)) {
                siblings.add(segmentAtLevel);
            }
        }
        return Array.from(siblings);
    };
    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);
        const checks = await Promise.all(extensions.map(async (ext)=>{
            const absolutePathWithExtension = `${absolutePath}${ext}`;
            const exists = await fileExistsInDirectory(dirname, `${filename}${ext}`);
            // Call `addMissingDependency` for all files even if they didn't match,
            // because they might be added or removed during development.
            this.addMissingDependency(absolutePathWithExtension);
            return exists ? absolutePathWithExtension : undefined;
        }));
        return checks.find((result)=>result);
    };
    const metadataResolver = async (dirname, filename, exts)=>{
        const absoluteDir = createAbsolutePath(appDir, dirname);
        const checks = await Promise.all(exts.map(async (ext)=>{
            // 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}`;
            const exists = await fileExistsInDirectory(dirname, filenameWithExt);
            // Call `addMissingDependency` for all files even if they didn't match,
            // because they might be added or removed during development.
            this.addMissingDependency(absolutePathWithExtension);
            return exists ? absolutePathWithExtension : undefined;
        }));
        return checks.filter((result)=>result !== undefined);
    };
    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,
        getStaticSiblingSegments,
        loaderContext: this,
        pageExtensions,
        basePath,
        collectedDeclarations,
        isGlobalNotFoundEnabled,
        explicitParallelRouteChildren,
        strictRouteMatching,
        isFinalRouteMatcher,
        isDev: !!isDev
    });
    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 new Error(message);
            }
            // 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,
                getStaticSiblingSegments,
                loaderContext: this,
                pageExtensions,
                basePath,
                collectedDeclarations,
                isGlobalNotFoundEnabled,
                explicitParallelRouteChildren,
                strictRouteMatching,
                isFinalRouteMatcher,
                isDev: !!isDev
            });
        }
    }
    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
    }, {
        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 = `import { instrumentModuleGetter } from 'next/dist/server/app-render/module-loading/instrument-module-getter'\n` + collectedDeclarations.map(([varName, modulePath])=>{
        return `const ${varName} = instrumentModuleGetter(() => import(/* webpackMode: "eager" */ ${JSON.stringify(modulePath)}));\n`;
    }).join('');
    return header + code;
};
const _default1 = nextAppLoader;

//# sourceMappingURL=index.js.map