Skip to content
Merged
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
23 changes: 23 additions & 0 deletions .changeset/add-local-uploads-command.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
---
"wrangler": minor
---

Add `wrangler r2 bucket local-uploads` command to manage local uploads for R2 buckets

When enabled, object data is written to the nearest region first, then asynchronously replicated to the bucket's primary region.

Docs: https://developers.cloudflare.com/r2/buckets/local-uploads

```bash
# Get local uploads status
wrangler r2 bucket local-uploads get my-bucket

# Enable local uploads (will prompt for confirmation)
wrangler r2 bucket local-uploads enable my-bucket

# Enable without confirmation prompt
wrangler r2 bucket local-uploads enable my-bucket --force

# Disable local uploads
wrangler r2 bucket local-uploads disable my-bucket
```
5 changes: 5 additions & 0 deletions .changeset/cf-worker-header.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"miniflare": minor
---

Add `CF-Worker` header to outgoing fetch requests in local development to match production behavior. A new optional `zone` option allows specifying the zone value for the header. When not specified, the header defaults to `${worker-name}.example.com`.
7 changes: 7 additions & 0 deletions .changeset/metal-planes-hammer.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
"@cloudflare/vite-plugin": minor
---

Add `experimental.prerenderWorker` option to the plugin config.

This enables configuring a dedicated Worker for prerendering at build time. This is an experimental feature and may change or be removed at any time.
4 changes: 3 additions & 1 deletion .github/workflows/test-and-check.yml
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,9 @@ jobs:
# We are running the package tests first be able to get early feedback on changes.
# There is no point in running the fixtures if a package is broken.
if: steps.changes.outputs.everything_but_markdown == 'true'
run: pnpm run test:ci --log-order=stream --concurrency=1 --filter="./packages/*"
# We skip @cloudflare/vitest-pool-workers tests in CI on Windows because they're very flaky. We still run the vitest-pool-workers-examples fixture, which is a comprehensive set of example tests and gives us a lot of confidence.
# The @cloudflare/vitest-pool-workers tests skipped are things like watch mode, which constantly times out probably due to the github runners in use.
run: pnpm run test:ci --log-order=stream --concurrency=1 --filter="./packages/*" ${{ matrix.os == 'windows-latest' && '--filter="!./packages/vitest-pool-workers"' || '' }}
env:
NODE_OPTIONS: "--max_old_space_size=8192"
WRANGLER_LOG_PATH: ${{ runner.temp }}/wrangler-debug-logs/
Expand Down
13 changes: 13 additions & 0 deletions packages/miniflare/src/plugins/core/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,10 @@ const CoreOptionsSchemaInput = z.intersection(
// Strip the CF-Connecting-IP header from outbound fetches
stripCfConnectingIp: z.boolean().default(true),

// Zone to use for the CF-Worker header in outbound fetches
// If not specified, defaults to `${worker-name}.example.com`
zone: z.string().optional(),

/** Configuration used to connect to the container engine */
containerEngine: z
.union([
Expand Down Expand Up @@ -924,6 +928,9 @@ export const CORE_PLUGIN: Plugin<
}

if (options.stripCfConnectingIp) {
// Use the zone option if provided, otherwise default to `${worker-name}.example.com`
const workerName = options.name ?? "worker";
const cfWorkerValue = options.zone ?? `${workerName}.example.com`;
services.push({
name: getStripCfConnectingIpName(workerIndex),
worker: {
Expand All @@ -935,6 +942,12 @@ export const CORE_PLUGIN: Plugin<
],
compatibilityDate: "2025-01-01",
compatibilityFlags: ["connect_pass_through", "experimental"],
bindings: [
{
name: "CF_WORKER_ZONE",
text: cfWorkerValue,
},
],
globalOutbound: getGlobalOutbound(workerIndex, options),
},
});
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
interface Env {
CF_WORKER_ZONE: string;
}

export default {
fetch(request) {
fetch(request, env: Env) {
const headers = new Headers(request.headers);
headers.delete("CF-Connecting-IP");
headers.set("CF-Worker", env.CF_WORKER_ZONE);
return fetch(request, { headers });
},
} satisfies ExportedHandler;
} satisfies ExportedHandler<Env>;
67 changes: 67 additions & 0 deletions packages/miniflare/test/index.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3142,6 +3142,73 @@ test("Miniflare: does not strip CF-Connecting-IP when configured", async () => {
expect(await landingPage.text()).toEqual("fake-value");
});

// Test for https://github.com/cloudflare/workers-sdk/issues/4367
// The CF-Worker header should be added to outbound fetch requests to match production behavior
test("Miniflare: adds CF-Worker header to outbound requests with zone option", async () => {
const server = new Miniflare({
script:
"export default { fetch(request) { return new Response(request.headers.get(`CF-Worker`)) } }",
modules: true,
});
const serverUrl = await server.ready;

const client = new Miniflare({
name: "my-worker",
zone: "my-zone.example.com",
script: `export default { fetch(request) { return fetch('${serverUrl.href}') } }`,
modules: true,
});
useDispose(client);
useDispose(server);

const response = await client.dispatchFetch("http://example.com/");
// The CF-Worker header should be set to the zone value when provided
expect(await response.text()).toEqual("my-zone.example.com");
});

test("Miniflare: CF-Worker header defaults to worker-name.example.com when zone not set", async () => {
const server = new Miniflare({
script:
"export default { fetch(request) { return new Response(request.headers.get(`CF-Worker`)) } }",
modules: true,
});
const serverUrl = await server.ready;

const client = new Miniflare({
name: "my-worker",
// No zone set, should default to `${worker-name}.example.com`
script: `export default { fetch(request) { return fetch('${serverUrl.href}') } }`,
modules: true,
});
useDispose(client);
useDispose(server);

const response = await client.dispatchFetch("http://example.com/");
// The CF-Worker header should default to `${worker-name}.example.com` when no zone is specified
expect(await response.text()).toEqual("my-worker.example.com");
});

test("Miniflare: CF-Worker header defaults to worker.example.com when neither zone nor name set", async () => {
const server = new Miniflare({
script:
"export default { fetch(request) { return new Response(request.headers.get(`CF-Worker`)) } }",
modules: true,
});
const serverUrl = await server.ready;

const client = new Miniflare({
// No name or zone set, should default to "worker.example.com"
script: `export default { fetch(request) { return fetch('${serverUrl.href}') } }`,
modules: true,
});
useDispose(client);
useDispose(server);

const response = await client.dispatchFetch("http://example.com/");
// The CF-Worker header should default to "worker.example.com" when neither zone nor name is specified
expect(await response.text()).toEqual("worker.example.com");
});

test("Miniflare: can use module fallback service", async () => {
const modulesRoot = "/";
const modules: Record<string, Omit<Worker_Module, "name">> = {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { expect, test } from "vitest";
import {
getTextResponse,
isBuild,
page,
satisfiesViteVersion,
viteTestUrl,
} from "../../__test-utils__";

test("returns the server rendered route at /", async () => {
expect(await getTextResponse()).toEqual("Hello world");
});

test.runIf(isBuild && satisfiesViteVersion("7.0.0"))(
"returns the prerendered route at /prerendered after the build",
async () => {
await page.goto(`${viteTestUrl}/prerendered`);
const content = await page.textContent("h1");
expect(content).toBe("Pre-rendered HTML");
}
);
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{
"name": "@playground/prerendering",
"private": true,
"type": "module",
"scripts": {
"build": "vite build",
"check:type": "tsc --build",
"dev": "vite dev",
"preview": "vite preview"
},
"devDependencies": {
"@cloudflare/vite-plugin": "workspace:*",
"@cloudflare/workers-tsconfig": "workspace:*",
"@cloudflare/workers-types": "catalog:default",
"typescript": "catalog:default",
"vite": "catalog:vite-plugin",
"wrangler": "workspace:*"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export default {
async fetch() {
return new Response("Hello world");
},
} satisfies ExportedHandler;
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
export default {
async fetch(request) {
const url = new URL(request.url);

if (url.pathname === "/prerendered") {
return new Response(
`\
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Pre-rendering</title>
</head>
<body>
<h1>Pre-rendered HTML</h1>
</body>
</html>`,
{
headers: { "Content-Type": "text/html" },
}
);
}

return new Response(null, { status: 404 });
},
} satisfies ExportedHandler;
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"files": [],
"references": [
{ "path": "./tsconfig.node.json" },
{ "path": "./tsconfig.worker.json" }
]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"extends": ["@cloudflare/workers-tsconfig/base.json"],
"include": ["vite.config.ts", "vite.config.*.ts", "__tests__"]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"extends": ["@cloudflare/workers-tsconfig/worker.json"],
"include": ["src"]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"$schema": "http://turbo.build/schema.json",
"extends": ["//"],
"tasks": {
"build": {
"outputs": ["dist/**"]
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import * as fsp from "node:fs/promises";
import * as path from "node:path";
import { fileURLToPath } from "node:url";
import { cloudflare } from "@cloudflare/vite-plugin";
import { defineConfig, preview } from "vite";

export default defineConfig({
plugins: [
cloudflare({
inspectorPort: false,
persistState: false,
experimental: {
prerenderWorker: {
config(_, { entryWorkerConfig }) {
return {
...entryWorkerConfig,
name: "prerender",
main: "./src/prerender.ts",
};
},
},
},
}),
{
name: "prerender-plugin",
enforce: "post",
buildApp: {
order: "post",
async handler(builder) {
const previewServer = await preview({
// Needed because the tests are run from a different directory
root: path.dirname(fileURLToPath(import.meta.url)),
logLevel: "silent",
preview: {
port: 0,
},
});

const baseUrl = previewServer.resolvedUrls?.local[0];
const clientOutputDirectory =
builder.environments.client?.config.build.outDir;

if (baseUrl && clientOutputDirectory) {
const response = await fetch(new URL("/prerendered", baseUrl));
const html = await response.text();

await fsp.writeFile(
path.resolve(
builder.config.root,
clientOutputDirectory,
"prerendered.html"
),
html
);

console.log("Pre-rendered /prerendered");
}

await previewServer.close();
},
},
},
],
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"main": "./src/index.ts",
}
5 changes: 5 additions & 0 deletions packages/vite-plugin-cloudflare/playground/vitest-setup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -309,6 +309,11 @@ export async function startDefaultServe(): Promise<
const builder = await createBuilder(buildConfig);
await builder.buildApp();

// This environment variable is used to indicate to the preview server that it is being run during a build
// We need to delete it here as, during testing, preview also runs in the same process after the build completes
// eslint-disable-next-line turbo/no-undeclared-env-vars
delete process.env.CLOUDFLARE_VITE_BUILD;

const previewConfig = await loadConfig({
command: "serve",
mode: "development",
Expand Down
Loading
Loading