UNPKG

@analogjs/platform

Version:

The fullstack meta-framework for Angular

170 lines 14.5 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; const isRouteLikeFile = (path) => path.includes(`routes`) || path.includes(`pages`) || path.includes('content'); const invalidateFileModules = (server, path) => { const normalizedPath = normalizePath(path); // A newly added page can be discovered before its final contents settle. // Invalidate the page module itself so later edits don't keep serving the // first incomplete transform from Vite's module graph. const fileModules = server.moduleGraph.getModulesByFile?.(normalizedPath) ?? server.moduleGraph.fileToModulesMap.get(normalizedPath); fileModules?.forEach((mod) => { server.moduleGraph.invalidateModule(mod); mod.importers.forEach((imp) => { server.moduleGraph.invalidateModule(imp); }); }); }; 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 (!isRouteLikeFile(path)) { return; } invalidateFileModules(server, path); 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('change', 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,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicm91dGVyLXBsdWdpbi5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uLy4uLy4uL3BhY2thZ2VzL3BsYXRmb3JtL3NyYy9saWIvcm91dGVyLXBsdWdpbi50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEVBQUUsYUFBYSxFQUFxQyxNQUFNLE1BQU0sQ0FBQztBQUN4RSxPQUFPLEVBQUUsUUFBUSxFQUFFLE1BQU0sWUFBWSxDQUFDO0FBQ3RDLE9BQU8sRUFBRSxPQUFPLEVBQUUsTUFBTSxXQUFXLENBQUM7QUFJcEM7Ozs7Ozs7Ozs7Ozs7Ozs7O0dBaUJHO0FBQ0gsTUFBTSxVQUFVLFlBQVksQ0FBQyxPQUFpQjtJQUM1QyxNQUFNLGFBQWEsR0FBRyxhQUFhLENBQUMsT0FBTyxFQUFFLGFBQWEsSUFBSSxPQUFPLENBQUMsR0FBRyxFQUFFLENBQUMsQ0FBQztJQUM3RSxJQUFJLE1BQWtCLENBQUM7SUFDdkIsSUFBSSxJQUFZLENBQUM7SUFDakIsTUFBTSxlQUFlLEdBQUcsQ0FBQyxJQUFZLEVBQUUsRUFBRSxDQUN2QyxJQUFJLENBQUMsUUFBUSxDQUFDLFFBQVEsQ0FBQztRQUN2QixJQUFJLENBQUMsUUFBUSxDQUFDLE9BQU8sQ0FBQztRQUN0QixJQUFJLENBQUMsUUFBUSxDQUFDLFNBQVMsQ0FBQyxDQUFDO0lBQzNCLE1BQU0scUJBQXFCLEdBQUcsQ0FBQyxNQUFxQixFQUFFLElBQVksRUFBRSxFQUFFO1FBQ3BFLE1BQU0sY0FBYyxHQUFHLGFBQWEsQ0FBQyxJQUFJLENBQUMsQ0FBQztRQUMzQyx5RUFBeUU7UUFDekUsMEVBQTBFO1FBQzFFLHVEQUF1RDtRQUN2RCxNQUFNLFdBQVcsR0FDZixNQUFNLENBQUMsV0FBVyxDQUFDLGdCQUFnQixFQUFFLENBQUMsY0FBYyxDQUFDO1lBQ3JELE1BQU0sQ0FBQyxXQUFXLENBQUMsZ0JBQWdCLENBQUMsR0FBRyxDQUFDLGNBQWMsQ0FBQyxDQUFDO1FBRTFELFdBQVcsRUFBRSxPQUFP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