Static asset management
Static asset management using yarn and webpack
While the standard way of including static assets like javascript, css files in src/resources
still currently works, this method of copy/pasting source files can be prone to mistakes, and hard to update and maintain. We propose a more maintainable way of packaging and deploying dependencies using yarn v1 and Webpack 5.
In our example we have a simple javascript with a jquery-ui dependency to include in one of our sample helloworld components. The full source is available at asset-webpack-example github repository. You can also view this commit to see all changes done to switch to webpack.
Creating an entrypoint
The first thing we need to do is organize our own code that we want to execute when loading our component and use it as an entry point when creating a webpack bundle. We can then use ES6 imports to refer to our dependencies.
In our example, we are adding a button that can toggle to show/hide a div using jquery-ui dependency. We can add the following source in our src/javascript
folder:
- jqueryWebpackDemo.js
- style.css
And refactor our embedded javascript code from this to this:
import 'jquery-ui';
import './style.css';
function main() {
$(function () {
console.log('jquery version: ' + $.fn.jquery);
$('button').on('click', function () {
$('.fold').toggle('fold', 1000);
});
});
}
main();
Notice how we do not import jquery here. We will come back to this when setting up webpack config using ProvidePlugin
.
Now that we’ve refactored our source, we can then proceed to setup webpack in order to build our bundle that we can then include in our jsp.
Setting up webpack for the project
The goal here is to create a webpack bundle of all our static assets in src/main/resources/javascript/apps
folder to be able to refer to them in our jsp.
To start, add the frontend-maven-plugin to your pom file:
<plugin>
<groupId>com.github.eirslett</groupId>
<artifactId>frontend-maven-plugin</artifactId>
<version>1.13.4</version>
<executions>
<execution>
<id>npm install node and yarn</id>
<phase>generate-resources</phase>
<goals>
<goal>install-node-and-yarn</goal>
</goals>
<configuration>
<nodeVersion>v18.18.0</nodeVersion>
<yarnVersion>v1.22.19</yarnVersion>
</configuration>
</execution>
<execution>
<id>yarn install</id>
<phase>generate-resources</phase>
<goals>
<goal>yarn</goal>
</goals>
</execution>
<execution>
<id>yarn post-install</id>
<phase>generate-resources</phase>
<goals>
<goal>yarn</goal>
</goals>
<configuration>
<arguments>${yarn.arguments}</arguments>
</configuration>
</execution>
</executions>
</plugin>
Also, optionally include a production mode in your properties for some bundle optimization:
<properties>
<yarn.arguments>build:production</yarn.arguments>
</properties>
Declaring dependencies
Next, create a package.json file and add any required dependencies. Here, we are importing jquery and jquery-ui modules (full file contents here). Specifically, we want to add our webpack config imports as devDependencies, and our package imports in dependencies. We can use the command yarn add <dependencies> instead of manually editing the package.json
file. Here, we’ve added yarn add jquery jquery-ui latest packages:
{
"dependencies": {
"jquery": "^3.7",
"jquery-ui": "^1.13.2"
}
}
Creating resource bundle using webpack
To create the bundle that we can refer to in our jsp, we then add a webpack.config.js. We assume that all webpack dependencies have been added as devDependencies in our package.json
.
The first section is to add an entry point from where our source is, which in this case is jqueryWebpackDemo.js
entry: {
main: {
import: path.resolve(__dirname, 'src/javascript/jqueryWebpackDemo'),
}
}
Next, we define the output of our compiled bundle which in this case will have the file name main.asset-webpack-example.bundle.js
and will be generated in our javascript/apps
folder:
output: {
path: path.resolve(__dirname, 'src/main/resources/javascript/apps'),
filename: `[name].asset-webpack-example.bundle.js`,
chunkFilename: '[name].asset-webpack-example.[chunkhash:6].js'
}
[hash]
template can also be added in the filename to generate a unique file whenever related content has changed. This can be useful for invalidating cache for the bundled resource. See https://webpack.js.org/configuration/output/#outputdevtoolmodulefilenametemplate
In order to bundle our css, we also need to add this module rule:
module: {
rules: [{
test: /\.css$/i,
use: ["style-loader","css-loader"],
}]
}
For this example, we also need to add a workaround for jquery, jquery-ui dependencies as they are not natively imported using ES6 modules. First we need to add a built-in webpack ProvidePlugin to make jquery available in the global scope (this is also why we do not need to import jquery in our entrypoint):
plugins: [
new ProvidePlugin({
'window.jQuery': 'jquery',
'window.$': 'jquery',
'jQuery': 'jquery',
'$': 'jquery',
})
]
Next we also need to add resolves in order to import jquery-ui properly:
resolve: {
alias: {
'jquery': 'jquery/src/jquery',
'jquery-ui': 'jquery-ui/dist/jquery-ui'
},
modules: [
path.resolve(__dirname, 'src/javascript'),
path.resolve(__dirname, 'node_modules')
]
}
Finally, once all the webpack setup is complete, we then need to include the newly generated bundle into the jsp:
<template:addResources
type="javascript"
resources="apps/main.asset-webpack-example.bundle.js"/>
We can then run our full mvn clean install to build the generated artifacts. You should be able to see the generated bundles under src/main/resources/javascript/apps folder (as specified in the webpack output configuration).
You should be able to verify the browser console that the jquery version matches with what is declared in the dependencies, and that the toggle button is working as before.