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
29 changes: 29 additions & 0 deletions .changeset/gold-frogs-rest.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
---
"@cloudflare/vitest-pool-workers": patch
---

Added [Vite dependency pre-bundling](https://vite.dev/guide/dep-pre-bundling) support. If you encounter module resolution issues—such as: `Error: Cannot use require() to import an ES Module` or `Error: No such module`—you can now bundle these dependencies using the [deps.optimizer](https://vitest.dev/config/#deps-optimizer) option:

```tsx
import { defineWorkersConfig } from "@cloudflare/vitest-pool-workers/config";

export default defineWorkersConfig({
test: {
deps: {
optimizer: {
ssr: {
enabled: true,
include: ["your-package-name"],
},
},
},
poolOptions: {
workers: {
// ...
},
},
},
});
```

Fixed #6591, #6581, #6405.

This file was deleted.

This file was deleted.

This file was deleted.

This file was deleted.

This file was deleted.

This file was deleted.

This file was deleted.

34 changes: 34 additions & 0 deletions fixtures/vitest-pool-workers-examples/module-resolution/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# ⚡ module-resolution

This fixture demonstrates that the Vitest integration correctly resolves modules, including:

- A CommonJS package that requires a directory rather than a specific file.
- A package without a main entrypoint or with browser field mapping, handled via [Dependency Pre-Bundling](#dependency-pre-bundling).

## Dependency Pre-Bundling

[Dependency Pre-Bundling](https://vite.dev/guide/dep-pre-bundling) is a Vite feature that converts dependencies shipped as CommonJS or UMD into ESM. If you encounter module resolution issues—such as: `Error: Cannot use require() to import an ES Module` or `Error: No such module`—you can pre-bundle these dependencies using the [deps.optimizer](https://vitest.dev/config/#deps-optimizer) option:

```ts
import { defineWorkersConfig } from "@cloudflare/vitest-pool-workers/config";

export default defineWorkersConfig({
test: {
deps: {
optimizer: {
ssr: {
enabled: true,
include: ["your-package-name"],
},
},
},
poolOptions: {
workers: {
// ...
},
},
},
});
```

See our [vitest config](./vitest.config.ts) for an example of how we pre-bundled `discord-api-types/v10` and `@microlabs/otel-cf-workers`.
Original file line number Diff line number Diff line change
@@ -1,4 +1,10 @@
import { Toucan } from "toucan-js";
// Testing dependency without a main entrypoint
// @see https://github.com/cloudflare/workers-sdk/issues/6591
import "discord-api-types/v10";
// Testing dependency with browser field mapping
// @see https://github.com/cloudflare/workers-sdk/issues/6581
import "@microlabs/otel-cf-workers";

export default {
async fetch(): Promise<Response> {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { instrument } from "@microlabs/otel-cf-workers";
import { Utils } from "discord-api-types/v10";
import dep from "ext-dep";
import { assert, describe, test } from "vitest";

describe("test", () => {
test("resolves commonjs directory dependencies correctly", async () => {
assert.equal(dep, 123);
});

// This requires the `deps.optimizer` option to be set in the vitest config
test("resolves dependency without a default entrypoint", async () => {
assert.isFunction(Utils.isDMInteraction);
});

// This requires the `deps.optimizer` option to be set in the vitest config
test("resolves dependency with mapping on the browser field", async () => {
assert.isFunction(instrument);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,14 @@ import { defineWorkersProject } from "@cloudflare/vitest-pool-workers/config";

export default defineWorkersProject({
test: {
deps: {
optimizer: {
ssr: {
enabled: true,
include: ["discord-api-types/v10", "@microlabs/otel-cf-workers"],
},
},
},
poolOptions: {
workers: {
wrangler: { configPath: "./wrangler.toml" },
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
name = "external-package-resolution"
name = "module-resolution"
main = "src/index.ts"
compatibility_date = "2024-04-05"
7 changes: 5 additions & 2 deletions fixtures/vitest-pool-workers-examples/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,16 @@
"devDependencies": {
"@cloudflare/vitest-pool-workers": "workspace:*",
"@cloudflare/workers-types": "^4.20250204.0",
"@microlabs/otel-cf-workers": "1.0.0-rc.45",
"@types/node": "catalog:default",
"ext-dep": "file:./internal-module-resolution/vendor/ext-dep",
"discord-api-types": "0.37.98",
"ext-dep": "file:./module-resolution/vendor/ext-dep",
"jose": "^5.2.2",
"miniflare": "workspace:*",
"run-script-os": "^1.1.6",
"toucan-js": "^3.3.1",
"toucan-js": "3.4.0",
"typescript": "catalog:default",
"vite": "catalog:default",
"vitest": "catalog:default",
"wrangler": "workspace:*"
},
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@
"tree-kill": "^1.2.2",
"turbo": "^2.2.3",
"typescript": "catalog:default",
"vite": "^5.0.12",
"vite": "catalog:default",
"vitest": "catalog:default"
},
"packageManager": "[email protected]",
Expand Down
13 changes: 13 additions & 0 deletions packages/vitest-pool-workers/src/config/index.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import assert from "node:assert";
import crypto from "node:crypto";
import fs from "node:fs/promises";
import { builtinModules } from "node:module";
import path from "node:path";
import { MessageChannel, receiveMessageOnPort } from "node:worker_threads";
import { workerdBuiltinModules } from "../shared/builtin-modules";
import type {
WorkersConfigPluginAPI,
WorkersPoolOptions,
Expand Down Expand Up @@ -147,6 +149,17 @@ function createConfigPlugin(): Plugin<WorkersConfigPluginAPI> {
// https://github.com/vitejs/vite/blob/v5.1.4/packages/vite/src/node/plugins/resolve.ts#L175
config.ssr.target = "webworker";

// Pre-bundling dependencies with vite
config.test.deps ??= {};
config.test.deps.optimizer ??= {};
config.test.deps.optimizer.ssr ??= {};
config.test.deps.optimizer.ssr.enabled ??= true;
config.test.deps.optimizer.ssr.exclude ??= [];
ensureArrayIncludes(config.test.deps.optimizer.ssr.exclude, [
...workerdBuiltinModules,
...builtinModules.concat(builtinModules.map((m) => `node:${m}`)),
]);

// Ideally, we would force `pool` to be @cloudflare/vitest-pool-workers here,
// but the tests in `packages/vitest-pool-workers` define `pool` as "../..".
config.test.pool ??= "@cloudflare/vitest-pool-workers";
Expand Down
2 changes: 1 addition & 1 deletion packages/vitest-pool-workers/src/pool/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import {
} from "miniflare";
import semverSatisfies from "semver/functions/satisfies.js";
import { createMethodsRPC } from "vitest/node";
import { workerdBuiltinModules } from "../shared/builtin-modules";
import { createChunkingSocket } from "../shared/chunking-socket";
import { CompatibilityFlagAssertions } from "./compatibility-flag-assertions";
import { OPTIONS_PATH, parseProjectOptions } from "./config";
Expand All @@ -41,7 +42,6 @@ import {
import {
ensurePosixLikePath,
handleModuleFallbackRequest,
workerdBuiltinModules,
} from "./module-fallback";
import type {
SourcelessWorkerOptions,
Expand Down
28 changes: 21 additions & 7 deletions packages/vitest-pool-workers/src/pool/module-fallback.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import util from "node:util";
import * as cjsModuleLexer from "cjs-module-lexer";
import { buildSync } from "esbuild";
import { ModuleRuleTypeSchema, Response } from "miniflare";
import { workerdBuiltinModules } from "../shared/builtin-modules";
import { isFileNotFoundError } from "./helpers";
import type { ModuleRuleType, Request, Worker_Module } from "miniflare";
import type { ViteDevServer } from "vite";
Expand Down Expand Up @@ -41,6 +42,19 @@ function trimSuffix(suffix: string, value: string) {
return value.substring(0, value.length - suffix.length);
}

/**
* When pre-bundling is enabled, Vite will add a hash to the end of the file path
* e.g. `/node_modules/.vite/deps/my-dep.js?v=f3sf2ebd`
*
* @see https://vite.dev/guide/features.html#npm-dependency-resolving-and-pre-bundling
* @see https://github.com/cloudflare/workers-sdk/pull/5673
*/
const versionHashRegExp = /\?v=[0-9a-f]+$/;

function trimViteVersionHash(filePath: string) {
return filePath.replace(versionHashRegExp, "");
}

// RegExp for path suffix to force loading module as specific type.
// (e.g. `/path/to/module.wasm?mf_vitest_force=CompiledWasm`)
// This suffix will be added by the pool when fetching a module that matches a
Expand All @@ -54,12 +68,6 @@ const forceModuleTypeRegexp = new RegExp(
`\\?mf_vitest_force=(${ModuleRuleTypeSchema.options.join("|")})$`
);

// Node.js built-in modules provided by `workerd`
export const workerdBuiltinModules = new Set([
...VITEST_POOL_WORKERS_DEFINE_BUILTIN_MODULES,
"__STATIC_CONTENT_MANIFEST",
]);

// `chai` contains circular `require()`s which aren't supported by `workerd`
// TODO(someday): support circular `require()` in `workerd`
const bundleDependencies = ["chai"];
Expand Down Expand Up @@ -323,7 +331,8 @@ async function viteResolve(
// (Specifically, the "tinyrainbow" module imports `node:tty` as `tty`)
return id;
}
return resolved.id;

return trimViteVersionHash(resolved.id);
}

type ResolveMethod = "import" | "require";
Expand Down Expand Up @@ -565,6 +574,11 @@ export async function handleModuleFallbackRequest(
return await load(vite, logBase, method, target, specifier, filePath);
} catch (e) {
debuglog(logBase, "error:", e);
console.error(
`[vitest-pool-workers] Failed to ${method} ${JSON.stringify(target)} from ${JSON.stringify(referrer)}.`,
"To resolve this, try bundling the relevant dependency with Vite.",
"For more details, refer to https://developers.cloudflare.com/workers/testing/vitest-integration/known-issues/#module-resolution"
);
}

return new Response(null, { status: 404 });
Expand Down
5 changes: 5 additions & 0 deletions packages/vitest-pool-workers/src/shared/builtin-modules.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
// Node.js built-in modules provided by `workerd`
export const workerdBuiltinModules = new Set([
...VITEST_POOL_WORKERS_DEFINE_BUILTIN_MODULES,
"__STATIC_CONTENT_MANIFEST",
]);
2 changes: 1 addition & 1 deletion packages/vitest-pool-workers/tsconfig.emit.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,5 @@
"incremental": false
},
// Only want to emit `.d.ts` for `/config` sub-export
"include": ["./src/config/**/*.ts"]
"include": ["./src/shared/types-global.d.ts", "./src/config/**/*.ts"]
}
2 changes: 1 addition & 1 deletion packages/workers-editor-shared/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@
"eslint": "^8.49.0",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"vite": "^5.0.12",
"vite": "catalog:default",
"vite-plugin-dts": "^4.0.1"
},
"peerDependencies": {
Expand Down
Loading
Loading