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 - 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.
{
"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"
}
}
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
.
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'
],
}
});
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
.
{
// ...
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',
}
}
}
}
}
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.
The integration of our JS and CSS scripts is now done with the following extension in the <head>
area of our 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>
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
.
<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>
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.
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
}
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.
Further readings
Install Node.js
Frontend Maven Plugin
Gradle Plugin for Node
Top comments (3)
Nice post, just what I was looking for!
Thank you so much for sharing, this content has been so helpful.
You're welcome, good luck!