March 30, 2021
Estimated Post Reading Time ~

Building a simple and scaleable ultra-modern front-end build system for AEM

Front-ends for AEM pre-2017

If you've paid attention to the front-end world within AEM you've likely seen blogs to integrate grunt or gulp. These solutions are good starts, but don't solve many problems.

  • How do we write modern ES6 syntax?
  • What about linting?
  • How do we have good separation between authoring and publish clientlibs?
  • How do we support a multi-tennant lifestyle?
  • Why are we using tools that have overlap? i.e. NPM and Bower

My front-end proposal scales better than most solutions while supporting ultra-modern front-end standards.

Modern front-ends for AEM in 2017

Let's talk tools, the whys, and then about the hows.

Tools

  • Dependency management: NPM
  • Task Runner: Webpack
  • Bundler: Webpack
  • Transpiling: Babel
  • Linting: ESLint + Airbnb Style Guide
  • Syntax: ES6 & LESS
  • Templating: TBD

Whys

We're using NPM instead of Bower because there's no longer a need for Bower... most FEDs understand package.json these days.

We're using webpack because it's a decent task runner and an excellent bundler... let's not use Grunt / Gulp to then run webpack... one tool. Webpack also doesn't need to be installed globally which makes on-boarding easier.

We use Babel to transpile our ES6 into something browsers can understand. It's widely documented which, again, helps on-boarding.

We're using ESLint + Airbnb Javascript Style Guide so our code follows strict standards that are widely used and documented. Again, this helps on-boarding... the clearest path to having documentation for your own project is to leverage tools that already have exceptional docs.

ES6 is the future (technically the present), let's start using it. LESS is AEM friendly, so if we ever decide to use AEM's native LESS compiler, we're not having to re-write everything.

Templating... templating is a challenging one that I don't think has been solved sufficiently. Handlebars is a natural fit, but it misses the mark in certain contexts. If I'm doing a new AEM project today, I'm using HBS, but I'm yearning for something better.

Hows

So, how does all of this work?

The high level overview

  • We start with a simple folder for our area of concern... publish, author, theme, bu1, bu2, whatever.
  • It uses .content.xml, css.txt, and js.txt to create a proper clientlib.
  • It has src and dist folders in it.
  • Inside src are our source files to be run through eslint, babel, and webpack.
  • Our dist folder consumes the output.

About our clientlib definition

If you've never worked with AEM, you might just think it's OK to drop a script tag on your page and be done. Unfortunately, you will end up hobbling AEM functionality and make more work for your back-end developers. Wrapping your JS (minified or not) inside a proper clientlib is incredibly important and will reduce work in the long run. Certain granite views are built around consuming a namespaced clientlib, and if you wish to view the raw JS files on non-dispatched servers (i.e. 4502), BE winds up needing to write content disposition configs because AEM serves up raw JS as attachments when they're not proper clientlibs.

Proxied Clientlibs

We add a property to our clientlib definition to allow it to be proxied out to /etc.clientlibs/. This reduces folder traversal... we're no longer digging into /etc/clientlibs/ourproject and /apps/ourproject/components. Everything just sits in /apps/ourproject/clientlibs and /apps/ourproject/components.

The .content.xml

<?xml version="1.0" encoding="UTF-8"?>
<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"
allowProxy="{Boolean}true"
categories="[pugranch.common.publish]"/>

Our webpack config

The basics is that we have entries, outputs, rules, and plugins.

The entries exist in the src folder, outputs go in the dist folder, rules and plugins should be fairly readable...

/**
* Pug Ranch Client Library Build System
*/
const webpack = require('webpack');
const ExtractTextPlugin = require('extract-text-webpack-plugin');
const UglifyJSPlugin = require('uglifyjs-webpack-plugin');
const project = './jcr_root/apps/pugranch/clientlibs';
module.exports = {
entry: {
'base/publish': [
`${project}/base/publish/src/js/app.js`,
`${project}/base/publish/src/less/app.less`,
],
'base/author': [
`${project}/base/author/src/js/app.js`,
`${project}/base/author/src/less/app.less`,
],
'base/theme': [
`${project}/base/theme/src/js/app.js`,
`${project}/base/theme/src/less/app.less`,
],
},
output: {
path: `${__dirname}/jcr_root/apps/pugranch/clientlibs`,
filename: '[name]/dist/js/app.min.js',
},
module: {
rules: [
{
test: /\.js$/,
exclude: /(node_modules|bower_components)/,
use: {
loader: 'babel-loader',
options: {
presets: ['env'],
},
},
},
{
test: /\.js$/,
exclude: /(node_modules|bower_components)/,
loader: 'eslint-loader',
options: {
failOnError: true,
},
},
{
test: /\.less$/,
use: ExtractTextPlugin.extract({
use: [{
loader: 'css-loader',
options: {
url: false,
minimize: true,
},
}, {
loader: 'less-loader',
options: {
url: false,
},
}],
}),
},
],
},
plugins: [
new UglifyJSPlugin(),
new webpack.NoEmitOnErrorsPlugin(),
new ExtractTextPlugin({ filename: '[name]/dist/css/app.min.css', disable: false }),
],
stats: {
colors: true,
},
};

With our three entries (clientlibs) we have a lot of ground covered while being separated into their areas of concern:

  1. Base Publish - All structure and base-level features consumed by all parties (authors, visitors, etc.)
  2. Base Author - All structure and base-level features that are only consumed by authors. Everything from TouchUI hacks to Dialog listeners.
  3. Base Theme - All LESS / JS that can be individualized based on a tenant's requirements (think font families, margin & padding sizes, etc.)

Developer on-boarding

With one maven command, everything is pulled in and setup to start working:

mvn clean install -PautoInstallPackage

Day-to-day development

Things differ a bit here for BE and FE. BE can continue to use the maven tools they're use to. FE can switch over to start using tools they're use to:

cd ui.apps
npm run aemlocal

<new terminal tab>

aemsync

By paring our aemlocal script (defined in package.json and basically calling webpack --watch) with aemsync, we can now build anything on the front-end without any maven cruft. Want to add a new dropdown to a dialog? No problem. Want to wire up a new some new javascript functionality? No problem. All of this syncs up seamlessly to AEM with perfect separation of concerns.

Source Code

Want to see how it all works? Go check the source code.



By aem4beginner

No comments:

Post a Comment

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