Skip to content

Instantly share code, notes, and snippets.

@Eptagone
Last active November 3, 2025 17:59
Show Gist options
  • Save Eptagone/f7a513da95220a4f9e399d9a8110d593 to your computer and use it in GitHub Desktop.
Save Eptagone/f7a513da95220a4f9e399d9a8110d593 to your computer and use it in GitHub Desktop.

Rolldown Workers Plugin

This is an experimental custom rolldown plugin to compile workers.

Warning: This plugin has not been fully tested yet. Please share any details you have found.

Features

With this plugin, you can import your workers like this.

// Web Worker
import Worker from "./my-web-worker.ts?worker";
const instance = new Worker(/* options */)
// Web Shared Worker
import SharedWorker from "./my-shared-worker.ts?sharedworker";
const instance = new SharedWorker(/* options */)
// Worker Thread (NodeJS)
import Worker from "./my-worker-thread.ts?thread";
const instance = new Worker(/* options */)
/// <reference lib="dom" />
// web worker
declare module "*?worker" {
const workerConstructor: {
new(options: WorkerOptions): Worker;
};
export default workerConstructor;
}
declare module "*?sharedworker" {
const sharedWorkerConstructor: {
new(options: WorkerOptions): SharedWorker;
};
export default sharedWorkerConstructor;
}
declare module "*?thread" {
const WorkerHandler: {
new(options: import("node:worker_threads").WorkerOptions): import("node:worker_threads").Worker;
};
export default WorkerHandler;
}
import path from "path";
import { BindingMagicString, type EmittedFile, type Plugin } from "rolldown";
type AvailableQueryParams = "worker" | "sharedworker" | "thread" | "importer";
interface ImportQueryParams extends URLSearchParams {
has(key: AvailableQueryParams): boolean;
}
function getImportQueryParams(id: string): ImportQueryParams {
return new URL(id.replace(/^C:/, "/"), "file:").searchParams;
}
const WORKER_ASSET_PREFIX = "__ROLLDOWN_WORKERS_ASSET__";
const WORKER_ASSET_PATTERN = /__ROLLDOWN_WORKERS_ASSET__([\w\d]+)__/g;
const plugin = (): Plugin => {
return {
name: "rolldown/workers",
resolveId(source, importer): string | void {
const query = getImportQueryParams(source);
if (query.has("thread") || query.has("worker")) {
return source + `&importer=${importer}`;
}
},
async load(id): Promise<string | null | void> {
const query = getImportQueryParams(id);
const importer = query.get("importer");
if (importer && (query.has("thread") || query.has("worker") || query.has("sharedworker"))) {
let chunkId = id.replace(/\?.*/m, "");
let resolvedId = await this.resolve(chunkId, importer, { skipSelf: true });
if (resolvedId === null) {
if (!chunkId.startsWith(".")) {
chunkId = `./${chunkId}`;
resolvedId = await this.resolve(chunkId, importer, { skipSelf: true });
}
if (resolvedId === null) {
this.info(`Cannot resolve ${id} in ${importer}`);
return null;
}
}
const chunk: EmittedFile = {
type: "chunk",
id: resolvedId.id,
importer: importer,
};
const referenceId = this.emitFile(chunk);
const assetRefId = `${WORKER_ASSET_PREFIX}${referenceId}__`;
const url = query.has("thread") ? `path.resolve(import.meta.dirname, ${assetRefId})` : assetRefId;
const signature = query.has("sharedworker") ? "SharedWorker" : "Worker";
let code = `export default function createWorker(options) { return new ${signature}(${url}, options); }`;
if (query.has("thread")) {
code = `import path from 'node:path'; import { Worker } from 'node:worker_threads'; ${code}`;
}
return code;
}
},
renderChunk(code, chunk): string | void {
if (code.match(WORKER_ASSET_PATTERN)) {
let match: RegExpExecArray | null;
const s = new BindingMagicString(code);
while ((match = WORKER_ASSET_PATTERN.exec(code))) {
const [full, hash] = match;
const filename = this.getFileName(hash!);
let outputFilepath = path.posix.relative(path.dirname(chunk.fileName), filename);
if (!outputFilepath.startsWith(".")) {
outputFilepath = `./${outputFilepath}`;
}
const replacement = JSON.stringify(outputFilepath);
s.overwrite(match.index, match.index + full.length, replacement);
}
return s.toString();
}
},
};
};
export default plugin;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment