Skip to content

Worker Exports ​

Worker support is split across three package entry points. @chiballc/utils/web-workers manages dedicated web workers, @chiballc/utils/service-workers manages service-worker channels, and @chiballc/utils/plugins default-exports the Vite plugin. Each local example uses imports from its surrounding entry-point section.

Web workers ​

ts
import {
  createRawWorker,
  defineWRouter,
  defineWTransporter,
  terminateCachedWorker,
  useWebWorker,
} from "@chiballc/utils/web-workers";

WorkerWrapper ​

WorkerWrapper is the type of a function that accepts optional WorkerOptions and returns a browser Worker. Vite's ?worker imports are compatible with this shape.

ts
const createWorker: WorkerWrapper = (options) => new SearchWorker(options);

WorkerClient ​

WorkerClient<T> is the object returned by useWebWorker. It exposes the underlying read-only worker, postMessage, sendMessage, sendMessages, onMessage, onError, release, and terminate methods. The generic type is available to callers who want to describe the client message domain.

ts
const stop = client.onMessage((event) => console.log(event.data));
const matches = await client.sendMessage<string[]>("SEARCH", { query: "nuxt" });
stop();

createRawWorker ​

createRawWorker(wrapper) constructs a Worker from an existing worker, URL, string URL, Vite worker constructor, or WorkerWrapper. New workers are created as module workers. It returns undefined only when the wrapper cannot produce a worker.

ts
createRawWorker(SearchWorker);
createRawWorker(new URL("./search.worker.ts", import.meta.url));
createRawWorker("/workers/search.js");

useWebWorker ​

useWebWorker(key, wrapper, options?) creates or reuses a WorkerClient. The keyless form is useWebWorker(wrapper, options?); it derives a key from the wrapper. ephemeral and autoTerminateOnRelease terminate the worker after the last client releases it. alwaysNew bypasses reuse and creates a unique worker. It returns undefined when no key or worker can be resolved.

ts
const shared = useWebWorker("search", SearchWorker, { autoTerminateOnRelease: true });
const isolated = useWebWorker(SearchWorker, { alwaysNew: true, ephemeral: true });
shared?.release();
isolated?.release();

terminateCachedWorker ​

terminateCachedWorker(key) terminates and removes a worker stored in the module's raw worker cache. It is safe to call when the key is not present.

ts
terminateCachedWorker("search");

defineWTransporter ​

defineWTransporter(handler) installs a worker-side message listener. The handler receives an object containing the incoming type and data, plus the original MessageEvent. Returning a value or promise sends { id, data }; throwing sends { id, error } for request messages.

ts
defineWTransporter(({ type, query }) => {
  if (type !== "SEARCH") throw new Error("Unsupported message");
  return searchIndex(query);
});

defineWRouter ​

defineWRouter(handlers) is a typed dispatcher built on defineWTransporter. It selects a handler by the incoming type field and warns when no handler exists.

ts
defineWRouter({
  SEARCH: ({ query }) => searchIndex(query),
  CLEAR: () => searchIndex.clear(),
});

Service workers ​

ts
import {
  defineSwRouter,
  defineSwTransporter,
  getCommunicationChannels,
  useServiceWorker,
  waitForBroadcastReady,
} from "@chiballc/utils/service-workers";

AppServiceWorkerChannel ​

AppServiceWorkerChannel is the TypeScript type of the values in the application-defined AppServiceWorkers interface. Augment that interface in your application when you want type-checked service-worker channel names.

ts
declare global {
  interface AppServiceWorkers {
    Cache: "cache";
  }
}
const channel: AppServiceWorkerChannel = "cache";

waitForBroadcastReady ​

waitForBroadcastReady(registration, timeoutMs?) resolves when a registration is active. It resolves immediately for an already active registration, otherwise listens for installation and activation. The default timeout is 30 seconds.

ts
const registration = await navigator.serviceWorker.register("/cache-sw.js", { type: "module" });
await waitForBroadcastReady(registration, 10_000);

getCommunicationChannels ​

getCommunicationChannels(channelName) creates the BroadcastChannel pair used by the protocol. It returns requestChannel at the provided name and responseChannel at ${channelName}:responses.

ts
const { requestChannel, responseChannel } = getCommunicationChannels("cache");
requestChannel.postMessage({ type: "WARM" });
responseChannel.addEventListener("message", ({ data }) => console.log(data));

useServiceWorker ​

useServiceWorker(url, channelName, options?) registers the worker as a module service worker, waits for activation, and opens its broadcast channels. It returns undefined outside a browser main thread or when service workers are unavailable. The returned client exposes registration, channelName, postMessage, sendMessage, sendMessages, onMessage, and close.

ts
const cacheWorker = await useServiceWorker("/cache-sw.js", "cache", { scope: "/" });
const status = await cacheWorker?.sendMessage<{ entries: number }>("STATUS");
cacheWorker?.close();

defineSwTransporter ​

defineSwTransporter(channelName, handler) installs a service-worker-side listener for a channel. The handler receives the message type merged with its data and an event that adds broadcast(type, data). It returns a client with close, or undefined outside a service-worker global scope.

ts
defineSwTransporter("cache", async ({ type, url }, event) => {
  if (type !== "CACHE_URL") return;
  await caches.open("api").then((cache) => cache.add(url));
  event.broadcast("CACHE_UPDATED", { url });
  return { cached: true };
});

defineSwRouter ​

defineSwRouter(channelName, handlers) dispatches channel messages by their type field. It is the multi-handler form of defineSwTransporter and returns the same closeable client or undefined outside a service worker.

ts
defineSwRouter("cache", {
  STATUS: async () => ({ entries: (await caches.keys()).length }),
  CLEAR: async () => {
    const names = await caches.keys();
    await Promise.all(names.map((name) => caches.delete(name)));
    return { cleared: names.length };
  },
});

Vite plugin ​

ts
import serviceWorkerPlugin from "@chiballc/utils/plugins";

ServiceWorkerPluginOptions ​

ServiceWorkerPluginOptions describes the plugin configuration. appServiceWorkersEnum supplies an optional enum for diagnostic logging. format is "iife" by default or "es" for native module workers. publicDir overrides the output directory. logLevel is "info" by default and also accepts "silent" and "debug".

ts
const options: ServiceWorkerPluginOptions = {
  appServiceWorkersEnum: { Cache: "cache" },
  format: "es",
  publicDir: "public",
  logLevel: "debug",
};

default: serviceWorkerPlugin ​

The default export, serviceWorkerPlugin(options?), returns the Vite plugin pair that implements ?serviceworker imports. In development it transforms and serves the imported worker at a stable root URL. On build it bundles each discovered worker using the selected format and writes it to publicDir, the application's public directory, or Vite's output directory in that order.

ts
export default defineConfig({
  plugins: [serviceWorkerPlugin({ format: "iife", logLevel: "info" })],
});

import cacheWorkerUrl from "./cache-sw.ts?serviceworker";

Released under the ISC license.