Search code examples
javascriptwebpackweb-workerworker-loadernpm-package

Unable to bundle a Web Worker to be imported like an NPM package


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.


Solution

  • 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
            }
        }
    },