Debugging Double Imports with Vite

Today I hit dependency issues that make me wonder "how is my code actually being bundled?" This happened to me with YJS - a library that really doesn't like being included twice in your application.

The Problem

I had a clear symptom: YJS was throwing errors about duplicate instances. I knew this meant that some of chunks were including the library multiple times, but I was at a loss for how this was happening.

A Peek into Vite's Chunk Resolution

When I saw the console.error from YJS about duplicate imports in my app, my first debugging instinct was to modify the source in node_modules to identify where the imports were coming from. Using npm ls yjs showed only one version. This led me down an interesting path - I was editing what looked like the only occurance of yjs, but the duplicate import error message persisted. From there I moved to the theory was that if I only have one version, might I have both a CommonJS and an ESM import. No. Only ESM imports.

What I didn't realize at first was that PNPM uses symlinks, so while I was editing what seemed like one file, Vite was actually seeing multiple paths to it. Vite resolves modules based on their file paths, not the underlying files on disk. This means two different paths pointing to the same file (via symlinks) are treated as different modules.

Here's the configuration option that finally helped me understand what Vite was actually seeing during the build process:

build: {
  rollupOptions: {
    output: {
      manualChunks(id) {
        if (id.includes('yjs/dist/yjs.mjs')) {
          console.log("someone asked for yjs", id)
        }
      }
    }
  }
}

This configuration hooks into Vite's chunk resolution process. Every time Vite considers a module for bundling, it calls this function. By adding a simple console.log, you can see the full path of each module being considered.

In my case, the output revealed:

someone asked for yjs [REDACTED]/node_modules/yjs/dist/yjs.mjs
someone asked for yjs [REDACTED]/vendor/y-websocket/node_modules/.pnpm/yjs@13.6.23/node_modules/yjs/dist/yjs.mjs

What You Learn

This simple debugging technique shows you:

  1. Which files your build system is actually processing
  2. The full resolution path for each module
  3. When the same library is being pulled from different locations

You can modify the path check (id.includes()) to debug any dependency you're curious about. It's like having X-ray vision into your build process.

Beyond Just Fixing Bugs

In my case, seeing these paths made it obvious that my project structure had evolved beyond what a simple vendor folder could handle. I chose to set up npm workspaces, but that is not to say that this is the only solution.

Build tools don't have to be magic. Sometimes you just need to know where to add a console.log.