diff options
Diffstat (limited to 'packages/astro/src/vite-plugin-astro-server/trailing-slash.ts')
-rw-r--r-- | packages/astro/src/vite-plugin-astro-server/trailing-slash.ts | 38 |
1 files changed, 38 insertions, 0 deletions
diff --git a/packages/astro/src/vite-plugin-astro-server/trailing-slash.ts b/packages/astro/src/vite-plugin-astro-server/trailing-slash.ts new file mode 100644 index 000000000..00f71b784 --- /dev/null +++ b/packages/astro/src/vite-plugin-astro-server/trailing-slash.ts @@ -0,0 +1,38 @@ +import type * as vite from 'vite'; +import type { AstroSettings } from '../types/astro.js'; + +import { collapseDuplicateTrailingSlashes, hasFileExtension } from '@astrojs/internal-helpers/path'; +import { trailingSlashMismatchTemplate } from '../template/4xx.js'; +import { writeHtmlResponse, writeRedirectResponse } from './response.js'; + +export function trailingSlashMiddleware(settings: AstroSettings): vite.Connect.NextHandleFunction { + const { trailingSlash } = settings.config; + + return function devTrailingSlash(req, res, next) { + const url = new URL(`http://localhost${req.url}`); + let pathname: string; + try { + pathname = decodeURI(url.pathname); + } catch (e) { + /* malformed uri */ + return next(e); + } + if (pathname.startsWith('/_') || pathname.startsWith('/@')) { + return next(); + } + + const destination = collapseDuplicateTrailingSlashes(pathname, true); + if (pathname && destination !== pathname) { + return writeRedirectResponse(res, 301, `${destination}${url.search}`); + } + + if ( + (trailingSlash === 'never' && pathname.endsWith('/') && pathname !== '/') || + (trailingSlash === 'always' && !pathname.endsWith('/') && !hasFileExtension(pathname)) + ) { + const html = trailingSlashMismatchTemplate(pathname, trailingSlash); + return writeHtmlResponse(res, 404, html); + } + return next(); + }; +} |