universal-autoloader
Version:
Universal plugin, that use the _file system_ scan, to load in a server all routes in a directory.
56 lines (52 loc) • 2.45 kB
JavaScript
import fs from 'node:fs';
import { pathToFileURL } from 'node:url';
const countParams = (filepath) => {
return (filepath.match(/\[(.*?)\]/gu) || []).length;
};
const sortRoutesByParams = (routes) => {
return routes.sort((a, b) => countParams(a) - countParams(b));
};
const transformToRoute = (filepath) => {
return filepath.replace(/\.(ts|tsx|mjs|js|jsx|cjs)$/u, "").replaceAll(/\[\.\.\..*?\]/gu, "*").replaceAll(/\[(.+?)\]/gu, (_, match) => `:${match}`).replace(/\/?\((.*?)\)/, "").replaceAll("]-[", "-:").replaceAll("]/", "/").replaceAll(/\[|\]/gu, "").replace(/\/?index$/, "");
};
const DEFAULT_PATTERN = "**/*.{ts,tsx,mjs,js,jsx,cjs}";
const DEFAULT_ROUTES_DIR = "./routes";
const DEFAULT_METHOD = "get";
const autoloadRoutes = async (app, {
pattern = DEFAULT_PATTERN,
prefix = "",
routesDir = DEFAULT_ROUTES_DIR,
defaultMethod = DEFAULT_METHOD,
viteDevServer,
skipNoRoutes = false,
skipImportErrors = false
}) => {
if (!fs.existsSync(routesDir)) {
throw new Error(`Directory ${routesDir} doesn't exist`);
}
if (!fs.statSync(routesDir).isDirectory()) {
throw new Error(`${routesDir} isn't a directory`);
}
const files = typeof Bun === "undefined" ? fs.globSync(pattern, { cwd: routesDir }) : await Array.fromAsync(new Bun.Glob(pattern).scan({ cwd: routesDir }));
if (files.length === 0 && !skipNoRoutes) {
throw new Error(`No matches found in ${routesDir} (you can disable this error with 'skipFailGlob' option to true)`);
}
for (const file of sortRoutesByParams(files)) {
const universalFilepath = file.replaceAll("\\", "/");
const initFilepath = `${routesDir}/${universalFilepath}`;
const { default: importedRoute } = await (viteDevServer ? viteDevServer.ssrLoadModule(initFilepath, { fixStacktrace: true }) : import(pathToFileURL(initFilepath).href));
if (!importedRoute && !skipImportErrors) {
throw new Error(`${initFilepath} doesn't have default export (you can disable this error with 'skipImportErrors' option to true)`);
}
if (typeof importedRoute === "function") {
const matchedFile = universalFilepath.match(/\/?\((.*?)\)/);
const method = matchedFile ? matchedFile[1] : defaultMethod;
const route = `${prefix}/${transformToRoute(universalFilepath)}`;
app[method](route, importedRoute);
} else {
console.warn(`Exported function of ${initFilepath} is not a function`);
}
}
return app;
};
export { autoloadRoutes };