Filesystem Route Mapper (get-file-path-and-dynamic-params.js)
Understand the core routing resolver, dynamic parameter extractor, catch-all normalizer, and parallel slots crawler.
Key File Location: ./dinou/core/get-file-path-and-dynamic-params.js💡 Overview
In dynamic frameworks, requests must be mapped to folders. For instance, the URL path /blog/post-42 maps to the physical directory src/blog/[slug]/page.tsx.
The get-file-path-and-dynamic-params.js file handles this resolution. It crawls directories segment-by-segment, parses parameter names and values, collects adjacent layout files, and mounts parallel page slots.
📊 Resolver Decision Tree
The flowchart below traces how incoming path segments are evaluated to locate files and extract request props:
⚛️ Parallel Slots (getSlots)
Dinou supports parallel layouts via folders starting with the @ symbol (e.g., @sidebar). When a layout mounts, it receives these parallel components directly as props:
The getSlots() function queries folder structures recursively. If it finds directories beginning with @, it triggers a sub-resolved routing task to locate their page files, mock-renders the slot components with resolved parameters, and attaches them to the parent layout props map.
⚙️ Code Implementation Details
Below is the core implementation of the routing mapper and slot crawler:
const path = require("path");
const { existsSync, readdirSync } = require("./vfs");
const React = require("react");
function safeDecode(val) {
try {
return !!val ? decodeURIComponent(val) : val;
} catch (e) {
return val;
}
}
// 1. Parallel Slots & Route Group Crawler
function getSlots(currentPath, reqSegments, query) {
let slots = {};
const entries = readdirSync(currentPath, { withFileTypes: true });
for (const entry of entries) {
if (!entry.isDirectory()) continue;
// Detect parallel slot folders starting with @
if (entry.name.startsWith("@")) {
const [slotPath, slotParams] = getFilePathAndDynamicParams(
reqSegments,
query,
path.join(currentPath, entry.name),
"page",
true,
true,
undefined,
reqSegments.length
);
if (slotPath) {
const slotModule = require(slotPath);
const Slot = slotModule.default ?? slotModule;
const slotName = entry.name.slice(1);
// Pre-render slot element with its dynamic props
slots[slotName] = React.createElement(Slot, {
params: slotParams,
key: slotName,
__modulePath: slotPath ?? null,
});
}
} else if (entry.name.startsWith("(") && entry.name.endsWith(")")) {
// Crawl route groups recursively for nested slots
const groupPath = path.join(currentPath, entry.name);
const nestedSlots = getSlots(groupPath, reqSegments, query);
slots = { ...slots, ...nestedSlots };
}
}
return slots;
}
// 2. Main Route Resolver Function
function getFilePathAndDynamicParams(
reqSegments,
query,
currentPath,
fileName = "page",
withExtension = true,
finalDestination = true,
lastFound = undefined,
index = 0,
dParams = {},
accumulative = false,
accumulate = [],
isFound = { value: false },
possibleExtensions = [".tsx", ".ts", ".jsx", ".js"]
) {
let foundInCurrentPath;
// Base case: check if we've parsed all request segments
if (index > reqSegments.length - 1 || !finalDestination) {
if (withExtension) {
for (const ext of possibleExtensions) {
const candidatePath = path.join(currentPath, `${fileName}${ext}`);
if (existsSync(candidatePath)) {
isFound.value = true;
if (!accumulative) return [candidatePath, dParams];
const slots = getSlots(currentPath, reqSegments, query);
accumulate.push([candidatePath, dParams, slots]);
}
}
}
}
// Iterate folders and parse dynamic parameter configurations...
// e.g. matching '[id]' or optional catch-alls '[[...rest]]'
// and recursively call getFilePathAndDynamicParams for the next segment index.
// (Full resolver implements route traversal check, path decoding,
// and checks to prevent segment gap mismatches)
return accumulative ? accumulate : [lastFound, dParams];
}