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?
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.
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
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.
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:
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:
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:
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.
Okay, figured it out.
send
is actually not a method, the method needed ispostMessage