Skip to content

Conversation

@guyco3
Copy link

@guyco3 guyco3 commented Jan 17, 2026

fixes #21422

What is this PR solving?

This PR fixes an issue where Web Workers defined in dependencies using new URL('./worker.js', import.meta.url) fail to load after dependency optimization (Rolldown).

The Problem

In the new dependency optimizer (Rolldown), libraries are bundled into .vite/deps. When a library contains a relative worker constructor, that relative path becomes invalid because the actual worker file remains in the original node_modules directory. Rolldown's internal asset scanner attempts to resolve these paths relative to the new bundle location, leading to "File does not exist" errors and broken worker loading.

The Solution

I added a transform hook to the vite:dep-pre-bundle plugin within rolldownDepPlugin.ts:

  • Correct Relative Path: It detects new URL patterns within optimized dependencies and rebases them to the correct relative paths pointing to the original source file.
  • Rolldown Bypass: It utilizes both /* @vite-ignore */ and string concatenation ('' + import.meta.url) to make the expression dynamic. This effectively "blinds" Rolldown's static analysis scanner, preventing it from incorrectly attempting to bundle the absolute path as a relative asset.
  • Automatic Permissions: It automatically adds the library's asset directory to server.fs.allow, ensuring the dev server permits the browser to fetch the worker via the absolute path.

Unit Test & Housekeeping Updates

  • Snapshot Updates: Updated snapshots in packages/vite/src/node/__tests__/plugins/import.spec.ts to reflect minor formatting changes (tabs to spaces) triggered by the project's Prettier configuration. No logic changes were made to these core tests.
  • Lockfile: Updated pnpm-lock.yaml to the latest format to resolve workspace integrity issues encountered when adding the new playground test package.

How has this been tested?

I added a new test case in the optimize-deps playground:

  1. New Test Package: Created @vitejs/test-dep-with-worker-repro which exports workers from the root and a nested directory.
  2. E2E Test: Added assertions in playground/optimize-deps/__tests__/optimize-deps.spec.ts to verify that worker messages are correctly received in the browser logs during dev mode.

Summary of Changes

File Purpose
rolldownDepPlugin.ts Core Fix: Detects new URL and rebases to absolute paths.
import.spec.ts Maintenance: Updates snapshots to match current project formatting.
pnpm-lock.yaml Integrity: Updates workspace dependencies for the new test.
playground/optimize-deps/... Verification: New E2E test case for the worker fix.

Before vs After Tests

Failed Test Log (Before Fix)
base) guycohen@Guys-MacBook-Air vite % pnpm vitest --config vitest.config.e2e.ts playground/optimize-deps -t "worker"

 DEV  v4.0.17 /Users/guycohen/dev/vite

--------------------------------------------------x

⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ Failed Tests 1 ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯

 FAIL  playground/optimize-deps/__tests__/optimize-deps.spec.ts > should fix standard web worker URLs in optimized dependencies
AssertionError: expected '[vite] connecting...\n%cDownload the …' to match /Message from lib: worker-success/

- Expected: 
/Message from lib: worker-success/

+ Received: 
"[vite] connecting...
%cDownload the React DevTools for a better development experience: https://react.dev/link/react-devtools font-weight:bold
ok
ok
Module \"fs\" has been externalized for browser compatibility. Cannot access \"fs.readFileSync\" in client code. See https://vite.dev/guide/troubleshooting.html#module-externalized-for-browser-compatibility for more details.
dep-with-builtin-module-esm TypeError: (0 , import_browser_external_fs.readFileSync) is not a function
    at http://localhost:5173/node_modules/.vite/deps/@vitejs_test-dep-with-builtin-module-esm.js?v=a8c2d33d:8:46
Module \"path\" has been externalized for browser compatibility. Cannot access \"path.join\" in client code. See https://vite.dev/guide/troubleshooting.html#module-externalized-for-browser-compatibility for more details.
dep-with-builtin-module-esm TypeError: import_browser_external_path.default.join is not a function
    at http://localhost:5173/node_modules/.vite/deps/@vitejs_test-dep-with-builtin-module-esm.js?v=a8c2d33d:13:39
