diff --git a/packages/vite/src/node/server/index.ts b/packages/vite/src/node/server/index.ts index 2dd71aa85c84f8..75d5f0249835bb 100644 --- a/packages/vite/src/node/server/index.ts +++ b/packages/vite/src/node/server/index.ts @@ -1013,12 +1013,12 @@ export async function _createServer( } else { // main transform middleware middlewares.use(transformMiddleware(server)) - - // serve static files - middlewares.use(serveRawFsMiddleware(server)) - middlewares.use(serveStaticMiddleware(server)) } + // serve static files + middlewares.use(serveRawFsMiddleware(server)) + middlewares.use(serveStaticMiddleware(server)) + // html fallback if (config.appType === 'spa' || config.appType === 'mpa') { middlewares.use( diff --git a/playground/assets-sanitize/__tests__/assets-sanitize.spec.ts b/playground/assets-sanitize/__tests__/assets-sanitize.spec.ts index c95cbf6dc11eca..e7bcd186b66264 100644 --- a/playground/assets-sanitize/__tests__/assets-sanitize.spec.ts +++ b/playground/assets-sanitize/__tests__/assets-sanitize.spec.ts @@ -1,12 +1,5 @@ import { expect, test } from 'vitest' -import { - getBg, - isBuild, - isBundled, - isBundledDev, - page, - readManifest, -} from '~utils' +import { getBg, isBuild, isBundledDev, page, readManifest } from '~utils' if (isBuild) { test('importing asset with special char in filename works in build', async () => { @@ -45,10 +38,8 @@ if (isBuild) { }) } -// this checks that the dev server refuses to serve /.env. Build and bundled -// dev serve only the files in the output, so a request can never reach a -// project file. The check does not apply to them. -test.runIf(!isBundled)('denied .env', async () => { +// this checks that the dev server refuses to serve /.env +test.runIf(!isBuild)('denied .env', async () => { expect(await page.textContent('.unsafe-dotenv')).toBe('403') expect(await page.textContent('.unsafe-dotenv-double-slash')).toBe('200') // SPA fallback }) diff --git a/playground/dynamic-import/__tests__/dynamic-import.spec.ts b/playground/dynamic-import/__tests__/dynamic-import.spec.ts index 07a4dd3f0c9451..4275f5ed3a10a6 100644 --- a/playground/dynamic-import/__tests__/dynamic-import.spec.ts +++ b/playground/dynamic-import/__tests__/dynamic-import.spec.ts @@ -15,22 +15,14 @@ test('should load literal dynamic import', async () => { await expect.poll(() => page.textContent('.view')).toMatch('Baz view') }) -// bundled dev: the `@vite-ignore` import asks for /views/qux.js when clicked. -// That file is in the project root but not in the bundle. -// Bundled dev serves only the bundle, so the request fails. -// This is a real gap that should be fixed, not expected behavior. -// Tracked in vitejs/vite#23028 -test.skipIf(isBundledDev)( - 'should load full dynamic import from public', - async () => { - await page.click('.qux') - await expect.poll(() => page.textContent('.view')).toMatch('Qux view') - // No warning should be logged as we are using @vite-ignore - expect( - serverLogs.some((log) => log.includes('cannot be analyzed by vite')), - ).toBe(false) - }, -) +test('should load full dynamic import from public', async () => { + await page.click('.qux') + await expect.poll(() => page.textContent('.view')).toMatch('Qux view') + // No warning should be logged as we are using @vite-ignore + expect( + serverLogs.some((log) => log.includes('cannot be analyzed by vite')), + ).toBe(false) +}) test('should load data URL of `blob:`', async () => { await page.click('.issue-2658-1') @@ -48,15 +40,10 @@ test('should have same reference on static and dynamic js import, .mxd', async ( }) // in this case, it is not possible to detect the correct module -// bundled dev: the `@vite-ignore` URL points at the source file -// ../files/mxd.js. That file is not in the bundle, so it is not served. -test.skipIf(isBundledDev)( - 'should have same reference on static and dynamic js import, .mxd2', - async () => { - await page.click('.mxd2') - await expect.poll(() => page.textContent('.view')).toMatch('false') - }, -) +test('should have same reference on static and dynamic js import, .mxd2', async () => { + await page.click('.mxd2') + await expect.poll(() => page.textContent('.view')).toMatch('false') +}) test('should have same reference on static and dynamic js import, .mxdjson', async () => { await page.click('.mxdjson') diff --git a/playground/fs-serve/__tests__/commonTests.ts b/playground/fs-serve/__tests__/commonTests.ts index a0f8d1c456a299..e13033e66be602 100644 --- a/playground/fs-serve/__tests__/commonTests.ts +++ b/playground/fs-serve/__tests__/commonTests.ts @@ -15,7 +15,14 @@ import type { Page } from 'playwright-chromium' import WebSocket from 'ws' import testJSON from '../safe.json' import { getWindows83ShortNameForDotEnv as getWindows83ShortNameForDotEnv } from '../root/windows83Filename' -import { browser, isServe, page, viteServer, viteTestUrl } from '~utils' +import { + browser, + isBundledDev, + isServe, + page, + viteServer, + viteTestUrl, +} from '~utils' const getViteTestIndexHtmlUrl = () => { const srcPrefix = viteTestUrl.endsWith('/') ? '' : '/' @@ -23,6 +30,12 @@ const getViteTestIndexHtmlUrl = () => { return viteTestUrl + srcPrefix + 'src/' } +// `viteTestUrl` keeps its trailing slash when the playground sets a base, so +// plain concatenation would produce `//`-prefixed paths, which the server +// refuses to serve as files. +const getViteTestUrl = (pathname: string) => + viteTestUrl.replace(/\/$/, '') + pathname + const safeJsonContent = fs.readFileSync( path.resolve(import.meta.dirname, '../safe.json'), 'utf-8', @@ -104,6 +117,10 @@ describe.runIf(isServe)('matrix', () => { content: safeJsonContent, status: '200', disableVariants: [''], + // bundled dev: an imported file outside `fs.allow` is exempted through + // the module graph (`safeModulesPath`), which stays empty under bundled + // dev even if the import is inlined into the bundle (vitejs/vite#23028) + skip: isBundledDev, }, { name: 'safe fetch imported with query', @@ -111,6 +128,8 @@ describe.runIf(isServe)('matrix', () => { content: safeJsonContent, status: '200', disableVariants: [''], + // bundled dev: same `safeModulesPath` exemption as 'safe fetch imported' + skip: isBundledDev, }, { @@ -131,6 +150,13 @@ describe.runIf(isServe)('matrix', () => { testId: 'unsafe-html', content: /403 Restricted/, status: '403', + // bundled dev: static serve middleware does not handle HTML files, + // so the request went through to the HTML middleware, + // which returns 404 for non-existent files (vitejs/vite#23028). + // + // In this case, only normal variant is affected, + // whereas `-fs` variant is handled by `serveRawFsMiddleware`, which checks every file type directly. + disableVariants: isBundledDev ? [''] : [], }, { name: 'unsafe HTML fetch outside root', @@ -332,7 +358,7 @@ describe.runIf(isServe)('matrix', () => { describe('fetch', () => { test('serve with configured headers', async () => { - const res = await fetch(viteTestUrl + '/src/') + const res = await fetch(getViteTestUrl('/src/')) expect(res.headers.get('x-served-by')).toBe('vite') }) }) @@ -402,14 +428,14 @@ describe('cross origin', () => { }) test('fetch HTML file', async () => { - const status = await fetchStatusFromPage(page, viteTestUrl + '/src/') + const status = await fetchStatusFromPage(page, getViteTestUrl('/src/')) expect(status).toBe(200) }) test.runIf(isServe)('fetch JS file', async () => { const status = await fetchStatusFromPage( page, - viteTestUrl + '/src/code.js', + getViteTestUrl('/src/code.js'), ) expect(status).toBe(200) }) @@ -425,7 +451,7 @@ describe('cross origin', () => { test('fetch with allowed hosts', async () => { const viteTestUrlUrl = new URL(viteTestUrl) - const res = await fetch(viteTestUrl + '/src/index.html', { + const res = await fetch(getViteTestUrl('/src/index.html'), { headers: { Host: viteTestUrlUrl.host }, }) expect(res.status).toBe(200) diff --git a/playground/fs-serve/__tests__/fs-serve.spec.ts b/playground/fs-serve/__tests__/fs-serve.spec.ts index 4619ec11b32b81..6423c4171682d0 100644 --- a/playground/fs-serve/__tests__/fs-serve.spec.ts +++ b/playground/fs-serve/__tests__/fs-serve.spec.ts @@ -1,7 +1,7 @@ import net from 'node:net' import path from 'node:path' import { describe, expect, test } from 'vitest' -import { isServe, isWindows, viteTestUrl } from '~utils' +import { isBundledDev, isServe, isWindows, viteTestUrl } from '~utils' import './commonTests' describe.runIf(isServe)('invalid request', () => { @@ -45,6 +45,7 @@ describe.runIf(isServe)('invalid request', () => { target: string status: string content?: string + skip?: boolean }> = [ { name: 'basic request', @@ -104,15 +105,23 @@ describe.runIf(isServe)('invalid request', () => { '/node_modules/.vite/deps/..\\..\\..\\unsafe.map', status: isWindows ? 'HTTP/1.1 403 Forbidden' : 'HTTP/1.1 200 OK', content: isWindows ? undefined : 'Cache-Control: no-cache', + // bundled dev: the 200 comes from the dep optimizer's sourcemap + // handler, and there is no dep optimizer under bundled dev + // (vitejs/vite#23028) + skip: isBundledDev, }, { name: 'HTML outside root with relative path', target: '/../unsafe.html', status: 'HTTP/1.1 403 Forbidden', + // bundled dev: `.html` requests are not served from disk, so the fs + // checks never answer 403 for them — the request 404s instead + // (fail-closed, nothing is served) (vitejs/vite#23028) + skip: isBundledDev, }, ] - for (const { name, target, status, content } of testCases) { - test(name, async () => { + for (const { name, target, status, content, skip } of testCases) { + test(name, { skip }, async () => { const response = await sendRawRequest(viteTestUrl, target) expect(response).toContain(status) if (content !== undefined) { diff --git a/playground/json/__tests__/csr/json-csr.spec.ts b/playground/json/__tests__/csr/json-csr.spec.ts index a903bca9aa1ebe..240b88a61e59c6 100644 --- a/playground/json/__tests__/csr/json-csr.spec.ts +++ b/playground/json/__tests__/csr/json-csr.spec.ts @@ -3,7 +3,7 @@ import { expect, test } from 'vitest' import deepJson from 'vue/package.json' import testJson from '../../test.json' import hmrJson from '../../hmr.json' -import { editFile, isBundled, isBundledDev, isServe, page } from '~utils' +import { editFile, isBundled, isServe, page } from '~utils' const stringified = JSON.stringify(testJson) const deepStringified = JSON.stringify(deepJson) @@ -33,9 +33,7 @@ test('dynamic import, named', async () => { expect(await page.textContent('.dynamic-named')).toBe(testJson.hello) }) -// bundled dev: fetch('/test.json') asks the server for a file in the project -// root. That file is not in the bundle, and bundled dev serves only the bundle. -test.skipIf(isBundledDev)('fetch', async () => { +test('fetch', async () => { expect(await page.textContent('.fetch')).toBe(stringified) }) diff --git a/playground/resolve/__tests__/resolve.spec.ts b/playground/resolve/__tests__/resolve.spec.ts index 08ce7fe4530dc6..cca29da0c3046e 100644 --- a/playground/resolve/__tests__/resolve.spec.ts +++ b/playground/resolve/__tests__/resolve.spec.ts @@ -5,7 +5,6 @@ import { describe, expect, test } from 'vitest' import { isBuild, isBundled, - isBundledDev, isServe, isWindows, page, @@ -284,10 +283,7 @@ test.runIf(isBuild)('sideEffects field glob pattern is respected', async () => { expect(sideEffectValues).toStrictEqual(['success']) }) -// bundled dev: these HEAD requests ask for source files (/absolute.js, -// /style.css). Those files are not in the bundle, and bundled dev serves -// only the bundle. -describe.runIf(isServe && !isBundledDev)('HEAD request handling', () => { +describe.runIf(isServe)('HEAD request handling', () => { test('HEAD request to JS file returns correct Content-Type', async () => { const response = await fetch(new URL('/absolute.js', viteTestUrl), { method: 'HEAD', diff --git a/vitest.config.e2e.ts b/vitest.config.e2e.ts index 9b235669a142b0..966d2d173931db 100644 --- a/vitest.config.e2e.ts +++ b/vitest.config.e2e.ts @@ -16,9 +16,6 @@ const bundledDevExclude = [ './playground/css-codesplit/__tests__/css-codesplit.spec.ts', './playground/css-no-codesplit/__tests__/css-no-codesplit.spec.ts', './playground/forward-console/__test__/forward-console.spec.ts', - './playground/fs-serve/__tests__/base/fs-serve-base.spec.ts', - './playground/fs-serve/__tests__/deny/fs-serve-deny.spec.ts', - './playground/fs-serve/__tests__/fs-serve.spec.ts', './playground/hmr/__tests__/hmr.spec.ts', './playground/legacy/__tests__/chunk-importmap/legacy-chunk-importmap.spec.ts', './playground/object-hooks/__tests__/object-hooks.spec.ts',