Part 2
When it is time to deploy your app for production, simply run the vite build command. By default, it uses
https://vitejs.dev/guide/build.html
In the case of AEM we don’t have an HTML file as build entry point, we do have a Javascript file. In this example we specify an input set to main.ts in the vite.config.js
export default defineConfig({
build: {
outDir: 'dist/clientlib-esmodule',
manifest: true,
rollupOptions: {
output: {
assetFileNames:
'etc.clientlibs/aem-vite-demo/clientlibs/clientlib-esmodule/resources/[ext]/[name].[hash][extname]',
chunkFileNames:
'etc.clientlibs/aem-vite-demo/clientlibs/clientlib-esmodule/resources/chunks/[name].[hash].js',
entryFileNames:
'etc.clientlibs/aem-vite-demo/clientlibs/clientlib-esmodule/resources/js/[name].[hash].js',
},
input: {
app: 'src/main.ts',
},
},
}
});
Not only the input is important here, also note:
outDir: directory where Vite will write its output to. Will be used by vite-aem-clientlib-generatornpm package (see later in this post).
manifest: when set to true, the build will also generate a manifest.json file that contains a mapping of non-hashed asset filenames to their hashed versions, which can then be used by a server framework to render the correct asset links.
fileNames: allows you to specify a directory structure and naming convention that will be used for the files written to the outDir. In the case of how I have setup the AEM & Vite integration it’s important that those paths mimic the exact location of chunks living in resources folders of proxy enabled clientlibs. For example: http://localhost:4502/etc.clientlibs/aem-vite-demo/clientlibs/clientlib-esmodule/resources/js/app.405d9e8c.js
The manifest has a Record
file: proxy path pointing to the chunk file living in a /resources clientlib folder
isEntry: true if the chunk needs to be loaded directly
isDynamicEntry: true if the chunk is dynamically loaded within another chunk
imports: list of chunk names that also need to be loaded directly
dynamicImports: list of chunk names that are dynamically loaded within another chunk
css: lists of proxy paths pointing to css files living in a /resources clientlib folder
{
"src/main.ts": {
"file": "etc.clientlibs/aem-vite-demo/clientlibs/clientlib-esmodule/resources/js/app.405d9e8c.js",
"src": "src/main.ts",
"isEntry": true,
"imports": [
"_vendor.9c4555cf.js"
],
"dynamicImports": [
"src/other-module.ts"
],
"css": [
"etc.clientlibs/aem-vite-demo/clientlibs/clientlib-esmodule/resources/css/app.6c6c8de1.css"
]
},
"_vendor.9c4555cf.js": {
"file": "etc.clientlibs/aem-vite-demo/clientlibs/clientlib-esmodule/resources/chunks/vendor.9c4555cf.js"
},
"src/other-module.ts": {
"file": "etc.clientlibs/aem-vite-demo/clientlibs/clientlib-esmodule/resources/chunks/other-module.40df526e.js",
"src": "src/other-module.ts",
"isDynamicEntry": true,
"imports": [
"src/main.ts",
"_vendor.9c4555cf.js"
],
"dynamicImports": [
"src/nested-module.ts"
],
"css": [
"etc.clientlibs/aem-vite-demo/clientlibs/clientlib-esmodule/resources/css/other-module.d8b88b27.css"
]
},
"src/nested-module.ts": {
"file": "etc.clientlibs/aem-vite-demo/clientlibs/clientlib-esmodule/resources/chunks/nested-module.42a67543.js",
"src": "src/nested-module.ts",
"isDynamicEntry": true,
"css": [
"etc.clientlibs/aem-vite-demo/clientlibs/clientlib-esmodule/resources/css/nested-module.e9adb657.css"
]
}
}
I’ve created a CLI that can be used to inspect the manifest and create a modified clientlib. The clientlib will contain specific properties that can be used to generated the correct tags for the HTML.
An example:
<link rel="stylesheet" href="/assets/{{ manifest['main.js'].css }}" />
<script type="module" src="/assets/{{ manifest['main.js'].file }}"></script>
The CLI can be called using vite-aem-lib generate, the first thing it will do is look for the configuration file: vite.lib.config.js, note that at this time only the CommonJS format is supported. A configuration file for a Vite enabled multi clientlib setup can look something like this:
const path = require('path');
const buildManifest = (name) => {
return path.join(__dirname, 'dist', name, 'manifest.json');
};
const buildResourcesDir = (name) => {
return path.join(
__dirname,
'dist',
name,
'etc.clientlibs',
'aem-vite-demo',
'clientlibs',
name,
'resources'
);
};
const buildClientlibDir = (name) => {
return path.join(
__dirname,
'..',
'ui.apps',
'src',
'main',
'content',
'jcr_root',
'apps',
'aem-vite-demo',
'clientlibs',
name
);
};
const createLib = (name, categories) => {
return {
manifest: buildManifest(name),
resourcesDir: buildResourcesDir(name),
clientlibDir: buildClientlibDir(name),
categories: [...categories],
properties: {
moduleIdentifier: 'vite',
},
};
};
module.exports = {
libs: [
createLib('clientlib-esmodule', ['aem-vite-demo.esmodule']),
createLib('clientlib-esmodule-another', ['aem-vite-demo.esmodule.another']),
],
};
This is what CLI does in a nutshell:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<jcr:root xmlns:cq="http://www.day.com/jcr/cq/1.0" xmlns:jcr="http://www.jcp.org/jcr/1.0"
jcr:primaryType="cq:ClientLibraryFolder" categories="[aem-vite-demo.esmodule]"
cssProcessor="[default:none,min:none]" jsProcessor="[default:none,min:none]" allowProxy="{Boolean}true"
scripts="[/etc.clientlibs/aem-vite-demo/clientlibs/clientlib-esmodule/resources/js/app.405d9e8c.js]"
preloads="[/etc.clientlibs/aem-vite-demo/clientlibs/clientlib-esmodule/resources/chunks/vendor.9c4555cf.js]"
stylesheets="[/etc.clientlibs/aem-vite-demo/clientlibs/clientlib-esmodule/resources/css/app.6c6c8de1.css]"
moduleIdentifier="vite"/>
The .content.xml file has 3 custom properties that will be used during tag generation in the Java implementation:
scripts: “”
preloads: “”
stylesheets: “”
Note that none of the dynamic imported chunks are listed in any of these properties, they will be loaded dynamically and are not used during tag generation.
You might also find the modulepreload rel of the style tag weird, at least it was for me at the beginning. We generate directives for entry chunks and their direct imports. I hope the quotes below give you some insight:
Module-based development offers some real advantages in terms of cacheability, helping you reduce the number of bytes you need to ship to your users. The finer granularity of the code also helps with the loading story, by letting you prioritize the critical code in your application.
However, module dependencies introduce a loading problem, in that the browser needs to wait for a module to load before it finds out what its dependencies are. One way around this is by preloading the dependencies, so that the browser knows about all the files ahead of time and can keep the connection busy.
https://developers.google.com/web/updates/2017/12/modulepreload
tells the browser to download and cache a resource (like a script or a stylesheet) as soon as possible. It’s helpful when you need that resource a few seconds after loading the page, and you want to speed it up.
The browser doesn’t do anything with the resource after downloading it. Scripts aren’t executed, stylesheets aren’t applied. It’s just cached — so that when something else needs it, it’s available immediately.
https://3perf.com/blog/link-rels
So is just for modules?
In a nutshell, yes. By having a specific link type for preloading modules, we can write simple HTML without worrying about what credentials mode we’re using. The defaults just work.
https://developers.google.com/web/updates/2017/12/modulepreload
I’ve created a demo project based on the AEM archetype, you can look into the repository to see the full code and try it out yourself. Here is a small demonstration on how a multi clientlib setup using Vite looks like:
Legacy support, take a look at the Vite documentation for more info.
modulepreload polyfill as not all browsers support the link relation yet.
I hoped you liked this 2 part integration series as much as I did.
Written by Jeroen Druwé.
Our sales team is always ready to discuss a challenge you are currently struggling with and see how we can help you come up with a solution. We have an in-depth knowledge and years of experience with the Adobe platforms so get in touch and we'll happily help you build a more scaleable, adaptable and personalised experience for your customers.