Skip to content

Commit 9d17ea7

Browse files
committed
fix(vite): keep ?import module requests on vite in dev
An imported `.json` has no extension in `ASSET_EXT_RE`, so with `Sec-Fetch-Dest` absent the dev middleware handed `/x.json?import` to the SSR catch-all, which answered it with HTML. Vite only ever tags module graph fetches with `?import`, so treat that query as an authoritative asset signal in the fallback branch.
1 parent bfc2f5e commit 9d17ea7

2 files changed

Lines changed: 18 additions & 1 deletion

File tree

src/build/vite/dev.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,11 @@ import { getEnvRunner } from "./env.ts";
2424
const ASSET_EXT_RE =
2525
/^(?:[jt]sx?|mjs|cjs|css|s[ac]ss|less|styl|vue|svelte|astro|mdx?|map|wasm|png|jpe?g|gif|svg|webp|avif|ico|bmp|woff2?|ttf|otf|eot|mp[34]|webm|wav|ogg|m4a)$/i;
2626

27+
// Vite tags module graph fetches for files it has to serve as modules (e.g. an imported `.json`)
28+
// with an `?import` query. Only the module graph emits it — a page navigation never does — so it
29+
// stays authoritative for extensions deliberately left out of `ASSET_EXT_RE` (#4433).
30+
const VITE_IMPORT_QUERY_RE = /[?&]import(?:[&=]|$)/;
31+
2732
// workerd built-in module namespaces (`cloudflare:workers`, `cloudflare:sockets`, `workerd:...`).
2833
// These are provided natively by the runtime and have no host-side representation, so they must be
2934
// externalized for the in-worker module runner to `import()` them directly instead of being fetched
@@ -313,7 +318,7 @@ export async function configureViteDevServer(ctx: NitroPluginContext, server: Vi
313318
const isAsset =
314319
typeof fetchDest === "string" && fetchDest !== "empty"
315320
? !/^(?:document|iframe|frame)$/.test(fetchDest)
316-
: isAssetByExt;
321+
: VITE_IMPORT_QUERY_RE.test(req.url!) || isAssetByExt;
317322

318323
// Non-asset requests go to Nitro: the catch-all (`matchedHandlers` are all catch-all here,
319324
// since explicit routes already returned) renders them, and bare (extensionless) unmatched

test/vite/app.test.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,18 @@ describe("vite:app", () => {
8282
expect(res.status).not.toBe(200);
8383
});
8484

85+
// #4433: Vite marks module-graph fetches for files it has to serve as modules with an `?import`
86+
// query. The marker only ever comes from the module graph, never from a page navigation, so such
87+
// a request must reach Vite even when the extension is not a known asset type (`.json`) and
88+
// `Sec-Fetch-Dest` is absent.
89+
test("does not let the SSR catch-all swallow ?import module requests", async () => {
90+
const res = await fetch(`${serverURL}/missing-module.json?import`, {
91+
headers: { accept: "*/*" },
92+
redirect: "manual",
93+
});
94+
expect(res.status).not.toBe(200);
95+
});
96+
8597
// HTTPError thrown from the SSR entry must propagate to the nitro app so the h3
8698
// error handler preserves its status and headers (consistent with production).
8799
test("propagates HTTPError status and headers from the SSR entry", async () => {

0 commit comments

Comments
 (0)