Skip to content

Commit

Permalink
feat: handle static assets in case-sensitive manner
Browse files Browse the repository at this point in the history
  • Loading branch information
benmccann committed Oct 17, 2022
1 parent f542727 commit 118e36d
Show file tree
Hide file tree
Showing 4 changed files with 70 additions and 15 deletions.
37 changes: 23 additions & 14 deletions packages/vite/src/node/preview.ts
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,30 @@ 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) => {
// TODO: why is this necessary? what's screwing up the request URL?
// tons of tests fail without this since we're receiving URLs like //assets/dep-42fa3c.js
const fixedUrl = req.url!.startsWith('//')
? req.url!.substring(1)
: req.url!
const url = new URL(fixedUrl, 'http://example.com')
if (shouldServe(url, distDir)) {
return assetServer(req, res, next)
}
next()
})

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

Expand Down Expand Up @@ -52,7 +53,11 @@ export function servePublicMiddleware(
if (isImportRequest(req.url!) || isInternalRequest(req.url!)) {
return next()
}
serve(req, res, next)
const url = new URL(req.url!, 'http://example.com')
if (shouldServe(url, dir)) {
return serve(req, res, next)
}
next()
}
}

Expand Down
34 changes: 34 additions & 0 deletions packages/vite/src/node/utils.ts
Expand Up @@ -1190,3 +1190,37 @@ export const isNonDriveRelativeAbsolutePath = (p: string): boolean => {
if (!isWindows) return p.startsWith('/')
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: URL, assetsDir: string): boolean {
const pathname = decodeURIComponent(url.pathname)
const file = assetsDir + pathname
if (
!fs.existsSync(file) ||
(!fs.statSync(file).isDirectory() &&
isCaseInsensitiveFS && // can skip case check on Linux
!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
}
7 changes: 7 additions & 0 deletions playground/assets/__tests__/assets.spec.ts
@@ -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 118e36d

Please sign in to comment.