Skip to content
This repository has been archived by the owner on Dec 1, 2019. It is now read-only.

Question - Web workers #183

Closed
Sorceror opened this issue Jul 7, 2016 · 18 comments
Closed

Question - Web workers #183

Sorceror opened this issue Jul 7, 2016 · 18 comments

Comments

@Sorceror
Copy link

Sorceror commented Jul 7, 2016

Is it possible to use this loader with Web workers? Do I need to configure it somehow?
I've try to find any TypeScript/WebPack related web workers examples, but without any luck.
It's probably naive question, but could someone point me to the right direction or link some existing typescript + webpack example with web workers?
It would help me a lot!

@luchillo17
Copy link

What exactly you plan for Web Workers? I have Web Services working in mine for App Caching my output files, depending on the use you have for Web Workers things might work.

Just in case my repo: Luchillo/Ng2-Pro-webpack-starter, i'm using a plugin named offline-plugin which uses the output and generates the SW file, maybe it can give an idea of a plugin for Web Workers, depending on your use case.

@Sorceror
Copy link
Author

I've eventually find out how exactly configure webpack worker loader together with typescript, so this issue can be closed.
I'm using them to side-load data from three.js visualization, because there is quite a lot of them and I don't want to block UI because of long loading times.

@Anupheaus
Copy link

Hi @Sorceror, I'm trying to do the same thing, would you mind posting your working configuration please?

@Sorceror
Copy link
Author

Sorceror commented Jan 6, 2017

Configuration is based on webpack version 2.1.0-beta.25 .
Install proper webpack loader with npm install worker-loader --save-dev and then when you want to instance worker from let's say loaderWorker.ts which is on path ../workers/loaderWorker.ts use

private worker: Worker = new (<any>require("worker-loader?name=loaderWorker.js!../workers/loaderWorker"));

Based on https://github.com/webpack/webpack/tree/master/examples/web-worker , microsoft/TypeScript#1946 and webpack-contrib/worker-loader@b19200b

@atamocius
Copy link

Hi @Sorceror, did you use a separate tsconfig for your web workers? If so, how did you configure your webpack.config? If not, how are you able to use "postMessage" in your web worker?

@Sorceror
Copy link
Author

Sorceror commented Mar 8, 2017

Hey @antonmata , I'm not sure what you mean by "separate tsconfig for web workers", I have one tsconfig.json file for whole project that contains only common parameters (module, outDir, removeComments, target (ES6), sourceMap).

webpack.config file then contains pretty much only configuration of ts-loader nothing else.

postMessage function is only used in client code (regular js code, not in the web workers) to send some message to web worker.

private _myWorker: PromiseWorker = new PromiseWorker(new (<any>require("myworker?name=myWorker.js!../workers/myWorker")));
...
this._myWorker.postMessage(data, "doStuff").then(response => {
	...
});

As a response register function is used, which I do import in worker code (e.g. myWorker.ts). Something like

import { register } from '../path-to-libs/promiseWorker/register';

register(message => {
	if (message.content) {
		return `My response to message from web worker`;
	} else
		throw `Message does not contain any data to be loaded ${message.toString()}`;
});

I did modify the library a bit when I port it to TS for my project, so I'm sorry if the arguments does not fit 100%, but methods name and the way of usage should be completely same.

@atamocius
Copy link

atamocius commented Mar 8, 2017

@Sorceror, thanks for the response.

Ah I see! I was trying to use postMessage inside the worker. But since the WebWorker typings "lib.webworker.d.ts" was not being loaded by default in Visual Studio Code, I moved the worker files to a different folder with its own tsconfig with the "webworker" lib added to get intellisense:

{
  ...
  "compilerOptions": {
    ...
    "lib": [
      "webworker",
      ...
    ]
  }
  ...
}

