May 3, 2020
Estimated Post Reading Time ~

Front-End Adventures in AEM: Part I

I’ve been a Web Designer and Developer since 2003. I’ve seen things and I’ve written things. But the area of the web I’ve always found the most fascinating and interesting is the Front-End. Whether it’s debating CSS architecture/convention, finding cool new tricks with Service Workers, or playing with Houdini, the Front-End is an endless challenge where we balance priorities like design/ux, browser-support, accessibility, and SEO.

Like many purveyors of the web, I’ve benefited greatly from online blogging and Q&A forums like Stack Overflow. I’ve gleaned valuable tips and tricks on how to stretch web technologies to their limits to accomplish client objectives. However, in 2018 I landed on a platform and project unlike any before: To launch a site on top of a proprietary content management system by Adobe called “Adobe Experience Manager” or colloquially “AEM”.

What is it that makes AEM so fundamentally different for building front-ends on other platforms like Drupal/WordPress, Spring, or Apollo?

Nobody writes about the Front-End and AEM…
So here we are. The purpose of this article series is to describe my experiences working within AEM to provide modern Front-End tooling and workflows to my fellow developers. With a little ingenuity, AEM developers can have niceties like PostCSS, Webpack/Rollup, and even Hot Module Replacement (HMR).

*note* — This article series is written with AEM 6.4 in mind but the concepts should apply to future versions and even some older (6.2) Touch UI versions.

*note* — Adobe is notorious for moving their documentation around and their helpx docs and forums often have broken links. I’ll occasionally link to their docs so if the links stop working let me know and I’ll try and update the article.

The Client Library (Clientlib)
AEM ships front end assets to end-users via Client Libraries or clientlibs. It composes these clientlibs via a .content.xml file; and then optionally a css.txt file, a js.txtfile, and zero to many .css/.less and .js source files that are listed in their respective .txt file. I’ll cover these files in more detail later in this article.

*note* - While the css.txt and js.txt files aren’t required, leaving them out of a clientlib can lead to weird side effects. For example, if Clientlib A has a dependency on Clientlib B, but has no js.txt or css.txt file, Clientlib B’s JS and CSS will NOT be included when Clientlib A is loaded on a page. However, if Clientlib B depends on Clientlib C and Clientlib B does have a js.txt and css.txt file, then Clientlib C’s JS and CSS will load normally in this situation. All this to say, it’s best to include these .txt files even if they’re empty to avoid these fringe situations.

A typical clientlib folder

These files are stored in the JCR or Java Content Repository and updating the files in the JCR usually results in an updated clientlib (depending on your caching settings). For .less files, AEM processes them using the LESS compiler via Rhino. The exact Rhino and LESS version depend on your AEM version and service pack.

AEM then attempts to minify and obfuscate (JS) your files before concatenating them into their associated clientlibs. By default, this is done with YUI. It can also be configured to Google’s Closure Compiler or “none” which simply concatenates the files.

*tip* - Do not use YUI for any clientlibs. YUI is a largely antiquated tool that can eat unknown CSS selectors/rules and can blow up on ES5 rules that are perfectly valid for your target browsers. I recommend using Google Closure Compiler for AEM core component dependencies and the “none” option for any dependencies handled by Webpack or Rollup. Instead, let your bundler handle the processing in those situations.

A clientlib’s .content.xml file
A clientlib’s .content.xml file

A clientlib’s .content.xml file is kind of like the blueprint of a clientlib. Let’s break it down:

XML Namespaces: xmlns:cq and xmlns:jcr
The xmlns:cq and xmlns:jcr attributes are XML namespaces used to identify the jcr and cq properties and values used in the rest of the document. They’re often seen listed above the rest as they’re often set then forgotten. Both the namespaces above are required for setting the jcr:primaryType of the current folder.

jcr:primaryType string
For clientlibs, this needs to be cq:ClientLibraryFolder. This registers the parent folder for AEM as a Client Library.

categories string<array>
An array of categories that this clientlib belongs to. Typically, a clientlib will only have one category and you can almost think of it as a name that this clientlib will be referenced by later. However, additional categories can be supplied as well.

