Skip to content

Instantly share code, notes, and snippets.

@blakef
Last active March 13, 2017 20:26
Show Gist options
  • Save blakef/754aeb2747ee269db38992fe4f5e6c05 to your computer and use it in GitHub Desktop.
Save blakef/754aeb2747ee269db38992fe4f5e6c05 to your computer and use it in GitHub Desktop.
Isomorphic Webpack Applications

Isomorphic Webpack Applications

Applications that can run on the client and server side, which for our purposes this means in both the Node and browser environment.

So, how do we do this and how do we use webpack?

Dependencies

Lets install some dependencies:

$ npm i webpack@beta babel-loader babel-preset-es2015 -D

You'll want webpack 2, since it hadles es6 code and modules more gracefully.

Webpack

Lets start with a basic webpack config and some module shimming based on the execution environment. First we want to target umd.

module.exports = {
    entry: './src/index.js',
    output: {
        path: path.join(__dirname, 'dist'),
        filename: 'demo.js',
        libraryTarget: 'umd'
    },
    externals: [{
        WebSocket: {
            commonjs: 'WebSocket',
            commonjs2: 'ws',
            amd: 'WebSocket',
            root: 'WebSocket'
        }
    }],
    module: {
        loaders: [
            { test: /\.js$/, loader: 'babel?presets[]=es2015', exclude: [/node_modules/] }
        ]
    }
};

Now lets add the sample application ./dist/index.js:

import WebSocket from "WebSocket";

const ws = new WebSocket('ws://echo.websocket.org');

ws.addEventListener('message', msg => {
    console.log(`Received a message: ${msg.data}`);
    ws.close();
});

ws.addEventListener('open', () => {
    ws.send('Hello, World!');
});

We then build the application under webpack:

$ webpack webpack.config.js

Running

Lets test it in node:

$ node
> require('./dist/demo')
{}
> Received a message: Hello, World!
>

To test in the browser, we're going to use webpack-dev-server:

$ npm i webpack-dev-server -S

And modify our config to include:

module.export = {
    ...
    output: {
        path: path.resolve(__dirname, 'dist'),
        filename: 'demo.js'
    },
    ...
};

Along with ./dist/index.html to load our bundle:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>IsoDemo</title>
</head>
    <body>
      <script src="main.js"></script>
    </body>
</html>

And run our example

$ webpack-dev-server --content-base dist/

Open your browser and point to the server (default is http://localhost:8080). If you open up a console, you should see the expect output:

Received a message: Hello, World!

This is great, we've build a library which will open a WebSocket whether it's run from node or the browser.

WebWorkers

Lets do something a bit more involved, lets see if we can create an isomorphic webworker. Unfortunately the above pattern won't work if we're using a loader, as we're unable to modify the worker-loader, which directly references Worker:

...
var constructor = "new Worker(__webpack_public_path__ + " + JSON.stringify(workerFile) + ")";
...

A more robust method it to write another loader which shims to the correct Worker. We create a shim-loader.js in the ./src directory:

shim-loader.js

module.exports = function(source) {
    var output = "/** WebWorker Shimming for Isomorphic Applications */\r\n" +
        "var Worker = require('WebWorker');\r\n" +
        source;
    this.callback(null, output);
}

This references a WebWorker module, which we'll have to now use the same process as before to shim in a node module. Using something similar to the previous example, lets shim in the npm module webworker-threads when we're in Node:

webpack.config.js

module.exports = {
    ...
    externals: [
        externals: [{
            WebWorker: {
                commonjs2: ['webworker-threads', 'Worker'],
                commonjs: 'Worker',
                amd: 'Worker',
                root: 'Worker'
            }
        }],
    ]
    ...
};

Now that webpack is doing what we want, lets complete the webworker example:

demo.js

self.onmessage = function onmessage(msg) {
    self.postMessage(msg.data.toUpperCase());
    self.close();
};

And modify index.js:

import worker from './shim-loader!worker?name=demo.worker.js!./demo';

let w = worker();
w.onmessage = function onmessage(msg) {
    console.log(`Received a message: ${msg.data}`);
};
w.onclose = function() {
    console.log('Worker has terminated');
};

w.send('Hello, World!');

Lets install our new node dependencies:

$ npm i worker-loader -D
$ npm i webworker-threads -S

Running our bundle in either environment now renders a similar result:

$ webpack
Hash: 3dce2e18a4d438f13f21
Version: webpack 2.1.0-beta.14
Time: 873ms
         Asset     Size  Chunks             Chunk Names
demo.worker.js  2.16 kB          [emitted]
       main.js  3.38 kB       0  [emitted]  main
    + 3 hidden modules
Child worker:
             Asset     Size  Chunks             Chunk Names
    demo.worker.js  2.16 kB       0  [emitted]  main
        + 1 hidden modules
$ cd dist
$ node
> require('./main')
{}
> Received a message: HELLO, WORLD!

Success.

@rexmondo
Copy link

Hi,

I have followed this gist because it is a good example of shimming webworkers into node, which I require for a project, but for some reason at the final step it keeps telling me that send is not a function. Is it possible to get the version numbers of the packages you are using so I can see if it is updates that have broken it?

Thanks,
Ben

@rexmondo
Copy link

Okay, figured it out. send is actually not a method, the method needed is postMessage

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment