Skip to content
Open
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
7 changes: 6 additions & 1 deletion packages/plugin-rsc/src/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ import {
directRequestRE,
evalValue,
normalizeViteImportAnalysisUrl,
parseRequestUrl,
prepareError,
slash,
} from './plugins/vite-utils'
Expand Down Expand Up @@ -1056,7 +1057,11 @@ export default function vitePluginRsc(
}

server.middlewares.use(async (req, res, next) => {
const url = new URL(req.url ?? '/', `http://localhost`)
const url = parseRequestUrl(req.url)
if (!url) {
next()
return
}
if (url.pathname === '/__vite_rsc_load_module_dev_proxy') {
try {
const handler = await createHandler(url)
Expand Down
8 changes: 6 additions & 2 deletions packages/plugin-rsc/src/plugins/find-source-map-url.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import {
type Plugin,
type ViteDevServer,
} from 'vite'
import { slash } from './vite-utils'
import { parseRequestUrl, slash } from './vite-utils'

//
// support findSourceMapURL
Expand All @@ -21,7 +21,11 @@ export function vitePluginFindSourceMapURL(): Plugin[] {
apply: 'serve',
configureServer(server) {
server.middlewares.use(async (req, res, next) => {
const url = new URL(req.url!, `http://localhost`)
const url = parseRequestUrl(req.url)
if (!url) {
next()
return
}
if (url.pathname === '/__vite_rsc_findSourceMapURL') {
let filename = url.searchParams.get('filename')!
let environmentName = url.searchParams.get('environmentName')!
Expand Down
21 changes: 21 additions & 0 deletions packages/plugin-rsc/src/plugins/vite-utils.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { describe, expect, it } from 'vitest'
import { parseRequestUrl } from './vite-utils'

describe(parseRequestUrl, () => {
it('parses normal request urls', () => {
expect(parseRequestUrl('/')?.pathname).toBe('/')
expect(
parseRequestUrl('/__vite_rsc_findSourceMapURL?filename=a')?.pathname,
).toBe('/__vite_rsc_findSourceMapURL')
expect(parseRequestUrl(undefined)?.pathname).toBe('/')
})

it('returns undefined instead of throwing for unparseable request urls', () => {
// A request to `//` is valid HTTP but `new URL('//', base)` throws
// ERR_INVALID_URL. An uncaught throw inside an async Connect middleware
// becomes an unhandled rejection and kills the dev server, so the
// middleware skips to `next()` on undefined instead.
expect(parseRequestUrl('//')).toBeUndefined()
expect(parseRequestUrl('//[')).toBeUndefined()
})
})
12 changes: 12 additions & 0 deletions packages/plugin-rsc/src/plugins/vite-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -165,3 +165,15 @@ export function evalValue<T = any>(rawValue: string): T {

// https://github.com/vitejs/vite/blob/84079a84ad94de4c1ef4f1bdb2ab448ff2c01196/packages/vite/src/node/utils.ts#L321
export const directRequestRE: RegExp = /(\?|&)direct=?(?:&|$)/

// Parse an incoming dev-middleware request URL without throwing. Requests
// like `//` or `//[` are valid HTTP but do not parse as URLs; an uncaught
// throw inside an async Connect middleware becomes an unhandled rejection
// and takes down the whole dev server. Callers skip to `next()` on undefined.
export function parseRequestUrl(reqUrl: string | undefined): URL | undefined {
try {
return new URL(reqUrl ?? '/', 'http://localhost')
} catch {
return undefined
}
}
Loading