a lighting bolt strikes a coffee cup

Adventures in AEM: Switching from Webpack to Vite

By: Josiah Huckins - 8/23/2024
minute read


Most Adobe Experience Manager (AEM) projects depend heavily on the Adobe Maven Archetype. For years, this project has been used as the starting code template for many marketing sites, headful and headless delivery, single page apps and other solutions hosted by the AEM framework.


These days, you'd be hard pressed to find a project not using it for AEM development. The ui.frontend module has been a part of the archetype since 2019, being included with release 24. It introduced an improved way to create client libraries, aligning with more current javascript development practices. With this module you could now integrate a NodeJS/NPM workflow with your AEM client libraries and have it all conveniently packaged for you by a mvn clean install command, alongside your Java bundle and components.

Along with ui.frontend came a javascript bundler known as Webpack. Over the years, this has been the tool for bundling javascript, typescript, stylesheets and the like from source files. As of this writing, release 50 of the archetype still uses it. While it has been a great addition to the archetype derived projects, other bundling tools have been released, with more modern syntax and much faster build times. One such build tool is Vite.


Hello Vite

Vite is more akin to a dynamic module importer than just a bundler. Rich details on its purpose can be found here. Some of the features include a dev server with hot module replacement and ES module based bundling. It has great support for Vue and React (among many many others) via plugins.


Speed is Key

I've been involved in numerous AEM projects using ui.frontend over the last 5 years. One thing that has consistently been a problem for these projects is the build times. As the codebase grows, this problem is greatly increased. In the case of one client, the build times for just this module exceeded 25 minutes! That's not including the time needed to compile the Java code, run unit tests, package everything and deploy it. The build steps would take 40 minutes on average. This may not seem like a big deal, but I want speedy builds that take me from source to usable AEM solution in under 5 minutes.

Vite has the speed. To prove it I ran the npm run dev command against an AEM maven project generated using the default settings. I also ran this command against a different project with default settings, except that Webpack was replaced with Vite as the bundler. Here are the results:

Webpack


Webpack build time

Vite


Vite build time

Vite completed the build in ~20 seconds less than Webpack, an 80% reduction in build time for the same code! 25 seconds isn't much, but imagine if that were 25 minutes like my real world example above, wouldn't it be great to reduce the build time to a few minutes?


Vite Install in ui.frontend

I hope that was enough to convince you to at least experiment with the idea of switching to Vite. Here's how I set it up. Note I based a lot of this on the awesome AEM Vite plugin, but had to make some modifications to account for glob pattern matching.

This setup assumes you already have an AEM project generated from the Adobe Maven Archetype version 24 or newer. If you don't, be sure to generate a project by following the Usage section in their project README.

Before we install Vite, make sure you are using major version 18 of NodeJS or newer and major version 10 of NPM, or newer. You can ensure you are using proper versions of each by updating the frontend-maven-plugin's nodeVersion and npmVersion values. These can be updated in your maven project's parent pom.xml (located in the root of the maven project). I've set these values to use Node 21 and NPM 10:

<nodeVersion>v21.2.0</nodeVersion>
<npmVersion>10.2.0</npmVersion>

Now we are ready to begin. First, we need to remove all traces of Webpack from the ui.frontend module's package.json. Then we need to remove webpack scripts from the project and also run this terminal command from the ui.frontend directory:

npm uninstall webpack webpack-cli webpack-dev-server html-webpack-plugin

With that, we need to install vite, the vite aem plugin, vite-tsconfig-paths, and node-sass-glob-importer via this command (again from the ui.frontend directory):

npm install -D vite @aem-vite/vite-aem-plugin vite-tsconfig-paths node-sass-glob-importer


Configure Vite

Add this to the compilerOptions in tsconfig.json:

"types": ["vite/client"]

In the src/main folder, rename the webpack folder to vite.

Create a vite.config.mts script in the ui.frontend folder with these contents (this is described in detail here). Note, the publicDir path has been updated to 'src/main/vite' and Webpack has been replaced with vite in the input paths.



Also notice our custom constant projectName. This is used to define the project name, and should match the value you used when creating the AEM project from the archetype (appId parameter). In the vite plugin section (viteForAem), I've added '${projectName}/us/en' to the contentPaths array, you'll want to modify this to include any content paths you want to use with the Vite DevServer (per the documentation, be sure to exclude /content).


Handling Glob Imports

In newer AEM archetype versions, typescript is used to configure the bundled scripts and stylesheets. The main.ts and main.scss files use glob pattern matching to corral the files in various subdirectories. This is a desired approach as it means you can put site, page or component level scripts in directories under src, and have them all imported without having to explicitly declare them.

However, Vite has its own way of parsing glob patterns. If you mave modified main.ts and main.scss to not use glob patterns, you can skip the following.

To allow Vite to parse and import glob pattern matched files, modify src/main/vite/site/main.ts. Comment out or remove these lines:

import "./main.scss";

import "./**/*.js";
import "./**/*.ts";
import '../components/**/*.js';

Add this line:

const modules = import.meta.glob(['./**/*.js', './**/*.ts', '../components/**/*.js'], { eager: true })

This essentially imports the pattern matched javascript and typescript files. You'll notice we removed the main.scss import even though its not using a glob pattern. The reason for this is that the imports in main.scss also utilize glob patterns. Since they are Sass files, we handled them above, using the node-sass-glob-importer plugin in our vite.config.mts file:

import globImporter from 'node-sass-glob-importer'; ... css: { preprocessorOptions: { scss: { additionalData: `@import "src/main/vite/site/_variables.scss";`, importer: globImporter() } } }, ...

That additionalData property is used to ensure the variables file is included first. Without this line, you may see errors in other Sass files that reference the variables, when Vite uses them.
You should comment out or remove this line in main.scss:

@import 'variables';

Finally, we should update the package.json file, replacing the dev, prod and start scripts entries with these:

"dev": "vite build --mode development",
"build": "vite build",
"prod": "vite build --mode production",
"start": "vite preview",


Ready to Go!

With those tasks complete, you are ready to build your AEM frontend fast and lean! If you've used HMR in the past, you can continue using it for the immediate review of changes. We enabled its invocation when adding that start script in package.json. Just run npm run start to begin previewing your code in the browser.

Note also that the maven build command was tested with the changes above and works as expected. Tools such as Adobe's aem-clientlib-generator work with AEM Vite to prep and package your code for deployment to AEM.

Vite's speed stems from its great support of ES module handling. That being said, your project may require source changes beyond what was detailed in this post. If you intend to use Vite as your long term frontend build tool, your code should be using ECMAScript module syntax. Any legacy scripts using CommonJS or custom modularity should be replaced or refactored to support this.

As Experience Cloud products like the I/O runtime, serverless functions and Edge Delivery Services are utilized more prominently, dependence on Javascript derived frameworks and tools is increasing. Customers need to modernize their development and CI processes to keep up. I hope this post is helpful in that effort.


Comments