Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Hot reload for spring boot backend + frontend

I rode those docs about spring dev tools and hot reload

https://docs.spring.io/spring-boot/docs/current/reference/html/howto-hotswapping.html https://docs.spring.io/spring-boot/docs/current/reference/html/using-boot-devtools.html#using-boot-devtools-restart-exclude

And according to that is see there is such posibility to hot reload java and backed React/Typescript/webpack app

This is our architecture (in shortcut)

mainmodule
    backendModules <- those modules are just maven project have theirs poms and etc
          backendModule1
          backendModule2
fontendModule
       content <- React/Typescript/Webpack/Less etc

backendModule2 - We are using to start backend backendModule1 - is just some additional services fontendModule/content - is our whole react app

Am I right if I say to have reload for our frontend files I have to:

  1. Configure Intellij as I'm doing it normally for backend <- this is easy

    1a. Change registry

    1b. select build project automatically

My question is what I have to do to force reloading of frontend files - So developer just need to run 1 app and then backend + frontend will be reloaded automaticlly

  1. Add frontend extensions to resource patterns(Intellij: Build, execution -> compiler)? jsx, json, js, less and etc?

  2. According to doc add "spring.devtools.restart.additional-paths"

Did anyone was able to do that? We are not getting any errors and etc...

Let me know if something seems to be unclear so we can clarify that

like image 581
Oskar Woźniak Avatar asked Jan 28 '23 06:01

Oskar Woźniak


1 Answers

I know of 2 different ways that work to do that.

1) Using Intellij File Watcher Plugin

2) Running webpack dev server as a reverse proxy for your spring-boot app


1)

  • Install Plugin "File Watchers" from Repositories
  • Go to Settings - Tools - File Watchers
  • Define a Webpack Task
    • File Types: Any
    • Define a new Scope to watch only your javascript files
    • Program: Either run webpack from /node_modules/webpack/bin/webpack.js or create some other executable file (like $ProjectFileDir$/webpack.sh)
    • If you choose to run webpack from node_modules (which btw didnt work for me because of relative path issues) insert Arguments (like --config)
    • Set output path to refresh: $ProjectFileDir$/src/main/static/js/bundled
    • Set working directory to: $ProjectFileDir$

Now if you save a file in your defined scope the webpack task will run. After that you have to refresh the page in your browser. Got the idea from https://intellij-support.jetbrains.com


2) This way is more advanced, but harder to configure correctly. You will have auto-reload (the page refreshes) and full hot reloading (the react state persists)

The basic idea is to run a webpack-dev-server and use that server as a reverse proxy for your spring-boot backend. The dev-server will handle requests to hot-reloaded content himself and pass everything else to your backend. Got the idea from https://www.codingbismuth.com/ .

As a example configuration

{
  "name": "",
  "version": "0.0.1",
  "description": "",
  "repository": {
    "type": "git",
    "url": ""
  },
  "keywords": [
    "xy"
  ],
  "author": "murphy",
  "license": "",
  "bugs": {
    "url": ""
  },
  "scripts": {
    "start:dev": "webpack-dev-server --config webpack.dev_server.js"
  },
  "homepage": "",
  "dependencies": {
    "file-saver": "^1.3.3",
    "prop-types": "^15.5.10",
    "react": "^16.2.0",
    "react-bootstrap-typeahead": "^2.3.0",
    "react-dom": "^16.2.0",
    "react-modal": "^3.1.8",
    "react-router": "^4.2.0",
    "react-router-dom": "^4.2.2",
    "react-datetime": "^2.11.1",
    "rest": "^1.3.1",
    "moment": "^2.20.1",
    "webpack": "^3.10.0",
    "swagger-ui": "^3.13.4",
    "webpack-dev-server": "^2.11.2"
  },
  "devDependencies": {
    "webpack-cli": "^2.0.15",
    "react-hot-loader": "^4.1.2",
    "babel-core": "^6.18.2",
    "babel-eslint": "^8.0.3",
    "babel-loader": "^7.1.2",
    "babel-polyfill": "^6.26.0",
    "babel-preset-es2015": "^6.18.0",
    "babel-preset-react": "^6.16.0",
    "eslint": "^4.13.1",
    "eslint-plugin-react": "^7.5.1",
    "eslint-loader": "^1.9.0",
    "eslint-watch": "^3.1.3",
    "eslint-config-airbnb": "^16.1.0",
    "eslint-plugin-jsx-a11y": "^6.0.2",
    "eslint-plugin-import": "^2.8.0",
    "eslint-plugin-flowtype": "^2.40.1",
    "uglifyjs-webpack-plugin2": "^1.0.3"
  }
}

const { resolve } = require('path');
const path = require('path');
const webpack = require('webpack');

module.exports = {
    context: resolve(__dirname, '.'),

    entry: [
        'react-hot-loader/patch',
        // activate HMR for React

        'webpack-dev-server/client?http://localhost:8888',
        // bundle the client for webpack-dev-server
        // and connect to the provided endpoint

        'webpack/hot/only-dev-server',
        // bundle the client for hot reloading
        // only- means to only hot reload for successful updates

        // the entry point of our app
        './src/main/js/router/mainrouter.jsx',
    ],
    output: {
        filename: './mainbundle.js',
        // the output bundle

        path: resolve(__dirname, '/src/main/resources/static/js/bundled'),

        publicPath: '/js/bundled/',
        // necessary for HMR to know where to load the hot update chunks
    },

    devtool: 'sourcemaps',
    devServer: {
        hot: true,
        contentBase: [resolve(__dirname, "."), resolve(__dirname, "./src/main/resources/static/js/bundled")],
        proxy: {
            "/": {
                target: {
                    host: "localhost",
                    protocol: 'http:',
                    port: 8087,
                },
            },
            ignorePath: true,
            changeOrigin: true,
            secure: false,
        },
        publicPath: '/js/bundled/',
        port: 8888,
        host: "localhost",
    },
    module: {
        rules: [
            {
                enforce: "pre",
                test: /\.jsx$/,
                exclude: /node_modules/,
                loader: "eslint-loader",
                options: {
                    // fix: true, // autofix
                    cache: true,
                    failOnError: false,
                    emitWarning: true,
                    quiet: true,
                },
            },
            {
                test: path.join(__dirname, '.'),
                exclude: /node_modules/,
                loader: "babel-loader",
                query: {
                    // cacheDirectory: true,
                    presets: ['es2015', 'react'],
                },
            },
            {
                test: /\.css$/,
                use: ['style-loader', 'css-loader?modules'],
            },
        ],
    },

    plugins: [
        new webpack.HotModuleReplacementPlugin(),
        // enable HMR globally

        new webpack.NamedModulesPlugin(),
        // prints more readable module names in the browser console on HMR updates
    ],
};

The spring boot app is running on :8087 and the webpack dev server on :8888. Now in your index.html you include the mainbundle.js. Run your spring-boot app and in a second terminal run:

npm run start:dev

Access the webpage on :8888 to have hot reloading on file changes.

like image 120
murphy1312 Avatar answered Jan 30 '23 20:01

murphy1312