Search code examples
typescriptbrowserifyaureliaweb-worker

Proper implementation of web workers in Aurelia and TypeScript


Sadly, I couldn't find a single answer to this on the whole web.

I have an Aurelia app based on TypeScript / Aurelia CLI / RequireJS. With a structure like this:

|data
   |-MyService.ts
|workers
   |-SomeWorker.ts/js

There exists a package called aurelia-pal-worker, but without documentation or complex examples.


What I've tried so far

  • Typed-Web-Workers which is nice but too limiting
  • Having a SomeWorker.js and used Browserify as an additional buildstep in aurelia_project.

The Browserify approach works, as long as I require external libs like RxJs. Of course this breaks when I try to require("../data/MyService.ts"). For this to work, I would need to replace the whole build pipeline with another, that runs the entire aurelia project through Browserify with the tsify plugin.

It seems to my I've got 3 choices:

  • Find a working example to compile a TypeScript file down to a web worker and use aurelia-pal-worker to import dependencies.
  • Use TypedWorker and just throw expensive functions into a thread like:
    new TypedWoker(expensiveFuncFromService, handleOutput)
  • Compile MyService.ts to separate JS-Files (instead of bundling it) and require it like this:
    require("/scripts/MyService.js")

The last two don't seem very appealing to me, but should be straightforward to do. Any hints or examples are highly appreciated!

PS: For anyone not familiar with Aurelia: It uses a gulp pipeline under the hood.


Solution

  • So after some fiddling around I switched to a webpack based solution, which allows me to use the amazing webpack-worker-loader.

    This was the best tradeoff between modifying my existing project and having it up and running again quickly.

    This is how it looks like in the end:

    custom_typings/worker-loader.d.ts

    declare module "worker-loader!*" {
      const content: new () => any;
      export = content;
    }
    

    worker/some-service.ts

    export class SomeService {
      public doStuff() {
        console.log("[SomeService] Stuff was done");
      }
    }
    

    worker/my-worker.ts

    import "rxjs/add/observable/interval";
    
    import { Observable } from "rxjs/Observable";
    import { SomeService } from "./some-service";
    
    const svc = new SomeService();
    svc.doStuff();
    
    console.log("[Worker] Did stuff");
    onmessage = event => {
        console.log(event);
    };
    
    Observable.interval(1000).subscribe(x => postMessage(x));
    

    After the worker is loaded like this:

    import * as MyWorker from "worker-loader!./worker/my-worker";
    const worker = new MyWorker();
    worker.onmessage = msg => console.log("[MyClass] got msg from worker", msg);
    

    It will generate the following console output:

    1: "[SomeService] Stuff was done"
    2: "[Worker] Did stuff"
    3: "[MyClass] got msg from worker", 1
    4: "[MyClass] got msg from worker", 2
    ...
    

    You need full blown DI in a worker?

    Fear not, with a little help of this answer I figured out how rewrite this with our webpack based solution:

    let container: Container = null;
    let myService: SuperComplexService = null;
    
    // Import the loader abstraction, so the DI container knows how to resolve our modules.
    import("aurelia-pal-worker")
      .then(pal => pal.initialize())
      // We need some polyfills (like "Reflect.defineMetadata")
      .then(() => import("aurelia-polyfills"))
      // Then we get the DI service and create a container
      .then(() => import("aurelia-dependency-injection"))
      .then(({ Container }) => (container = new Container()))
      .then(() => import("../services/my-super-complex-service")) // Here we go!
      .then(({ SuperComplexService }) => (myService = container.get(SuperComplexService) as SuperComplexService))
      .then(() => startWorker());
    
    const startWorker = async() => {
      // Let's get going!
    }
    

    All credits for this loader-chain go to @jeremy-danyow.