Skip to content
Draft
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: 7 additions & 0 deletions .changeset/vite-plugin-api-plugin-config.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
"@cloudflare/vite-plugin": patch
---

Expose the options passed to `cloudflare()` as `api.pluginConfig` on the plugin

Tools that load a project's Vite config can now read the Cloudflare plugin's options from the `vite-plugin-cloudflare` plugin's `api.pluginConfig`, without re-parsing the config file. This is used to run the Vite plugin's playground tests against `@cloudflare/rsbuild-plugin`.
9 changes: 9 additions & 0 deletions .github/workflows/e2e-vite.yml
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,15 @@ jobs:
NODE_OPTIONS: "--max_old_space_size=8192"
CI_OS: ${{ matrix.os }}

- name: Run Vite plugin E2E tests against the Rsbuild plugin
if: steps.changes.outputs.everything_but_markdown == 'true' && matrix.os == 'ubuntu-latest'
run: pnpm dotenv -- pnpm turbo test:e2e:rsbuild -F @cloudflare/vite-plugin --log-order=stream
timeout-minutes: 20
env:
NODE_DEBUG: "vite-plugin:test"
NODE_OPTIONS: "--max_old_space_size=8192"
CI_OS: ${{ matrix.os }}

- name: Upload turbo logs
if: always()
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
Expand Down
56 changes: 56 additions & 0 deletions .github/workflows/vite-plugin-playgrounds.yml
Original file line number Diff line number Diff line change
Expand Up @@ -131,3 +131,59 @@ jobs:
with:
name: turbo-runs-${{ matrix.os }}-${{ matrix.vite }}
path: .turbo/runs

# The playground suite against `@cloudflare/rsbuild-plugin`, whose harness
# translates each playground's Vite config. These steps run without `--only`,
# so turbo builds the plugins and keys the cache on their dependency graph.
test-rsbuild:
timeout-minutes: 45
name: Vite Plugin Playground (ubuntu-latest, rsbuild)
runs-on: ubuntu-latest
env:
CI_OS: ubuntu-latest
steps:
- name: Checkout Repo
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
with:
persist-credentials: false
- name: Filter changed paths
uses: dorny/paths-filter@fbd0ab8f3e69293af611ebaee6363fc25e6d187d # v4.0.1
id: changes
with:
filters: |
everything_but_markdown:
- '!**/*.md'
- name: Install Dependencies
if: steps.changes.outputs.everything_but_markdown == 'true'
uses: ./.github/actions/install-dependencies
with:
turbo-api: ${{ secrets.TURBO_API }}
turbo-team: ${{ secrets.TURBO_TEAM }}
turbo-token: ${{ secrets.TURBO_TOKEN }}
turbo-signature: ${{ secrets.TURBO_REMOTE_CACHE_SIGNATURE_KEY }}
- name: Install playwright
if: steps.changes.outputs.everything_but_markdown == 'true'
run: |
pnpm --filter @vite-plugin-cloudflare/playground playwright:install
- name: Run dev playground tests
if: steps.changes.outputs.everything_but_markdown == 'true'
run: |
pnpm dotenv -- pnpm turbo test:ci:rsbuild:serve --filter @vite-plugin-cloudflare/playground
env:
NODE_OPTIONS: "--max_old_space_size=8192"
WRANGLER_LOG_PATH: ${{ runner.temp }}/wrangler-debug-logs/
TEST_REPORT_PATH: ${{ runner.temp }}/test-report/index.html
- name: Run build/preview playground tests
if: steps.changes.outputs.everything_but_markdown == 'true'
run: |
pnpm dotenv -- pnpm turbo test:ci:rsbuild:build --filter @vite-plugin-cloudflare/playground
env:
NODE_OPTIONS: "--max_old_space_size=8192"
WRANGLER_LOG_PATH: ${{ runner.temp }}/wrangler-debug-logs/
TEST_REPORT_PATH: ${{ runner.temp }}/test-report/index.html
- name: Upload turbo logs
if: always()
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: turbo-runs-ubuntu-latest-rsbuild
path: .turbo/runs
1 change: 1 addition & 0 deletions packages/rsbuild-plugin-cloudflare/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
.tmp
46 changes: 46 additions & 0 deletions packages/rsbuild-plugin-cloudflare/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
# `@cloudflare/rsbuild-plugin`

