UNPKG

polen

Version:

A framework for delightful GraphQL developer portals

209 lines (208 loc) 9.02 kB
import { Content } from '#api/content/$'; import { polenVirtual } from '#api/vite/vi'; import { reportDiagnostics } from '#lib/file-router/diagnostic-reporter'; import { FileRouter } from '#lib/file-router/index'; import { debugPolen } from '#singletons/debug'; import { superjson } from '#singletons/superjson'; import mdx from '@mdx-js/rollup'; import { Arr, Cache, Path, Str } from '@wollybeard/kit'; import { recmaCodeHike, remarkCodeHike } from 'codehike/mdx'; import remarkFrontmatter from 'remark-frontmatter'; import remarkGfm from 'remark-gfm'; import { viProjectData } from './core.js'; const debug = debugPolen.sub(`vite-pages`); export const viProjectRoutes = polenVirtual([`project`, `routes.jsx`], { allowPluginProcessing: true }); export const viProjectPagesCatalog = polenVirtual([`project`, `data`, `pages-catalog.jsonsuper`], { allowPluginProcessing: true, }); /** * Pages plugin with tree support */ export const Pages = ({ config, }) => { const scanPages = Cache.memoize(debug.trace(async function scanPages() { const result = await Content.scan({ dir: config.paths.project.absolute.pages, glob: `**/*.{md,mdx}`, }); return result; })); const invalidateVirtualModules = (server) => { const routesModule = server.moduleGraph.getModuleById(viProjectRoutes.id); if (routesModule) { server.moduleGraph.invalidateModule(routesModule); debug(`Invalidated routes virtual module`); } const catalogModule = server.moduleGraph.getModuleById(viProjectPagesCatalog.id); if (catalogModule) { server.moduleGraph.invalidateModule(catalogModule); debug(`Invalidated pages catalog virtual module`); } const projectDataModule = server.moduleGraph.getModuleById(viProjectData.id); if (projectDataModule) { server.moduleGraph.invalidateModule(projectDataModule); debug(`Invalidated project data virtual module`); } }; const generateRoutesModule = (pages) => { const $ = { routes: `routes`, }; const s = Str.Builder(); s `export const ${$.routes} = []`; // Generate imports and route objects for (const { route, metadata } of pages) { const filePathExp = Path.format(route.file.path.absolute); const pathExp = FileRouter.routeToPathExpression(route); const $$ = { ...$, Component: Str.Case.camel(`page ` + Str.titlizeSlug(pathExp)), }; s ` import ${$$.Component} from '${filePathExp}' ${$$.routes}.push({ path: '${pathExp}', Component: ${$$.Component}, metadata: ${JSON.stringify(metadata)} }) `; } return s.render(); }; const chConfig = { components: { code: 'CodeBlock' }, syntaxHighlighting: { theme: `github-light`, }, }; return [ // Plugin 1: MDX Processing { enforce: `pre`, ...mdx({ jsxImportSource: `polen/react`, providerImportSource: `polen/mdx`, remarkPlugins: [ // Parse frontmatter blocks so they're removed from content remarkFrontmatter, remarkGfm, [remarkCodeHike, chConfig], ], recmaPlugins: [ [recmaCodeHike, chConfig], ], rehypePlugins: [], }), }, // Plugin 2: Pages Management { name: `polen:pages`, // Dev server configuration configureServer(server) { // Add pages directory to watcher debug(`configureServer: watch pages directory`, config.paths.project.absolute.pages); server.watcher.add(config.paths.project.absolute.pages); // Handle file additions and deletions const handleFileStructureChange = async (file, event) => { if (!Content.isPageFile(file, config.paths.project.absolute.pages)) return; debug(`Page file ${event === `add` ? `added` : `deleted`}:`, file); // Clear cache and rescan scanPages.clear(); const newScanResult = await scanPages(); // Invalidate virtual modules invalidateVirtualModules(server); // Report any diagnostics reportDiagnostics(newScanResult.diagnostics); // Trigger full reload to ensure routes are updated server.ws.send({ type: `full-reload` }); }; server.watcher.on(`add`, (file) => handleFileStructureChange(file, `add`)); server.watcher.on(`unlink`, (file) => handleFileStructureChange(file, `unlink`)); }, // Hot update handling for existing files async handleHotUpdate({ file, server, modules }) { debug(`handleHotUpdate`, file); if (!Content.isPageFile(file, config.paths.project.absolute.pages)) return; debug(`Page file changed:`, file); // Get current pages before clearing cache const oldPages = await scanPages(); // Clear cache and rescan scanPages.clear(); const newScanResult = await scanPages(); // Check if the visible pages list changed. This can happen when: // - A page's frontmatter `hidden` field changes (true <-> false) // - A page's frontmatter affects its route (though we don't support this yet) // If only the content changed (not frontmatter), we can use fast HMR. const pageStructureChanged = !oldPages || !Arr.equalShallowly(oldPages.list.map(p => Path.format(p.route.file.path.absolute)), newScanResult.list.map(p => Path.format(p.route.file.path.absolute))); if (!pageStructureChanged) { debug(`Page content changed, allowing HMR`); // Let default HMR handle the MDX file change return modules; } // // ━━ Manual Invalidation // debug(`Page structure changed, triggering full reload`); // Invalidate virtual modules and trigger reload invalidateVirtualModules(server); reportDiagnostics(newScanResult.diagnostics); server.ws.send({ type: `full-reload` }); return []; }, resolveId(id) { if (id === viProjectPagesCatalog.id) { return viProjectPagesCatalog.resolved; } }, load: { async handler(id) { if (id !== viProjectPagesCatalog.resolved) return; debug(`hook load`); const scanResult = await scanPages(); reportDiagnostics(scanResult.diagnostics); debug(`found visible`, { count: scanResult.list.length }); // // ━━ Build Sidebar // const sidebarIndex = Content.buildSidebarIndex(scanResult); // // ━━ Put It All together // const projectPagesCatalog = { sidebarIndex, pages: scanResult.list, }; // Return just the JSON string - let the JSON plugin handle the transformation return superjson.stringify(projectPagesCatalog); }, }, }, // Plugin 3: Virtual Module for React Router Routes { name: `polen:routes`, resolveId(id) { if (id === viProjectRoutes.id) { return viProjectRoutes.resolved; } }, load: { handler: async (id) => { if (id !== viProjectRoutes.resolved) return; debug(`Loading viProjectRoutes virtual module`); const scanResult = await scanPages(); reportDiagnostics(scanResult.diagnostics); const code = generateRoutesModule(scanResult.list); // Generate the module code return { code, moduleType: `js`, }; }, }, }, ]; }; //# sourceMappingURL=pages.js.map