UNPKG

@analogjs/platform

Version:

The fullstack meta-framework for Angular

152 lines 12.6 kB
import { normalizePath } from 'vite'; import { globSync } from 'tinyglobby'; import { resolve } from 'node:path'; /** * Router plugin that handles route file discovery and hot module replacement. * * This plugin provides three main functionalities: * 1. Route invalidation when files are added/deleted (HMR workaround) * 2. Dynamic route file discovery and import generation * 3. Content route file discovery for markdown and Analog content * * @param options Configuration options for the router plugin * @returns Array of Vite plugins for route handling * * IMPORTANT: This plugin uses tinyglobby for file discovery. * Key behavior with { dot: true, absolute: true }: * - Returns absolute paths for ALL discovered files * - Path normalization is required to match expected output format * - Files within project root must use relative paths in object keys * - Files outside project root keep absolute paths in object keys */ export function routerPlugin(options) { const workspaceRoot = normalizePath(options?.workspaceRoot ?? process.cwd()); let config; let root; return [ { name: 'analogjs-router-invalidate-routes', configureServer(server) { /** * Invalidates route modules when files are added or deleted. * This is a workaround for Vite's HMR limitations with dynamic imports. * * @param path The file path that was added or deleted */ function invalidateRoutes(path) { if (path.includes(`routes`) || path.includes(`pages`) || path.includes('content')) { server.moduleGraph.fileToModulesMap.forEach((mods) => { mods.forEach((mod) => { if (mod.id?.includes('analogjs') && mod.id?.includes('fesm')) { server.moduleGraph.invalidateModule(mod); mod.importers.forEach((imp) => { server.moduleGraph.invalidateModule(imp); }); } }); }); server.ws.send({ type: 'full-reload', }); } } server.watcher.on('add', invalidateRoutes); server.watcher.on('unlink', invalidateRoutes); }, }, { name: 'analog-glob-routes', config(_config) { config = _config; root = normalizePath(resolve(workspaceRoot, config.root || '.') || '.'); }, /** * Transforms code to replace ANALOG_ROUTE_FILES and ANALOG_CONTENT_ROUTE_FILES * placeholders with actual dynamic imports of discovered route and content files. * * @param code The source code to transform * @returns Transformed code with dynamic imports or undefined if no transformation needed */ transform(code) { if (code.includes('ANALOG_ROUTE_FILES') || code.includes('ANALOG_CONTENT_ROUTE_FILES')) { // Discover route files using tinyglobby // NOTE: { absolute: true } returns absolute paths for ALL files const routeFiles = globSync([ `${root}/app/routes/**/*.ts`, `${root}/src/app/routes/**/*.ts`, `${root}/src/app/pages/**/*.page.ts`, ...(options?.additionalPagesDirs || [])?.map((glob) => `${workspaceRoot}${glob}/**/*.page.ts`), ], { dot: true, absolute: true }); // Discover content files using tinyglobby const contentRouteFiles = globSync([ `${root}/src/app/routes/**/*.md`, `${root}/src/app/pages/**/*.md`, `${root}/src/content/**/*.md`, ...(options?.additionalContentDirs || [])?.map((glob) => `${workspaceRoot}${glob}/**/*.md`), ], { dot: true, absolute: true }); let result = code.replace('ANALOG_ROUTE_FILES = {};', ` ANALOG_ROUTE_FILES = {${routeFiles.map((module) => { // CRITICAL: tinyglobby returns absolute paths, but we need relative paths for project files // to match expected output format. Library files keep absolute paths. const key = module.startsWith(root) ? module.replace(root, '') : module; return `"${key}": () => import('${module}')`; })}}; `); result = result.replace('ANALOG_CONTENT_ROUTE_FILES = {};', ` ANALOG_CONTENT_ROUTE_FILES = {${contentRouteFiles.map((module) => { // Same path normalization as route files const key = module.startsWith(root) ? module.replace(root, '') : module; return `"${key}": () => import('${module}?analog-content-file=true').then(m => m.default)`; })}}; `); return { code: result, map: { mappings: '' }, }; } return; }, }, { name: 'analog-glob-endpoints', /** * Transforms code to replace ANALOG_PAGE_ENDPOINTS placeholder * with actual dynamic imports of discovered server endpoint files. * * @param code The source code to transform * @returns Transformed code with dynamic imports or undefined if no transformation needed */ transform(code) { if (code.includes('ANALOG_PAGE_ENDPOINTS')) { // Discover server endpoint files using tinyglobby const endpointFiles = globSync([ `${root}/src/app/pages/**/*.server.ts`, ...(options?.additionalPagesDirs || []).map((glob) => `${workspaceRoot}${glob}/**/*.server.ts`), ], { dot: true, absolute: true }); const result = code.replace('ANALOG_PAGE_ENDPOINTS = {};', ` ANALOG_PAGE_ENDPOINTS = {${endpointFiles.map((module) => { // Same path normalization for consistency const key = module.startsWith(root) ? module.replace(root, '') : module; return `"${key}": () => import('${module}')`; })}}; `); return { code: result, map: { mappings: '' }, }; } return; }, }, ]; } //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicm91dGVyLXBsdWdpbi5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uLy4uLy4uL3BhY2thZ2VzL3BsYXRmb3JtL3NyYy9saWIvcm91dGVyLXBsdWdpbi50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEVBQUUsYUFBYSxFQUFzQixNQUFNLE1BQU0sQ0FBQztBQUN6RCxPQUFPLEVBQUUsUUFBUSxFQUFFLE1BQU0sWUFBWSxDQUFDO0FBQ3RDLE9BQU8sRUFBRSxPQUFPLEVBQUUsTUFBTSxXQUFXLENBQUM7QUFJcEM7Ozs7Ozs7Ozs7Ozs7Ozs7O0dBaUJHO0FBQ0gsTUFBTSxVQUFVLFlBQVksQ0FBQyxPQUFpQjtJQUM1QyxNQUFNLGFBQWEsR0FBRyxhQUFhLENBQUMsT0FBTyxFQUFFLGFBQWEsSUFBSSxPQUFPLENBQUMsR0FBRyxFQUFFLENBQUMsQ0FBQztJQUM3RSxJQUFJLE1BQWtCLENBQUM7SUFDdkIsSUFBSSxJQUFZLENBQUM7SUFFakIsT0FBTztRQUNMO1lBQ0UsSUFBSSxFQUFFLG1DQUFtQztZQUN6QyxlQUFlLENBQUMsTUFBTTtnQkFDcEI7Ozs7O21CQUtHO2dCQUNILFNBQVMsZ0JBQWdCLENBQUMsSUFBWTtvQkFDcEMsSUFDRSxJQUFJLENBQUMsUUFBUSxDQUFDLFFBQVEsQ0FBQzt3QkFDdkIsSUFBSSxDQUFDLFFBQVEsQ0FBQyxPQUFPLENBQUM7d0JBQ3RCLElBQUksQ0FBQyxRQUFRLENBQUMsU0FBUyxDQUFDLEVBQ3hCLENBQUM7d0JBQ0QsTUFBTSxDQUFDLFdBQVcsQ0FBQyxnQkFBZ0IsQ0FBQyxPQUFPLENBQUMsQ0FBQyxJQUFJLEVBQUUsRUFBRTs0QkFDbkQsSUFBSSxDQUFDLE9BQU8sQ0FBQyxDQUFDLEdBQUcsRUFBRSxFQUFFO2dDQUNuQixJQUFJLEdBQUcsQ0FBQyxFQUFFLEVBQUUsUUFBUSxDQUFDLFVBQVUsQ0FBQyxJQUFJLEdBQUcsQ0FBQyxFQUFFLEVBQUUsUUFBUSxDQUFDLE1BQU0sQ0FBQyxF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