Module \"fs\" has been externalized for browser compatibility. Cannot access \"fs.readFileSync\" in client code. See https://vite.dev/guide/troubleshooting.html#module-externalized-for-browser-compatibility for more details.
Module \"path\" has been externalized for browser compatibility. Cannot access \"path.join\" in client code. See https://vite.dev/guide/troubleshooting.html#module-externalized-for-browser-compatibility for more details.
dep-with-builtin-module-cjs TypeError: path.join is not a function
    at http://localhost:5173/node_modules/.vite/deps/@vitejs_test-dep-with-builtin-module-cjs.js?v=e1b01b58:9:8
    at http://localhost:5173/node_modules/.vite/deps/chunk-Dlsxdu6H.js?v=4e0e41d7:9:48
    at http://localhost:5173/node_modules/.vite/deps/@vitejs_test-dep-with-builtin-module-cjs.js?v=e1b01b58:19:16
Module \"fs\" has been externalized for browser compatibility. Cannot access \"fs.readFileSync\" in client code. See https://vite.dev/guide/troubleshooting.html#module-externalized-for-browser-compatibility for more details.
[vite] connected.
Failed to load resource: the server responded with a status of 404 (Not Found)"

 ❯ playground/optimize-deps/__tests__/optimize-deps.spec.ts:377:6
    375|   await expect
    376|     .poll(() => browserLogs.join('\n'))
    377|     .toMatch(/Message from lib: worker-success/)
       |      ^
    378|   await expect
    379|     .poll(() => browserLogs.join('\n'))

Caused by: Error: Matcher did not succeed in time.
 ❯ playground/optimize-deps/__tests__/optimize-deps.spec.ts:375:3

⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[1/1]⎯


 Test Files  1 failed | 1 skipped (2)
      Tests  1 failed | 50 skipped (51)
   Start at  15:49:28
   Duration  5.83s (transform 139ms, setup 525ms, import 141ms, tests 3.33s, environment 0ms)

 FAIL  Tests failed. Watching for file changes...
       press h to show help, press q to quit
