Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Webpack bundle Node Express with hot reloading = hell

I hate to admit it, but I've been spending three long evenings trying to do - what I thought would be straightforward thing to do. I finally reached the stage where I'm fed up with it, and frankly rather frustrated because "it just won't work".

Here is what I try to achieve:

  1. Bundle my Express server with Webpack (although my current code just renders a string in the browser, it is supposed to compile server rendered React components compiled with Babel)
  2. Save the bundle in memory (or on disk if there is no other way)
  3. Run webpack / dev / hot middleware to serve my Node Express app in a way that changes to the server rendered pages (which will be React components) will auto-update in the browser.

I've tried numerous combinations, tutorials that have been deprecated, npm packages that are no longer maintained and downloaded examples that just don't work.

Here is my current setup:

webpack.server.config.js:

const path = require('path');
const webpack = require('webpack');
const nodeExternals = require('webpack-node-externals');

module.exports = {
    name: 'server',
    mode: 'development',
    target: 'node',
    externals: nodeExternals(),
    entry: [ './src/server/index' ],
    output: {
        path: path.resolve(__dirname, 'dist'),
        // path: "/",
        filename: '[name].js',
        publicPath: '/assets/',
        libraryTarget: 'commonjs2'
    },
    plugins: [
        new webpack.HotModuleReplacementPlugin()
    ],
    module: {
        rules: [
            {
                test: /.js$/,
                loader: 'babel-loader',
                include: path.resolve(__dirname, 'src/'),
                exclude: /node_modules/,
                options: {
                    presets:
                        [['@babel/preset-env', { modules: 'false' }], '@babel/preset-react'],
                    plugins: [
                        ['@babel/plugin-proposal-object-rest-spread', { useBuiltIns: true }],
                        '@babel/plugin-proposal-class-properties'
                    ]
                }
            },
            {
                test: /\.scss$/,
                loader: 'ignore-loader'
            },
            {
                test: /\.css$/,
                loader: 'ignore-loader'
            },
            {
                test: /\.(jpg|png|svg|gif|pdf)$/,
                loader: 'file-loader',
                options: {
                    name: '[path][name].[ext]'
                }
            }
        ]
    }
};

index.js:

import http from 'http';
import fs from "fs";
import express from "express";
import favicon from 'serve-favicon';
// import renderer from "./renderer";
import renderApp from './welcome';


const app = express();

app.use(favicon('./public/favicon.ico'));
app.use(express.static("public"));


if (process.env.NODE_ENV !== 'production') {

    const webpack = require('webpack');
    const webpackDevMiddleware = require('webpack-dev-middleware');
    const webpackHotMiddleware = require('webpack-hot-middleware');
    const serverConfig = require('../../webpack.server.config');

    const compiler = webpack(serverConfig);

    app.use(webpackDevMiddleware(compiler, {
        stats: {colors: true},
        headers: { "Access-Control-Allow-Origin": "http://localhost"},
        publicPath: serverConfig.output.publicPath
    }));

    app.use(require("webpack-hot-middleware")(compiler));

}

app.get("*", function(req, res) {
    fs.readFile("./src/server/html/index.html", "utf8", function(err, data) {
        const context = {};
        const html = renderApp();
        //const html = renderer(data, req.path, context);

        res.set('content-type', 'text/html');
        res.send(html);
        res.end();
    });
});

const PORT = process.env.PORT || 8080;

app.listen(3000);

Frankly I'm also rather confused about how this is supposed to work.
Are the following steps supposed to be executed?:

  • webpack webpack.server.config.js --watch
  • node dist/server.js // webpack output folder

Would this magically hot reload my server?

All help is welcome, or if you happened to have a working demo.
I just couldn't manage to make this work.

In the end I will also hot reload (re-render) my client bundle but I guess that will be the easy part as I've seen many, many resources about that.

like image 762
html_programmer Avatar asked May 21 '19 22:05

html_programmer


1 Answers

Night sleep was probably needed.
I got this working (incl with React server rendered components) using StartServerPlugin.
Following setup hot reloads the Node Express server:

const path = require('path');
const webpack = require('webpack');
const nodeExternals = require('webpack-node-externals');
const StartServerPlugin = require('start-server-webpack-plugin');

module.exports = {
    name: 'server',
    mode: 'development',
    target: 'node',
    externals: nodeExternals({
        whitelist: ['webpack/hot/poll?1000']
    }),
    entry: [ 'webpack/hot/poll?1000', './src/server/index' ],
    output: {
        path: path.resolve(__dirname, 'dist'),
        // path: "/",
        filename: 'server.js',
        publicPath: '/assets/',
        libraryTarget: 'commonjs2'
    },
    plugins: [
        new StartServerPlugin({'name': 'server.js', nodeArgs: ['--inspect']}),
        new webpack.NamedModulesPlugin(),
        new webpack.HotModuleReplacementPlugin(),
        new webpack.DefinePlugin({
            "process.env": {
                "BUILD_TARGET": JSON.stringify('server')
            }
        })
    ],
    module: {
        rules: [
            {
                test: /.js$/,
                loader: 'babel-loader',
                include: path.resolve(__dirname, 'src/'),
                exclude: /node_modules/,
                options: {
                    presets:
                        [['@babel/preset-env', { modules: 'false' }], '@babel/preset-react'],
                    plugins: [
                        ['@babel/plugin-proposal-object-rest-spread', { useBuiltIns: true }],
                        '@babel/plugin-proposal-class-properties'
                    ]
                }
            },
            {
                test: /\.scss$/,
                loader: 'ignore-loader'
            },
            {
                test: /\.css$/,
                loader: 'ignore-loader'
            },
            {
                test: /\.(jpg|png|svg|gif|pdf)$/,
                loader: 'file-loader',
                options: {
                    name: '[path][name].[ext]'
                }
            }
        ]
    }
};

index.js:

import http from 'http'
import app from './server'

const server = http.createServer(app)
let currentApp = app;

const PORT = process.env.PORT || 8080;

server.listen(PORT);

if (module.hot) {
    module.hot.accept('./server', () => {
        server.removeListener('request', currentApp);
        server.on('request', app);
        currentApp = app;
    })
}

server.js:

import http from 'http';
import fs from "fs";
import express from "express";
import favicon from 'serve-favicon';
import renderer from "./renderer";
import renderApp from './welcome';


const app = express();

app.use(favicon('./public/favicon.ico'));
app.use(express.static("public"));


app.get("*", function(req, res) {
    fs.readFile("./src/server/html/index.html", "utf8", function(err, data) {
        const context = {};
        //const html = renderApp();
        console.log('test');
        const html = renderer(data, req.path, context);
        res.set('content-type', 'text/html');
        res.send(html);
        res.end();
    });
});

export default app;

Run with:

rm -rf ./dist && webpack --config webpack.server.config.js --watch
like image 127
html_programmer Avatar answered Nov 11 '22 19:11

html_programmer