Skip to content

Commit c1368c3

Browse files
authoredNov 12, 2022
feat: handle static assets in case-sensitive manner (#10475)
Co-authored-by: Bjorn Lu <bjornlu.dev@gmail.com> Closes #9260
1 parent 7ac2535 commit c1368c3

File tree

4 files changed

+68
-15
lines changed

4 files changed

+68
-15
lines changed
 

‎packages/vite/src/node/preview.ts

+17-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,24 @@ 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 assetServer = 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+
if (shouldServe(req.url!, distDir)) {
129+
return assetServer(req, res, next)
130+
}
131+
next()
132+
})
130133

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

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

+5-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,10 @@ export function servePublicMiddleware(
5253
if (isImportRequest(req.url!) || isInternalRequest(req.url!)) {
5354
return next()
5455
}
55-
serve(req, res, next)
56+
if (shouldServe(req.url!, dir)) {
57+
return serve(req, res, next)
58+
}
59+
next()
5660
}
5761
}
5862

‎packages/vite/src/node/utils.ts

+39
Original file line numberDiff line numberDiff line change
@@ -1198,6 +1198,45 @@ export const isNonDriveRelativeAbsolutePath = (p: string): boolean => {
11981198
return windowsDrivePathPrefixRE.test(p)
11991199
}
12001200

1201+
/**
1202+
* Determine if a file is being requested with the correct case, to ensure
1203+
* consistent behaviour between dev and prod and across operating systems.
1204+
*/
1205+
export function shouldServe(url: string, assetsDir: string): boolean {
1206+
// viteTestUrl is set to something like http://localhost:4173/ and then many tests make calls
1207+
// like `await page.goto(viteTestUrl + '/example')` giving us URLs beginning with a double slash
1208+
const pathname = decodeURI(
1209+
new URL(url.startsWith('//') ? url.substring(1) : url, 'http://example.com')
1210+
.pathname
1211+
)
1212+
const file = path.join(assetsDir, pathname)
1213+
if (
1214+
!fs.existsSync(file) ||
1215+
(isCaseInsensitiveFS && // can skip case check on Linux
1216+
!fs.statSync(file).isDirectory() &&
1217+
!hasCorrectCase(file, assetsDir))
1218+
) {
1219+
return false
1220+
}
1221+
return true
1222+
}
1223+
1224+
/**
1225+
* Note that we can't use realpath here, because we don't want to follow
1226+
* symlinks.
1227+
*/
1228+
function hasCorrectCase(file: string, assets: string): boolean {
1229+
if (file === assets) return true
1230+
1231+
const parent = path.dirname(file)
1232+
1233+
if (fs.readdirSync(parent).includes(path.basename(file))) {
1234+
return hasCorrectCase(parent, assets)
1235+
}
1236+
1237+
return false
1238+
}
1239+
12011240
export function joinUrlSegments(a: string, b: string): string {
12021241
if (!a || !b) {
12031242
return a || b || ''

‎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)
Please sign in to comment.