Skip to content

Fix module resolution for relative require() inside CJS deps when the project path contains spaces - #15119

Open
dario-piotrowicz wants to merge 1 commit into
mainfrom
dario/vitest-spaces
Open

Fix module resolution for relative require() inside CJS deps when the project path contains spaces#15119
dario-piotrowicz wants to merge 1 commit into
mainfrom
dario/vitest-spaces

Conversation

@dario-piotrowicz

Copy link
Copy Markdown
Member

Fixes #15048

When a project lives under a directory with a space in its name, externalized CommonJS dependencies that use relative require() calls (e.g. require("./lib/impl.js")) would fail with "No such module" because workerd percent-encodes the space as %20 in the module name, and subsequent relative imports inherit that encoding. The module fallback handler now retries resolution with decoded paths when the percent-encoded path doesn't exist on disk.


  • Tests
    • Tests included/updated
    • Automated tests not possible - manual testing has been completed as follows:
    • Additional testing not necessary because:
  • Public documentation
    • Cloudflare docs PR(s):
    • Documentation not necessary because: bugfix

A picture of a cute animal (not mandatory, but encouraged)

@github-actions

Copy link
Copy Markdown
Contributor

✅ All changesets look good

@ask-bonk

ask-bonk Bot commented Aug 10, 2026

Copy link
Copy Markdown
Contributor

pnpm isn't readily available and installing deps would be slow/network-dependent. My review can proceed based on static analysis, which is thorough. Let me finalize.

Review Summary

The change is well-designed and well-documented. Key findings:

  1. Correctness of the fix: The %20 (space) case genuinely falls through the existing #14655 sentinel mechanism because %20 is printable-ASCII, so encodeRedirectLocation/decodeEncodedSpecifier are no-ops for it. The retry-with-decode approach correctly targets this gap.

  2. decodeURIComponent throw safety: The /%[0-9A-Fa-f]{2}/ guard doesn't prevent decodeURIComponent from throwing on invalid UTF-8 byte sequences (e.g. lone %C0), but the whole retry body is wrapped in try/catch returning undefined, so this is safe.

  3. Redirect flow: When load returns a 301 redirect to the decoded filePath, workerd re-requests with a path that exists on disk, resolving on the first attempt. No infinite-loop or double-response risk. rawTarget is preserved for the name field correctly.

  4. Literal-%20-on-disk preservation: Confirmed by the existing test at line 210 — the first resolve succeeds directly for literal %20 paths, so the retry never runs. Design claim holds.

  5. Minor edge case (not a bug): A contrived collision where a literal %20 path fails to resolve but a decoded-space path exists could resolve to a different file. Pre-existing behavior would 404 anyway; acceptable trade-off.

No actionable issues found. The logic is sound, the retry is properly guarded, the test is a meaningful regression guard, and the changeset is appropriate.

LGTM

github run

@pkg-pr-new

pkg-pr-new Bot commented Aug 10, 2026

Copy link
Copy Markdown
@cloudflare/autoconfig

npm i https://pkg.pr.new/@cloudflare/autoconfig@15119

@cloudflare/build-output-utils

npm i https://pkg.pr.new/@cloudflare/build-output-utils@15119

@cloudflare/config

npm i https://pkg.pr.new/@cloudflare/config@15119

create-cloudflare

npm i https://pkg.pr.new/create-cloudflare@15119

@cloudflare/deploy-helpers

npm i https://pkg.pr.new/@cloudflare/deploy-helpers@15119

@cloudflare/kv-asset-handler

npm i https://pkg.pr.new/@cloudflare/kv-asset-handler@15119

miniflare

npm i https://pkg.pr.new/miniflare@15119

@cloudflare/pages-functions

npm i https://pkg.pr.new/@cloudflare/pages-functions@15119

@cloudflare/pages-shared

npm i https://pkg.pr.new/@cloudflare/pages-shared@15119

@cloudflare/unenv-preset

npm i https://pkg.pr.new/@cloudflare/unenv-preset@15119

@cloudflare/vite-plugin

npm i https://pkg.pr.new/@cloudflare/vite-plugin@15119

@cloudflare/vitest-pool-workers

npm i https://pkg.pr.new/@cloudflare/vitest-pool-workers@15119

@cloudflare/workers-auth

npm i https://pkg.pr.new/@cloudflare/workers-auth@15119

@cloudflare/workers-editor-shared

npm i https://pkg.pr.new/@cloudflare/workers-editor-shared@15119

@cloudflare/workers-utils

npm i https://pkg.pr.new/@cloudflare/workers-utils@15119

wrangler

npm i https://pkg.pr.new/wrangler@15119

commit: 25fd996

@dario-piotrowicz
dario-piotrowicz marked this pull request as ready for review August 11, 2026 08:27
@workers-devprod
workers-devprod requested review from a team and emily-shen and removed request for a team August 11, 2026 08:28
@workers-devprod

Copy link
Copy Markdown
Contributor

Codeowners approval required for this PR:

  • @cloudflare/wrangler
Show detailed file reviewers
  • .changeset/fix-cjs-relative-require-spaces.md: [@cloudflare/wrangler]
  • packages/vitest-pool-workers/src/pool/module-fallback.ts: [@cloudflare/wrangler]
  • packages/vitest-pool-workers/test/module-fallback.test.ts: [@cloudflare/wrangler]

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Devin Review: No Issues Found

Devin Review analyzed this PR and found no bugs or issues to report.

Open in Devin Review

@petebacondarwin

Copy link
Copy Markdown
Contributor

I am slightly worried by the approach of "just trying" to decode it - there might be cases where this hits a corner case that is broken.

In this PR (#14882) I used a prefix based approach to a similar problem (since we have control over both sides) that means we know exactly whether we need to decode things or not.

Can we consider that approach?

@petebacondarwin
petebacondarwin requested review from petebacondarwin and removed request for emily-shen August 11, 2026 15:12
@changeset-bot

changeset-bot Bot commented Aug 12, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 25fd996

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
@cloudflare/vitest-pool-workers Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@petebacondarwin

Copy link
Copy Markdown
Contributor

We recently landed #14994, a large change to Miniflare's configuration internals that touched ~177 files across the repo. Leaving this PR on its old base was likely to cause conflicts, so we've rebased it onto the latest main and force-pushed the result.

Your local copy of this branch is now out of date. Before you push again, please reset to the new version:

git fetch origin
git checkout dario/vitest-spaces
git reset --hard origin/dario/vitest-spaces

Because the base moved a long way, it's also worth reinstalling before you carry on — the lockfile changed:

pnpm install

Sorry for the interruption. If the rebase looks wrong, or CI now fails in a way that seems related to the Miniflare config change rather than your own work, comment here and we'll help get it sorted.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Untriaged

Development

Successfully merging this pull request may close these issues.

[vitest-pool-workers] Paths with spaces still break on relative require() inside externalized CJS deps (the #14152 fix covers only file: rawSpecifiers)

3 participants