> [!WARNING]
> This is an experimental prototype. It is not published.

Runs your Worker in workerd during `rsbuild dev` and `rsbuild preview`, and writes Cloudflare Build Output from `rsbuild build`.

```ts
// rsbuild.config.ts
import { pluginCloudflare } from "@cloudflare/rsbuild-plugin";
import { defineConfig } from "@rsbuild/core";

export default defineConfig({
plugins: [pluginCloudflare()],
});
```

The plugin takes the same options as `cloudflare()` from `@cloudflare/vite-plugin`, except `tunnel` and `viteEnvironment`, plus `envDir` and `envMode` for loading `.env` files. It shares its configuration, bindings, assets, Node.js compatibility and Build Output handling with the Vite plugin through `@cloudflare/workers-dev-shared`, and keeps only the Rsbuild glue itself.

Each Worker is bundled in its own Rsbuild environment (`cloudflare` for the entry Worker), and the user's environments are the client.

## Not supported

- Hot module replacement inside the Worker. Changes to the Worker rebuild it and update workerd.
- Containers.
- Prerendering.
- The tunnel, CLI shortcuts and `cf` CLI integration.

## Tests

Besides this package's unit tests, the Vite plugin's playground suite runs against this plugin:

```sh
pnpm --filter @vite-plugin-cloudflare/playground test:ci:rsbuild:serve
pnpm --filter @vite-plugin-cloudflare/playground test:ci:rsbuild:build
```

See `packages/vite-plugin-cloudflare/playground/vitest.config.e2e.rsbuild.ts` for the playgrounds and tests that are skipped, and why.

So does the Vite plugin's e2e suite, which installs fixtures from a mock npm registry:

```sh
pnpm turbo test:e2e:rsbuild -F @cloudflare/vite-plugin
```

