Using Webpack with Spring Boot
Tags: springboot, node, frontend, webpack
Published: 2023-05-02 11:36:27

Webpack is a powerful tool for frontend development. It allows us to improve the structure of our JavaScript/CSS/SCSS code and to prepare it for production usage. How can we **integrate Webpack into our Spring Boot application**?

Spoiler: in Bootify's Free plan, Spring Boot applications can be initialized with their custom database schema and frontend stack. **This includes Webpack with Bootstrap or Tailwind CSS for the frontend**, so you get the whole following configuration including DevServer and build integration exactly matching your individual setup. [Open your project](https://bootify.io/) - without registration directly in the browser.

## Prepare Node.js

The starting point for working with Webpack is Node.js, which uses npm to manage the external dependencies for the frontend. For this we first need to create the `package.json` in the top folder of our Spring Boot project.

```json
{
  "name": "my-app-frontend",
  "author": "Bootify.io",
  "scripts": {
    "devserver": "webpack serve --mode development",
    "build": "webpack --mode production"
  },
  "dependencies": {
    "@popperjs/core": "^2.11.7",
    "bootstrap": "^5.2.3"
  },
  "devDependencies": {
    "@babel/core": "^7.21.4",
    "@babel/preset-env": "^7.21.4",
    "autoprefixer": "10.4.14",
    "babel-loader": "^9.1.2",
    "css-loader": "^6.7.3",
    "css-minimizer-webpack-plugin": "^5.0.0",
    "mini-css-extract-plugin": "^2.7.5",
    "postcss-loader": "^7.2.4",
    "postcss-preset-env": "^8.3.2",
    "sass": "^1.62.0",
    "sass-loader": "^13.2.2",
    "style-loader": "^3.3.2",
    "warnings-to-errors-webpack-plugin": "^2.3.0",
    "webpack": "^5.80.0",
    "webpack-cli": "^5.0.1",
    "webpack-dev-server": "^4.13.3"
  }
}
```
_&nbsp; Our package.json with Webpack and Bootstrap dependencies_

In the `scripts` section we can define our own commands that can be executed with `npm run <script-name>`. Here we are already **preparing to start the DevServer we will use during development, as well as building our JS/CSS files** for their usage in the final jar of our Spring Boot app. The background on this follows later on.

In the `dependencies` section are the libraries that will be available in the browser in the actually delivered frontend. In our example we include Bootstrap in the current version 5.2.3 and the required Popper. In `devDependencies` are the **packages that are relevant for development** and will not be available in the browser. Especially Webpack is included here as well as other dependencies for processing our JS/CSS files.

After the `package.json` is created and Node.js is installed on the system, we can initialize the project once with `npm install`. All defined dependencies will be automatically downloaded to the `node_modules` folder. New required libraries can be added later on with `npm install <package-name>`. The `--save-dev` parameter would put them in the `devDependencies` area.

##  Configure Webpack

The central file for managing Webpack is `webpack.config.js`.

```js
module.exports = (env, argv) => ({
  entry: './src/main/resources/js/app.js',
  output: {
    path: path.resolve(__dirname, './target/classes/static'),
    filename: 'js/bundle.js'
  },
  devtool: argv.mode === 'production' ? false : 'eval-source-map',
  optimization: {
    minimize: true,
    minimizer: [
      new TerserPlugin(),
      new CssMinimizerPlugin()
    ]
  },
  plugins: [
    new MiniCssExtractPlugin({
      filename: "css/bundle.css"
    }),
    new WarningsToErrorsPlugin()
  ],
  module: {
    rules: [
      {
        test: /\.js$'/,
        include: path.resolve(__dirname, './src/main/resources/js'),
        use: {
          loader: 'babel-loader',
          options: {
            presets: ['@babel/preset-env'],
          },
        },
      },
      {
        test: /\.scss$/,
        include: path.resolve(__dirname, './src/main/resources/scss'),
        use: [
          argv.mode === 'production' ? MiniCssExtractPlugin.loader : 'style-loader',
          {
            loader: 'css-loader',
            options: {
              importLoaders: 1,
              sourceMap: true
            }
          },
          {
            loader: 'postcss-loader',
            options: {
              postcssOptions: {
                plugins: [
                  require('autoprefixer'),
                ]
              },
              sourceMap: true
            }
          },
          {
            loader: 'sass-loader',
            options: { sourceMap: true }
          }
        ]
      }
    ]
  },
  resolve: {
    modules: [
      path.resolve(__dirname, './src/main/resources'),
      'node_modules'
    ],
  }
});
```
_&nbsp; Webpack configuration for creating bundle.js and bundle.css_

The `entry` and `output` sections define the entry and output points for processing our JS/CSS files. Entry point is exclusively the `resources/js/app.js` file, but it **references other SCSS files and will automatically split them up for the build**. With the configuration of the `devtool` parameter, source maps are available during development to see our actual written sources in the browser's DevTools.

