Skip to content

Commit

Permalink
fix(vitest-pool-workers): enable dependency pre-bundling (#7810)
Browse files Browse the repository at this point in the history
  • Loading branch information
edmundhung authored Feb 11, 2025
1 parent c8719b1 commit ac4f30b
Show file tree
Hide file tree
Showing 31 changed files with 645 additions and 593 deletions.
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

0 comments on commit ac4f30b

Please sign in to comment.