Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions packages/vite/src/node/server/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
15 changes: 3 additions & 12 deletions playground/assets-sanitize/__tests__/assets-sanitize.spec.ts
Original file line number Diff line number Diff line change
@@ -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 () => {
Expand Down Expand Up @@ -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
})
37 changes: 12 additions & 25 deletions playground/dynamic-import/__tests__/dynamic-import.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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')
Expand All @@ -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')
Expand Down
36 changes: 31 additions & 5 deletions playground/fs-serve/__tests__/commonTests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,27 @@ 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('/') ? '' : '/'
// NOTE: viteTestUrl is set lazily
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',
Expand Down Expand Up @@ -104,13 +117,19 @@ 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',
testId: 'safe-imported-query',
content: safeJsonContent,
status: '200',
disableVariants: [''],
// bundled dev: same `safeModulesPath` exemption as 'safe fetch imported'
skip: isBundledDev,
},

{
Expand All @@ -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',
Expand Down Expand Up @@ -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')
})
})
Expand Down Expand Up @@ -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)
})
Expand All @@ -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)
Expand Down
15 changes: 12 additions & 3 deletions playground/fs-serve/__tests__/fs-serve.spec.ts
Original file line number Diff line number Diff line change
@@ -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', () => {
Expand Down Expand Up @@ -45,6 +45,7 @@ describe.runIf(isServe)('invalid request', () => {
target: string
status: string
content?: string
skip?: boolean
}> = [
{
name: 'basic request',
Expand Down Expand Up @@ -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) {
Expand Down
6 changes: 2 additions & 4 deletions playground/json/__tests__/csr/json-csr.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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)
})

Expand Down
6 changes: 1 addition & 5 deletions playground/resolve/__tests__/resolve.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import { describe, expect, test } from 'vitest'
import {
isBuild,
isBundled,
isBundledDev,
isServe,
isWindows,
page,
Expand Down Expand Up @@ -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',
Expand Down
3 changes: 0 additions & 3 deletions vitest.config.e2e.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down
Loading