Skip to content

Commit d611caf

Browse files
Metafiles support in vite-plugin-cloudflare (#8432)
* Add noUncheckedIndexedAccess to workers-shared * Metafiles support in vite-plugin-cloudflare * fixup! Metafiles support in vite-plugin-cloudflare * Update changeset to make it more clear that the feature is experimental and not to be relied upon. * await the polling in the test * fix file watching on Windows --------- Co-authored-by: Peter Bacon Darwin <[email protected]> Co-authored-by: Pete Bacon Darwin <[email protected]>
1 parent 1f27e26 commit d611caf

File tree

18 files changed

+461
-57
lines changed

18 files changed

+461
-57
lines changed

.changeset/deep-symbols-heal.md

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
---
2+
"@cloudflare/vite-plugin": patch
3+
---
4+
5+
Experimental: add support for Workers Assets metafiles (\_headers and \_redirects) in `vite dev`.
6+
7+
**Experimental feature**: This feature is being made available behind an experimental option (`headersAndRedirectsDevModeSupport`) in the cloudflare plugin configuration. It could change or be removed at any time.
8+
9+
```ts
10+
cloudflare({
11+
// ...
12+
experimental: { headersAndRedirectsDevModeSupport: true },
13+
}),
14+
```
15+
16+
Currently, in this experimental mode, requests that would result in an HTML response or a 404 response will take into account the \_headers and \_redirects settings.
17+
18+
Known limitation: requests for existing static assets will be served directly by Vite without considering the \_headers or \_redirects settings.
19+
20+
Production deployments or using `vite preview` already accurately supports the `_headers` and `_footers` features. The recommendation is to use `vite preview` for local testing of these settings.
Lines changed: 67 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,71 @@
1-
import { expect, test } from "vitest";
2-
import { page, viteTestUrl } from "../../__test-utils__";
1+
import { describe, expect, test } from "vitest";
2+
import { isBuild, page, viteTestUrl } from "../../__test-utils__";
33

4-
test("returns the correct home page", async () => {
5-
const content = await page.textContent("h1");
6-
expect(content).toBe("Vite + React");
7-
});
4+
describe("react-spa", () => {
5+
test("returns the correct home page", async () => {
6+
const content = await page.textContent("h1");
7+
expect(content).toBe("Vite + React");
8+
});
89

9-
test("allows updating state", async () => {
10-
const button = page.getByRole("button", { name: "increment" });
11-
const contentBefore = await button.innerText();
12-
expect(contentBefore).toBe("count is 0");
13-
await button.click();
14-
const contentAfter = await button.innerText();
15-
expect(contentAfter).toBe("count is 1");
16-
});
10+
test("allows updating state", async () => {
11+
const button = page.getByRole("button", { name: "increment" });
12+
const contentBefore = await button.innerText();
13+
expect(contentBefore).toBe("count is 0");
14+
await button.click();
15+
const contentAfter = await button.innerText();
16+
expect(contentAfter).toBe("count is 1");
17+
});
18+
19+
test("returns the home page for not found routes", async () => {
20+
await page.goto(`${viteTestUrl}/random-page`);
21+
const content = await page.textContent("h1");
22+
expect(content).toBe("Vite + React");
23+
});
24+
25+
// All these _headers tests will fail without experimental support turned on in dev mode
26+
// But they will pass in build/preview mode.
27+
describe("_headers", () => {
28+
failsIf(!isBuild)("applies _headers to HTML responses", async ({}) => {
29+
const response = await fetch(viteTestUrl);
30+
expect(response.headers.get("X-Header")).toBe("Custom-Value!!!");
31+
});
1732

18-
test("returns the home page for not found routes", async () => {
19-
await page.goto(`${viteTestUrl}/random-page`);
20-
const content = await page.textContent("h1");
21-
expect(content).toBe("Vite + React");
33+
failsIf(!isBuild)("applies _headers to static assets", async ({}) => {
34+
const response = await fetch(`${viteTestUrl}/vite.svg`);
35+
expect(response.headers.get("X-Header")).toBe("Custom-Value!!!");
36+
});
37+
});
38+
39+
// All these _redirects tests will fail without experimental support turned on in dev mode
40+
// But they will pass in build/preview mode.
41+
describe("_redirects", () => {
42+
failsIf(!isBuild)("applies _redirects to HTML responses", async ({}) => {
43+
const response = await fetch(`${viteTestUrl}/foo`, {
44+
redirect: "manual",
45+
});
46+
expect(response.status).toBe(302);
47+
expect(response.headers.get("Location")).toBe("/bar");
48+
});
49+
50+
failsIf(!isBuild)("applies _redirects to static assets", async ({}) => {
51+
const response = await fetch(`${viteTestUrl}/redirect.svg`, {
52+
redirect: "manual",
53+
});
54+
expect(response.status).toBe(302);
55+
expect(response.headers.get("Location")).toBe("/target.svg");
56+
});
57+
58+
failsIf(!isBuild)(
59+
"supports proxying static assets to rewritten contents with _redirects",
60+
async ({}) => {
61+
const response = await fetch(`${viteTestUrl}/rewrite.svg`);
62+
expect(response.status).toBe(200);
63+
expect(await response.text()).toContain("target.svg");
64+
}
65+
);
66+
});
2267
});
68+
69+
function failsIf(condition: boolean) {
70+
return condition ? test.fails : test;
71+
}
Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
import { readFileSync, writeFileSync } from "node:fs";
2+
import { join } from "node:path";
3+
import { describe, expect, test, vi } from "vitest";
4+
import { isBuild, page, viteTestUrl } from "../../../__test-utils__";
5+
6+
describe(
7+
"react-spa (with experimental support)",
8+
{ sequential: true, concurrent: false },
9+
() => {
10+
test("returns the correct home page", async () => {
11+
const content = await page.textContent("h1");
12+
expect(content).toBe("Vite + React");
13+
});
14+
15+
test("allows updating state", async () => {
16+
const button = page.getByRole("button", { name: "increment" });
17+
const contentBefore = await button.innerText();
18+
expect(contentBefore).toBe("count is 0");
19+
await button.click();
20+
const contentAfter = await button.innerText();
21+
expect(contentAfter).toBe("count is 1");
22+
});
23+
24+
test("returns the home page for not found routes", async () => {
25+
await page.goto(`${viteTestUrl}/random-page`);
26+
const content = await page.textContent("h1");
27+
expect(content).toBe("Vite + React");
28+
});
29+
30+
// All these tests will fail without experimental support turned on
31+
describe("_headers", () => {
32+
test("applies _headers to HTML responses", async ({}) => {
33+
const response = await fetch(viteTestUrl);
34+
expect(response.headers.get("X-Header")).toBe("Custom-Value!!!");
35+
});
36+
37+
// Since Vite will return static assets immediately without invoking the Worker at all
38+
// such requests will not have _headers applied.
39+
failsIf(!isBuild)("applies _headers to static assets", async ({}) => {
40+
const response = await fetch(`${viteTestUrl}/vite.svg`);
41+
expect(response.headers.get("X-Header")).toBe("Custom-Value!!!");
42+
});
43+
});
44+
45+
// All these tests will fail without experimental support turned on
46+
describe("_redirects", () => {
47+
test("applies _redirects to HTML responses", async ({}) => {
48+
const response = await fetch(`${viteTestUrl}/foo`, {
49+
redirect: "manual",
50+
});
51+
expect(response.status).toBe(302);
52+
expect(response.headers.get("Location")).toBe("/bar");
53+
});
54+
55+
// Since Vite will return static assets immediately without invoking the Worker at all
56+
// such requests will not have _redirects applied.
57+
failsIf(!isBuild)("applies _redirects to static assets", async ({}) => {
58+
const response = await fetch(`${viteTestUrl}/redirect.svg`, {
59+
redirect: "manual",
60+
});
61+
expect(response.status).toBe(302);
62+
expect(response.headers.get("Location")).toBe("/target.svg");
63+
});
64+
65+
// Since Vite will return static assets immediately without invoking the Worker at all
66+
// such requests will not have _redirects applied.
67+
failsIf(!isBuild)(
68+
"supports proxying static assets to rewritten contents with _redirects",
69+
async ({}) => {
70+
const response = await fetch(`${viteTestUrl}/rewrite.svg`);
71+
expect(response.status).toBe(200);
72+
expect(await response.text()).toContain("target.svg");
73+
}
74+
);
75+
});
76+
}
77+
);
78+
79+
describe("reloading the server", () => {
80+
test.skipIf(isBuild)(
81+
"reloads config when the _headers or _redirects files change",
82+
async ({ onTestFinished }) => {
83+
const headersPath = join(__dirname, "../../public/_headers");
84+
const originalHeaders = readFileSync(headersPath, "utf8");
85+
const redirectsPath = join(__dirname, "../../public/_redirects");
86+
const originalRedirects = readFileSync(redirectsPath, "utf8");
87+
onTestFinished(async () => {
88+
writeFileSync(headersPath, originalHeaders);
89+
writeFileSync(redirectsPath, originalRedirects);
90+
});
91+
92+
const headersBefore = await fetch(viteTestUrl);
93+
expect(headersBefore.headers.get("X-Header")).toBe("Custom-Value!!!");
94+
const redirectBefore = await fetch(`${viteTestUrl}/foo`, {
95+
redirect: "manual",
96+
});
97+
expect(redirectBefore.status).toBe(302);
98+
99+
// We make both these changes at the same time because there is something strange about the test setup
100+
// where fetches result in 500 errors, due to Miniflare stubs being reused after disposal.
101+
writeFileSync(headersPath, "");
102+
writeFileSync(redirectsPath, "");
103+
104+
// Wait for Vite to reload
105+
await vi.waitFor(async () => {
106+
const headersAfter = await fetch(viteTestUrl);
107+
expect(headersAfter.headers.get("X-Header")).not.toBe(
108+
"Custom-Value!!!"
109+
);
110+
const redirectAfter = await fetch(`${viteTestUrl}/foo`, {
111+
redirect: "manual",
112+
});
113+
expect(redirectAfter.status).not.toBe(302);
114+
});
115+
}
116+
);
117+
});
118+
119+
function failsIf(condition: boolean) {
120+
return condition ? test.fails : test;
121+
}
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
/*
2+
X-Header: Custom-Value!!!
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
/foo /bar
2+
/redirect.svg /target.svg
3+
/rewrite.svg /target.svg 200
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
hey there
Lines changed: 27 additions & 0 deletions
Loading
Lines changed: 27 additions & 0 deletions
Loading
Lines changed: 8 additions & 0 deletions
Loading
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import { cloudflare } from "@cloudflare/vite-plugin";
2+
import react from "@vitejs/plugin-react";
3+
import { defineConfig } from "vite";
4+
5+
export default defineConfig({
6+
plugins: [
7+
react(),
8+
cloudflare({
9+
inspectorPort: false,
10+
persistState: false,
11+
experimental: { headersAndRedirectsDevModeSupport: true },
12+
}),
13+
],
14+
});

0 commit comments

Comments
 (0)