Skip to content

Commit e6846e3

Browse files
committed
feat: handle static assets in case-sensitive manner
1 parent f542727 commit e6846e3

File tree

4 files changed

+68
-15
lines changed

4 files changed

+68
-15
lines changed

packages/vite/src/node/preview.ts

+23-14
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ import {
1515
import { openBrowser } from './server/openBrowser'
1616
import compression from './server/middlewares/compression'
1717
import { proxyMiddleware } from './server/middlewares/proxy'
18-
import { resolveHostname, resolveServerUrls } from './utils'
18+
import { resolveHostname, resolveServerUrls, shouldServe } from './utils'
1919
import { printServerUrls } from './logger'
2020
import { resolveConfig } from '.'
2121
import type { InlineConfig, ResolvedConfig } from '.'
@@ -112,21 +112,30 @@ export async function preview(
112112
// static assets
113113
const distDir = path.resolve(config.root, config.build.outDir)
114114
const headers = config.preview.headers
115-
app.use(
116-
previewBase,
117-
sirv(distDir, {
118-
etag: true,
119-
dev: true,
120-
single: config.appType === 'spa',
121-
setHeaders(res) {
122-
if (headers) {
123-
for (const name in headers) {
124-
res.setHeader(name, headers[name]!)
125-
}
115+
const asset_server = sirv(distDir, {
116+
etag: true,
117+
dev: true,
118+
single: config.appType === 'spa',
119+
setHeaders(res) {
120+
if (headers) {
121+
for (const name in headers) {
122+
res.setHeader(name, headers[name]!)
126123
}
127124
}
128-
})
129-
)
125+
}
126+
})
127+
app.use(previewBase, async (req, res, next) => {
128+
// TODO: why is this necessary? what's screwing up the request URL?
129+
// tons of tests fail without this since we're receiving URLs like //assets/dep-42fa3c.js
130+
const fixedUrl = req.url!.startsWith('//')
131+
? req.url!.substring(1)
132+
: req.url!
133+
const url = new URL(fixedUrl, 'http://example.com')
134+
if (shouldServe(url, distDir)) {
135+
return asset_server(req, res, next)
136+
}
137+
next()
138+
})
130139

131140
// apply post server hooks from plugins
132141
postHooks.forEach((fn) => fn && fn())

packages/vite/src/node/server/middlewares/static.ts

+6-1
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import {
1414
isInternalRequest,
1515
isParentDirectory,
1616
isWindows,
17+
shouldServe,
1718
slash
1819
} from '../../utils'
1920

@@ -52,7 +53,11 @@ export function servePublicMiddleware(
5253
if (isImportRequest(req.url!) || isInternalRequest(req.url!)) {
5354
return next()
5455
}
55-
serve(req, res, next)
56+
const url = new URL(req.url!, 'http://example.com')
57+
if (shouldServe(url, dir)) {
58+
return serve(req, res, next)
59+
}
60+
next()
5661
}
5762
}
5863

packages/vite/src/node/utils.ts

+32
Original file line numberDiff line numberDiff line change
@@ -1190,3 +1190,35 @@ export const isNonDriveRelativeAbsolutePath = (p: string): boolean => {
11901190
if (!isWindows) return p.startsWith('/')
11911191
return windowsDrivePathPrefixRE.test(p)
11921192
}
1193+
1194+
/**
1195+
* Determine if a file is being requested with the correct case, to ensure
1196+
* consistent behaviour between dev and prod and across operating systems.
1197+
*/
1198+
export function shouldServe(url: URL, assetsDir: string): boolean {
1199+
const pathname = decodeURIComponent(url.pathname)
1200+
const file = assetsDir + pathname
1201+
if (
1202+
!fs.existsSync(file) ||
1203+
(!fs.statSync(file).isDirectory() && !hasCorrectCase(file, assetsDir))
1204+
) {
1205+
return false
1206+
}
1207+
return true
1208+
}
1209+
1210+
/**
1211+
* Note that we can't use realpath here, because we don't want to follow
1212+
* symlinks.
1213+
*/
1214+
function hasCorrectCase(file: string, assets: string): boolean {
1215+
if (file === assets) return true
1216+
1217+
const parent = path.dirname(file)
1218+
1219+
if (fs.readdirSync(parent).includes(path.basename(file))) {
1220+
return hasCorrectCase(parent, assets)
1221+
}
1222+
1223+
return false
1224+
}

playground/assets/__tests__/assets.spec.ts

+7
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import fetch from 'node-fetch'
12
import { describe, expect, test } from 'vitest'
23
import {
34
browserLogs,
@@ -12,6 +13,7 @@ import {
1213
readFile,
1314
readManifest,
1415
untilUpdated,
16+
viteTestUrl,
1517
watcher
1618
} from '~utils'
1719

@@ -27,6 +29,11 @@ test('should have no 404s', () => {
2729
})
2830
})
2931

32+
test('should get a 404 when using incorrect case', async () => {
33+
expect((await fetch(viteTestUrl + 'icon.png')).status).toBe(200)
34+
expect((await fetch(viteTestUrl + 'ICON.png')).status).toBe(404)
35+
})
36+
3037
describe('injected scripts', () => {
3138
test('@vite/client', async () => {
3239
const hasClient = await page.$(

0 commit comments

Comments
 (0)