UNPKG

vjsrouter

Version:

A modern, file-system based router for vanilla JavaScript with SSR support.

143 lines (118 loc) 5.05 kB
// File: cli/commands/build.cjs const fs = require('fs'); const path = require('path'); /** * A robust recursive function to find all page files and layouts. * It correctly maps layouts to their specific directories. * @param {string} baseDir - The root 'pages' directory. * @param {string} currentDir - The directory currently being scanned. * @returns {{pages: string[], layouts: Map<string, string>}} An object containing page files and a map of layouts. */ function _recursivelyFindFiles(baseDir, currentDir) { const entries = fs.readdirSync(currentDir, { withFileTypes: true }); let pages = []; const layouts = new Map(); const layoutFile = entries.find(entry => entry.isFile() && entry.name === '_layout.js'); if (layoutFile) { const layoutPath = path.join(currentDir, layoutFile.name); // Use '.' for the root directory to handle it correctly. const relativeDir = path.relative(baseDir, currentDir) || '.'; layouts.set(relativeDir, path.relative(baseDir, layoutPath)); } for (const entry of entries) { const fullPath = path.join(currentDir, entry.name); if (entry.isDirectory()) { const nested = _recursivelyFindFiles(baseDir, fullPath); pages = pages.concat(nested.pages); nested.layouts.forEach((value, key) => layouts.set(key, value)); } else if (entry.isFile() && entry.name !== '_layout.js' && (entry.name.endsWith('.js') || entry.name.endsWith('.mjs'))) { pages.push(path.relative(baseDir, fullPath)); } } return { pages, layouts }; } /** * Creates a clean, URL-friendly route path from a file path and handles dynamic segments. * This version includes robust checks to prevent invalid path generation. * @param {string} relativeFilePath - The file path relative to the pages directory. * @returns {string} A clean, URL-friendly route path. */ function _createRoutePathFromFile(relativeFilePath) { // Special case for the root index file to prevent any ambiguity. if (relativeFilePath === 'index.js' || relativeFilePath === 'index.mjs') { return '/'; } const urlPath = relativeFilePath.split(path.sep).join('/'); let route = urlPath.replace(/\.m?js$/, ''); // More specific replacement for '/index' to avoid bugs with other paths. if (route.endsWith('/index')) { route = route.slice(0, -'/index'.length); } route = route.replace(/\[([^\]]+)\]/g, ':$1'); if (!route.startsWith('/')) { route = '/' + route; } // This robust cleanup prevents invalid paths like `/page/` that could lead to errors. if (route.length > 1 && route.endsWith('/')) { route = route.slice(0, -1); } // If after all this, the route is empty, it must be the root. if (route === '') { return '/'; } return route; } /** * The main build function that scans the file system and generates the routes.json manifest. * @param {Object} options - Configuration options for the build. */ function buildRoutesManifest(options = {}) { const pagesDir = options.pagesDir || path.join(process.cwd(), 'pages'); const outputFile = options.outputFile || path.join(process.cwd(), 'routes.json'); console.log(`[vjsrouter-cli] Starting build...`); console.log(`[vjsrouter-cli] > Scanning for pages and layouts in: ${pagesDir}`); if (!fs.existsSync(pagesDir)) { console.error(`\x1b[31m[vjsrouter-cli] ERROR: The specified pages directory does not exist: ${pagesDir}\x1b[0m`); process.exit(1); } const { pages, layouts } = _recursivelyFindFiles(pagesDir, pagesDir); if (pages.length === 0) { console.warn(`\x1b[33m[vjsrouter-cli] WARNING: No page files were found.\x1b[0m`); } else { console.log(`[vjsrouter-cli] > Found ${pages.length} page files and ${layouts.size} layout files.`); } const routes = pages.map(pageFile => { const routePath = _createRoutePathFromFile(pageFile); const browserSafePagePath = '/pages/' + pageFile.split(path.sep).join('/'); const pageDir = path.dirname(pageFile); const layoutHierarchy = []; let currentDir = pageDir; // This loop correctly walks up the directory tree to find all applicable layouts. while (true) { if (layouts.has(currentDir)) { layoutHierarchy.unshift('/pages/' + layouts.get(currentDir).split(path.sep).join('/')); } if (currentDir === '.') break; currentDir = path.dirname(currentDir); } return { path: routePath, file: browserSafePagePath, layouts: layoutHierarchy, }; }); routes.sort((a, b) => a.path.localeCompare(b.path)); const manifest = { createdAt: new Date().toISOString(), routeCount: routes.length, routes: routes, }; try { fs.writeFileSync(outputFile, JSON.stringify(manifest, null, 2)); console.log(`\x1b[32m[vjsrouter-cli] SUCCESS: Route manifest created at ${outputFile}\x1b[0m`); } catch (error) { console.error(`\x1b[31m[vjsrouter-cli] ERROR: Failed to write manifest file to ${outputFile}\x1b[0m`, error); process.exit(1); } } module.exports = { buildRoutesManifest };