RSC Tree Builder (get-jsx.js)
Understand how pages are crawled, how data dependencies are fetched via page functions, and how layout wrappers are mounted recursively.
Key File Location: ./dinou/core/get-jsx.js💡 Overview
In standard frameworks, a router only matches paths to a single file. In Dinou, get-jsx.js is the central compiler entrypoint that resolves the entire React Server Component (RSC) tree for a request. It handles file lookup, triggers Server-side data fetching via page functions, wraps the page inside parent layouts, and configures fallback routes.
📊 RSC Resolution Pipeline
The flowchart below shows how routes are compiled to Server elements:
⚙️ Page Functions & getProps
To load dynamic data on the server, Dinou supports adjacent data helpers inside page_functions.js. The loader executes these hooks:
- Server Data Loader: When a route resolves,
get-jsx.jssearches forpage_functions.js, imports it, and runsgetProps(params). - Props Injection: Merges the output data properties (e.g.
pageandlayoutprops) into the component, enabling data hydration before rendering. - Layout Nesting & Reset: Walks directory segments upwards, nesting layouts recursively. If a layout folder contains
reset_layout, layout nesting halts.
⚙️ Complete Code Walkthrough
Below is the full, complete code of get-jsx.js:
const path = require("path");
const { existsSync } = require("./vfs");
const React = require("react");
const { getFilePathAndDynamicParams } = require("./get-file-path-and-dynamic-params");
const importModule = require("./import-module");
const { asyncRenderJSXToClientJSX } = require("./render-jsx-to-client-jsx");
async function getJSX(
reqPath,
query,
isNotFound = null,
isDevelopment = false,
forceNotFound = false,
) {
const srcFolder = path.resolve(process.cwd(), "src");
const reqSegments = reqPath.split("/").filter(Boolean);
let pagePath;
// 1. Resolve path segments to find physical files
const [filePath, dParams] = getFilePathAndDynamicParams(
reqSegments,
query,
srcFolder,
);
pagePath = filePath;
let dynamicParams = dParams ?? {};
let jsx;
let pageFunctionsProps;
// 2. Handle 404 - Not Found files fallback routing
if (!pagePath || forceNotFound) {
if (isNotFound) isNotFound.value = true;
const [notFoundPath, dParams] = getFilePathAndDynamicParams(
reqSegments,
query,
srcFolder,
"not_found",
true,
false,
);
if (!notFoundPath) {
jsx = React.createElement("div", null, `Page not found: no "page" file found for "${reqPath}"`);
} else {
const pageModule = await importModule(notFoundPath);
const Page = pageModule.default ?? pageModule;
let props = { params: dParams ?? {} };
const notFoundDir = path.dirname(notFoundPath);
// Check if page_functions.js contains server getProps() hooks for 404
const [pageFunctionsPath] = getFilePathAndDynamicParams(
reqSegments,
query,
notFoundDir,
"page_functions",
true,
true,
undefined,
reqSegments.length,
);
if (pageFunctionsPath) {
const pageFunctionsModule = await importModule(pageFunctionsPath);
const getProps = pageFunctionsModule.getProps;
pageFunctionsProps = await getProps?.(dParams ?? {});
props = { ...props, ...(pageFunctionsProps?.page ?? {}) };
}
jsx = React.createElement(Page, props);
}
} else {
// 3. Resolve active route page component
if (isNotFound) isNotFound.value = false;
const pageModule = await importModule(pagePath);
const Page = pageModule.default ?? pageModule;
let props = { params: dynamicParams };
const pageFolder = path.dirname(pagePath);
const [pageFunctionsPath] = getFilePathAndDynamicParams(
reqSegments,
query,
pageFolder,
"page_functions",
true,
true,
undefined,
reqSegments.length,
);
if (pageFunctionsPath) {
const pageFunctionsModule = await importModule(pageFunctionsPath);
const getProps = pageFunctionsModule.getProps;
pageFunctionsProps = await getProps?.(dynamicParams);
props = { ...props, ...(pageFunctionsProps?.page ?? {}) };
}
jsx = React.createElement(Page, props);
}
// 4. Check for 'no_layout' boundary bypass
if (
getFilePathAndDynamicParams(
reqSegments,
query,
srcFolder,
"no_layout",
false,
)[0]
) {
return jsx;
}
// 5. Wrap layout components recursively (Outer -> Inner)
const layouts = getFilePathAndDynamicParams(
reqSegments,
query,
srcFolder,
"layout",
true,
false,
undefined,
0,
{},
true,
);
if (layouts && Array.isArray(layouts)) {
let index = 0;
for (const [layoutPath, dParams, slots] of layouts.reverse()) {
const layoutModule = await importModule(layoutPath);
const layoutFolderPath = path.dirname(layoutPath);
const resetLayoutPath = getFilePathAndDynamicParams(
[],
{},
layoutFolderPath,
"reset_layout",
false,
)[0];
const Layout = layoutModule.default ?? layoutModule;
const updatedSlots = {};
// 6. Map and try-catch render parallel slots in layouts
for (const [slotName, slotElement] of Object.entries(slots)) {
let updatedSlotElement;
try {
await asyncRenderJSXToClientJSX(slotElement);
updatedSlotElement = slotElement;
} catch (e) {
const slotFilePath = slotElement.props?.__modulePath;
if (slotFilePath) {
const realSlotFolder = path.dirname(slotFilePath);
const [slotErrorPath, slotErrorParams] = getFilePathAndDynamicParams(
reqSegments,
query,
realSlotFolder,
"error",
true,
true,
undefined,
reqSegments.length,
);
if (slotErrorPath) {
const slotErrorModule = await importModule(slotErrorPath);
const SlotError = slotErrorModule.default ?? slotErrorModule;
updatedSlotElement = React.createElement(SlotError, {
params: slotErrorParams,
key: slotName,
error: { message: e.message || "Unknown Slot Error", name: e.name },
});
} else {
updatedSlotElement = null;
}
} else {
updatedSlotElement = null;
}
} finally {
updatedSlots[slotName] = updatedSlotElement;
}
}
let props = { params: dParams, ...updatedSlots };
if (index === layouts.length - 1 || resetLayoutPath) {
props = { ...props, ...(pageFunctionsProps?.layout ?? {}) };
}
jsx = React.createElement(Layout, props, jsx);
if (resetLayoutPath) {
break;
}
index++;
}
}
return jsx;
}
module.exports = getJSX;