Static asset management

May 22, 2024

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.