4

My goal is to be able to publish a Web Worker NPM package which can be imported normally (import MyPkg from 'my-pkg') without requiring the user to import it with worker-loader (inline or otherwise)

To accomplish this, I've tried using a Babel build script as well as Webpack with worker-loader.

In the following examples there are two projects: the Web Worker package ("Package") which is npm linked to a test application ("App").

The Package is split into two files: entry.webpack.js and index.worker.js. The entry, when built and moved to /dist is designated as the main file in the package.json, and it currently looks like this:

entry.webpack.js

var MyPkg = require('worker-loader!./index.worker.js')
module.exports = MyPkg

index.worker.js

// This is just example code. It doesn't really matter
// what this code does so long as it ends up being run
// as a Web Worker.

var selfRef = self;

function ExampleWorker () {
  console.log('Running Worker...');
  setTimeout(function () {
    // wait 10 seconds then post a message
    selfRef.postMessage({foo: "bar"});
  }, 10000)
}

module.exports = ExampleWorker

I then bundle the Package with Webpack:

package.json

"build": "rm -rf dist/*.* && webpack --progress"

webpack.config.js

module.exports = {
  mode: 'production',
  devtool: 'source-map',
  entry: __dirname + '/src/entry.webpack.js',
  output: {
    filename: 'bundle.js',
    path: __dirname + '/dist'
  },
  optimization: {
    minimize: false
  }
}

This generates two files: bundle.js and a Web Worker file as a hash: [hash].worker.js with the code we want evaluated in it. They key part in this, though, is that because we used worker-loader inline to import, the webpack compiled output looks something like:

module.exports = function() {
  return new Worker(__webpack_require__.p + "53dc9610ebc22e0dddef.worker.js");
};

Finally, the App should be able to import it and use it like this:

App.js

import MyPkg from 'my-pkg'
// logging MyPkg here produces `{}`

const worker = new MyPkg()
// That throws an Error:
// Uncaught TypeError: _my_pkg__WEBPACK_IMPORTED_MODULE_4___default.a is not a constructor

worker.onmessage = event => {
  // this is where we'd receive our message from the web worker
}

However, you can get it to work if, in the App itself you import the worker build like this:

import MyPkg from 'my-pkg/dist/53dc9610ebc22e0dddef.worker.js'

But, it's a requirement of the package to:

A) NOT require applications using the package to have to explicitly install worker-loader and B) not have to reference the my-pkg/dist/[hash].worker.js explicitly.

I've tried also designating the built [hash].worker.js' as themain` in package.json but that doesn't work either.


Edit 1: I forgot to mention that I'm basing all of this off of how react-pdf does it. If you take a look in /src/entry.webpack.js and follow how it works throughout the package you'll see a few similarities.

adamellsworth
  • 361
  • 1
  • 3
  • 15

1 Answers1

3

you could try worker-loader with option:

{
    test: /\.worker\.js$/,
    use: {
        loader: 'worker-loader',
        options: {
            name: '[name].[hash:8].js',
            // notice here
            inline: true,
            fallback: false
        }
    }
},
Haina Ma
  • 56
  • 3
  • To add to this asnwer I also needed to add the option `publicPath: //my.url.com/` for it to work in production. In dev `publicPath` is set to `/`. – adamellsworth Jun 10 '20 at 17:50