UNPKG

@itrocks/route

Version:

Domain-driven route manager with automatic generation, decorators, and static routes

106 lines 3.72 kB
import { appDir } from '@itrocks/app-dir'; import { readFileSync } from 'node:fs'; import { writeFileSync } from 'node:fs'; import ts from 'typescript'; // TODO support aliasing: import { Route as Alias } from './route.js' ; @Alias('/') const scanPath = appDir; const staticRoutesFile = appDir + '/static-routes.json'; let source; try { source = readFileSync(staticRoutesFile) + ''; } catch { source = '{}'; } const routes = JSON.parse(source); const modules = {}; for (const [path, module] of Object.entries(routes)) { (modules[module] ?? (modules[module] = [])).push(path); } function saveStaticRoutes() { return writeFileSync(staticRoutesFile, JSON.stringify(routes, null, '\t') + '\n'); } export default () => (context) => (sourceFile) => { let hasRoute = false; const module = sourceFile.fileName.slice(scanPath.length, -3); const validRoutes = {}; function isRoute(node) { if (!ts.isImportDeclaration(node)) return false; if (!node.importClause) return false; const moduleSpecifier = node.moduleSpecifier; if (moduleSpecifier.text !== '@itrocks/route') return false; if (node.importClause.name?.getText() === 'Route') { return true; } const namedBindings = node.importClause.namedBindings; if (!namedBindings || !ts.isNamedImports(namedBindings)) return false; for (const importSpecifier of namedBindings.elements) { if (importSpecifier.name.getText() === 'Route') { return true; } } return false; } function routeDecoratorValues(node) { const routes = []; if (!ts.canHaveDecorators(node)) return []; for (const decorator of ts.getDecorators(node) ?? []) { if (!ts.isCallExpression(decorator.expression)) continue; if (decorator.expression.expression.getText() !== 'Route') continue; const argument = decorator.expression.arguments[0]; if (!argument || !ts.isStringLiteral(argument)) continue; routes.push(argument.text); } return routes; } const visit = (node) => { if (hasRoute ||= isRoute(node)) { for (const path of routeDecoratorValues(node)) { if (!path) continue; validRoutes[path] = module; if (module === routes[path]) continue; if (routes[path]) { const moduleRoutes = modules[routes[path]]; delete moduleRoutes[moduleRoutes.indexOf(path)]; } (modules[module] ?? (modules[module] = [])).push(path); routes[path] = module; } } return ts.visitEachChild(node, visit, context); }; const resultNode = ts.visitNode(sourceFile, visit); const moduleRoutes = modules[module]; if (moduleRoutes) { let deletions = 0; for (const path of moduleRoutes) { if (validRoutes[path]) continue; delete moduleRoutes[moduleRoutes.indexOf(path)]; delete routes[path]; deletions++; } if (deletions) { modules[module] = moduleRoutes.filter(path => path); if (!modules[module].length) { delete modules[module]; } saveStaticRoutes(); } else if (Object.keys(validRoutes).length) { saveStaticRoutes(); } } return resultNode; }; //# sourceMappingURL=static-routes-plugin.js.map