In the `optimization` and `module` sections we define the processing rules. By using Babel we can write modern ES6 JavaScript, which will be transformed for maximum compatibility. We write our styles with SCSS, but it is transformed and minimized to CSS for later delivery.

## Setting up DevServer for development

The DevServer is **recommended during development to be able to track all changes directly in the browser**. Otherwise, the build would have to be run after each change. The DevServer can be configured with the following extension to our `webpack.config.js`.

```js
{
  // ...
  devServer: {
    port: 8081,
    compress: true,
    watchFiles: [
      'src/main/resources/templates/**/*.html',
      'src/main/resources/js/**/*.js',
      'src/main/resources/scss/**/*.scss'
    ],
    proxy: {
      '**': {
        target: 'http://localhost:8080',
        secure: false,
        prependPath: false,
        headers: {
          'X-Devserver': '1',
        }
      }
    }
  }
}
```
_&nbsp; Extension to configure the DevServer_

With this setup, the DevServer runs on port 8081 and forwards all requests that it cannot answer itself to our Spring Boot app on port 8080. So during development, we should access our running application via `localhost:8081`. By defining `watchFiles` the **browser will reload automatically after every file change** - here the HTML extension also includes our Thymeleaf templates. This works best by setting up [hot reload for Thymeleaf](https://bootify.io/frontend/thymeleaf-templates-hot-reload.html).

The integration of our JS and CSS scripts is now done with the following extension in the `<head>` area of our HTML.

```html
<link th:if="${T(io.bootify.WebUtils).getRequest().getHeader('X-Devserver') != '1'}"
        th:href="@{/css/bundle.css}" rel="stylesheet" />
<script th:src="@{/js/bundle.js}" defer></script>
```
_&nbsp; Integrating JS/CSS files_

By using the DevServer, we can include the files like normal static resources. However, during development, the DevServer always delivers the JavaScript along with the CSS, so in this case we don't need to include the styles separately and disable them via `th:if`.

## Build integration with Maven or Gradle

In `webpack.config.js` we have defined that the processed files are written to `target/classes/static`. So they will be included in our final jar and can be accessed as a static resource.

For the integration with Maven the `frontend-maven-plugin` is required. This runs as an additional step within `mvnw package`, downloads Node.js independently and executes `npm install` and `npm run build`.

```xml
<plugin>
    <groupId>com.github.eirslett</groupId>
    <artifactId>frontend-maven-plugin</artifactId>
    <version>1.12.1</version>
    <executions>
        <execution>
            <id>nodeAndNpmSetup</id>
            <goals>
                <goal>install-node-and-npm</goal>
            </goals>
        </execution>
        <execution>
            <id>npmInstall</id>
            <goals>
                <goal>npm</goal>
            </goals>
            <configuration>
                <arguments>install</arguments>
            </configuration>
        </execution>
        <execution>
            <id>npmRunBuild</id>
            <goals>
                <goal>npm</goal>
            </goals>
            <configuration>
                <arguments>run build</arguments>
            </configuration>
        </execution>
    </executions>
    <configuration>
        <nodeVersion>v18.16.0</nodeVersion>
    </configuration>
</plugin>
```
_&nbsp; Configuration of the frontend-maven-plugin for Webpack_

For Gradle, the Node plugin is used, which also executes `npm run build`. By configuring `inputs` and `outputs` we enable caching and the steps are skipped during `gradlew build` **if there have been no changes to the source files**.

```gradle
plugins {
    // ...
    id 'com.github.node-gradle.node' version '3.6.0'
}

// ...

node {
    download.set(true)
    version.set('18.16.0')
}

task npmRunBuild(type: NpmTask) {
    args = ['run', 'build']
    dependsOn npmInstall

    inputs.files(fileTree('node_modules'))
    inputs.files(fileTree('src/main/resources'))
    inputs.file('package.json')
    inputs.file('webpack.config.js')
    outputs.dir("$buildDir/resources/main/static")
}

processResources {
    dependsOn npmRunBuild
}
```
_&nbsp; Integration of the frontend build into the Gradle build_

With this we have **configured Webpack for its usage in our Spring Boot app**! The DevServer supports us during development, and with the right plugin the JS/CSS files are prepared and integrated into our fat jar.

Bootify can be used to initialize Spring Boot applications with their own database schema and frontend. Here, **Webpack can be chosen with Bootstrap or Tailwind CSS**. In the Professional Plan advanced features like multi-module projects and Spring Security are available.

[**&raquo; Start Project on Bootify.io**](https://bootify.io/)
&nbsp;


### Further readings

[Install Node.js](https://nodejs.org/en/)   
[Frontend Maven Plugin](https://github.com/eirslett/frontend-maven-plugin)   
[Gradle Plugin for Node](https://github.com/node-gradle/gradle-node-plugin)