diff --git a/packages/vite/src/node/server/middlewares/transform.ts b/packages/vite/src/node/server/middlewares/transform.ts index e41d5040480626..3da7689ffa752b 100644 --- a/packages/vite/src/node/server/middlewares/transform.ts +++ b/packages/vite/src/node/server/middlewares/transform.ts @@ -40,6 +40,17 @@ const debugCache = createDebugger('vite:cache') const knownIgnoreList = new Set(['/', '/favicon.ico']) +const documentFetchDests = new Set([ + 'document', + 'iframe', + 'frame', + 'fencedframe', +]) +function isDocumentFetchDest(req: Connect.IncomingMessage) { + const fetchDest = req.headers['sec-fetch-dest'] + return fetchDest !== undefined && documentFetchDests.has(fetchDest) +} + // TODO: consolidate this regex pattern with the url, raw, and inline checks in plugins const urlRE = /[?&]url\b/ const rawRE = /[?&]raw\b/ @@ -63,6 +74,11 @@ export function cachedTransformMiddleware( return function viteCachedTransformMiddleware(req, res, next) { const environment = server.environments.client + if (isDocumentFetchDest(req)) { + res.appendHeader('Vary', 'Sec-Fetch-Dest') + return next() + } + // check if we can return 304 early const ifNoneMatch = req.headers['if-none-match'] if (ifNoneMatch) { @@ -102,7 +118,8 @@ export function transformMiddleware( if ( (req.method !== 'GET' && req.method !== 'HEAD') || - knownIgnoreList.has(req.url!) + knownIgnoreList.has(req.url!) || + isDocumentFetchDest(req) ) { return next() } diff --git a/playground/html/__tests__/html.spec.ts b/playground/html/__tests__/html.spec.ts index 0ae26d2fc832f2..635b028db44e75 100644 --- a/playground/html/__tests__/html.spec.ts +++ b/playground/html/__tests__/html.spec.ts @@ -273,6 +273,16 @@ describe('link with props', () => { }) }) +describe.runIf(isServe)('SPA fallback', () => { + test('should serve index.html via page navigation even when path matches file basename', async () => { + const response = await page.goto(viteTestUrl + '/test') + expect(response.status()).toBe(200) + const content = await page.content() + expect(content).toContain('Transformed') + expect(content).not.toContain('This is test.js') + }) +}) + describe.runIf(isServe)('invalid', () => { test('should be 500 with overlay', async () => { const response = await page.goto(viteTestUrl + '/invalid.html') diff --git a/playground/html/test.js b/playground/html/test.js new file mode 100644 index 00000000000000..494ec96549637c --- /dev/null +++ b/playground/html/test.js @@ -0,0 +1,4 @@ +// This file is used to test https://github.com/vitejs/vite/issues/20705 +// The dev server should NOT serve this file when navigating to /test +export const message = + 'This is test.js - should not be served for /test navigation'