one
Version:
One is a new React Framework that makes Vite serve both native and web.
291 lines (290 loc) • 12.7 kB
JavaScript
import { getDefaultRenderMode } from "../config";
import { getPageExport } from "../utils/getPageExport";
import {
matchArrayGroupName,
matchDeepDynamicRouteName,
matchDynamicName,
matchGroupName,
removeSupportedExtensions
} from "./matchers";
const validPlatforms = /* @__PURE__ */ new Set(["android", "ios", "native", "web"]);
function getRoutes(contextModule, options = {}) {
const directoryTree = getDirectoryTree(contextModule, options);
if (!directoryTree)
return null;
const rootNode = flattenDirectoryTreeToRoutes(directoryTree, options);
return options.ignoreEntryPoints || crawlAndAppendInitialRoutesAndEntryFiles(rootNode, options), rootNode;
}
function getDirectoryTree(contextModule, options) {
const importMode = options.importMode || process.env.One_ROUTER_IMPORT_MODE, ignoreList = [/^\.\/\+html\.[tj]sx?$/];
options.ignore && ignoreList.push(...options.ignore), options.preserveApiRoutes || ignoreList.push(/\+api\.[tj]sx?$/);
const rootDirectory = {
files: /* @__PURE__ */ new Map(),
subdirectories: /* @__PURE__ */ new Map()
};
let hasRoutes = !1, isValid = !1;
for (const filePath of contextModule.keys()) {
if (ignoreList.some((regex) => regex.test(filePath)))
continue;
isValid = !0;
const meta = getFileMeta(filePath, options);
if (meta.specificity < 0)
continue;
const type = meta.isLayout ? "layout" : meta.renderMode || getDefaultRenderMode();
let node = {
type,
loadRoute() {
if (options.ignoreRequireErrors)
try {
return contextModule(filePath);
} catch {
return {};
}
else
return contextModule(filePath);
},
contextKey: filePath,
route: "",
// This is overwritten during hoisting based upon the _layout
dynamic: null,
children: []
// While we are building the directory tree, we don't know the node's children just yet. This is added during hoisting
};
if (!(process.env.NODE_ENV === "development" && node.type !== "api" && importMode === "sync" && !getPageExport(node.loadRoute())))
for (const route of extrapolateGroups(meta.route)) {
const subdirectoryParts = route.split("/").slice(0, -1);
let directory = rootDirectory;
for (const part of subdirectoryParts) {
let subDirectory = directory.subdirectories.get(part);
subDirectory || (subDirectory = {
files: /* @__PURE__ */ new Map(),
subdirectories: /* @__PURE__ */ new Map()
}, directory.subdirectories.set(part, subDirectory)), directory = subDirectory;
}
if (node = { ...node, route }, meta.isLayout) {
directory.layout ??= [];
const existing = directory.layout[meta.specificity];
if (existing) {
if (process.env.NODE_ENV !== "production")
throw new Error(
`The layouts "${filePath}" and "${existing.contextKey}" conflict on the route "/${route}". Please remove or rename one of these files.`
);
} else
node = getLayoutNode(node, options), directory.layout[meta.specificity] = node;
} else if (meta.isMiddleware)
directory.middleware = node;
else if (type === "api") {
const fileKey = `${route}+api`;
let nodes = directory.files.get(fileKey);
nodes || (nodes = [], directory.files.set(fileKey, nodes));
const existing = nodes[0];
if (existing) {
if (process.env.NODE_ENV !== "production")
throw new Error(
`The API route file "${filePath}" and "${existing.contextKey}" conflict on the route "/${route}". Please remove or rename one of these files.`
);
} else
nodes[0] = node;
} else {
let nodes = directory.files.get(route);
nodes || (nodes = [], directory.files.set(route, nodes));
const existing = nodes[meta.specificity];
if (existing) {
if (process.env.NODE_ENV !== "production")
throw new Error(
`The route files "${filePath}" and "${existing.contextKey}" conflict on the route "/${route}". Please remove or rename one of these files.`
);
} else
hasRoutes ||= !0, nodes[meta.specificity] = node;
}
}
}
return isValid ? (rootDirectory.layout || (rootDirectory.layout = [
{
type: "layout",
loadRoute: () => ({
default: (() => {
try {
return require("../views/Navigator");
} catch (e) {
return {
DefaultNavigator: () => {
throw e;
}
};
}
})().DefaultNavigator
}),
// Generate a fake file name for the directory
contextKey: "router/build/views/Navigator.js",
route: "",
generated: !0,
dynamic: null,
children: []
}
]), hasRoutes && appendSitemapRoute(rootDirectory), appendNotFoundRoute(rootDirectory), rootDirectory) : null;
}
function flattenDirectoryTreeToRoutes(directory, options, layout, pathToRemove = "", parentMiddlewares) {
if (directory.layout) {
const previousLayout = layout;
layout = getMostSpecific(directory.layout), previousLayout && previousLayout.children.push(layout);
const newRoute = layout.route.replace(pathToRemove, "");
pathToRemove = layout.route ? `${layout.route}/` : "", layout.route = newRoute, layout.dynamic = generateDynamic(layout.route);
}
if (!layout) throw new Error("One Internal Error: No nearest layout");
const middlewares = directory.middleware ? [...parentMiddlewares || [], directory.middleware] : parentMiddlewares;
for (const routes of directory.files.values()) {
const routeNode = getMostSpecific(routes);
routeNode.route = routeNode.route.replace(pathToRemove, ""), routeNode.dynamic = generateDynamic(routeNode.route), routeNode.middlewares = middlewares, layout.children.push(routeNode);
}
for (const child of directory.subdirectories.values())
flattenDirectoryTreeToRoutes(child, options, layout, pathToRemove, middlewares);
return layout;
}
function getFileMeta(key, options) {
key = key.replace(/^\.\//, "");
const parts = key.split("/");
let route = removeSupportedExtensions(key);
const filename = parts[parts.length - 1], filenameWithoutExtensions = removeSupportedExtensions(filename), isLayout = filenameWithoutExtensions.startsWith("_layout"), isMiddleware = filenameWithoutExtensions.startsWith("_middleware"), [_fullname, renderModeFound] = filename.match(/\+(api|ssg|ssr|spa)\.(\w+\.)?[jt]sx?$/) || [], renderMode = renderModeFound;
if (filenameWithoutExtensions.startsWith("(") && filenameWithoutExtensions.endsWith(")"))
throw new Error(`Invalid route ./${key}. Routes cannot end with '(group)' syntax`);
if (renderMode !== "api" && filename.startsWith("+") && filenameWithoutExtensions !== "+not-found") {
const renamedRoute = [...parts.slice(0, -1), filename.slice(1)].join("/");
throw new Error(
`Invalid route ./${key}. Route nodes cannot start with the '+' character. "Please rename to ${renamedRoute}"`
);
}
let specificity = 0;
const platformExtension = filenameWithoutExtensions.split(".")[1], hasPlatformExtension = validPlatforms.has(platformExtension), usePlatformRoutes = options.platformRoutes ?? !0;
if (hasPlatformExtension) {
if (usePlatformRoutes && options.platform ? platformExtension === options.platform ? specificity = 2 : platformExtension === "native" && options.platform !== "web" ? specificity = 1 : platformExtension !== options.platform && (specificity = -1) : specificity = -1, renderMode === "api" && specificity !== 0)
throw new Error(
`Api routes cannot have platform extensions. Please remove '.${platformExtension}' from './${key}'`
);
route = route.replace(new RegExp(`.${platformExtension}$`), "");
}
return {
route,
specificity,
isLayout,
isMiddleware,
renderMode
};
}
function getMostSpecific(routes) {
const route = routes[routes.length - 1];
if (!routes[0])
throw new Error(
` [one] The file ${route.contextKey} does not have a fallback sibling file without a platform extension in routes (${routes[0]}, ${routes.length}):
${routes.map((r) => r.contextKey || "NONE").join(`
`)}.`
);
return routes[routes.length - 1];
}
function getIgnoreList(options) {
const ignore = [/^\.\/\+html\.[tj]sx?$/, ...options?.ignore ?? []];
return options?.preserveApiRoutes !== !0 && ignore.push(/\+api\.[tj]sx?$/), ignore;
}
function extrapolateGroups(key, keys = /* @__PURE__ */ new Set()) {
const match = matchArrayGroupName(key);
if (!match)
return keys.add(key), keys;
const groups = match.split(",");
if (new Set(groups).size !== groups.length)
throw new Error(`Array syntax cannot contain duplicate group name "${groups}" in "${key}".`);
if (groups.length === 1)
return keys.add(key), keys;
for (const group of groups)
extrapolateGroups(key.replace(match, group.trim()), keys);
return keys;
}
function generateDynamic(path) {
const dynamic = path.split("/").map((part) => {
if (part === "+not-found")
return {
name: "+not-found",
deep: !0,
notFound: !0
};
const deepDynamicName = matchDeepDynamicRouteName(part), dynamicName = deepDynamicName ?? matchDynamicName(part);
return dynamicName ? { name: dynamicName, deep: !!deepDynamicName } : null;
}).filter((part) => !!part);
return dynamic.length === 0 ? null : dynamic;
}
function appendSitemapRoute(directory) {
directory.files.has("_sitemap") || directory.files.set("_sitemap", [
{
loadRoute() {
return { default: () => null, getNavOptions: () => {
} };
},
route: "_sitemap",
type: "ssg",
contextKey: "",
generated: !0,
internal: !0,
dynamic: null,
children: []
}
]);
}
function appendNotFoundRoute(directory) {
directory.files.has("+not-found") || directory.files.set("+not-found", [
{
loadRoute() {
return { default: () => null };
},
type: "spa",
route: "+not-found",
contextKey: "",
generated: !0,
internal: !0,
dynamic: [{ name: "+not-found", deep: !0, notFound: !0 }],
children: []
}
]);
}
function getLayoutNode(node, options) {
const groupName = matchGroupName(node.route);
let initialRouteName = node.children.find((child) => child.route.replace(/\/index$/, "") === groupName)?.route;
return {
...node,
route: node.route.replace(/\/?_layout$/, ""),
children: [],
// Each layout should have its own children
initialRouteName
};
}
function crawlAndAppendInitialRoutesAndEntryFiles(node, options, entryPoints = []) {
if (node.type === "spa" || node.type === "ssg" || node.type === "ssr")
node.entryPoints = [.../* @__PURE__ */ new Set([...entryPoints, node.contextKey])];
else if (node.type === "layout") {
if (!node.children)
throw new Error(`Layout "${node.contextKey}" does not contain any child routes`);
entryPoints = [...entryPoints, node.contextKey];
const groupName = matchGroupName(node.route);
let initialRouteName = node.children.find((child) => child.route.replace(/\/index$/, "") === groupName)?.route;
const loaded = node.loadRoute();
if (loaded?.unstable_settings && (initialRouteName = loaded.unstable_settings.initialRouteName ?? initialRouteName, groupName && (initialRouteName = loaded.unstable_settings?.[groupName]?.initialRouteName ?? initialRouteName)), initialRouteName) {
const initialRoute = node.children.find((child) => child.route === initialRouteName);
if (!initialRoute) {
const validInitialRoutes = node.children.filter((child) => !child.generated).map((child) => `'${child.route}'`).join(", ");
throw groupName ? new Error(
`Layout ${node.contextKey} has invalid initialRouteName '${initialRouteName}' for group '(${groupName})'. Valid options are: ${validInitialRoutes}`
) : new Error(
`Layout ${node.contextKey} has invalid initialRouteName '${initialRouteName}'. Valid options are: ${validInitialRoutes}`
);
}
node.initialRouteName = initialRouteName, entryPoints.push(initialRoute.contextKey);
}
for (const child of node.children)
crawlAndAppendInitialRoutesAndEntryFiles(child, options, entryPoints);
}
}
export {
extrapolateGroups,
generateDynamic,
getIgnoreList,
getRoutes
};
//# sourceMappingURL=getRoutes.js.map