Webpack hot reload using webpack dev server and rails server

Viewed 7173

My current application is set up using Ruby on Rails and React/Typescript. I am trying to set up hot reloading.

Here is the current folder structure

Project Root
  - app => all the rails code
  - frontend => all the react code
  - webpack => list of configuration files, like development.js and production.js

This project isn't using react_on_rails or webpacker. The frontend code is kept separate from the backend code. The Rails backend serves up an html

<div id='root' />

and the react code will run off of that.

This is the command I tried to run to get hot reloading to work

node_modules/.bin/webpack-dev-server --config=./webpack/development.js  --hotOnly --entry=../frontend/Entry.tsx --allowedHosts=localhost:3000

However, not only is hot reloading not working, the changes I made are not showing up in the browser as well. Everything looks like in the terminal.

My issue here is I technically have two servers running at the same time.

localhost:3000 => Rails server
localhost:8080 => Webpack dev server. 

If I change webpack server to point to 3000 as well, the rails app will not work properly.

Is there a way where I can get hot reloading to work using this setup?

here are the webpack version

"webpack": "^4.20.1",
"webpack-cli": "^3.1.1",
"webpack-dev-server": "^3.7.1" 

webpack.development.config.js

const webpack = require('webpack');
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin');
const CaseSensitivePathsPlugin = require('case-sensitive-paths-webpack-plugin');

module.exports = {
  context: __dirname,
  entry: '../frontend/Entry.tsx',
  devtool: 'source-maps',
  resolve: {
    extensions: ['*', '.js', '.jsx', '.ts', '.tsx'],
    modules: [
      'node_modules',
      path.resolve(__dirname, '../frontend'),
      path.resolve(__dirname, '../node_modules')
    ]
  },
  output: {
    path: path.join(__dirname, `../public/javascripts/`),
    publicPath: `/javascripts/`,
    filename: '[name]-[hash].js'
  },
  module: {
    rules: [
      {
        test: /\.(t|j)sx?$/,
        loader: 'ts-loader',
        options: {
          // disable type checker - we will use it in fork plugin
          transpileOnly: true
        }
      },
      {
        enforce: 'pre',
        test: /\.(t|j)sx?$/,
        loader: 'source-map-loader'
      },
      {
        test: /\.css$/,
        use: ['style-loader', 'css-loader']
      },
      {
        test: /\.scss$/,
        use: ['style-loader', 'css-loader', 'sass-loader']
      },
      {
        test: /\.(png|svg|jpg|gif)$/,
        use: [
          {
            loader: 'file-loader',
            options: {
              name: '[name]-[hash].[ext]',
              outputPath: 'images/'
            }
          },
          {
            loader: 'image-webpack-loader',
            options: {
              pngquant: {
                quality: '40',
                speed: 4
              }
            }
          }
        ]
      }
    ]
  },
  plugins: [
    new webpack.DefinePlugin({
      'process.env': {
        NODE_ENV: JSON.stringify('development')
      }
    }),
    new HtmlWebpackPlugin({
      template: path.join(__dirname, '..', 'application.html'),
      filename: path.join(__dirname, '..', 'app', 'views', 'layouts', '_javascript.html.erb')
    }),
    // runs typescript type checker on a separate process.
    new ForkTsCheckerWebpackPlugin({
      checkSyntacticErrors: true,
      tsconfig: '../tsconfig.json'
    }),
    new CaseSensitivePathsPlugin()
  ],
  optimization: {
    splitChunks: { chunks: 'all' }
  }
};
1 Answers

Since you are setting up webpack dev server the first time, the problem is two fold,

  1. Setup webpack dev server
  2. Configure hot reload

Setting up webpack dev server

I presume your app is the api server. Similarly webpack-dev-server too is a http server. Its just a wrapper around expressjs infact.

while using webpack dev server during development, the bundles are served by webpack dev server, and all xhr requests are made to this dev server. In order to route these requests to your app server, you need to add proxy rules to your webpack config.

On a high level the flow would look as follows.

browser ---(xhr requests)-----> webpack-dev-server -----(proxy api requests)--->app server


In order to add a proxy rule to route all api request to your rails server, your api routes should be prepended with /api, eg, /api/customers so that all request matching /api are forwarded to the rails server

A sample config to support the above flow would be something as follows in your webpack config file

module.exports = {
  // ...your other configs
  devServer: {
    contentBase: path.join(__dirname, 'public/'),
    port: 8080,
    publicPath: 'http://localhost:8080/', // Path of your dev server
    historyApiFallback: true, // add this if you are not using browser router
    proxy: {
      '/api': { // string to look for proxying requests to api
        target: 'http://localhost:3000', // Path of your rails api server
      },
    },
  },
  // ...your other configs
}

Setting up Hot reload

In order to setup hot reload, I would recommend to use Dan Abramov's react-hot-loader as its less buggy in hmr patching.

Setting up hmr is easy

  1. Add the dependency yarn add react-hot-loader
  2. Add babel plugin in your .babelrc

    {
      "plugins": ["react-hot-loader/babel"]
    }
    
  3. Mark your root component as hot exported

    import { hot } from 'react-hot-loader/root'; // this should be imported before react and react-dom
    const App = () => <div>Hello World!</div>;
    export default hot(App);
    

Note: Its safe to add react-hot-loader in your dependencies, because in your production build. Hot reload package will be stripped out.

To start the webpack server in hot mode, you can add a script like below in your package.json.

"scripts": {
    "start": "webpack-dev-server --hot --mode development --config ./webpack.dev.config"
  }
Related