See `packages/vite-plugin-cloudflare/e2e/vitest.config.rsbuild.ts` for the tests that are skipped.
70 changes: 70 additions & 0 deletions packages/rsbuild-plugin-cloudflare/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
{
"name": "@cloudflare/rsbuild-plugin",
"version": "0.0.0",
"private": true,
"description": "Cloudflare plugin for Rsbuild (experimental prototype)",
"keywords": [
"cloudflare",
"cloudflare-workers",
"rsbuild",
"rsbuild-plugin",
"rspack",
"workers"
],
"homepage": "https://github.com/cloudflare/workers-sdk/tree/main/packages/rsbuild-plugin-cloudflare#readme",
"bugs": {
"url": "https://github.com/cloudflare/workers-sdk/issues"
},
"license": "MIT",
"repository": {
"type": "git",
"url": "https://github.com/cloudflare/workers-sdk.git",
"directory": "packages/rsbuild-plugin-cloudflare"
},
"files": [
"dist"
],
"type": "module",
"sideEffects": false,
"main": "./dist/index.mjs",
"types": "./dist/index.d.mts",
"exports": {
".": {
"types": "./dist/index.d.mts",
"import": "./dist/index.mjs"
}
},
"scripts": {
"build": "tsdown",
"check:type": "tsc",
"dev": "tsdown --watch",
"test:ci": "vitest run",
"test:watch": "vitest"
},
"dependencies": {
"@cloudflare/config": "workspace:*",
"@cloudflare/runtime-types": "workspace:*",
"@cloudflare/unenv-preset": "workspace:*",
"miniflare": "workspace:*",
"unenv": "2.0.0-rc.24"
},
"devDependencies": {
"@cloudflare/build-output-utils": "workspace:*",
"@cloudflare/workers-dev-shared": "workspace:*",
"@cloudflare/workers-tsconfig": "workspace:*",
"@cloudflare/workers-types": "catalog:default",
"@cloudflare/workers-utils": "workspace:*",
"@remix-run/node-fetch-server": "^0.8.0",
"@rsbuild/core": "^2.2.13",
"@types/node": "catalog:default",
"cf": "catalog:default",
"get-port": "^7.1.0",
"mlly": "^1.7.4",
"tsdown": "0.16.3",
"typescript": "catalog:default",
"vitest": "catalog:default"
},
"peerDependencies": {
"@rsbuild/core": "^2.0.0"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
.cloudflare
dist
.wrangler
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
interface Env {
ASSETS: Fetcher;
}

export default {
async fetch(request, env) {
const { pathname } = new URL(request.url);

if (pathname === "/api/hello") {
return Response.json({ name: "Cloudflare" });
}

const response = await env.ASSETS.fetch(
new URL("/index.html", request.url)
);
return new Response(response.body, {
headers: { "content-type": "text/html", "x-from-worker": "true" },
});
},
} satisfies ExportedHandler<Env>;
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { bindings, defineConfig } from "cf/config";
import * as entrypoint from "./api/index.ts" with { type: "cf-worker" };

export default defineConfig({
worker: {
name: "rsbuild-spa-with-api",
entrypoint,
compatibilityDate: "2025-06-01",
assets: { notFoundHandling: "single-page-application" },
env: { ASSETS: bindings.assets() },
},
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { pluginCloudflare } from "@cloudflare/rsbuild-plugin";
import { defineConfig } from "@rsbuild/core";

export default defineConfig({
source: { entry: { index: "./src/index.ts" } },
plugins: [pluginCloudflare({ types: { includeRuntime: false } })],
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
void fetch("/api/hello")
.then((response) => response.json() as Promise<{ name: string }>)
.then(({ name }) => {
document.body.textContent = `Hello, ${name}!`;
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"extends": "@cloudflare/workers-tsconfig/worker.json",
"compilerOptions": {
"types": ["@cloudflare/workers-types"]
},
"include": ["api", "src", "cloudflare.config.ts"]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
SECRET=from-dev-vars
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
.cloudflare
dist
.wrangler
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { bindings, defineConfig } from "cf/config";
import * as entrypoint from "./src/index.ts" with { type: "cf-worker" };

export default defineConfig({
worker: {
name: "rsbuild-worker",
entrypoint,
compatibilityDate: "2025-06-01",
compatibilityFlags: ["nodejs_compat"],
env: {
KV: bindings.kv(),
GREETING: bindings.text("Hello"),
SECRET: bindings.secret(),
},
},
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { pluginCloudflare } from "@cloudflare/rsbuild-plugin";
import { defineConfig } from "@rsbuild/core";

export default defineConfig({
plugins: [pluginCloudflare({ types: { includeRuntime: false } })],
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { Buffer } from "node:buffer";
import { env } from "cloudflare:workers";

interface Env {
KV: KVNamespace;
GREETING: string;
SECRET: string;
}

export default {
async fetch(request, workerEnv) {
const { pathname } = new URL(request.url);

switch (pathname) {
case "/kv": {
await workerEnv.KV.put("key", "value");
return new Response(await workerEnv.KV.get("key"));
}
case "/node": {
return new Response(Buffer.from("aGVsbG8=", "base64").toString());
}
case "/process": {
return new Response(typeof process.env);
}
case "/dynamic-import": {
const { message } = await import("./lazy");
return new Response(message);
}
case "/secret": {
return new Response(workerEnv.SECRET);
}
}

return new Response(`${(env as Env).GREETING} from rsbuild + workerd`);
},
} satisfies ExportedHandler<Env>;
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export const message = "lazy chunk";
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"extends": "@cloudflare/workers-tsconfig/worker.json",
"compilerOptions": {
"types": ["@cloudflare/workers-types", "@types/node"]
},
"include": ["src", "cloudflare.config.ts"]
}
Loading
Loading