Repository: withastro/astro
File: packages/astro/src/vite-plugin-astro-server/plugin.ts (TypeScript)

Code before:
	manifest: routeManifest,
	ssrManifest: devSSRManifest,
}: AstroPluginOptions): vite.Plugin {
	return {
		name: 'astro:server',
		configureServer(viteServer) {
			const loader = createViteLoader(viteServer);
			const pipeline = DevPipeline.create(routeManifest, {
				loader,
				logger,
				manifest: devSSRManifest,
				settings,
			});
			const controller = createController({ loader });
			const localStorage = new AsyncLocalStorage();

			/** rebuild the route cache + manifest */
			async function rebuildManifest(path: string | null = null) {
				pipeline.clearRouteCache();

				// If a route changes, we check if it's part of the manifest and check for its prerender value
				if (path !== null) {
					const route = routeManifest.routes.find((r) => path.endsWith(r.component));
					if (!route) {
						return;
					}
					if (route.type !== 'page' && route.type !== 'endpoint') return;

					const routePath = fileURLToPath(new URL(route.component, settings.config.root));
					if (!fsMod.existsSync(routePath)) {
						// Route was renamed so it does not exist anymore
						return;
					}
					const content = await fsMod.promises.readFile(routePath, 'utf-8');
					await getRoutePrerenderOption(content, route, settings, logger);
				} else {
					routeManifest = injectDefaultDevRoutes(
						settings,
						devSSRManifest,
						await createRouteManifest({ settings, fsMod }, logger, { dev: true }),
					);
				}
				await runHookRoutesResolved({ routes: routeManifest.routes, settings, logger });

				warnMissingAdapter(logger, settings);
				pipeline.manifest.checkOrigin =
					settings.config.security.checkOrigin && settings.buildOutput === 'server';
				pipeline.setManifestData(routeManifest);
			}

			// Rebuild route manifest on file change

Review comment (bug):
There's an edge case where this causes a conflict:
- Component `src/pages/index.astro` exists
- Component `src/src/pages/index.astro` is edited

`path.endsWith(r.component)` will match the index page and apply the prerender option from `src/src/pages/index.astro` instead of its own.

~~At first, I thought of suggesting `path === join(settings.root, r.component)`, but that would lead to problems with entry points on `node_modules` using import paths.~~

Given Ema's comment that these are already resolved to file paths then they could be compared for equality combining with the root path.

Separately, is `path` normalized across platforms to account for `\` and `/` separators?

Code after:
	fs: fsMod,
	manifest: routeManifest,
	ssrManifest: devSSRManifest,
}: AstroPluginOptions): vite.Plugin {
	return {
		name: 'astro:server',
		configureServer(viteServer) {
			const loader = createViteLoader(viteServer);
			const pipeline = DevPipeline.create(routeManifest, {
				loader,
				logger,
				manifest: devSSRManifest,
				settings,
			});
			const controller = createController({ loader });
			const localStorage = new AsyncLocalStorage();

			/** rebuild the route cache + manifest */
			async function rebuildManifest(path: string | null = null) {
				pipeline.clearRouteCache();

				// If a route changes, we check if it's part of the manifest and check for its prerender value
				if (path !== null) {
					const route = routeManifest.routes.find(
						(r) =>
							normalizePath(path) ===
							normalizePath(fileURLToPath(new URL(r.component, settings.config.root))),
					);
					if (!route) {
						return;
					}
					if (route.type !== 'page' && route.type !== 'endpoint') return;

					const routePath = fileURLToPath(new URL(route.component, settings.config.root));
					try {
						const content = await fsMod.promises.readFile(routePath, 'utf-8');
						await getRoutePrerenderOption(content, route, settings, logger);
					} catch (_) {}
				} else {
					routeManifest = injectDefaultDevRoutes(
						settings,
						devSSRManifest,
						await createRouteManifest({ settings, fsMod }, logger, { dev: true }),
					);
				}
				await runHookRoutesResolved({ routes: routeManifest.routes, settings, logger });

				warnMissingAdapter(logger, settings);
				pipeline.manifest.checkOrigin =
					settings.config.security.checkOrigin && settings.buildOutput === 'server';
				pipeline.setManifestData(routeManifest);