Skip to content

Commit

Permalink
feat: handle static assets in case-sensitive manner (#10475)
Browse files Browse the repository at this point in the history
Co-authored-by: Bjorn Lu <bjornlu.dev@gmail.com>
Closes #9260
  • Loading branch information
benmccann authored Nov 12, 2022
1 parent 7ac2535 commit c1368c3
Show file tree
Hide file tree
Showing 4 changed files with 68 additions and 15 deletions.
31 changes: 17 additions & 14 deletions packages/vite/src/node/preview.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import {
import { openBrowser } from './server/openBrowser'
import compression from './server/middlewares/compression'
import { proxyMiddleware } from './server/middlewares/proxy'
import { resolveHostname, resolveServerUrls } from './utils'
import { resolveHostname, resolveServerUrls, shouldServe } from './utils'
import { printServerUrls } from './logger'
import { resolveConfig } from '.'
import type { InlineConfig, ResolvedConfig } from '.'
Expand Down Expand Up @@ -112,21 +112,24 @@ export async function preview(
// static assets
const distDir = path.resolve(config.root, config.build.outDir)
const headers = config.preview.headers
app.use(
previewBase,
sirv(distDir, {
etag: true,
dev: true,
single: config.appType === 'spa',
setHeaders(res) {
if (headers) {
for (const name in headers) {
res.setHeader(name, headers[name]!)
}
const assetServer = sirv(distDir, {
etag: true,
dev: true,
single: config.appType === 'spa',
setHeaders(res) {
if (headers) {
for (const name in headers) {
res.setHeader(name, headers[name]!)
}
}
})
)
}
})
app.use(previewBase, async (req, res, next) => {
if (shouldServe(req.url!, distDir)) {
return assetServer(req, res, next)
}
next()
})

// apply post server hooks from plugins
postHooks.forEach((fn) => fn && fn())
Expand Down
6 changes: 5 additions & 1 deletion packages/vite/src/node/server/middlewares/static.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {
isInternalRequest,
isParentDirectory,
isWindows,
shouldServe,
slash
} from '../../utils'

Expand Down Expand Up @@ -52,7 +53,10 @@ export function servePublicMiddleware(
if (isImportRequest(req.url!) || isInternalRequest(req.url!)) {
return next()
}
serve(req, res, next)
if (shouldServe(req.url!, dir)) {
return serve(req, res, next)
}
next()
}
}

Expand Down
39 changes: 39 additions & 0 deletions packages/vite/src/node/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1198,6 +1198,45 @@ export const isNonDriveRelativeAbsolutePath = (p: string): boolean => {
return windowsDrivePathPrefixRE.test(p)
}

/**
* Determine if a file is being requested with the correct case, to ensure
* consistent behaviour between dev and prod and across operating systems.
*/
export function shouldServe(url: string, assetsDir: string): boolean {
// viteTestUrl is set to something like http://localhost:4173/ and then many tests make calls
// like `await page.goto(viteTestUrl + '/example')` giving us URLs beginning with a double slash
const pathname = decodeURI(
new URL(url.startsWith('//') ? url.substring(1) : url, 'http://example.com')
.pathname
)
const file = path.join(assetsDir, pathname)
if (
!fs.existsSync(file) ||
(isCaseInsensitiveFS && // can skip case check on Linux
!fs.statSync(file).isDirectory() &&
!hasCorrectCase(file, assetsDir))
) {
return false
}
return true
}

/**
* Note that we can't use realpath here, because we don't want to follow
* symlinks.
*/
function hasCorrectCase(file: string, assets: string): boolean {
if (file === assets) return true

const parent = path.dirname(file)

if (fs.readdirSync(parent).includes(path.basename(file))) {
return hasCorrectCase(parent, assets)
}

return false
}

export function joinUrlSegments(a: string, b: string): string {
if (!a || !b) {
return a || b || ''
Expand Down
7 changes: 7 additions & 0 deletions playground/assets/__tests__/assets.spec.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import fetch from 'node-fetch'
import { describe, expect, test } from 'vitest'
import {
browserLogs,
Expand All @@ -12,6 +13,7 @@ import {
readFile,
readManifest,
untilUpdated,
viteTestUrl,
watcher
} from '~utils'

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

test('should get a 404 when using incorrect case', async () => {
expect((await fetch(viteTestUrl + 'icon.png')).status).toBe(200)
expect((await fetch(viteTestUrl + 'ICON.png')).status).toBe(404)
})

describe('injected scripts', () => {
test('@vite/client', async () => {
const hasClient = await page.$(
Expand Down

0 comments on commit c1368c3

Please sign in to comment.