-
-
Notifications
You must be signed in to change notification settings - Fork 26.9k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Is it possible to use load webworkers? #1277
Comments
There is currently no support for compiling code for web workers from That said you can always add any unprocessed files (including workers) to the We recognize this is suboptimal but this is really waiting for somebody to figure out how it should work, and send a pull request with a proof of concept. |
I am not sure how ugly is this workaround, but I created an inline worker with a CRA app and worked like a charm, without any external file or configuration changes 🤓 const worker = () => {
setInterval(() => {
postMessage({foo: "bar"});
}, 1000);
}
let code = worker.toString();
code = code.substring(code.indexOf("{")+1, code.lastIndexOf("}"));
const blob = new Blob([code], {type: "application/javascript"});
const worker = new Worker(URL.createObjectURL(blob));
worker.onmessage = (m) => {
console.log("msg", m.data.foo);
}; |
@thiagoxvo - cool solution. working example: // worker.js
const workercode = () => {
self.onmessage = function(e) { // without self, onmessage is not defined
console.log('Message received from main script');
var workerResult = 'Received from main: ' + (e.data);
console.log('Posting message back to main script');
self.postMessage(workerResult); // here it's working without self
}
};
let code = workercode.toString();
code = code.substring(code.indexOf("{")+1, code.lastIndexOf("}"));
const blob = new Blob([code], {type: "application/javascript"});
const worker_script = URL.createObjectURL(blob);
module.exports = worker_script; //main.js
import worker_script from './worker';
var myWorker = new Worker(worker_script);
myWorker.onmessage = (m) => {
console.log("msg from worker: ", m.data);
};
myWorker.postMessage('im from main'); |
There are two main approaches here:
1. Inline patternFrom my POV inline pattern comes from the fact that webpack, and browserify by default generate one file and one-file approach comes from pre-HTTP2-age. It is considered to be good practice to have less files for HTTP1, but this considered to be antipattern for HTTP2. Also allowing execution of blobs in web workers with CSP considered to be security breach: it allows web workers to get around same-origin restrictions. 2. Separate files2.1. Make a way to provide more than one entry for webpack configurationmaybe configuration in package.json? webworkers: ["pdfworker.js"] See other fields in package.json 2.2. Use some kind of webpack plugin, which will generate separate file for each workerTODO: google for options Thoughts? |
What do you think of |
@yonatanmn your code didn't worked for me, in newer versions of React (^15.5.4) using self will throw an error and the code won't compile. (Error thrown: Unexpected use of 'self' no-restricted-globals) So I modified your solution just using let instead of self, and works for me:
main.js file remains the same |
I am creating a React app in which I need some intensive client-side computation to happen in a separate execution thread, i.e. in a Web Worker. This computation uses the math js module. Reading this discussion has been helpful for understanding why this is non-trivial and why my initial attempts at using the “web-worker loader” (for webpack) weren't going to work. I made a start with the “inline worker as blob” approach, initially offered by @thiagoxvo, but am a bit puzzled by how best to load modules within the Worker itself, this now being outside of Webpack’s control. The Mozilla documentation for Web Workers states:
So for example, in my worker I call:
If I call this in an inline-blob worker, I get an error
However, by adding the unprocessed JS files to my public folder as suggested by @gaearon (instructions in issue 1574) – it seems I have a solution. But this approach involves including math js twice in two different ways, firstly using ES6 modules (inside the main thread of the React App, as normal) and also using a CDN for within the Web Worker. It means I will be developing one batch of JS in /src - which does get compiled, and another in /public which does not. Does anyone know of a better solution? |
I am going to close this because it seems like this is a relatively rare use case and might justify a custom build setup anyway. Whether it's done by ejecting or by separating the common piece into an independently compiled package for multiple targets. |
Okay, if anyone stumbles upon this thread, here’s a bit messy, but quick and easy way to get WebWorkers working.
const lodashCloneDeep = require('lodash/cloneDeep');
module.exports = function override(config, env) {
// Add worker-loader by hijacking configuration for regular .js files.
const workerExtension = /\.worker\.js$/;
const babelLoader = config.module.rules.find(
rule => rule.loader && rule.loader.indexOf('babel-loader') !== -1
);
const workerLoader = lodashCloneDeep(babelLoader);
workerLoader.test = workerExtension;
workerLoader.use = [
'worker-loader',
{ // Old babel-loader configuration goes here.
loader: workerLoader.loader,
options: workerLoader.options,
},
];
delete workerLoader.loader;
delete workerLoader.options;
babelLoader.exclude = (babelLoader.exclude || []).concat([workerExtension]);
config.module.rules.push(workerLoader);
// Optionally output the final config to check it.
//console.dir(config, { depth: 10, colors: true });
return config;
};
import myDoSomething from 'my-do-something';
onmessage = async function (message) { // eslint-disable-line no-undef
console.log('Message received from main script', message.data);
const workerResult = await myDoSomething(message.data);
console.log('Posting message back to main script');
postMessage(workerResult);
};
import MySomethingWorker from './MySomething.worker.js';
const worker = new MySomethingWorker();
P.S. As a sidenote, a bit of feedback, as I’ve just began experimenting with create-react-app (having always created my own configurations before). I must say it feels that it kind of lost the second part of "convention over configuration" principle. That is, it almost entirely discarded the ability "to specify unconventional aspects of the application". For example, I had to install the aforementioned react-app-rewired just to get my own module resolve paths and LESS support — being unable to configure such basic things was quite a surprise. |
The solution from @speicus does not seem to be working anymore. I get:
I will try to dig deep in the loader files and see if I can make it work. |
@RassaLibre and @speicus: I was able to get it working by making the following modifications: const workerExtension = /\.worker\.js$/;
function isBabelLoader(rule) {
return rule.loader && rule.loader.indexOf('babel-loader') !== -1;
}
function findBabelLoader(rule) {
if (isBabelLoader(rule)) {
return rule;
}
if (Array.isArray(rule.use) && rule.use.find(isBabelLoader)) {
return rule;
}
return Array.isArray(rule.oneOf) && rule.oneOf.find(isBabelLoader);
}
function searchRules(rules) {
for (let i = 0; i < rules.length; i++) {
const babelRule = findBabelLoader(rules[i]);
if (babelRule) {
return babelRule;
}
}
return {};
}
const babelLoader = searchRules(config.module.rules); Note that it's not thoroughly tested on many configurations, so please be careful. |
The example above worked with the old version of Updated my initial comment with this info. |
I have decided to eject the app and added worker-loader. It took me 5 mins and I don't regret doing so. Thank you guys for replying so quickly though :-) |
@RassaLibre what did you do? added babel's workers-loader? I need a solution for working loaders too. Thanks! |
Based on the previous answers, I wrote a helper class to make things a bit easier when working with Web Workers in React. The helper class looks like so: // WebWorker.js
export default class WebWorker {
constructor(worker) {
let code = worker.toString();
code = code.substring(code.indexOf("{") + 1, code.lastIndexOf("}"));
const blob = new Blob([code], { type: "application/javascript" });
return new Worker(URL.createObjectURL(blob));
}
} Then, you write your worker code like this: // MyWorker.js
// @args: You can pass your worker parameters on initialisation
export default function MyWorker(args) {
let onmessage = e => { // eslint-disable-line no-unused-vars
// Write your code here...
postMessage("Response");
};
} To create a Web Worker instance, write like so: // App.js
// WebWorker helper class
import WebWorker from './utils/WebWorker';
// Your web worker
import MyWorker from './MyWorker';
// Worker initialisation
const workerInstance = new WebWorker(MyWorker);
const workerInstanceWithParams = new WebWorker(new MyWorker("foo"));
// Communication with worker
workerInstance.addEventListener("message", e => console.log(e.data), false);
workerInstance.postMessage("bar"); Using this method, you simply utilise the WebWorker helper class, and there's no need for Webpack loaders or anything like that. It worked for me and solved my problems. Hopefully we won't have to do workarounds like these in the future. |
@packetstracer Exactly! Eject, add workers-loader and then follow the docs for workers-loader to add your own worker. I was done in 5 mins with a minimum effort :-) |
@danielpox I tried this solution too but I think I had some troubles with it, it just did not compile properly so I eventually gave up and ejected the app. btw. don't be afraid of ejecting, everything stays exactly the same, you just have the power to change/add stuff to the webpack config so if you know webpack, you are just fine. |
@RassaLibre I see. What errors did you get? |
@danielpox I've just tried it and, although I don't get any compilation issues, on debugging the (Chrome:63.0.3239.84, react-scripts:1.0.17) |
@speicus works for me, with @brianchirls modification thanks!! |
@speicus works in dev but in production build its not working. In fact it shows like the web worker is not there at all. Any suggestion what I might be missing? |
maybe this library will help? https://github.com/developit/workerize |
@danielpox You solution is the easiest for me that works. Interestingly your method only works when you create worker instances without params like:
Using it with new it creates just empty blob
This is not a big deal. We can pass "default" parameters later as a message. |
We are currently working to add support for WebWorkers: #3660. If you have any comments, use-cases, feedback, etc. please post it in that issue. |
In the past I've used the web-worker loader in webpack, but with create-react-app, I get the
no-webpack-loader-syntax
error.Is there another way that you recommend to do this? I can't find anything in your documentation about webworkers, and it seems there isn't another way to load them in webpack.
The text was updated successfully, but these errors were encountered: