Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Web workers inside a JS library with Rollup

I am building a negamax engine in Typescript that uses Thread.js web-workers. It is a npm library that will be imported by an application built using webpack.

I am using Rollup to build the engine - how can I export the web-worker files so they are copied into the client's build directory as a separate chunk?

like image 249
Shukant Pal Avatar asked Nov 06 '22 09:11

Shukant Pal


1 Answers

There are plugins for that: Alorel/rollup-plugin-web-worker, darionco/rollup-plugin-web-worker-loader

..but I ended up doing it by scratch, using a separate build configuration for the worker(s). This simply gives me more control over the situation.

Attached is the rollup.config.worker.js that I use.

The main rollup.config.mjs imports this file, has its configuration as the first build configuration. The real build config uses @rollup/plugin-replace to inject the worker's hash to the code loading it.

/*
* Rollup config for building web worker(s)
*
* Imported by the main rollup config.
*/
import sizes from '@atomico/rollup-plugin-sizes'
import resolve from '@rollup/plugin-node-resolve'
import replace from '@rollup/plugin-replace'
import { terser } from 'rollup-plugin-terser'

import {dirname} from 'path'
import {fileURLToPath} from 'url'

const myPath = dirname(fileURLToPath(import.meta.url));

const watch = process.env.ROLLUP_WATCH;

const REGION = process.env.REGION;
if (!REGION) throw new Error("'REGION' env.var. not provided");

let loggingAdapterProxyHash;

const catchHashPlugin = {
  name: 'my-plugin',

  // Below, one can define hooks for various stages of the build.
  //
  generateBundle(_ /*options*/, bundle) {
    Object.keys(bundle).forEach( fileName => {
      // filename: "proxy.worker-520aaa52.js"
      //
      const [_,c1] = fileName.match(/^proxy.worker-([a-f0-9]+)\.js$/) || [];
      if (c1) {
        loggingAdapterProxyHash = c1;
        return;
      }
      console.warn("Unexpected bundle generated:", fileName);
    });
  }
};

const pluginsWorker = [
  resolve({
    mainFields: ["esm2017", "module"],
    modulesOnly: true       // "inspect resolved files to assert that they are ES2015 modules"
  }),
  replace({
    'env.REGION': JSON.stringify(REGION),
    //
    preventAssignment: true   // to mitigate a console warning (Rollup 2.44.0); remove with 2.45?
  }),
  //!watch && terser(),
  catchHashPlugin,

  !watch && sizes(),
];

const configWorker = {
  input: './adapters/logging/proxy.worker.js',
  output: {
    dir: myPath + '/out/worker',   // under which 'proxy.worker-{hash}.js' (including imports, tree-shaken-not-stirred)
    format: 'es',   // "required"
    entryFileNames: '[name]-[hash].js',   // .."chunks created from entry points"; default is: '[name].js'

    sourcemap: true,   // have source map even for production
  },

  plugins: pluginsWorker
}

export default configWorker;
export { loggingAdapterProxyHash }

Using in main config:

  replace({
    'env.PROXY_WORKER_HASH': () => {
      const hash= loggingAdapterProxyHash;
      assert(hash, "Worker hash not available, yet!");
      return JSON.stringify(hash);
    },
    //
    preventAssignment: true   // to mitigate a console warning (Rollup 2.44.0); remove with 2.45?
  }),

..and in the Worker-loading code:

const PROXY_WORKER_HASH = env.PROXY_WORKER_HASH;    // injected by Rollup build

...
new Worker(`/workers/proxy.worker-${PROXY_WORKER_HASH}.js?...`);

If anyone wants to get a link to the whole repo, leave a message and I'll post it there. It's still in flux.


Edit:

After writing the answer I came across this: Building module web workers for cross browser compatibility with rollup (blog, Jul 2020)

TL;DR If you wish to use EcmaScript Modules for the worker, watch out! Firefox and Safari don't have the support, as of today. source And the Worker constructor needs to be told that the worker source is ESM.

like image 134
akauppi Avatar answered Nov 14 '22 21:11

akauppi