Test Log (After Fix) ```bash (base) guycohen@Guys-MacBook-Air vite % pnpm vitest --config vitest.config.e2e.ts playground/optimize-deps -t "worker"

DEV v4.0.17 /Users/guycohen/dev/vite

--------------------------------------------------*

Test Files 1 passed | 1 skipped (2)
Tests 1 passed | 50 skipped (51)
Start at 15:46:53
Duration 3.70s (transform 86ms, setup 720ms, import 68ms, tests 883ms, environment 0ms)

PASS Waiting for file changes...
press h to show help, press q to quit

</details>

@guyco3 guyco3 force-pushed the fix-optimize-deps-worker-assets branch 5 times, most recently from e579679 to 160562a Compare January 17, 2026 23:03
@guyco3 guyco3 force-pushed the fix-optimize-deps-worker-assets branch from 160562a to aa501ea Compare January 17, 2026 23:11
@guyco3 guyco3 marked this pull request as ready for review January 17, 2026 23:15
@guyco3 guyco3 marked this pull request as draft January 17, 2026 23:19
@guyco3 guyco3 marked this pull request as ready for review January 17, 2026 23:21
@sapphi-red sapphi-red added the feat: deps optimizer Esbuild Dependencies Optimization label Jan 19, 2026
@guyco3 guyco3 changed the title fix(optimizer): map relative worker paths to absolute FS locations fix(optimizer): map relative worker paths to correct relative file location Jan 20, 2026
@guyco3
Copy link
Author

guyco3 commented Jan 20, 2026

I have updated the PR to address the recent feedback and improve the stability of the implementation:

  • Relative Paths: Switched from absolute FS_PREFIX locations to resolved relative paths. This ensures the cache remains valid across different project environments and machines.
  • Improved Regex: Refined the transformation logic to target new Worker(new URL(...)) and new SharedWorker(...) constructors specifically. This prevents accidental transformation of standard assets (images, data files) that use new URL but are not workers.
  • Performance Optimization: The plugin now returns null early if a file is outside node_modules or does not contain a worker constructor, as suggested by the Graphite bot.
  • New Unit Tests: Created a dedicated test suite in packages/vite/src/node/__tests__/optimizer/rolldownDepPlugin.spec.ts. These tests verify:
    • Correct path rebasing from the cache directory.
    • Performance-driven null returns for non-target files.
    • Support for multiple workers and various quote styles.

@guyco3
Copy link
Author

guyco3 commented Jan 20, 2026

I'm open to discussing the workerRE regex logic, as I tried to balance false positives and false negative rate versus readability and simplicity.

The current regex doesn't handle these cases:

  • comments inside URL: If someone puts a comment between the parenthesis and the quote (e.g., new URL(/* ignore */ './w.js'...)), this regex will not match it.
  • Template Literals: It does not match backticks (worker.js).
  • Trailing commas: ('w.js', import.meta.url, ). The extra comma at the end would break this specific match.
    Not sure how much we should care about these edge cases...
const workerRE =
            /(new\s+(?:Shared)?Worker\s*\(\s*)new\s+URL\s*\(\s*(['"])((?:(?!\2).|\\.)*)\2\s*,\s*import\.meta\.url\s*\)/g

Comment on lines +335 to +342
const assetDir = path.dirname(absolutePath)

if (
environment.config.server.fs.allow &&
!environment.config.server.fs.allow.includes(assetDir)
) {
environment.config.server.fs.allow.push(assetDir)
}
Copy link
Member

Choose a reason for hiding this comment

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

I guess this part is not needed because it'll be handled by the other internal plugins.

Comment on lines +310 to +311
const workerRE =
/(new\s+(?:Shared)?Worker\s*\(\s*)new\s+URL\s*\(\s*(['"])((?:(?!\2).|\\.)*)\2\s*,\s*import\.meta\.url\s*\)/g
Copy link
Member

Choose a reason for hiding this comment

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

Improved Regex: Refined the transformation logic to target new Worker(new URL(...)) and new SharedWorker(...) constructors specifically. This prevents accidental transformation of standard assets (images, data files) that use new URL but are not workers.

We have the same issue with non-worker assets. Was there a specific reason to limit to the workers? If not, would you revert that change and add tests for non-worker assets as well?

Comment on lines +306 to +307
if (!id.includes('node_modules')) return null

Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
if (!id.includes('node_modules')) return null

If the file is bundled by the optimizer, I think we need to resolve the path in new URL.

let match
while ((match = workerRE.exec(code))) {
hasReplacements = true
const [fullMatch, prefix, _, url] = match
Copy link
Member

Choose a reason for hiding this comment

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

Let's skip URLs that starts with protocols:

if (isDataUrl(url)) {
  continue
}

and new URL with /* @vite-ignore */.

I think you can copy some code from

let s: MagicString | undefined
const assetImportMetaUrlRE =
/\bnew\s+URL\s*\(\s*('[^']+'|"[^"]+"|`[^`]+`)\s*,\s*import\.meta\.url\s*(?:,\s*)?\)/dg
const cleanString = stripLiteral(code)
let match: RegExpExecArray | null
while ((match = assetImportMetaUrlRE.exec(cleanString))) {
const [[startIndex, endIndex], [urlStart, urlEnd]] = match.indices!
if (hasViteIgnoreRE.test(code.slice(startIndex, urlStart))) continue
const rawUrl = code.slice(urlStart, urlEnd)
if (!s) s = new MagicString(code)

@guyco3 guyco3 requested a review from sapphi-red January 20, 2026 09:07
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

feat: deps optimizer Esbuild Dependencies Optimization

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Importing worker from a vite library fails because of dependency optimization

2 participants