webpack-server-runner-plugin

0.0.7 • Public • Published

webpack-server-runner-plugin

Development server runner for full stack Webpack development

  • Run your server code and serve your client code on webpack --watch
  • Keep hot module replacement (HMR) connection between server and browser on both client update and server update
  • Compatible with Express, Koa, Connect and all other libraries that export a listener for http.createServer()
  • No HMR acceptance code needed in your server or client code

Install

npm install --save-dev webpack-server-runner-plugin

Usage

This plugin is intended for development use only.

In your server entry file

Your server entry file should export the listener function for http.createServer. This is compatible with Express 4's Application generator, where bin/www imports this listener function from app.js. So in Express you should do

const app = express();
...
module.exports = app;

In webpack.config.js

This section describes how to update your development Webpack config to be compatible with ServerRunnerPlugin. You should use a multiple configuration setup for your server and client code:

// in webpack.config.js
const server = { /* server webpack config object */ };
const client = { /* client webpack config object */ };
module.exports = [server, client];

You can add more named clients.

Follow these steps to make your webpack config compatible with ServerRunnerPlugin:

For the server config, external node_modules should be excluded from the build. You could e.g. use webpack-node-externals for this. Import both libraries:

const path = require('path');
const nodeExternals = require('webpack-node-externals');
const ServerRunnerPlugin = require('webpack-server-runner-plugin');

Initialize ServerRunnerPlugin with optional port:

const serverRunner = new ServerRunnerPlugin({
    port: 3000, // default
    ssl: { // optional for local https server instead of http
        cert: ...,
        key: ...
    }
});

Configuration objects can have a name for better debug output. The server config must have target: 'node', which makes the Webpack output compatible with Node.js. A context is also required.

const server = {
    name: 'server',
    target: 'node',
    context: __dirname,

server.entry must be an array containing a Webpack hot client and your server entry file:

    entry: [
        'webpack/hot/poll?1000',
        './server'
    ],

Use your favorite setup to make sure external node_modules - except for the webpack hot client of your choice - are not included in the build, e.g.:

    externals: nodeExternals({
        whitelist: ['webpack/hot/poll?1000']
    }),

Output libraryTarget must be set and can be either 'commonjs2' or 'commonjs':

    output: {
        path: path.join(__dirname, 'dist/server'),
        filename: 'index.js',
        libraryTarget: 'commonjs2'
    },

Apart from our newly created serverRunner, we also have to include the HMR plugin, and NoErrorsPlugin to not interrupt the HMR with broken builds:

    plugins: [
        new webpack.HotModuleReplacementPlugin(),
        new webpack.NoErrorsPlugin(),
        serverRunner
    ]
};

Our client configuration object should include webpack-hot-middleware with dynamicPublicPath for each (named or unnamed) entry:

const client = {
    name: 'client',
    entry: [
        'webpack-hot-middleware/client?dynamicPublicPath=true',
        './client'
    ],
    context: __dirname,

For named entries, this will look like

const hotMiddleware = 'webpack-hot-middleware/client?dynamicPublicPath=true';
const client = {
    entry: {
        a: [hotMiddleware, './a'],
        b: [hotMiddleware, './b']
    },

ServerRunnerPlugin will serve the contents of output.path on the path component of publicPath. (You cannot use '/' because then it will override your server routes.)

    output: {
        path: path.join(__dirname, 'dist/client'),
        publicPath: '/static/'
    },

Finally, include a new serverRunner.StaticFilesPlugin() and the necessary HMR modules:

    plugins: [
        new webpack.HotModuleReplacementPlugin(),
        new webpack.NoErrorsPlugin(),
        new serverRunner.StaticFilesPlugin()
    ]
};

module.exports = [server, client];

Please file an issue or create a pull request on GitHub if you had to take additional steps to make your setup working.

Run

Now you can build, watch, run your server and serve your client with one command:

webpack --watch

Please note that this plugin enhences the webpack command, not webpack-dev-server. webpack-dev-server only serves client content.

Tips for Node-targeted Webpack configuration

These settings are not essential for ServerRunnerPlugin.

Source maps

In your server config, setting a devtool is not enough to get correct source line numbers in stack traces. You can achieve this by using this plugin (just like this entire configuration, you should only use this for development):

npm install --save-dev source-map-support
const server = {
    ...,
    plugins: [
        ...,
        new webpack.BannerPlugin(
            'require("source-map-support").install();', {
            raw: true,
            entryOnly: false
        })
    ]
};

Using __dirname in source files

In your server code, you can use __dirname relative to a source file with this option:

const server = {
    ...,
    node: {
        __dirname: true
    }
};

Webpack configurations for multiple environments

There are multiple ways to combine the common parts of webpack configurations for different environments (e.g. development, test, acceptation and production). There are NPM packages that make inheritance of configuration objects more easy by providing features for combining arrays. However, you can easily define all configurations in one place without a library, using boolean variables for all environments and [].filter(Boolean) to remove all falsy values from the resulting arrays:

    plugins: [
        new webpack.DefinePlugin(globals),
        new webpack.optimize.OccurenceOrderPlugin(),
        (TEST || DEV) && new webpack.BannerPlugin(
            'require("source-map-support").install();', {
            raw: true,
            entryOnly: false
        }),
        DEV && new webpack.HotModuleReplacementPlugin(),
        DEV && new webpack.NoErrorsPlugin(),
        DEV && serverRunner
    ].filter(Boolean),
    devtool: (TEST || DEV) ? 'source-map' : undefined

How it works

ServerRunnerPlugin runs your server code via a require() statement to the output file. It modifies your entry module like DllPlugin does: it exports the internal require function and the entry module object to be able to update the server after an HMR module reload of the main module.

You can optionally add more module.hot.accept() calls in your server code for other modules. If you don't, all HMR replacements will bubble up to your main module, which will then be accepted by ServerRunnerPlugin.

Internally, ServerRunnerPlugin dispatches every incoming request between your client files and server routes based on the output.publicPath setting in your Webpack client config. The client files are served by an express app.

You can even attach more StaticFilesPlugins (with a different output.publicPath) if you have more than one client config.

License

MIT

Readme

Keywords

none

Package Sidebar

Install

npm i webpack-server-runner-plugin

Weekly Downloads

1

Version

0.0.7

License

MIT

Last publish

Collaborators

  • maartenth