Integrating a modern frontend in a multi tenant AEM project (part 2)
Natalia Venditto
Posted on July 12, 2019
It's time we release the second part of this series. And get to the actual frontend setup!
The frontend build module
if you haven't yet, I recommend you start by reviewing the proposed structure, by reading the first part. It is important for everything else to make sense.
As mentioned, the frontend build will be in a completely separated Maven module. Why? Well, to begin with, because then our backend counterparts can skip that part of the compilation (by, for example, implementing a Maven profile). Frontend build times are exponentially higher now, and sometimes not necessary for some backend features.
Another reason, separation of concerns. It's better to encapsulate code that does something completely specialized. Same as we do with functions, etc, right?
So we will extend our current structure by adding a new module called frontend, like this.
That makes the frontend one, a folder. But when it comes to Maven, in order for it to be a module, you need to add a pom.xml file to it. We will get to this pom file later (or rather, the one that's inside of the package 2 folders below), but now, let's focus purely in frontend matters.
The frontend module as an npm package
For maven, this will be a module. But for npm, this will be a package. So let's go ahead and make it one with npm init
. As usual, we answer a few questions and get our package.json
. Here I am assuming you have node and npm installed either globally or locally. Now we could potentially, start installing dependencies.
What dependencies?
Well, that will very much depend on the stack you choose, and the browsers you have to give support to. But since we decided in the part 1 of this series that we were going to write our javascript as ES6 and our css as .scss, we will need loaders and plugins to load, transpile/compile those languages. And if we mention loaders and plugins, is because we're using webpack, so we will need that, too. We also said we won't use a task manager such as Gulp or Grunt, but will use the npm CLI to run script directly from our package.json
So at a minimum, these are some of the node modules you will need as dev dependencies.
- webpack
- webpack-cli
- npm-run-all
to run the whole show. You will need, at minimum
to transpile your ES. And
- node-sass
to work your scss code.
Linters
Also, because you care for code quality, you will want to have due linting configuration and tools, such as
- eslint
- eslint-loader
- eslint-plugin-import
- eslint-config-airbnb-base (this is an industry standard, and the one I follow, with some tweaks, here and there)
Additionally, of course, you would want to keep an eye on your styles with
- stylelint
You will need
- postcss
- postcss-loader and
- browserslist
to run
- autoprefixer
and ensure cross browser support.
And because you will have to be traversing your structure to gather clientlib entries here and there, you will also need
- fast-glob (or glob, but fast-glob is...well, faster and lighter)
Once you have all that, run and npm install
.
NOTE: Whether you make each one a dependency or devDependency, will very much depend on if you need them at build time, or run time. Make conscious decisions!
Now you need to focus on the rest of items inside of this package. So my recommendation is that you create a series of folders to store
- configurations
- tasks
- other scripts
- utilities
So that that folder contents looks something like this
Configuration
It's time to hit the configuration scene. The configurable aspect of webpack is by far its best feature when it comes to large enterprise platforms, like the one we're discussing, it's essencial.
But since you have so many tools, and you probably want to keep concerns sparated so they're more maintainable, I encourage you to create a configs folder inside your package, and import them (or relevant members) to the webpack config, in aggregation.
That way you will have several configurations in that folder, like this:
- babel.config.js
- clientlibs.config.js
- poject.paths.config.js
- project.alias.config.js
- webpack.css.config.js
- webpack.js.config.js
- wepack.config.js
One of the most important configuration files, is where you will store all the paths to accomplish your tasks (we call it poject.paths.config.js
here. Since you have to go collect .scss and js entry files from different components and modules, you want to establish certain patterns, and then traverse your structure to fetch them.
For you to have an idea, something like this
Once the example repository is ready, you will be able to see exactly what this file looks like but it's basically a series of constants receiving the paths values to be able to retrieve all the commons and components .scss and .js files, in order to process them. Here is a gist of it
Some of those constants will receive values from the environment (hence they are called environment variables), like NODE_ENV, or whatever other information you need to process at build time.
We will learn more about this in a bit.
Another important piece of information are the aliases. Especially for webpack. You won't want to be counting dots back and forth to import your modules or its members, your utilities and common code. This is why you will want to create project aliases, that you can both use to import JS as much as to include paths for .scss
(here I will have to assume, that you are used to working with webpack, and understand what I am talking about already. If you don't, follow this link )
Utilities
As usual, utilities may be additional helper functions and snippets that are necessary to run your tasks, but are generic enough that can be called from different sources. It is a good practice to keep them in their own files, and under a utility folder.
Task time!
Now you have done some configuration and got stocked with utilities, you have to do something with all that! Common tasks you may want to perform include linting your code, compiling your .scss, implementing plugins. All those tasks will live in your tasks folder, and you will be able to execute those tasks directly from your package.json file, when executing npm run build
. Of course you may just use webpack mechanics, default or custom to initiate all those processes, but at large scale, it's not unlikely you need to perform additional operations.
And what about the other scripts?
As you've seen me mention before, I really like keeping things separated, so whatever tasks may not be strictly necessary on each run, or are very specific, like generating svg sprites, or processing other assets in any way, etc, you may want to put in this folder.
Collecting the entries from a tenants components
If you're familiar with how webpack works, you know that it basically takes an entry, processes it, and then produces an output. If you're still not familiar, please read about it here
In the case of a multi tenant project, you will have a collection of entry points to process, and you want to make sure to collect them all, according to a certain pattern (since it's basically impossible to hardcode a path in this case!). So how do you do that? Basically you use glob to traverse your tree and get those files that match, according to a pattern or naming convention, and their extension. Something like this:
Executing your tasks from your package.json
Again, if you're familiar with npm, you know that every package.json
has a scripts
property, where you can declare execution commands.
One module that is very helpful, and I recommend you install, is npm-run-all
. As explained here the npm run-script
command can only run one script at a time, so you're much better when you're able to simplify this.
Like this:
But we were talking about a frontend build in AEM!
Yes! And how do you make sure you get all the environment variables and have the frontend build running, when you (or Jenkins!) execute a maven build? Learn in the next part of this series!
Posted on July 12, 2019
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.