Use Webpack to split out a module so that it can be loaded in a WebWorker

4.4k views Asked by At

I have a web app that I compile with webpack. One of the modules that my code uses is named table.js. Until recently, it's just been another module and has been compiled into my bundle.js file with everything else.

Now I need to run table.js in a Web Worker, so I need to pull it and its dependencies into a separate file that can be loaded both standalone and by my other modules.

At first I thought to include table.js in my webpack.config.js's entry.

var config = {
    ...
    entry: {
        app: [ './src/main.js', './src/classes/table.js' ],
        vendors: [],
    },
    ...
}

That didn't work. Then I thought to separate it out like my vendors bundle.

var config = {
    /* for vendors (and other modules) we have a CDN for */
    addExternal: function (name, globalVar) {
        this.externals[name] = globalVar;
        this.entry.vendors.push(name);
    },

    /* for vendors we don't have a CDN for */
    addVendor: function (name, path) {
        this.resolve.alias[name] = path;
        this.entry.vendors.push(name);
    },

    addPlugin: function (plugin) {
        this.plugins.push(plugin);
    },

    entry: {
        app: [ './src/main.js' ],
        vendors: [],
        table: [ __dirname + '/src/classes/table.js' ]
    },

    plugins: [],

    externals: { },

    output: {
        path: __dirname + '/public/dist/',
        filename: 'bundle.js',
        publicPath: '/dist/',
        sourceMapFile: '[file].map'
    },

    resolve: {
        alias: { 'table': './src/classes/table.js' },
        extensions: [ '', '.js', '.jsx' ]
    },
    ...
}

/* add vendors and externals */
...

config.addPlugin(new CommonsChunkPlugin('vendors', 'vendors.js'));
config.addPlugin(new CommonsChunkPlugin('table', 'table.js'));

This seems to pull Table and its dependencies into a chunk of bundle.js, 1.bundle.js. Unfortunately, then calling import Table from 'table' causes this error:

ERROR in CommonsChunkPlugin: While running in normal mode it's not allowed to use a non-entry chunk (table)

I also have a circular dependency between TableStore and Table. TableStore needs to stay in bundle.js because it shouldn't be loaded into the Web Worker. Previously, when I've needed to throw things into a separate chunk, I've done:

if (someThingNeedsRequiring) {
    require.ensure([], () => {
        require('something');
    }
}

With the circular dependency, this doesn't seem to work.

/* table.js */
let _inWebWorker = self instanceof Window,
    TableStore = null;

if (!_inWebWorker) {
    require.ensure([], function() { TableStore = require('../stores/table-store'); } );
}

/* table-store.js */
import Table from 'table';

Could someone set me straight on the correct way to have my webpack.config.js and how to use my imports in my module files?

2

There are 2 answers

0
dx_over_dt On BEST ANSWER

(It's been quite a while since I figured this out, and I haven't touched the project in nearly six months, so I may have missed some of the details. Comment if it's not working, and I'll try to figure out what I'm missing.)

webpack.config

It turns out there are two handy-dandy JavaScript packages for doing what I want: worker-loader and workerjs.

npm install --save workerjs worker-loader

I added this in my webpack.config.js:

var config = {
    // ...

    worker: {
        output: {
            filename: '[name].worker.js',
            chunkFilename: '[name].worker.js'
        }
    },

    // ...
}

require()

In order to specify that I want my class to be run in a WebWorker file, my require looks like:

// ecmaScript 6
import TableWorker from 'worker?name=tableRoller!path/to/table';

// ecmaScript 5
var TableWorker = require('worker?name=tableRoller!path/to/table');

TableWorker is just a variable name I used for table.js's export default class Table {...}. The name=tableRoller specifies the generated outputted [name].worker.js filename. For example, I have another WebWorker named distCalc.worker.js, so my import looks like:

import DistWorker from 'worker?name=distCalc!path/to/distWorker';

Note that in this case, distWorker only ever runs in a WebWorker, while Table is used in both my main.js entry point and my tableRoller.worker.js WebWorker file.

workerjs and worker-loader generate a new entry point file and pull in all of the dependencies of those classes. Tobias Koppers (worker-loader) and Eugene Ware (workerjs) are geniuses.

Detecting WebWorker

My _inWebWorker detection is:

let _inWebWorker = typeof Window === 'undefined';
0
n0m4dz On

Change output filename in your webpack.config.js file

output: {
    path: __dirname + '/public/dist/',
    filename: '[name].js',
    publicPath: '/dist/',
    sourceMapFile: '[file].map'
},

then Webpack can separate your entries with its name in dist directory.