Using comlink with typescript and worker-loader

worker-loader and comlink are two solution which make web-workers a joy to use. This short post summarizes how to make them play well with each other in a typescript codebase.

What is comlink ?

Comlink is a Google project that implements a proxy based RPC mechanism to invoke methods on objects present in web-workers.

Being proxy based, most of the times invocation is fairly transparent and the outcome is a lot easier to read than if we were using postMessage and MessagePort APIs directly. Internally of-course comlink use the same APIs under the hood.

Comlink’s README already outlines the usage adequately and also David East has written a great introduction to Comlink here, so this post will mostly focus on usage with typescript.

Worker loader

worker-loader is a webpack plugin that makes it trivial to use webworkers with webpack. When you are using webpack, you’d usually want to have hash appended file paths for long term caching and worker-loader removes the need to refer to a manifest to derive the file path to be used for passing to Worker constructor.

With worker-loader installed, we can simply do:

And it will create a separate webpack chunk, and generate a facade class instantiating which gives us the worker instance. This worker instance happens to be what Comlink.proxy expects, so we can just pass it on.

Typescript integration

The last piece here is to make typescript play well. Because as of now, typescript has no way to figure out the type of the imported ExpensiveProcessor because the code generated by webpack doesn’t go through the type checker.

The first step is to declare an ambient module having a wildcard declaration for all worker-loader related imports so that worker-loader imports are identified as Worker implementations.

The second step is to pass a type parameter to Comlink.proxy:

Here IProcessorFacade is an interface implemented by ExpensiveProcessor that defines a subset of the public API that plays well with structured cloning.

Of course, this generics based implementation is not exactly type safe even though it eliminates all type errors.

The reason is that there is nothing stopping us from exposing something that doesn’t implement IProcessorFacade but until the typescript’s compile time transformation API gets well supported by all ts ecosystem tooling, the only practical solution is to use a lint rule based on file name conventions if this is a concern.