1
\$\begingroup\$

Description

rollup-plugin-bundle-worker does a great job of shimming web workers into a rollup bundle, this example shows how to extend it to allow importScripts statements inside the web worker and to share code between the main bundle and the web worker.

Motivation

The serialise/de-serialise processes for transferring objects to and from web workers is the same in both domains, so this is a natural use case but generally speaking, the UMD bundle for shared code can be consumed in both domains. The main problem in doing this is that rollup-plugin-bundle-worker materialises the worker as a blob URL and when importScripts is called from such a domain, it will resolve the file URLs accordingly and the loads will fail. This example is one solution to that problem.

Basic architecture

The code is divided into two UMD bundles: the main bundle, which contains the purely web worker code as well as the main app; and the shared bundle, which contains common code. It's exactly the same bundle for both so is cached and loaded only once. In order to re-base the URLs for the importScripts statements in the worker, it needs the app, base URL. This is posted to the worker by the app as an initialisation step. In response, the worker uses the base URL to load requirejs from the server, which it then uses to asynchronously load the shared bundle. After the shared code is loaded, the worker posts back to the main app to signal it's readiness.

The shared code is written as ES2015 modules and rolled up into a UMD bundle, so there is a seperate build step for that that must be done in the src/common directory. The main bundle is then built from the project root in a separate step. It consumes the common bundle, as a global object, with an ES2015 import statement and the web worker code, which is not an ES2015 module. The build steps are chained in the package.json build script.

I've got the feeling I'm missing the trees for the forest but here is my solution...

enter image description here

index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Test FPS</title>
</head>
<body>
<div id="log"></div>
<script src="./src/common/bundle.js"></script>
<script src="./build/bundle.js"></script>
</body>
</html>

index.js

import WebWorker from 'worker!./simpleWorker.js'
import shared from 'shared'

const hostPath = document.location.pathname.match(/(^.*\/)\w+\.html/)[1];

function log(msg) {
  // Use a fragment: browser will only render/reflow once.
  var fragment = document.createDocumentFragment();
  fragment.appendChild(document.createTextNode(msg));
  fragment.appendChild(document.createElement('br'));

  document.querySelector("#log").appendChild(fragment);
}

const webWorker = new WebWorker();

const routes = {
  message: function (m) {
    log(`"${m}" ${shared("Received in main")}`);
  },
  injected: function (m) {
    webWorker.postMessage({
      method: 'route',
      message: "injected"
    });
    this.message(m);
  }
};
webWorker.onmessage = function (e) {
  routes[e.data.method](e.data.message);
};

webWorker.postMessage({
  method: 'inject',
  message: document.location.protocol + '//' + document.location.host + hostPath
});

simpleWorker.js

var test;
function message(m){
  self.postMessage({
    method: 'message',
    message: m
  })
}

function route(m){
  message(`"${m}" ${test("received in worker")}`)
}

var inject = (function(imports, module) {

  const req = ['src/libs/require.js'];

  return function inject(baseURL) {

    let reqUrl = baseURL + req;
    let importsURL = imports.map(function (u) {
      return baseURL + u;
    });

    importScripts(reqUrl);
    message(`${performance.now()}\tloaded:\t${reqUrl}`);

    require(['require'].concat(importsURL),
      function () {
        module.apply(this, Array.from(arguments).slice(1))
      });
  }
})(['src/common/bundle.js'], function (shared) {
  test = shared;
  self.postMessage({
    method: 'injected',
    message: `${performance.now()}\tloaded:\tsrc/common/bundle.js`
  });
});

self.onmessage = function (e) {
  let r = route, i = inject;
  self[e.data.method](e.data.message);
};

shared.js

export default function test(m) {
  return m;
}

rollup.config.js

import bundleWorker from 'rollup-plugin-bundle-worker';

export default {
  entry: './src/index.js',
  format: 'umd',
  moduleName: 'test',
  dest: './build/bundle.js',
  globals: {
    shared: 'shared'
  },
  plugins: [
    bundleWorker(),
  ]
};

src/common/rollup.config.js

import bundleWorker from 'rollup-plugin-bundle-worker';

export default {
  entry: 'shared.js',
  format: 'umd',
  moduleName: 'shared',
  dest: 'bundle.js',
  plugins: [
    bundleWorker(),
  ]
};
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.