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
11 changes: 11 additions & 0 deletions .changeset/friendly-connection-timeout.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
---
"wrangler": patch
---

Show helpful messages for errors outside of Wrangler's control. This prevents unnecessary Sentry reports.

Errors now handled with user-friendly messages:

- Connection timeouts to Cloudflare's API (`UND_ERR_CONNECT_TIMEOUT`) - typically due to slow networks or connectivity issues
- File system permission errors (`EPERM`, `EACCES`) - caused by insufficient permissions, locked files, or antivirus software
- DNS resolution failures (`ENOTFOUND`) - caused by network connectivity issues or DNS configuration problems
20 changes: 20 additions & 0 deletions .changeset/major-snails-post.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
---
"@cloudflare/vite-plugin": minor
---

Add support for child environments.

This is to support React Server Components via [@vitejs/plugin-rsc](https://github.com/vitejs/vite-plugin-react/tree/main/packages/plugin-rsc) and frameworks that build on top of it. A `childEnvironments` option is now added to the plugin config to enable using multiple environments within a single Worker. The parent environment can import modules from a child environment in order to access a separate module graph. For a typical RSC use case, the plugin might be configured as in the following example:

```ts
export default defineConfig({
plugins: [
cloudflare({
viteEnvironment: {
name: "rsc",
childEnvironments: ["ssr"],
},
}),
],
});
```
19 changes: 19 additions & 0 deletions .changeset/stupid-pants-push.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
---
"@cloudflare/vite-plugin": patch
"@cloudflare/vitest-pool-workers": patch
"@cloudflare/kv-asset-handler": patch
"miniflare": patch
---

Bundle more third-party dependencies to reduce supply chain risk

Previously, several small utility packages were listed as runtime dependencies and
installed separately. These are now bundled directly into the published packages,
reducing the number of external dependencies users need to trust.

Bundled dependencies:

- **miniflare**: `acorn`, `acorn-walk`, `exit-hook`, `glob-to-regexp`, `stoppable`
- **kv-asset-handler**: `mime`
- **vite-plugin-cloudflare**: `@remix-run/node-fetch-server`, `defu`, `get-port`, `picocolors`, `tinyglobby`
- **vitest-pool-workers**: `birpc`, `devalue`, `get-port`, `semver`
43 changes: 43 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -395,6 +395,49 @@ CLOUDFLARE_ACCOUNT_ID="<Account ID for the token you just created>" CLOUDFLARE_A
> [!NOTE]
> Workers and other resources created in the E2E tests might not always be cleaned up. Internal users with access to the "DevProd Testing" account can rely on an automated job to clean up the Workers and other resources, but if you use another account, please be aware you may want to manually delete the Workers and other resources yourself.
## Managing Package Dependencies

Packages in this monorepo should bundle their dependencies into the distributable code rather than leaving them as runtime `dependencies` that get installed by downstream users. This prevents dependency chain poisoning where a transitive dependency could introduce unexpected or malicious code.

### The Rule

- **Bundle dependencies**: Most dependencies should be listed in `devDependencies` and bundled into the package output by esbuild/tsup/etc.
- **External dependencies**: Only dependencies that _cannot_ be bundled should be listed in `dependencies`. These must be explicitly declared with documentation explaining why.

### Why This Matters

When users install one of our packages (e.g., `wrangler`), npm/pnpm will also install everything listed in `dependencies`. If one of those dependencies has unpinned transitive dependencies, a malicious actor could publish a compromised version that gets pulled into user installations. By bundling our dependencies, we control exactly what code ships.

### Adding a New External Dependency

If you need to add a dependency that cannot be bundled (native binaries, WASM modules, packages that must be resolved at runtime, etc.):

1. **Add to `dependencies`** in `package.json` with a pinned version
2. **Add to `EXTERNAL_DEPENDENCIES`** in `scripts/deps.ts` with a comment explaining why it can't be bundled
3. **Run `pnpm check:package-deps`** to verify the allowlist is correct

Example `scripts/deps.ts`:

```typescript
export const EXTERNAL_DEPENDENCIES = [
// Native binary - cannot be bundled
"workerd",

// WASM module that blows up when bundled
"blake3-wasm",

// Must be resolved at runtime when bundling user's worker code
"esbuild",
];
```

### Valid Reasons for External Dependencies

- **Native binaries**: Packages like `workerd` or `sharp` contain platform-specific binaries
- **WASM modules**: Some WASM packages don't bundle correctly
- **Runtime resolution**: Packages like `esbuild` or `unenv` that need to be resolved when bundling user code
- **Peer dependencies**: Packages the user is expected to provide (e.g., `react`, `vite`)

## Changesets

Every non-trivial change to the project - those that should appear in the changelog - must be captured in a "changeset".
Expand Down
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,12 @@
"author": "wrangler@cloudflare.com",
"scripts": {
"build": "dotenv -- turbo build",
"check": "pnpm check:fixtures && pnpm check:private-packages && pnpm check:deployments && node lint-turbo.mjs && dotenv -- turbo check:lint check:type check:format type:tests",
"check": "pnpm check:fixtures && pnpm check:private-packages && pnpm check:package-deps && pnpm check:deployments && node lint-turbo.mjs && dotenv -- turbo check:lint check:type check:format type:tests",
"check:deployments": "node -r esbuild-register tools/deployments/deploy-non-npm-packages.ts check",
"check:fixtures": "node -r esbuild-register tools/deployments/validate-fixtures.ts",
"check:format": "prettier . --check --ignore-unknown",
"check:lint": "dotenv -- turbo check:lint",
"check:package-deps": "node -r esbuild-register tools/deployments/validate-package-dependencies.ts",
"check:private-packages": "node -r esbuild-register tools/deployments/validate-private-packages.ts",
"check:type": "dotenv -- turbo check:type type:tests",
"dev": "dotenv -- turbo dev",
Expand Down
4 changes: 1 addition & 3 deletions packages/kv-asset-handler/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,16 +36,14 @@
"test": "vitest",
"test:ci": "vitest run"
},
"dependencies": {
"mime": "^3.0.0"
},
"devDependencies": {
"@cloudflare/eslint-config-shared": "workspace:*",
"@cloudflare/vitest-pool-workers": "catalog:default",
"@cloudflare/workers-types": "catalog:default",
"@types/mime": "^3.0.4",
"@types/node": "catalog:default",
"eslint": "catalog:default",
"mime": "^3.0.0",
"tsup": "8.3.0",
"vitest": "~2.1.0"
},
Expand Down
10 changes: 5 additions & 5 deletions packages/miniflare/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -44,12 +44,7 @@
},
"dependencies": {
"@cspotcode/source-map-support": "0.8.1",
"acorn": "8.14.0",
"acorn-walk": "8.3.2",
"exit-hook": "2.2.1",
"glob-to-regexp": "0.4.1",
"sharp": "^0.34.5",
"stoppable": "1.1.0",
"undici": "catalog:default",
"workerd": "1.20260114.0",
"ws": "catalog:default",
Expand All @@ -75,6 +70,8 @@
"@types/stoppable": "^1.1.1",
"@types/which": "^2.0.1",
"@types/ws": "^8.5.7",
"acorn": "8.14.0",
"acorn-walk": "8.3.2",
"capnp-es": "^0.0.11",
"capnweb": "^0.1.0",
"chokidar": "^4.0.1",
Expand All @@ -86,8 +83,10 @@
"eslint-config-prettier": "^9.0.0",
"eslint-plugin-es": "^4.1.0",
"eslint-plugin-prettier": "^5.0.1",
"exit-hook": "2.2.1",
"expect-type": "^0.15.0",
"get-port": "^7.1.0",
"glob-to-regexp": "0.4.1",
"heap-js": "^2.5.0",
"http-cache-semantics": "^4.1.0",
"kleur": "^4.1.5",
Expand All @@ -96,6 +95,7 @@
"pretty-bytes": "^6.0.0",
"rimraf": "catalog:default",
"source-map": "^0.6.1",
"stoppable": "1.1.0",
"ts-dedent": "^2.2.0",
"typescript": "catalog:default",
"vitest": "catalog:default",
Expand Down
33 changes: 33 additions & 0 deletions packages/miniflare/scripts/deps.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/**
* Dependencies that _are not_ bundled along with miniflare.
*
* These must be explicitly documented with a reason why they cannot be bundled.
* This list is validated by `tools/deployments/validate-package-dependencies.ts`.
*/
export const EXTERNAL_DEPENDENCIES = [
// Must be external - uses require.resolve() and require.cache manipulation
// to load fresh instances of the module at runtime (see sourcemap.ts)
"@cspotcode/source-map-support",

// Native binary with platform-specific builds - cannot be bundled
"sharp",

// Large HTTP client with optional native dependencies; commonly shared
// with other packages to avoid version conflicts and duplication
"undici",

// Native binary - Cloudflare's JavaScript runtime cannot be bundled
"workerd",

// Has optional native bindings (bufferutil, utf-8-validate) for performance;
// commonly shared with other packages to avoid duplication
"ws",

// Must be external - dynamically required at runtime via require("youch")
// for lazy loading of pretty error pages
"youch",

// Large validation library; commonly shared as a dependency
// to avoid version conflicts and bundle size duplication
"zod",
];
1 change: 1 addition & 0 deletions packages/vite-plugin-cloudflare/eslint.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ export default defineConfig([
globalIgnores([
"**/dist",
"**/e2e",
"scripts/**",
"tsdown.config.ts",
"vitest.config.ts",
"src/__tests__/fixtures/**",
Expand Down
10 changes: 5 additions & 5 deletions packages/vite-plugin-cloudflare/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -44,12 +44,7 @@
},
"dependencies": {
"@cloudflare/unenv-preset": "workspace:*",
"@remix-run/node-fetch-server": "^0.8.0",
"defu": "^6.1.4",
"get-port": "^7.1.0",
"miniflare": "workspace:*",
"picocolors": "^1.1.1",
"tinyglobby": "^0.2.12",
"unenv": "2.0.0-rc.24",
"wrangler": "workspace:*",
"ws": "catalog:default"
Expand All @@ -61,12 +56,17 @@
"@cloudflare/workers-tsconfig": "workspace:*",
"@cloudflare/workers-types": "catalog:default",
"@cloudflare/workers-utils": "workspace:*",
"@remix-run/node-fetch-server": "^0.8.0",
"@types/node": "catalog:vite-plugin",
"@types/semver": "^7.5.1",
"@types/ws": "^8.5.13",
"defu": "^6.1.4",
"get-port": "^7.1.0",
"magic-string": "^0.30.12",
"mlly": "^1.7.4",
"picocolors": "^1.1.1",
"semver": "^7.7.1",
"tinyglobby": "^0.2.12",
"tree-kill": "^1.2.2",
"tsdown": "0.16.3",
"typescript": "catalog:default",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { expect, test } from "vitest";
import { getTextResponse, isBuild } from "../../__test-utils__";

test.runIf(!isBuild)("can import module from child environment", async () => {
const response = await getTextResponse();
expect(response).toBe("Hello from the child environment");
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{
"name": "@playground/child-environment",
"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,6 @@
// @ts-expect-error - no types
import { getEnvironmentName } from "virtual:environment-name";

export function getMessage() {
return `Hello from the ${getEnvironmentName()} environment`;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
declare global {
// In real world usage, this is accessed by `@vitejs/plugin-rsc`
function __VITE_ENVIRONMENT_RUNNER_IMPORT__(
environmentName: string,
id: string
): Promise<unknown>;
}

export default {
async fetch() {
const childEnvironmentModule = (await __VITE_ENVIRONMENT_RUNNER_IMPORT__(
"child",
"./src/child-environment-module"
)) as { getMessage: () => string };

return new Response(childEnvironmentModule.getMessage());
},
} 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", "__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,28 @@
import { cloudflare } from "@cloudflare/vite-plugin";
import { defineConfig } from "vite";

export default defineConfig({
plugins: [
cloudflare({
inspectorPort: false,
persistState: false,
viteEnvironment: {
name: "parent",
childEnvironments: ["child"],
},
}),
{
name: "virtual-module-plugin",
resolveId(source) {
if (source === "virtual:environment-name") {
return "\0virtual:environment-name";
}
},
load(id) {
if (id === "\0virtual:environment-name") {
return `export function getEnvironmentName() { return ${JSON.stringify(this.environment.name)} }`;
}
},
},
],
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"$schema": "./node_modules/wrangler/config-schema.json",
"name": "worker",
"main": "./src/index.ts",
}
15 changes: 15 additions & 0 deletions packages/vite-plugin-cloudflare/scripts/deps.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
/**
* Dependencies that _are not_ bundled along with @cloudflare/vite-plugin.
*
* These must be explicitly documented with a reason why they cannot be bundled.
* This list is validated by `tools/deployments/validate-package-dependencies.ts`.
*/
export const EXTERNAL_DEPENDENCIES = [
// Must be external - resolved at runtime when bundling user's worker code
// to provide Node.js compatibility polyfills
"unenv",

// Has optional native bindings (bufferutil, utf-8-validate) for performance;
// commonly shared with other packages to avoid duplication
"ws",
];
Loading
Loading