mirror of
https://github.com/LukeHagar/vercel.git
synced 2025-12-06 21:07:47 +00:00
[cli] Fix dev query string merging (#8183)
Since `vercel dev` performs query string merging with `vercel.json` routes and the requested url, there is sometimes a slight difference between the request that the browser sends and the request that the upstream dev server receives.
Most servers treat the query string key with "empty" value the same as undefined value. Meaning, these two URLs should be equivalent because each has empty values:
- `http://example.com/src/App.vue?vue&type=style&index=0&lang.css`
- `http://example.com/src/App.vue?vue=&type=style&index=0&lang.css=`
However, `vite dev` handles these two URLs differently because of [string comparisons](2289d04af5/packages/plugin-vue/src/handleHotUpdate.ts (L98-L99)) instead of URL parsing, which causes requests from `vercel dev` to fail. Thus, this PR changes from Node.js native URL parsing to a custom implementation in order to handle this corner case and preserve the difference between `?a=` and `?a`.
Ideally this would be [fixed in vite](https://github.com/vitejs/vite/pull/9589), however we might run into other dev servers that also fail in the same way in the future.
- Fixes https://github.com/vercel/vercel/issues/7283
- Closes https://github.com/vitejs/vite/pull/9589
- Related to https://github.com/nodejs/node/issues/9500
This commit is contained in:
59
packages/cli/src/util/dev/parse-query-string.ts
Normal file
59
packages/cli/src/util/dev/parse-query-string.ts
Normal file
@@ -0,0 +1,59 @@
|
||||
/**
|
||||
* This function is necessary to account for the difference between
|
||||
* `?a=` and `?a` because native `url.parse(str, true)` can't tell.
|
||||
* @param querystring - The querystring to parse, also known as the "search" string.
|
||||
*/
|
||||
export function parseQueryString(
|
||||
querystring?: string
|
||||
): Record<string, string[]> {
|
||||
const query: Record<string, string[]> = Object.create(null);
|
||||
if (!querystring || !querystring.startsWith('?') || querystring === '?') {
|
||||
return query;
|
||||
}
|
||||
const params = querystring.slice(1).split('&');
|
||||
for (let param of params) {
|
||||
let [key, value] = param.split('=');
|
||||
if (key !== undefined) {
|
||||
key = decodeURIComponent(key);
|
||||
}
|
||||
if (value !== undefined) {
|
||||
value = decodeURIComponent(value);
|
||||
}
|
||||
|
||||
let existing = query[key];
|
||||
if (!existing) {
|
||||
existing = [];
|
||||
query[key] = existing;
|
||||
}
|
||||
|
||||
existing.push(value);
|
||||
}
|
||||
return query;
|
||||
}
|
||||
|
||||
/**
|
||||
* This function is necessary to account for the difference between
|
||||
* `?a=` and `?a` because native `url.format({ query })` can't tell.
|
||||
* @param query - The query object to stringify.
|
||||
*/
|
||||
export function formatQueryString(
|
||||
query: Record<string, string[]> | undefined
|
||||
): string | undefined {
|
||||
if (!query) {
|
||||
return undefined;
|
||||
}
|
||||
let s = '';
|
||||
let prefix = '?';
|
||||
for (let [key, values] of Object.entries(query)) {
|
||||
for (let value of values) {
|
||||
s += prefix;
|
||||
s += encodeURIComponent(key);
|
||||
if (value !== undefined) {
|
||||
s += '=';
|
||||
s += encodeURIComponent(value);
|
||||
}
|
||||
prefix = '&';
|
||||
}
|
||||
}
|
||||
return s || undefined;
|
||||
}
|
||||
Reference in New Issue
Block a user