denoland/deno · error · TypeError

Cannot import scripts in a module worker

Error message

Cannot import scripts in a module worker

What it means

importScripts exists only in classic workers; Deno workers default to `type: 'module'`, and importScripts in runtime/js/99_main.js checks op_worker_get_type() on every call, throwing TypeError('Cannot import scripts in a module worker') otherwise. ES modules must be loaded with import statements or dynamic import() instead. Deno itself bootstraps classic workers by generating an importScripts(...) call for the main script, which is why the function exists at all.

Source

Thrown at runtime/js/99_main.js:392

      // delivering this already-dequeued message so a handler that re-armed
      // itself in a microtask after the previous dispatch (e.g. reassigning
      // `onmessage` inside a `.then`) is installed first -- otherwise the
      // message reaches the stale handler and is lost. A synchronous
      // checkpoint can't help: V8 won't run microtasks reentrantly while we
      // are already inside one.
      await new Promise((resolve) => queueMicrotask(() => resolve()));
      if (isClosing) break;
      op_worker_maybe_wait_for_debugger();
      dispatchWorkerMessage(syncData);
    }
  }
}

let loadedMainWorkerScript = false;

function importScripts(...urls) {
  if (op_worker_get_type() !== "classic") {
    throw new TypeError("Cannot import scripts in a module worker");
  }

  const baseUrl = location.getLocationHref();
  const parsedUrls = ArrayPrototypeMap(urls, (scriptUrl) => {
    try {
      return new url.URL(scriptUrl, baseUrl ?? undefined).href;
    } catch {
      throw new DOMException(
        `Failed to parse URL: ${scriptUrl}`,
        "SyntaxError",
      );
    }
  });

  // A classic worker's main script has looser MIME type checks than any
  // imported scripts, so we use `loadedMainWorkerScript` to distinguish them.
  // TODO(andreubotella) Refactor worker creation so the main script isn't
  // loaded with `importScripts()`.

View on GitHub (pinned to a961cdec3b)

Solutions

  1. Replace importScripts with a static `import` or `await import(url)` — dynamic import covers runtime-computed URLs
  2. If the script only exists as classic JS, create the worker with `{ type: 'classic' }` (note: classic workers do not get TypeScript/ESM resolution for subsequent imports)
  3. When re-authoring is impossible, fetch the script text and load it via a data: URL import

Example fix

// before
importScripts('https://example.com/lib.js');

// after
const lib = await import('https://example.com/lib.js');
Defensive patterns

Strategy: fallback

Validate before calling

// dual-mode loader that works in classic and module workers
async function loadScript(url) {
  if (typeof importScripts === 'function') {
    importScripts(url); // classic worker
  } else {
    await import(url); // module worker
  }
}

Prevention

When it happens

Trigger: Calling importScripts() inside `new Worker(url, { type: 'module' })` or with the default type; running web-worker scripts written for classic workers (common in older libraries) unchanged in Deno.

Common situations: Porting classic worker scripts that load UMD/global scripts via importScripts; mixing CDN script-tag style loading into module workers; sharing one worker file between a browser classic context and Deno.

Related errors


AI-assisted analysis of denoland/deno@a961cdec3b (2026-08-20). Data as JSON: /api/errors/a546639f2e7a211b. Report an issue: GitHub.