My next problem was in how to configure webpack to use that tsconfig only for the worker files. However, I could not see a way to configure the "worker-loader" to use a specific tsconfig. So I abandoned the idea and just went with using webpack's multi-compiler; essentially creating 1 webpack config for the workers, and another for the rest.

However, after seeing your response, I might try that out. I don't know how I would feel losing proper webworker intellisense (ie. using "window" inside a worker file should throw an error in vscode), but if it means getting rid of the 2nd webpack config, it might be worth it. Thanks for pointing me to "promise-worker". I would have not known about it, hadn't you mentioned it.

Thanks!

@Sorceror
Copy link
Author

Sorceror commented Mar 8, 2017

@antonmata I'm not sure that I understand what exactly are you trying to achieve. You want to send message from one web worker to another? The sole purpose of the library is to send message to web worker from regular client code and then get answer back in promise manner.

I guess you could send the message from one worker to another if you would create the other worker instance in the worker that want to send the message to the other worker :), or if you pass the instance of the other worker to the sender worker somehow..

But if you have only the problem of getting proper *.d.ts file loaded into VS Code if completely different problem. If typings cannot be downloaded automatically by npm @types command , I use custom_typings directory where I store all "other" typings files. This directory is on path ROOT_PROJECT_PATH/custom_typings/LIBRARY_NAME/*.d.ts and VS Code is loading all those files automatically without issue. Of course it will work properly only for libraries installed by npm (or yarn).

@atamocius
Copy link

@Sorceror, Sorry if I wasn't clear.

I am just trying to run a simple worker. I am calling worker.postMessage from my index.tsx. Then inside the webworker file, run the code and call postMessage to send back the results:

onmessage = e => {
    ...
    // Do calculation
    ...
    postMessage(result);
};

In terms of intellisense/typings, it is that postMessage inside the web worker file that was causing the trouble. There are actually several postMessage method signatures but what is giving me the trouble is in writing a web worker file. If you don't set the proper typings/libraries, vscode and TypeScript will interpret your call to postMessage as Window.postMessage (as declared in lib.d.ts).

There are actually 2 "global" method signatures for postMessage. You can see one in lib.d.ts and another in lib.webworker.d.ts. By default, in TypeScript, lib.d.ts is the one that is loaded, but if you want to write a web worker, you need to find a way to load lib.webworker.d.ts. You can't load it via npm @types or typings as it is already part of the TypeScript npm package. The only way I found to do it is by moving all your web worker files to a separate folder and create a tsconfig inside it with the "lib": [ "webworker" ]. If you don't do this, an open web worker file in vscode will have an intellisense similar to a standard *.ts file which includes the Window object, and thus uses the signature in lib.d.ts rather than lib.webworker.d.ts (you can test this by writing window.alert() and seeing if it shows a syntax error in the editor).

In any case, I already made peace with the solution that I found. Not the most ideal solution, but works. I just wanted to know if I could get worker-loader to play nice with the setup, but alas, I don't think I'll be able to get my cake and eat it too. =P Thank you though for talking about your solution and for putting up with my mindless ramblings. ;)

For reference:
postMessage in lib.d.ts

declare function postMessage(message: any, targetOrigin: string, transfer?: any[]): void;

postMessage in lib.webworker.d.ts

declare function postMessage(message: any, transfer?: any[]): void;

@Sorceror
Copy link
Author

Sorceror commented Mar 8, 2017

@antonmata You're using it differently than it was designed :).

If you check one of my previous answers, you'll see how to use the code.

Create worker somewhere in your main code, something like this

private _myWorker: PromiseWorker = new PromiseWorker(new (<any>require("myworker?name=myWorker.js!../workers/myWorker")));
...
this._myWorker.postMessage(data, "doStuff").then(response => {
	// here in response is what did you 'return' in the worker
});

So you'll send the message to worker with postMessage(...) method it will return promise to which you can attach .then(result => ...) method and react on the result when it's available.

In the worker though, you have to use register(...) method to start to listening to messages. Similar to this

import { register } from '../path-to-libs/promiseWorker/register';

register(message => {
	if (message.content) {
		return `My response to message from web worker`;
	} else
		throw `Message does not contain any data to be loaded ${message.toString()}`;
});

So instead or using postMessage() to send the result back, the result is send by calling return value instead, or throw error if necessary and the returned value will be passed as value of the promise (.then(result => ...) call).

Check the documentation of https://github.com/nolanlawson/promise-worker for further details.

@atamocius
Copy link

@Sorceror, Yeah, the main difference is that you are using promise-worker. I opted to just use postMessage to send the results back to the caller.

I think there is a misunderstanding; I was not pertaining to Worker.postMessage when I said postMessage. I know, just like what you have illustrated, that it is a call that the caller will make to instantiate an instance of the web worker. I was pertaining to calling postMessage from inside the web worker file. VS Code and TypeScript wrongly interprets it as Window.postMessage.

Here is my index.tsx that runs the web worker:

// index.tsx

let worker: Worker = new Worker("dist/js/calcpi.worker.js");

worker.onmessage = e => {
    subject.next(e.data);
};

// This is not the "postMessage" that I kept on referring to
worker.postMessage({
    loop: "1000000000"
});

And here is my web worker, CalcPi.worker.ts, that calls postMessage. This is where the syntax errors happen. This is where I needed to properly load lib.webworker.d.ts for it to have proper intellisense and to compile.

// CalcPi.worker.ts

onmessage = e => {
    let result = calculatePi(e.data.loop);
    postMessage(result);   // <-- This was being wrongly interpreted as window.postMessage by VS Code and TypeScript
};

function calculatePi(loop: string)
{
    let c = parseInt(loop);
    let f = parseFloat(loop);
    let n = 1;

    //these errors will need more work…
    if (isNaN(c) || f != c ) {
        throw("errInvalidNumber");
    } else if (c<=0) {
        throw("errNegativeNumber");
    }
    
    let pi = 0;
    for (let i = 0; i <= c; i++) {
        pi = pi + (4 / n) - (4 / (n + 2));
        n = n + 4;
    }

    return { "PiValue": pi };
}

@semagarcia
Copy link

Hi all!

I'm facing the same problem with VCode adn TypeScript, throwing the error "Supplied parameters do not match any signature of call target", because my postMessage implementation was:

postMessage({ event: 'completed', result: result });

And the error in Angular (v4):
image

What I've done, and seems to work for me is to modify the call in that way:

postMessage.apply(null, [{ event: 'completed', result: result }]);

And works well. I hope this could be helpful for someone.

@lukas-shawford
Copy link

Thanks @semagarcia for the workaround.

@tomlagier
Copy link

tomlagier commented May 8, 2017

I wanted to give a quite update - just got this working on TypeScript 2.3 with Webpack 1.14 and worker-loader 0.8 with the following configuration:

App.tsx

import * as MyWorker from "worker-loader!../../worker";
const worker: Worker = new MyWorker();

typings/custom.d.ts

declare module "worker-loader!../../worker" {
  const content: any;
  export = content;
}

@Sorceror
Copy link
Author

Sorceror commented May 8, 2017

There might be slightly more general solution using wildcard character in module names

App.tsx

import * as MyWorker from "worker-loader!../../worker";
const worker: Worker = new MyWorker();

typings/custom.d.ts

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

Notice the * symbol in declared module name.

I'm not able to test this solution right now, so consider it just as a suggestion.

@tomlagier
Copy link

Works great, thanks @Sorceror

@mogadanez
Copy link

@Sorceror
I got

TS1192: Module '"worker-loader!*"' has no default export.

in this configuration

@Sorceror
Copy link
Author

Sorceror commented Nov 1, 2017

You didn't post the configuration, but I guess this microsoft/TypeScript#3337 (comment) should resolve your problem.
So, add --allowSyntheticDefaultImports as compilation parameter.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests

8 participants