Coder Perfect

What if you didn’t have a separate Javascript file for your web workers?

Problem

As far as I can tell, web workers need to be written in a separate JavaScript file, and called like this:

new Worker('longrunning.js')

I’d rather not have to distribute my workers in separate files because I’m using the closure compiler to merge and minify all of my JavaScript source code. Is there a way to accomplish this?

new Worker(function() {
    //Long-running work here
});

Why does the typical approach to accomplish background work have to load a whole separate JavaScript file from the web server, given how important first-class functions are to JavaScript?

Asked by Ben Dilts

Solution #1

http://www.html5rocks.com/en/tutorials/workers/basics/#toc-inlineworkers

Answered by vsync

Solution #2

HTML5rocks’ method for embedding web worker code in HTML is quite bad. A blob of escaping JavaScript-as-a-string isn’t much better, not least because it hampers work flow (the Closure compiler doesn’t deal with strings).

Personally, I prefer the toString methods, but that regex, @dan-man!

My preferred approach:

// Build a worker from an anonymous function body
var blobURL = URL.createObjectURL( new Blob([ '(',

function(){
    //Long-running work here
}.toString(),

')()' ], { type: 'application/javascript' } ) ),

worker = new Worker( blobURL );

// Won't be needing this anymore
URL.revokeObjectURL( blobURL );

The intersection of these three tables is called support:

However, even if the optional ‘name’ argument matches, this won’t work for a SharedWorker because the URL must be an exact match. A separate JavaScript file is required for a SharedWorker.

There’s a new technique to solve this problem that’s much more effective. Again, save the worker code as a function (rather than a static string) and convert it with.toString(), then store it in CacheStorage under a static URL of your choice.

// Post code from window to ServiceWorker...
navigator.serviceWorker.controller.postMessage(
 [ '/my_workers/worker1.js', '(' + workerFunction1.toString() + ')()' ]
);

// Insert via ServiceWorker.onmessage. Or directly once window.caches is exposed
caches.open( 'myCache' ).then( function( cache )
{
 cache.put( '/my_workers/worker1.js',
  new Response( workerScript, { headers: {'content-type':'application/javascript'}})
 );
});

There are two options for a backup plan. Put a genuine JavaScript file at /my workers/worker1.js, or use the ObjectURL method as shown above.

The following are some of the benefits of this strategy:

Answered by Adria

Solution #3

A single JavaScript file that is aware of its execution context and can operate as both a parent script and a worker can be created. Let’s start with the fundamental structure of a file like this:

(function(global) {
    var is_worker = !this.document;
    var script_path = is_worker ? null : (function() {
        // append random number and time to ID
        var id = (Math.random()+''+(+new Date)).substring(2);
        document.write('<script id="wts' + id + '"></script>');
        return document.getElementById('wts' + id).
            previousSibling.src;
    })();
    function msg_parent(e) {
        // event handler for parent -> worker messages
    }
    function msg_worker(e) {
        // event handler for worker -> parent messages
    }
    function new_worker() {
        var w = new Worker(script_path);
        w.addEventListener('message', msg_worker, false);
        return w;
    }
    if (is_worker)
        global.addEventListener('message', msg_parent, false);

    // put the rest of your library here
    // to spawn a worker, use new_worker()
})(this);

As you can see, the script includes code for both the parent and the worker, including a check with!document to verify if its own unique instance is a worker. Because the path specified to new Worker is relative to the parent page, not the script, the script path computation is utilized to appropriately determine the script’s path relative to the parent page.

Answered by Delan Azabani

Solution #4

How about this for a worker factory using the Blob method:

var BuildWorker = function(foo){
   var str = foo.toString()
             .match(/^\s*function\s*\(\s*\)\s*\{(([\s\S](?!\}$))*[\s\S])/)[1];
   return  new Worker(window.URL.createObjectURL(
                      new Blob([str],{type:'text/javascript'})));
}

So you could do something like this with it…

var myWorker = BuildWorker(function(){
   //first line of worker
   self.onmessage(){....};
   //last line of worker
});

EDIT:

Bridged-worker.js is a new extension of this notion that makes cross-thread communication easier.

EDIT 2:

The link above is to a gist I made. It was then made into a real repo by someone else.

Answered by dan-man

Solution #5

As independent Programs, web workers run in completely separate environments.

This means that code can’t be transported from one context to another in object form because they’d be able to reference objects from the other context via closures. This is especially important because ECMAScript is supposed to be a single threaded language, and because web workers run in different threads, you risk performing non-thread-safe activities.

This means that web workers must be initialized with source code once more.

According to the WHATWG specification,

However, it does not explain why a string containing source code could not have been passed to the constructor.

Answered by Sean Kinsey

Post is based on https://stackoverflow.com/questions/5408406/web-workers-without-a-separate-javascript-file