vitejs/vite · error · Error

Circular worker imports detected. Vite does not support it…

Error message

Circular worker imports detected. Vite does not support it. Import chain: ${newBundleChain.map((id) => prettifyUrl(id, config.root)).join(' -> ')}

What it means

bundleWorkerEntry bundles a worker entry with rolldown, threading config.bundleChain to detect cycles. If the worker's import graph leads back to the same worker entry being bundled, Vite throws because recursively bundling a worker into itself is unsupported and would loop forever.

Solutions

  1. Read the printed import chain to find which worker is re-entered, then break that edge.
  2. Extract the shared code both workers use into a third non-worker module that neither worker entry re-imports.
  3. Avoid importing a worker file from another worker file; communicate via postMessage at runtime instead.
  4. Check barrel/index files for accidental re-exports of the worker entry.

Example fix

// before — workerA.ts imports workerB which imports workerA
// after — move shared logic to shared.ts imported by both
// workerA.ts: import { work } from './shared'
// workerB.ts: import { work } from './shared'
Defensive patterns

Strategy: validation

Validate before calling

// Detect cycles among worker entries before bundling
function hasWorkerCycle(graph, start) {
  const seen = new Set()
  function visit(node, stack) {
    if (stack.has(node)) return true
    stack.add(node)
    for (const next of graph[node] ?? []) if (visit(next, stack)) return true
    stack.delete(node)
    return seen.has(node) ? false : (seen.add(node), false)
  }
  return visit(start, new Set())
}

Try / catch

try {
  return await bundleWorkerEntry(config, id)
} catch (e) {
  if (/Circular worker imports/.test(e.message)) {
    logger.error(`Fix the cycle printed in the error before retrying.`)
  }
  throw e
}

Prevention

When it happens

Trigger: Worker A imports module M which imports Worker A again (or Worker A imports Worker B which imports Worker A). Each bundleWorkerEntry call pushes the input onto bundleChain; a repeat entry triggers the throw.

Common situations: Refactoring that made a worker re-import its own entry, circular deps between two worker files, or a barrel file that re-exports a worker entry that itself imports the barrel.

Related errors


AI-assisted analysis of vitejs/vite@b4d66fee14 (2026-08-11). Data as JSON: /api/errors/45f4e7e7d1a45f6e. Report an issue: GitHub.

Appendix: source

Thrown at packages/vite/src/node/plugins/worker.ts:178

const workerOutputCaches = new WeakMap<ResolvedConfig, WorkerOutputCache>()

async function bundleWorkerEntry(
  config: ResolvedConfig,
  id: string,
): Promise<WorkerBundle> {
  const input = cleanUrl(id)

  const workerOutput = workerOutputCaches.get(config.mainConfig || config)!
  workerOutput.removeBundleIfInvalidated(input)

  const bundleInfo = workerOutput.getWorkerBundle(input)
  if (bundleInfo) {
    return bundleInfo
  }

  const newBundleChain = [...config.bundleChain, input]
  if (config.bundleChain.includes(input)) {
    throw new Error(
      'Circular worker imports detected. Vite does not support it. ' +
        `Import chain: ${newBundleChain.map((id) => prettifyUrl(id, config.root)).join(' -> ')}`,
    )
  }

  // bundle the file as entry to support imports
  const { rolldown } = await import('rolldown')
  const { plugins, rolldownOptions, format } = config.worker
  const workerConfig = await plugins(newBundleChain)
  const workerEnvironment = new BuildEnvironment('client', workerConfig) // TODO: should this be 'worker'?
  await workerEnvironment.init()

  const chunkMetadataMap = new ChunkMetadataMap()
  const workerBuildTarget = workerEnvironment.config.build.target
  const bundle = await rolldown({
    ...rolldownOptions,
    input,
    plugins: workerEnvironment.plugins.map((p) =>

View on GitHub (pinned to b4d66fee14)