AEM Patterns is a module for AEM that is intended to allow direct reuse of pattern library artifacts by AEM components.
A standard development workflow for building AEM components from a frontend pattern library involves first packaging the compiled CSS/JS artifacts into an AEM client library. From there, HTL for each component is generated manually using the pattern library's markup templates (usually written in Handlebars) as a guide. This process introduces several unwanted effects into the development lifecycle:
- Transcription of Handlebars into HTL is time consuming and error-prone. The manual nature of the process means that components from the pattern library must be fully retested once imported into AEM.
- Any bug fixes produced for the frontend patterns must be subsequently migrated into the HTL templates.
- The composition patterns available in Handlebars (e.g., a large component being composed of calls into simpler templates such as a heading or button) are difficult to replicate in HTL, often leading to markup for a single frontend pattern being repeated across several HTL files, further complicating any updates to the markup.
The overall effect of these issues is an unnecessary level of coupling between the frontend and AEM development teams.
AEM Patterns seeks to greatly reduce this coupling by allowing pattern templates to be imported as-is into AEM Client Libraries alongside CSS and JS artifacts.
- Separate concerns: Let frontend development teams take full ownership of HTML/JS/CSS, while AEM teams focus on WCM functionality
- Avoid coupling to AEM: Write templates in an implementation-agnostic language and avoid having to train frontend development teams in HTL
- Simplify local development: No need for frontend developers to maintain a local AEM instance
- Reduce retesting: Verify frontend functionality directly in the pattern library, and focus on WCM functionality in AEM
A typical HTL-based AEM component generates markup via an HTL template. This template will generally start by initializing a corresponding Sling Model with a data-sly-use
attribute. The component markup is then rendered, calling properties on the model as necessary.
HTL:
<sly data-sly-use.model="com.example.Model"></sly>
<h1>${model.heading}</h1>
Model class:
@Model(adaptables = Resource.class, resourceType = ResourceTypes.MODEL)
public class Model {
@Inject
private String heading;
public String getHeading() { return heading; }
}
AEM Patterns inverts this relationship. Developers still create a Sling Model exposing all properties required by the template. However, the model now additionally specifies the template it should be called with, using a Sling Model Exporter. Adding an @Exporter
annotation to a Sling Model automatically registers a servlet for the models resource type. This servlet responds to incoming requests by instantiating a model instance and converting that model into an output string via some custom logic.
The AEM Patterns exporter looks up the pattern template for a given model (specified via an option on the @Exporter
annotation), executes the template against the model, and returns the output. Language support is provided via plugin, using the PatternProvider service interface.
Below is the updated model class, now annotated as an @Exporter
. This exporter annotation registers a servlet serving html requests for the component (extensions = "html"
). This servlet calls the custom Exporter provided by AEM Patterns (name = ExporterTypes.PATTERN
) which looks up the desired template (@ExporterOption(name = ExporterOptions.TEMPLATE_PATH, value = "/apps/example/frontend/patterns/model.hbs")
) and calls it, supplying the model as context. The exporter delegates to the HandlebarsPatternProvider (since this plugin is registered to serve templates with an hbs
extension), which itself uses a Java Handlebars engine to render the component's output.
Model class:
@Model(adaptables = Resource.class, resourceType = ResourceTypes.MODEL)
@Exporter(name = ExporterTypes.PATTERN, extensions = "html",
options = @ExporterOption(name = ExporterOptions.TEMPLATE_PATH,
value = "/apps/example/frontend/patterns/model.hbs"))
public class Model {
@Inject
private String heading;
public String getHeading() { return heading; }
}
Handlebars:
The result is a an AEM component that doesn't require any HTL, and which instead uses frontend patterns directly from the pattern library. AEM developers can then create dialogs to expose the properties of a model to authors, or pull property values from a service, or generate them using custom logic, etc.
AEM Patterns comes with an example project demonstrating its use.
The frontend module is a maven module that wraps an npm project. This project consists of a Fractal pattern library, and a build which produces the compiled CSS and JS files. The pattern library consists of several base patterns (button, heading, link) and a single component, a carousel, which leverages the basic elements.
The wrapping Maven project kicks off the npm build, and copies the frontend artifacts and pattern templates into a ClientLibrary that is then packaged up and installed in AEM.
The Example Core module revolves around Carousel.java, the Sling Model representing an AEM Carousel component. This model uses an @Exporter annotation to generate its markup using the patterns imported into AEM by the frontend project.
The UI project generates the authoring dialogs for the Carousel component using the cq-component-maven-plugin, and also provides a page template for testing.
Projects can optionally use the provided ExportFrontendArtifactsPlugin to export frontend artifacts (JS, CSS, static files, and templates) into AEM. The plugin generates an AEM Client Library from the supplied files, which can then be installed into an AEM instance using the Content Package Maven Plugin. See The AEM Patterns example frontend project for usage.
Note that the plugin is optional -- any method for installing the pattern templates in AEM is acceptable.
AEM Patterns supports Handlebars templates out of the box. However any language with a Java templating engine could theoretically be supported with minimal effort. See HandlebarsPatternProvider.java for an example.