Skip to content

in globalSetup, wrong module version served in pnpm workspaces #10028

@deyaaeldeen

Description

@deyaaeldeen

Describe the bug

When a pnpm workspace has multiple versions of the same dependency (e.g., @azure/core-lro@2.7.2 from a published npm package and @azure/core-lro@3.x from a workspace link), vitest serves the wrong version to some importers, causing:

SyntaxError: [vite] The requested module '@azure/core-lro' does not provide an export named 'deserializeState'

Root cause: The configEnvironment hook in vitest's VitestResolver plugin returns early for the __vitest__ environment (line: if (name === "__vitest_vm__" || name === "__vitest__") return;). This skips setting resolve.noExternal = true for the main test environment. Without this, Vite's import analysis plugin treats bare specifiers of node_modules packages as "external" and does not resolve them during transform, leaving raw bare specifiers in the transformed code.

This triggers a cascade of bugs:

  1. Vite's createIsExternal caches externalization decisions by bare specifier (the processedIds map), ignoring the importer. Once @azure/core-lro is deemed external for one importer, ALL importers get the same result.
  2. Vite's module runner caches modules by URL (urlToIdModuleMap). The bare specifier @azure/core-lro maps to whichever version was resolved first (2.7.2), and all subsequent importers get that cached module.
  3. The { cache: true } protocol between client and server doesn't carry module identity. The server resolves the bare specifier to the correct version (3.x), sees it was already transformed, and returns { cache: true }. The client interprets this as "use what you have" and serves the wrong version (2.7.2).

Workaround: Add resolve.dedupe: ["@azure/core-lro"] to vitest.config.ts. This forces Vite's import analysis to resolve the bare specifier from the project root, producing a consistent /@fs/... URL that bypasses all three cache issues.

Reproduction

https://github.com/Azure/azure-sdk-for-js

git clone https://github.com/Azure/azure-sdk-for-js.git
cd azure-sdk-for-js
git checkout cd25924f9c263e012f50a447a770ac68d703d2a4
pnpm install
cd sdk/batch/batch
npx vitest run --no-coverage test/files.spec.ts

Expected: Tests pass (or fail for non-resolution reasons)
Actual: SyntaxError: The requested module '@azure/core-lro' does not provide an export named 'deserializeState'

Steps to reproduce

The dependency tree that triggers this:

  • @azure/batch (test target) depends on:
    • @azure/arm-batch (workspace link) → @azure/core-lro workspace (3.x, has deserializeState)
    • @azure/arm-resources@5.2.0 (published npm) → @azure/core-lro@2.7.2 (no deserializeState)

The arm-resources package uses ESM syntax without "type": "module" in package.json, so vitest's shouldExternalize returns false for its files — they're transformed by Vite. During transform, Vite's import analysis encounters import { ... } from "@azure/core-lro":

  1. shouldExternalize(environment, "@azure/core-lro", "arm-resources/deployments.js") → calls createIsExternal → resolves to 2.7.2 (in node_modules) → canExternalizeFile → true → bare specifier NOT resolved → cached in processedIds["@azure/core-lro"] = true
  2. shouldExternalize(environment, "@azure/core-lro", "arm-batch/pollingHelpers.js")processedIds.has("@azure/core-lro") → returns cached truebare specifier NOT resolved (even though this importer should get 3.x)
  3. Module runner receives __vite_ssr_import__("@azure/core-lro") from both importers → first resolution (2.7.2) cached in urlToIdModuleMap["@azure/core-lro"] → second importer gets stale 2.7.2 → crash

System Info

vitest/4.1.2 linux-x64 node-v22.22.1
vite/7.3.1
pnpm/10.33.0

Used Package Manager

pnpm

Validations

Metadata

Metadata

Assignees

No one assigned

    Labels

    p3-minor-bugAn edge case that only affects very specific usage (priority)

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions