Adding Babel support to an AEM instance

This exposé will outline the steps to be able to add Babel support to an existing AEM instance.

What is Babel

Babel is a compiler/processor of "futuristic" javascript and relative libraries like JSX and Flow. Babel provides you with the tools, whether it be a pre-processor (like Gulp or Grunt) plugin or a command-line interface, to compile ECMAScript 6 javascript into legacy browser compatible ECMAScript 5 javascript.

For example, one of the new ways that ES6 represents a function is like this:

let functionName = () => {
	console.log('does something');
};

While this Javascript above would run completely fine in an up-to-date version of Chrome or Firefox, this would in turn cause all sorts of syntax errors in legacy versions of IE. So what Babel does is turn this:

let functionName = () => {
	console.log('does something');
};

into this:

var functionName = function() {
	console.log('does something');
};

That's much better. While the changes are pretty subtle in this example, these changes result in valid, working Javascript versus unrecognizable, broken Javascript.

NOTE: For the sake of this example, we will be using the Babel ECMAscript 2015 preset plugin. For more information on plugins, refer to this page.

Step 1 - Setting up your Maven build script

First we are going to set up the Maven build script. This script will run during a normal Maven build.

Within your AEM installation you will have a directory within the clientlibs directory called development. It is worth noting that the names of these directories could change from project to project. This directory will house all of your SASS/SCSS, fronted-build scripts, and fonts if you have any. Create a directory called compile-js. Next we are going to create these three files and their associated contents:

.gitignore

node_modules

gulpfile.js

var gulp,
  srcFolder,
  jsBaseFolder,
  babel,
  es2015,
  rename;

gulp = require('gulp');
babel = require('gulp-babel');
es2015 = require('babel-preset-es2015');
rename = require('gulp-rename');

srcFolder = '../../';
jsBaseFolder = srcFolder + 'foot/js/';

// THIS DEFINES THE GULP TASK
gulp.task('compile-js', function() {
  return gulp.src([jsBaseFolder + '**/*.js', '!' + jsBaseFolder + '_compiled/**', '!' + jsBaseFolder + 'vendor/**'])
    .pipe(babel({
      "presets": [es2015]
    }))
    .pipe(rename({dirname: ''}))
    .pipe(gulp.dest(jsBaseFolder + '_compiled'))
});

// THIS RUNS THE TASK FIRED BY THE MAVEN BUILD
gulp.task('build', ['compile-js']);

package.json

{
  "name": "maven-babel-build",
  "version": "0.1.0",
  "description": "Gulp build that compiles ES6 javascript using Babel.",
  "main": "gulpfile.js",
  "scripts": {
    "gulp": "gulp",
    "build": "gulp build"
  },
  "author": "",
  "license": "ISC",
  "dependencies": {
    "babel-preset-es2015": "6.9.0",
    "gulp": "3.8.11",
    "gulp-babel": "6.1.2",
    "gulp-rename": "^1.2.2"
  }
}

The combination of these three files installs the necessary NodeJS dependencies, sets up the Gulp task that Maven will run during the build process, and the gitignore provides an extra layer of protection from accidentally adding the compiled files to your repository.

NOTE: You may need to change the srcFolder and jsBaseFolder based on the names and locations of your development directories.

Step 2 - Adding your compilation destination directory

Next we need to create a directory that will be location that Babel will store all of the compiled Javascript and will be served to AEM from. This directory will be created within the js directory that is located within the foot directory. Once you have found the right folder, create a new directory named _compiled (just like in the gulp file up above) and within the newly created directory, create another .gitignore file with the following contents:

*
# Except this file
!.gitignore

This will allow Git to add the directory to the repository but ignore the directory's contents. This folder has to be present of the Gulp scripts will not run.

Step 3 - Adding the Maven profile to the project's configuration

Now that we have the Gulp script set up, we now need to write a task/profile for Maven to actually run the task during it's build process. Before we add the profile, we need to make sure that the the Maven Antrun plugin is a dependency of the project. Open up the pom.xml directly within the root of the project (not the component pom.xml). Within the build -> pluginManagement -> plugins nodes, add the following snippet:

<plugin>
  <groupId>org.apache.maven.plugins</groupId>
  <artifactId>maven-antrun-plugin</artifactId>
  <version>1.7</version>
</plugin>

This adds the Maven Antrun plugin as a project dependency. Now that this is complete, we need to add the build profile that will compile the ECMAscript using Babel during a Maven build. This will need to be done within the pom.xml file that is located within the components directory.

If you go ahead and open up this file, scroll down to the profiles node, and within this node, paste the following code:

<profile>
	<id>compile-es6-javascript</id>
	<build>
	    <plugins>
	        <plugin>
	            <groupId>org.apache.maven.plugins</groupId>
	            <artifactId>maven-antrun-plugin</artifactId>
	            <executions>
	                <execution>
	                    <id>exec-gulp-compile-es6-javascript</id>
	                    <phase>generate-resources</phase>
	                    <goals>
	                        <goal>run</goal>
	                    </goals>
	                    <configuration>
	                        <tasks>
	                            <echo message="Compiling valid ECMAscript 6 with Babel" />
	                            <property name="antExecDir" value="${project.build.directory}/../src/main/content/jcr_root/etc/clientlibs/{THE PATH TO THE DEVELOPMENT DIRECTORY}/development/compile-js/" />
	                            <echo message="Exec dir: ${antExecDir}" />
	                            <exec dir="${antExecDir}" executable="npm" spawn="false" failonerror="true">
	                                <arg value="install" />
	                            </exec>
	                            <exec dir="${antExecDir}" executable="gulp" spawn="false" failonerror="true">
	                                <arg value="build" />
	                            </exec>
	                        </tasks>
	                    </configuration>
	                </execution>
	                <execution>
	                    <id>clean-compiled-javascript</id>
	                    <phase>clean</phase>
	                    <goals>
	                        <goal>run</goal>
	                    </goals>
	                    <configuration>
	                        <tasks>
	                            <echo message="Removing the old compiled EcmaScript 6 files" />
	                            <property name="antCleanDir" value="${project.build.directory}/../src/main/content/jcr_root/etc/clientlibs/{THE PATH TO THE FOOT DIRECTORY}/foot/js/_compile" />
	                            <delete file="${antCleanDir}/**" />
	                            <echo message="Done removing the old javascript" />
	                        </tasks>
	                    </configuration>
	                </execution>
	            </executions>
	        </plugin>
	    </plugins>
	</build>
</profile>

If you are using an IDE like IntelliJ or Eclipse, this will effectively add an option for you to run the "compile-es6-javascript" task during your Maven build process. Make sure you enable this option, otherwise when you refresh the page, there will not be any Javascript present.

Step 4 - Adding the profile to Jenkins (optional)

NOTE: If you don't use Jenkins for deployment automation, skip this step.

To add to a standard build

Login to Jenkins, go to the specific job that you would like to enable the newly created "compile-es6-javascript" profile for. Then open up the "Configure" page and scroll down to the Build section. It should look something like this:

Notice the "Goals and options" input. The new profile that we just created was inserted in the comma delimited list of dependencies. This will tell Jenkins to run the profile during the build process.

To add to a stage release

If you are adding this new profile to a Maven stage release, the configurations are slightly different to prevent Maven from throwing some annoying errors complaining about snapshots missing.

The value that we are going to change resides within the Build Environment section, versus the Build section that is listed above. There you will add the same "compile-es6-javascript" value to the Release goals and options input. So it will look something like this:

-Dresume=false clean release:clean release:prepare release:perform -Pinstall-into-6.1stage,aem-6.1-deps,compile-es6-javascript,autoInstallPackageAuthor -Darguments="-Pinstall-into-6.1stage,aem-6.1-deps,compile-es6-javascript,autoInstallPackageAuthor"

This is extremely important that this is only entered here to prevent Jenkins from failing due to missing snapshot .jar files.

Wrapping it up

Before we tie a bow on this, I can understand that all environments are different, paths/directories can be in different places and named differently. However I hope that the spirit of this workflow is detailed enough for you to implement in future projects.