A common predefined category that you may see clientlibs added to is cq.authoring.dialog. This is a special category that loads for all authoring dialogs in the Touch UI editor which I’ll touch on more later. You can see all of the categories available to your AEM instance by visiting the dumplibs page (http://localhost:4502/libs/granite/ui/content/dumplibs.html) while your AEM instance is running. You may need to change your host/port depending on your configuration.

*tip* - For loading clientlibs in the editor, I’ve found it useful to have a single clientlib dedicated to the cq.authoring.dialog category and then specifying all the clientlibs I want to be loaded there in the dependencies array. This clientlib should only have a .content.xml file and empty css.txt/js.txt files. This prevents AEM from bundling all my clientlibs into a single large package for the editor and allows me to target the individual clientlibs via Hot Module Replacement.

dependencies string<array>
This is an array of categories that this clientlib relies on. For example, if you rely on a separate dependencies/vendor bundle, you would list it here. This field helps AEM determine the load order of all the clientlibs so be sure you keep your dependencies up-to-date.

embed (not pictured above) string<array>
Similar to dependencies, embed is used to specify clientlib categories that this clientlib relies on. However, unlike dependencies, embed actually merges these clientlibs together. Generally speaking, you should almost always prefer dependencies to embed. Failing to do so can result in duplicate code across clientlibs or overly bloated clientlibs. However, “embedding code is useful for providing access to libraries that are stored in secured areas of the JCR”. Think of embed as an npm require or import. Since you’re probably using some sort of Front-End build setup, just handle your embedding there.

cssProcessor and jsProcessor string<array>
The help site has a great write up about the technical details here, but I want to cover the bullet points. Basically, these two properties consist of an array of two values. The first specify what kind of processing you want to do by default, or “not in production”. The second is what it should do when the clientlib should be minified.

In general, consider setting everything to ["default:none", "min:none"] for your authored clientlibs and then set a default of cssProcessor: ["default:none", "min:none"] and jsProcessor: ["default:none", "min:gcc;compilationLevel=advanced"] in the config manager. This way, we can handle minification and obfuscation through something like Webpack for our code, and Adobe’s library clientlibs can be handled via the default configuration.

*tip* - We should always minify and obfuscate (if possible) our code for production but where we do that depends on the clientlib’s purpose. For default, you should probably always specify none. This makes it easier to debug your code while in development. For minification, the answer is a bit more nuanced:

If your clientlib has an embed — Turn on Google Closure Compiler for JS with as much obfuscation as possible. For CSS, do not use YUI for anything. To reiterate, YUI is notorious for silently eating CSS selectors and rules.

If your clientlib does not have an embed — Consider turning off everything for both CSS and JS. Instead, have Webpack, Rollup, or whatever build process you use to handle the minification and obfuscation for you. You’ll be able to achieve better results with something tailored for your code.

allowProxy boolean
If a client library is located under /apps (everything under /apps is protected), this property allows access to it via proxy servlet. You can read more here but the gist is that this property must be set if your clientlib is under /apps and it should be under /apps because it makes more sense organizationally. There’s also a strong possibility that clientlibs won’t work under /etc moving forward as Adobe shifts more and more code out of /etc.

channels (not pictured above) string<array>
This property is very useful if your team is leveraging the screens feature in AEM. This allows you to scope a client library folder to one or more screens which are great when libraries of the same category are designed for different device capabilities.

replaces (not pictured above) string
This property is most used when migrating clientlibs within the JCR but can be used to replace other existing libraries with something different. The value is the location in the JCR path to the old clientlib. The categories must also match the old clientlib’s in order for this property to take effect.

longCacheKey string
This property is poorly documented but can be very useful. It’s an optional string that allows you to fingerprint your clientlibs (i.e. clientlib.FINGERPRINT.js). It even supports placeholders in the value (i.e. ${project.version}-${buildNumber}). This does require the build-helper-maven-plugin to be configured (see examples).

*tip* - Fingerprinting your clientlibs is a performance must. An alternative to longCacheKey for fingerprinting is Versioned Clientlibs from ACS AEM Commons. The advantage of this solution is that it will hash your JS file and CSS files independently even as a part of the same clientlib unlike with longCacheKey. However, it does require an additional dependency, only supports MD5 hashes as of this writing, and requires setup to help AEM determine the hash. longCacheKey is ultimately more flexible but would require you to have separate clientlibs for JS/CSS if you wanted to provide them separate values.

Resources Folder
Your clientlibs may have external dependencies. For example, a CSS file may depend on a WOFF file for a custom font or an SVG file for a background image. Similarly, a JS file may have an asynchronous JavaScript dependency that loads in at a certain viewport location. In AEM, these are considered “resources” and belong in a resources sub-directory under the clientlib’s directory.

So if your clientlib exists here in the JCR: 
/apps/myproject/clientlibs/main
then your resource would exist here in the JCR: /apps/myproject/clientlibs/main/resources/font.woff

You could then access this font like so in your clientlib’s css file:

@font-face {
    font-family: "Open Sans";
    src: url("resources/font.woff") format("woff");
}


You can also nest additional directories underneath resources depending on your organizational needs so the above font url could be resources/fonts/font.woff.

*tip* - You can put any static file inside the resources folder, it doesn’t just have to be an asset for a clientlib to reference. For example, sometimes I’ll have large external SVG’s (like sprites) that I don’t want to be stored in the DAM because I rely on them in my components. Storing them in a clientlib’s resource folder gives me a consistent reference point that I can use anywhere.

Component Dependent Clientlibs
In an increasingly HTTP/2 world, one may be tempted to author individual clientlibs for each component and to deliver them on an as-needed basis. This can make a lot of sense, especially when dealing with multiple sites sharing components on a single AEM instance. AEM doesn’t currently support HTTP/2 for all requests but it can be configured to use HTTP/2 for content delivery.

Unfortunately, loading the component dependent clientlibs is another story. There’s currently no way to do this out of the box. As the forum article suggests there are custom solutions that may potentially solve this issue but they come with risks. One thing to highlight, however, is that this applies to CSS and JavaScript in the <head>. If you need to load component-specific JavaScript at the end of the <body> you’re probably fine to put your clientlib calls inside of your components HTL.

Finally, none of the above applies to AMD (Asynchronous Module Definition) JavaScript modules or asynchronously loaded CSS. These can live as resources to the main clientlib folder and can be pulled in just like on any other project.

extraClientLibs
In an almost complete reversal of what was just written, you CAN have component-specific JavaScript and CSS for your component’s Touch UI dialog. This can be accomplished using the extraClientLibs property on a component’s .content.xml file. This feature is locked to the editor is really unfortunate but it may be useful if you’re designing heavy dialog components.

Conclusion
Clientlib’s take a little getting used to for the first-time Front-End AEM Developer. It’s not as simple as dropping your static assets in a bin and referencing them with <script> and <style> tags. But now that we know the in’s and out’s of making clientlib’s tick we can build on that foundation to build truly inspiring and accessible experiences.

WRITTEN BY: Benjamin Solum



By aem4beginner

No comments:

Post a Comment

If you have any doubts or questions, please let us know.