rollup-plugin-bundle-worker icon indicating copy to clipboard operation
rollup-plugin-bundle-worker copied to clipboard

require third party modules

Open kennetpostigo opened this issue 8 years ago • 2 comments

Is it possible to bring in third party code similar to webworkify-webpack? So I can bring in modules like lodash for example.

kennetpostigo avatar Dec 01 '16 05:12 kennetpostigo

I was thinking about this yesterday and it should be possible to reuse rollup within the plugin load method to rollup the file to be "workified". I'm not sure when I'll get time to have a go at it, so if it's still a requirement for you, feel free to submit a PR!

andyearnshaw avatar Mar 30 '17 08:03 andyearnshaw

I was lazy and did it with requirejs but I'm sure a dramatically pruned subset of that code base would do the job. The way I did it also allows for a shared.js bundle that can be loaded ES6 style in the app and using requirejs in the worker. It means there are two bundles but the shared one is cached by the app so the worker gets it for free.

When you use importScript in a blob, you need to fix the base URL and to do that, you need to have it sent from the app. Here is one way to do it.

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.

worker.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);
};

cool-Blue avatar Apr 11 '17 08:04 cool-Blue