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
- Replace importScripts with a static `import` or `await import(url)` — dynamic import covers runtime-computed URLs
- 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)
- 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
- Default to ES module workers in Deno; load code with static or dynamic import
- Write worker scripts in one mode — never mix importScripts with import/export syntax
- If classic scripts are unavoidable, create the Worker with { type: 'classic' } deliberately
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
- resolve hook must return { shortCircuit: true } or call next
- load hook must return { shortCircuit: true } or call nextLoa
- SyntaxError
- Expected the second argument to assertSnapshot() to be an op
- Snapshot serializer must return a string
AI-assisted analysis of denoland/deno@a961cdec3b (2026-08-20).
Data as JSON: /api/errors/a546639f2e7a211b.
Report an issue: GitHub.